feat(settings): add a settings option to disable AOD

disabled by default
This commit is contained in:
Nishant Mishra
2025-10-21 11:41:38 +05:30
parent 518f172054
commit 4293f0d5f1
10 changed files with 148 additions and 84 deletions

View File

@@ -51,7 +51,10 @@ class MainActivity : ComponentActivity() {
appContainer.appTimerRepository.colorScheme = colorScheme appContainer.appTimerRepository.colorScheme = colorScheme
} }
AppScreen(timerViewModel = timerViewModel, isAODEnabled = true) AppScreen(
timerViewModel = timerViewModel,
isAODEnabled = preferencesState.aodEnabled
)
} }
} }
} }

View File

@@ -177,8 +177,8 @@ fun SharedTransitionScope.AlwaysOnDisplay(
} }
} }
val x by animateIntAsState(randomX) val x by animateIntAsState(randomX, motionScheme.slowSpatialSpec())
val y by animateIntAsState(randomY) val y by animateIntAsState(randomY, motionScheme.slowSpatialSpec())
Box( Box(
modifier = modifier modifier = modifier

View File

@@ -15,6 +15,7 @@ import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@@ -143,6 +144,7 @@ fun SettingsScreenRoot(
onAlarmEnabledChange = viewModel::saveAlarmEnabled, onAlarmEnabledChange = viewModel::saveAlarmEnabled,
onVibrateEnabledChange = viewModel::saveVibrateEnabled, onVibrateEnabledChange = viewModel::saveVibrateEnabled,
onBlackThemeChange = viewModel::saveBlackTheme, onBlackThemeChange = viewModel::saveBlackTheme,
onAodEnabledChange = viewModel::saveAodEnabled,
onAlarmSoundChanged = { onAlarmSoundChanged = {
viewModel.saveAlarmSound(it) viewModel.saveAlarmSound(it)
Intent(context, TimerService::class.java).apply { Intent(context, TimerService::class.java).apply {
@@ -170,6 +172,7 @@ private fun SettingsScreen(
onAlarmEnabledChange: (Boolean) -> Unit, onAlarmEnabledChange: (Boolean) -> Unit,
onVibrateEnabledChange: (Boolean) -> Unit, onVibrateEnabledChange: (Boolean) -> Unit,
onBlackThemeChange: (Boolean) -> Unit, onBlackThemeChange: (Boolean) -> Unit,
onAodEnabledChange: (Boolean) -> Unit,
onAlarmSoundChanged: (Uri?) -> Unit, onAlarmSoundChanged: (Uri?) -> Unit,
onThemeChange: (String) -> Unit, onThemeChange: (String) -> Unit,
onColorSchemeChange: (Color) -> Unit, onColorSchemeChange: (Color) -> Unit,
@@ -181,14 +184,14 @@ private fun SettingsScreen(
checkedIconColor = colorScheme.primary, checkedIconColor = colorScheme.primary,
) )
val themeMap: Map<String, Pair<Int, String>> = remember { val themeMap: Map<String, Pair<Int, Int>> = remember {
mapOf( mapOf(
"auto" to Pair( "auto" to Pair(
R.drawable.brightness_auto, R.drawable.brightness_auto,
context.getString(R.string.system_default) R.string.system_default
), ),
"light" to Pair(R.drawable.light_mode, context.getString(R.string.light)), "light" to Pair(R.drawable.light_mode, R.string.light),
"dark" to Pair(R.drawable.dark_mode, context.getString(R.string.dark)) "dark" to Pair(R.drawable.dark_mode, R.string.dark)
) )
} }
val reverseThemeMap: Map<String, String> = remember { val reverseThemeMap: Map<String, String> = remember {
@@ -232,27 +235,39 @@ private fun SettingsScreen(
putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, alarmSound.toUri()) putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, alarmSound.toUri())
} }
val switchItems = remember(preferencesState.blackTheme, alarmEnabled, vibrateEnabled) { val switchItems = remember(
preferencesState.blackTheme,
preferencesState.aodEnabled,
alarmEnabled,
vibrateEnabled
) {
listOf( listOf(
SettingsSwitchItem( SettingsSwitchItem(
checked = preferencesState.blackTheme, checked = preferencesState.blackTheme,
icon = R.drawable.contrast, icon = R.drawable.contrast,
label = context.getString(R.string.black_theme), label = R.string.black_theme,
description = context.getString(R.string.black_theme_desc), description = R.string.black_theme_desc,
onClick = onBlackThemeChange onClick = onBlackThemeChange
), ),
SettingsSwitchItem(
checked = preferencesState.aodEnabled,
icon = R.drawable.aod,
label = R.string.always_on_display,
description = R.string.always_on_display_desc,
onClick = onAodEnabledChange
),
SettingsSwitchItem( SettingsSwitchItem(
checked = alarmEnabled, checked = alarmEnabled,
icon = R.drawable.alarm_on, icon = R.drawable.alarm_on,
label = context.getString(R.string.alarm), label = R.string.alarm,
description = context.getString(R.string.alarm_desc), description = R.string.alarm_desc,
onClick = onAlarmEnabledChange onClick = onAlarmEnabledChange
), ),
SettingsSwitchItem( SettingsSwitchItem(
checked = vibrateEnabled, checked = vibrateEnabled,
icon = R.drawable.mobile_vibrate, icon = R.drawable.mobile_vibrate,
label = context.getString(R.string.vibrate), label = R.string.vibrate,
description = context.getString(R.string.vibrate_desc), description = R.string.vibrate_desc,
onClick = onVibrateEnabledChange onClick = onVibrateEnabledChange
) )
) )
@@ -404,14 +419,13 @@ private fun SettingsScreen(
.clip(middleListItemShape) .clip(middleListItemShape)
) )
} }
item { itemsIndexed(switchItems.take(2)) { index, item ->
val item = switchItems[0]
ListItem( ListItem(
leadingContent = { leadingContent = {
Icon(painterResource(item.icon), contentDescription = null) Icon(painterResource(item.icon), contentDescription = null)
}, },
headlineContent = { Text(item.label) }, headlineContent = { Text(stringResource(item.label)) },
supportingContent = { Text(item.description) }, supportingContent = { Text(stringResource(item.description)) },
trailingContent = { trailingContent = {
Switch( Switch(
checked = item.checked, checked = item.checked,
@@ -435,7 +449,9 @@ private fun SettingsScreen(
) )
}, },
colors = listItemColors, colors = listItemColors,
modifier = Modifier.clip(bottomListItemShape) modifier = Modifier
.padding(top = if (index != 0) 16.dp else 0.dp)
.clip(if (index == 0) bottomListItemShape else cardShape)
) )
} }
@@ -454,13 +470,13 @@ private fun SettingsScreen(
.clickable(onClick = { ringtonePickerLauncher.launch(intent) }) .clickable(onClick = { ringtonePickerLauncher.launch(intent) })
) )
} }
itemsIndexed(switchItems.drop(1)) { index, item -> itemsIndexed(switchItems.drop(2)) { index, item ->
ListItem( ListItem(
leadingContent = { leadingContent = {
Icon(painterResource(item.icon), contentDescription = null) Icon(painterResource(item.icon), contentDescription = null)
}, },
headlineContent = { Text(item.label) }, headlineContent = { Text(stringResource(item.label)) },
supportingContent = { Text(item.description) }, supportingContent = { Text(stringResource(item.description)) },
trailingContent = { trailingContent = {
Switch( Switch(
checked = item.checked, checked = item.checked,
@@ -487,7 +503,7 @@ private fun SettingsScreen(
modifier = Modifier modifier = Modifier
.clip( .clip(
when (index) { when (index) {
switchItems.lastIndex - 1 -> bottomListItemShape switchItems.lastIndex - 2 -> bottomListItemShape
else -> middleListItemShape else -> middleListItemShape
} }
) )
@@ -546,6 +562,7 @@ fun SettingsScreenPreview() {
onAlarmEnabledChange = {}, onAlarmEnabledChange = {},
onVibrateEnabledChange = {}, onVibrateEnabledChange = {},
onBlackThemeChange = {}, onBlackThemeChange = {},
onAodEnabledChange = {},
onAlarmSoundChanged = {}, onAlarmSoundChanged = {},
onThemeChange = {}, onThemeChange = {},
onColorSchemeChange = {}, onColorSchemeChange = {},
@@ -557,7 +574,7 @@ fun SettingsScreenPreview() {
data class SettingsSwitchItem( data class SettingsSwitchItem(
val checked: Boolean, val checked: Boolean,
@param:DrawableRes val icon: Int, @param:DrawableRes val icon: Int,
val label: String, @param:StringRes val label: Int,
val description: String, @param:StringRes val description: Int,
val onClick: (Boolean) -> Unit val onClick: (Boolean) -> Unit
) )

View File

@@ -31,11 +31,12 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
@@ -50,14 +51,16 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun ThemeDialog( fun ThemeDialog(
themeMap: Map<String, Pair<Int, String>>, themeMap: Map<String, Pair<Int, Int>>,
reverseThemeMap: Map<String, String>, reverseThemeMap: Map<String, String>,
theme: String, theme: String,
setShowThemeDialog: (Boolean) -> Unit, setShowThemeDialog: (Boolean) -> Unit,
onThemeChange: (String) -> Unit onThemeChange: (String) -> Unit
) { ) {
val selectedOption = val selectedOption =
remember { mutableStateOf(themeMap[theme]!!.second) } remember { mutableIntStateOf(themeMap[theme]!!.second) }
val context = LocalContext.current
BasicAlertDialog( BasicAlertDialog(
onDismissRequest = { setShowThemeDialog(false) } onDismissRequest = { setShowThemeDialog(false) }
@@ -80,7 +83,7 @@ fun ThemeDialog(
verticalArrangement = Arrangement.spacedBy(2.dp), verticalArrangement = Arrangement.spacedBy(2.dp),
modifier = Modifier.selectableGroup() modifier = Modifier.selectableGroup()
) { ) {
themeMap.entries.forEachIndexed { index: Int, pair: Map.Entry<String, Pair<Int, String>> -> themeMap.entries.forEachIndexed { index: Int, pair: Map.Entry<String, Pair<Int, Int>> ->
val text = pair.value.second val text = pair.value.second
val selected = text == selectedOption.value val selected = text == selectedOption.value
@@ -94,7 +97,10 @@ fun ThemeDialog(
} }
}, },
headlineContent = { headlineContent = {
Text(text = text, style = MaterialTheme.typography.bodyLarge) Text(
text = stringResource(text),
style = MaterialTheme.typography.bodyLarge
)
}, },
colors = if (!selected) listItemColors else selectedListItemColors, colors = if (!selected) listItemColors else selectedListItemColors,
modifier = Modifier modifier = Modifier
@@ -110,7 +116,11 @@ fun ThemeDialog(
selected = (text == selectedOption.value), selected = (text == selectedOption.value),
onClick = { onClick = {
selectedOption.value = text selectedOption.value = text
onThemeChange(reverseThemeMap[selectedOption.value]!!) onThemeChange(
reverseThemeMap[context.getString(
selectedOption.intValue
)]!!
)
}, },
role = Role.RadioButton role = Role.RadioButton
) )

View File

@@ -25,7 +25,7 @@ import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
@Composable @Composable
fun ThemePickerListItem( fun ThemePickerListItem(
theme: String, theme: String,
themeMap: Map<String, Pair<Int, String>>, themeMap: Map<String, Pair<Int, Int>>,
reverseThemeMap: Map<String, String>, reverseThemeMap: Map<String, String>,
items: Int, items: Int,
index: Int, index: Int,
@@ -53,7 +53,7 @@ fun ThemePickerListItem(
}, },
headlineContent = { Text(stringResource(R.string.theme)) }, headlineContent = { Text(stringResource(R.string.theme)) },
supportingContent = { supportingContent = {
Text(themeMap[theme]!!.second) Text(stringResource(themeMap[theme]!!.second))
}, },
colors = listItemColors, colors = listItemColors,
items = items, items = items,

View File

@@ -14,5 +14,6 @@ import androidx.compose.ui.graphics.Color
data class PreferencesState( data class PreferencesState(
val theme: String = "auto", val theme: String = "auto",
val colorScheme: String = Color.White.toString(), val colorScheme: String = Color.White.toString(),
val blackTheme: Boolean = false val blackTheme: Boolean = false,
val aodEnabled: Boolean = false
) )

View File

@@ -80,12 +80,15 @@ class SettingsViewModel(
?: preferenceRepository.saveStringPreference("color_scheme", Color.White.toString()) ?: preferenceRepository.saveStringPreference("color_scheme", Color.White.toString())
val blackTheme = preferenceRepository.getBooleanPreference("black_theme") val blackTheme = preferenceRepository.getBooleanPreference("black_theme")
?: preferenceRepository.saveBooleanPreference("black_theme", false) ?: preferenceRepository.saveBooleanPreference("black_theme", false)
val aodEnabled = preferenceRepository.getBooleanPreference("aod_enabled")
?: preferenceRepository.saveBooleanPreference("aod_enabled", false)
_preferencesState.update { currentState -> _preferencesState.update { currentState ->
currentState.copy( currentState.copy(
theme = theme, theme = theme,
colorScheme = colorScheme, colorScheme = colorScheme,
blackTheme = blackTheme blackTheme = blackTheme,
aodEnabled = aodEnabled
) )
} }
} }
@@ -196,6 +199,15 @@ class SettingsViewModel(
} }
} }
fun saveAodEnabled(aodEnabled: Boolean) {
viewModelScope.launch {
_preferencesState.update { currentState ->
currentState.copy(aodEnabled = aodEnabled)
}
preferenceRepository.saveBooleanPreference("aod_enabled", aodEnabled)
}
}
companion object { companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory { val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer { initializer {

View File

@@ -95,6 +95,9 @@ class TimerViewModel(
) )
).toUri() ).toUri()
preferenceRepository.getBooleanPreference("aod_enabled")
?: preferenceRepository.saveBooleanPreference("aod_enabled", false)
_time.update { timerRepository.focusTime } _time.update { timerRepository.focusTime }
cycles = 0 cycles = 0
startTime = 0L startTime = 0L

View File

@@ -0,0 +1,16 @@
<!--
~ Copyright (c) 2025 Nishant Mishra
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#e3e3e3"
android:pathData="M360,480h240q17,0 28.5,-11.5T640,440q0,-17 -11.5,-28.5T600,400L360,400q-17,0 -28.5,11.5T320,440q0,17 11.5,28.5T360,480ZM400,620h160q17,0 28.5,-11.5T600,580q0,-17 -11.5,-28.5T560,540L400,540q-17,0 -28.5,11.5T360,580q0,17 11.5,28.5T400,620ZM280,920q-33,0 -56.5,-23.5T200,840v-720q0,-33 23.5,-56.5T280,40h400q33,0 56.5,23.5T760,120v124q18,7 29,22t11,34v80q0,19 -11,34t-29,22v404q0,33 -23.5,56.5T680,920L280,920Z" />
</vector>

View File

@@ -1,60 +1,62 @@
<resources> <resources>
<string name="app_name">Tomato</string>
<string name="start">Start</string>
<string name="stop">Stop</string>
<string name="focus">Focus</string>
<string name="short_break">Short break</string>
<string name="long_break">Long break</string>
<string name="exit">Exit</string>
<string name="skip">Skip</string>
<string name="stop_alarm">Stop alarm</string>
<string name="min_remaining_notification">%1$s min remaining</string>
<string name="paused">Paused</string>
<string name="completed">Completed</string>
<string name="up_next_notification">Up next: %1$s (%2$s)</string>
<string name="start_next">Start next</string>
<string name="choose_color_scheme">Choose color scheme</string>
<string name="ok">OK</string>
<string name="color_scheme">Color scheme</string>
<string name="dynamic">Dynamic</string>
<string name="color">Color</string>
<string name="system_default">System default</string>
<string name="alarm">Alarm</string> <string name="alarm">Alarm</string>
<string name="light">Light</string> <string name="alarm_desc">Ring alarm when a timer completes</string>
<string name="dark">Dark</string>
<string name="choose_theme">Choose theme</string>
<string name="productivity_analysis">Productivity analysis</string>
<string name="productivity_analysis_desc">Focus durations at different times of the day</string>
<string name="alarm_sound">Alarm sound</string> <string name="alarm_sound">Alarm sound</string>
<string name="always_on_display">Always On Display</string>
<string name="always_on_display_desc">Tap anywhere when viewing the timer to switch to AOD mode</string>
<string name="app_name">Tomato</string>
<string name="black_theme">Black theme</string> <string name="black_theme">Black theme</string>
<string name="black_theme_desc">Use a pure black dark theme</string> <string name="black_theme_desc">Use a pure black dark theme</string>
<string name="alarm_desc">Ring alarm when a timer completes</string> <string name="break_">Break</string>
<string name="vibrate">Vibrate</string> <string name="choose_color_scheme">Choose color scheme</string>
<string name="vibrate_desc">Vibrate when a timer completes</string> <string name="choose_theme">Choose theme</string>
<string name="theme">Theme</string> <string name="color">Color</string>
<string name="settings">Settings</string> <string name="color_scheme">Color scheme</string>
<string name="completed">Completed</string>
<string name="dark">Dark</string>
<string name="dynamic">Dynamic</string>
<string name="exit">Exit</string>
<string name="focus">Focus</string>
<string name="focus_per_day_avg">focus per day (avg)</string>
<string name="last_month">Last month</string>
<string name="last_week">Last week</string>
<string name="last_year">Last year</string>
<string name="light">Light</string>
<string name="long_break">Long break</string>
<string name="min_remaining_notification">%1$s min remaining</string>
<string name="monthly_productivity_analysis">Monthly productivity analysis</string>
<string name="more">More</string>
<string name="more_info">More info</string>
<string name="ok">OK</string>
<string name="pause">Pause</string>
<string name="paused">Paused</string>
<string name="play">Play</string>
<string name="pomodoro_info">A \"session\" is a sequence of pomodoro intervals that contain focus intervals, short break intervals, and a long break interval. The last break of a session is always a long break.</string>
<string name="productivity_analysis">Productivity analysis</string>
<string name="productivity_analysis_desc">Focus durations at different times of the day</string>
<string name="restart">Restart</string>
<string name="session_length">Session length</string> <string name="session_length">Session length</string>
<string name="session_length_desc">Focus intervals in one session: %1$d</string> <string name="session_length_desc">Focus intervals in one session: %1$d</string>
<string name="pomodoro_info">A \"session\" is a sequence of pomodoro intervals that contain focus intervals, short break intervals, and a long break interval. The last break of a session is always a long break.</string> <string name="settings">Settings</string>
<string name="stats">Stats</string> <string name="short_break">Short break</string>
<string name="today">Today</string> <string name="skip">Skip</string>
<string name="break_">Break</string>
<string name="last_week">Last week</string>
<string name="focus_per_day_avg">focus per day (avg)</string>
<string name="more_info">More info</string>
<string name="weekly_productivity_analysis">Weekly productivity analysis</string>
<string name="last_month">Last month</string>
<string name="monthly_productivity_analysis">Monthly productivity analysis</string>
<string name="stop_alarm_question">Stop Alarm?</string>
<string name="stop_alarm_dialog_text">Current timer session is complete. Tap anywhere to stop the alarm.</string>
<string name="timer_session_count">%1$d of %2$d</string>
<string name="more">More</string>
<string name="pause">Pause</string>
<string name="play">Play</string>
<string name="restart">Restart</string>
<string name="skip_to_next">Skip to next</string> <string name="skip_to_next">Skip to next</string>
<string name="up_next">Up next</string> <string name="start">Start</string>
<string name="start_next">Start next</string>
<string name="stats">Stats</string>
<string name="stop">Stop</string>
<string name="stop_alarm">Stop alarm</string>
<string name="stop_alarm_dialog_text">Current timer session is complete. Tap anywhere to stop the alarm.</string>
<string name="stop_alarm_question">Stop Alarm?</string>
<string name="system_default">System default</string>
<string name="theme">Theme</string>
<string name="timer">Timer</string> <string name="timer">Timer</string>
<string name="timer_progress">Timer progress</string> <string name="timer_progress">Timer progress</string>
<string name="last_year">Last year</string> <string name="timer_session_count">%1$d of %2$d</string>
<string name="today">Today</string>
<string name="up_next">Up next</string>
<string name="up_next_notification">Up next: %1$s (%2$s)</string>
<string name="vibrate">Vibrate</string>
<string name="vibrate_desc">Vibrate when a timer completes</string>
<string name="weekly_productivity_analysis">Weekly productivity analysis</string>
</resources> </resources>