diff --git a/tasks-app-shared/src/commonMain/kotlin/net/opatry/tasks/app/ui/component/TaskMenu.kt b/tasks-app-shared/src/commonMain/kotlin/net/opatry/tasks/app/ui/component/TaskMenu.kt index e3bdffe3..859507da 100644 --- a/tasks-app-shared/src/commonMain/kotlin/net/opatry/tasks/app/ui/component/TaskMenu.kt +++ b/tasks-app-shared/src/commonMain/kotlin/net/opatry/tasks/app/ui/component/TaskMenu.kt @@ -53,33 +53,40 @@ import net.opatry.tasks.resources.task_menu_new_list import net.opatry.tasks.resources.task_menu_unindent import org.jetbrains.compose.resources.stringResource -sealed class TaskMenuAction { - data object Dismiss : TaskMenuAction() - data object AddSubTask : TaskMenuAction() - data object MoveToTop : TaskMenuAction() - data object Unindent : TaskMenuAction() - data object Indent : TaskMenuAction() - data class MoveToList(val targetParentList: TaskListUIModel) : TaskMenuAction() - data object MoveToNewList : TaskMenuAction() - data object Delete : TaskMenuAction() +sealed class TaskAction { + data object ToggleCompletion : TaskAction() + data object Edit : TaskAction() + data object UpdateDueDate : TaskAction() + data object AddSubTask : TaskAction() + data object MoveToTop : TaskAction() + data object Unindent : TaskAction() + data object Indent : TaskAction() + data class MoveToList(val targetParentList: TaskListUIModel) : TaskAction() + data object MoveToNewList : TaskAction() + data object Delete : TaskAction() } @Composable -fun TaskMenu(taskLists: List, task: TaskUIModel, expanded: Boolean, onAction: (TaskMenuAction) -> Unit) { +fun TaskMenu( + taskLists: List, + task: TaskUIModel, + expanded: Boolean, + onAction: (TaskAction?) -> Unit +) { val currentTaskList = taskLists.firstOrNull { it.tasks.map(TaskUIModel::id).contains(task.id) } val taskPosition by remember(currentTaskList) { mutableStateOf(currentTaskList?.tasks?.indexOf(task) ?: -1) } val canMoveToTop by remember(task) { derivedStateOf { taskPosition > 0 && task.canIndent } } DropdownMenu( expanded = expanded, - onDismissRequest = { onAction(TaskMenuAction.Dismiss) } + onDismissRequest = { onAction(null) } ) { if (canMoveToTop) { DropdownMenuItem( text = { RowWithIcon(stringResource(Res.string.task_menu_move_to_top)) }, - onClick = { onAction(TaskMenuAction.MoveToTop) }, + onClick = { onAction(TaskAction.MoveToTop) }, enabled = false ) } @@ -89,7 +96,7 @@ fun TaskMenu(taskLists: List, task: TaskUIModel, expanded: Bool text = { RowWithIcon(stringResource(Res.string.task_menu_add_subtask), LucideIcons.SquareStack) }, - onClick = { onAction(TaskMenuAction.AddSubTask) }, + onClick = { onAction(TaskAction.AddSubTask) }, enabled = false ) } @@ -99,7 +106,7 @@ fun TaskMenu(taskLists: List, task: TaskUIModel, expanded: Bool text = { RowWithIcon(stringResource(Res.string.task_menu_indent)) }, - onClick = { onAction(TaskMenuAction.Indent) }, + onClick = { onAction(TaskAction.Indent) }, enabled = false ) } @@ -109,7 +116,7 @@ fun TaskMenu(taskLists: List, task: TaskUIModel, expanded: Bool text = { RowWithIcon(stringResource(Res.string.task_menu_unindent)) }, - onClick = { onAction(TaskMenuAction.Unindent) }, + onClick = { onAction(TaskAction.Unindent) }, enabled = false ) } @@ -128,7 +135,7 @@ fun TaskMenu(taskLists: List, task: TaskUIModel, expanded: Bool text = { RowWithIcon(stringResource(Res.string.task_menu_new_list), LucideIcons.ListPlus) }, - onClick = { onAction(TaskMenuAction.MoveToNewList) }, + onClick = { onAction(TaskAction.MoveToNewList) }, enabled = false, ) @@ -147,7 +154,7 @@ fun TaskMenu(taskLists: List, task: TaskUIModel, expanded: Bool } }, enabled = taskList.id != currentTaskList?.id, - onClick = { onAction(TaskMenuAction.MoveToList(taskList)) } + onClick = { onAction(TaskAction.MoveToList(taskList)) } ) } } @@ -160,7 +167,7 @@ fun TaskMenu(taskLists: List, task: TaskUIModel, expanded: Bool RowWithIcon(stringResource(Res.string.task_menu_delete), LucideIcons.Trash2) } }, - onClick = { onAction(TaskMenuAction.Delete) } + onClick = { onAction(TaskAction.Delete) } ) } } \ No newline at end of file diff --git a/tasks-app-shared/src/commonMain/kotlin/net/opatry/tasks/app/ui/screen/tasksPane.kt b/tasks-app-shared/src/commonMain/kotlin/net/opatry/tasks/app/ui/screen/tasksPane.kt index 4df1ee4c..4d2e1d00 100644 --- a/tasks-app-shared/src/commonMain/kotlin/net/opatry/tasks/app/ui/screen/tasksPane.kt +++ b/tasks-app-shared/src/commonMain/kotlin/net/opatry/tasks/app/ui/screen/tasksPane.kt @@ -118,10 +118,10 @@ import net.opatry.tasks.app.ui.component.EditTextDialog import net.opatry.tasks.app.ui.component.EmptyState import net.opatry.tasks.app.ui.component.MissingScreen import net.opatry.tasks.app.ui.component.RowWithIcon +import net.opatry.tasks.app.ui.component.TaskAction import net.opatry.tasks.app.ui.component.TaskListMenu import net.opatry.tasks.app.ui.component.TaskListMenuAction import net.opatry.tasks.app.ui.component.TaskMenu -import net.opatry.tasks.app.ui.component.TaskMenuAction import net.opatry.tasks.app.ui.model.DateRange import net.opatry.tasks.app.ui.model.TaskListUIModel import net.opatry.tasks.app.ui.model.TaskUIModel @@ -648,21 +648,24 @@ fun TasksColumn( } } items(tasks, key = TaskUIModel::id) { task -> - TaskRow( + RemainingTaskRow( taskLists, task, - showDate = taskSorting == TaskListSorting.Manual, - onToggleCompletionState = { onToggleCompletionState(task) }, - onEditTask = { onEditTask(task) }, - onUpdateDueDate = { onUpdateDueDate(task) }, - onNewSubTask = { onNewSubTask(task) }, - onUnindent = { onUnindent(task) }, - onIndent = { onIndent(task) }, - onMoveToTop = { onMoveToTop(task) }, - onMoveToList = { onMoveToList(task, it) }, - onMoveToNewList = { onMoveToNewList(task) }, - onDeleteTask = { onDeleteTask(task) }, - ) + simpleDisplay = taskSorting == TaskListSorting.DueDate + ) { action -> + when (action) { + TaskAction.ToggleCompletion -> onToggleCompletionState(task) + TaskAction.Edit -> onEditTask(task) + TaskAction.UpdateDueDate -> onUpdateDueDate(task) + TaskAction.AddSubTask -> onNewSubTask(task) + TaskAction.Unindent -> onUnindent(task) + TaskAction.Indent -> onIndent(task) + TaskAction.MoveToTop -> onMoveToTop(task) + is TaskAction.MoveToList -> onMoveToList(task, action.targetParentList) + TaskAction.MoveToNewList -> onMoveToNewList(task) + TaskAction.Delete -> onDeleteTask(task) + } + } } } @@ -678,10 +681,9 @@ fun TasksColumn( ) { RowWithIcon( icon = { - if (showCompleted) { - Icon(LucideIcons.ChevronDown, null) - } else { - Icon(LucideIcons.ChevronRight, null) + when { + showCompleted -> Icon(LucideIcons.ChevronDown, null) + else -> Icon(LucideIcons.ChevronRight, null) } } ) { @@ -693,22 +695,20 @@ fun TasksColumn( } } } + if (showCompleted) { items(completedTasks, key = TaskUIModel::id) { task -> - TaskRow( - taskLists, + CompletedTaskRow( task, - showDate = true, - onToggleCompletionState = { onToggleCompletionState(task) }, - onEditTask = { onEditTask(task) }, - onUpdateDueDate = { onUpdateDueDate(task) }, - onNewSubTask = { onNewSubTask(task) }, - onUnindent = { onUnindent(task) }, - onIndent = { onIndent(task) }, - onMoveToTop = { onMoveToTop(task) }, - onMoveToList = { onMoveToList(task, it) }, - onMoveToNewList = { onMoveToNewList(task) }, - onDeleteTask = { onDeleteTask(task) }, + onAction = { action -> + when (action) { + TaskAction.ToggleCompletion -> onToggleCompletionState(task) + TaskAction.Edit -> onEditTask(task) + TaskAction.UpdateDueDate -> onUpdateDueDate(task) + TaskAction.Delete -> onDeleteTask(task) + else -> Unit + } + }, ) } } @@ -769,36 +769,24 @@ fun DateRange.toLabel(sectionLabel: Boolean = false): String = when (this) { } @Composable -fun TaskRow( +private fun RemainingTaskRow( taskLists: List, task: TaskUIModel, modifier: Modifier = Modifier, - showDate: Boolean = true, - onToggleCompletionState: () -> Unit = {}, - onEditTask: () -> Unit = {}, - onUpdateDueDate: () -> Unit = {}, - onNewSubTask: () -> Unit = {}, - onUnindent: () -> Unit = {}, - onIndent: () -> Unit = {}, - onMoveToTop: () -> Unit = {}, - onMoveToList: (TaskListUIModel) -> Unit = {}, - onMoveToNewList: () -> Unit = {}, - onDeleteTask: () -> Unit = {}, + simpleDisplay: Boolean = false, + onAction: (TaskAction) -> Unit, ) { var showContextualMenu by remember { mutableStateOf(false) } - // TODO remember? - val (taskCheckIcon, taskCheckIconColor) = when { - task.isCompleted -> LucideIcons.CircleCheckBig to MaterialTheme.colorScheme.primary - else -> LucideIcons.Circle to MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) - } - - Row(modifier.clickable(onClick = onEditTask)) { + Row(modifier.clickable(onClick = { onAction(TaskAction.Edit) })) { IconButton( - onClick = onToggleCompletionState, - Modifier.padding(start = 36.dp * task.indent) + onClick = { onAction(TaskAction.ToggleCompletion) }, + modifier = if (simpleDisplay) + Modifier + else + Modifier.padding(start = 36.dp * task.indent) ) { - Icon(taskCheckIcon, null, tint = taskCheckIconColor) + Icon(LucideIcons.Circle, null, tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)) } Column( Modifier @@ -807,7 +795,6 @@ fun TaskRow( ) { Text( task.title, - textDecoration = TextDecoration.LineThrough.takeIf { task.isCompleted }, style = MaterialTheme.typography.titleMedium, overflow = TextOverflow.Ellipsis, maxLines = 1 @@ -821,46 +808,78 @@ fun TaskRow( maxLines = 2 ) } - if (showDate && task.dueDate != null) { + if (!simpleDisplay && task.dueDate != null) { AssistChip( - onClick = onUpdateDueDate, + onClick = { onAction(TaskAction.UpdateDueDate) }, shape = MaterialTheme.shapes.large, label = { Text( task.dateRange.toLabel(), - color = if (task.isCompleted) - MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) - else - task.dateRange.toColor() + color = task.dateRange.toColor() ) }, ) } } - if (task.isCompleted) { - IconButton(onClick = onDeleteTask) { - Icon(LucideIcons.Trash, stringResource(Res.string.task_list_pane_delete_task_icon_content_desc)) + Box { + IconButton(onClick = { showContextualMenu = true }) { + Icon(LucideIcons.EllipsisVertical, stringResource(Res.string.task_list_pane_task_options_icon_content_desc)) } - } else { - Box { - IconButton(onClick = { showContextualMenu = true }) { - Icon(LucideIcons.EllipsisVertical, stringResource(Res.string.task_list_pane_task_options_icon_content_desc)) - } - TaskMenu(taskLists, task, showContextualMenu) { action -> - showContextualMenu = false - when (action) { - TaskMenuAction.Dismiss -> Unit - TaskMenuAction.AddSubTask -> onNewSubTask() - TaskMenuAction.Indent -> onIndent() - TaskMenuAction.Unindent -> onUnindent() - TaskMenuAction.MoveToTop -> onMoveToTop() - is TaskMenuAction.MoveToList -> onMoveToList(action.targetParentList) - TaskMenuAction.MoveToNewList -> onMoveToNewList() - TaskMenuAction.Delete -> onDeleteTask() - } - } + TaskMenu(taskLists, task, showContextualMenu) { action -> + showContextualMenu = false + action?.let(onAction) + } + } + } +} + +@Composable +private fun CompletedTaskRow( + task: TaskUIModel, + onAction: (TaskAction) -> Unit, +) { + Row(Modifier.clickable(onClick = { onAction(TaskAction.Edit) })) { + IconButton(onClick = { onAction(TaskAction.ToggleCompletion) }) { + Icon(LucideIcons.CircleCheckBig, null, tint = MaterialTheme.colorScheme.primary) + } + Column( + Modifier + .weight(1f) + .padding(vertical = 8.dp) + ) { + Text( + task.title, + textDecoration = TextDecoration.LineThrough, + style = MaterialTheme.typography.titleMedium, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + + if (task.notes.isNotBlank()) { + Text( + task.notes, + style = MaterialTheme.typography.bodySmall, + overflow = TextOverflow.Ellipsis, + maxLines = 2 + ) + } + if (task.dueDate != null) { + AssistChip( + onClick = { onAction(TaskAction.UpdateDueDate) }, + shape = MaterialTheme.shapes.large, + label = { + Text( + task.dateRange.toLabel(), + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + }, + ) } } + + IconButton(onClick = { onAction(TaskAction.Delete) }) { + Icon(LucideIcons.Trash, stringResource(Res.string.task_list_pane_delete_task_icon_content_desc)) + } } } @@ -871,7 +890,7 @@ private fun TaskRowScaffold( dueDate: LocalDate? = Clock.System.todayIn(TimeZone.currentSystemDefault()), isCompleted: Boolean = false ) { - TaskRow( + RemainingTaskRow( emptyList(), TaskUIModel( id = 0L, @@ -879,8 +898,8 @@ private fun TaskRowScaffold( notes = notes, dueDate = dueDate, isCompleted = isCompleted - ), - ) + ) + ) {} } @TaskfolioPreview