From ee73dd3076445a3ed8b5cc70e2eee99ddf1b936d Mon Sep 17 00:00:00 2001 From: Xitee <59659167+Xitee1@users.noreply.github.com> Date: Mon, 6 Jul 2026 19:01:56 +0200 Subject: [PATCH] feat(settings): add screen rotation mode setting Add a rotation-mode setting so users can control screen orientation independently of the previously hard-wired behavior. A new "Auto-rotate" row in Settings > Appearance opens a dialog with three modes: - System (default): rotate only while the OS auto-rotate flag is on, observed via Settings.System.ACCELEROMETER_ROTATION (no new permission) - Always: rotate regardless of the system lock (prior behavior) - Portrait: never rotate The mode is threaded through rememberDeviceOrientation, which gates the OrientationEventListener (and the system-flag ContentObserver in System mode) so both the window lock and the timer's in-Compose counter-rotation follow the setting. Co-Authored-By: Claude Fable 5 --- .../navigation/SleepTimerNavHost.kt | 7 +- .../core/data/model/AutoRotateMode.kt | 15 +++ .../core/data/model/UserSettings.kt | 1 + .../data/repository/SettingsRepository.kt | 2 + .../data/repository/SettingsRepositoryImpl.kt | 7 + .../feature/timer/settings/SettingsScreen.kt | 19 +++ .../timer/settings/SettingsViewModel.kt | 5 + .../components/AutoRotateModeDialog.kt | 122 ++++++++++++++++++ .../timer/timer/AppOrientationViewModel.kt | 30 +++++ .../feature/timer/timer/DeviceOrientation.kt | 60 ++++++++- .../feature/timer/timer/TimerScreen.kt | 2 +- .../timer/src/main/res/values-de/strings.xml | 8 ++ feature/timer/src/main/res/values/strings.xml | 8 ++ 13 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/model/AutoRotateMode.kt create mode 100644 feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/components/AutoRotateModeDialog.kt create mode 100644 feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/AppOrientationViewModel.kt diff --git a/app/src/main/kotlin/dev/xitee/sleeptimer/navigation/SleepTimerNavHost.kt b/app/src/main/kotlin/dev/xitee/sleeptimer/navigation/SleepTimerNavHost.kt index 1766f21..191df89 100644 --- a/app/src/main/kotlin/dev/xitee/sleeptimer/navigation/SleepTimerNavHost.kt +++ b/app/src/main/kotlin/dev/xitee/sleeptimer/navigation/SleepTimerNavHost.kt @@ -6,6 +6,8 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState @@ -15,6 +17,7 @@ import dev.xitee.sleeptimer.feature.timer.about.AboutScreen import dev.xitee.sleeptimer.feature.timer.settings.SettingsScreen import dev.xitee.sleeptimer.feature.timer.settings.ThemePickerScreen import dev.xitee.sleeptimer.feature.timer.timer.AppOrientationController +import dev.xitee.sleeptimer.feature.timer.timer.AppOrientationViewModel import dev.xitee.sleeptimer.feature.timer.timer.DeviceOrientation import dev.xitee.sleeptimer.feature.timer.timer.TimerScreen import dev.xitee.sleeptimer.feature.timer.timer.rememberDeviceOrientation @@ -30,7 +33,9 @@ fun SleepTimerNavHost() { // briefly shows Timer's counter-rotated content being rotated by the // window manager, appearing upside-down. In natural portrait there is no // orientation flip, so the fade is kept. - val deviceOrientation by rememberDeviceOrientation() + val autoRotateMode by hiltViewModel() + .autoRotateMode.collectAsStateWithLifecycle() + val deviceOrientation by rememberDeviceOrientation(mode = autoRotateMode) val animate = deviceOrientation == DeviceOrientation.PORTRAIT AppOrientationController(orientation = deviceOrientation, lockPortrait = isTimer) diff --git a/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/model/AutoRotateMode.kt b/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/model/AutoRotateMode.kt new file mode 100644 index 0000000..029d02d --- /dev/null +++ b/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/model/AutoRotateMode.kt @@ -0,0 +1,15 @@ +package dev.xitee.sleeptimer.core.data.model + +enum class AutoRotateMode { + System, + Always, + Portrait, + ; + + companion object { + val Default: AutoRotateMode = System + + fun fromStorage(value: String?): AutoRotateMode = + entries.firstOrNull { it.name == value } ?: Default + } +} diff --git a/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/model/UserSettings.kt b/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/model/UserSettings.kt index d6a6eda..3380692 100644 --- a/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/model/UserSettings.kt +++ b/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/model/UserSettings.kt @@ -10,6 +10,7 @@ data class UserSettings( val hapticFeedbackEnabled: Boolean = true, val theme: ThemeId = ThemeId.Default, val starsEnabled: Boolean = true, + val autoRotateMode: AutoRotateMode = AutoRotateMode.Default, val stepMinutes: Int = 5, val presetMinutes: Int = 15, ) diff --git a/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/repository/SettingsRepository.kt b/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/repository/SettingsRepository.kt index afba765..45c620d 100644 --- a/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/repository/SettingsRepository.kt +++ b/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/repository/SettingsRepository.kt @@ -1,5 +1,6 @@ package dev.xitee.sleeptimer.core.data.repository +import dev.xitee.sleeptimer.core.data.model.AutoRotateMode import dev.xitee.sleeptimer.core.data.model.ThemeId import dev.xitee.sleeptimer.core.data.model.UserSettings import kotlinx.coroutines.flow.Flow @@ -15,6 +16,7 @@ interface SettingsRepository { suspend fun updateHapticFeedback(enabled: Boolean) suspend fun updateTheme(theme: ThemeId) suspend fun updateStarsEnabled(enabled: Boolean) + suspend fun updateAutoRotateMode(mode: AutoRotateMode) suspend fun updateStepMinutes(minutes: Int) suspend fun updatePresetMinutes(minutes: Int) } diff --git a/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/repository/SettingsRepositoryImpl.kt b/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/repository/SettingsRepositoryImpl.kt index 80dca3f..42a4259 100644 --- a/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/repository/SettingsRepositoryImpl.kt +++ b/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/repository/SettingsRepositoryImpl.kt @@ -6,6 +6,7 @@ import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey +import dev.xitee.sleeptimer.core.data.model.AutoRotateMode import dev.xitee.sleeptimer.core.data.model.ThemeId import dev.xitee.sleeptimer.core.data.model.UserSettings import kotlinx.coroutines.flow.Flow @@ -28,6 +29,7 @@ class SettingsRepositoryImpl @Inject constructor( val HAPTIC_FEEDBACK = booleanPreferencesKey("haptic_feedback") val THEME = stringPreferencesKey("theme") val STARS_ENABLED = booleanPreferencesKey("stars_enabled") + val AUTO_ROTATE_MODE = stringPreferencesKey("auto_rotate_mode") val STEP_MINUTES = intPreferencesKey("step_minutes") val PRESET_MINUTES = intPreferencesKey("preset_minutes") } @@ -46,6 +48,7 @@ class SettingsRepositoryImpl @Inject constructor( hapticFeedbackEnabled = prefs[HAPTIC_FEEDBACK] ?: d.hapticFeedbackEnabled, theme = ThemeId.fromStorage(prefs[THEME]), starsEnabled = prefs[STARS_ENABLED] ?: d.starsEnabled, + autoRotateMode = AutoRotateMode.fromStorage(prefs[AUTO_ROTATE_MODE]), stepMinutes = prefs[STEP_MINUTES] ?: d.stepMinutes, presetMinutes = prefs[PRESET_MINUTES] ?: d.presetMinutes, ) @@ -87,6 +90,10 @@ class SettingsRepositoryImpl @Inject constructor( dataStore.edit { it[STARS_ENABLED] = enabled } } + override suspend fun updateAutoRotateMode(mode: AutoRotateMode) { + dataStore.edit { it[AUTO_ROTATE_MODE] = mode.name } + } + override suspend fun updateStepMinutes(minutes: Int) { dataStore.edit { it[STEP_MINUTES] = minutes.coerceIn(1, 30) } } diff --git a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/SettingsScreen.kt b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/SettingsScreen.kt index b62ae20..b98ab50 100644 --- a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/SettingsScreen.kt +++ b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/SettingsScreen.kt @@ -30,6 +30,7 @@ import androidx.compose.material.icons.filled.Bluetooth import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.MusicOff import androidx.compose.material.icons.filled.PhoneAndroid +import androidx.compose.material.icons.filled.ScreenRotation import androidx.compose.material.icons.filled.Vibration import androidx.compose.material.icons.filled.Wifi import androidx.compose.material3.Icon @@ -53,9 +54,12 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LifecycleEventEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import dev.xitee.sleeptimer.core.service.shizuku.ShizukuManager +import dev.xitee.sleeptimer.core.data.model.AutoRotateMode import dev.xitee.sleeptimer.feature.timer.R +import dev.xitee.sleeptimer.feature.timer.settings.components.AutoRotateModeDialog import dev.xitee.sleeptimer.feature.timer.settings.components.FadeOutSlider import dev.xitee.sleeptimer.feature.timer.settings.components.ScreenLockMethodDialog +import dev.xitee.sleeptimer.feature.timer.settings.components.labelRes import dev.xitee.sleeptimer.feature.timer.settings.components.SettingsToggleRow import dev.xitee.sleeptimer.feature.timer.settings.components.SettingsTopBar import dev.xitee.sleeptimer.feature.timer.settings.components.ShizukuRequiredDialog @@ -115,6 +119,7 @@ private fun SettingsContent( var shizukuDialogExplanation by remember { mutableStateOf(null) } var pendingShizukuToggle by remember { mutableStateOf<(() -> Unit)?>(null) } var showMethodDialog by remember { mutableStateOf(false) } + var showAutoRotateDialog by remember { mutableStateOf(false) } fun requestWithShizuku(explanation: String, enableAction: () -> Unit) { if (viewModel.isShizukuReady()) { @@ -167,6 +172,14 @@ private fun SettingsContent( ) } + if (showAutoRotateDialog) { + AutoRotateModeDialog( + selected = uiState.settings.autoRotateMode, + onSelect = { viewModel.updateAutoRotateMode(it) }, + onDismiss = { showAutoRotateDialog = false }, + ) + } + // Auto-complete the pending toggle if Shizuku transitions to Ready while dialog is open. LaunchedEffect(uiState.shizukuState, pendingShizukuToggle) { if (pendingShizukuToggle != null && uiState.shizukuState == ShizukuManager.State.Ready) { @@ -224,6 +237,12 @@ private fun SettingsContent( onCheckedChange = { viewModel.updateStarsEnabled(it) }, enabled = AppThemes.byId(uiState.settings.theme).allowStars, ) + SettingsNavigationRow( + icon = Icons.Default.ScreenRotation, + title = stringResource(R.string.auto_rotate_title), + description = stringResource(uiState.settings.autoRotateMode.labelRes), + onClick = { showAutoRotateDialog = true }, + ) SectionHeader(stringResource(R.string.category_sleep_timer)) SettingsToggleRow( diff --git a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/SettingsViewModel.kt b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/SettingsViewModel.kt index 9529424..c6dcc63 100644 --- a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/SettingsViewModel.kt +++ b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/SettingsViewModel.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext +import dev.xitee.sleeptimer.core.data.model.AutoRotateMode import dev.xitee.sleeptimer.core.data.model.ThemeId import dev.xitee.sleeptimer.core.data.repository.SettingsRepository import dev.xitee.sleeptimer.core.service.shizuku.ShizukuManager @@ -100,6 +101,10 @@ class SettingsViewModel @Inject constructor( viewModelScope.launch { settingsRepository.updateStarsEnabled(enabled) } } + fun updateAutoRotateMode(mode: AutoRotateMode) { + viewModelScope.launch { settingsRepository.updateAutoRotateMode(mode) } + } + fun updateStepMinutes(minutes: Int) { viewModelScope.launch { settingsRepository.updateStepMinutes(minutes) } } diff --git a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/components/AutoRotateModeDialog.kt b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/components/AutoRotateModeDialog.kt new file mode 100644 index 0000000..b3c783c --- /dev/null +++ b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/components/AutoRotateModeDialog.kt @@ -0,0 +1,122 @@ +package dev.xitee.sleeptimer.feature.timer.settings.components + +import androidx.annotation.StringRes +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.PhoneAndroid +import androidx.compose.material.icons.filled.ScreenLockPortrait +import androidx.compose.material.icons.filled.ScreenRotation +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import dev.xitee.sleeptimer.core.data.model.AutoRotateMode +import dev.xitee.sleeptimer.feature.timer.R +import dev.xitee.sleeptimer.feature.timer.theme.appTheme + +/** Title string shown in the settings row's description slot for the selected mode. */ +@get:StringRes +val AutoRotateMode.labelRes: Int + get() = when (this) { + AutoRotateMode.System -> R.string.auto_rotate_system_title + AutoRotateMode.Always -> R.string.auto_rotate_always_title + AutoRotateMode.Portrait -> R.string.auto_rotate_portrait_title + } + +@Composable +fun AutoRotateModeDialog( + selected: AutoRotateMode, + onSelect: (AutoRotateMode) -> Unit, + onDismiss: () -> Unit, +) { + AlertDialog( + onDismissRequest = onDismiss, + icon = { Icon(Icons.Default.ScreenRotation, contentDescription = null) }, + title = { Text(stringResource(R.string.auto_rotate_dialog_title)) }, + text = { + Column { + ModeOption( + icon = Icons.Default.PhoneAndroid, + title = stringResource(R.string.auto_rotate_system_title), + description = stringResource(R.string.auto_rotate_system_description), + isSelected = selected == AutoRotateMode.System, + onClick = { onSelect(AutoRotateMode.System); onDismiss() }, + ) + Spacer(Modifier.height(8.dp)) + ModeOption( + icon = Icons.Default.ScreenRotation, + title = stringResource(R.string.auto_rotate_always_title), + description = stringResource(R.string.auto_rotate_always_description), + isSelected = selected == AutoRotateMode.Always, + onClick = { onSelect(AutoRotateMode.Always); onDismiss() }, + ) + Spacer(Modifier.height(8.dp)) + ModeOption( + icon = Icons.Default.ScreenLockPortrait, + title = stringResource(R.string.auto_rotate_portrait_title), + description = stringResource(R.string.auto_rotate_portrait_description), + isSelected = selected == AutoRotateMode.Portrait, + onClick = { onSelect(AutoRotateMode.Portrait); onDismiss() }, + ) + } + }, + confirmButton = {}, + dismissButton = { + TextButton(onClick = onDismiss) { + Text(stringResource(R.string.shizuku_action_cancel)) + } + }, + ) +} + +@Composable +private fun ModeOption( + icon: ImageVector, + title: String, + description: String, + isSelected: Boolean, + onClick: () -> Unit, +) { + val accent = appTheme().accent + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + icon, + contentDescription = null, + tint = if (isSelected) accent else LocalContentColor.current, + modifier = Modifier.size(28.dp), + ) + Spacer(Modifier.width(16.dp)) + Column { + Text( + title, + style = MaterialTheme.typography.titleMedium, + color = if (isSelected) accent else LocalContentColor.current, + fontWeight = if (isSelected) FontWeight.SemiBold else null, + ) + Text(description, style = MaterialTheme.typography.bodyMedium) + } + } +} diff --git a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/AppOrientationViewModel.kt b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/AppOrientationViewModel.kt new file mode 100644 index 0000000..bf158f2 --- /dev/null +++ b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/AppOrientationViewModel.kt @@ -0,0 +1,30 @@ +package dev.xitee.sleeptimer.feature.timer.timer + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import dev.xitee.sleeptimer.core.data.model.AutoRotateMode +import dev.xitee.sleeptimer.core.data.model.UserSettings +import dev.xitee.sleeptimer.core.data.repository.SettingsRepository +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject + +// Exposes the auto-rotate setting to SleepTimerNavHost, which sits outside any +// NavBackStackEntry and so has no destination-scoped ViewModel of its own. +@HiltViewModel +class AppOrientationViewModel @Inject constructor( + settingsRepository: SettingsRepository, +) : ViewModel() { + + val autoRotateMode: StateFlow = + settingsRepository.settings + .map { it.autoRotateMode } + .stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(5_000), + UserSettings().autoRotateMode, + ) +} diff --git a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/DeviceOrientation.kt b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/DeviceOrientation.kt index ddd6b12..8ca5d58 100644 --- a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/DeviceOrientation.kt +++ b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/DeviceOrientation.kt @@ -1,6 +1,11 @@ package dev.xitee.sleeptimer.feature.timer.timer +import android.content.Context import android.content.pm.ActivityInfo +import android.database.ContentObserver +import android.os.Handler +import android.os.Looper +import android.provider.Settings import android.view.OrientationEventListener import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -8,6 +13,7 @@ import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext +import dev.xitee.sleeptimer.core.data.model.AutoRotateMode enum class DeviceOrientation(val degrees: Int) { PORTRAIT(0), @@ -31,11 +37,27 @@ fun DeviceOrientation.toActivityInfoOrientation(): Int = when (this) { } @Composable -fun rememberDeviceOrientation(): State { +fun rememberDeviceOrientation(mode: AutoRotateMode): State { val context = LocalContext.current val state = remember { mutableStateOf(DeviceOrientation.PORTRAIT) } - DisposableEffect(Unit) { + // Resolve whether rotation is allowed. In System mode this follows the OS-wide + // auto-rotate flag; the ContentObserver inside rememberSystemAutoRotate only exists + // in the composition while this branch is active, so it self-disposes on mode change. + val enabled = when (mode) { + AutoRotateMode.System -> rememberSystemAutoRotate().value + AutoRotateMode.Always -> true + AutoRotateMode.Portrait -> false + } + + DisposableEffect(enabled) { + if (!enabled) { + // Auto-rotate is off: pin to portrait and never register the sensor so + // AppOrientationController locks the window and the timer content stops + // counter-rotating, at no battery cost. + state.value = DeviceOrientation.PORTRAIT + return@DisposableEffect onDispose {} + } val listener = object : OrientationEventListener(context) { override fun onOrientationChanged(orientation: Int) { if (orientation == ORIENTATION_UNKNOWN) return @@ -54,6 +76,40 @@ fun rememberDeviceOrientation(): State { return state } +// Tracks the system-wide auto-rotate flag (Settings > Display > Auto-rotate, or the +// quick-settings tile). Readable and observable without any permission. The observer is +// process-scoped, so flips made while the app is backgrounded still land before the user +// returns; the synchronous initial read means there is no wrong-value first frame. +@Composable +private fun rememberSystemAutoRotate(): State { + val context = LocalContext.current + val state = remember { mutableStateOf(readSystemAutoRotate(context)) } + + DisposableEffect(context) { + state.value = readSystemAutoRotate(context) + val observer = object : ContentObserver(Handler(Looper.getMainLooper())) { + override fun onChange(selfChange: Boolean) { + state.value = readSystemAutoRotate(context) + } + } + context.contentResolver.registerContentObserver( + Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), + false, + observer, + ) + onDispose { context.contentResolver.unregisterContentObserver(observer) } + } + + return state +} + +private fun readSystemAutoRotate(context: Context): Boolean = + Settings.System.getInt( + context.contentResolver, + Settings.System.ACCELEROMETER_ROTATION, + 0, + ) == 1 + // Hysteresis: stick with the current bucket until the device pose is more than // 60° away from its centre — 15° past the natural 45° boundary — so small wobbles // don't flip state. diff --git a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/TimerScreen.kt b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/TimerScreen.kt index b402ec8..bbdd9c9 100644 --- a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/TimerScreen.kt +++ b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/TimerScreen.kt @@ -101,7 +101,7 @@ private fun TimerContent( val context = LocalContext.current val screenDescription = stringResource(R.string.screen_description) - val orientation by rememberDeviceOrientation() + val orientation by rememberDeviceOrientation(mode = settings.autoRotateMode) val isLandscape = orientation == DeviceOrientation.LANDSCAPE_LEFT || orientation == DeviceOrientation.LANDSCAPE_RIGHT val animatedAngle = animatedRotationAngle(orientation) diff --git a/feature/timer/src/main/res/values-de/strings.xml b/feature/timer/src/main/res/values-de/strings.xml index 75de465..176ba0c 100644 --- a/feature/timer/src/main/res/values-de/strings.xml +++ b/feature/timer/src/main/res/values-de/strings.xml @@ -21,6 +21,14 @@ Sternen-Hintergrund Driftendes Sternenfeld hinter dem Dial Für dieses Theme nicht verfügbar + Automatisch drehen + Drehverhalten wählen + Systemeinstellung + Dreht nur, wenn die Bildschirmdrehung im System aktiviert ist + Immer + Dreht mit dem Gerät, auch wenn die Drehung im System gesperrt ist + Hochformat + Nie drehen Sleep Timer Wiedergabe Medienwiedergabe stoppen diff --git a/feature/timer/src/main/res/values/strings.xml b/feature/timer/src/main/res/values/strings.xml index 3ed966c..c83645a 100644 --- a/feature/timer/src/main/res/values/strings.xml +++ b/feature/timer/src/main/res/values/strings.xml @@ -21,6 +21,14 @@ Stars background Drifting starfield behind the dial Not available on this theme + Auto-rotate + Choose rotation mode + System setting + Rotate only when system auto-rotate is on + Always + Rotate with the device, even if system rotation is locked + Portrait + Never rotate Sleep Timer Playback Stop audio and video playback