Theme is now working properly

Fix overdue use case
Allow selecting today as due date
Dates are now real badges
Animate done tasks
This commit is contained in:
Geoffroy Bonneville
2025-09-17 15:14:33 -04:00
parent 926a9bf66b
commit 78ce584900
9 changed files with 139 additions and 80 deletions

View File

@@ -7,7 +7,7 @@
<activity
android:name="com.wismna.geoffroy.donext.presentation.MainActivity"
android:exported="true"
android:label="@string/title_activity_main"
android:label="DoNext"
android:theme="@style/Theme.DoNext">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -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<List<TaskListWithOverdue>> {
return taskRepository.getTaskListsWithOverdue(Instant.parse("2025-09-15T12:00:00Z").toEpochMilli())
return taskRepository.getTaskListsWithOverdue(
LocalDate.now()
.atStartOfDay(ZoneOffset.UTC) // or system default
.toInstant()
.toEpochMilli()
)
}
}

View File

@@ -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() }
}
}
}

View File

@@ -107,7 +107,6 @@ fun MainScreen(
onNavigate = { route ->
scope.launch { drawerState.close() }
navController.navigate(route) {
//launchSingleTop = true
restoreState = true
}
}

View File

@@ -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
)
}
}

View File

@@ -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)
}
)
}

View File

@@ -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(

View File

@@ -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 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)

View File

@@ -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(