mirror of
https://github.com/wismna/DoNext.git
synced 2025-10-03 07:30:13 -04:00
Display the due date in the bottom sheet
This commit is contained in:
@@ -3,12 +3,13 @@ package com.wismna.geoffroy.donext.domain.usecase
|
||||
import com.wismna.geoffroy.donext.domain.model.Priority
|
||||
import com.wismna.geoffroy.donext.domain.model.Task
|
||||
import com.wismna.geoffroy.donext.domain.repository.TaskRepository
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
class AddTaskUseCase @Inject constructor(
|
||||
private val repository: TaskRepository
|
||||
) {
|
||||
suspend operator fun invoke(taskListId: Long, title: String, description: String?, priority: Priority) {
|
||||
suspend operator fun invoke(taskListId: Long, title: String, description: String?, priority: Priority, dueDate: Instant?) {
|
||||
repository.insertTask(
|
||||
Task(
|
||||
taskListId = taskListId,
|
||||
@@ -17,6 +18,7 @@ class AddTaskUseCase @Inject constructor(
|
||||
isDeleted = false,
|
||||
isDone = false,
|
||||
priority = priority,
|
||||
dueDate = dueDate
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@@ -3,12 +3,13 @@ package com.wismna.geoffroy.donext.domain.usecase
|
||||
import com.wismna.geoffroy.donext.domain.model.Priority
|
||||
import com.wismna.geoffroy.donext.domain.model.Task
|
||||
import com.wismna.geoffroy.donext.domain.repository.TaskRepository
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
class UpdateTaskUseCase @Inject constructor(
|
||||
private val repository: TaskRepository
|
||||
) {
|
||||
suspend operator fun invoke(taskId: Long, taskListId: Long, title: String, description: String?, priority: Priority) {
|
||||
suspend operator fun invoke(taskId: Long, taskListId: Long, title: String, description: String?, priority: Priority, dueDate: Instant?) {
|
||||
repository.updateTask(
|
||||
Task(
|
||||
id = taskId,
|
||||
@@ -18,6 +19,7 @@ class UpdateTaskUseCase @Inject constructor(
|
||||
isDeleted = false,
|
||||
isDone = false,
|
||||
priority = priority,
|
||||
dueDate = dueDate
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@@ -7,21 +7,37 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Clear
|
||||
import androidx.compose.material.icons.filled.DateRange
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.DatePicker
|
||||
import androidx.compose.material3.DatePickerDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberDatePickerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskViewModel
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -30,6 +46,7 @@ fun TaskBottomSheet(
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val titleFocusRequester = remember { FocusRequester() }
|
||||
var showDatePicker by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
titleFocusRequester.requestFocus()
|
||||
@@ -54,7 +71,7 @@ fun TaskBottomSheet(
|
||||
.focusRequester(titleFocusRequester),
|
||||
isError = viewModel.title.isEmpty(),
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Spacer(Modifier.height(12.dp))
|
||||
|
||||
// --- Description ---
|
||||
OutlinedTextField(
|
||||
@@ -64,15 +81,67 @@ fun TaskBottomSheet(
|
||||
maxLines = 3,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Spacer(Modifier.height(12.dp))
|
||||
|
||||
// --- Priority ---
|
||||
Text("Priority", style = MaterialTheme.typography.labelLarge)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
SingleChoiceSegmentedButton(
|
||||
value = viewModel.priority,
|
||||
onValueChange = { viewModel.onPriorityChanged(it) }
|
||||
Row (
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Priority", style = MaterialTheme.typography.labelLarge)
|
||||
SingleChoiceSegmentedButton(
|
||||
value = viewModel.priority,
|
||||
onValueChange = { viewModel.onPriorityChanged(it) }
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(12.dp))
|
||||
|
||||
// --- Due Date ---
|
||||
var showDatePicker by remember { mutableStateOf(false) }
|
||||
|
||||
OutlinedTextField(
|
||||
value = viewModel.dueDate?.atZone(ZoneId.systemDefault())
|
||||
?.toLocalDate()
|
||||
?.format(DateTimeFormatter.ofPattern("MMM d, yyyy")) ?: "",
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
label = { Text("Due Date") },
|
||||
trailingIcon = {
|
||||
Row {
|
||||
if (viewModel.dueDate != null) {
|
||||
IconButton(onClick = { viewModel.onDueDateChanged(null) }) {
|
||||
Icon(Icons.Default.Clear, contentDescription = "Clear due date")
|
||||
}
|
||||
}
|
||||
IconButton(onClick = { showDatePicker = true }) {
|
||||
Icon(Icons.Default.DateRange, contentDescription = "Pick due date")
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
if (showDatePicker) {
|
||||
val datePickerState = rememberDatePickerState(
|
||||
initialSelectedDateMillis = viewModel.dueDate?.toEpochMilli()
|
||||
)
|
||||
|
||||
DatePickerDialog(
|
||||
onDismissRequest = { showDatePicker = false },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
datePickerState.selectedDateMillis?.let { viewModel.onDueDateChanged(it) }
|
||||
showDatePicker = false
|
||||
}) { Text("OK") }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showDatePicker = false }) { Text("Cancel") }
|
||||
}
|
||||
) {
|
||||
DatePicker(state = datePickerState)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
Row (modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package com.wismna.geoffroy.donext.presentation.viewmodel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -12,6 +13,7 @@ import com.wismna.geoffroy.donext.domain.usecase.DeleteTaskUseCase
|
||||
import com.wismna.geoffroy.donext.domain.usecase.UpdateTaskUseCase
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@@ -27,6 +29,8 @@ class TaskViewModel @Inject constructor(
|
||||
private set
|
||||
var priority by mutableStateOf(Priority.NORMAL)
|
||||
private set
|
||||
var dueDate by mutableStateOf<Instant?>(null)
|
||||
private set
|
||||
|
||||
private var editingTaskId: Long? = null
|
||||
private var taskListId: Long? = null
|
||||
@@ -39,6 +43,7 @@ class TaskViewModel @Inject constructor(
|
||||
title = ""
|
||||
description = ""
|
||||
priority = Priority.NORMAL
|
||||
dueDate = null
|
||||
}
|
||||
|
||||
fun startEditTask(task: Task) {
|
||||
@@ -47,20 +52,25 @@ class TaskViewModel @Inject constructor(
|
||||
title = task.name
|
||||
description = task.description ?: ""
|
||||
priority = task.priority
|
||||
dueDate = task.dueDate
|
||||
}
|
||||
|
||||
fun onTitleChanged(value: String) { title = value }
|
||||
fun onDescriptionChanged(value: String) { description = value }
|
||||
fun onPriorityChanged(value: Priority) { priority = value }
|
||||
fun onDueDateChanged(value: Long?) {
|
||||
dueDate = value?.let { Instant.ofEpochMilli(it) }
|
||||
Log.d("TaskViewModel", "onDueDateChanged -> $dueDate (millis=$value)")
|
||||
}
|
||||
|
||||
fun save(onDone: (() -> Unit)? = null) {
|
||||
fun save(onDone: (() -> Unit)? = null) {
|
||||
if (title.isBlank()) return
|
||||
|
||||
viewModelScope.launch {
|
||||
if (isEditing()) {
|
||||
updateTaskUseCase(editingTaskId!!, taskListId!!, title, description, priority)
|
||||
updateTaskUseCase(editingTaskId!!, taskListId!!, title, description, priority, dueDate)
|
||||
} else {
|
||||
createTaskUseCase(taskListId!!, title, description, priority)
|
||||
createTaskUseCase(taskListId!!, title, description, priority, dueDate)
|
||||
}
|
||||
// reset state after save
|
||||
reset()
|
||||
@@ -77,7 +87,6 @@ class TaskViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/** Optional: manual reset */
|
||||
fun reset() {
|
||||
editingTaskId = null
|
||||
taskListId = null
|
||||
|
Reference in New Issue
Block a user