From 4501f1599939f32bfafe9a5f40b3270538280ab8 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Thu, 15 Oct 2026 17:35:30 +0200 Subject: [PATCH] Use floret's checked, switch and custom rows, hints and option rows --- .../calendula/ui/calendars/BackupScreen.kt | 15 +-- .../calendula/ui/calendars/CalendarsScreen.kt | 18 +-- .../ui/common/CalendarPickerGroups.kt | 9 +- .../calendula/ui/common/Picker.kt | 121 ++++-------------- .../calendula/ui/common/TimeZonePicker.kt | 15 +-- .../calendula/ui/edit/EventEditScreen.kt | 43 +------ .../calendula/ui/onboarding/ViewStep.kt | 21 +-- .../ui/settings/AppearanceSettings.kt | 24 +--- .../ui/settings/EventFormSettings.kt | 53 ++------ .../ui/settings/MonthViewStylePicker.kt | 34 ++--- .../ui/settings/NotificationSettings.kt | 38 ++---- .../calendula/ui/settings/PastEventsPicker.kt | 27 ++-- .../calendula/ui/settings/SettingsCommon.kt | 15 --- .../ui/settings/SpecialDatesSettings.kt | 62 +++------ .../calendula/ui/settings/ViewsSettings.kt | 109 +++++----------- .../calendula/ui/settings/WeekStartPicker.kt | 34 ++--- .../calendula/ui/settings/WidgetSettings.kt | 6 +- 17 files changed, 171 insertions(+), 473 deletions(-) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/BackupScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/BackupScreen.kt index 96e8c024..02782798 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/BackupScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/BackupScreen.kt @@ -1,7 +1,6 @@ package de.jeanlucmakiola.calendula.ui.calendars import android.net.Uri -import android.text.format.DateUtils import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Box @@ -58,10 +57,12 @@ import de.jeanlucmakiola.floret.components.CollapsingScaffold import de.jeanlucmakiola.floret.components.DialogAmountField import de.jeanlucmakiola.floret.components.DialogUnitDropdown import de.jeanlucmakiola.floret.components.FullScreenPicker +import de.jeanlucmakiola.floret.components.GroupedHint import de.jeanlucmakiola.floret.components.GroupedRow import de.jeanlucmakiola.floret.components.GroupedSectionHeader import de.jeanlucmakiola.floret.components.Position import de.jeanlucmakiola.floret.components.positionOf +import de.jeanlucmakiola.floret.locale.relativeTimeAgo import java.time.LocalDate // SAF mime filter for the restore picker. Source apps hand `.ics` files out @@ -224,7 +225,7 @@ private fun BackupContent( if (exportable.isEmpty()) { GroupedSectionHeader(stringResource(R.string.calendars_restore_header)) - HintText(restoreSummary) + GroupedHint(restoreSummary, style = MaterialTheme.typography.bodySmall) GroupedRow( title = stringResource(R.string.calendars_restore_action), position = Position.Alone, @@ -234,7 +235,7 @@ private fun BackupContent( return } - HintText(stringResource(R.string.calendars_backup_hint)) + GroupedHint(stringResource(R.string.calendars_backup_hint), style = MaterialTheme.typography.bodySmall) GroupedRow( title = stringResource(R.string.calendars_backup_action), position = Position.Top, @@ -273,7 +274,7 @@ private fun BackupContent( position = Position.Bottom, onClick = onEditInterval, ) - HintText(backupStatusText(autoBackup.status)) + GroupedHint(backupStatusText(autoBackup.status), style = MaterialTheme.typography.bodySmall) } } @@ -312,7 +313,7 @@ private fun ExportCalendarPicker( onDismiss = onDismiss, predictiveBack = true, ) { - HintText(stringResource(R.string.calendars_export_hint)) + GroupedHint(stringResource(R.string.calendars_export_hint), style = MaterialTheme.typography.bodySmall) calendars.forEachIndexed { index, calendar -> val isSelected = calendar.id in selected GroupedRow( @@ -378,9 +379,7 @@ private fun backupIntervalLabel(minutes: Long): String { @Composable private fun backupStatusText(status: BackupStatus): String { if (status.lastRun == 0L) return stringResource(R.string.calendars_auto_backup_status_never) - val relative = DateUtils.getRelativeTimeSpanString( - status.lastRun, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, - ).toString() + val relative = relativeTimeAgo(status.lastRun) return if (status.lastSuccess) { stringResource(R.string.calendars_auto_backup_status_ok, relative) } else { diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt index aad86c98..e0702494 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt @@ -83,12 +83,12 @@ import de.jeanlucmakiola.calendula.ui.common.LeadingAvatar import de.jeanlucmakiola.calendula.ui.common.SourceLogo import de.jeanlucmakiola.calendula.ui.common.curatedSourcePackage import de.jeanlucmakiola.floret.components.CollapsingScaffold +import de.jeanlucmakiola.floret.components.GroupedHint import de.jeanlucmakiola.floret.components.GroupedSectionHeader import de.jeanlucmakiola.floret.identity.LocalFloretDarkTheme import de.jeanlucmakiola.floret.identity.collapseExit import de.jeanlucmakiola.floret.identity.expandEnter import de.jeanlucmakiola.floret.identity.predictiveBack -import de.jeanlucmakiola.floret.components.GroupedListInset import de.jeanlucmakiola.floret.components.GroupedRow import de.jeanlucmakiola.floret.components.InlineTextField import de.jeanlucmakiola.floret.components.Position @@ -214,7 +214,7 @@ private fun CalendarsList( visible = visible, ) { // What the per-calendar / per-account switches below actually do. - HintText(stringResource(R.string.calendars_visibility_hint)) + GroupedHint(stringResource(R.string.calendars_visibility_hint), style = MaterialTheme.typography.bodySmall) // Local (device-only) calendars — one collapsible group. The header's // "+" adds a calendar; the switch enables/disables them all at once; @@ -235,7 +235,7 @@ private fun CalendarsList( onToggleAll = { enabled -> onSetAccountVisible(local.map { it.id }, enabled) }, ) { if (local.isEmpty()) { - HintText(stringResource(R.string.calendars_local_empty)) + GroupedHint(stringResource(R.string.calendars_local_empty), style = MaterialTheme.typography.bodySmall) } else { local.forEachIndexed { index, calendar -> val disabled = !calendar.isVisibleInSystem @@ -282,7 +282,7 @@ private fun CalendarsList( // collapsible group whose header opens the source app (icon) and toggles // all of its calendars at once (switch). GroupedSectionHeader(stringResource(R.string.calendars_synced_header)) - HintText(stringResource(R.string.calendars_synced_hint)) + GroupedHint(stringResource(R.string.calendars_synced_hint), style = MaterialTheme.typography.bodySmall) synced .groupByAccount() .forEach { group -> @@ -830,16 +830,6 @@ private fun CalendarGroupMenu( } } -@Composable -internal fun HintText(text: String) { - Text( - text = text, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(horizontal = GroupedListInset, vertical = 4.dp), - ) -} - /** * Pick the app to open for managing a synced calendar's account. The account's diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarPickerGroups.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarPickerGroups.kt index 802bbb8e..d9a6ce9f 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarPickerGroups.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarPickerGroups.kt @@ -34,7 +34,6 @@ import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.calendula.domain.CalendarSource import de.jeanlucmakiola.floret.components.GroupedRow import de.jeanlucmakiola.floret.components.Position -import de.jeanlucmakiola.floret.components.SelectedCheck /** * The app's single "which calendar" selection list, shared by the event editor @@ -114,17 +113,11 @@ private fun CalendarPickerGroup( leading = leading, ) calendars.forEachIndexed { index, calendar -> - val isSelected = calendar.id == selectedId GroupedRow( title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) }, position = if (index == calendars.lastIndex) Position.Bottom else Position.Middle, - selected = isSelected, + checked = calendar.id == selectedId, leading = { CalendarColorChip(calendar.color) }, - trailing = if (isSelected) { - { SelectedCheck() } - } else { - null - }, onClick = { onSelect(calendar.id) }, ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/Picker.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/Picker.kt index 41c857d1..f2505b2e 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/Picker.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/Picker.kt @@ -1,12 +1,8 @@ package de.jeanlucmakiola.calendula.ui.common -import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.material3.Checkbox -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -20,17 +16,16 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.floret.components.CustomAmountEditor +import de.jeanlucmakiola.floret.components.CustomExpandRow import de.jeanlucmakiola.floret.components.FullScreenPicker -import de.jeanlucmakiola.floret.components.GroupedListInset +import de.jeanlucmakiola.floret.components.GroupedHint import de.jeanlucmakiola.floret.components.GroupedRow import de.jeanlucmakiola.floret.components.Position -import de.jeanlucmakiola.floret.components.SelectedCheck import de.jeanlucmakiola.floret.components.positionOf -import de.jeanlucmakiola.floret.identity.collapseExit -import de.jeanlucmakiola.floret.identity.expandEnter import de.jeanlucmakiola.floret.locale.currentLocale import de.jeanlucmakiola.floret.reminders.ReminderOverride import de.jeanlucmakiola.floret.reminders.ReminderUnit +import de.jeanlucmakiola.floret.reminders.minutesOrEmpty import de.jeanlucmakiola.floret.reminders.reminderOverrideForMinutes import de.jeanlucmakiola.calendula.ui.agenda.AgendaRange import de.jeanlucmakiola.calendula.ui.agenda.agendaRangeWindowSummary @@ -88,7 +83,7 @@ fun ReminderDefaultPicker( } val inherits = current is ReminderOverride.Inherit val isNone = current is ReminderOverride.None - val selectedMinutes = (current as? ReminderOverride.Minutes)?.minutes.orEmpty() + val selectedMinutes = current.minutesOrEmpty() // Custom (non-preset) lead times seen this session, so unchecking one keeps // its row (unchecked) until the picker closes instead of vanishing mid-tap, // which would strand a hand-entered value with no way to re-check it. @@ -119,24 +114,14 @@ fun ReminderDefaultPicker( GroupedRow( title = stringResource(R.string.reminder_use_default), position = Position.Top, - selected = inherits, - trailing = if (inherits) { - { SelectedCheck() } - } else { - null - }, + checked = inherits, onClick = { apply(ReminderOverride.Inherit) }, ) } GroupedRow( title = stringResource(R.string.reminder_none), position = if (allowInherit) Position.Bottom else Position.Alone, - selected = isNone, - trailing = if (isNone) { - { SelectedCheck() } - } else { - null - }, + checked = isNone, onClick = { apply(ReminderOverride.None) }, ) Spacer(Modifier.height(24.dp)) @@ -152,17 +137,11 @@ fun ReminderDefaultPicker( onClick = { toggle(minute) }, ) } - // When expanded, the Custom row connects downward into the editor card - // so the two read as one grouped container (the per-calendar pattern). - GroupedRow( + CustomExpandRow( title = stringResource(R.string.event_edit_reminder_custom), - position = if (customExpanded) Position.Top else positionOf(rows.size, rowCount), - onClick = { customExpanded = !customExpanded }, - ) - AnimatedVisibility( - visible = customExpanded, - enter = expandEnter(), - exit = collapseExit(), + position = positionOf(rows.size, rowCount), + expanded = customExpanded, + onExpandedChange = { customExpanded = it }, ) { CustomReminderEditor( amountText = amountText, @@ -210,19 +189,6 @@ private fun CustomReminderEditor( ) } -/** A short explanatory paragraph shown under a picker's title, above the rows, - * on the same left margin as the rows themselves. */ -@Composable -internal fun PickerDescription(text: String) { - Text( - text = text, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(horizontal = GroupedListInset, vertical = 8.dp), - ) -} - - /** * Agenda-range picker, full-screen. Two grouped lists — the calendar-aligned * options (today / this week / this month) and the rolling windows (next 7 / 30 @@ -263,17 +229,11 @@ fun AgendaRangePicker( } val rangeRow: @Composable (AgendaRange, Position) -> Unit = { option, position -> - val isSelected = option == selected GroupedRow( title = agendaRangeLabel(option), summary = windowSummary(option), position = position, - selected = isSelected, - trailing = if (isSelected) { - { SelectedCheck() } - } else { - null - }, + checked = option == selected, onClick = { onSelect(option) onDismiss() @@ -282,7 +242,7 @@ fun AgendaRangePicker( } FullScreenPicker(title = title, onDismiss = onDismiss) { - PickerDescription(description) + GroupedHint(description, verticalPadding = 8.dp) // Calendar-aligned windows. calendarAligned.forEachIndexed { index, option -> @@ -295,7 +255,7 @@ fun AgendaRangePicker( rolling.forEachIndexed { index, option -> rangeRow(option, positionOf(index, rollingRowCount)) } - GroupedRow( + CustomExpandRow( title = if (customSelected) { agendaRangeLabel(selected) } else { @@ -304,19 +264,10 @@ fun AgendaRangePicker( // An unset custom window has no day count yet, so the row stays // single-line until it does. summary = if (customSelected) windowSummary(selected) else null, - position = if (customExpanded) Position.Top else positionOf(rolling.size, rollingRowCount), - selected = customSelected, - trailing = if (customSelected) { - { SelectedCheck() } - } else { - null - }, - onClick = { customExpanded = !customExpanded }, - ) - AnimatedVisibility( - visible = customExpanded, - enter = expandEnter(), - exit = collapseExit(), + position = positionOf(rolling.size, rollingRowCount), + expanded = customExpanded, + onExpandedChange = { customExpanded = it }, + checked = customSelected, ) { CustomDaysEditor( amountText = amountText, @@ -394,17 +345,12 @@ fun DurationPicker( } FullScreenPicker(title = title, onDismiss = onDismiss, predictiveBack = true) { - if (description != null) PickerDescription(description) + if (description != null) GroupedHint(description, verticalPadding = 8.dp) if (inheritLabel != null) { GroupedRow( title = inheritLabel, position = Position.Alone, - selected = inherits, - trailing = if (inherits) { - { SelectedCheck() } - } else { - null - }, + checked = inherits, onClick = { onSelect(null) onDismiss() @@ -413,39 +359,22 @@ fun DurationPicker( Spacer(Modifier.height(24.dp)) } presets.forEachIndexed { index, minute -> - val isSelected = minute == selected GroupedRow( title = label(minute), position = positionOf(index, rowCount), - selected = isSelected, - trailing = if (isSelected) { - { SelectedCheck() } - } else { - null - }, + checked = minute == selected, onClick = { onSelect(minute) onDismiss() }, ) } - // The Custom row connects downward into the editor card when expanded, so - // the two read as one grouped container (the shared custom-expand pattern). - GroupedRow( + CustomExpandRow( title = custom?.let { label(it) } ?: stringResource(R.string.event_edit_reminder_custom), - position = if (customExpanded) Position.Top else positionOf(presets.size, rowCount), - selected = custom != null, - trailing = if (custom != null) { - { SelectedCheck() } - } else { - null - }, - onClick = { customExpanded = !customExpanded }, - ) - AnimatedVisibility( - visible = customExpanded, - enter = expandEnter(), - exit = collapseExit(), + position = positionOf(presets.size, rowCount), + expanded = customExpanded, + onExpandedChange = { customExpanded = it }, + checked = custom != null, ) { CustomDurationEditor( amountText = amountText, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/TimeZonePicker.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/TimeZonePicker.kt index 13d43350..dc055fcf 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/TimeZonePicker.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/TimeZonePicker.kt @@ -42,7 +42,6 @@ import de.jeanlucmakiola.floret.components.GroupedRow import de.jeanlucmakiola.floret.components.GroupedSectionHeader import de.jeanlucmakiola.floret.components.InlineTextField import de.jeanlucmakiola.floret.components.Position -import de.jeanlucmakiola.floret.components.SelectedCheck import de.jeanlucmakiola.floret.components.positionOf import de.jeanlucmakiola.floret.locale.currentLocale import kotlinx.coroutines.Dispatchers @@ -117,13 +116,8 @@ fun TimeZonePickerDialog( title = stringResource(R.string.event_edit_timezone_device), summary = deviceSummary, position = Position.Alone, - selected = selected == null, + checked = selected == null, leading = { Icon(Icons.Default.Public, contentDescription = null) }, - trailing = if (selected == null) { - { SelectedCheck() } - } else { - null - }, onClick = { choose(null) }, ) } @@ -244,12 +238,7 @@ private fun ZoneRow( title = zone.label, summary = zoneDescriptor(zone), position = position, - selected = selected, - trailing = if (selected) { - { SelectedCheck() } - } else { - null - }, + checked = selected, onClick = onClick, ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt index 9663c5f5..9c99bb78 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt @@ -160,7 +160,6 @@ import de.jeanlucmakiola.floret.components.InlineTextField import de.jeanlucmakiola.floret.components.OptionCard import de.jeanlucmakiola.floret.components.OptionPicker import de.jeanlucmakiola.floret.components.Position -import de.jeanlucmakiola.floret.components.SelectedCheck import de.jeanlucmakiola.floret.components.groupedShape as floretGroupedShape import de.jeanlucmakiola.floret.components.positionOf import de.jeanlucmakiola.calendula.ui.common.REMINDER_PRESETS @@ -1672,12 +1671,7 @@ private fun RecurrencePickerDialog( GroupedRow( title = stringResource(R.string.event_edit_recurrence_none), position = positionOf(0, rowCount), - selected = current == null, - trailing = if (current == null) { - { SelectedCheck() } - } else { - null - }, + checked = current == null, onClick = { onSelect(null) }, ) RecurrenceFreq.entries.forEachIndexed { index, entry -> @@ -1685,24 +1679,14 @@ private fun RecurrencePickerDialog( title = stringResource(recurrencePresetLabel(entry)), summary = nextOccurrencesText(SimpleRecurrence(entry), startDate, locale), position = positionOf(index + 1, rowCount), - selected = isPlainPreset && parsed?.freq == entry, - trailing = if (isPlainPreset && parsed?.freq == entry) { - { SelectedCheck() } - } else { - null - }, + checked = isPlainPreset && parsed?.freq == entry, onClick = { onSelect(SimpleRecurrence(entry).toRRule()) }, ) } GroupedRow( title = stringResource(R.string.event_edit_recurrence_custom), position = positionOf(rowCount - 1, rowCount), - selected = current != null && !isPlainPreset, - trailing = if (current != null && !isPlainPreset) { - { SelectedCheck() } - } else { - null - }, + checked = current != null && !isPlainPreset, onClick = { customMode = true }, ) } else { @@ -1831,24 +1815,14 @@ private fun RecurrencePickerDialog( GroupedRow( title = stringResource(R.string.event_edit_recurrence_end_never), position = Position.Top, - selected = endMode == RecurrenceEndMode.Never, - trailing = if (endMode == RecurrenceEndMode.Never) { - { SelectedCheck() } - } else { - null - }, + checked = endMode == RecurrenceEndMode.Never, onClick = { endMode = RecurrenceEndMode.Never }, ) GroupedRow( title = stringResource(R.string.event_edit_recurrence_end_until), summary = untilSummary, position = Position.Middle, - selected = endMode == RecurrenceEndMode.Until, - trailing = if (endMode == RecurrenceEndMode.Until) { - { SelectedCheck() } - } else { - null - }, + checked = endMode == RecurrenceEndMode.Until, onClick = { endMode = RecurrenceEndMode.Until showUntilPicker = true @@ -1861,12 +1835,7 @@ private fun RecurrencePickerDialog( } else { Position.Bottom }, - selected = endMode == RecurrenceEndMode.Count, - trailing = if (endMode == RecurrenceEndMode.Count) { - { SelectedCheck() } - } else { - null - }, + checked = endMode == RecurrenceEndMode.Count, onClick = { endMode = RecurrenceEndMode.Count }, ) // Animated for the same reason the weekday card is: this is the diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/ViewStep.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/ViewStep.kt index 006f4518..1d512e25 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/ViewStep.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/ViewStep.kt @@ -19,7 +19,6 @@ import de.jeanlucmakiola.calendula.ui.agenda.AgendaViewPreview import de.jeanlucmakiola.calendula.ui.common.CalendarView import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS import de.jeanlucmakiola.calendula.ui.common.LocalMultiDayCount -import de.jeanlucmakiola.calendula.ui.common.PickerDescription import de.jeanlucmakiola.calendula.ui.common.icon import de.jeanlucmakiola.calendula.ui.common.label import de.jeanlucmakiola.calendula.ui.month.MonthStylePreview @@ -27,11 +26,11 @@ import de.jeanlucmakiola.calendula.ui.month.MonthViewStyle import de.jeanlucmakiola.calendula.ui.month.descriptionRes import de.jeanlucmakiola.calendula.ui.month.labelRes import de.jeanlucmakiola.calendula.ui.week.WeekViewPreview +import de.jeanlucmakiola.floret.components.GroupedHint import de.jeanlucmakiola.floret.components.GroupedListInset import de.jeanlucmakiola.floret.components.OnboardingScaffold import de.jeanlucmakiola.floret.components.OnboardingSpace import de.jeanlucmakiola.floret.components.GroupedRow -import de.jeanlucmakiola.floret.components.SelectedCheck import de.jeanlucmakiola.floret.components.ViewPreviewFrame import de.jeanlucmakiola.floret.components.positionOf import de.jeanlucmakiola.floret.locale.currentLocale @@ -77,11 +76,10 @@ internal fun ViewStep( } } IMPLEMENTED_VIEWS.forEachIndexed { index, view -> - val isSelected = view == choice.defaultView GroupedRow( title = view.label(), position = positionOf(index, IMPLEMENTED_VIEWS.size), - selected = isSelected, + checked = view == choice.defaultView, leading = { Icon( imageVector = view.icon, @@ -89,11 +87,6 @@ internal fun ViewStep( tint = MaterialTheme.colorScheme.onSurfaceVariant, ) }, - trailing = if (isSelected) { - { SelectedCheck() } - } else { - null - }, onClick = { onSelect(view) }, ) } @@ -127,18 +120,12 @@ internal fun MonthStyleStep( StepPreview(selected = choice.monthViewStyle) { style -> MonthStylePreview(style = style, weekStart = weekStart, height = PREVIEW_HEIGHT) } - PickerDescription(stringResource(choice.monthViewStyle.descriptionRes)) + GroupedHint(stringResource(choice.monthViewStyle.descriptionRes), verticalPadding = 8.dp) options.forEachIndexed { index, style -> - val isSelected = style == choice.monthViewStyle GroupedRow( title = stringResource(style.labelRes), position = positionOf(index, options.size), - selected = isSelected, - trailing = if (isSelected) { - { SelectedCheck() } - } else { - null - }, + checked = style == choice.monthViewStyle, onClick = { onSelect(style) }, ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/AppearanceSettings.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/AppearanceSettings.kt index 6febcc4d..83dd8151 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/AppearanceSettings.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/AppearanceSettings.kt @@ -51,18 +51,18 @@ import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.calendula.data.appname.LauncherName import de.jeanlucmakiola.calendula.data.prefs.ThemeMode import de.jeanlucmakiola.calendula.domain.FontRole -import de.jeanlucmakiola.calendula.ui.common.PickerDescription import de.jeanlucmakiola.calendula.ui.theme.BundledFont import de.jeanlucmakiola.calendula.ui.theme.FONT_CUSTOM_TOKEN import de.jeanlucmakiola.calendula.ui.theme.FONT_SYSTEM_TOKEN import de.jeanlucmakiola.calendula.ui.theme.resolveFontFamily import de.jeanlucmakiola.floret.components.CollapsingScaffold import de.jeanlucmakiola.floret.components.FullScreenPicker +import de.jeanlucmakiola.floret.components.GroupedHint import de.jeanlucmakiola.floret.components.GroupedListInset import de.jeanlucmakiola.floret.components.GroupedRow +import de.jeanlucmakiola.floret.components.GroupedSwitchRow import de.jeanlucmakiola.floret.components.OptionPicker import de.jeanlucmakiola.floret.components.Position -import de.jeanlucmakiola.floret.components.SelectedCheck import de.jeanlucmakiola.floret.components.positionOf /** @@ -127,17 +127,12 @@ internal fun AppearanceScreen( null }, ) - GroupedRow( + GroupedSwitchRow( title = stringResource(R.string.settings_soften_colors), + checked = state.softenColors, + onCheckedChange = viewModel::setSoftenColors, summary = stringResource(R.string.settings_soften_colors_summary), position = Position.Bottom, - trailing = { - Switch( - checked = state.softenColors, - onCheckedChange = viewModel::setSoftenColors, - ) - }, - onClick = { viewModel.setSoftenColors(!state.softenColors) }, ) Spacer(Modifier.height(16.dp)) @@ -209,7 +204,7 @@ internal fun AppearanceScreen( val systemDark = isSystemInDarkTheme() OptionPicker( title = stringResource(R.string.settings_theme), - header = { PickerDescription(stringResource(R.string.settings_theme_hint)) }, + header = { GroupedHint(stringResource(R.string.settings_theme_hint), verticalPadding = 8.dp) }, predictiveBack = true, options = ThemeMode.entries, selected = state.themeMode, @@ -407,7 +402,7 @@ private fun FontOptionRow( GroupedRow( title = label, position = position, - selected = selected, + checked = selected, leading = { if (preview != null) { Text( @@ -419,11 +414,6 @@ private fun FontOptionRow( Icon(imageVector = leadingIcon, contentDescription = null) } }, - trailing = if (selected) { - { SelectedCheck() } - } else { - null - }, onClick = onClick, ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/EventFormSettings.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/EventFormSettings.kt index f7d0b393..4d44c310 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/EventFormSettings.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/EventFormSettings.kt @@ -5,13 +5,10 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ExpandLess -import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material.icons.filled.Keyboard import androidx.compose.material.icons.filled.Schedule import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Switch import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -29,7 +26,10 @@ import de.jeanlucmakiola.calendula.ui.common.durationLabel import de.jeanlucmakiola.calendula.ui.common.eventFormFieldIcon import de.jeanlucmakiola.calendula.ui.common.eventFormFieldLabel import de.jeanlucmakiola.floret.components.CollapsingScaffold +import de.jeanlucmakiola.floret.components.ExpandIcon +import de.jeanlucmakiola.floret.components.GroupedHint import de.jeanlucmakiola.floret.components.GroupedRow +import de.jeanlucmakiola.floret.components.GroupedSwitchRow import de.jeanlucmakiola.floret.components.Position import de.jeanlucmakiola.floret.components.positionOf import de.jeanlucmakiola.floret.identity.collapseExit @@ -56,13 +56,15 @@ internal fun EventFormScreen( predictiveBack = true, visible = visible, ) { - SettingsHint(stringResource(R.string.settings_form_fields_hint)) + GroupedHint(stringResource(R.string.settings_form_fields_hint)) Spacer(Modifier.height(8.dp)) val fields = EventFormField.entries fields.forEachIndexed { index, field -> val checked = field in state.defaultFormFields - GroupedRow( + GroupedSwitchRow( title = stringResource(eventFormFieldLabel(field)), + checked = checked, + onCheckedChange = { viewModel.setFormFieldDefault(field, it) }, position = positionOf(index, fields.size), // The same icon the field carries in the new-event form. leading = { @@ -72,20 +74,15 @@ internal fun EventFormScreen( tint = MaterialTheme.colorScheme.onSurfaceVariant, ) }, - trailing = { - Switch( - checked = checked, - onCheckedChange = { viewModel.setFormFieldDefault(field, it) }, - ) - }, - onClick = { viewModel.setFormFieldDefault(field, !checked) }, ) } // Auto-focus the title on a new event (#10), on by default. Spacer(Modifier.height(24.dp)) - GroupedRow( + GroupedSwitchRow( title = stringResource(R.string.settings_autofocus_title), + checked = state.autofocusEventTitle, + onCheckedChange = { viewModel.setAutofocusEventTitle(it) }, summary = stringResource(R.string.settings_autofocus_title_hint), position = Position.Alone, leading = { @@ -95,13 +92,6 @@ internal fun EventFormScreen( tint = MaterialTheme.colorScheme.onSurfaceVariant, ) }, - trailing = { - Switch( - checked = state.autofocusEventTitle, - onCheckedChange = { viewModel.setAutofocusEventTitle(it) }, - ) - }, - onClick = { viewModel.setAutofocusEventTitle(!state.autofocusEventTitle) }, ) // How long a new timed event opens (#54), globally and per calendar — @@ -127,13 +117,7 @@ internal fun EventFormScreen( // Expanded, the header opens the calendar run below it and the // two read as one container (the shared expand pattern). position = if (durationSectionExpanded) Position.Top else Position.Bottom, - trailing = { - Icon( - imageVector = if (durationSectionExpanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - ) - }, + trailing = { ExpandIcon(durationSectionExpanded) }, onClick = { durationSectionExpanded = !durationSectionExpanded }, ) AnimatedVisibility( @@ -165,21 +149,12 @@ internal fun EventFormScreen( // Per-event colour on calendars that publish no colour set (some // CalDAV); off by default, since it may not survive their next sync. Spacer(Modifier.height(24.dp)) - GroupedRow( + GroupedSwitchRow( title = stringResource(R.string.settings_color_unsupported), + checked = state.allowColorOnUnsupportedCalendars, + onCheckedChange = { viewModel.setAllowColorOnUnsupportedCalendars(it) }, summary = stringResource(R.string.settings_color_unsupported_hint), position = Position.Alone, - trailing = { - Switch( - checked = state.allowColorOnUnsupportedCalendars, - onCheckedChange = { viewModel.setAllowColorOnUnsupportedCalendars(it) }, - ) - }, - onClick = { - viewModel.setAllowColorOnUnsupportedCalendars( - !state.allowColorOnUnsupportedCalendars, - ) - }, ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/MonthViewStylePicker.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/MonthViewStylePicker.kt index 5b9a55e3..c780a73d 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/MonthViewStylePicker.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/MonthViewStylePicker.kt @@ -6,16 +6,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import de.jeanlucmakiola.calendula.R -import de.jeanlucmakiola.calendula.ui.common.PickerDescription import de.jeanlucmakiola.calendula.ui.month.MonthStylePreview import de.jeanlucmakiola.calendula.ui.month.MonthViewStyle import de.jeanlucmakiola.calendula.ui.month.descriptionRes import de.jeanlucmakiola.calendula.ui.month.labelRes import de.jeanlucmakiola.floret.components.FullScreenPicker -import de.jeanlucmakiola.floret.components.GroupedRow -import de.jeanlucmakiola.floret.components.SelectedCheck +import de.jeanlucmakiola.floret.components.GroupedHint +import de.jeanlucmakiola.floret.components.OptionRows import de.jeanlucmakiola.floret.components.ViewPreviewFrame -import de.jeanlucmakiola.floret.components.positionOf import kotlinx.datetime.DayOfWeek /** @@ -29,7 +27,7 @@ import kotlinx.datetime.DayOfWeek * * Under the preview sits the selected style's own one-line blurb, then the * family's standard picker shape: connected single-line grouped rows with a - * tonal highlight and [SelectedCheck] on the current one. + * tonal highlight and a check on the current one. */ @Composable internal fun MonthViewStylePicker( @@ -58,23 +56,15 @@ internal fun MonthViewStylePicker( // The selected style's own blurb lives here, under its preview, rather // than on every row — the rows stay single-line and dense, and the words // describe exactly what is being shown above. - PickerDescription(stringResource(selected.descriptionRes)) - options.forEachIndexed { index, style -> - val isSelected = style == selected - GroupedRow( - title = stringResource(style.labelRes), - position = positionOf(index, options.size), - selected = isSelected, - trailing = if (isSelected) { - { SelectedCheck() } - } else { - null - }, - // Applies straight away; the preview above is the confirmation, - // so there is nothing to dismiss for. - onClick = { onSelect(style) }, - ) - } + GroupedHint(stringResource(selected.descriptionRes), verticalPadding = 8.dp) + // Applies straight away; the preview above is the confirmation, so + // there is nothing to dismiss for. + OptionRows( + options = options, + selected = selected, + label = { stringResource(it.labelRes) }, + onSelect = onSelect, + ) } } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/NotificationSettings.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/NotificationSettings.kt index 7fe559f0..486715ea 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/NotificationSettings.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/NotificationSettings.kt @@ -15,11 +15,8 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight -import androidx.compose.material.icons.filled.ExpandLess -import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Switch import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue @@ -43,12 +40,15 @@ import de.jeanlucmakiola.calendula.ui.common.ReminderDefaultPicker import de.jeanlucmakiola.calendula.ui.common.TimePickerAlert import de.jeanlucmakiola.calendula.ui.common.durationLabel import de.jeanlucmakiola.floret.components.CollapsingScaffold +import de.jeanlucmakiola.floret.components.ExpandIcon import de.jeanlucmakiola.floret.components.GroupedRow +import de.jeanlucmakiola.floret.components.GroupedSwitchRow import de.jeanlucmakiola.floret.components.Position import de.jeanlucmakiola.floret.components.SelectedCheck import de.jeanlucmakiola.floret.identity.collapseExit import de.jeanlucmakiola.floret.identity.expandEnter import de.jeanlucmakiola.floret.reminders.ReminderOverride +import de.jeanlucmakiola.floret.reminders.minutesOrEmpty import de.jeanlucmakiola.floret.reminders.reminderOverrideFor import kotlinx.datetime.LocalTime @@ -95,14 +95,12 @@ internal fun NotificationsScreen( predictiveBack = true, visible = visible, ) { - GroupedRow( + GroupedSwitchRow( title = stringResource(R.string.settings_reminders), + checked = state.remindersEnabled, + onCheckedChange = toggleReminders, summary = stringResource(R.string.settings_reminders_hint), position = Position.Top, - trailing = { - Switch(checked = state.remindersEnabled, onCheckedChange = toggleReminders) - }, - onClick = { toggleReminders(!state.remindersEnabled) }, ) GroupedRow( title = stringResource(R.string.settings_default_reminder), @@ -162,13 +160,7 @@ internal fun NotificationsScreen( title = stringResource(R.string.settings_calendar_reminders_title), summary = stringResource(R.string.settings_calendar_reminders_hint), position = Position.Alone, - trailing = { - Icon( - imageVector = if (calendarSectionExpanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - ) - }, + trailing = { ExpandIcon(calendarSectionExpanded) }, onClick = { calendarSectionExpanded = !calendarSectionExpanded }, ) AnimatedVisibility( @@ -203,13 +195,7 @@ internal fun NotificationsScreen( title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) }, position = if (expanded) Position.Top else Position.Alone, leading = { CalendarColorChip(calendar.color) }, - trailing = { - Icon( - imageVector = if (expanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - ) - }, + trailing = { ExpandIcon(expanded) }, onClick = { expandedCalendars = if (expanded) { expandedCalendars - calendar.id @@ -263,7 +249,7 @@ internal fun NotificationsScreen( presets = REMINDER_PRESETS, selected = state.defaultReminderMinutes.toReminderChoice(), allowInherit = false, - onSelect = { viewModel.setDefaultReminderMinutes(it.toMinutesList()) }, + onSelect = { viewModel.setDefaultReminderMinutes(it.minutesOrEmpty()) }, onDismiss = { showDefaultReminder = false }, ) } @@ -273,7 +259,7 @@ internal fun NotificationsScreen( presets = ALLDAY_REMINDER_PRESETS, selected = state.defaultAllDayReminderMinutes.toReminderChoice(), allowInherit = false, - onSelect = { viewModel.setDefaultAllDayReminderMinutes(it.toMinutesList()) }, + onSelect = { viewModel.setDefaultAllDayReminderMinutes(it.minutesOrEmpty()) }, onDismiss = { showAllDayReminder = false }, leadTimeSummary = allDayFiringTimeSummary(state.allDayReminderTimeMinutes), ) @@ -332,10 +318,6 @@ private data class OverrideTarget(val calendarId: Long, val isAllDay: Boolean) private fun List.toReminderChoice(): ReminderOverride = if (isEmpty()) ReminderOverride.None else ReminderOverride.Minutes(this) -/** A picked choice as global-default minutes (Inherit isn't offered for globals). */ -private fun ReminderOverride.toMinutesList(): List = - (this as? ReminderOverride.Minutes)?.minutes ?: emptyList() - /** Row summary for a calendar: its override, or the inherited global default. */ @Composable private fun calendarOverrideSummary( diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/PastEventsPicker.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/PastEventsPicker.kt index 4b34acb4..33b9fa39 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/PastEventsPicker.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/PastEventsPicker.kt @@ -27,10 +27,9 @@ import de.jeanlucmakiola.calendula.data.prefs.PastEventDisplay import de.jeanlucmakiola.calendula.domain.EventInstance import de.jeanlucmakiola.calendula.ui.agenda.AgendaDayHeader import de.jeanlucmakiola.calendula.ui.agenda.AgendaEventRow -import de.jeanlucmakiola.calendula.ui.common.PickerDescription import de.jeanlucmakiola.floret.components.FullScreenPicker -import de.jeanlucmakiola.floret.components.GroupedRow -import de.jeanlucmakiola.floret.components.SelectedCheck +import de.jeanlucmakiola.floret.components.GroupedHint +import de.jeanlucmakiola.floret.components.OptionRows import de.jeanlucmakiola.floret.components.positionOf import de.jeanlucmakiola.floret.identity.rememberReduceMotion import kotlinx.datetime.LocalDate @@ -80,21 +79,13 @@ internal fun PastEventsPicker( } } } - PickerDescription(stringResource(R.string.settings_past_events_hint)) - options.forEachIndexed { index, mode -> - val isSelected = mode == selected - GroupedRow( - title = stringResource(pastEventDisplayLabelRes(mode)), - position = positionOf(index, options.size), - selected = isSelected, - trailing = if (isSelected) { - { SelectedCheck() } - } else { - null - }, - onClick = { onSelect(mode) }, - ) - } + GroupedHint(stringResource(R.string.settings_past_events_hint), verticalPadding = 8.dp) + OptionRows( + options = options, + selected = selected, + label = { stringResource(pastEventDisplayLabelRes(it)) }, + onSelect = onSelect, + ) } } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsCommon.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsCommon.kt index d6409de3..746ff28d 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsCommon.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsCommon.kt @@ -4,12 +4,10 @@ import android.content.Context import android.content.Intent import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -22,8 +20,6 @@ import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat import de.jeanlucmakiola.calendula.ui.common.formatMinuteOfDay import de.jeanlucmakiola.calendula.ui.common.reminderLeadTimeLabel -import de.jeanlucmakiola.floret.components.GroupedListInset -import de.jeanlucmakiola.floret.components.GroupedSectionHeader import de.jeanlucmakiola.floret.locale.currentLocale /** @@ -64,17 +60,6 @@ internal fun CategoryIcon(icon: ImageVector, accent: ChipAccent) { } } -/** Muted supporting text under a [GroupedSectionHeader], matching the form-fields hint. */ -@Composable -internal fun SettingsHint(text: String) { - Text( - text = text, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(horizontal = GroupedListInset, vertical = 4.dp), - ) -} - internal fun openUrl(context: Context, url: String) { val intent = Intent(Intent.ACTION_VIEW, url.toUri()) runCatching { context.startActivity(intent) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SpecialDatesSettings.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SpecialDatesSettings.kt index 0e8559bb..2f20e9ac 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SpecialDatesSettings.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SpecialDatesSettings.kt @@ -1,7 +1,6 @@ package de.jeanlucmakiola.calendula.ui.settings import android.Manifest -import android.content.Context import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Column @@ -14,7 +13,6 @@ import androidx.compose.material3.AlertDialog import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface -import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -34,9 +32,12 @@ import de.jeanlucmakiola.calendula.domain.contacts.SpecialDateType import de.jeanlucmakiola.calendula.ui.common.ReminderDefaultPicker import de.jeanlucmakiola.floret.components.CollapsingScaffold import de.jeanlucmakiola.floret.components.GroupedRow +import de.jeanlucmakiola.floret.components.GroupedSwitchRow import de.jeanlucmakiola.floret.components.InlineTextField import de.jeanlucmakiola.floret.components.Position +import de.jeanlucmakiola.floret.locale.relativeTimeAgo import de.jeanlucmakiola.floret.reminders.ReminderOverride +import de.jeanlucmakiola.floret.reminders.minutesOrEmpty // --------------------------------------------------------------------------- // Contact special dates (issue #15) @@ -106,38 +107,26 @@ internal fun SpecialDatesScreen( Spacer(Modifier.height(16.dp)) } - GroupedRow( + GroupedSwitchRow( title = stringResource(R.string.settings_special_dates_enable), + checked = state.enabled, + onCheckedChange = { want -> if (want) requestOrEnable() else confirmDisableAll = true }, summary = stringResource(R.string.settings_special_dates_enable_hint), position = Position.Alone, - trailing = { - Switch( - checked = state.enabled, - onCheckedChange = { want -> if (want) requestOrEnable() else confirmDisableAll = true }, - ) - }, - onClick = { if (state.enabled) confirmDisableAll = true else requestOrEnable() }, ) if (state.enabled) { SpecialDateType.entries.forEachIndexed { index, type -> Spacer(Modifier.height(if (index == 0) 24.dp else 16.dp)) val on = type in state.types - GroupedRow( + GroupedSwitchRow( title = stringResource(specialDateTypeLabel(type)), + checked = on, + onCheckedChange = { want -> + if (want) viewModel.setSpecialDateTypeEnabled(type, true) + else confirmDisableType = type + }, position = if (on) Position.Top else Position.Alone, - trailing = { - Switch( - checked = on, - onCheckedChange = { want -> - if (want) viewModel.setSpecialDateTypeEnabled(type, true) - else confirmDisableType = type - }, - ) - }, - onClick = { - if (on) confirmDisableType = type else viewModel.setSpecialDateTypeEnabled(type, true) - }, ) if (on) { GroupedRow( @@ -148,7 +137,7 @@ internal fun SpecialDatesScreen( ) GroupedRow( title = stringResource(R.string.settings_special_dates_reminders), - summary = reminderChoiceLabel(specialDatesReminderMinutes(state.reminderChoices[type])), + summary = reminderChoiceLabel(state.reminderChoices[type]?.minutesOrEmpty().orEmpty()), position = Position.Bottom, onClick = { reminderPickerType = type }, ) @@ -156,21 +145,16 @@ internal fun SpecialDatesScreen( } Spacer(Modifier.height(24.dp)) - GroupedRow( + GroupedSwitchRow( title = stringResource(R.string.settings_special_dates_show_year), + checked = state.showYear, + onCheckedChange = viewModel::setSpecialDatesShowYear, summary = stringResource(R.string.settings_special_dates_show_year_hint), position = Position.Top, - trailing = { - Switch( - checked = state.showYear, - onCheckedChange = viewModel::setSpecialDatesShowYear, - ) - }, - onClick = { viewModel.setSpecialDatesShowYear(!state.showYear) }, ) GroupedRow( title = stringResource(R.string.settings_special_dates_sync_now), - summary = specialDatesLastRunLabel(context, state.lastRun), + summary = specialDatesLastRunLabel(state.lastRun), position = Position.Bottom, onClick = viewModel::syncSpecialDatesNow, ) @@ -223,10 +207,6 @@ internal fun SpecialDatesScreen( } } -/** The lead-time list backing a managed calendar's reminder choice (for the summary label). */ -private fun specialDatesReminderMinutes(choice: ReminderOverride?): List = - (choice as? ReminderOverride.Minutes)?.minutes.orEmpty() - @Composable private fun SpecialDatesDisableDialog( message: String, @@ -305,16 +285,12 @@ private fun specialDateTypeLabel(type: SpecialDateType): Int = when (type) { } @Composable -private fun specialDatesLastRunLabel(context: Context, lastRun: Long): String = +private fun specialDatesLastRunLabel(lastRun: Long): String = if (lastRun <= 0L) { stringResource(R.string.settings_special_dates_never_synced) } else { stringResource( R.string.settings_special_dates_last_synced, - android.text.format.DateUtils.getRelativeTimeSpanString( - lastRun, - System.currentTimeMillis(), - android.text.format.DateUtils.MINUTE_IN_MILLIS, - ).toString(), + relativeTimeAgo(lastRun), ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/ViewsSettings.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/ViewsSettings.kt index 65b91ea8..2dac5c61 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/ViewsSettings.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/ViewsSettings.kt @@ -33,7 +33,6 @@ import de.jeanlucmakiola.calendula.data.prefs.resolveFirstDay import de.jeanlucmakiola.calendula.ui.common.AgendaRangePicker import de.jeanlucmakiola.calendula.ui.common.CalendarView import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS -import de.jeanlucmakiola.calendula.ui.common.PickerDescription import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat import de.jeanlucmakiola.calendula.ui.common.TimelineHours import de.jeanlucmakiola.calendula.ui.common.TimelineScale @@ -46,8 +45,10 @@ import de.jeanlucmakiola.calendula.ui.common.label import de.jeanlucmakiola.calendula.ui.common.labelRes import de.jeanlucmakiola.calendula.ui.month.labelRes import de.jeanlucmakiola.floret.components.CollapsingScaffold +import de.jeanlucmakiola.floret.components.GroupedHint import de.jeanlucmakiola.floret.components.GroupedRow import de.jeanlucmakiola.floret.components.GroupedSectionHeader +import de.jeanlucmakiola.floret.components.GroupedSwitchRow import de.jeanlucmakiola.floret.components.OptionPicker import de.jeanlucmakiola.floret.components.Position import de.jeanlucmakiola.floret.components.ReorderableColumn @@ -112,29 +113,19 @@ internal fun ViewsScreen( position = Position.Middle, onClick = { showTimeFormat = true }, ) - GroupedRow( + GroupedSwitchRow( title = stringResource(R.string.settings_today_toolbar), + checked = state.todayButtonInToolbar, + onCheckedChange = viewModel::setTodayButtonInToolbar, summary = stringResource(R.string.settings_today_toolbar_summary), position = Position.Middle, - trailing = { - Switch( - checked = state.todayButtonInToolbar, - onCheckedChange = viewModel::setTodayButtonInToolbar, - ) - }, - onClick = { viewModel.setTodayButtonInToolbar(!state.todayButtonInToolbar) }, ) - GroupedRow( + GroupedSwitchRow( title = stringResource(R.string.settings_dim_completed), + checked = state.dimCompletedEvents, + onCheckedChange = viewModel::setDimCompletedEvents, summary = stringResource(R.string.settings_dim_completed_summary), position = Position.Bottom, - trailing = { - Switch( - checked = state.dimCompletedEvents, - onCheckedChange = viewModel::setDimCompletedEvents, - ) - }, - onClick = { viewModel.setDimCompletedEvents(!state.dimCompletedEvents) }, ) Spacer(Modifier.height(8.dp)) @@ -147,17 +138,12 @@ internal fun ViewsScreen( position = Position.Top, onClick = { showTapOpens = true }, ) - GroupedRow( + GroupedSwitchRow( title = stringResource(R.string.settings_drag_to_reschedule), + checked = state.dragToReschedule, + onCheckedChange = viewModel::setDragToReschedule, summary = stringResource(R.string.settings_drag_to_reschedule_summary), position = Position.Bottom, - trailing = { - Switch( - checked = state.dragToReschedule, - onCheckedChange = viewModel::setDragToReschedule, - ) - }, - onClick = { viewModel.setDragToReschedule(!state.dragToReschedule) }, ) Spacer(Modifier.height(8.dp)) @@ -168,17 +154,12 @@ internal fun ViewsScreen( position = Position.Top, onClick = { showMonthStyle = true }, ) - GroupedRow( + GroupedSwitchRow( title = stringResource(R.string.settings_week_numbers), + checked = state.showWeekNumbers, + onCheckedChange = viewModel::setShowWeekNumbers, summary = stringResource(R.string.settings_week_numbers_summary), position = Position.Bottom, - trailing = { - Switch( - checked = state.showWeekNumbers, - onCheckedChange = viewModel::setShowWeekNumbers, - ) - }, - onClick = { viewModel.setShowWeekNumbers(!state.showWeekNumbers) }, ) Spacer(Modifier.height(8.dp)) @@ -200,8 +181,10 @@ internal fun ViewsScreen( dimmed = hours.enabled, onClick = if (hours.enabled) null else ({ showTimelineScale = true }), ) - GroupedRow( + GroupedSwitchRow( title = stringResource(R.string.settings_visible_hours), + checked = hours.enabled, + onCheckedChange = { viewModel.setTimelineHours(hours.copy(enabled = it)) }, summary = if (hours.enabled) { stringResource( R.string.settings_visible_hours_on_summary, @@ -212,13 +195,6 @@ internal fun ViewsScreen( stringResource(R.string.settings_visible_hours_summary) }, position = Position.Middle, - trailing = { - Switch( - checked = hours.enabled, - onCheckedChange = { viewModel.setTimelineHours(hours.copy(enabled = it)) }, - ) - }, - onClick = { viewModel.setTimelineHours(hours.copy(enabled = !hours.enabled)) }, ) if (hours.enabled) { GroupedRow( @@ -234,17 +210,12 @@ internal fun ViewsScreen( onClick = { showHoursEnd = true }, ) } - GroupedRow( + GroupedSwitchRow( title = stringResource(R.string.settings_hour_lines), + checked = state.showHourGrid, + onCheckedChange = viewModel::setShowHourGrid, summary = stringResource(R.string.settings_hour_lines_summary), position = Position.Bottom, - trailing = { - Switch( - checked = state.showHourGrid, - onCheckedChange = viewModel::setShowHourGrid, - ) - }, - onClick = { viewModel.setShowHourGrid(!state.showHourGrid) }, ) Spacer(Modifier.height(8.dp)) @@ -261,34 +232,24 @@ internal fun ViewsScreen( position = Position.Middle, onClick = { showPastEvents = true }, ) - GroupedRow( + GroupedSwitchRow( title = stringResource(R.string.settings_agenda_show_today), + checked = state.agendaShowToday, + onCheckedChange = viewModel::setAgendaShowToday, summary = stringResource(R.string.settings_agenda_show_today_hint), position = Position.Middle, - trailing = { - Switch( - checked = state.agendaShowToday, - onCheckedChange = viewModel::setAgendaShowToday, - ) - }, - onClick = { viewModel.setAgendaShowToday(!state.agendaShowToday) }, ) - GroupedRow( + GroupedSwitchRow( title = stringResource(R.string.settings_agenda_range_bar), + checked = state.agendaShowRangeBar, + onCheckedChange = viewModel::setAgendaShowRangeBar, summary = stringResource(R.string.settings_agenda_range_bar_hint), position = Position.Bottom, - trailing = { - Switch( - checked = state.agendaShowRangeBar, - onCheckedChange = viewModel::setAgendaShowRangeBar, - ) - }, - onClick = { viewModel.setAgendaShowRangeBar(!state.agendaShowRangeBar) }, ) Spacer(Modifier.height(24.dp)) GroupedSectionHeader(stringResource(R.string.settings_quick_switch_header)) - SettingsHint(stringResource(R.string.settings_quick_switch_hint)) + GroupedHint(stringResource(R.string.settings_quick_switch_hint)) Spacer(Modifier.height(8.dp)) ReorderableColumn( items = config.order, @@ -313,7 +274,7 @@ internal fun ViewsScreen( Spacer(Modifier.height(24.dp)) GroupedSectionHeader(stringResource(R.string.settings_drawer_order_header)) - SettingsHint(stringResource(R.string.settings_drawer_order_hint)) + GroupedHint(stringResource(R.string.settings_drawer_order_hint)) Spacer(Modifier.height(8.dp)) ReorderableColumn( items = state.drawerViewOrder, @@ -341,7 +302,7 @@ internal fun ViewsScreen( if (showDefaultView) { OptionPicker( title = stringResource(R.string.settings_default_view), - header = { PickerDescription(stringResource(R.string.settings_default_view_hint)) }, + header = { GroupedHint(stringResource(R.string.settings_default_view_hint), verticalPadding = 8.dp) }, predictiveBack = true, options = IMPLEMENTED_VIEWS, selected = state.defaultView, @@ -372,7 +333,7 @@ internal fun ViewsScreen( val systemIs24Hour = DateFormat.is24HourFormat(LocalContext.current) OptionPicker( title = stringResource(R.string.settings_time_format), - header = { PickerDescription(stringResource(R.string.settings_time_format_hint)) }, + header = { GroupedHint(stringResource(R.string.settings_time_format_hint), verticalPadding = 8.dp) }, predictiveBack = true, options = TimeFormatPref.entries, selected = state.timeFormat, @@ -403,7 +364,7 @@ internal fun ViewsScreen( if (showTapOpens) { OptionPicker( title = stringResource(R.string.settings_tap_opens), - header = { PickerDescription(stringResource(R.string.settings_tap_opens_hint)) }, + header = { GroupedHint(stringResource(R.string.settings_tap_opens_hint), verticalPadding = 8.dp) }, predictiveBack = true, options = listOf(false, true), selected = state.tapOpensEditor, @@ -422,7 +383,7 @@ internal fun ViewsScreen( if (showMultiDayCount) { OptionPicker( title = stringResource(R.string.settings_multi_day_count), - header = { PickerDescription(stringResource(R.string.settings_multi_day_count_hint)) }, + header = { GroupedHint(stringResource(R.string.settings_multi_day_count_hint), verticalPadding = 8.dp) }, predictiveBack = true, options = SettingsPrefs.MULTI_DAY_COUNTS.toList(), selected = state.multiDayCount, @@ -434,7 +395,7 @@ internal fun ViewsScreen( if (showTimelineScale) { OptionPicker( title = stringResource(R.string.settings_timeline_scale), - header = { PickerDescription(stringResource(R.string.settings_timeline_scale_hint)) }, + header = { GroupedHint(stringResource(R.string.settings_timeline_scale_hint), verticalPadding = 8.dp) }, predictiveBack = true, // A height pinched on the timeline is listed alongside the presets // rather than left as a silently unticked list: it is the current @@ -453,7 +414,7 @@ internal fun ViewsScreen( if (showHoursStart) { OptionPicker( title = stringResource(R.string.settings_visible_hours_start), - header = { PickerDescription(stringResource(R.string.settings_visible_hours_start_hint)) }, + header = { GroupedHint(stringResource(R.string.settings_visible_hours_start_hint), verticalPadding = 8.dp) }, predictiveBack = true, options = TimelineHours.startOptions(hours.endHour), selected = hours.startHour, @@ -465,7 +426,7 @@ internal fun ViewsScreen( if (showHoursEnd) { OptionPicker( title = stringResource(R.string.settings_visible_hours_end), - header = { PickerDescription(stringResource(R.string.settings_visible_hours_end_hint)) }, + header = { GroupedHint(stringResource(R.string.settings_visible_hours_end_hint), verticalPadding = 8.dp) }, predictiveBack = true, options = TimelineHours.endOptions(hours.startHour), selected = hours.endHour, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/WeekStartPicker.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/WeekStartPicker.kt index 9d7ff70d..a7682fb7 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/WeekStartPicker.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/WeekStartPicker.kt @@ -8,14 +8,12 @@ import androidx.compose.ui.unit.dp import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.calendula.data.prefs.WeekStartPref import de.jeanlucmakiola.calendula.data.prefs.resolveFirstDay -import de.jeanlucmakiola.calendula.ui.common.PickerDescription import de.jeanlucmakiola.calendula.ui.month.MonthStylePreview import de.jeanlucmakiola.calendula.ui.month.MonthViewStyle import de.jeanlucmakiola.floret.components.FullScreenPicker -import de.jeanlucmakiola.floret.components.GroupedRow -import de.jeanlucmakiola.floret.components.SelectedCheck +import de.jeanlucmakiola.floret.components.GroupedHint +import de.jeanlucmakiola.floret.components.OptionRows import de.jeanlucmakiola.floret.components.ViewPreviewFrame -import de.jeanlucmakiola.floret.components.positionOf import de.jeanlucmakiola.floret.locale.currentLocale /** @@ -51,29 +49,23 @@ internal fun WeekStartPicker( height = PREVIEW_HEIGHT, ) } - PickerDescription(stringResource(R.string.settings_week_start_hint)) - options.forEachIndexed { index, option -> - val isSelected = option == selected - GroupedRow( - title = weekStartLabel(option), - summary = if (option == WeekStartPref.Auto) { + GroupedHint(stringResource(R.string.settings_week_start_hint), verticalPadding = 8.dp) + OptionRows( + options = options, + selected = selected, + label = { weekStartLabel(it) }, + onSelect = onSelect, + summary = { option -> + if (option == WeekStartPref.Auto) { stringResource( R.string.settings_week_start_auto_summary, weekStartLabel(WeekStartPref.Day(option.resolveFirstDay(locale))), ) } else { null - }, - position = positionOf(index, options.size), - selected = isSelected, - trailing = if (isSelected) { - { SelectedCheck() } - } else { - null - }, - onClick = { onSelect(option) }, - ) - } + } + }, + ) } } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/WidgetSettings.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/WidgetSettings.kt index d8301cc2..456efbe9 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/WidgetSettings.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/WidgetSettings.kt @@ -21,11 +21,11 @@ import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.calendula.data.prefs.resolveFirstDay import de.jeanlucmakiola.calendula.qs.NewEventTileService import de.jeanlucmakiola.calendula.ui.common.AgendaRangePicker -import de.jeanlucmakiola.calendula.ui.common.PickerDescription import de.jeanlucmakiola.calendula.ui.common.agendaRangeLabel import de.jeanlucmakiola.calendula.widget.WidgetSize import de.jeanlucmakiola.calendula.widget.agenda.metricsFor import de.jeanlucmakiola.floret.components.CollapsingScaffold +import de.jeanlucmakiola.floret.components.GroupedHint import de.jeanlucmakiola.floret.components.GroupedRow import de.jeanlucmakiola.floret.components.OptionPicker import de.jeanlucmakiola.floret.components.Position @@ -53,7 +53,7 @@ internal fun WidgetsScreen( predictiveBack = true, visible = visible, ) { - SettingsHint(stringResource(R.string.settings_widgets_hint)) + GroupedHint(stringResource(R.string.settings_widgets_hint)) Spacer(Modifier.height(8.dp)) GroupedRow( @@ -90,7 +90,7 @@ internal fun WidgetsScreen( if (showWidgetSize) { OptionPicker( title = stringResource(R.string.settings_widget_size), - header = { PickerDescription(stringResource(R.string.settings_widget_size_hint)) }, + header = { GroupedHint(stringResource(R.string.settings_widget_size_hint), verticalPadding = 8.dp) }, predictiveBack = true, options = WidgetSize.entries, selected = state.widgetSize,