From ef73319613acb12e841599788578569a9336969d Mon Sep 17 00:00:00 2001 From: Geoffroy Bonneville <24917789+wismna@users.noreply.github.com> Date: Thu, 11 Sep 2025 22:40:40 -0400 Subject: [PATCH] Removed order from tasks Tasks are ordered by priority and display is a bit different for each Done tasks are shown at the bottom Add done checkbox Add priority segmented button to bottom sheet Use database converters instead of manual mapping Some refactoring --- .../wismna/geoffroy/donext/data/Converters.kt | 8 +- .../wismna/geoffroy/donext/data/Mappers.kt | 9 +- .../donext/data/entities/TaskEntity.kt | 9 +- .../geoffroy/donext/data/local/Database.kt | 6 +- .../geoffroy/donext/data/local/dao/TaskDao.kt | 5 +- .../local/repository/TaskRepositoryImpl.kt | 6 +- .../geoffroy/donext/domain/model/Priority.kt | 12 ++ .../geoffroy/donext/domain/model/Task.kt | 4 +- .../domain/repository/TaskRepository.kt | 3 +- .../donext/domain/usecase/AddTaskUseCase.kt | 7 +- .../domain/usecase/ToggleTaskDoneUseCase.kt | 12 ++ .../donext/presentation/screen/MainScreen.kt | 136 +++++++++++++----- .../presentation/screen/TaskListScreen.kt | 121 +++++++++++++--- .../presentation/viewmodel/MainViewModel.kt | 15 +- .../viewmodel/TaskListViewModel.kt | 9 ++ 15 files changed, 270 insertions(+), 92 deletions(-) create mode 100644 donextv2/src/main/java/com/wismna/geoffroy/donext/domain/model/Priority.kt create mode 100644 donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/ToggleTaskDoneUseCase.kt 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 fd7809d..35391e0 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 @@ -1,9 +1,9 @@ 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? { @@ -14,4 +14,10 @@ class Converters { fun instantToTimestamp(instant: Instant?): Long? { return instant?.toEpochMilli() } + + @TypeConverter + fun fromPriority(priority: Priority): Int = priority.value + + @TypeConverter + fun toPriority(value: Int): Priority = Priority.fromValue(value) } \ No newline at end of file diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/data/Mappers.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/data/Mappers.kt index 59736c7..7ead5f0 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/data/Mappers.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/data/Mappers.kt @@ -4,19 +4,16 @@ import com.wismna.geoffroy.donext.data.entities.TaskEntity import com.wismna.geoffroy.donext.data.entities.TaskListEntity import com.wismna.geoffroy.donext.domain.model.Task import com.wismna.geoffroy.donext.domain.model.TaskList -import java.time.Instant fun TaskEntity.toDomain() = Task( id = id, name = name, taskListId = taskListId, description = description, - cycle = cycle, isDone = isDone, isDeleted = isDeleted, - dueDate = if (dueDate == null) null else Instant.ofEpochMilli(dueDate), + dueDate = dueDate, priority = priority, - order = order ) fun Task.toEntity() = TaskEntity( @@ -24,12 +21,10 @@ fun Task.toEntity() = TaskEntity( name = name, taskListId = taskListId, description = description, - cycle = cycle, priority = priority, - order = order, isDone = isDone, isDeleted = isDeleted, - dueDate = dueDate?.toEpochMilli() + dueDate = dueDate ) fun TaskListEntity.toDomain() = TaskList( 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 3c6c366..e3e2997 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 @@ -3,6 +3,8 @@ package com.wismna.geoffroy.donext.data.entities 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( @@ -10,10 +12,7 @@ data class TaskEntity( val id: Long = 0, val name: String, val description: String?, - val cycle: Int = 0, - val priority: Int, - @ColumnInfo(name = "display_order") - val order: Int, + val priority: Priority, @ColumnInfo(name = "done") val isDone: Boolean = false, @ColumnInfo(name = "deleted") @@ -21,5 +20,5 @@ data class TaskEntity( @ColumnInfo(name = "task_list_id") val taskListId: Long, @ColumnInfo(name = "due_date") - val dueDate: Long? = null + val dueDate: Instant? = null ) diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/data/local/Database.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/data/local/Database.kt index 28242ff..179aed2 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/data/local/Database.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/data/local/Database.kt @@ -41,9 +41,7 @@ abstract class AppDatabase : RoomDatabase() { id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT NOT NULL, description TEXT, - cycle INTEGER NOT NULL DEFAULT 0, priority INTEGER NOT NULL, - display_order INTEGER NOT NULL, done INTEGER NOT NULL DEFAULT 0, deleted INTEGER NOT NULL DEFAULT 0, task_list_id INTEGER NOT NULL, @@ -57,16 +55,14 @@ abstract class AppDatabase : RoomDatabase() { db.execSQL( """ INSERT INTO tasks_new ( - id, name, description, cycle, priority, display_order, + id, name, description, priority, done, deleted, task_list_id, due_date ) SELECT _id, -- old '_id' mapped to id name, description, - cycle, priority, - displayorder, -- old 'displayorder' mapped to display_order done, deleted, list, -- old 'list' mapped to task_list_id diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/data/local/dao/TaskDao.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/data/local/dao/TaskDao.kt index dcf9957..7f7e79a 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/data/local/dao/TaskDao.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/data/local/dao/TaskDao.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.Flow @Dao interface TaskDao { - @Query("SELECT * FROM tasks WHERE task_list_id = :listId ORDER BY display_order ASC") + @Query("SELECT * FROM tasks WHERE task_list_id = :listId ORDER BY done ASC, priority DESC") fun getTasksForList(listId: Long): Flow> @Insert(onConflict = OnConflictStrategy.REPLACE) @@ -25,9 +25,6 @@ interface TaskDao { @Query("UPDATE tasks SET deleted = :deleted WHERE id = :taskId") suspend fun markTaskDeleted(taskId: Long, deleted: Boolean) - @Query("UPDATE tasks SET cycle = cycle + 1 WHERE id = :taskId") - suspend fun increaseCycle(taskId: Long) - @Query("UPDATE tasks SET deleted = :deleted WHERE id = :taskListId") suspend fun deleteAllTasksFromList(taskListId: Long, deleted: Boolean) } \ No newline at end of file diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/data/local/repository/TaskRepositoryImpl.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/data/local/repository/TaskRepositoryImpl.kt index f650190..c6518e1 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/data/local/repository/TaskRepositoryImpl.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/data/local/repository/TaskRepositoryImpl.kt @@ -31,14 +31,10 @@ class TaskRepositoryImpl @Inject constructor( taskDao.markTaskDeleted(taskId, isDeleted) } - override suspend fun closeTask(taskId: Long, isDone: Boolean) { + override suspend fun toggleTaskDone(taskId: Long, isDone: Boolean) { taskDao.markTaskDone(taskId, isDone) } - override suspend fun increaseTaskCycle(taskId: Long) { - taskDao.increaseCycle(taskId) - } - override fun getTaskLists(): Flow> { return taskListDao.getTaskLists().map {entities -> entities.map { it.toDomain() }} } diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/model/Priority.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/model/Priority.kt new file mode 100644 index 0000000..249a536 --- /dev/null +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/model/Priority.kt @@ -0,0 +1,12 @@ +package com.wismna.geoffroy.donext.domain.model + +enum class Priority(val value: Int, val label: String) { + LOW(0, "Low"), + NORMAL(1, "Normal"), + HIGH(2, "High"); + + companion object { + fun fromValue(value: Int): Priority = + Priority.entries.firstOrNull { it.value == value } ?: NORMAL + } +} \ No newline at end of file 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 e9f80ab..f2a81a9 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 @@ -6,9 +6,7 @@ data class Task( val id: Long? = null, val name: String, val description: String?, - val cycle: Int, - val priority: Int, - val order: Int, + val priority: Priority, val isDone: Boolean, val isDeleted: Boolean, val taskListId: Long, diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/repository/TaskRepository.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/repository/TaskRepository.kt index 854e07a..540c2c6 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/repository/TaskRepository.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/repository/TaskRepository.kt @@ -9,8 +9,7 @@ interface TaskRepository { suspend fun insertTask(task: Task) suspend fun updateTask(task: Task) suspend fun deleteTask(taskId: Long, isDeleted: Boolean) - suspend fun closeTask(taskId: Long, isDone: Boolean) - suspend fun increaseTaskCycle(taskId: Long) + suspend fun toggleTaskDone(taskId: Long, isDone: Boolean) fun getTaskLists(): Flow> suspend fun insertTaskList(taskList: TaskList) 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 3c6cb5b..e791588 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 @@ -1,5 +1,6 @@ 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 javax.inject.Inject @@ -7,17 +8,15 @@ import javax.inject.Inject class AddTaskUseCase @Inject constructor( private val repository: TaskRepository ) { - suspend operator fun invoke(taskListId: Long, title: String, description: String?) { + suspend operator fun invoke(taskListId: Long, title: String, description: String?, priority: Priority) { repository.insertTask( Task( taskListId = taskListId, name = title, description = description ?: "", isDeleted = false, - cycle = 0, isDone = false, - priority = 0, - order = 0 + priority = priority, ) ) } diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/ToggleTaskDoneUseCase.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/ToggleTaskDoneUseCase.kt new file mode 100644 index 0000000..d806d46 --- /dev/null +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/ToggleTaskDoneUseCase.kt @@ -0,0 +1,12 @@ +package com.wismna.geoffroy.donext.domain.usecase + +import com.wismna.geoffroy.donext.domain.repository.TaskRepository +import javax.inject.Inject + +class ToggleTaskDoneUseCase @Inject constructor( + private val repository: TaskRepository +) { + suspend operator fun invoke(taskId: Long, isDone: Boolean) { + repository.toggleTaskDone(taskId, isDone) + } +} \ 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 6fb1d1f..d1bfef2 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 @@ -1,3 +1,5 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + package com.wismna.geoffroy.donext.presentation.screen import androidx.compose.foundation.layout.Box @@ -19,9 +21,13 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.PrimaryTabRow import androidx.compose.material3.Scaffold +import androidx.compose.material3.SegmentedButton +import androidx.compose.material3.SegmentedButtonDefaults +import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.Tab import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf @@ -30,6 +36,8 @@ import androidx.compose.runtime.saveable.rememberSaveable 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.focus.focusRequester import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -39,11 +47,11 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument +import com.wismna.geoffroy.donext.domain.model.Priority import com.wismna.geoffroy.donext.domain.model.TaskList import com.wismna.geoffroy.donext.presentation.viewmodel.MainViewModel import com.wismna.geoffroy.donext.presentation.viewmodel.TaskListViewModel -@OptIn(ExperimentalMaterial3Api::class) @Composable fun MainScreen( viewModel: MainViewModel, @@ -62,39 +70,7 @@ fun MainScreen( var selectedDestination by rememberSaveable { mutableIntStateOf(0) } if (showBottomSheet) { - ModalBottomSheet(onDismissRequest = { showBottomSheet = false }) { - Column(Modifier.padding(16.dp)) { - Text("New Task", style = MaterialTheme.typography.titleLarge) - Spacer(Modifier.height(8.dp)) - OutlinedTextField( - value = viewModel.title, - singleLine = true, - onValueChange = { viewModel.onTitleChanged(it) }, - label = { Text("Title") }, - modifier = Modifier.fillMaxWidth(), - isError = !viewModel.isTitleValid && viewModel.title.isNotEmpty(), - ) - Spacer(Modifier.height(8.dp)) - OutlinedTextField( - value = viewModel.description, - onValueChange = { viewModel.onDescriptionChanged(it) }, - label = { Text("Description") }, - maxLines = 3, - modifier = Modifier.fillMaxWidth() - ) - Spacer(Modifier.height(16.dp)) - Button( - onClick = { - val currentListId = viewModel.taskLists[selectedDestination].id - viewModel.createTask(currentListId) - showBottomSheet = false - }, - modifier = Modifier.align(Alignment.End) - ) { - Text("Add") - } - } - } + AddTaskBottomSheet(viewModel, selectedDestination, { showBottomSheet = false }) } Scaffold( @@ -123,7 +99,6 @@ fun MainScreen( } } }) { contentPadding -> - // NavHost will now automatically be below the tabs Box(modifier = Modifier .padding(contentPadding) .fillMaxSize() @@ -155,11 +130,100 @@ fun AppNavHost( } } +@Composable +fun AddTaskBottomSheet( + viewModel: MainViewModel, + selectedListIndex: Int, + onDismiss: () -> Unit +) { + val titleFocusRequester = remember { FocusRequester() } + + LaunchedEffect(Unit) { + titleFocusRequester.requestFocus() + } + + ModalBottomSheet(onDismissRequest = onDismiss) { + Column(Modifier.padding(16.dp)) { + Text( + "New Task", + style = MaterialTheme.typography.titleLarge + ) + Spacer(Modifier.height(8.dp)) + + // --- Title --- + OutlinedTextField( + value = viewModel.title, + singleLine = true, + onValueChange = { viewModel.onTitleChanged(it) }, + label = { Text("Title") }, + modifier = Modifier + .fillMaxWidth() + .focusRequester(titleFocusRequester), + isError = !viewModel.isTitleValid && viewModel.title.isNotEmpty(), + ) + Spacer(Modifier.height(8.dp)) + + // --- Description --- + OutlinedTextField( + value = viewModel.description, + onValueChange = { viewModel.onDescriptionChanged(it) }, + label = { Text("Description") }, + maxLines = 3, + modifier = Modifier.fillMaxWidth() + ) + Spacer(Modifier.height(8.dp)) + + // --- Priority --- + Text("Priority", style = MaterialTheme.typography.labelLarge) + Spacer(Modifier.height(4.dp)) + SingleChoiceSegmentedButton( + value = viewModel.priority, + onValueChange = { viewModel.onPriorityChanged(it) } + ) + Spacer(Modifier.height(16.dp)) + + // --- Add Button --- + Button( + onClick = { + val currentListId = viewModel.taskLists[selectedListIndex].id + viewModel.createTask(currentListId) + onDismiss() + viewModel.resetTaskForm() + }, + modifier = Modifier.align(Alignment.End) + ) { + Text("Add") + } + } + } +} + @Composable fun AddNewTaskButton(onClick: () -> Unit) { ExtendedFloatingActionButton( - onClick = { onClick() }, + onClick = onClick, icon = { Icon(Icons.Filled.Add, "Create a task.") }, text = { Text(text = "Create a task") }, ) +} + +@Composable +fun SingleChoiceSegmentedButton( + value: Priority, + onValueChange: (Priority) -> Unit) { + val options = listOf(Priority.LOW.label, Priority.NORMAL.label, Priority.HIGH.label) + + SingleChoiceSegmentedButtonRow { + options.forEachIndexed { index, label -> + SegmentedButton( + shape = SegmentedButtonDefaults.itemShape( + index = index, + count = options.size + ), + onClick = { onValueChange(Priority.fromValue(index)) }, + selected = index == value.value, + label = { Text(label) } + ) + } + } } \ No newline at end of file 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 c52ed97..8fb5f56 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,21 +1,31 @@ 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.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Checkbox +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.hilt.navigation.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 @Composable @@ -23,21 +33,96 @@ fun TaskListScreen(viewModel: TaskListViewModel = hiltViewModel()) { val tasks = viewModel.tasks LazyColumn( - verticalArrangement = Arrangement.spacedBy(4.dp)) { - items(tasks) { task -> - Row( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp) - ) { - Column { - } - Column { - val textStyle = TextStyle(textDecoration = if (task.isDeleted) TextDecoration.LineThrough else TextDecoration.None) - Text(task.name, fontSize = 18.sp, color = Color.DarkGray, style = textStyle) - Text(task.description.orEmpty(), fontSize = 14.sp, color = Color.LightGray, style = textStyle, maxLines = 3) + modifier = Modifier.fillMaxSize() + ) { + itemsIndexed(tasks, key = { _, 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)) + } } } + + TaskItem( + task = task, + onToggleDone = { isChecked -> + viewModel.updateTaskDone(task.id!!, isChecked) + } + ) } } +} + +@Composable +fun TaskItem( + task: Task, + onToggleDone: (Boolean) -> Unit +) { + 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) { + Priority.HIGH -> MaterialTheme.colorScheme.onSurface + Priority.NORMAL -> MaterialTheme.colorScheme.onSurface + Priority.LOW -> MaterialTheme.colorScheme.onSurfaceVariant + }, + textDecoration = if (task.isDone) TextDecoration.LineThrough else TextDecoration.None) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) + .alpha(if (task.isDone) 0.5f else 1f), + ) { + /*Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + verticalAlignment = Alignment.CenterVertically + ) {*/ + Checkbox( + checked = task.isDone, + onCheckedChange = onToggleDone, + modifier = Modifier + .size(40.dp) // Adjust size as needed + .clip(CircleShape) + ) + + Column( + modifier = Modifier.weight(1f) + ) { + Text( + text = task.name, + style = baseStyle + ) + + if (!task.description.isNullOrBlank()) { + Text( + text = task.description, + style = baseStyle.copy( + fontSize = MaterialTheme.typography.bodyMedium.fontSize + ), + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + } + } + // } + } } \ No newline at end of file diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/MainViewModel.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/MainViewModel.kt index 5f312f9..0a25503 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/MainViewModel.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/MainViewModel.kt @@ -5,6 +5,7 @@ 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.Priority import com.wismna.geoffroy.donext.domain.model.TaskList import com.wismna.geoffroy.donext.domain.usecase.AddTaskUseCase import com.wismna.geoffroy.donext.domain.usecase.GetTaskListsUseCase @@ -30,6 +31,8 @@ class MainViewModel @Inject constructor( private set var description by mutableStateOf("") private set + var priority by mutableStateOf(Priority.NORMAL) + private set val isTitleValid: Boolean get() = title.isNotBlank() @@ -46,15 +49,23 @@ class MainViewModel @Inject constructor( fun createTask(taskListId: Long) { if (!isTitleValid) return viewModelScope.launch { - addTask(taskListId, title, description) + addTask(taskListId, title, description, priority) } } fun onTitleChanged(newTitle: String) { title = newTitle } - fun onDescriptionChanged(newDesc: String) { description = newDesc } + fun onPriorityChanged(newPriority: Priority) { + priority = newPriority + } + + fun resetTaskForm() { + title = "" + description = "" + priority = Priority.NORMAL + } } \ No newline at end of file diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskListViewModel.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskListViewModel.kt index e6286b2..f4874bc 100644 --- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskListViewModel.kt +++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/viewmodel/TaskListViewModel.kt @@ -7,15 +7,18 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.wismna.geoffroy.donext.domain.model.Task +import com.wismna.geoffroy.donext.domain.usecase.ToggleTaskDoneUseCase import com.wismna.geoffroy.donext.domain.usecase.GetTasksForListUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class TaskListViewModel @Inject constructor( getTasks: GetTasksForListUseCase, + private val toggleTaskDone: ToggleTaskDoneUseCase, savedStateHandle: SavedStateHandle ) : ViewModel() { @@ -35,4 +38,10 @@ class TaskListViewModel @Inject constructor( } .launchIn(viewModelScope) } + + fun updateTaskDone(taskId: Long, isDone: Boolean) { + viewModelScope.launch { + toggleTaskDone(taskId, isDone) + } + } } \ No newline at end of file