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">
|
<project version="4">
|
||||||
<component name="deploymentTargetSelector">
|
<component name="deploymentTargetSelector">
|
||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
|
||||||
</SelectionState>
|
|
||||||
<SelectionState runConfigName="donextv2">
|
<SelectionState runConfigName="donextv2">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
<SelectionState runConfigName="overdueCount_correctlyCalculated()">
|
<SelectionState runConfigName="overdueCount_correctlyCalculated()">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
|
<SelectionState runConfigName="donext">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
</selectionStates>
|
</selectionStates>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -37,7 +37,8 @@ fun DueTodayTasksScreen(
|
|||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
task = task,
|
task = task,
|
||||||
onSwipeLeft = { viewModel.updateTaskDone(task.id!!) },
|
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
|
// TODO: add confirmation dialog
|
||||||
viewModel.deleteForever(item.task.id!!)
|
viewModel.deleteForever(item.task.id!!)
|
||||||
Toast.makeText(context, "Task deleted", Toast.LENGTH_SHORT).show()
|
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.Text
|
||||||
import androidx.compose.material3.rememberSwipeToDismissBoxState
|
import androidx.compose.material3.rememberSwipeToDismissBoxState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
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.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
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TaskItemScreen(
|
fun TaskItemScreen(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
task: Task,
|
task: Task,
|
||||||
viewModel: TaskItemViewModel = hiltViewModel<TaskItemViewModel>(),
|
|
||||||
onSwipeLeft: () -> Unit,
|
onSwipeLeft: () -> Unit,
|
||||||
onSwipeRight: () -> Unit
|
onSwipeRight: () -> Unit,
|
||||||
|
onTaskClick: (task: Task) -> Unit
|
||||||
) {
|
) {
|
||||||
viewModel.populateTask(task)
|
val viewModel = TaskItemViewModel(task)
|
||||||
// TODO: change this
|
|
||||||
val dismissState = rememberSwipeToDismissBoxState(
|
val dismissState = rememberSwipeToDismissBoxState(
|
||||||
confirmValueChange = {
|
|
||||||
when (it) {
|
|
||||||
SwipeToDismissBoxValue.StartToEnd -> { onSwipeRight() }
|
|
||||||
SwipeToDismissBoxValue.EndToStart -> { onSwipeLeft() }
|
|
||||||
SwipeToDismissBoxValue.Settled -> return@rememberSwipeToDismissBoxState false
|
|
||||||
}
|
|
||||||
return@rememberSwipeToDismissBoxState true
|
|
||||||
},
|
|
||||||
// positional threshold of 25%
|
// positional threshold of 25%
|
||||||
positionalThreshold = { it * .25f }
|
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(
|
val baseStyle = MaterialTheme.typography.bodyLarge.copy(
|
||||||
fontWeight = when (viewModel.priority) {
|
fontWeight = when (viewModel.priority) {
|
||||||
Priority.HIGH -> FontWeight.Bold
|
Priority.HIGH -> FontWeight.Bold
|
||||||
@@ -79,7 +90,7 @@ fun TaskItemScreen(
|
|||||||
)
|
)
|
||||||
Card(
|
Card(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onClick = { viewModel.onTaskClicked(task) },
|
onClick = { onTaskClick(task) },
|
||||||
colors = CardDefaults.cardColors(
|
colors = CardDefaults.cardColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
),
|
),
|
||||||
@@ -110,7 +121,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
|
||||||
@@ -123,7 +134,7 @@ fun TaskItemScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Due date badge
|
// Due date badge
|
||||||
viewModel.dueDate?.let { dueMillis ->
|
viewModel.dueDateText?.let { dueMillis ->
|
||||||
Badge(
|
Badge(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(
|
.align(
|
||||||
@@ -134,7 +145,7 @@ fun TaskItemScreen(
|
|||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(start = 1.dp, end = 1.dp),
|
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,
|
color = if (viewModel.isOverdue) Color.White else MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
style = MaterialTheme.typography.bodySmall
|
style = MaterialTheme.typography.bodySmall
|
||||||
)
|
)
|
||||||
@@ -151,7 +162,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,
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ fun TaskListScreen(
|
|||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
task = task,
|
task = task,
|
||||||
onSwipeLeft = { viewModel.updateTaskDone(task.id!!, true) },
|
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,
|
task = task,
|
||||||
onSwipeLeft = { viewModel.updateTaskDone(task.id!!, false) },
|
onSwipeLeft = { viewModel.updateTaskDone(task.id!!, false) },
|
||||||
onSwipeRight = { viewModel.deleteTask(task.id!!) },
|
onSwipeRight = { viewModel.deleteTask(task.id!!) },
|
||||||
|
onTaskClick = { viewModel.onTaskClicked(task) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,6 +36,12 @@ class DueTodayViewModel @Inject constructor(
|
|||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onTaskClicked(task: Task) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
uiEventBus.send(UiEvent.EditTask(task))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun updateTaskDone(taskId: Long) {
|
fun updateTaskDone(taskId: Long) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
toggleTaskDoneUseCase(taskId, true)
|
toggleTaskDoneUseCase(taskId, true)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
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.model.TaskWithListName
|
||||||
import com.wismna.geoffroy.donext.domain.usecase.EmptyRecycleBinUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.EmptyRecycleBinUseCase
|
||||||
import com.wismna.geoffroy.donext.domain.usecase.GetDeletedTasksUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.GetDeletedTasksUseCase
|
||||||
@@ -34,6 +35,12 @@ class RecycleBinViewModel @Inject constructor(
|
|||||||
loadDeletedTasks()
|
loadDeletedTasks()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onTaskClicked(task: Task) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
uiEventBus.send(UiEvent.EditTask(task))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun loadDeletedTasks() {
|
fun loadDeletedTasks() {
|
||||||
getDeletedTasksUseCase()
|
getDeletedTasksUseCase()
|
||||||
.onEach { tasks ->
|
.onEach { tasks ->
|
||||||
|
|||||||
@@ -1,68 +1,43 @@
|
|||||||
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
|
|
||||||
|
|
||||||
@HiltViewModel
|
class TaskItemViewModel(task: Task) {
|
||||||
class TaskItemViewModel @Inject constructor(
|
val id: Long = task.id!!
|
||||||
private val uiEventBus: UiEventBus
|
val name: String = task.name
|
||||||
): ViewModel() {
|
val description: String? = task.description
|
||||||
var id: Long? = null
|
val isDone: Boolean = task.isDone
|
||||||
var name: String? = null
|
val isDeleted: Boolean = task.isDeleted
|
||||||
var description: String? = null
|
val priority: Priority = task.priority
|
||||||
var dueDate: String? = null
|
|
||||||
var isDone: Boolean = false
|
|
||||||
var isDeleted: Boolean = false
|
|
||||||
var priority: Priority = Priority.NORMAL
|
|
||||||
var isOverdue: Boolean = false
|
|
||||||
val today: LocalDate = LocalDate.now(ZoneId.systemDefault())
|
val today: LocalDate = LocalDate.now(ZoneId.systemDefault())
|
||||||
|
|
||||||
fun populateTask(task: Task) {
|
val isOverdue: Boolean = task.dueDate?.let { millis ->
|
||||||
id = task.id!!
|
val dueDate = millis.toLocalDate()
|
||||||
name = task.name
|
dueDate.isBefore(today)
|
||||||
description = task.description
|
} ?: false
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onTaskClicked(task: Task) {
|
val dueDateText: String? = task.dueDate?.let { formatDueDate(it) }
|
||||||
viewModelScope.launch {
|
|
||||||
uiEventBus.send(UiEvent.EditTask(task))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun formatDueDate(dueMillis: Long): String {
|
private fun formatDueDate(dueMillis: Long): String {
|
||||||
val dueDateLocal = dueMillis.toLocalDate()
|
val dueDate = dueMillis.toLocalDate()
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
dueDateLocal.isEqual(today) -> "Today"
|
dueDate.isEqual(today) -> "Today"
|
||||||
dueDateLocal.isEqual(today.plusDays(1)) -> "Tomorrow"
|
dueDate.isEqual(today.plusDays(1)) -> "Tomorrow"
|
||||||
dueDateLocal.isEqual(today.minusDays(1)) -> "Yesterday"
|
dueDate.isEqual(today.minusDays(1)) -> "Yesterday"
|
||||||
dueDateLocal.isAfter(today) && dueDateLocal.isBefore(today.plusDays(7)) ->
|
dueDate.isAfter(today) && dueDate.isBefore(today.plusDays(7)) ->
|
||||||
dueDateLocal.dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.getDefault())
|
dueDate.dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.getDefault())
|
||||||
else ->
|
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)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onTaskClicked(task: Task) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
uiEventBus.send(UiEvent.EditTask(task))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun updateTaskDone(taskId: Long, isDone: Boolean) {
|
fun updateTaskDone(taskId: Long, isDone: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
toggleTaskDoneUseCase(taskId, isDone)
|
toggleTaskDoneUseCase(taskId, isDone)
|
||||||
|
|||||||
Reference in New Issue
Block a user