mirror of
https://github.com/wismna/DoNext.git
synced 2025-12-06 00:02:40 -05:00
Reverted TaskItemVM to a dumb state-like VM
Task Edit events are now carried to the parent composable
This commit is contained in:
6
.idea/deploymentTargetSelector.xml
generated
6
.idea/deploymentTargetSelector.xml
generated
@@ -2,15 +2,15 @@
|
||||
<project version="4">
|
||||
<component name="deploymentTargetSelector">
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="donextv2">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="overdueCount_correctlyCalculated()">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="donext">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
||||
@@ -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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TaskItemViewModel>(),
|
||||
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,
|
||||
|
||||
@@ -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) }
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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 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()))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user