From c3dd615d15d1b1c36b24b8c85ea13859c006ad5d Mon Sep 17 00:00:00 2001 From: Geoffroy Bonneville <24917789+wismna@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:59:32 -0400 Subject: [PATCH] Use events for task clicks --- .../screen/DueTodayTasksScreen.kt | 3 -- .../donext/presentation/screen/MainScreen.kt | 16 ++----- .../presentation/screen/RecycleBinScreen.kt | 3 -- .../presentation/screen/TaskItemScreen.kt | 11 ++--- .../presentation/screen/TaskListScreen.kt | 5 +-- .../presentation/ui/events/UiEventBus.kt | 16 +++++++ .../presentation/viewmodel/MainViewModel.kt | 8 ---- .../viewmodel/TaskItemViewModel.kt | 44 +++++++++++++++---- .../presentation/viewmodel/TaskViewModel.kt | 2 +- 9 files changed, 63 insertions(+), 45 deletions(-) create mode 100644 donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/ui/events/UiEventBus.kt diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/DueTodayTasksScreen.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/DueTodayTasksScreen.kt index f5b63a7..ed3cfff 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/DueTodayTasksScreen.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/DueTodayTasksScreen.kt @@ -13,14 +13,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import com.wismna.geoffroy.donext.domain.model.Task import com.wismna.geoffroy.donext.presentation.viewmodel.DueTodayViewModel @Composable fun DueTodayTasksScreen( modifier: Modifier = Modifier, viewModel: DueTodayViewModel = hiltViewModel(), - onTaskClick: (task: Task) -> Unit ) { val tasks = viewModel.dueTodayTasks @@ -41,7 +39,6 @@ fun DueTodayTasksScreen( TaskItemScreen( modifier = Modifier.animateItem(), task = task, - onTaskClick = { onTaskClick(task) }, onSwipeLeft = { viewModel.updateTaskDone(task.id!!) Toast.makeText(context, "Task done", Toast.LENGTH_SHORT).show() diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/MainScreen.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/MainScreen.kt index c2ea5c0..a472650 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/MainScreen.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/MainScreen.kt @@ -108,6 +108,7 @@ fun AppContent( navController.navigate(event.route) } is UiEvent.NavigateBack -> navController.popBackStack() + is UiEvent.EditTask -> { viewModel.showTaskSheet = true } else -> {} } } @@ -207,10 +208,7 @@ fun AppContent( } val taskListViewModel: TaskListViewModel = hiltViewModel(navBackStackEntry) - TaskListScreen( - viewModel = taskListViewModel, - onTaskClick = { task -> viewModel.onTaskClicked(task) } - ) + TaskListScreen(viewModel = taskListViewModel) } composable(AppDestination.ManageLists.route) { @@ -220,16 +218,10 @@ fun AppContent( ) } composable(AppDestination.DueTodayList.route) { - DueTodayTasksScreen ( - modifier = Modifier, - onTaskClick = { task -> viewModel.onTaskClicked(task) } - ) + DueTodayTasksScreen (modifier = Modifier) } composable(AppDestination.RecycleBin.route) { - RecycleBinScreen( - modifier = Modifier, - onTaskClick = { task -> viewModel.onTaskClicked(task) } - ) + RecycleBinScreen(modifier = Modifier) } } } diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/RecycleBinScreen.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/RecycleBinScreen.kt index cd1c21c..f89de08 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/RecycleBinScreen.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/RecycleBinScreen.kt @@ -29,14 +29,12 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import com.wismna.geoffroy.donext.domain.model.Task import com.wismna.geoffroy.donext.presentation.viewmodel.RecycleBinViewModel @Composable fun RecycleBinScreen( modifier: Modifier = Modifier, viewModel: RecycleBinViewModel = hiltViewModel(), - onTaskClick: (task: Task) -> Unit ) { val tasks = viewModel.deletedTasks @@ -77,7 +75,6 @@ fun RecycleBinScreen( TaskItemScreen( modifier = Modifier.animateItem(), task = item.task, - onTaskClick = { onTaskClick(item.task) }, onSwipeLeft = { viewModel.restore(item.task.id!!) Toast.makeText(context, "Task restored", Toast.LENGTH_SHORT).show() diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskItemScreen.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskItemScreen.kt index a610e67..192989a 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskItemScreen.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskItemScreen.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import com.wismna.geoffroy.donext.domain.model.Priority import com.wismna.geoffroy.donext.domain.model.Task import com.wismna.geoffroy.donext.presentation.viewmodel.TaskItemViewModel @@ -45,11 +46,11 @@ import com.wismna.geoffroy.donext.presentation.viewmodel.TaskItemViewModel fun TaskItemScreen( modifier: Modifier = Modifier, task: Task, - onTaskClick: (taskId: Long) -> Unit, + viewModel: TaskItemViewModel = hiltViewModel(), onSwipeLeft: () -> Unit, onSwipeRight: () -> Unit ) { - val viewModel = TaskItemViewModel(task) + viewModel.populateTask(task) // TODO: change this val dismissState = rememberSwipeToDismissBoxState( confirmValueChange = { @@ -78,7 +79,7 @@ fun TaskItemScreen( ) Card( modifier = modifier, - onClick = { onTaskClick(viewModel.id) }, + onClick = { viewModel.onTaskClicked(task) }, colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surfaceContainer, ), @@ -109,7 +110,7 @@ fun TaskItemScreen( ) { // Title Text( - text = viewModel.name, + text = viewModel.name!!, fontSize = 18.sp, style = baseStyle, modifier = Modifier @@ -150,7 +151,7 @@ fun TaskItemScreen( ) { if (!viewModel.description.isNullOrBlank()) { Text( - text = viewModel.description, + text = viewModel.description!!, color = MaterialTheme.colorScheme.tertiary, style = baseStyle.copy( fontSize = MaterialTheme.typography.bodyMedium.fontSize, diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskListScreen.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskListScreen.kt index f971d58..e0dbbb7 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskListScreen.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskListScreen.kt @@ -15,13 +15,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import com.wismna.geoffroy.donext.domain.model.Task import com.wismna.geoffroy.donext.presentation.viewmodel.TaskListViewModel @Composable fun TaskListScreen( viewModel: TaskListViewModel = hiltViewModel(), - onTaskClick: (Task) -> Unit) { +) { val tasks = viewModel.tasks // Split tasks into active and done @@ -43,7 +42,6 @@ fun TaskListScreen( TaskItemScreen( modifier = Modifier.animateItem(), task = task, - onTaskClick = { onTaskClick(task) }, onSwipeLeft = { viewModel.updateTaskDone(task.id!!, true) Toast.makeText(context, "Task done", Toast.LENGTH_SHORT).show() @@ -74,7 +72,6 @@ fun TaskListScreen( TaskItemScreen( modifier = Modifier.animateItem(), task = task, - onTaskClick = { onTaskClick(task) }, onSwipeLeft = { viewModel.updateTaskDone(task.id!!, false) Toast.makeText(context, "Task in progress", Toast.LENGTH_SHORT).show() diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/ui/events/UiEventBus.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/ui/events/UiEventBus.kt new file mode 100644 index 0000000..f2a62c1 --- /dev/null +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/ui/events/UiEventBus.kt @@ -0,0 +1,16 @@ +package com.wismna.geoffroy.donext.presentation.ui.events + +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class UiEventBus @Inject constructor() { + private val _events = MutableSharedFlow(replay = 1) + val events = _events.asSharedFlow() + + suspend fun send(event: UiEvent) { + _events.emit(event) + } +} diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/MainViewModel.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/MainViewModel.kt index 3e6c85e..a6a5470 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/MainViewModel.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/MainViewModel.kt @@ -7,7 +7,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.NavBackStackEntry import com.wismna.geoffroy.donext.domain.model.AppDestination -import com.wismna.geoffroy.donext.domain.model.Task import com.wismna.geoffroy.donext.domain.usecase.GetTaskListsUseCase import com.wismna.geoffroy.donext.presentation.ui.events.UiEvent import com.wismna.geoffroy.donext.presentation.ui.events.UiEventBus @@ -68,13 +67,6 @@ class MainViewModel @Inject constructor( } } - fun onTaskClicked(task: Task) { - showTaskSheet = true - viewModelScope.launch { - uiEventBus.send(UiEvent.EditTask(task)) - } - } - fun onDismissTaskSheet() { showTaskSheet = false viewModelScope.launch { diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskItemViewModel.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskItemViewModel.kt index 491905d..8aa1cc6 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskItemViewModel.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskItemViewModel.kt @@ -1,31 +1,42 @@ package com.wismna.geoffroy.donext.presentation.viewmodel +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.wismna.geoffroy.donext.domain.extension.toLocalDate import com.wismna.geoffroy.donext.domain.model.Priority import com.wismna.geoffroy.donext.domain.model.Task +import com.wismna.geoffroy.donext.presentation.ui.events.UiEvent +import com.wismna.geoffroy.donext.presentation.ui.events.UiEventBus +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import java.time.LocalDate import java.time.ZoneId import java.time.format.DateTimeFormatter import java.time.format.FormatStyle import java.time.format.TextStyle import java.util.Locale +import javax.inject.Inject -class TaskItemViewModel(task: Task) { - val id: Long = task.id!! - val name: String = task.name - val description: String? = task.description - val isDone: Boolean = task.isDone - val isDeleted: Boolean = task.isDeleted - val priority: Priority = task.priority +@HiltViewModel +class TaskItemViewModel @Inject constructor( + private val uiEventBus: UiEventBus +): ViewModel() { + var id: Long? = null + var name: String? = null + var description: String? = null + var dueDate: Long? = null + var isDone: Boolean = false + var isDeleted: Boolean = false + var priority: Priority = Priority.NORMAL val today: LocalDate = LocalDate.now(ZoneId.systemDefault()) - val isOverdue: Boolean = task.dueDate?.let { millis -> + val isOverdue: Boolean = dueDate?.let { millis -> val dueDate = millis.toLocalDate() dueDate.isBefore(today) } ?: false - val dueDateText: String? = task.dueDate?.let { formatDueDate(it) } + val dueDateText: String? = dueDate?.let { formatDueDate(it) } private fun formatDueDate(dueMillis: Long): String { val dueDate = dueMillis.toLocalDate() @@ -40,4 +51,19 @@ class TaskItemViewModel(task: Task) { dueDate.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.getDefault())) } } + + fun populateTask(task: Task) { + id = task.id!! + name = task.name + description = task.description + isDone = task.isDone + isDeleted = task.isDeleted + priority = task.priority + } + + fun onTaskClicked(task: Task) { + viewModelScope.launch { + uiEventBus.send(UiEvent.EditTask(task)) + } + } } \ No newline at end of file diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskViewModel.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskViewModel.kt index dae0c12..1f8a903 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskViewModel.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskViewModel.kt @@ -45,8 +45,8 @@ class TaskViewModel @Inject constructor( viewModelScope.launch { uiEventBus.events.collect { event -> when (event) { - is UiEvent.EditTask -> startEditTask(event.task) is UiEvent.CreateNewTask -> startNewTask(event.taskListId) + is UiEvent.EditTask -> startEditTask(event.task) is UiEvent.CloseTask -> reset() else -> {} }