Merge branch 'feat/32-settings-back-motion' into release/v1.0.0

This commit is contained in:
makiolaj committed 2026-11-02 19:29:43 +01:00
commit 8c5c664cd9
5 files changed
+53 -21

No files matched your search

@@ -49,11 +49,17 @@ internal fun AlarmDefaultsScreen(
state: SettingsUiState.Loaded,
viewModel: SettingsViewModel,
onBack: () -> Unit,
visible: Boolean,
) {
val defaults = state.defaults
var openPicker by rememberSaveable { mutableStateOf<OpenAlarmPicker?>(null) }
CollapsingScaffold(title = stringResource(R.string.settings_section_alarms), onBack = onBack) {
CollapsingScaffold(
title = stringResource(R.string.settings_section_alarms),
onBack = onBack,
predictiveBack = true,
visible = visible,
) {
SettingsHint(stringResource(R.string.settings_alarm_defaults_hint))
Spacer(Modifier.height(8.dp))
@@ -53,13 +53,19 @@ internal fun AppearanceScreen(
state: SettingsUiState.Loaded,
viewModel: SettingsViewModel,
onBack: () -> Unit,
visible: Boolean,
) {
val dynamicColorAvailable = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
var showTheme by rememberSaveable { mutableStateOf(false) }
var showAppIcon by rememberSaveable { mutableStateOf(false) }
val launcherIcon by viewModel.launcherIcon.collectAsStateWithLifecycle()
CollapsingScaffold(title = stringResource(R.string.settings_section_appearance), onBack = onBack) {
CollapsingScaffold(
title = stringResource(R.string.settings_section_appearance),
onBack = onBack,
predictiveBack = true,
visible = visible,
) {
GroupedRow(
title = stringResource(R.string.settings_theme),
summary = themeModeLabel(state.appearance.themeMode),
@@ -24,8 +24,14 @@ internal fun ClockSettingsScreen(
state: SettingsUiState.Loaded,
viewModel: SettingsViewModel,
onBack: () -> Unit,
visible: Boolean,
) {
CollapsingScaffold(title = stringResource(R.string.settings_section_clock), onBack = onBack) {
CollapsingScaffold(
title = stringResource(R.string.settings_section_clock),
onBack = onBack,
predictiveBack = true,
visible = visible,
) {
SettingsHint(stringResource(R.string.settings_clock_hint))
Spacer(Modifier.height(8.dp))
@@ -1,13 +1,10 @@
package de.jeanlucmakiola.clockula.ui.settings
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
@@ -28,6 +25,7 @@ import androidx.compose.material.icons.filled.PrivacyTip
import androidx.compose.material.icons.filled.Public
import androidx.compose.material.icons.filled.Timer
import androidx.compose.material.icons.filled.Translate
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -38,6 +36,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
@@ -63,6 +62,7 @@ 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.floret.identity.predictiveBackExit
/** The settings sub-screens reached from the hub's category rows. */
private enum class SettingsSection { Appearance, Alarms, Timers, Clock }
@@ -114,10 +114,6 @@ fun SettingsScreen(
val loaded = state as? SettingsUiState.Loaded
// Inside a sub-screen, system back (button or gesture) returns to the hub
// rather than popping the whole Settings destination.
BackHandler(enabled = section != null) { section = null }
Box(
modifier = modifier
.fillMaxSize()
@@ -133,17 +129,21 @@ fun SettingsScreen(
)
if (loaded != null) {
SlideInSection(visible = section == SettingsSection.Appearance) {
AppearanceScreen(state = loaded, viewModel = viewModel, onBack = { section = null })
val appearanceOpen = section == SettingsSection.Appearance
SlideInSection(visible = appearanceOpen) {
AppearanceScreen(state = loaded, viewModel = viewModel, onBack = { section = null }, visible = appearanceOpen)
}
SlideInSection(visible = section == SettingsSection.Alarms) {
AlarmDefaultsScreen(state = loaded, viewModel = viewModel, onBack = { section = null })
val alarmsOpen = section == SettingsSection.Alarms
SlideInSection(visible = alarmsOpen) {
AlarmDefaultsScreen(state = loaded, viewModel = viewModel, onBack = { section = null }, visible = alarmsOpen)
}
SlideInSection(visible = section == SettingsSection.Timers) {
TimerDefaultsScreen(state = loaded, viewModel = viewModel, onBack = { section = null })
val timersOpen = section == SettingsSection.Timers
SlideInSection(visible = timersOpen) {
TimerDefaultsScreen(state = loaded, viewModel = viewModel, onBack = { section = null }, visible = timersOpen)
}
SlideInSection(visible = section == SettingsSection.Clock) {
ClockSettingsScreen(state = loaded, viewModel = viewModel, onBack = { section = null })
val clockOpen = section == SettingsSection.Clock
SlideInSection(visible = clockOpen) {
ClockSettingsScreen(state = loaded, viewModel = viewModel, onBack = { section = null }, visible = clockOpen)
}
}
}
@@ -172,12 +172,20 @@ fun SettingsScreen(
}
}
/**
* A sub-screen layered over the hub, with Calendula's overlay motion: it slides
* in on the motion scheme's spring and leaves from its predictive-back preview.
* Inside it, back (button or gesture) returns to the hub rather than popping
* the whole Settings destination; the sub-screen's scaffold takes it.
*/
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun SlideInSection(visible: Boolean, content: @Composable () -> Unit) {
val slideSpec = MaterialTheme.motionScheme.defaultSpatialSpec<IntOffset>()
AnimatedVisibility(
visible = visible,
enter = slideInHorizontally { it } + fadeIn(),
exit = slideOutHorizontally { it } + fadeOut(),
enter = slideInHorizontally(slideSpec) { it } + fadeIn(),
exit = predictiveBackExit(),
) { content() }
}
@@ -35,12 +35,18 @@ internal fun TimerDefaultsScreen(
state: SettingsUiState.Loaded,
viewModel: SettingsViewModel,
onBack: () -> Unit,
visible: Boolean,
) {
val defaults = state.defaults
var showRampPicker by rememberSaveable { mutableStateOf(false) }
var silenceAfterOpen by rememberSaveable { mutableStateOf(false) }
CollapsingScaffold(title = stringResource(R.string.settings_section_timers), onBack = onBack) {
CollapsingScaffold(
title = stringResource(R.string.settings_section_timers),
onBack = onBack,
predictiveBack = true,
visible = visible,
) {
SettingsHint(stringResource(R.string.settings_timer_defaults_hint))
Spacer(Modifier.height(8.dp))