diff --git a/android/app/src/main/java/com/fabledsword/inkwell/ui/BoardFilters.kt b/android/app/src/main/java/com/fabledsword/inkwell/ui/BoardFilters.kt index c23b7c4..805c0e2 100644 --- a/android/app/src/main/java/com/fabledsword/inkwell/ui/BoardFilters.kt +++ b/android/app/src/main/java/com/fabledsword/inkwell/ui/BoardFilters.kt @@ -1,19 +1,16 @@ package com.fabledsword.inkwell.ui import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilterChip import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -115,53 +112,46 @@ internal fun FilterSheet( onChange: (BoardFilters) -> Unit, onDismiss: () -> Unit, ) { - ModalBottomSheet(onDismissRequest = onDismiss) { - Column( - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .navigationBarsPadding() - .verticalScroll(rememberScrollState()), - ) { - SheetTitle(R.string.filters) + Sheet( + title = R.string.filters, + onDismiss = onDismiss, + modifier = Modifier.verticalScroll(rememberScrollState()), + ) { + FilterHeading(R.string.filters_show) + FlowRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + FilterChip( + selected = filters.hasAttachment, + onClick = { onChange(filters.copy(hasAttachment = !filters.hasAttachment)) }, + label = { Text(stringResource(R.string.filters_has_attachment)) }, + ) + FilterChip( + selected = filters.sharedWithMe, + onClick = { onChange(filters.copy(sharedWithMe = !filters.sharedWithMe)) }, + label = { Text(stringResource(R.string.filters_shared_with_me)) }, + ) + } - FilterHeading(R.string.filters_show) + // No heading and no empty row when there are no tags yet: a section that + // can only ever say "none" is noise in a sheet this small. + if (labels.isNotEmpty()) { + FilterHeading(R.string.filters_tags) FlowRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) { - FilterChip( - selected = filters.hasAttachment, - onClick = { onChange(filters.copy(hasAttachment = !filters.hasAttachment)) }, - label = { Text(stringResource(R.string.filters_has_attachment)) }, - ) - FilterChip( - selected = filters.sharedWithMe, - onClick = { onChange(filters.copy(sharedWithMe = !filters.sharedWithMe)) }, - label = { Text(stringResource(R.string.filters_shared_with_me)) }, - ) - } - - // No heading and no empty row when there are no tags yet: a section that - // can only ever say "none" is noise in a sheet this small. - if (labels.isNotEmpty()) { - FilterHeading(R.string.filters_tags) - FlowRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) { - labels.forEach { label -> - FilterChip( - selected = label.id in filters.labelIds, - onClick = { onChange(filters.toggleLabel(label.id)) }, - label = { Text(label.name) }, - ) - } + labels.forEach { label -> + FilterChip( + selected = label.id in filters.labelIds, + onClick = { onChange(filters.toggleLabel(label.id)) }, + label = { Text(label.name) }, + ) } } + } - if (filters.count > 0) { - TextButton( - onClick = { onChange(BoardFilters()) }, - modifier = Modifier.align(Alignment.End), - ) { - Text(stringResource(R.string.filters_clear)) - } + if (filters.count > 0) { + TextButton( + onClick = { onChange(BoardFilters()) }, + modifier = Modifier.align(Alignment.End), + ) { + Text(stringResource(R.string.filters_clear)) } } } diff --git a/android/app/src/main/java/com/fabledsword/inkwell/ui/EditorPickers.kt b/android/app/src/main/java/com/fabledsword/inkwell/ui/EditorPickers.kt index bb6a2aa..25826cd 100644 --- a/android/app/src/main/java/com/fabledsword/inkwell/ui/EditorPickers.kt +++ b/android/app/src/main/java/com/fabledsword/inkwell/ui/EditorPickers.kt @@ -1,8 +1,10 @@ package com.fabledsword.inkwell.ui +import androidx.annotation.StringRes import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn @@ -85,69 +87,62 @@ fun LabelSheet( .map { it.id } .toSet() - ModalBottomSheet(onDismissRequest = onDismiss) { - Column( - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .imePadding() - .navigationBarsPadding(), - ) { - SheetTitle(R.string.label_picker_title) + Sheet( + title = R.string.label_picker_title, + onDismiss = onDismiss, + modifier = Modifier.imePadding(), + ) { + PlainTextField( + value = typed, + onValueChange = { typed = it }, + hint = R.string.label_new_hint, + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = + KeyboardActions(onDone = { + onAction(EditorAction.CreateLabel(typed)) + typed = "" + }), + ) - PlainTextField( - value = typed, - onValueChange = { typed = it }, - hint = R.string.label_new_hint, - singleLine = true, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = - KeyboardActions(onDone = { - onAction(EditorAction.CreateLabel(typed)) - typed = "" - }), - ) - - // Capped rather than unbounded: a sheet that grows past the screen - // makes its own scroll fight the sheet's drag gesture. - LazyColumn(modifier = Modifier.heightIn(max = LABEL_LIST_MAX_HEIGHT)) { - items(items = labels, key = { it.id }) { label -> - val fromTag = label.id in viaTag - Row(verticalAlignment = Alignment.CenterVertically) { - Checkbox( - checked = fromTag || label.id in manual, - enabled = !fromTag, - onCheckedChange = { on -> - val next = if (on) manual + label.id else manual - label.id - onAction(EditorAction.SetLabels(next.toList())) - }, - ) + // Capped rather than unbounded: a sheet that grows past the screen + // makes its own scroll fight the sheet's drag gesture. + LazyColumn(modifier = Modifier.heightIn(max = LABEL_LIST_MAX_HEIGHT)) { + items(items = labels, key = { it.id }) { label -> + val fromTag = label.id in viaTag + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox( + checked = fromTag || label.id in manual, + enabled = !fromTag, + onCheckedChange = { on -> + val next = if (on) manual + label.id else manual - label.id + onAction(EditorAction.SetLabels(next.toList())) + }, + ) + Text( + text = label.name, + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.padding(start = 4.dp), + ) + if (fromTag) { Text( - text = label.name, - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(start = 4.dp), + text = stringResource(R.string.label_from_tag), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(start = 8.dp), ) - if (fromTag) { - Text( - text = stringResource(R.string.label_from_tag), - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(start = 8.dp), - ) - } } } } + } - if (labels.isEmpty()) { - Text( - text = stringResource(R.string.label_none_body), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(vertical = 16.dp), - ) - } + if (labels.isEmpty()) { + Text( + text = stringResource(R.string.label_none_body), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(vertical = 16.dp), + ) } } } @@ -170,60 +165,53 @@ fun ReminderSheet( ) { var exact by remember { mutableStateOf(false) } - ModalBottomSheet(onDismissRequest = onDismiss) { - Column( - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .navigationBarsPadding(), - ) { - SheetTitle(R.string.reminder_title) - - reminderPresets().forEach { (labelRes, at) -> - Text( - text = "${stringResource(labelRes)} · ${formatInstant(rfc3339(at))}", - style = MaterialTheme.typography.bodyLarge, - modifier = - Modifier - .fillMaxWidth() - .clickable { - onAction(EditorAction.SetReminder(rfc3339(at))) - onDismiss() - }.padding(vertical = 12.dp), - ) - } - + Sheet( + title = R.string.reminder_title, + onDismiss = onDismiss, + ) { + reminderPresets().forEach { (labelRes, at) -> Text( - text = stringResource(R.string.reminder_pick), + text = "${stringResource(labelRes)} · ${formatInstant(rfc3339(at))}", style = MaterialTheme.typography.bodyLarge, modifier = Modifier .fillMaxWidth() - .clickable { exact = true } - .padding(vertical = 12.dp), + .clickable { + onAction(EditorAction.SetReminder(rfc3339(at))) + onDismiss() + }.padding(vertical = 12.dp), ) + } - // Repeat only appears once there IS a reminder — a recurrence rule on - // a note with no time to recur from is a setting that does nothing. - if (note.remindAt != null) { - RecurrenceChips( - current = note.recurrence, - onPick = { onAction(EditorAction.SetRecurrence(it)) }, - ) - Text( - text = stringResource(R.string.reminder_clear), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.error, - modifier = - Modifier - .fillMaxWidth() - .clickable { - onAction(EditorAction.ClearReminder) - onDismiss() - }.padding(vertical = 12.dp), - ) - } + Text( + text = stringResource(R.string.reminder_pick), + style = MaterialTheme.typography.bodyLarge, + modifier = + Modifier + .fillMaxWidth() + .clickable { exact = true } + .padding(vertical = 12.dp), + ) + + // Repeat only appears once there IS a reminder — a recurrence rule on + // a note with no time to recur from is a setting that does nothing. + if (note.remindAt != null) { + RecurrenceChips( + current = note.recurrence, + onPick = { onAction(EditorAction.SetRecurrence(it)) }, + ) + Text( + text = stringResource(R.string.reminder_clear), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.error, + modifier = + Modifier + .fillMaxWidth() + .clickable { + onAction(EditorAction.ClearReminder) + onDismiss() + }.padding(vertical = 12.dp), + ) } } @@ -337,6 +325,36 @@ private fun RecurrenceChips( } } +/** + * A bottom sheet with its title: full width, the screen's side margin, and clear of + * the navigation bar. [modifier] adds to that, after the inset (ime padding, a + * scroll, extra space at the bottom). + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun Sheet( + @StringRes title: Int, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, + verticalArrangement: Arrangement.Vertical = Arrangement.Top, + content: @Composable ColumnScope.() -> Unit, +) { + ModalBottomSheet(onDismissRequest = onDismiss) { + Column( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .navigationBarsPadding() + .then(modifier), + verticalArrangement = verticalArrangement, + ) { + SheetTitle(title) + content() + } + } +} + @Composable internal fun SheetTitle(labelRes: Int) { Text( diff --git a/android/app/src/main/java/com/fabledsword/inkwell/ui/ShareSheet.kt b/android/app/src/main/java/com/fabledsword/inkwell/ui/ShareSheet.kt index 8d4c27b..f154305 100644 --- a/android/app/src/main/java/com/fabledsword/inkwell/ui/ShareSheet.kt +++ b/android/app/src/main/java/com/fabledsword/inkwell/ui/ShareSheet.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -18,7 +17,6 @@ import androidx.compose.material3.FilterChip import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.RadioButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -75,23 +73,17 @@ fun ShareSheet( onUnshare: (shareId: String) -> Unit, onDismiss: () -> Unit, ) { - ModalBottomSheet(onDismissRequest = onDismiss) { - Column( - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .padding(bottom = 16.dp) - .navigationBarsPadding(), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - SheetTitle(R.string.share_title) - when { - state.loading -> Muted(stringResource(R.string.share_loading)) - state.needsServer -> Muted(stringResource(R.string.share_needs_server)) - state.loadError != null -> ErrorText(state.loadError) - else -> ShareBody(state, onShare, onUnshare) - } + Sheet( + title = R.string.share_title, + onDismiss = onDismiss, + modifier = Modifier.padding(bottom = 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + when { + state.loading -> Muted(stringResource(R.string.share_loading)) + state.needsServer -> Muted(stringResource(R.string.share_needs_server)) + state.loadError != null -> ErrorText(state.loadError) + else -> ShareBody(state, onShare, onUnshare) } } }