diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/tasklist/TaskListScreen.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/tasklist/TaskListScreen.kt index 1bf373c..d15d6c6 100644 --- a/app/src/main/java/de/jeanlucmakiola/agendula/ui/tasklist/TaskListScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/tasklist/TaskListScreen.kt @@ -52,6 +52,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MediumTopAppBar import androidx.compose.material3.Scaffold @@ -139,9 +140,12 @@ fun TaskListScreen( val list = content?.list val listName = list?.name val listId = (filter as? TaskFilter.OfList)?.listId - // Editing is offered for a device-only list. A collection that belongs to an - // account is the server's to rename or remove, not ours. + // Editing is offered for any writable list, device or synced — renaming and + // recolouring a synced collection is a server write the same sheet already + // handles. A read-only share keeps the icon but disables it: tapping it + // explains why rather than the button silently not being there. var editingList by rememberSaveable { mutableStateOf(false) } + var readOnlyHint by rememberSaveable { mutableStateOf(false) } val listWriteFailure by viewModel.listWriteFailure.collectAsStateWithLifecycle() val listDeleted by viewModel.listDeleted.collectAsStateWithLifecycle() // The list this screen is about is gone; there is nothing left to show. @@ -152,6 +156,12 @@ fun TaskListScreen( viewModel.clearListWriteFailure() } } + LaunchedEffect(readOnlyHint) { + if (readOnlyHint) { + delay(4_000) + readOnlyHint = false + } + } // One add affordance, never two: a real list with the setting on gets a pinned // bottom quick-add bar; everything else (incl. smart lists, which have no single // target list) gets the floating "New task" button. @@ -181,11 +191,20 @@ fun TaskListScreen( IconButton(onClick = { showSort = true }, enabled = content != null) { Icon(Icons.AutoMirrored.Rounded.Sort, contentDescription = stringResource(R.string.sort_title)) } - if (list != null && list.isLocal) { - IconButton(onClick = { editingList = true }) { + if (list != null) { + IconButton( + onClick = { + if (list.isReadOnly) readOnlyHint = true else editingList = true + }, + ) { Icon( Icons.Rounded.Edit, contentDescription = stringResource(R.string.list_edit_title), + tint = if (list.isReadOnly) { + LocalContentColor.current.copy(alpha = 0.38f) + } else { + LocalContentColor.current + }, ) } } @@ -230,23 +249,28 @@ fun TaskListScreen( .height(SnackChipHeight), contentAlignment = Alignment.CenterStart, ) { - // One chip, one anchor: the undo receipt takes precedence, and a - // refused list write reports itself once the undo window is clear. + // One chip, one anchor, in priority order: the undo receipt first, + // then a refused list write once the undo window is clear, then the + // read-only edit hint — the quietest of the three, so it never + // outranks something the other two are actually reporting. val failure = listWriteFailure - if (undo != null || failure == null) { - SnackChip( - visible = undo != null, + when { + undo != null -> SnackChip( + visible = true, message = stringResource( if (undo is UndoReceipt.Completed) R.string.task_completed else R.string.task_deleted, ), actionLabel = stringResource(R.string.undo), onAction = viewModel::undo, ) - } else { - SnackChip( + failure != null -> SnackChip( visible = true, message = stringResource(listWriteFailureMessage(failure)), ) + else -> SnackChip( + visible = readOnlyHint, + message = stringResource(R.string.list_read_only), + ) } } }