Compare commits

...

3 Commits

Author SHA1 Message Date
Geoffroy Bonneville
f8fd041f8e Refactor MainScreen and MainViewModel
Add list button now open bottom sheet
Add list bottom sheet works but design is WIP
WIP on inline list editing
2025-09-17 18:18:14 -04:00
Geoffroy Bonneville
0c5bf77b4d Remove unused imports 2025-09-17 15:15:15 -04:00
Geoffroy Bonneville
78ce584900 Theme is now working properly
Fix overdue use case
Allow selecting today as due date
Dates are now real badges
Animate done tasks
2025-09-17 15:14:33 -04:00
14 changed files with 341 additions and 163 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

@@ -0,0 +1,18 @@
package com.wismna.geoffroy.donext.domain.model
sealed class AppDestination(
val route: String,
val title: String,
val showBackButton: Boolean = false,
) {
data class TaskList(val taskListId: Long, val name: String) : AppDestination(
route = "taskList/$taskListId",
title = name,
)
object ManageLists : AppDestination(
route = "manageLists",
title = "Manage Lists",
showBackButton = true,
)
}

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

@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.CircularProgressIndicator
@@ -39,45 +40,26 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.wismna.geoffroy.donext.domain.model.TaskListWithOverdue
import com.wismna.geoffroy.donext.domain.model.AppDestination
import com.wismna.geoffroy.donext.presentation.viewmodel.MainViewModel
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskListViewModel
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskViewModel
import kotlinx.coroutines.launch
sealed class AppDestination(
val route: String,
val title: String,
val showBackButton: Boolean = false,
val actions: @Composable (() -> Unit)? = null
) {
data class TaskList(val taskListId: Long, val name: String) : AppDestination(
route = "taskList/$taskListId",
title = name,
)
object ManageLists : AppDestination(
route = "manageLists",
title = "Manage Lists",
showBackButton = true,
actions = { ManageListsActions() }
)
}
@Composable
fun MainScreen(
modifier: Modifier = Modifier,
viewModel: MainViewModel = hiltViewModel()
) {
val navController = rememberNavController()
var showBottomSheet by remember { mutableStateOf(false) }
var showTaskSheet by remember { mutableStateOf(false) }
var showAddListSheet by remember { mutableStateOf(false) }
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
val taskViewModel: TaskViewModel = hiltViewModel()
@@ -89,25 +71,26 @@ fun MainScreen(
return
}
val firstListId = viewModel.taskLists.firstOrNull()?.id
if (showBottomSheet) {
TaskBottomSheet(taskViewModel) { showBottomSheet = false }
val startDestination = viewModel.destinations.firstOrNull() ?: AppDestination.ManageLists
if (showTaskSheet) {
TaskBottomSheet(taskViewModel) { showTaskSheet = false }
}
if (showAddListSheet) {
AddListBottomSheet { showAddListSheet = false }
}
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = remember(navBackStackEntry, viewModel.taskLists) {
deriveDestination(navBackStackEntry, viewModel.taskLists)
}
val currentDestination =
viewModel.deriveDestination(navBackStackEntry?.destination?.route)
?: startDestination
ModalNavigationDrawer(
drawerContent = {
MenuScreen (
taskLists = viewModel.taskLists,
currentDestination = currentDestination,
onNavigate = { route ->
scope.launch { drawerState.close() }
navController.navigate(route) {
//launchSingleTop = true
restoreState = true
}
}
@@ -120,7 +103,7 @@ fun MainScreen(
containerColor = Color.Transparent,
topBar = {
TopAppBar(
title = { Text(currentDestination.title) },
title = { Text(currentDestination!!.title) },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
@@ -128,7 +111,7 @@ fun MainScreen(
),
navigationIcon = {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onPrimaryContainer) {
if (currentDestination.showBackButton) {
if (currentDestination!!.showBackButton) {
IconButton(onClick = { navController.popBackStack() }) {
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
}
@@ -142,7 +125,13 @@ fun MainScreen(
}
}
},
actions = { currentDestination.actions?.invoke() }
actions = {
if (currentDestination is AppDestination.ManageLists) {
IconButton(onClick = { showAddListSheet = true }) {
Icon(Icons.Default.Add, contentDescription = "Add List")
}
}
}
)
},
floatingActionButton = {
@@ -150,7 +139,7 @@ fun MainScreen(
is AppDestination.TaskList -> {
TaskListFab(
taskListId = dest.taskListId,
showBottomSheet = { showBottomSheet = it }
showBottomSheet = { showTaskSheet = it }
)
}
else -> null
@@ -166,8 +155,7 @@ fun MainScreen(
) {
NavHost(
navController = navController,
startDestination = firstListId?.let { "taskList/$it" }
?: AppDestination.ManageLists.route,
startDestination = startDestination.route,
enterTransition = {
slideInHorizontally(initialOffsetX = { fullWidth -> fullWidth }, animationSpec = tween(300))
},
@@ -181,7 +169,7 @@ fun MainScreen(
slideOutHorizontally(targetOffsetX = { fullWidth -> fullWidth }, animationSpec = tween(300))
}
) {
viewModel.taskLists.forEach { list ->
viewModel.destinations.forEach { destination ->
composable(
route = "taskList/{taskListId}",
arguments = listOf(navArgument("taskListId") {
@@ -193,40 +181,20 @@ fun MainScreen(
viewModel = viewModel,
onTaskClick = { task ->
taskViewModel.startEditTask(task)
showBottomSheet = true
showTaskSheet = true
}
)
}
}
composable(AppDestination.ManageLists.route) {
ManageListsScreen(modifier = Modifier)
ManageListsScreen(
modifier = Modifier,
showAddListSheet = {showAddListSheet = true}
)
}
}
}
}
}
}
fun deriveDestination(
navBackStackEntry: NavBackStackEntry?,
taskLists: List<TaskListWithOverdue>
): AppDestination {
val route = navBackStackEntry?.destination?.route
return when {
route == AppDestination.ManageLists.route -> AppDestination.ManageLists
route?.startsWith("taskList/") == true || route == "taskList/{taskListId}" -> {
val idArg = navBackStackEntry.arguments?.getLong("taskListId")
val taskListId = idArg ?: route.substringAfter("taskList/", "").toLongOrNull()
val matching = taskLists.find { it.id == taskListId }
matching?.let { AppDestination.TaskList(it.id, it.name) }
?: taskLists.firstOrNull()?.let { AppDestination.TaskList(it.id, it.name) }
?: AppDestination.ManageLists
}
else -> {
taskLists.firstOrNull()?.let { AppDestination.TaskList(it.id, it.name) }
?: AppDestination.ManageLists
}
}
}

View File

@@ -1,14 +1,20 @@
package com.wismna.geoffroy.donext.presentation.screen
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
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.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
@@ -16,24 +22,38 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
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.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.wismna.geoffroy.donext.domain.model.TaskList
import com.wismna.geoffroy.donext.presentation.viewmodel.ManageListsViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ManageListsScreen(
modifier: Modifier,
viewModel: ManageListsViewModel = hiltViewModel()
viewModel: ManageListsViewModel = hiltViewModel(),
showAddListSheet: () -> Unit
) {
val lists = viewModel.taskLists
LazyColumn(modifier = modifier.fillMaxWidth().padding()) {
itemsIndexed(lists, key = { _, list -> list.id!! }) { index, list ->
ListItem(
modifier = Modifier.animateItem(),
headlineContent = { Text(list.name) },
trailingContent = {
Row {
@@ -54,10 +74,83 @@ fun ManageListsScreen(
}
@Composable
fun ManageListsActions(
viewModel: ManageListsViewModel = hiltViewModel()
fun EditableListRow(
list: TaskList,
onNameChange: (String) -> Unit,
//onTypeChange: (ListType) -> Unit,
onDone: () -> Unit
) {
IconButton(onClick = { viewModel.createTaskList("Test", 1) }) {
Icon(Icons.Default.Add, contentDescription = "Add List")
var name by remember { mutableStateOf(list.name) }
//var type by remember { mutableStateOf(list.type) }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().padding(8.dp)
) {
TextField(
value = name,
onValueChange = { name = it },
modifier = Modifier.weight(1f),
singleLine = true
)
// TODO: implement type
//DropdownSelector(selected = type, onSelect = { type = it; onTypeChange(it) })
IconButton(onClick = onDone) {
Icon(Icons.Default.Check, contentDescription = "Save")
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddListBottomSheet(
viewModel: ManageListsViewModel = hiltViewModel(),
onDismiss: () -> Unit
) {
val titleFocusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
titleFocusRequester.requestFocus()
}
ModalBottomSheet(onDismissRequest = onDismiss) {
var name by remember { mutableStateOf("") }
//var type by remember { mutableStateOf(ListType.Default) }
//var description by remember { mutableStateOf("") }
Column(modifier = Modifier.padding(16.dp)) {
Text("Create New List", style = MaterialTheme.typography.titleMedium)
Spacer(Modifier.height(8.dp))
TextField(
value = name,
onValueChange = { name = it },
label = { Text("List Name") },
singleLine = true
)
Spacer(Modifier.height(8.dp))
//DropdownSelector(selected = type, onSelect = { type = it })
/*Spacer(Modifier.height(8.dp))
TextField(
value = description,
onValueChange = { description = it },
label = { Text("Description") },
maxLines = 3
)*/
Spacer(Modifier.height(16.dp))
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
TextButton(onClick = onDismiss) { Text("Cancel") }
Spacer(Modifier.width(8.dp))
Button(onClick = {
viewModel.createTaskList(name/*, type, description*/, 1)
onDismiss()
}) {
Text("Add")
}
}
}
}
}

View File

@@ -18,11 +18,13 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.wismna.geoffroy.donext.domain.model.TaskListWithOverdue
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.wismna.geoffroy.donext.domain.model.AppDestination
import com.wismna.geoffroy.donext.presentation.viewmodel.MenuViewModel
@Composable
fun MenuScreen(
taskLists: List<TaskListWithOverdue>,
viewModel: MenuViewModel = hiltViewModel(),
currentDestination: AppDestination,
onNavigate: (String) -> Unit
) {
@@ -42,7 +44,7 @@ fun MenuScreen(
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(16.dp)
)
taskLists.forEach { list ->
viewModel.taskLists.forEach { list ->
NavigationDrawerItem(
label = { Text(list.name) },
icon = { Icon(Icons.Default.List, contentDescription = list.name) },

View File

@@ -5,7 +5,6 @@ import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.IntrinsicSize
@@ -15,7 +14,7 @@ import androidx.compose.foundation.layout.height
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 +87,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)
),
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

@@ -42,7 +42,6 @@ import com.wismna.geoffroy.donext.domain.model.Priority
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskViewModel
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
@@ -143,8 +142,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 +172,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(

View File

@@ -5,8 +5,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.wismna.geoffroy.donext.domain.model.TaskListWithOverdue
import com.wismna.geoffroy.donext.domain.usecase.GetTaskListsWithOverdueUseCase
import com.wismna.geoffroy.donext.domain.model.AppDestination
import com.wismna.geoffroy.donext.domain.usecase.GetTaskListsUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -14,23 +14,33 @@ import javax.inject.Inject
@HiltViewModel
class MainViewModel @Inject constructor(
getTaskListsWithOverdue: GetTaskListsWithOverdueUseCase
getTaskLists: GetTaskListsUseCase
) : ViewModel() {
var taskLists by mutableStateOf<List<TaskListWithOverdue>>(emptyList())
private set
/*val destinations: List<AppDestination>
get() = taskLists.map { AppDestination.TaskList(it.id, it.name) } +
AppDestination.ManageLists*/
var isLoading by mutableStateOf(true)
private set
var destinations by mutableStateOf<List<AppDestination>>(emptyList())
private set
init {
getTaskListsWithOverdue()
getTaskLists()
.onEach { lists ->
taskLists = lists
destinations = lists.map { taskList ->
AppDestination.TaskList(taskList.id!!, taskList.name)
} + AppDestination.ManageLists
isLoading = false
}
.launchIn(viewModelScope)
}
fun deriveDestination(route: String?): AppDestination? {
if (route == null) return null
return destinations.firstOrNull { dest ->
when (dest) {
is AppDestination.TaskList -> route.startsWith("tasklist/")
else -> dest.route == route
}
}
}
}

View File

@@ -0,0 +1,30 @@
package com.wismna.geoffroy.donext.presentation.viewmodel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.wismna.geoffroy.donext.domain.model.TaskListWithOverdue
import com.wismna.geoffroy.donext.domain.usecase.GetTaskListsWithOverdueUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject
@HiltViewModel
class MenuViewModel @Inject constructor(
getTaskListsWithOverdue: GetTaskListsWithOverdueUseCase
) : ViewModel() {
var taskLists by mutableStateOf<List<TaskListWithOverdue>>(emptyList())
private set
init {
getTaskListsWithOverdue()
.onEach { lists ->
taskLists = lists
}
.launchIn(viewModelScope)
}
}