feat: [#8] vue Today (TodayScreen, TodayViewModel, groupement par projet)
This commit is contained in:
@@ -0,0 +1,96 @@
|
|||||||
|
package com.planify.mobile.ui.today
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Today
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.planify.mobile.domain.model.Task
|
||||||
|
import com.planify.mobile.ui.components.EmptyState
|
||||||
|
import com.planify.mobile.ui.components.SectionHeader
|
||||||
|
import com.planify.mobile.ui.components.TaskRow
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TodayScreen(
|
||||||
|
onTaskClick: (Task) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
viewModel: TodayViewModel = hiltViewModel(),
|
||||||
|
) {
|
||||||
|
val state by viewModel.uiState.collectAsState()
|
||||||
|
val collapsedSections = remember { mutableStateOf(setOf<String>()) }
|
||||||
|
|
||||||
|
if (state.totalCount == 0 && !state.isLoading) {
|
||||||
|
EmptyState(
|
||||||
|
icon = Icons.Outlined.Today,
|
||||||
|
title = "Rien pour aujourd'hui",
|
||||||
|
subtitle = "Profitez de votre journée !",
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(modifier = modifier.fillMaxSize()) {
|
||||||
|
if (state.overdueTasks.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
SectionHeader(
|
||||||
|
name = "En retard",
|
||||||
|
taskCount = state.overdueTasks.size,
|
||||||
|
collapsed = "overdue" in collapsedSections.value,
|
||||||
|
onToggleCollapse = {
|
||||||
|
collapsedSections.value = collapsedSections.value.toggle("overdue")
|
||||||
|
},
|
||||||
|
onAddTask = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if ("overdue" !in collapsedSections.value) {
|
||||||
|
items(state.overdueTasks, key = { "overdue_${it.id}" }) { task ->
|
||||||
|
TaskRow(
|
||||||
|
task = task,
|
||||||
|
onCheckedChange = { viewModel.toggleTask(task) },
|
||||||
|
onClick = { onTaskClick(task) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item { HorizontalDivider(Modifier.padding(horizontal = 16.dp)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
state.tasksByProject.forEach { (projectName, tasks) ->
|
||||||
|
item(key = "header_$projectName") {
|
||||||
|
SectionHeader(
|
||||||
|
name = projectName,
|
||||||
|
taskCount = tasks.size,
|
||||||
|
collapsed = projectName in collapsedSections.value,
|
||||||
|
onToggleCollapse = {
|
||||||
|
collapsedSections.value = collapsedSections.value.toggle(projectName)
|
||||||
|
},
|
||||||
|
onAddTask = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (projectName !in collapsedSections.value) {
|
||||||
|
items(tasks, key = { it.id }) { task ->
|
||||||
|
TaskRow(
|
||||||
|
task = task,
|
||||||
|
onCheckedChange = { viewModel.toggleTask(task) },
|
||||||
|
onClick = { onTaskClick(task) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Set<String>.toggle(key: String) =
|
||||||
|
if (contains(key)) minus(key) else plus(key)
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.planify.mobile.ui.today
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.planify.mobile.domain.model.Task
|
||||||
|
import com.planify.mobile.domain.repository.ProjectRepository
|
||||||
|
import com.planify.mobile.domain.repository.TaskRepository
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
data class TodayUiState(
|
||||||
|
val tasksByProject: Map<String, List<Task>> = emptyMap(),
|
||||||
|
val overdueTasks: List<Task> = emptyList(),
|
||||||
|
val totalCount: Int = 0,
|
||||||
|
val isLoading: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class TodayViewModel @Inject constructor(
|
||||||
|
private val taskRepository: TaskRepository,
|
||||||
|
private val projectRepository: ProjectRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
val uiState = combine(
|
||||||
|
taskRepository.getTodayTasks(),
|
||||||
|
taskRepository.getOverdueTasks(),
|
||||||
|
projectRepository.getAllProjects(),
|
||||||
|
) { today, overdue, projects ->
|
||||||
|
val projectMap = projects.associateBy { it.id }
|
||||||
|
val grouped = today
|
||||||
|
.groupBy { task -> projectMap[task.projectId]?.name ?: task.projectId }
|
||||||
|
TodayUiState(
|
||||||
|
tasksByProject = grouped,
|
||||||
|
overdueTasks = overdue,
|
||||||
|
totalCount = today.size + overdue.size,
|
||||||
|
)
|
||||||
|
}.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(5_000),
|
||||||
|
initialValue = TodayUiState(isLoading = true),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun toggleTask(task: Task) {
|
||||||
|
viewModelScope.launch { taskRepository.checkTask(task.id, !task.checked) }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user