diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/data/Converters.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/data/Converters.kt index 35391e0..181ab88 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/data/Converters.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/data/Converters.kt @@ -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 diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/data/entities/TaskEntity.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/data/entities/TaskEntity.kt index e3e2997..83b1842 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/data/entities/TaskEntity.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/data/entities/TaskEntity.kt @@ -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 ) diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/model/Task.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/model/Task.kt index f2a81a9..80ec93a 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/model/Task.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/model/Task.kt @@ -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 ) diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/AddTaskUseCase.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/AddTaskUseCase.kt index d293da3..78faaa7 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/AddTaskUseCase.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/AddTaskUseCase.kt @@ -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, diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/UpdateTaskUseCase.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/UpdateTaskUseCase.kt index d5949cc..0773cf9 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/UpdateTaskUseCase.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/UpdateTaskUseCase.kt @@ -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, diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/MainScreen.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/MainScreen.kt index 3eb7d6f..68b01c3 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/MainScreen.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/MainScreen.kt @@ -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 + ) + //} } ) } diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskListScreen.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskListScreen.kt index 9a4ddf6..14b0200 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskListScreen.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskListScreen.kt @@ -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 diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskScreen.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskScreen.kt index 8b336f0..cfdb84c 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskScreen.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskScreen.kt @@ -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( diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskViewModel.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskViewModel.kt index 70c847a..d058ae7 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskViewModel.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskViewModel.kt @@ -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(null) + var dueDate by mutableStateOf(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