From 30d3efa9defe1a499fe23d7e9aa94d49129625a6 Mon Sep 17 00:00:00 2001
From: Geoffroy Bonneville <24917789+wismna@users.noreply.github.com>
Date: Fri, 10 Oct 2025 18:23:07 -0400
Subject: [PATCH] Reverted TaskItemVM to a dumb state-like VM Task Edit events
are now carried to the parent composable
---
.idea/deploymentTargetSelector.xml | 6 +-
.../screen/DueTodayTasksScreen.kt | 3 +-
.../presentation/screen/RecycleBinScreen.kt | 3 +-
.../presentation/screen/TaskItemScreen.kt | 47 +++++++++-----
.../presentation/screen/TaskListScreen.kt | 5 +-
.../viewmodel/DueTodayViewModel.kt | 6 ++
.../viewmodel/RecycleBinViewModel.kt | 7 ++
.../viewmodel/TaskItemViewModel.kt | 65 ++++++-------------
.../viewmodel/TaskListViewModel.kt | 6 ++
9 files changed, 78 insertions(+), 70 deletions(-)
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index 98cd5b2..586219f 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -2,15 +2,15 @@
-
-
-
+
+
+
\ No newline at end of file
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 8ff489f..32df149 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
@@ -37,7 +37,8 @@ fun DueTodayTasksScreen(
modifier = Modifier.animateItem(),
task = task,
onSwipeLeft = { viewModel.updateTaskDone(task.id!!) },
- onSwipeRight = { viewModel.deleteTask(task.id!!) }
+ onSwipeRight = { viewModel.deleteTask(task.id!!) },
+ onTaskClick = { viewModel.onTaskClicked(task) }
)
}
}
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 1a45618..c5717cd 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
@@ -80,7 +80,8 @@ fun RecycleBinScreen(
// TODO: add confirmation dialog
viewModel.deleteForever(item.task.id!!)
Toast.makeText(context, "Task deleted", Toast.LENGTH_SHORT).show()
- }
+ },
+ onTaskClick = { viewModel.onTaskClicked(item.task) }
)
}
}
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 2000be4..5ae4240 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
@@ -28,6 +28,8 @@ import androidx.compose.material3.SwipeToDismissBoxValue
import androidx.compose.material3.Text
import androidx.compose.material3.rememberSwipeToDismissBoxState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
@@ -37,33 +39,42 @@ 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
+import kotlinx.coroutines.flow.filter
@Composable
fun TaskItemScreen(
modifier: Modifier = Modifier,
task: Task,
- viewModel: TaskItemViewModel = hiltViewModel(),
onSwipeLeft: () -> Unit,
- onSwipeRight: () -> Unit
+ onSwipeRight: () -> Unit,
+ onTaskClick: (task: Task) -> Unit
) {
- viewModel.populateTask(task)
- // TODO: change this
+ val viewModel = TaskItemViewModel(task)
+
val dismissState = rememberSwipeToDismissBoxState(
- confirmValueChange = {
- when (it) {
- SwipeToDismissBoxValue.StartToEnd -> { onSwipeRight() }
- SwipeToDismissBoxValue.EndToStart -> { onSwipeLeft() }
- SwipeToDismissBoxValue.Settled -> return@rememberSwipeToDismissBoxState false
- }
- return@rememberSwipeToDismissBoxState true
- },
// positional threshold of 25%
positionalThreshold = { it * .25f }
)
+ LaunchedEffect(dismissState) {
+ snapshotFlow { dismissState.targetValue }
+ .filter { it != SwipeToDismissBoxValue.Settled }
+ .collect { target ->
+ when (target) {
+ SwipeToDismissBoxValue.StartToEnd -> {
+ onSwipeRight()
+ dismissState.reset()
+ }
+ SwipeToDismissBoxValue.EndToStart -> {
+ onSwipeLeft()
+ }
+ else -> Unit
+ }
+ }
+ }
+
val baseStyle = MaterialTheme.typography.bodyLarge.copy(
fontWeight = when (viewModel.priority) {
Priority.HIGH -> FontWeight.Bold
@@ -79,7 +90,7 @@ fun TaskItemScreen(
)
Card(
modifier = modifier,
- onClick = { viewModel.onTaskClicked(task) },
+ onClick = { onTaskClick(task) },
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainer,
),
@@ -110,7 +121,7 @@ fun TaskItemScreen(
) {
// Title
Text(
- text = viewModel.name!!,
+ text = viewModel.name,
fontSize = 18.sp,
style = baseStyle,
modifier = Modifier
@@ -123,7 +134,7 @@ fun TaskItemScreen(
)
// Due date badge
- viewModel.dueDate?.let { dueMillis ->
+ viewModel.dueDateText?.let { dueMillis ->
Badge(
modifier = Modifier
.align(
@@ -134,7 +145,7 @@ fun TaskItemScreen(
) {
Text(
modifier = Modifier.padding(start = 1.dp, end = 1.dp),
- text = viewModel.dueDate!!,
+ text = viewModel.dueDateText,
color = if (viewModel.isOverdue) Color.White else MaterialTheme.colorScheme.onPrimaryContainer,
style = MaterialTheme.typography.bodySmall
)
@@ -151,7 +162,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 7b39dc5..029ad27 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
@@ -55,7 +55,8 @@ fun TaskListScreen(
modifier = Modifier.animateItem(),
task = task,
onSwipeLeft = { viewModel.updateTaskDone(task.id!!, true) },
- onSwipeRight = { viewModel.deleteTask(task.id!!) }
+ onSwipeRight = { viewModel.deleteTask(task.id!!) },
+ onTaskClick = { viewModel.onTaskClicked(task) }
)
}
@@ -80,8 +81,8 @@ fun TaskListScreen(
task = task,
onSwipeLeft = { viewModel.updateTaskDone(task.id!!, false) },
onSwipeRight = { viewModel.deleteTask(task.id!!) },
+ onTaskClick = { viewModel.onTaskClicked(task) }
)
}
-
}
}
\ No newline at end of file
diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/DueTodayViewModel.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/DueTodayViewModel.kt
index 948e332..6868ffa 100644
--- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/DueTodayViewModel.kt
+++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/DueTodayViewModel.kt
@@ -36,6 +36,12 @@ class DueTodayViewModel @Inject constructor(
.launchIn(viewModelScope)
}
+ fun onTaskClicked(task: Task) {
+ viewModelScope.launch {
+ uiEventBus.send(UiEvent.EditTask(task))
+ }
+ }
+
fun updateTaskDone(taskId: Long) {
viewModelScope.launch {
toggleTaskDoneUseCase(taskId, true)
diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/RecycleBinViewModel.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/RecycleBinViewModel.kt
index 6a71ab4..b79cb02 100644
--- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/RecycleBinViewModel.kt
+++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/RecycleBinViewModel.kt
@@ -5,6 +5,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.wismna.geoffroy.donext.domain.model.Task
import com.wismna.geoffroy.donext.domain.model.TaskWithListName
import com.wismna.geoffroy.donext.domain.usecase.EmptyRecycleBinUseCase
import com.wismna.geoffroy.donext.domain.usecase.GetDeletedTasksUseCase
@@ -34,6 +35,12 @@ class RecycleBinViewModel @Inject constructor(
loadDeletedTasks()
}
+ fun onTaskClicked(task: Task) {
+ viewModelScope.launch {
+ uiEventBus.send(UiEvent.EditTask(task))
+ }
+ }
+
fun loadDeletedTasks() {
getDeletedTasksUseCase()
.onEach { tasks ->
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 5be6666..491905d 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,68 +1,43 @@
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
-@HiltViewModel
-class TaskItemViewModel @Inject constructor(
- private val uiEventBus: UiEventBus
-): ViewModel() {
- var id: Long? = null
- var name: String? = null
- var description: String? = null
- var dueDate: String? = null
- var isDone: Boolean = false
- var isDeleted: Boolean = false
- var priority: Priority = Priority.NORMAL
- var isOverdue: Boolean = false
+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
+
val today: LocalDate = LocalDate.now(ZoneId.systemDefault())
- fun populateTask(task: Task) {
- id = task.id!!
- name = task.name
- description = task.description
- dueDate = task.dueDate?.let { formatDueDate(it) }
- isDone = task.isDone
- isDeleted = task.isDeleted
- priority = task.priority
- isOverdue = task.dueDate?.let { millis ->
- val dueDate = millis.toLocalDate()
- dueDate.isBefore(today)
- } ?: false
- }
+ val isOverdue: Boolean = task.dueDate?.let { millis ->
+ val dueDate = millis.toLocalDate()
+ dueDate.isBefore(today)
+ } ?: false
- fun onTaskClicked(task: Task) {
- viewModelScope.launch {
- uiEventBus.send(UiEvent.EditTask(task))
- }
- }
+ val dueDateText: String? = task.dueDate?.let { formatDueDate(it) }
private fun formatDueDate(dueMillis: Long): String {
- val dueDateLocal = dueMillis.toLocalDate()
+ val dueDate = dueMillis.toLocalDate()
return when {
- dueDateLocal.isEqual(today) -> "Today"
- dueDateLocal.isEqual(today.plusDays(1)) -> "Tomorrow"
- dueDateLocal.isEqual(today.minusDays(1)) -> "Yesterday"
- dueDateLocal.isAfter(today) && dueDateLocal.isBefore(today.plusDays(7)) ->
- dueDateLocal.dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.getDefault())
+ dueDate.isEqual(today) -> "Today"
+ dueDate.isEqual(today.plusDays(1)) -> "Tomorrow"
+ dueDate.isEqual(today.minusDays(1)) -> "Yesterday"
+ dueDate.isAfter(today) && dueDate.isBefore(today.plusDays(7)) ->
+ dueDate.dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.getDefault())
else ->
- dueDateLocal.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.getDefault()))
+ dueDate.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.getDefault()))
}
}
-
}
\ No newline at end of file
diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskListViewModel.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskListViewModel.kt
index 733fae0..f69ea4a 100644
--- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskListViewModel.kt
+++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskListViewModel.kt
@@ -43,6 +43,12 @@ class TaskListViewModel @Inject constructor(
.launchIn(viewModelScope)
}
+ fun onTaskClicked(task: Task) {
+ viewModelScope.launch {
+ uiEventBus.send(UiEvent.EditTask(task))
+ }
+ }
+
fun updateTaskDone(taskId: Long, isDone: Boolean) {
viewModelScope.launch {
toggleTaskDoneUseCase(taskId, isDone)