mirror of
https://github.com/wismna/DoNext.git
synced 2025-12-06 00:02:40 -05:00
Restore due date on task list items
Replace toast with snackbar with an undo action
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
package com.wismna.geoffroy.donext.presentation.screen
|
package com.wismna.geoffroy.donext.presentation.screen
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@@ -10,7 +9,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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.presentation.viewmodel.DueTodayViewModel
|
import com.wismna.geoffroy.donext.presentation.viewmodel.DueTodayViewModel
|
||||||
@@ -31,7 +29,6 @@ fun DueTodayTasksScreen(
|
|||||||
Text("Nothing due today !")
|
Text("Nothing due today !")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val context = LocalContext.current
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = modifier.padding(8.dp)
|
modifier = modifier.padding(8.dp)
|
||||||
) {
|
) {
|
||||||
@@ -39,15 +36,8 @@ fun DueTodayTasksScreen(
|
|||||||
TaskItemScreen(
|
TaskItemScreen(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
task = task,
|
task = task,
|
||||||
onSwipeLeft = {
|
onSwipeLeft = { viewModel.updateTaskDone(task.id!!) },
|
||||||
viewModel.updateTaskDone(task.id!!)
|
onSwipeRight = { viewModel.deleteTask(task.id!!) }
|
||||||
Toast.makeText(context, "Task done", Toast.LENGTH_SHORT).show()
|
|
||||||
},
|
|
||||||
onSwipeRight = {
|
|
||||||
viewModel.deleteTask(task.id!!)
|
|
||||||
Toast.makeText(context, "Task moved to recycle bin", Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ import androidx.compose.material3.LocalContentColor
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalNavigationDrawer
|
import androidx.compose.material3.ModalNavigationDrawer
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.SnackbarResult
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
@@ -53,7 +57,6 @@ import com.wismna.geoffroy.donext.domain.model.AppDestination
|
|||||||
import com.wismna.geoffroy.donext.presentation.ui.events.UiEvent
|
import com.wismna.geoffroy.donext.presentation.ui.events.UiEvent
|
||||||
import com.wismna.geoffroy.donext.presentation.viewmodel.MainViewModel
|
import com.wismna.geoffroy.donext.presentation.viewmodel.MainViewModel
|
||||||
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskListViewModel
|
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskListViewModel
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -63,7 +66,6 @@ fun MainScreen(
|
|||||||
) {
|
) {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
if (viewModel.isLoading) {
|
if (viewModel.isLoading) {
|
||||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
@@ -88,7 +90,7 @@ fun MainScreen(
|
|||||||
},
|
},
|
||||||
drawerState = drawerState
|
drawerState = drawerState
|
||||||
) {
|
) {
|
||||||
AppContent(viewModel = viewModel, navController = navController, scope = scope, drawerState = drawerState)
|
AppContent(viewModel = viewModel, navController = navController, drawerState = drawerState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,9 +99,11 @@ fun AppContent(
|
|||||||
modifier : Modifier = Modifier,
|
modifier : Modifier = Modifier,
|
||||||
viewModel: MainViewModel,
|
viewModel: MainViewModel,
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
scope: CoroutineScope,
|
|
||||||
drawerState: DrawerState
|
drawerState: DrawerState
|
||||||
) {
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.uiEventBus.events.collectLatest { event ->
|
viewModel.uiEventBus.events.collectLatest { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
@@ -109,13 +113,24 @@ fun AppContent(
|
|||||||
}
|
}
|
||||||
is UiEvent.NavigateBack -> navController.popBackStack()
|
is UiEvent.NavigateBack -> navController.popBackStack()
|
||||||
is UiEvent.EditTask -> { viewModel.showTaskSheet = true }
|
is UiEvent.EditTask -> { viewModel.showTaskSheet = true }
|
||||||
else -> {}
|
is UiEvent.ShowUndoSnackbar -> {
|
||||||
|
val result = snackbarHostState.showSnackbar(
|
||||||
|
message = event.message,
|
||||||
|
actionLabel = "Undo",
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
if (result == SnackbarResult.ActionPerformed) {
|
||||||
|
event.undoAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier.background(MaterialTheme.colorScheme.primaryContainer),
|
modifier = modifier.background(MaterialTheme.colorScheme.primaryContainer),
|
||||||
containerColor = Color.Transparent,
|
containerColor = Color.Transparent,
|
||||||
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(viewModel.currentDestination.title) },
|
title = { Text(viewModel.currentDestination.title) },
|
||||||
|
|||||||
@@ -75,11 +75,9 @@ fun RecycleBinScreen(
|
|||||||
TaskItemScreen(
|
TaskItemScreen(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
task = item.task,
|
task = item.task,
|
||||||
onSwipeLeft = {
|
onSwipeLeft = { viewModel.restore(item.task.id!!) },
|
||||||
viewModel.restore(item.task.id!!)
|
|
||||||
Toast.makeText(context, "Task restored", Toast.LENGTH_SHORT).show()
|
|
||||||
},
|
|
||||||
onSwipeRight = {
|
onSwipeRight = {
|
||||||
|
// 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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ fun TaskItemScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Due date badge
|
// Due date badge
|
||||||
viewModel.dueDateText?.let { dueMillis ->
|
viewModel.dueDate?.let { dueMillis ->
|
||||||
Badge(
|
Badge(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(
|
.align(
|
||||||
@@ -134,7 +134,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.dueDateText,
|
text = viewModel.dueDate!!,
|
||||||
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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.wismna.geoffroy.donext.presentation.screen
|
package com.wismna.geoffroy.donext.presentation.screen
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@@ -9,26 +9,39 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
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.presentation.viewmodel.TaskListViewModel
|
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskListViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TaskListScreen(
|
fun TaskListScreen(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
viewModel: TaskListViewModel = hiltViewModel<TaskListViewModel>(),
|
viewModel: TaskListViewModel = hiltViewModel<TaskListViewModel>(),
|
||||||
) {
|
) {
|
||||||
val tasks = viewModel.tasks
|
val tasks = viewModel.tasks
|
||||||
|
|
||||||
|
if (tasks.isEmpty()) {
|
||||||
|
// Placeholder when recycle bin is empty
|
||||||
|
Column (
|
||||||
|
modifier = modifier.fillMaxSize().padding(10.dp),
|
||||||
|
Arrangement.Center
|
||||||
|
) {
|
||||||
|
Text("Nothing here!", modifier.fillMaxWidth(), textAlign = TextAlign.Center)
|
||||||
|
Text("Add tasks with the Create Task button", modifier.fillMaxWidth(), textAlign = TextAlign.Center)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Split tasks into active and done
|
// Split tasks into active and done
|
||||||
val (active, done) = remember(tasks) {
|
val (active, done) = remember(tasks) {
|
||||||
tasks.partition { !it.isDone }
|
tasks.partition { !it.isDone }
|
||||||
}
|
}
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(8.dp),
|
contentPadding = PaddingValues(8.dp),
|
||||||
@@ -42,14 +55,8 @@ fun TaskListScreen(
|
|||||||
TaskItemScreen(
|
TaskItemScreen(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
task = task,
|
task = task,
|
||||||
onSwipeLeft = {
|
onSwipeLeft = { viewModel.updateTaskDone(task.id!!, true) },
|
||||||
viewModel.updateTaskDone(task.id!!, true)
|
onSwipeRight = { viewModel.deleteTask(task.id!!) }
|
||||||
Toast.makeText(context, "Task done", Toast.LENGTH_SHORT).show()
|
|
||||||
},
|
|
||||||
onSwipeRight = {
|
|
||||||
viewModel.deleteTask(task.id!!)
|
|
||||||
Toast.makeText(context, "Task moved to recycle bin", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,14 +79,8 @@ fun TaskListScreen(
|
|||||||
TaskItemScreen(
|
TaskItemScreen(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
task = task,
|
task = task,
|
||||||
onSwipeLeft = {
|
onSwipeLeft = { viewModel.updateTaskDone(task.id!!, false) },
|
||||||
viewModel.updateTaskDone(task.id!!, false)
|
onSwipeRight = { viewModel.deleteTask(task.id!!) },
|
||||||
Toast.makeText(context, "Task in progress", Toast.LENGTH_SHORT).show()
|
|
||||||
},
|
|
||||||
onSwipeRight = {
|
|
||||||
viewModel.deleteTask(task.id!!)
|
|
||||||
Toast.makeText(context, "Task moved to recycle bin", Toast.LENGTH_SHORT).show()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import com.wismna.geoffroy.donext.domain.model.Task
|
|||||||
sealed class UiEvent {
|
sealed class UiEvent {
|
||||||
data class Navigate(val route: String) : UiEvent()
|
data class Navigate(val route: String) : UiEvent()
|
||||||
data object NavigateBack : UiEvent()
|
data object NavigateBack : UiEvent()
|
||||||
data class ShowSnackbar(val message: String) : UiEvent()
|
|
||||||
|
|
||||||
data class EditTask(val task: Task) : UiEvent()
|
data class EditTask(val task: Task) : UiEvent()
|
||||||
data class CreateNewTask(val taskListId: Long) : UiEvent()
|
data class CreateNewTask(val taskListId: Long) : UiEvent()
|
||||||
data object CloseTask : UiEvent()
|
data object CloseTask : UiEvent()
|
||||||
|
data class ShowUndoSnackbar(
|
||||||
|
val message: String,
|
||||||
|
val undoAction: () -> Unit
|
||||||
|
) : UiEvent()
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,8 @@ import com.wismna.geoffroy.donext.domain.model.Task
|
|||||||
import com.wismna.geoffroy.donext.domain.usecase.GetDueTodayTasksUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.GetDueTodayTasksUseCase
|
||||||
import com.wismna.geoffroy.donext.domain.usecase.ToggleTaskDeletedUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.ToggleTaskDeletedUseCase
|
||||||
import com.wismna.geoffroy.donext.domain.usecase.ToggleTaskDoneUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.ToggleTaskDoneUseCase
|
||||||
|
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 dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@@ -17,16 +19,17 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class DueTodayViewModel @Inject constructor(
|
class DueTodayViewModel @Inject constructor(
|
||||||
getDueTodayTasks: GetDueTodayTasksUseCase,
|
getDueTodayTasksUseCase: GetDueTodayTasksUseCase,
|
||||||
private val toggleTaskDeleted: ToggleTaskDeletedUseCase,
|
private val toggleTaskDeletedUseCase: ToggleTaskDeletedUseCase,
|
||||||
private val toggleTaskDone: ToggleTaskDoneUseCase
|
private val toggleTaskDoneUseCase: ToggleTaskDoneUseCase,
|
||||||
|
private val uiEventBus: UiEventBus
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
var dueTodayTasks by mutableStateOf<List<Task>>(emptyList())
|
var dueTodayTasks by mutableStateOf<List<Task>>(emptyList())
|
||||||
private set
|
private set
|
||||||
|
|
||||||
init {
|
init {
|
||||||
getDueTodayTasks()
|
getDueTodayTasksUseCase()
|
||||||
.onEach { tasks ->
|
.onEach { tasks ->
|
||||||
dueTodayTasks = tasks
|
dueTodayTasks = tasks
|
||||||
}
|
}
|
||||||
@@ -35,12 +38,35 @@ class DueTodayViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun updateTaskDone(taskId: Long) {
|
fun updateTaskDone(taskId: Long) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
toggleTaskDone(taskId, true)
|
toggleTaskDoneUseCase(taskId, true)
|
||||||
|
|
||||||
|
uiEventBus.send(
|
||||||
|
UiEvent.ShowUndoSnackbar(
|
||||||
|
message = "Task done",
|
||||||
|
undoAction = {
|
||||||
|
viewModelScope.launch {
|
||||||
|
toggleTaskDoneUseCase(taskId, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteTask(taskId: Long) {
|
fun deleteTask(taskId: Long) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
toggleTaskDeleted(taskId, true)
|
toggleTaskDeletedUseCase(taskId, true)
|
||||||
|
|
||||||
|
uiEventBus.send(
|
||||||
|
UiEvent.ShowUndoSnackbar(
|
||||||
|
message = "Task moved to trash",
|
||||||
|
undoAction = {
|
||||||
|
viewModelScope.launch {
|
||||||
|
toggleTaskDeletedUseCase(taskId, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,10 +46,10 @@ class MainViewModel @Inject constructor(
|
|||||||
AppDestination.ManageLists +
|
AppDestination.ManageLists +
|
||||||
AppDestination.RecycleBin +
|
AppDestination.RecycleBin +
|
||||||
AppDestination.DueTodayList
|
AppDestination.DueTodayList
|
||||||
isLoading = false
|
|
||||||
if (startDestination == AppDestination.ManageLists && destinations.isNotEmpty()) {
|
if (startDestination == AppDestination.ManageLists && destinations.isNotEmpty()) {
|
||||||
startDestination = destinations.first()
|
startDestination = destinations.first()
|
||||||
}
|
}
|
||||||
|
isLoading = false
|
||||||
}
|
}
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ 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.AppDestination
|
|
||||||
import com.wismna.geoffroy.donext.domain.model.TaskListWithOverdue
|
import com.wismna.geoffroy.donext.domain.model.TaskListWithOverdue
|
||||||
import com.wismna.geoffroy.donext.domain.usecase.GetDueTodayTasksUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.GetDueTodayTasksUseCase
|
||||||
import com.wismna.geoffroy.donext.domain.usecase.GetTaskListsWithOverdueUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.GetTaskListsWithOverdueUseCase
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import com.wismna.geoffroy.donext.domain.usecase.EmptyRecycleBinUseCase
|
|||||||
import com.wismna.geoffroy.donext.domain.usecase.GetDeletedTasksUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.GetDeletedTasksUseCase
|
||||||
import com.wismna.geoffroy.donext.domain.usecase.PermanentlyDeleteTaskUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.PermanentlyDeleteTaskUseCase
|
||||||
import com.wismna.geoffroy.donext.domain.usecase.ToggleTaskDeletedUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.ToggleTaskDeletedUseCase
|
||||||
|
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 dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@@ -18,10 +20,11 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class RecycleBinViewModel @Inject constructor(
|
class RecycleBinViewModel @Inject constructor(
|
||||||
private val getDeletedTasks: GetDeletedTasksUseCase,
|
private val getDeletedTasksUseCase: GetDeletedTasksUseCase,
|
||||||
private val restoreTask: ToggleTaskDeletedUseCase,
|
private val toggleTaskDeletedUseCase: ToggleTaskDeletedUseCase,
|
||||||
private val permanentlyDeleteTask: PermanentlyDeleteTaskUseCase,
|
private val permanentlyDeleteTaskUseCase: PermanentlyDeleteTaskUseCase,
|
||||||
private val emptyRecycleBinUseCase: EmptyRecycleBinUseCase
|
private val emptyRecycleBinUseCase: EmptyRecycleBinUseCase,
|
||||||
|
private val uiEventBus: UiEventBus
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
var deletedTasks by mutableStateOf<List<TaskWithListName>>(emptyList())
|
var deletedTasks by mutableStateOf<List<TaskWithListName>>(emptyList())
|
||||||
@@ -32,7 +35,7 @@ class RecycleBinViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun loadDeletedTasks() {
|
fun loadDeletedTasks() {
|
||||||
getDeletedTasks()
|
getDeletedTasksUseCase()
|
||||||
.onEach { tasks ->
|
.onEach { tasks ->
|
||||||
deletedTasks = tasks
|
deletedTasks = tasks
|
||||||
}
|
}
|
||||||
@@ -41,14 +44,25 @@ class RecycleBinViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun restore(taskId: Long) {
|
fun restore(taskId: Long) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
restoreTask(taskId, false)
|
toggleTaskDeletedUseCase(taskId, false)
|
||||||
loadDeletedTasks()
|
loadDeletedTasks()
|
||||||
|
|
||||||
|
uiEventBus.send(
|
||||||
|
UiEvent.ShowUndoSnackbar(
|
||||||
|
message = "Task restored",
|
||||||
|
undoAction = {
|
||||||
|
viewModelScope.launch {
|
||||||
|
toggleTaskDeletedUseCase(taskId, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteForever(taskId: Long) {
|
fun deleteForever(taskId: Long) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
permanentlyDeleteTask(taskId)
|
permanentlyDeleteTaskUseCase(taskId)
|
||||||
loadDeletedTasks()
|
loadDeletedTasks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,41 +24,25 @@ class TaskItemViewModel @Inject constructor(
|
|||||||
var id: Long? = null
|
var id: Long? = null
|
||||||
var name: String? = null
|
var name: String? = null
|
||||||
var description: String? = null
|
var description: String? = null
|
||||||
var dueDate: Long? = null
|
var dueDate: String? = null
|
||||||
var isDone: Boolean = false
|
var isDone: Boolean = false
|
||||||
var isDeleted: Boolean = false
|
var isDeleted: Boolean = false
|
||||||
var priority: Priority = Priority.NORMAL
|
var priority: Priority = Priority.NORMAL
|
||||||
|
var isOverdue: Boolean = false
|
||||||
val today: LocalDate = LocalDate.now(ZoneId.systemDefault())
|
val today: LocalDate = LocalDate.now(ZoneId.systemDefault())
|
||||||
|
|
||||||
val isOverdue: Boolean = dueDate?.let { millis ->
|
|
||||||
val dueDate = millis.toLocalDate()
|
|
||||||
dueDate.isBefore(today)
|
|
||||||
} ?: false
|
|
||||||
|
|
||||||
val dueDateText: String? = dueDate?.let { formatDueDate(it) }
|
|
||||||
|
|
||||||
private fun formatDueDate(dueMillis: Long): String {
|
|
||||||
val dueDate = dueMillis.toLocalDate()
|
|
||||||
|
|
||||||
return when {
|
|
||||||
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 ->
|
|
||||||
dueDate.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.getDefault()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun populateTask(task: Task) {
|
fun populateTask(task: Task) {
|
||||||
id = task.id!!
|
id = task.id!!
|
||||||
name = task.name
|
name = task.name
|
||||||
description = task.description
|
description = task.description
|
||||||
|
dueDate = task.dueDate?.let { formatDueDate(it) }
|
||||||
isDone = task.isDone
|
isDone = task.isDone
|
||||||
isDeleted = task.isDeleted
|
isDeleted = task.isDeleted
|
||||||
priority = task.priority
|
priority = task.priority
|
||||||
|
isOverdue = task.dueDate?.let { millis ->
|
||||||
|
val dueDate = millis.toLocalDate()
|
||||||
|
dueDate.isBefore(today)
|
||||||
|
} ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onTaskClicked(task: Task) {
|
fun onTaskClicked(task: Task) {
|
||||||
@@ -66,4 +50,19 @@ class TaskItemViewModel @Inject constructor(
|
|||||||
uiEventBus.send(UiEvent.EditTask(task))
|
uiEventBus.send(UiEvent.EditTask(task))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatDueDate(dueMillis: Long): String {
|
||||||
|
val dueDateLocal = 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())
|
||||||
|
else ->
|
||||||
|
dueDateLocal.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.getDefault()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,8 @@ import com.wismna.geoffroy.donext.domain.model.Task
|
|||||||
import com.wismna.geoffroy.donext.domain.usecase.GetTasksForListUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.GetTasksForListUseCase
|
||||||
import com.wismna.geoffroy.donext.domain.usecase.ToggleTaskDeletedUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.ToggleTaskDeletedUseCase
|
||||||
import com.wismna.geoffroy.donext.domain.usecase.ToggleTaskDoneUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.ToggleTaskDoneUseCase
|
||||||
|
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 dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@@ -18,10 +20,11 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class TaskListViewModel @Inject constructor(
|
class TaskListViewModel @Inject constructor(
|
||||||
getTasks: GetTasksForListUseCase,
|
|
||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
private val toggleTaskDone: ToggleTaskDoneUseCase,
|
getTasksUseCase: GetTasksForListUseCase,
|
||||||
private val toggleTaskDeleted: ToggleTaskDeletedUseCase,
|
private val toggleTaskDoneUseCase: ToggleTaskDoneUseCase,
|
||||||
|
private val toggleTaskDeletedUseCase: ToggleTaskDeletedUseCase,
|
||||||
|
private val uiEventBus: UiEventBus
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
var tasks by mutableStateOf<List<Task>>(emptyList())
|
var tasks by mutableStateOf<List<Task>>(emptyList())
|
||||||
@@ -32,7 +35,7 @@ class TaskListViewModel @Inject constructor(
|
|||||||
private val taskListId: Long = checkNotNull(savedStateHandle["taskListId"])
|
private val taskListId: Long = checkNotNull(savedStateHandle["taskListId"])
|
||||||
|
|
||||||
init {
|
init {
|
||||||
getTasks(taskListId)
|
getTasksUseCase(taskListId)
|
||||||
.onEach { list ->
|
.onEach { list ->
|
||||||
tasks = list
|
tasks = list
|
||||||
isLoading = false
|
isLoading = false
|
||||||
@@ -42,12 +45,34 @@ class TaskListViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun updateTaskDone(taskId: Long, isDone: Boolean) {
|
fun updateTaskDone(taskId: Long, isDone: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
toggleTaskDone(taskId, isDone)
|
toggleTaskDoneUseCase(taskId, isDone)
|
||||||
|
|
||||||
|
uiEventBus.send(
|
||||||
|
UiEvent.ShowUndoSnackbar(
|
||||||
|
message = "Task done",
|
||||||
|
undoAction = {
|
||||||
|
viewModelScope.launch {
|
||||||
|
toggleTaskDoneUseCase(taskId, !isDone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun deleteTask(taskId: Long) {
|
fun deleteTask(taskId: Long) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
toggleTaskDeleted(taskId, true)
|
toggleTaskDeletedUseCase(taskId, true)
|
||||||
|
|
||||||
|
uiEventBus.send(
|
||||||
|
UiEvent.ShowUndoSnackbar(
|
||||||
|
message = "Task moved to trash",
|
||||||
|
undoAction = {
|
||||||
|
viewModelScope.launch {
|
||||||
|
toggleTaskDeletedUseCase(taskId, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user