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
}
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 y by animateIntAsState(randomY)
val x by animateIntAsState(randomX, motionScheme.slowSpatialSpec())
val y by animateIntAsState(randomY, motionScheme.slowSpatialSpec())
Box(
modifier = modifier

View File

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

View File

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

View File

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

View File

@@ -14,5 +14,6 @@ import androidx.compose.ui.graphics.Color
data class PreferencesState(
val theme: String = "auto",
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())
val blackTheme = preferenceRepository.getBooleanPreference("black_theme")
?: preferenceRepository.saveBooleanPreference("black_theme", false)
val aodEnabled = preferenceRepository.getBooleanPreference("aod_enabled")
?: preferenceRepository.saveBooleanPreference("aod_enabled", false)
_preferencesState.update { currentState ->
currentState.copy(
theme = theme,
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 {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {

View File

@@ -95,6 +95,9 @@ class TimerViewModel(
)
).toUri()
preferenceRepository.getBooleanPreference("aod_enabled")
?: preferenceRepository.saveBooleanPreference("aod_enabled", false)
_time.update { timerRepository.focusTime }
cycles = 0
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>
<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="light">Light</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_desc">Ring alarm when a timer completes</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_desc">Use a pure black dark theme</string>
<string name="alarm_desc">Ring alarm when a timer completes</string>
<string name="vibrate">Vibrate</string>
<string name="vibrate_desc">Vibrate when a timer completes</string>
<string name="theme">Theme</string>
<string name="settings">Settings</string>
<string name="break_">Break</string>
<string name="choose_color_scheme">Choose color scheme</string>
<string name="choose_theme">Choose theme</string>
<string name="color">Color</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_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="stats">Stats</string>
<string name="today">Today</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="settings">Settings</string>
<string name="short_break">Short break</string>
<string name="skip">Skip</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_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>