From 94c9fc845e00f8b90cbf88f3491d1681e360b8fb Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sun, 28 Jun 2026 20:22:35 +0200 Subject: [PATCH] refactor(theme,motion): draw identity (theme + content transitions) from floret-kit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adopt floret-kit's identity module, shared with Agendula: - CalendulaTheme becomes a thin wrapper over the kit's FloretExpressiveTheme (dynamic colour, system light/dark, standard motion scheme). Calendula keeps its identity locally: the seed-derived CalendulaLightFallback/DarkFallback and CalendulaTypography. No call-site changes — CalendulaTheme keeps its name. - The generic content transitions move to identity: calendarExpandEnter -> expandEnter, calendarCollapseExit -> collapseExit, calendarItemEnter -> itemEnter, calendarAnimateItem -> animateItemMotion, calendarFadeThrough -> fadeThrough, plus Modifier.predictiveBack and rememberReduceMotion (same names). Call sites across the screens repointed to floret.identity. - CalendarTransitions.kt keeps only what's genuinely calendar-specific: the directional month/week/day slide (calendarSlideTransition) and its specs (rememberCalendarSlideSpec / rememberCalendarFadeSpec). Compile + unit tests + lintDebug green. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/build.gradle.kts | 1 + .../calendula/ui/CalendarHost.kt | 4 +- .../calendula/ui/agenda/AgendaScreen.kt | 4 +- .../calendula/ui/calendars/CalendarsScreen.kt | 10 +- .../ui/common/CalendarTransitions.kt | 169 +----------------- .../calendula/ui/common/GroupedList.kt | 1 + .../calendula/ui/common/Picker.kt | 10 +- .../calendula/ui/day/DayScreen.kt | 2 +- .../calendula/ui/detail/EventDetailScreen.kt | 2 +- .../calendula/ui/edit/EventEditScreen.kt | 10 +- .../calendula/ui/imports/ImportScreen.kt | 2 +- .../calendula/ui/month/MonthScreen.kt | 2 +- .../calendula/ui/search/SearchScreen.kt | 6 +- .../calendula/ui/settings/SettingsScreen.kt | 12 +- .../calendula/ui/theme/Theme.kt | 44 ++--- .../calendula/ui/week/WeekScreen.kt | 2 +- 16 files changed, 56 insertions(+), 225 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b314b46..6aa33f8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -163,6 +163,7 @@ dependencies { implementation("de.jeanlucmakiola.floret:core-locale") implementation("de.jeanlucmakiola.floret:core-crash") implementation("de.jeanlucmakiola.floret:core-reminders") + implementation("de.jeanlucmakiola.floret:identity") implementation(libs.kotlinx.coroutines.core) debugImplementation(libs.androidx.ui.tooling) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt index d300334..bd7dbe8 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt @@ -24,7 +24,7 @@ import de.jeanlucmakiola.calendula.domain.EventForm import de.jeanlucmakiola.calendula.domain.EventInstance import de.jeanlucmakiola.calendula.ui.agenda.AgendaScreen import de.jeanlucmakiola.calendula.ui.calendars.CalendarsScreen -import de.jeanlucmakiola.calendula.ui.common.calendarFadeThrough +import de.jeanlucmakiola.floret.identity.fadeThrough import de.jeanlucmakiola.calendula.ui.common.CalendarView import de.jeanlucmakiola.calendula.ui.common.drillToDay import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec @@ -244,7 +244,7 @@ fun CalendarHost( // Switching between the peer views (month/week/day/agenda) is lateral // navigation, so it fades through rather than sliding — paging *within* a // view keeps the directional slide. AnimatedContent keyed on the view type. - val viewSwitch = calendarFadeThrough() + val viewSwitch = fadeThrough() AnimatedContent( targetState = view, transitionSpec = { viewSwitch }, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaScreen.kt index 8c6073c..de42f1a 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaScreen.kt @@ -56,7 +56,7 @@ import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.calendula.domain.EventInstance import de.jeanlucmakiola.calendula.ui.common.AgendaRangePicker import de.jeanlucmakiola.calendula.ui.common.agendaRangeLabel -import de.jeanlucmakiola.calendula.ui.common.calendarAnimateItem +import de.jeanlucmakiola.floret.identity.animateItemMotion import de.jeanlucmakiola.calendula.ui.common.CalendarDrawer import de.jeanlucmakiola.calendula.ui.common.CalendarFabColumn import de.jeanlucmakiola.calendula.ui.common.CalendarFailure @@ -316,7 +316,7 @@ private fun AgendaList( AgendaEventRow( event = event, position = positionOf(index, day.events.size), - modifier = calendarAnimateItem(), + modifier = animateItemMotion(), onClick = { onEventClick(event) }, ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt index 4900a71..e1f16ad 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt @@ -98,9 +98,9 @@ import de.jeanlucmakiola.calendula.ui.common.CALENDAR_COLOR_PALETTE import de.jeanlucmakiola.calendula.ui.common.CalendarColorChip import de.jeanlucmakiola.calendula.ui.common.DialogAmountField import de.jeanlucmakiola.calendula.ui.common.DialogUnitDropdown -import de.jeanlucmakiola.calendula.ui.common.calendarCollapseExit -import de.jeanlucmakiola.calendula.ui.common.calendarExpandEnter -import de.jeanlucmakiola.calendula.ui.common.predictiveBack +import de.jeanlucmakiola.floret.identity.collapseExit +import de.jeanlucmakiola.floret.identity.expandEnter +import de.jeanlucmakiola.floret.identity.predictiveBack import de.jeanlucmakiola.calendula.ui.common.CollapsingScaffold import de.jeanlucmakiola.calendula.ui.common.ColorSwatchRow import de.jeanlucmakiola.calendula.ui.common.GroupedRow @@ -664,8 +664,8 @@ private fun CalendarGroup( ) AnimatedVisibility( visible = expanded, - enter = calendarExpandEnter(), - exit = calendarCollapseExit(), + enter = expandEnter(), + exit = collapseExit(), ) { Column(content = body) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarTransitions.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarTransitions.kt index acc1132..54a5b7a 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarTransitions.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarTransitions.kt @@ -1,56 +1,26 @@ package de.jeanlucmakiola.calendula.ui.common -import android.provider.Settings -import androidx.activity.BackEventCompat -import androidx.activity.compose.PredictiveBackHandler import androidx.compose.animation.ContentTransform -import androidx.compose.animation.EnterTransition -import androidx.compose.animation.ExitTransition -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.FiniteAnimationSpec -import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkVertically import androidx.compose.animation.slideInHorizontally -import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.togetherWith -import androidx.compose.foundation.lazy.LazyItemScope -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.dp -import kotlin.coroutines.cancellation.CancellationException /** - * Whether the user has asked the system to remove animations (Settings → - * Accessibility → "Remove animations", which sets the global animator duration - * scale to 0). Compose animations do *not* honour this platform flag on their - * own, so the shared motion helpers in this file check it and fall back to a - * quick cross-fade — opacity only, no spatial movement — to respect the - * vestibular intent of the setting while keeping state changes legible. - * - * Read once at composition; the scale changes rarely and only takes full effect - * after a process restart anyway. + * Calendar-specific motion. The family's generic content transitions — section + * expand/collapse, item reveal, list relayout, cross-fade, predictive-back, and + * the reduce-motion check — now live in floret-kit's identity module + * (`expandEnter`, `collapseExit`, `itemEnter`, `animateItemMotion`, + * `fadeThrough`, `Modifier.predictiveBack`, `rememberReduceMotion`). What stays + * here is only what's specific to paging the calendar grid: the directional + * month/week/day slide and the specs that feed it. */ -@Composable -fun rememberReduceMotion(): Boolean { - val resolver = LocalContext.current.contentResolver - return remember(resolver) { - Settings.Global.getFloat(resolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) == 0f - } -} /** * The M3 Expressive spatial spring used for the month/week slide: the *fast* @@ -98,128 +68,3 @@ fun calendarSlideTransition( return slideInHorizontally(spec) { w -> dir * w } .togetherWith(slideOutHorizontally(spec) { w -> -dir * w }) } - -/** - * Cross-fade [ContentTransform] for swapping whole screens or content blocks - * where there is no meaningful spatial direction (e.g. onboarding gates). Pure - * opacity, so it doubles as its own reduced-motion form. - */ -@OptIn(ExperimentalMaterial3ExpressiveApi::class) -@Composable -fun calendarFadeThrough(): ContentTransform { - val fade = MaterialTheme.motionScheme.fastEffectsSpec() - return fadeIn(fade).togetherWith(fadeOut(fade)) -} - -/** - * Enter transition for a vertically-revealed section (expandable rows, inline - * fields): height grows from the top while fading in. Under reduced motion the - * height growth is dropped, leaving a quick fade. - * - * Pair with [calendarCollapseExit]. This is the promoted form of the pattern - * originally inlined in the event edit form, so every expandable surface in the - * app reveals the same way. - */ -@OptIn(ExperimentalMaterial3ExpressiveApi::class) -@Composable -fun calendarExpandEnter(reduceMotion: Boolean = rememberReduceMotion()): EnterTransition { - val fade = fadeIn(MaterialTheme.motionScheme.fastEffectsSpec()) - return if (reduceMotion) { - fade - } else { - expandVertically(MaterialTheme.motionScheme.fastSpatialSpec()) + fade - } -} - -/** Exit counterpart to [calendarExpandEnter]: shrink + fade, or fade only under reduced motion. */ -@OptIn(ExperimentalMaterial3ExpressiveApi::class) -@Composable -fun calendarCollapseExit(reduceMotion: Boolean = rememberReduceMotion()): ExitTransition { - val fade = fadeOut(MaterialTheme.motionScheme.fastEffectsSpec()) - return if (reduceMotion) { - fade - } else { - shrinkVertically(MaterialTheme.motionScheme.fastSpatialSpec()) + fade - } -} - -/** - * Enter transition for content revealed by an `AnimatedContent`/`AnimatedVisibility` - * (e.g. search results once a query resolves): a gentle rise + fade. Reduced - * motion keeps the fade only. - */ -@OptIn(ExperimentalMaterial3ExpressiveApi::class) -@Composable -fun calendarItemEnter(reduceMotion: Boolean = rememberReduceMotion()): EnterTransition { - val fade = fadeIn(MaterialTheme.motionScheme.fastEffectsSpec()) - return if (reduceMotion) { - fade - } else { - fade + slideInVertically(MaterialTheme.motionScheme.fastSpatialSpec()) { h -> h / 6 } - } -} - -/** - * Shared [LazyItemScope.animateItem] wiring so list rows fade/relocate with the - * app's motion scheme instead of Compose's default spring. Returns a bare - * [Modifier] under reduced motion so rows snap into place. Requires the list to - * supply stable item keys. - */ -@OptIn(ExperimentalMaterial3ExpressiveApi::class) -@Composable -fun LazyItemScope.calendarAnimateItem(reduceMotion: Boolean = rememberReduceMotion()): Modifier = - if (reduceMotion) { - Modifier - } else { - Modifier.animateItem( - fadeInSpec = MaterialTheme.motionScheme.fastEffectsSpec(), - placementSpec = MaterialTheme.motionScheme.fastSpatialSpec(), - fadeOutSpec = MaterialTheme.motionScheme.fastEffectsSpec(), - ) - } - -/** - * Standard Android predictive-back transform for a full-screen overlay: as the - * back gesture is dragged, the surface scales toward ~90%, shifts toward the - * swiped edge and rounds its corners, previewing what's behind. Completing the - * gesture invokes [onBack]; cancelling springs it back. - * - * A drop-in replacement for a screen's own `BackHandler(onBack)` — register it - * once and apply the returned [Modifier] to that screen's root so the preview - * respects the same back semantics. Under reduced motion the visual preview is - * skipped (the back still works); on API < 34 the system delivers no progress, - * so it degrades to a plain back. - */ -@Composable -fun Modifier.predictiveBack( - onBack: () -> Unit, - enabled: Boolean = true, - reduceMotion: Boolean = rememberReduceMotion(), -): Modifier { - val progress = remember { Animatable(0f) } - var fromLeftEdge by remember { mutableStateOf(true) } - - PredictiveBackHandler(enabled = enabled) { events -> - try { - events.collect { event -> - fromLeftEdge = event.swipeEdge == BackEventCompat.EDGE_LEFT - progress.snapTo(FastOutSlowInEasing.transform(event.progress)) - } - onBack() - progress.snapTo(0f) - } catch (_: CancellationException) { - progress.animateTo(0f) - } - } - - if (reduceMotion) return this - return this.graphicsLayer { - val p = progress.value - val scale = 1f - 0.1f * p - scaleX = scale - scaleY = scale - translationX = (if (fromLeftEdge) 1f else -1f) * 24.dp.toPx() * p - shape = RoundedCornerShape(32.dp.toPx() * p) - clip = p > 0f - } -} diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/GroupedList.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/GroupedList.kt index c183aab..e86b1a0 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/GroupedList.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/GroupedList.kt @@ -40,6 +40,7 @@ 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.floret.identity.predictiveBack /** * Position of a row within a grouped list, after the Android-15 settings diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/Picker.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/Picker.kt index ef6c199..1a2004b 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/Picker.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/Picker.kt @@ -39,6 +39,8 @@ import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogWindowProvider import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.calendula.ui.agenda.AgendaRange +import de.jeanlucmakiola.floret.identity.collapseExit +import de.jeanlucmakiola.floret.identity.expandEnter import de.jeanlucmakiola.floret.reminders.ReminderOverride import de.jeanlucmakiola.floret.reminders.ReminderUnit import de.jeanlucmakiola.floret.reminders.decomposeReminderMinutes @@ -182,8 +184,8 @@ fun ReminderDefaultPicker( ) AnimatedVisibility( visible = customExpanded, - enter = calendarExpandEnter(), - exit = calendarCollapseExit(), + enter = expandEnter(), + exit = collapseExit(), ) { CustomReminderEditor( amountText = amountText, @@ -360,8 +362,8 @@ fun AgendaRangePicker( ) AnimatedVisibility( visible = customExpanded, - enter = calendarExpandEnter(), - exit = calendarCollapseExit(), + enter = expandEnter(), + exit = collapseExit(), ) { CustomDaysEditor( amountText = amountText, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/day/DayScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/day/DayScreen.kt index 97cbdce..ba1d613 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/day/DayScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/day/DayScreen.kt @@ -76,7 +76,7 @@ import de.jeanlucmakiola.calendula.ui.common.NowLine import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition import de.jeanlucmakiola.calendula.ui.common.rememberCalendarFadeSpec -import de.jeanlucmakiola.calendula.ui.common.rememberReduceMotion +import de.jeanlucmakiola.floret.identity.rememberReduceMotion import de.jeanlucmakiola.calendula.ui.common.next import de.jeanlucmakiola.calendula.ui.common.pastelize import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt index c187ac3..8392514 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt @@ -91,7 +91,7 @@ import de.jeanlucmakiola.calendula.domain.EventInstance import de.jeanlucmakiola.calendula.domain.EventStatus import de.jeanlucmakiola.calendula.domain.RecurringWriteScope import de.jeanlucmakiola.calendula.domain.Reminder -import de.jeanlucmakiola.calendula.ui.common.predictiveBack +import de.jeanlucmakiola.floret.identity.predictiveBack import de.jeanlucmakiola.calendula.ui.common.CalendarFailure import de.jeanlucmakiola.calendula.ui.common.OptionCard import de.jeanlucmakiola.calendula.ui.common.currentLocale diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt index 6eed2bd..3067217 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt @@ -114,9 +114,9 @@ import de.jeanlucmakiola.calendula.domain.RecurringWriteScope import de.jeanlucmakiola.calendula.domain.SimpleRecurrence import de.jeanlucmakiola.calendula.domain.parseSimpleRecurrence import de.jeanlucmakiola.calendula.domain.toRRule -import de.jeanlucmakiola.calendula.ui.common.calendarCollapseExit -import de.jeanlucmakiola.calendula.ui.common.calendarExpandEnter -import de.jeanlucmakiola.calendula.ui.common.predictiveBack +import de.jeanlucmakiola.floret.identity.collapseExit +import de.jeanlucmakiola.floret.identity.expandEnter +import de.jeanlucmakiola.floret.identity.predictiveBack import de.jeanlucmakiola.calendula.ui.common.CALENDAR_COLOR_PALETTE import de.jeanlucmakiola.calendula.ui.common.CalendarDatePickerDialog import de.jeanlucmakiola.calendula.ui.common.ColorSwatchRow @@ -416,8 +416,8 @@ private fun OptionalFormSection( ) { AnimatedVisibility( visible = visible, - enter = calendarExpandEnter(), - exit = calendarCollapseExit(), + enter = expandEnter(), + exit = collapseExit(), ) { Column(modifier = Modifier.fillMaxWidth(), content = content) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/imports/ImportScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/imports/ImportScreen.kt index 881b836..64d7bcd 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/imports/ImportScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/imports/ImportScreen.kt @@ -38,7 +38,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.calendula.domain.EventForm import de.jeanlucmakiola.calendula.domain.ics.IcsParseWarning -import de.jeanlucmakiola.calendula.ui.common.predictiveBack +import de.jeanlucmakiola.floret.identity.predictiveBack import de.jeanlucmakiola.calendula.ui.common.OptionCard /** diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/month/MonthScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/month/MonthScreen.kt index d5a1ca4..a8c1996 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/month/MonthScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/month/MonthScreen.kt @@ -67,7 +67,7 @@ import de.jeanlucmakiola.calendula.ui.common.CalendarView import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition import de.jeanlucmakiola.calendula.ui.common.rememberCalendarFadeSpec -import de.jeanlucmakiola.calendula.ui.common.rememberReduceMotion +import de.jeanlucmakiola.floret.identity.rememberReduceMotion import de.jeanlucmakiola.calendula.ui.common.currentLocale import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec import de.jeanlucmakiola.calendula.ui.common.next diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/search/SearchScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/search/SearchScreen.kt index c015173..6a90766 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/search/SearchScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/search/SearchScreen.kt @@ -48,8 +48,8 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.calendula.domain.EventInstance -import de.jeanlucmakiola.calendula.ui.common.calendarAnimateItem -import de.jeanlucmakiola.calendula.ui.common.predictiveBack +import de.jeanlucmakiola.floret.identity.animateItemMotion +import de.jeanlucmakiola.floret.identity.predictiveBack import de.jeanlucmakiola.calendula.ui.common.GroupedRow import de.jeanlucmakiola.calendula.ui.common.InlineTextField import de.jeanlucmakiola.calendula.ui.common.Position @@ -175,7 +175,7 @@ private fun SearchResults( SearchResultRow( event = event, position = positionOf(index, events.size), - modifier = calendarAnimateItem(), + modifier = animateItemMotion(), onClick = { onEventClick(event) }, ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt index c0953c2..ee7c999 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt @@ -90,12 +90,12 @@ import de.jeanlucmakiola.calendula.data.prefs.TimeFormatPref import de.jeanlucmakiola.calendula.data.prefs.WeekStartPref import de.jeanlucmakiola.calendula.domain.EventFormField import de.jeanlucmakiola.calendula.qs.NewEventTileService -import de.jeanlucmakiola.calendula.ui.common.calendarCollapseExit +import de.jeanlucmakiola.floret.identity.collapseExit import de.jeanlucmakiola.floret.crash.CrashReportDialog import de.jeanlucmakiola.floret.crash.CrashReporter import de.jeanlucmakiola.floret.crash.openIssueTracker import de.jeanlucmakiola.floret.crash.submitCrashReport -import de.jeanlucmakiola.calendula.ui.common.calendarExpandEnter +import de.jeanlucmakiola.floret.identity.expandEnter import de.jeanlucmakiola.calendula.ui.common.AgendaRangePicker import de.jeanlucmakiola.calendula.ui.common.agendaRangeLabel import de.jeanlucmakiola.calendula.ui.common.CollapsingScaffold @@ -841,8 +841,8 @@ private fun NotificationsScreen( ) AnimatedVisibility( visible = calendarSectionExpanded, - enter = calendarExpandEnter(), - exit = calendarCollapseExit(), + enter = expandEnter(), + exit = collapseExit(), ) { Column { state.writableCalendars.forEach { calendar -> @@ -871,8 +871,8 @@ private fun NotificationsScreen( ) AnimatedVisibility( visible = expanded, - enter = calendarExpandEnter(), - exit = calendarCollapseExit(), + enter = expandEnter(), + exit = collapseExit(), ) { Column { val timed = state.perCalendarReminderOverride.choiceFor(calendar.id) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/theme/Theme.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/theme/Theme.kt index 8b42f63..bb463a0 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/theme/Theme.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/theme/Theme.kt @@ -1,49 +1,31 @@ package de.jeanlucmakiola.calendula.ui.theme -import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.MaterialExpressiveTheme -import androidx.compose.material3.MotionScheme -import androidx.compose.material3.dynamicDarkColorScheme -import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext +import de.jeanlucmakiola.floret.identity.FloretExpressiveTheme /** - * App theme. Honors: - * - System light/dark. - * - Dynamic Color on API 31+, else falls back to the hand-tuned scheme - * derived from [CalendulaSeed]. + * Calendula's theme: the family's [FloretExpressiveTheme] machinery — dynamic + * colour on API 31+, system light/dark, and the **standard** motion scheme + * (spring choreography without the overshoot) — fed with Calendula's own + * identity: its seed-derived fallback schemes ([CalendulaLightFallback] / + * [CalendulaDarkFallback]) and [CalendulaTypography]. * - * The Settings screen (later) can override useDynamicColor and themePreference, - * but the V1 foundation just follows the system. + * The mechanics live in floret-kit's identity module; the look stays here, so a + * sibling app reuses the same theming without inheriting Calendula's palette. */ -@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun CalendulaTheme( darkTheme: Boolean = isSystemInDarkTheme(), dynamicColor: Boolean = true, content: @Composable () -> Unit, ) { - val colorScheme = when { - dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { - val ctx = LocalContext.current - if (darkTheme) dynamicDarkColorScheme(ctx) else dynamicLightColorScheme(ctx) - } - darkTheme -> CalendulaDarkFallback - else -> CalendulaLightFallback - } - - // MaterialExpressiveTheme routes all component + custom motion through - // MaterialTheme.motionScheme (switches, chips, pickers, calendar slide, - // FAB, field reveal). The STANDARD scheme is a deliberate choice over - // expressive(): same spring choreography, but without the overshoot — - // the bouncy variant felt overdone in review (2026-06-11). - MaterialExpressiveTheme( - colorScheme = colorScheme, + FloretExpressiveTheme( + lightScheme = CalendulaLightFallback, + darkScheme = CalendulaDarkFallback, + darkTheme = darkTheme, + dynamicColor = dynamicColor, typography = CalendulaTypography, - motionScheme = MotionScheme.standard(), content = content, ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt index 63bbe74..502e0b6 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt @@ -81,7 +81,7 @@ import de.jeanlucmakiola.calendula.ui.common.NowLine import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition import de.jeanlucmakiola.calendula.ui.common.rememberCalendarFadeSpec -import de.jeanlucmakiola.calendula.ui.common.rememberReduceMotion +import de.jeanlucmakiola.floret.identity.rememberReduceMotion import de.jeanlucmakiola.calendula.ui.common.currentLocale import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat import de.jeanlucmakiola.calendula.ui.common.LocalShowHourLines