refactor(theme,motion): draw identity (theme + content transitions) from floret-kit
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) <noreply@anthropic.com>
This commit is contained in:
@@ -163,6 +163,7 @@ dependencies {
|
|||||||
implementation("de.jeanlucmakiola.floret:core-locale")
|
implementation("de.jeanlucmakiola.floret:core-locale")
|
||||||
implementation("de.jeanlucmakiola.floret:core-crash")
|
implementation("de.jeanlucmakiola.floret:core-crash")
|
||||||
implementation("de.jeanlucmakiola.floret:core-reminders")
|
implementation("de.jeanlucmakiola.floret:core-reminders")
|
||||||
|
implementation("de.jeanlucmakiola.floret:identity")
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
|
|
||||||
debugImplementation(libs.androidx.ui.tooling)
|
debugImplementation(libs.androidx.ui.tooling)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import de.jeanlucmakiola.calendula.domain.EventForm
|
|||||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||||
import de.jeanlucmakiola.calendula.ui.agenda.AgendaScreen
|
import de.jeanlucmakiola.calendula.ui.agenda.AgendaScreen
|
||||||
import de.jeanlucmakiola.calendula.ui.calendars.CalendarsScreen
|
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.CalendarView
|
||||||
import de.jeanlucmakiola.calendula.ui.common.drillToDay
|
import de.jeanlucmakiola.calendula.ui.common.drillToDay
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
||||||
@@ -244,7 +244,7 @@ fun CalendarHost(
|
|||||||
// Switching between the peer views (month/week/day/agenda) is lateral
|
// Switching between the peer views (month/week/day/agenda) is lateral
|
||||||
// navigation, so it fades through rather than sliding — paging *within* a
|
// navigation, so it fades through rather than sliding — paging *within* a
|
||||||
// view keeps the directional slide. AnimatedContent keyed on the view type.
|
// view keeps the directional slide. AnimatedContent keyed on the view type.
|
||||||
val viewSwitch = calendarFadeThrough()
|
val viewSwitch = fadeThrough()
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
targetState = view,
|
targetState = view,
|
||||||
transitionSpec = { viewSwitch },
|
transitionSpec = { viewSwitch },
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ import de.jeanlucmakiola.calendula.R
|
|||||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||||
import de.jeanlucmakiola.calendula.ui.common.AgendaRangePicker
|
import de.jeanlucmakiola.calendula.ui.common.AgendaRangePicker
|
||||||
import de.jeanlucmakiola.calendula.ui.common.agendaRangeLabel
|
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.CalendarDrawer
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarFabColumn
|
import de.jeanlucmakiola.calendula.ui.common.CalendarFabColumn
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarFailure
|
import de.jeanlucmakiola.calendula.ui.common.CalendarFailure
|
||||||
@@ -316,7 +316,7 @@ private fun AgendaList(
|
|||||||
AgendaEventRow(
|
AgendaEventRow(
|
||||||
event = event,
|
event = event,
|
||||||
position = positionOf(index, day.events.size),
|
position = positionOf(index, day.events.size),
|
||||||
modifier = calendarAnimateItem(),
|
modifier = animateItemMotion(),
|
||||||
onClick = { onEventClick(event) },
|
onClick = { onEventClick(event) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.CalendarColorChip
|
||||||
import de.jeanlucmakiola.calendula.ui.common.DialogAmountField
|
import de.jeanlucmakiola.calendula.ui.common.DialogAmountField
|
||||||
import de.jeanlucmakiola.calendula.ui.common.DialogUnitDropdown
|
import de.jeanlucmakiola.calendula.ui.common.DialogUnitDropdown
|
||||||
import de.jeanlucmakiola.calendula.ui.common.calendarCollapseExit
|
import de.jeanlucmakiola.floret.identity.collapseExit
|
||||||
import de.jeanlucmakiola.calendula.ui.common.calendarExpandEnter
|
import de.jeanlucmakiola.floret.identity.expandEnter
|
||||||
import de.jeanlucmakiola.calendula.ui.common.predictiveBack
|
import de.jeanlucmakiola.floret.identity.predictiveBack
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CollapsingScaffold
|
import de.jeanlucmakiola.calendula.ui.common.CollapsingScaffold
|
||||||
import de.jeanlucmakiola.calendula.ui.common.ColorSwatchRow
|
import de.jeanlucmakiola.calendula.ui.common.ColorSwatchRow
|
||||||
import de.jeanlucmakiola.calendula.ui.common.GroupedRow
|
import de.jeanlucmakiola.calendula.ui.common.GroupedRow
|
||||||
@@ -664,8 +664,8 @@ private fun CalendarGroup(
|
|||||||
)
|
)
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = expanded,
|
visible = expanded,
|
||||||
enter = calendarExpandEnter(),
|
enter = expandEnter(),
|
||||||
exit = calendarCollapseExit(),
|
exit = collapseExit(),
|
||||||
) {
|
) {
|
||||||
Column(content = body)
|
Column(content = body)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +1,26 @@
|
|||||||
package de.jeanlucmakiola.calendula.ui.common
|
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.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.core.FiniteAnimationSpec
|
||||||
import androidx.compose.animation.expandVertically
|
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.shrinkVertically
|
|
||||||
import androidx.compose.animation.slideInHorizontally
|
import androidx.compose.animation.slideInHorizontally
|
||||||
import androidx.compose.animation.slideInVertically
|
|
||||||
import androidx.compose.animation.slideOutHorizontally
|
import androidx.compose.animation.slideOutHorizontally
|
||||||
import androidx.compose.animation.togetherWith
|
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.ExperimentalMaterial3ExpressiveApi
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
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.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import kotlin.coroutines.cancellation.CancellationException
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the user has asked the system to remove animations (Settings →
|
* Calendar-specific motion. The family's generic content transitions — section
|
||||||
* Accessibility → "Remove animations", which sets the global animator duration
|
* expand/collapse, item reveal, list relayout, cross-fade, predictive-back, and
|
||||||
* scale to 0). Compose animations do *not* honour this platform flag on their
|
* the reduce-motion check — now live in floret-kit's identity module
|
||||||
* own, so the shared motion helpers in this file check it and fall back to a
|
* (`expandEnter`, `collapseExit`, `itemEnter`, `animateItemMotion`,
|
||||||
* quick cross-fade — opacity only, no spatial movement — to respect the
|
* `fadeThrough`, `Modifier.predictiveBack`, `rememberReduceMotion`). What stays
|
||||||
* vestibular intent of the setting while keeping state changes legible.
|
* here is only what's specific to paging the calendar grid: the directional
|
||||||
*
|
* month/week/day slide and the specs that feed it.
|
||||||
* Read once at composition; the scale changes rarely and only takes full effect
|
|
||||||
* after a process restart anyway.
|
|
||||||
*/
|
*/
|
||||||
@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*
|
* 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 }
|
return slideInHorizontally(spec) { w -> dir * w }
|
||||||
.togetherWith(slideOutHorizontally(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<Float>()
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
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.identity.predictiveBack
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Position of a row within a grouped list, after the Android-15 settings
|
* Position of a row within a grouped list, after the Android-15 settings
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ import androidx.compose.ui.window.DialogProperties
|
|||||||
import androidx.compose.ui.window.DialogWindowProvider
|
import androidx.compose.ui.window.DialogWindowProvider
|
||||||
import de.jeanlucmakiola.calendula.R
|
import de.jeanlucmakiola.calendula.R
|
||||||
import de.jeanlucmakiola.calendula.ui.agenda.AgendaRange
|
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.ReminderOverride
|
||||||
import de.jeanlucmakiola.floret.reminders.ReminderUnit
|
import de.jeanlucmakiola.floret.reminders.ReminderUnit
|
||||||
import de.jeanlucmakiola.floret.reminders.decomposeReminderMinutes
|
import de.jeanlucmakiola.floret.reminders.decomposeReminderMinutes
|
||||||
@@ -182,8 +184,8 @@ fun ReminderDefaultPicker(
|
|||||||
)
|
)
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = customExpanded,
|
visible = customExpanded,
|
||||||
enter = calendarExpandEnter(),
|
enter = expandEnter(),
|
||||||
exit = calendarCollapseExit(),
|
exit = collapseExit(),
|
||||||
) {
|
) {
|
||||||
CustomReminderEditor(
|
CustomReminderEditor(
|
||||||
amountText = amountText,
|
amountText = amountText,
|
||||||
@@ -360,8 +362,8 @@ fun AgendaRangePicker(
|
|||||||
)
|
)
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = customExpanded,
|
visible = customExpanded,
|
||||||
enter = calendarExpandEnter(),
|
enter = expandEnter(),
|
||||||
exit = calendarCollapseExit(),
|
exit = collapseExit(),
|
||||||
) {
|
) {
|
||||||
CustomDaysEditor(
|
CustomDaysEditor(
|
||||||
amountText = amountText,
|
amountText = amountText,
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ import de.jeanlucmakiola.calendula.ui.common.NowLine
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill
|
import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill
|
||||||
import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition
|
import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarFadeSpec
|
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.next
|
||||||
import de.jeanlucmakiola.calendula.ui.common.pastelize
|
import de.jeanlucmakiola.calendula.ui.common.pastelize
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ import de.jeanlucmakiola.calendula.domain.EventInstance
|
|||||||
import de.jeanlucmakiola.calendula.domain.EventStatus
|
import de.jeanlucmakiola.calendula.domain.EventStatus
|
||||||
import de.jeanlucmakiola.calendula.domain.RecurringWriteScope
|
import de.jeanlucmakiola.calendula.domain.RecurringWriteScope
|
||||||
import de.jeanlucmakiola.calendula.domain.Reminder
|
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.CalendarFailure
|
||||||
import de.jeanlucmakiola.calendula.ui.common.OptionCard
|
import de.jeanlucmakiola.calendula.ui.common.OptionCard
|
||||||
import de.jeanlucmakiola.calendula.ui.common.currentLocale
|
import de.jeanlucmakiola.calendula.ui.common.currentLocale
|
||||||
|
|||||||
@@ -114,9 +114,9 @@ import de.jeanlucmakiola.calendula.domain.RecurringWriteScope
|
|||||||
import de.jeanlucmakiola.calendula.domain.SimpleRecurrence
|
import de.jeanlucmakiola.calendula.domain.SimpleRecurrence
|
||||||
import de.jeanlucmakiola.calendula.domain.parseSimpleRecurrence
|
import de.jeanlucmakiola.calendula.domain.parseSimpleRecurrence
|
||||||
import de.jeanlucmakiola.calendula.domain.toRRule
|
import de.jeanlucmakiola.calendula.domain.toRRule
|
||||||
import de.jeanlucmakiola.calendula.ui.common.calendarCollapseExit
|
import de.jeanlucmakiola.floret.identity.collapseExit
|
||||||
import de.jeanlucmakiola.calendula.ui.common.calendarExpandEnter
|
import de.jeanlucmakiola.floret.identity.expandEnter
|
||||||
import de.jeanlucmakiola.calendula.ui.common.predictiveBack
|
import de.jeanlucmakiola.floret.identity.predictiveBack
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CALENDAR_COLOR_PALETTE
|
import de.jeanlucmakiola.calendula.ui.common.CALENDAR_COLOR_PALETTE
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarDatePickerDialog
|
import de.jeanlucmakiola.calendula.ui.common.CalendarDatePickerDialog
|
||||||
import de.jeanlucmakiola.calendula.ui.common.ColorSwatchRow
|
import de.jeanlucmakiola.calendula.ui.common.ColorSwatchRow
|
||||||
@@ -416,8 +416,8 @@ private fun OptionalFormSection(
|
|||||||
) {
|
) {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = visible,
|
visible = visible,
|
||||||
enter = calendarExpandEnter(),
|
enter = expandEnter(),
|
||||||
exit = calendarCollapseExit(),
|
exit = collapseExit(),
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.fillMaxWidth(), content = content)
|
Column(modifier = Modifier.fillMaxWidth(), content = content)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||||||
import de.jeanlucmakiola.calendula.R
|
import de.jeanlucmakiola.calendula.R
|
||||||
import de.jeanlucmakiola.calendula.domain.EventForm
|
import de.jeanlucmakiola.calendula.domain.EventForm
|
||||||
import de.jeanlucmakiola.calendula.domain.ics.IcsParseWarning
|
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
|
import de.jeanlucmakiola.calendula.ui.common.OptionCard
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill
|
import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill
|
||||||
import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition
|
import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarFadeSpec
|
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.currentLocale
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
||||||
import de.jeanlucmakiola.calendula.ui.common.next
|
import de.jeanlucmakiola.calendula.ui.common.next
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import de.jeanlucmakiola.calendula.R
|
import de.jeanlucmakiola.calendula.R
|
||||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||||
import de.jeanlucmakiola.calendula.ui.common.calendarAnimateItem
|
import de.jeanlucmakiola.floret.identity.animateItemMotion
|
||||||
import de.jeanlucmakiola.calendula.ui.common.predictiveBack
|
import de.jeanlucmakiola.floret.identity.predictiveBack
|
||||||
import de.jeanlucmakiola.calendula.ui.common.GroupedRow
|
import de.jeanlucmakiola.calendula.ui.common.GroupedRow
|
||||||
import de.jeanlucmakiola.calendula.ui.common.InlineTextField
|
import de.jeanlucmakiola.calendula.ui.common.InlineTextField
|
||||||
import de.jeanlucmakiola.calendula.ui.common.Position
|
import de.jeanlucmakiola.calendula.ui.common.Position
|
||||||
@@ -175,7 +175,7 @@ private fun SearchResults(
|
|||||||
SearchResultRow(
|
SearchResultRow(
|
||||||
event = event,
|
event = event,
|
||||||
position = positionOf(index, events.size),
|
position = positionOf(index, events.size),
|
||||||
modifier = calendarAnimateItem(),
|
modifier = animateItemMotion(),
|
||||||
onClick = { onEventClick(event) },
|
onClick = { onEventClick(event) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,12 +90,12 @@ import de.jeanlucmakiola.calendula.data.prefs.TimeFormatPref
|
|||||||
import de.jeanlucmakiola.calendula.data.prefs.WeekStartPref
|
import de.jeanlucmakiola.calendula.data.prefs.WeekStartPref
|
||||||
import de.jeanlucmakiola.calendula.domain.EventFormField
|
import de.jeanlucmakiola.calendula.domain.EventFormField
|
||||||
import de.jeanlucmakiola.calendula.qs.NewEventTileService
|
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.CrashReportDialog
|
||||||
import de.jeanlucmakiola.floret.crash.CrashReporter
|
import de.jeanlucmakiola.floret.crash.CrashReporter
|
||||||
import de.jeanlucmakiola.floret.crash.openIssueTracker
|
import de.jeanlucmakiola.floret.crash.openIssueTracker
|
||||||
import de.jeanlucmakiola.floret.crash.submitCrashReport
|
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.AgendaRangePicker
|
||||||
import de.jeanlucmakiola.calendula.ui.common.agendaRangeLabel
|
import de.jeanlucmakiola.calendula.ui.common.agendaRangeLabel
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CollapsingScaffold
|
import de.jeanlucmakiola.calendula.ui.common.CollapsingScaffold
|
||||||
@@ -841,8 +841,8 @@ private fun NotificationsScreen(
|
|||||||
)
|
)
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = calendarSectionExpanded,
|
visible = calendarSectionExpanded,
|
||||||
enter = calendarExpandEnter(),
|
enter = expandEnter(),
|
||||||
exit = calendarCollapseExit(),
|
exit = collapseExit(),
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
state.writableCalendars.forEach { calendar ->
|
state.writableCalendars.forEach { calendar ->
|
||||||
@@ -871,8 +871,8 @@ private fun NotificationsScreen(
|
|||||||
)
|
)
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = expanded,
|
visible = expanded,
|
||||||
enter = calendarExpandEnter(),
|
enter = expandEnter(),
|
||||||
exit = calendarCollapseExit(),
|
exit = collapseExit(),
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
val timed = state.perCalendarReminderOverride.choiceFor(calendar.id)
|
val timed = state.perCalendarReminderOverride.choiceFor(calendar.id)
|
||||||
|
|||||||
@@ -1,49 +1,31 @@
|
|||||||
package de.jeanlucmakiola.calendula.ui.theme
|
package de.jeanlucmakiola.calendula.ui.theme
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
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.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import de.jeanlucmakiola.floret.identity.FloretExpressiveTheme
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App theme. Honors:
|
* Calendula's theme: the family's [FloretExpressiveTheme] machinery — dynamic
|
||||||
* - System light/dark.
|
* colour on API 31+, system light/dark, and the **standard** motion scheme
|
||||||
* - Dynamic Color on API 31+, else falls back to the hand-tuned scheme
|
* (spring choreography without the overshoot) — fed with Calendula's own
|
||||||
* derived from [CalendulaSeed].
|
* identity: its seed-derived fallback schemes ([CalendulaLightFallback] /
|
||||||
|
* [CalendulaDarkFallback]) and [CalendulaTypography].
|
||||||
*
|
*
|
||||||
* The Settings screen (later) can override useDynamicColor and themePreference,
|
* The mechanics live in floret-kit's identity module; the look stays here, so a
|
||||||
* but the V1 foundation just follows the system.
|
* sibling app reuses the same theming without inheriting Calendula's palette.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CalendulaTheme(
|
fun CalendulaTheme(
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
dynamicColor: Boolean = true,
|
dynamicColor: Boolean = true,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val colorScheme = when {
|
FloretExpressiveTheme(
|
||||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
lightScheme = CalendulaLightFallback,
|
||||||
val ctx = LocalContext.current
|
darkScheme = CalendulaDarkFallback,
|
||||||
if (darkTheme) dynamicDarkColorScheme(ctx) else dynamicLightColorScheme(ctx)
|
darkTheme = darkTheme,
|
||||||
}
|
dynamicColor = dynamicColor,
|
||||||
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,
|
|
||||||
typography = CalendulaTypography,
|
typography = CalendulaTypography,
|
||||||
motionScheme = MotionScheme.standard(),
|
|
||||||
content = content,
|
content = content,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ import de.jeanlucmakiola.calendula.ui.common.NowLine
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill
|
import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill
|
||||||
import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition
|
import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarFadeSpec
|
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.currentLocale
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
|
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalShowHourLines
|
import de.jeanlucmakiola.calendula.ui.common.LocalShowHourLines
|
||||||
|
|||||||
Reference in New Issue
Block a user