From 78ce5849008ca9030f2c2e5c7242c81713c51c2a Mon Sep 17 00:00:00 2001 From: Geoffroy Bonneville <24917789+wismna@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:14:33 -0400 Subject: [PATCH] Theme is now working properly Fix overdue use case Allow selecting today as due date Dates are now real badges Animate done tasks --- donextv2/src/main/AndroidManifest.xml | 2 +- .../usecase/GetTaskListsWithOverdueUseCase.kt | 10 ++- .../donext/presentation/MainActivity.kt | 3 +- .../donext/presentation/screen/MainScreen.kt | 1 - .../presentation/screen/TaskItemScreen.kt | 23 +++--- .../presentation/screen/TaskListScreen.kt | 74 ++++++++++++------- .../donext/presentation/screen/TaskScreen.kt | 5 +- .../donext/presentation/ui/theme/Color.kt | 28 +++++-- .../donext/presentation/ui/theme/Theme.kt | 73 +++++++++++------- 9 files changed, 139 insertions(+), 80 deletions(-) diff --git a/donextv2/src/main/AndroidManifest.xml b/donextv2/src/main/AndroidManifest.xml index 0da9a03..513a0bb 100644 --- a/donextv2/src/main/AndroidManifest.xml +++ b/donextv2/src/main/AndroidManifest.xml @@ -7,7 +7,7 @@ diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/GetTaskListsWithOverdueUseCase.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/GetTaskListsWithOverdueUseCase.kt index 23c4f67..3bcc155 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/GetTaskListsWithOverdueUseCase.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/GetTaskListsWithOverdueUseCase.kt @@ -3,13 +3,19 @@ package com.wismna.geoffroy.donext.domain.usecase import com.wismna.geoffroy.donext.domain.model.TaskListWithOverdue import com.wismna.geoffroy.donext.domain.repository.TaskRepository import kotlinx.coroutines.flow.Flow -import java.time.Instant +import java.time.LocalDate +import java.time.ZoneOffset import javax.inject.Inject class GetTaskListsWithOverdueUseCase @Inject constructor( private val taskRepository: TaskRepository ) { operator fun invoke(): Flow> { - return taskRepository.getTaskListsWithOverdue(Instant.parse("2025-09-15T12:00:00Z").toEpochMilli()) + return taskRepository.getTaskListsWithOverdue( + LocalDate.now() + .atStartOfDay(ZoneOffset.UTC) // or system default + .toInstant() + .toEpochMilli() + ) } } \ No newline at end of file diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/MainActivity.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/MainActivity.kt index 4f0a20d..73c0091 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/MainActivity.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/MainActivity.kt @@ -4,6 +4,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.isSystemInDarkTheme import com.wismna.geoffroy.donext.presentation.screen.MainScreen import com.wismna.geoffroy.donext.presentation.ui.theme.DoNextTheme import dagger.hilt.android.AndroidEntryPoint @@ -14,7 +15,7 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { - DoNextTheme { MainScreen() } + DoNextTheme(darkTheme = isSystemInDarkTheme(), dynamicColor = false) { MainScreen() } } } } \ No newline at end of file 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 b15e434..da1584d 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 @@ -107,7 +107,6 @@ fun MainScreen( onNavigate = { route -> scope.launch { drawerState.close() } navController.navigate(route) { - //launchSingleTop = true restoreState = true } } diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskItemScreen.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskItemScreen.kt index 3e3e845..2f0be54 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskItemScreen.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskItemScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Badge import androidx.compose.material3.Checkbox import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -88,23 +89,19 @@ fun TaskItemScreen( // Due date badge viewModel.dueDateText?.let { dueMillis -> - Box( + Badge( modifier = Modifier - .align( - if (viewModel.description.isNullOrBlank()) Alignment.CenterEnd - else Alignment.TopEnd - ) - .background( - color = if (viewModel.isOverdue) MaterialTheme.colorScheme.error - else MaterialTheme.colorScheme.primary.copy(alpha = 0.1f), - shape = RoundedCornerShape(12.dp) - ) - .padding(horizontal = 8.dp, vertical = 2.dp) + .align( + if (viewModel.description.isNullOrBlank()) Alignment.CenterEnd + else Alignment.TopEnd + ), + containerColor = if (viewModel.isOverdue) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primaryContainer ) { Text( + modifier = Modifier.padding(start = 1.dp, end = 1.dp), text = viewModel.dueDateText, - style = MaterialTheme.typography.bodySmall, - color = if (viewModel.isOverdue) Color.White else MaterialTheme.colorScheme.primary + color = if (viewModel.isOverdue) Color.White else MaterialTheme.colorScheme.onPrimaryContainer, + style = MaterialTheme.typography.bodySmall ) } } 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 ad3c020..e442e10 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 @@ -1,19 +1,20 @@ package com.wismna.geoffroy.donext.presentation.screen -import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -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.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel @@ -29,36 +30,53 @@ fun TaskListScreen( onTaskClick: (Task) -> Unit) { val tasks = viewModel.tasks + // Split tasks into active and done + val (active, done) = remember(tasks) { + tasks.partition { !it.isDone } + } + LazyColumn( - modifier = modifier.fillMaxSize().padding() + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) ) { - itemsIndexed(tasks, key = { id, task -> task.id!! }) { index, task -> - if (index > 0) { - val prev = tasks[index - 1] - - when { - // Divider between non-done and done tasks - !prev.isDone && task.isDone -> { - HorizontalDivider( - thickness = 1.dp, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f), - modifier = Modifier.padding(vertical = 8.dp) - ) - } - - // Extra spacing between different priorities (only if done status is same) - prev.priority != task.priority && prev.isDone == task.isDone -> { - Spacer(modifier = Modifier.height(20.dp)) - } - } - } - + // Active tasks section + items( + items = active, + key = { it.id!! } + ) { task -> TaskItemScreen( modifier = Modifier.animateItem(), viewModel = TaskItemViewModel(task), onClick = { onTaskClick(task) }, - onToggleDone = { isChecked -> - viewModel.updateTaskDone(task.id!!, isChecked) + onToggleDone = { checked -> + viewModel.updateTaskDone(task.id!!, checked) + } + ) + } + + // Divider between active and done (optional) + if (done.isNotEmpty() && active.isNotEmpty()) { + item { + HorizontalDivider( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + ) + } + } + + // Done tasks section + items( + items = done, + key = { it.id!! } + ) { task -> + TaskItemScreen( + modifier = Modifier.animateItem(), + viewModel = TaskItemViewModel(task), + onClick = { onTaskClick(task) }, + onToggleDone = { checked -> + viewModel.updateTaskDone(task.id!!, checked) } ) } 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 5e11f93..58c883c 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 @@ -143,8 +143,8 @@ fun TaskBottomSheet( initialSelectedDateMillis = viewModel.dueDate, selectableDates = object: SelectableDates { override fun isSelectableDate(utcTimeMillis: Long): Boolean { - val todayStartMillis = LocalDate.now() - .atStartOfDay(ZoneId.systemDefault()) + val todayStartMillis = LocalDate.now(ZoneOffset.UTC) + .atStartOfDay(ZoneOffset.UTC) .toInstant() .toEpochMilli() return utcTimeMillis >= todayStartMillis @@ -173,6 +173,7 @@ fun TaskBottomSheet( 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/ui/theme/Color.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/ui/theme/Color.kt index a1b8176..8d64a10 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/ui/theme/Color.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/ui/theme/Color.kt @@ -2,10 +2,26 @@ package com.wismna.geoffroy.donext.presentation.ui.theme import androidx.compose.ui.graphics.Color -val Purple80 = Color(0xFFD0BCFF) -val PurpleGrey80 = Color(0xFFCCC2DC) -val Pink80 = Color(0xFFEFB8C8) +// Primary shades +val Purple80 = Color(0xFFD0BCFF) // Light theme primary +val Purple40 = Color(0xFF6650A4) // Dark theme primary -val Purple40 = Color(0xFF6650a4) -val PurpleGrey40 = Color(0xFF625b71) -val Pink40 = Color(0xFF7D5260) \ No newline at end of file +val PurpleGrey80 = Color(0xFFCCC2DC) +val PurpleGrey40 = Color(0xFF625B71) + +val Pink80 = Color(0xFFEFB8C8) +val Pink40 = Color(0xFF7D5260) + +// Container variants +val Purple80Container = Color(0xFFEADDFF) +val Purple40Container = Color(0xFF381E72) + +val PurpleGrey80Container = Color(0xFFE8DEF8) +val PurpleGrey40Container = Color(0xFF4A4458) + +val Pink80Container = Color(0xFFFFD8E4) +val Pink40Container = Color(0xFF633B48) + +// Surface container +val LightSurfaceContainer = Color(0xFFF5F3FF) +val DarkSurfaceContainer = Color(0xFF1E1B2C) diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/ui/theme/Theme.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/ui/theme/Theme.kt index 56e735b..909ab7c 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/ui/theme/Theme.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/ui/theme/Theme.kt @@ -8,34 +8,12 @@ import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -private val DarkColorScheme = darkColorScheme( - primary = Purple80, - secondary = PurpleGrey80, - tertiary = Pink80 -) - -private val LightColorScheme = lightColorScheme( - primary = Purple40, - secondary = PurpleGrey40, - tertiary = Pink40 - - /* Other default colors to override - background = Color(0xFFFFFBFE), - surface = Color(0xFFFFFBFE), - onPrimary = Color.White, - onSecondary = Color.White, - onTertiary = Color.White, - onBackground = Color(0xFF1C1B1F), - onSurface = Color(0xFF1C1B1F), - */ -) - @Composable fun DoNextTheme( darkTheme: Boolean = isSystemInDarkTheme(), - // Dynamic color is available on Android 12+ dynamicColor: Boolean = true, content: @Composable () -> Unit ) { @@ -45,8 +23,51 @@ fun DoNextTheme( if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } - darkTheme -> DarkColorScheme - else -> LightColorScheme + darkTheme -> darkColorScheme( + primary = Purple40, + onPrimary = Color.White, + primaryContainer = Purple40Container, + onPrimaryContainer = Color.White, + secondary = PurpleGrey40, + onSecondary = Color.White, + secondaryContainer = PurpleGrey40Container, + onSecondaryContainer = Color.White, + tertiary = Pink40, + onTertiary = Color.White, + tertiaryContainer = Pink40Container, + onTertiaryContainer = Color.White, + background = Color(0xFF121212), + onBackground = Color.White, + surface = Color(0xFF121212), + onSurface = Color.White, + surfaceVariant = DarkSurfaceContainer, + onSurfaceVariant = Color.White, + error = Color(0xFFCF6679), + onError = Color.Black + ) + + else -> lightColorScheme( + primary = Purple80, + onPrimary = Color.Black, + primaryContainer = Purple80Container, + onPrimaryContainer = Color.Black, + secondary = PurpleGrey80, + onSecondary = Color.Black, + secondaryContainer = PurpleGrey80Container, + onSecondaryContainer = Color.Black, + tertiary = Pink80, + onTertiary = Color.Black, + tertiaryContainer = Pink80Container, + onTertiaryContainer = Color.Black, + background = Color(0xFFFFFBFE), + onBackground = Color.Black, + surface = Color(0xFFFFFBFE), + onSurface = Color.Black, + surfaceVariant = LightSurfaceContainer, + onSurfaceVariant = Color.Black, + error = Color(0xFFB00020), + onError = Color.White + ) } MaterialTheme( @@ -54,4 +75,4 @@ fun DoNextTheme( typography = Typography, content = content ) -} \ No newline at end of file +}