feat: [#8] vue Today (TodayScreen, TodayViewModel, groupement par projet)

This commit is contained in:
2026-06-06 06:05:03 +02:00
parent ce22d49824
commit 65a54af66a
2 changed files with 146 additions and 0 deletions
@@ -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) }
}
}