Use events for task clicks

This commit is contained in:
Geoffroy Bonneville
2025-10-10 10:59:32 -04:00
parent e07f389fac
commit c3dd615d15
9 changed files with 63 additions and 45 deletions

View File

@@ -13,14 +13,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.wismna.geoffroy.donext.domain.model.Task
import com.wismna.geoffroy.donext.presentation.viewmodel.DueTodayViewModel import com.wismna.geoffroy.donext.presentation.viewmodel.DueTodayViewModel
@Composable @Composable
fun DueTodayTasksScreen( fun DueTodayTasksScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: DueTodayViewModel = hiltViewModel(), viewModel: DueTodayViewModel = hiltViewModel(),
onTaskClick: (task: Task) -> Unit
) { ) {
val tasks = viewModel.dueTodayTasks val tasks = viewModel.dueTodayTasks
@@ -41,7 +39,6 @@ fun DueTodayTasksScreen(
TaskItemScreen( TaskItemScreen(
modifier = Modifier.animateItem(), modifier = Modifier.animateItem(),
task = task, task = task,
onTaskClick = { onTaskClick(task) },
onSwipeLeft = { onSwipeLeft = {
viewModel.updateTaskDone(task.id!!) viewModel.updateTaskDone(task.id!!)
Toast.makeText(context, "Task done", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Task done", Toast.LENGTH_SHORT).show()

View File

@@ -108,6 +108,7 @@ fun AppContent(
navController.navigate(event.route) navController.navigate(event.route)
} }
is UiEvent.NavigateBack -> navController.popBackStack() is UiEvent.NavigateBack -> navController.popBackStack()
is UiEvent.EditTask -> { viewModel.showTaskSheet = true }
else -> {} else -> {}
} }
} }
@@ -207,10 +208,7 @@ fun AppContent(
} }
val taskListViewModel: TaskListViewModel = hiltViewModel(navBackStackEntry) val taskListViewModel: TaskListViewModel = hiltViewModel(navBackStackEntry)
TaskListScreen( TaskListScreen(viewModel = taskListViewModel)
viewModel = taskListViewModel,
onTaskClick = { task -> viewModel.onTaskClicked(task) }
)
} }
composable(AppDestination.ManageLists.route) { composable(AppDestination.ManageLists.route) {
@@ -220,16 +218,10 @@ fun AppContent(
) )
} }
composable(AppDestination.DueTodayList.route) { composable(AppDestination.DueTodayList.route) {
DueTodayTasksScreen ( DueTodayTasksScreen (modifier = Modifier)
modifier = Modifier,
onTaskClick = { task -> viewModel.onTaskClicked(task) }
)
} }
composable(AppDestination.RecycleBin.route) { composable(AppDestination.RecycleBin.route) {
RecycleBinScreen( RecycleBinScreen(modifier = Modifier)
modifier = Modifier,
onTaskClick = { task -> viewModel.onTaskClicked(task) }
)
} }
} }
} }

View File

@@ -29,14 +29,12 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.wismna.geoffroy.donext.domain.model.Task
import com.wismna.geoffroy.donext.presentation.viewmodel.RecycleBinViewModel import com.wismna.geoffroy.donext.presentation.viewmodel.RecycleBinViewModel
@Composable @Composable
fun RecycleBinScreen( fun RecycleBinScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: RecycleBinViewModel = hiltViewModel(), viewModel: RecycleBinViewModel = hiltViewModel(),
onTaskClick: (task: Task) -> Unit
) { ) {
val tasks = viewModel.deletedTasks val tasks = viewModel.deletedTasks
@@ -77,7 +75,6 @@ fun RecycleBinScreen(
TaskItemScreen( TaskItemScreen(
modifier = Modifier.animateItem(), modifier = Modifier.animateItem(),
task = item.task, task = item.task,
onTaskClick = { onTaskClick(item.task) },
onSwipeLeft = { onSwipeLeft = {
viewModel.restore(item.task.id!!) viewModel.restore(item.task.id!!)
Toast.makeText(context, "Task restored", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Task restored", Toast.LENGTH_SHORT).show()

View File

@@ -37,6 +37,7 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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.Priority
import com.wismna.geoffroy.donext.domain.model.Task import com.wismna.geoffroy.donext.domain.model.Task
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskItemViewModel import com.wismna.geoffroy.donext.presentation.viewmodel.TaskItemViewModel
@@ -45,11 +46,11 @@ import com.wismna.geoffroy.donext.presentation.viewmodel.TaskItemViewModel
fun TaskItemScreen( fun TaskItemScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
task: Task, task: Task,
onTaskClick: (taskId: Long) -> Unit, viewModel: TaskItemViewModel = hiltViewModel<TaskItemViewModel>(),
onSwipeLeft: () -> Unit, onSwipeLeft: () -> Unit,
onSwipeRight: () -> Unit onSwipeRight: () -> Unit
) { ) {
val viewModel = TaskItemViewModel(task) viewModel.populateTask(task)
// TODO: change this // TODO: change this
val dismissState = rememberSwipeToDismissBoxState( val dismissState = rememberSwipeToDismissBoxState(
confirmValueChange = { confirmValueChange = {
@@ -78,7 +79,7 @@ fun TaskItemScreen(
) )
Card( Card(
modifier = modifier, modifier = modifier,
onClick = { onTaskClick(viewModel.id) }, onClick = { viewModel.onTaskClicked(task) },
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainer, containerColor = MaterialTheme.colorScheme.surfaceContainer,
), ),
@@ -109,7 +110,7 @@ fun TaskItemScreen(
) { ) {
// Title // Title
Text( Text(
text = viewModel.name, text = viewModel.name!!,
fontSize = 18.sp, fontSize = 18.sp,
style = baseStyle, style = baseStyle,
modifier = Modifier modifier = Modifier
@@ -150,7 +151,7 @@ fun TaskItemScreen(
) { ) {
if (!viewModel.description.isNullOrBlank()) { if (!viewModel.description.isNullOrBlank()) {
Text( Text(
text = viewModel.description, text = viewModel.description!!,
color = MaterialTheme.colorScheme.tertiary, color = MaterialTheme.colorScheme.tertiary,
style = baseStyle.copy( style = baseStyle.copy(
fontSize = MaterialTheme.typography.bodyMedium.fontSize, fontSize = MaterialTheme.typography.bodyMedium.fontSize,

View File

@@ -15,13 +15,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.wismna.geoffroy.donext.domain.model.Task
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskListViewModel import com.wismna.geoffroy.donext.presentation.viewmodel.TaskListViewModel
@Composable @Composable
fun TaskListScreen( fun TaskListScreen(
viewModel: TaskListViewModel = hiltViewModel<TaskListViewModel>(), viewModel: TaskListViewModel = hiltViewModel<TaskListViewModel>(),
onTaskClick: (Task) -> Unit) { ) {
val tasks = viewModel.tasks val tasks = viewModel.tasks
// Split tasks into active and done // Split tasks into active and done
@@ -43,7 +42,6 @@ fun TaskListScreen(
TaskItemScreen( TaskItemScreen(
modifier = Modifier.animateItem(), modifier = Modifier.animateItem(),
task = task, task = task,
onTaskClick = { onTaskClick(task) },
onSwipeLeft = { onSwipeLeft = {
viewModel.updateTaskDone(task.id!!, true) viewModel.updateTaskDone(task.id!!, true)
Toast.makeText(context, "Task done", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Task done", Toast.LENGTH_SHORT).show()
@@ -74,7 +72,6 @@ fun TaskListScreen(
TaskItemScreen( TaskItemScreen(
modifier = Modifier.animateItem(), modifier = Modifier.animateItem(),
task = task, task = task,
onTaskClick = { onTaskClick(task) },
onSwipeLeft = { onSwipeLeft = {
viewModel.updateTaskDone(task.id!!, false) viewModel.updateTaskDone(task.id!!, false)
Toast.makeText(context, "Task in progress", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Task in progress", Toast.LENGTH_SHORT).show()

View File

@@ -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<UiEvent>(replay = 1)
val events = _events.asSharedFlow()
suspend fun send(event: UiEvent) {
_events.emit(event)
}
}

View File

@@ -7,7 +7,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.navigation.NavBackStackEntry import androidx.navigation.NavBackStackEntry
import com.wismna.geoffroy.donext.domain.model.AppDestination 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.domain.usecase.GetTaskListsUseCase
import com.wismna.geoffroy.donext.presentation.ui.events.UiEvent import com.wismna.geoffroy.donext.presentation.ui.events.UiEvent
import com.wismna.geoffroy.donext.presentation.ui.events.UiEventBus 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() { fun onDismissTaskSheet() {
showTaskSheet = false showTaskSheet = false
viewModelScope.launch { viewModelScope.launch {

View File

@@ -1,31 +1,42 @@
package com.wismna.geoffroy.donext.presentation.viewmodel 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.extension.toLocalDate
import com.wismna.geoffroy.donext.domain.model.Priority import com.wismna.geoffroy.donext.domain.model.Priority
import com.wismna.geoffroy.donext.domain.model.Task 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.LocalDate
import java.time.ZoneId import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle import java.time.format.FormatStyle
import java.time.format.TextStyle import java.time.format.TextStyle
import java.util.Locale import java.util.Locale
import javax.inject.Inject
class TaskItemViewModel(task: Task) { @HiltViewModel
val id: Long = task.id!! class TaskItemViewModel @Inject constructor(
val name: String = task.name private val uiEventBus: UiEventBus
val description: String? = task.description ): ViewModel() {
val isDone: Boolean = task.isDone var id: Long? = null
val isDeleted: Boolean = task.isDeleted var name: String? = null
val priority: Priority = task.priority 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 today: LocalDate = LocalDate.now(ZoneId.systemDefault())
val isOverdue: Boolean = task.dueDate?.let { millis -> val isOverdue: Boolean = dueDate?.let { millis ->
val dueDate = millis.toLocalDate() val dueDate = millis.toLocalDate()
dueDate.isBefore(today) dueDate.isBefore(today)
} ?: false } ?: false
val dueDateText: String? = task.dueDate?.let { formatDueDate(it) } val dueDateText: String? = dueDate?.let { formatDueDate(it) }
private fun formatDueDate(dueMillis: Long): String { private fun formatDueDate(dueMillis: Long): String {
val dueDate = dueMillis.toLocalDate() val dueDate = dueMillis.toLocalDate()
@@ -40,4 +51,19 @@ class TaskItemViewModel(task: Task) {
dueDate.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.getDefault())) 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))
}
}
} }

View File

@@ -45,8 +45,8 @@ class TaskViewModel @Inject constructor(
viewModelScope.launch { viewModelScope.launch {
uiEventBus.events.collect { event -> uiEventBus.events.collect { event ->
when (event) { when (event) {
is UiEvent.EditTask -> startEditTask(event.task)
is UiEvent.CreateNewTask -> startNewTask(event.taskListId) is UiEvent.CreateNewTask -> startNewTask(event.taskListId)
is UiEvent.EditTask -> startEditTask(event.task)
is UiEvent.CloseTask -> reset() is UiEvent.CloseTask -> reset()
else -> {} else -> {}
} }