Use floret's checked, switch and custom rows, hints and option rows

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