refactor(ui): one selection check and one motion spec across the pickers
All checks were successful
Translations / check (pull_request) Successful in 6s
CI / ci (pull_request) Successful in 5m57s

Three inconsistencies the recurrence-picker review surfaced, all of the
same kind: the redesigned picker had quietly diverged from the family.

SelectedCheck existed as five copies app-side plus a private one in the
kit, and they had drifted — the kit drew Icons.Rounded.Check, every app
copy drew Icons.Default.Check. Delete all five against the kit's now
public primitive (floret-kit!2), which settles the glyph on Rounded.

The recurrence picker was the only full-screen picker whose selected row
carried no check, relying on the tonal highlight alone. Add it to all six
rows, matching OptionPicker, reminder, agenda range, timezone, calendar
and Settings.

Its two new AnimatedVisibility blocks were the only ones in the app using
Compose's bare defaults rather than expandEnter()/collapseExit(). Those
helpers honour rememberReduceMotion(), so the weekday and count cards
were ignoring the system "remove animations" setting — an accessibility
regression, not just a style drift.

Bumps the floret-kit pointer; needs floret-kit!2 merged first.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-20 15:20:37 +02:00
parent 98d76deee5
commit 463811056d
6 changed files with 49 additions and 44 deletions

View File

@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.height
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.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Cloud import androidx.compose.material.icons.filled.Cloud
import androidx.compose.material.icons.filled.PhoneAndroid import androidx.compose.material.icons.filled.PhoneAndroid
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@@ -33,6 +32,7 @@ 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
@@ -99,13 +99,7 @@ private fun CalendarPickerGroup(
selected = isSelected, selected = isSelected,
leading = { CalendarColorChip(calendar.color) }, leading = { CalendarColorChip(calendar.color) },
trailing = if (isSelected) { trailing = if (isSelected) {
{ { SelectedCheck() }
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
)
}
} else { } else {
null null
}, },

View File

@@ -5,7 +5,6 @@ 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.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -26,6 +25,7 @@ import de.jeanlucmakiola.floret.components.CustomAmountEditor
import de.jeanlucmakiola.floret.components.FullScreenPicker import de.jeanlucmakiola.floret.components.FullScreenPicker
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.collapseExit
import de.jeanlucmakiola.floret.identity.expandEnter import de.jeanlucmakiola.floret.identity.expandEnter
@@ -207,14 +207,6 @@ private fun PickerDescription(text: String) {
) )
} }
@Composable
private fun SelectedCheck() {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
)
}
/** /**
* Agenda-range picker, full-screen. Two grouped lists — the calendar-aligned * Agenda-range picker, full-screen. Two grouped lists — the calendar-aligned

View File

@@ -9,7 +9,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Public import androidx.compose.material.icons.filled.Public
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
@@ -41,6 +40,7 @@ import de.jeanlucmakiola.floret.components.FullScreenPicker
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
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
@@ -223,14 +223,6 @@ private fun SectionHeader(text: String) {
) )
} }
@Composable
private fun SelectedCheck() {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
)
}
@Composable @Composable
private fun ZoneRow( private fun ZoneRow(

View File

@@ -145,6 +145,7 @@ 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
@@ -1379,6 +1380,11 @@ private fun RecurrencePickerDialog(
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, selected = 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 ->
@@ -1386,6 +1392,11 @@ private fun RecurrencePickerDialog(
title = stringResource(recurrencePresetLabel(entry)), title = stringResource(recurrencePresetLabel(entry)),
position = positionOf(index + 1, rowCount), position = positionOf(index + 1, rowCount),
selected = isPlainPreset && parsed?.freq == entry, selected = isPlainPreset && parsed?.freq == entry,
trailing = if (isPlainPreset && parsed?.freq == entry) {
{ SelectedCheck() }
} else {
null
},
onClick = { onSelect(SimpleRecurrence(entry).toRRule()) }, onClick = { onSelect(SimpleRecurrence(entry).toRRule()) },
) )
} }
@@ -1393,6 +1404,11 @@ private fun RecurrencePickerDialog(
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, selected = current != null && !isPlainPreset,
trailing = if (current != null && !isPlainPreset) {
{ SelectedCheck() }
} else {
null
},
onClick = { customMode = true }, onClick = { customMode = true },
) )
} else { } else {
@@ -1477,7 +1493,11 @@ private fun RecurrencePickerDialog(
// Weekday picks — only meaningful on a weekly rule. The 16dp gap // Weekday picks — only meaningful on a weekly rule. The 16dp gap
// sits inside the animated block so it grows/shrinks with the card. // sits inside the animated block so it grows/shrinks with the card.
AnimatedVisibility(visible = freq == RecurrenceFreq.Weekly) { AnimatedVisibility(
visible = freq == RecurrenceFreq.Weekly,
enter = expandEnter(),
exit = collapseExit(),
) {
GroupedSurface( GroupedSurface(
position = Position.Alone, position = Position.Alone,
modifier = Modifier modifier = Modifier
@@ -1507,6 +1527,11 @@ private fun RecurrencePickerDialog(
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, selected = endMode == RecurrenceEndMode.Never,
trailing = if (endMode == RecurrenceEndMode.Never) {
{ SelectedCheck() }
} else {
null
},
onClick = { endMode = RecurrenceEndMode.Never }, onClick = { endMode = RecurrenceEndMode.Never },
) )
GroupedRow( GroupedRow(
@@ -1514,6 +1539,11 @@ private fun RecurrencePickerDialog(
summary = untilSummary, summary = untilSummary,
position = Position.Middle, position = Position.Middle,
selected = endMode == RecurrenceEndMode.Until, selected = endMode == RecurrenceEndMode.Until,
trailing = if (endMode == RecurrenceEndMode.Until) {
{ SelectedCheck() }
} else {
null
},
onClick = { onClick = {
endMode = RecurrenceEndMode.Until endMode = RecurrenceEndMode.Until
showUntilPicker = true showUntilPicker = true
@@ -1527,12 +1557,21 @@ private fun RecurrencePickerDialog(
Position.Bottom Position.Bottom
}, },
selected = endMode == RecurrenceEndMode.Count, selected = 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
// picker's other collapsible block, and a bare `if` here made // picker's other collapsible block, and a bare `if` here made
// the ends group snap while the card above it glided. // the ends group snap while the card above it glided.
AnimatedVisibility(visible = endMode == RecurrenceEndMode.Count) { AnimatedVisibility(
visible = endMode == RecurrenceEndMode.Count,
enter = expandEnter(),
exit = collapseExit(),
) {
GroupedSurface( GroupedSurface(
position = Position.Bottom, position = Position.Bottom,
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.padding(horizontal = 16.dp),

View File

@@ -43,7 +43,6 @@ import androidx.compose.material.icons.filled.BugReport
import androidx.compose.material.icons.filled.Cake import androidx.compose.material.icons.filled.Cake
import androidx.compose.material.icons.filled.CalendarMonth import androidx.compose.material.icons.filled.CalendarMonth
import androidx.compose.material.icons.filled.Dashboard import androidx.compose.material.icons.filled.Dashboard
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.DragHandle import androidx.compose.material.icons.filled.DragHandle
import androidx.compose.material.icons.filled.SwapVert import androidx.compose.material.icons.filled.SwapVert
import androidx.compose.material.icons.filled.ExpandLess import androidx.compose.material.icons.filled.ExpandLess
@@ -131,6 +130,7 @@ import de.jeanlucmakiola.floret.components.Position
import de.jeanlucmakiola.calendula.ui.common.REMINDER_PRESETS import de.jeanlucmakiola.calendula.ui.common.REMINDER_PRESETS
import de.jeanlucmakiola.calendula.ui.common.ReminderDefaultPicker import de.jeanlucmakiola.calendula.ui.common.ReminderDefaultPicker
import de.jeanlucmakiola.calendula.ui.common.TimePickerAlert import de.jeanlucmakiola.calendula.ui.common.TimePickerAlert
import de.jeanlucmakiola.floret.components.SelectedCheck
import de.jeanlucmakiola.floret.components.positionOf import de.jeanlucmakiola.floret.components.positionOf
import de.jeanlucmakiola.calendula.ui.common.reminderLeadTimeLabel import de.jeanlucmakiola.calendula.ui.common.reminderLeadTimeLabel
import de.jeanlucmakiola.calendula.ui.common.SnoozeDurationPicker import de.jeanlucmakiola.calendula.ui.common.SnoozeDurationPicker
@@ -1093,13 +1093,7 @@ private fun NotificationsScreen(
}, },
position = Position.Top, position = Position.Top,
trailing = if (batteryExempt) { trailing = if (batteryExempt) {
{ { SelectedCheck() }
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
)
}
} else { } else {
null null
}, },
@@ -1845,13 +1839,7 @@ private fun FontOptionRow(
} }
}, },
trailing = if (selected) { trailing = if (selected) {
{ { SelectedCheck() }
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
)
}
} else { } else {
null null
}, },