diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/AlarmDefaultsScreen.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/AlarmDefaultsScreen.kt index 5792857..72b1d66 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/AlarmDefaultsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/AlarmDefaultsScreen.kt @@ -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(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)) diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/AppearanceScreen.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/AppearanceScreen.kt index 441736b..62fcf63 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/AppearanceScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/AppearanceScreen.kt @@ -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), diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/ClockSettingsScreen.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/ClockSettingsScreen.kt index d23bb78..e1f2362 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/ClockSettingsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/ClockSettingsScreen.kt @@ -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)) diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/SettingsScreen.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/SettingsScreen.kt index 73b884d..35b135a 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/SettingsScreen.kt @@ -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() AnimatedVisibility( visible = visible, - enter = slideInHorizontally { it } + fadeIn(), - exit = slideOutHorizontally { it } + fadeOut(), + enter = slideInHorizontally(slideSpec) { it } + fadeIn(), + exit = predictiveBackExit(), ) { content() } } diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/TimerDefaultsScreen.kt b/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/TimerDefaultsScreen.kt index 671a73f..8468d92 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/TimerDefaultsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/ui/settings/TimerDefaultsScreen.kt @@ -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))