mirror of
https://github.com/wismna/DoNext.git
synced 2025-10-03 15:40:14 -04:00
Tasks lists are now re-orderable
Edit task bottom sheet displays proper header
This commit is contained in:
@@ -56,6 +56,7 @@ dependencies {
|
|||||||
implementation("androidx.navigation:navigation-compose:2.9.4")
|
implementation("androidx.navigation:navigation-compose:2.9.4")
|
||||||
implementation("androidx.hilt:hilt-navigation-compose:1.3.0")
|
implementation("androidx.hilt:hilt-navigation-compose:1.3.0")
|
||||||
implementation("androidx.test.ext:junit-ktx:1.3.0")
|
implementation("androidx.test.ext:junit-ktx:1.3.0")
|
||||||
|
implementation("sh.calvin.reorderable:reorderable:3.0.0")
|
||||||
androidTestImplementation(platform("androidx.compose:compose-bom:2025.09.00"))
|
androidTestImplementation(platform("androidx.compose:compose-bom:2025.09.00"))
|
||||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||||
|
@@ -31,6 +31,7 @@ interface TaskListDao {
|
|||||||
LEFT JOIN tasks t ON t.task_list_id = tl.id
|
LEFT JOIN tasks t ON t.task_list_id = tl.id
|
||||||
WHERE tl.deleted = 0
|
WHERE tl.deleted = 0
|
||||||
GROUP BY tl.id
|
GROUP BY tl.id
|
||||||
|
ORDER BY tl.display_order ASC
|
||||||
""")
|
""")
|
||||||
fun getTaskListsWithOverdue(nowMillis: Long): Flow<List<TaskListWithOverdue>>
|
fun getTaskListsWithOverdue(nowMillis: Long): Flow<List<TaskListWithOverdue>>
|
||||||
|
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
package com.wismna.geoffroy.donext.presentation.screen
|
package com.wismna.geoffroy.donext.presentation.screen
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -9,17 +14,18 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.filled.Check
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.material.icons.filled.Edit
|
import androidx.compose.material.icons.filled.Edit
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.ListItem
|
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
@@ -32,12 +38,18 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.semantics.CustomAccessibilityAction
|
||||||
|
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||||
|
import androidx.compose.ui.semantics.customActions
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import com.wismna.geoffroy.donext.presentation.viewmodel.ManageListsViewModel
|
import com.wismna.geoffroy.donext.presentation.viewmodel.ManageListsViewModel
|
||||||
|
import sh.calvin.reorderable.ReorderableItem
|
||||||
|
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -46,17 +58,83 @@ fun ManageListsScreen(
|
|||||||
viewModel: ManageListsViewModel = hiltViewModel(),
|
viewModel: ManageListsViewModel = hiltViewModel(),
|
||||||
showAddListSheet: () -> Unit
|
showAddListSheet: () -> Unit
|
||||||
) {
|
) {
|
||||||
val lists = viewModel.taskLists
|
var lists = viewModel.taskLists.toMutableList()
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
|
val reorderState = rememberReorderableLazyListState(
|
||||||
|
lazyListState = lazyListState,
|
||||||
|
onMove = { from, to ->
|
||||||
|
viewModel.moveTaskList(from.index, to.index)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
LazyColumn(modifier = modifier.fillMaxWidth().padding()) {
|
LazyColumn(modifier = modifier.fillMaxWidth().padding(), state = lazyListState) {
|
||||||
itemsIndexed(lists, key = { _, list -> list.id!! }) { index, list ->
|
itemsIndexed(lists, key = { _, list -> list.id!! }) { index, list ->
|
||||||
|
|
||||||
var isInEditMode by remember { mutableStateOf(false) }
|
var isInEditMode by remember { mutableStateOf(false) }
|
||||||
var editedName by remember { mutableStateOf(list.name) }
|
var editedName by remember { mutableStateOf(list.name) }
|
||||||
ListItem(
|
ReorderableItem(
|
||||||
modifier = Modifier.animateItem(),
|
state = reorderState,
|
||||||
headlineContent = {
|
key = list.id!!
|
||||||
if (isInEditMode) {
|
) {
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
Card(
|
||||||
|
onClick = {},
|
||||||
|
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 5.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
|
||||||
|
),
|
||||||
|
modifier = Modifier.draggableHandle(
|
||||||
|
onDragStopped = {
|
||||||
|
viewModel.commitTaskListOrder()
|
||||||
|
},
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
)
|
||||||
|
.clearAndSetSemantics {
|
||||||
|
customActions = listOf(
|
||||||
|
CustomAccessibilityAction(
|
||||||
|
label = "Move Up",
|
||||||
|
action = {
|
||||||
|
if (index > 0) {
|
||||||
|
lists = lists.toMutableList().apply {
|
||||||
|
add(index - 1, removeAt(index))
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
CustomAccessibilityAction(
|
||||||
|
label = "Move Down",
|
||||||
|
action = {
|
||||||
|
if (index < lists.size - 1) {
|
||||||
|
lists = lists.toMutableList().apply {
|
||||||
|
add(index + 1, removeAt(index))
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(10.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
AnimatedContent(
|
||||||
|
//modifier = Modifier.padding(start = 12.dp),
|
||||||
|
targetState = isInEditMode,
|
||||||
|
transitionSpec = {
|
||||||
|
fadeIn() togetherWith fadeOut()
|
||||||
|
},
|
||||||
|
label = "Headline transition"
|
||||||
|
) { isEditing ->
|
||||||
|
if (isEditing) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = editedName,
|
value = editedName,
|
||||||
onValueChange = { editedName = it },
|
onValueChange = { editedName = it },
|
||||||
@@ -65,9 +143,15 @@ fun ManageListsScreen(
|
|||||||
} else {
|
} else {
|
||||||
Text(list.name)
|
Text(list.name)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = isInEditMode,
|
||||||
|
transitionSpec = {
|
||||||
|
fadeIn() togetherWith fadeOut()
|
||||||
},
|
},
|
||||||
trailingContent = {
|
label = "Trailing transition"
|
||||||
if (isInEditMode) {
|
) { editing ->
|
||||||
|
if (editing) {
|
||||||
Row {
|
Row {
|
||||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onPrimaryContainer) {
|
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onPrimaryContainer) {
|
||||||
IconButton(onClick = { isInEditMode = false }) {
|
IconButton(onClick = { isInEditMode = false }) {
|
||||||
@@ -88,14 +172,18 @@ fun ManageListsScreen(
|
|||||||
Icon(Icons.Default.Edit, contentDescription = "Edit")
|
Icon(Icons.Default.Edit, contentDescription = "Edit")
|
||||||
}
|
}
|
||||||
IconButton(onClick = { viewModel.deleteTaskList(list.id!!) }) {
|
IconButton(onClick = { viewModel.deleteTaskList(list.id!!) }) {
|
||||||
Icon(Icons.Default.Delete, contentDescription = "Delete")
|
Icon(
|
||||||
}
|
Icons.Default.Delete,
|
||||||
}
|
contentDescription = "Delete"
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
HorizontalDivider()
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -61,7 +61,7 @@ fun TaskBottomSheet(
|
|||||||
ModalBottomSheet(onDismissRequest = onDismiss) {
|
ModalBottomSheet(onDismissRequest = onDismiss) {
|
||||||
Column(Modifier.padding(16.dp)) {
|
Column(Modifier.padding(16.dp)) {
|
||||||
Text(
|
Text(
|
||||||
"New Task",
|
if (viewModel.isEditing()) "Edit Task" else "New Task",
|
||||||
style = MaterialTheme.typography.titleLarge
|
style = MaterialTheme.typography.titleLarge
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
|
@@ -40,7 +40,9 @@ class MainViewModel @Inject constructor(
|
|||||||
AppDestination.TaskList(taskList.id!!, taskList.name)
|
AppDestination.TaskList(taskList.id!!, taskList.name)
|
||||||
} + AppDestination.ManageLists
|
} + AppDestination.ManageLists
|
||||||
isLoading = false
|
isLoading = false
|
||||||
if (!destinations.isEmpty()) startDestination = destinations.first()
|
if (startDestination == AppDestination.ManageLists && destinations.isNotEmpty()) {
|
||||||
|
startDestination = destinations.first()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
@@ -50,4 +50,21 @@ class ManageListsViewModel @Inject constructor(
|
|||||||
deleteTaskListUseCase(taskId)
|
deleteTaskListUseCase(taskId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun moveTaskList(fromIndex: Int, toIndex: Int) {
|
||||||
|
val mutable = taskLists.toMutableList()
|
||||||
|
val item = mutable.removeAt(fromIndex)
|
||||||
|
mutable.add(toIndex, item)
|
||||||
|
taskLists = mutable
|
||||||
|
}
|
||||||
|
|
||||||
|
fun commitTaskListOrder() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
taskLists.forEachIndexed { index, list ->
|
||||||
|
if (list.order != index) {
|
||||||
|
updateTaskListUseCase(list.id!!, list.name, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user