mirror of
https://github.com/wismna/DoNext.git
synced 2025-10-03 07:30:13 -04:00
Simplify due date data type
Due date displays proper date Overdue tasks display as red
This commit is contained in:
@@ -2,19 +2,8 @@ package com.wismna.geoffroy.donext.data
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import com.wismna.geoffroy.donext.domain.model.Priority
|
||||
import java.time.Instant
|
||||
|
||||
class Converters {
|
||||
@TypeConverter
|
||||
fun fromTimestamp(value: Long?): Instant? {
|
||||
return value?.let { Instant.ofEpochMilli(it) }
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun instantToTimestamp(instant: Instant?): Long? {
|
||||
return instant?.toEpochMilli()
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromPriority(priority: Priority): Int = priority.value
|
||||
|
||||
|
@@ -4,7 +4,6 @@ import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.wismna.geoffroy.donext.domain.model.Priority
|
||||
import java.time.Instant
|
||||
|
||||
@Entity(tableName = "tasks")
|
||||
data class TaskEntity(
|
||||
@@ -20,5 +19,5 @@ data class TaskEntity(
|
||||
@ColumnInfo(name = "task_list_id")
|
||||
val taskListId: Long,
|
||||
@ColumnInfo(name = "due_date")
|
||||
val dueDate: Instant? = null
|
||||
val dueDate: Long? = null
|
||||
)
|
||||
|
@@ -1,7 +1,5 @@
|
||||
package com.wismna.geoffroy.donext.domain.model
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
data class Task(
|
||||
val id: Long? = null,
|
||||
val name: String,
|
||||
@@ -10,5 +8,5 @@ data class Task(
|
||||
val isDone: Boolean,
|
||||
val isDeleted: Boolean,
|
||||
val taskListId: Long,
|
||||
val dueDate: Instant? = null
|
||||
val dueDate: Long? = null
|
||||
)
|
||||
|
@@ -3,13 +3,12 @@ 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, dueDate: Instant?) {
|
||||
suspend operator fun invoke(taskListId: Long, title: String, description: String?, priority: Priority, dueDate: Long?) {
|
||||
repository.insertTask(
|
||||
Task(
|
||||
taskListId = taskListId,
|
||||
|
@@ -3,13 +3,12 @@ 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, dueDate: Instant?) {
|
||||
suspend operator fun invoke(taskId: Long, taskListId: Long, title: String, description: String?, priority: Priority, dueDate: Long?) {
|
||||
repository.updateTask(
|
||||
Task(
|
||||
id = taskId,
|
||||
|
@@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.Badge
|
||||
import androidx.compose.material3.BadgedBox
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
@@ -78,11 +80,19 @@ fun MainScreen(
|
||||
selectedDestination = index
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = destination.name,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
/*BadgedBox(
|
||||
badge = {
|
||||
if (overdueCount > 0) {
|
||||
Badge { Text(overdueCount.toString()) }
|
||||
}
|
||||
}
|
||||
) {*/
|
||||
Text(
|
||||
text = destination.name,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
//}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -28,6 +29,9 @@ 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.TaskListViewModel
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneOffset
|
||||
|
||||
@Composable
|
||||
fun TaskListScreen(viewModel: TaskListViewModel = hiltViewModel(), onTaskClick: (Task) -> Unit) {
|
||||
@@ -74,13 +78,23 @@ fun TaskItem(
|
||||
onClick: () -> Unit,
|
||||
onToggleDone: (Boolean) -> Unit
|
||||
) {
|
||||
val today = remember {
|
||||
LocalDate.now(ZoneOffset.UTC)
|
||||
}
|
||||
val isOverdue = task.dueDate?.let { millis ->
|
||||
val dueDate = Instant.ofEpochMilli(millis)
|
||||
.atZone(ZoneOffset.UTC)
|
||||
.toLocalDate()
|
||||
dueDate.isBefore(today)
|
||||
} ?: false
|
||||
|
||||
val baseStyle = MaterialTheme.typography.bodyLarge.copy(
|
||||
fontWeight = when (task.priority) {
|
||||
Priority.HIGH -> FontWeight.Bold
|
||||
Priority.NORMAL -> FontWeight.Normal
|
||||
Priority.LOW -> FontWeight.Normal
|
||||
},
|
||||
color = when (task.priority) {
|
||||
color = if (isOverdue && !task.isDone) MaterialTheme.colorScheme.error else when (task.priority) {
|
||||
Priority.HIGH -> MaterialTheme.colorScheme.onSurface
|
||||
Priority.NORMAL -> MaterialTheme.colorScheme.onSurface
|
||||
Priority.LOW -> MaterialTheme.colorScheme.onSurfaceVariant
|
||||
|
@@ -25,7 +25,6 @@ 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
|
||||
@@ -36,8 +35,10 @@ 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.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -46,7 +47,6 @@ fun TaskBottomSheet(
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val titleFocusRequester = remember { FocusRequester() }
|
||||
var showDatePicker by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
titleFocusRequester.requestFocus()
|
||||
@@ -98,11 +98,15 @@ fun TaskBottomSheet(
|
||||
|
||||
// --- Due Date ---
|
||||
var showDatePicker by remember { mutableStateOf(false) }
|
||||
val formattedDate = viewModel.dueDate?.let {
|
||||
Instant.ofEpochMilli(it)
|
||||
.atZone(ZoneOffset.UTC)
|
||||
.toLocalDate()
|
||||
.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM))
|
||||
} ?: ""
|
||||
|
||||
OutlinedTextField(
|
||||
value = viewModel.dueDate?.atZone(ZoneId.systemDefault())
|
||||
?.toLocalDate()
|
||||
?.format(DateTimeFormatter.ofPattern("MMM d, yyyy")) ?: "",
|
||||
value = formattedDate,
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
label = { Text("Due Date") },
|
||||
@@ -122,9 +126,7 @@ fun TaskBottomSheet(
|
||||
)
|
||||
|
||||
if (showDatePicker) {
|
||||
val datePickerState = rememberDatePickerState(
|
||||
initialSelectedDateMillis = viewModel.dueDate?.toEpochMilli()
|
||||
)
|
||||
val datePickerState = rememberDatePickerState(initialSelectedDateMillis = viewModel.dueDate)
|
||||
|
||||
DatePickerDialog(
|
||||
onDismissRequest = { showDatePicker = false },
|
||||
@@ -144,7 +146,9 @@ fun TaskBottomSheet(
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
Row (modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Row (
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = if (viewModel.isEditing()) Arrangement.SpaceBetween else Arrangement.End) {
|
||||
// --- Delete Button ---
|
||||
if (viewModel.isEditing()) {
|
||||
Button(
|
||||
|
@@ -1,6 +1,5 @@
|
||||
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
|
||||
@@ -13,7 +12,6 @@ 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
|
||||
@@ -29,7 +27,7 @@ class TaskViewModel @Inject constructor(
|
||||
private set
|
||||
var priority by mutableStateOf(Priority.NORMAL)
|
||||
private set
|
||||
var dueDate by mutableStateOf<Instant?>(null)
|
||||
var dueDate by mutableStateOf<Long?>(null)
|
||||
private set
|
||||
|
||||
private var editingTaskId: Long? = null
|
||||
@@ -58,10 +56,7 @@ class TaskViewModel @Inject constructor(
|
||||
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 onDueDateChanged(value: Long?) { dueDate = value }
|
||||
|
||||
fun save(onDone: (() -> Unit)? = null) {
|
||||
if (title.isBlank()) return
|
||||
|
Reference in New Issue
Block a user