From 5d0e648c0774cd77d54f4b5d59707bd82775f8f6 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Tue, 4 Nov 2025 19:56:40 +0530 Subject: [PATCH 1/9] chore(deps): update dependencies --- app/build.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7fe184d..6f39b02 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -61,6 +61,9 @@ android { getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } + debug { + applicationIdSuffix = ".debug" + } } flavorDimensions += "version" From 41e8618d1450f273aad363df68ac0fb3dd160aed Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Tue, 4 Nov 2025 20:01:12 +0530 Subject: [PATCH 2/9] fix(notification): make notification visible on lockscreen --- app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt b/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt index bf445c8..3df1ce1 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/AppContainer.kt @@ -23,6 +23,7 @@ import android.content.Context import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationCompat.VISIBILITY_PUBLIC import androidx.core.app.NotificationManagerCompat import kotlinx.coroutines.flow.MutableStateFlow import org.nsh07.pomodoro.R @@ -83,6 +84,7 @@ class DefaultAppContainer(context: Context) : AppContainer { .setSilent(true) .setOngoing(true) .setRequestPromotedOngoing(true) + .setVisibility(VISIBILITY_PUBLIC) } override val timerState: MutableStateFlow by lazy { From ee5bf647c6ddccffafe9f1ba1cbbab0a3cf7968f Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Wed, 5 Nov 2025 08:51:58 +0530 Subject: [PATCH 3/9] fix(timer): significantly reduce recomposition count (by ~90%) This improves the performance and perceived smoothness of the timer screen by a lot --- app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt | 5 +---- .../pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt | 9 +++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt index 16a539b..e581922 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt @@ -45,7 +45,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -79,9 +78,7 @@ fun AppScreen( val context = LocalContext.current val uiState by timerViewModel.timerState.collectAsStateWithLifecycle() - val remainingTime by timerViewModel.time.collectAsStateWithLifecycle() - - val progress by rememberUpdatedState((uiState.totalTime.toFloat() - remainingTime) / uiState.totalTime) + val progress by timerViewModel.progress.collectAsStateWithLifecycle() val layoutDirection = LocalLayoutDirection.current val motionScheme = motionScheme diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt index 2c4e493..64fce02 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt @@ -30,8 +30,11 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.nsh07.pomodoro.TomatoApplication @@ -55,6 +58,12 @@ class TimerViewModel( val timerState: StateFlow = _timerState.asStateFlow() val time: StateFlow = _time.asStateFlow() + + val progress = _time.map { + val uiState = timerState.value + (uiState.totalTime.toFloat() - it) / uiState.totalTime + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 0f) + private var cycles = 0 private var startTime = 0L From d441289e72831cdd5c4c814edc29a7442ef61113 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Wed, 5 Nov 2025 12:52:18 +0530 Subject: [PATCH 4/9] fix(timer): combine flows to fix a bug with the progress indicator --- .../pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt index 64fce02..9052bcb 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt @@ -33,7 +33,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -59,9 +59,8 @@ class TimerViewModel( val time: StateFlow = _time.asStateFlow() - val progress = _time.map { - val uiState = timerState.value - (uiState.totalTime.toFloat() - it) / uiState.totalTime + val progress = _time.combine(_timerState) { remainingTime, uiState -> + (uiState.totalTime.toFloat() - remainingTime) / uiState.totalTime }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 0f) private var cycles = 0 From 42582545bfd88d990b6099f4ec0d98af316a53ee Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Fri, 7 Nov 2025 17:50:07 +0530 Subject: [PATCH 5/9] refactor(settings): use MVI intents instead of lambdas #117 --- .../ui/settingsScreen/SettingsScreen.kt | 42 +++---------------- .../settingsScreen/screens/AlarmSettings.kt | 17 ++++---- .../screens/AppearanceSettings.kt | 16 +++---- .../settingsScreen/screens/TimerSettings.kt | 20 ++++----- .../viewModel/SettingsAction.kt | 32 ++++++++++++++ .../viewModel/SettingsViewModel.kt | 29 +++++++++---- 6 files changed, 83 insertions(+), 73 deletions(-) create mode 100644 app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt index c8ae71f..0f48913 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt @@ -19,8 +19,6 @@ package org.nsh07.pomodoro.ui.settingsScreen import android.annotation.SuppressLint import android.app.LocaleManager -import android.content.Intent -import android.net.Uri import android.os.Build import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -55,7 +53,6 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource @@ -68,7 +65,6 @@ import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation3.runtime.entryProvider import androidx.navigation3.ui.NavDisplay import org.nsh07.pomodoro.R -import org.nsh07.pomodoro.service.TimerService import org.nsh07.pomodoro.ui.Screen import org.nsh07.pomodoro.ui.settingsScreen.components.AboutCard import org.nsh07.pomodoro.ui.settingsScreen.components.ClickableListItem @@ -78,6 +74,7 @@ import org.nsh07.pomodoro.ui.settingsScreen.screens.AlarmSettings import org.nsh07.pomodoro.ui.settingsScreen.screens.AppearanceSettings import org.nsh07.pomodoro.ui.settingsScreen.screens.TimerSettings import org.nsh07.pomodoro.ui.settingsScreen.viewModel.PreferencesState +import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsAction import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsViewModel import org.nsh07.pomodoro.ui.settingsScreens import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar @@ -92,8 +89,6 @@ fun SettingsScreenRoot( modifier: Modifier = Modifier, viewModel: SettingsViewModel = viewModel(factory = SettingsViewModel.Factory) ) { - val context = LocalContext.current - val backStack = viewModel.backStack DisposableEffect(Unit) { @@ -134,20 +129,7 @@ fun SettingsScreenRoot( vibrateEnabled = vibrateEnabled, dndEnabled = dndEnabled, alarmSound = alarmSound, - onAlarmEnabledChange = viewModel::saveAlarmEnabled, - onVibrateEnabledChange = viewModel::saveVibrateEnabled, - onBlackThemeChange = viewModel::saveBlackTheme, - onAodEnabledChange = viewModel::saveAodEnabled, - onDndEnabledChange = viewModel::saveDndEnabled, - onAlarmSoundChanged = { - viewModel.saveAlarmSound(it) - Intent(context, TimerService::class.java).apply { - action = TimerService.Actions.RESET.toString() - context.startService(this) - } - }, - onThemeChange = viewModel::saveTheme, - onColorSchemeChange = viewModel::saveColorScheme, + onAction = viewModel::onAction, setShowPaywall = setShowPaywall, modifier = modifier ) @@ -168,14 +150,7 @@ private fun SettingsScreen( vibrateEnabled: Boolean, dndEnabled: Boolean, alarmSound: String, - onAlarmEnabledChange: (Boolean) -> Unit, - onVibrateEnabledChange: (Boolean) -> Unit, - onBlackThemeChange: (Boolean) -> Unit, - onAodEnabledChange: (Boolean) -> Unit, - onDndEnabledChange: (Boolean) -> Unit, - onAlarmSoundChanged: (Uri?) -> Unit, - onThemeChange: (String) -> Unit, - onColorSchemeChange: (Color) -> Unit, + onAction: (SettingsAction) -> Unit, setShowPaywall: (Boolean) -> Unit, modifier: Modifier = Modifier ) { @@ -316,9 +291,7 @@ private fun SettingsScreen( alarmEnabled = alarmEnabled, vibrateEnabled = vibrateEnabled, alarmSound = alarmSound, - onAlarmEnabledChange = onAlarmEnabledChange, - onVibrateEnabledChange = onVibrateEnabledChange, - onAlarmSoundChanged = onAlarmSoundChanged, + onAction = onAction, onBack = backStack::removeLastOrNull ) } @@ -326,9 +299,7 @@ private fun SettingsScreen( AppearanceSettings( preferencesState = preferencesState, isPlus = isPlus, - onBlackThemeChange = onBlackThemeChange, - onThemeChange = onThemeChange, - onColorSchemeChange = onColorSchemeChange, + onAction = onAction, setShowPaywall = setShowPaywall, onBack = backStack::removeLastOrNull ) @@ -342,8 +313,7 @@ private fun SettingsScreen( shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, longBreakTimeInputFieldState = longBreakTimeInputFieldState, sessionsSliderState = sessionsSliderState, - onAodEnabledChange = onAodEnabledChange, - onDndEnabledChange = onDndEnabledChange, + onAction = onAction, setShowPaywall = setShowPaywall, onBack = backStack::removeLastOrNull, ) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt index bc46e07..b8ebdb4 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt @@ -17,6 +17,7 @@ package org.nsh07.pomodoro.ui.settingsScreen.screens +import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.media.RingtoneManager @@ -65,6 +66,7 @@ import kotlinx.coroutines.withContext import org.nsh07.pomodoro.R import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem import org.nsh07.pomodoro.ui.settingsScreen.viewModel.PreferencesState +import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsAction import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors @@ -80,9 +82,7 @@ fun AlarmSettings( alarmEnabled: Boolean, vibrateEnabled: Boolean, alarmSound: String, - onAlarmEnabledChange: (Boolean) -> Unit, - onVibrateEnabledChange: (Boolean) -> Unit, - onAlarmSoundChanged: (Uri?) -> Unit, + onAction: (SettingsAction) -> Unit, onBack: () -> Unit, modifier: Modifier = Modifier ) { @@ -112,10 +112,11 @@ fun AlarmSettings( @Suppress("DEPRECATION") result.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) } - onAlarmSoundChanged(uri) + onAction(SettingsAction.SaveAlarmSound(uri)) } } + @SuppressLint("LocalContextGetResourceValueCall") val ringtonePickerIntent = remember(alarmSound) { Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply { putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM) @@ -136,14 +137,14 @@ fun AlarmSettings( icon = R.drawable.alarm_on, label = R.string.sound, description = R.string.alarm_desc, - onClick = onAlarmEnabledChange + onClick = { onAction(SettingsAction.SaveAlarmEnabled(it)) } ), SettingsSwitchItem( checked = vibrateEnabled, icon = R.drawable.mobile_vibrate, label = R.string.vibrate, description = R.string.vibrate_desc, - onClick = onVibrateEnabledChange + onClick = { onAction(SettingsAction.SaveVibrateEnabled(it)) } ) ) } @@ -247,8 +248,6 @@ fun AlarmSettingsPreview() { alarmEnabled = true, vibrateEnabled = false, alarmSound = "", - onAlarmEnabledChange = {}, - onVibrateEnabledChange = {}, - onAlarmSoundChanged = {}, + onAction = {}, onBack = {}) } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AppearanceSettings.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AppearanceSettings.kt index d5a12ff..755e02d 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AppearanceSettings.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AppearanceSettings.kt @@ -39,7 +39,6 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -51,6 +50,7 @@ import org.nsh07.pomodoro.ui.settingsScreen.components.ColorSchemePickerListItem import org.nsh07.pomodoro.ui.settingsScreen.components.PlusDivider import org.nsh07.pomodoro.ui.settingsScreen.components.ThemePickerListItem import org.nsh07.pomodoro.ui.settingsScreen.viewModel.PreferencesState +import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsAction import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors @@ -64,9 +64,7 @@ import org.nsh07.pomodoro.utils.toColor fun AppearanceSettings( preferencesState: PreferencesState, isPlus: Boolean, - onBlackThemeChange: (Boolean) -> Unit, - onThemeChange: (String) -> Unit, - onColorSchemeChange: (Color) -> Unit, + onAction: (SettingsAction) -> Unit, setShowPaywall: (Boolean) -> Unit, onBack: () -> Unit, modifier: Modifier = Modifier @@ -106,7 +104,7 @@ fun AppearanceSettings( item { ThemePickerListItem( theme = preferencesState.theme, - onThemeChange = onThemeChange, + onThemeChange = { onAction(SettingsAction.SaveTheme(it)) }, items = if (isPlus) 3 else 1, index = 0 ) @@ -122,7 +120,7 @@ fun AppearanceSettings( items = 3, index = if (isPlus) 1 else 0, isPlus = isPlus, - onColorChange = onColorSchemeChange, + onColorChange = { onAction(SettingsAction.SaveColorScheme(it)) }, ) } item { @@ -131,7 +129,7 @@ fun AppearanceSettings( icon = R.drawable.contrast, label = R.string.black_theme, description = R.string.black_theme_desc, - onClick = onBlackThemeChange + onClick = { onAction(SettingsAction.SaveBlackTheme(it)) } ) ListItem( leadingContent = { @@ -180,9 +178,7 @@ fun AppearanceSettingsPreview() { AppearanceSettings( preferencesState = preferencesState, isPlus = false, - onBlackThemeChange = {}, - onThemeChange = {}, - onColorSchemeChange = {}, + onAction = {}, setShowPaywall = {}, onBack = {} ) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt index b0c7ea3..74504f0 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt @@ -36,6 +36,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape @@ -76,6 +77,7 @@ import org.nsh07.pomodoro.R import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem import org.nsh07.pomodoro.ui.settingsScreen.components.MinuteInputField import org.nsh07.pomodoro.ui.settingsScreen.components.PlusDivider +import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsAction import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors @@ -96,11 +98,10 @@ fun TimerSettings( shortBreakTimeInputFieldState: TextFieldState, longBreakTimeInputFieldState: TextFieldState, sessionsSliderState: SliderState, - onAodEnabledChange: (Boolean) -> Unit, - onDndEnabledChange: (Boolean) -> Unit, + setShowPaywall: (Boolean) -> Unit, + onAction: (SettingsAction) -> Unit, onBack: () -> Unit, - modifier: Modifier = Modifier, - setShowPaywall: (Boolean) -> Unit + modifier: Modifier = Modifier ) { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val context = LocalContext.current @@ -110,7 +111,7 @@ fun TimerSettings( LaunchedEffect(Unit) { if (!notificationManagerService.isNotificationPolicyAccessGranted()) - onDndEnabledChange(false) + onAction(SettingsAction.SaveDndEnabled(false)) } val switchItems = listOf( @@ -128,7 +129,7 @@ fun TimerSettings( } else if (!it && notificationManagerService.isNotificationPolicyAccessGranted()) { notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL) } - onDndEnabledChange(it) + onAction(SettingsAction.SaveDndEnabled(it)) } ), SettingsSwitchItem( @@ -136,7 +137,7 @@ fun TimerSettings( icon = R.drawable.aod, label = R.string.always_on_display, description = R.string.always_on_display_desc, - onClick = onAodEnabledChange + onClick = { onAction(SettingsAction.SaveAodEnabled(it)) } ) ) @@ -313,7 +314,7 @@ fun TimerSettings( item { PlusDivider(setShowPaywall) } - itemsIndexed(switchItems.drop(1)) { index, item -> + items(switchItems.drop(1)) { item -> ListItem( leadingContent = { Icon( @@ -408,8 +409,7 @@ private fun TimerSettingsPreview() { shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, longBreakTimeInputFieldState = longBreakTimeInputFieldState, sessionsSliderState = sessionsSliderState, - onAodEnabledChange = {}, - onDndEnabledChange = {}, + onAction = {}, setShowPaywall = {}, onBack = {} ) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt new file mode 100644 index 0000000..ae5541a --- /dev/null +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsAction.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * This file is part of Tomato - a minimalist pomodoro timer for Android. + * + * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tomato. + * If not, see . + */ + +package org.nsh07.pomodoro.ui.settingsScreen.viewModel + +import android.net.Uri +import androidx.compose.ui.graphics.Color + +sealed interface SettingsAction { + data class SaveAlarmEnabled(val enabled: Boolean) : SettingsAction + data class SaveVibrateEnabled(val enabled: Boolean) : SettingsAction + data class SaveBlackTheme(val enabled: Boolean) : SettingsAction + data class SaveAodEnabled(val enabled: Boolean) : SettingsAction + data class SaveDndEnabled(val enabled: Boolean) : SettingsAction + data class SaveAlarmSound(val uri: Uri?) : SettingsAction + data class SaveTheme(val theme: String) : SettingsAction + data class SaveColorScheme(val color: Color) : SettingsAction +} diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt index 94ba2c6..07aa448 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt @@ -103,6 +103,19 @@ class SettingsViewModel( } } + fun onAction(action: SettingsAction) { + when (action) { + is SettingsAction.SaveAlarmSound -> saveAlarmSound(action.uri) + is SettingsAction.SaveAlarmEnabled -> saveAlarmEnabled(action.enabled) + is SettingsAction.SaveVibrateEnabled -> saveVibrateEnabled(action.enabled) + is SettingsAction.SaveDndEnabled -> saveDndEnabled(action.enabled) + is SettingsAction.SaveColorScheme -> saveColorScheme(action.color) + is SettingsAction.SaveTheme -> saveTheme(action.theme) + is SettingsAction.SaveBlackTheme -> saveBlackTheme(action.enabled) + is SettingsAction.SaveAodEnabled -> saveAodEnabled(action.enabled) + } + } + private fun updateSessionLength() { viewModelScope.launch { timerRepository.sessionLength = preferenceRepository.saveIntPreference( @@ -160,35 +173,35 @@ class SettingsViewModel( longBreakFlowCollectionJob?.cancel() } - fun saveAlarmEnabled(enabled: Boolean) { + private fun saveAlarmEnabled(enabled: Boolean) { viewModelScope.launch { timerRepository.alarmEnabled = enabled preferenceRepository.saveBooleanPreference("alarm_enabled", enabled) } } - fun saveVibrateEnabled(enabled: Boolean) { + private fun saveVibrateEnabled(enabled: Boolean) { viewModelScope.launch { timerRepository.vibrateEnabled = enabled preferenceRepository.saveBooleanPreference("vibrate_enabled", enabled) } } - fun saveDndEnabled(enabled: Boolean) { + private fun saveDndEnabled(enabled: Boolean) { viewModelScope.launch { timerRepository.dndEnabled = enabled preferenceRepository.saveBooleanPreference("dnd_enabled", enabled) } } - fun saveAlarmSound(uri: Uri?) { + private fun saveAlarmSound(uri: Uri?) { viewModelScope.launch { timerRepository.alarmSoundUri = uri preferenceRepository.saveStringPreference("alarm_sound", uri.toString()) } } - fun saveColorScheme(colorScheme: Color) { + private fun saveColorScheme(colorScheme: Color) { viewModelScope.launch { _preferencesState.update { currentState -> currentState.copy(colorScheme = colorScheme.toString()) @@ -197,7 +210,7 @@ class SettingsViewModel( } } - fun saveTheme(theme: String) { + private fun saveTheme(theme: String) { viewModelScope.launch { _preferencesState.update { currentState -> currentState.copy(theme = theme) @@ -206,7 +219,7 @@ class SettingsViewModel( } } - fun saveBlackTheme(blackTheme: Boolean) { + private fun saveBlackTheme(blackTheme: Boolean) { viewModelScope.launch { _preferencesState.update { currentState -> currentState.copy(blackTheme = blackTheme) @@ -215,7 +228,7 @@ class SettingsViewModel( } } - fun saveAodEnabled(aodEnabled: Boolean) { + private fun saveAodEnabled(aodEnabled: Boolean) { viewModelScope.launch { _preferencesState.update { currentState -> currentState.copy(aodEnabled = aodEnabled) From f4fa16b5df3df52a65d7b1985bcf2f7898dfa3eb Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Fri, 7 Nov 2025 22:49:44 +0530 Subject: [PATCH 6/9] feat(settings): remove BMC link in Play Store version --- .../settingsScreen/components/AboutButtons.kt | 87 +++++++++++++++++++ .../ui/settingsScreen/components/AboutCard.kt | 39 +-------- app/src/main/res/drawable/weblate.xml | 26 ++++++ app/src/main/res/values/strings.xml | 1 + .../settingsScreen/components/AboutButtons.kt | 86 ++++++++++++++++++ 5 files changed, 202 insertions(+), 37 deletions(-) create mode 100644 app/src/foss/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutButtons.kt create mode 100644 app/src/main/res/drawable/weblate.xml create mode 100644 app/src/play/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutButtons.kt diff --git a/app/src/foss/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutButtons.kt b/app/src/foss/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutButtons.kt new file mode 100644 index 0000000..3ce2377 --- /dev/null +++ b/app/src/foss/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutButtons.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * This file is part of Tomato - a minimalist pomodoro timer for Android. + * + * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tomato. + * If not, see . + */ + +package org.nsh07.pomodoro.ui.settingsScreen.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import org.nsh07.pomodoro.R + +@Composable +fun TopButton( + buttonColors: ButtonColors, + modifier: Modifier = Modifier +) { + val uriHandler = LocalUriHandler.current + Button( + colors = buttonColors, + onClick = { uriHandler.openUri("https://coff.ee/nsh07") }, + modifier = modifier + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painterResource(R.drawable.bmc), + contentDescription = null, + modifier = Modifier.height(24.dp) + ) + + Text(text = stringResource(R.string.bmc)) + } + } +} + +@Composable +fun BottomButton( + buttonColors: ButtonColors, + modifier: Modifier = Modifier +) { + val uriHandler = LocalUriHandler.current + Button( + colors = buttonColors, + onClick = { uriHandler.openUri("https://hosted.weblate.org/engage/tomato/") }, + modifier = modifier + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painterResource(R.drawable.weblate), + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + + Text(text = stringResource(R.string.help_with_translation)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutCard.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutCard.kt index 14028fe..3ce8e3a 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutCard.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutCard.kt @@ -24,10 +24,8 @@ import androidx.compose.foundation.layout.FlowRow 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.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults @@ -119,41 +117,8 @@ fun AboutCard( modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - Button( - colors = buttonColors, - onClick = { uriHandler.openUri("https://coff.ee/nsh07") } - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - painterResource(R.drawable.bmc), - contentDescription = null, - modifier = Modifier.height(24.dp) - ) - - Text(text = stringResource(R.string.bmc)) - } - } - - Button( - colors = buttonColors, - onClick = { uriHandler.openUri("https://play.google.com/store/apps/details?id=org.nsh07.pomodoro") } - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - painterResource(R.drawable.play_store), - contentDescription = null, - modifier = Modifier.size(20.dp) - ) - - Text(text = stringResource(R.string.rate_on_google_play)) - } - } + TopButton(buttonColors) + BottomButton(buttonColors) } } } \ No newline at end of file diff --git a/app/src/main/res/drawable/weblate.xml b/app/src/main/res/drawable/weblate.xml new file mode 100644 index 0000000..22126d0 --- /dev/null +++ b/app/src/main/res/drawable/weblate.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a608b21..078f5d7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -92,4 +92,5 @@ Rate on Google Play BuyMeACoffee Selected + Help with translation \ No newline at end of file diff --git a/app/src/play/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutButtons.kt b/app/src/play/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutButtons.kt new file mode 100644 index 0000000..aebde4a --- /dev/null +++ b/app/src/play/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutButtons.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * This file is part of Tomato - a minimalist pomodoro timer for Android. + * + * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tomato. + * If not, see . + */ + +package org.nsh07.pomodoro.ui.settingsScreen.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import org.nsh07.pomodoro.R + +@Composable +fun TopButton( + buttonColors: ButtonColors, + modifier: Modifier = Modifier +) { + val uriHandler = LocalUriHandler.current + Button( + colors = buttonColors, + onClick = { uriHandler.openUri("https://hosted.weblate.org/engage/tomato/") }, + modifier = modifier + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painterResource(R.drawable.weblate), + contentDescription = null, + modifier = Modifier.size(24.dp) + ) + + Text(text = stringResource(R.string.help_with_translation)) + } + } +} + +@Composable +fun BottomButton( + buttonColors: ButtonColors, + modifier: Modifier = Modifier +) { + val uriHandler = LocalUriHandler.current + Button( + colors = buttonColors, + onClick = { uriHandler.openUri("https://play.google.com/store/apps/details?id=org.nsh07.pomodoro") }, + modifier = modifier + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painterResource(R.drawable.play_store), + contentDescription = null, + modifier = Modifier.size(24.dp) + ) + + Text(text = stringResource(R.string.rate_on_google_play)) + } + } +} \ No newline at end of file From 538c984d40c553d025dcba06d4461cb4f35674d3 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Sun, 9 Nov 2025 09:17:55 +0530 Subject: [PATCH 7/9] refactor: rename "preferences" to "settings" to maintain consistency --- .../java/org/nsh07/pomodoro/MainActivity.kt | 10 +++---- .../ui/settingsScreen/SettingsScreen.kt | 14 ++++----- .../settingsScreen/screens/AlarmSettings.kt | 12 ++++---- .../screens/AppearanceSettings.kt | 14 ++++----- .../viewModel/PreferencesState.kt | 19 ------------ .../settingsScreen/viewModel/SettingsState.kt | 29 +++++++++++++++++++ .../viewModel/SettingsViewModel.kt | 16 +++++----- 7 files changed, 62 insertions(+), 52 deletions(-) delete mode 100644 app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/PreferencesState.kt create mode 100644 app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt diff --git a/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt index 1917159..a36bb6d 100644 --- a/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt +++ b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt @@ -50,15 +50,15 @@ class MainActivity : ComponentActivity() { } setContent { - val preferencesState by settingsViewModel.preferencesState.collectAsStateWithLifecycle() + val settingsState by settingsViewModel.settingsState.collectAsStateWithLifecycle() - val darkTheme = when (preferencesState.theme) { + val darkTheme = when (settingsState.theme) { "dark" -> true "light" -> false else -> isSystemInDarkTheme() } - val seed = preferencesState.colorScheme.toColor() + val seed = settingsState.colorScheme.toColor() val isPlus by settingsViewModel.isPlus.collectAsStateWithLifecycle() val isPurchaseStateLoaded by settingsViewModel.isPurchaseStateLoaded.collectAsStateWithLifecycle() @@ -77,7 +77,7 @@ class MainActivity : ComponentActivity() { TomatoTheme( darkTheme = darkTheme, seedColor = seed, - blackTheme = preferencesState.blackTheme + blackTheme = settingsState.blackTheme ) { val colorScheme = colorScheme LaunchedEffect(colorScheme) { @@ -86,7 +86,7 @@ class MainActivity : ComponentActivity() { AppScreen( isPlus = isPlus, - isAODEnabled = preferencesState.aodEnabled, + isAODEnabled = settingsState.aodEnabled, setTimerFrequency = { appContainer.appTimerRepository.timerFrequency = it } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt index 0f48913..2827eab 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt @@ -73,8 +73,8 @@ import org.nsh07.pomodoro.ui.settingsScreen.components.PlusPromo import org.nsh07.pomodoro.ui.settingsScreen.screens.AlarmSettings import org.nsh07.pomodoro.ui.settingsScreen.screens.AppearanceSettings import org.nsh07.pomodoro.ui.settingsScreen.screens.TimerSettings -import org.nsh07.pomodoro.ui.settingsScreen.viewModel.PreferencesState import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsAction +import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsState import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsViewModel import org.nsh07.pomodoro.ui.settingsScreens import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar @@ -106,7 +106,7 @@ fun SettingsScreenRoot( val dndEnabled by viewModel.dndEnabled.collectAsStateWithLifecycle(false) val alarmSound by viewModel.alarmSound.collectAsStateWithLifecycle(viewModel.currentAlarmSound) - val preferencesState by viewModel.preferencesState.collectAsStateWithLifecycle() + val settingsState by viewModel.settingsState.collectAsStateWithLifecycle() val sessionsSliderState = rememberSaveable( saver = SliderState.Saver( @@ -119,7 +119,7 @@ fun SettingsScreenRoot( SettingsScreen( isPlus = isPlus, - preferencesState = preferencesState, + settingsState = settingsState, backStack = backStack, focusTimeInputFieldState = focusTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, @@ -140,7 +140,7 @@ fun SettingsScreenRoot( @Composable private fun SettingsScreen( isPlus: Boolean, - preferencesState: PreferencesState, + settingsState: SettingsState, backStack: SnapshotStateList, focusTimeInputFieldState: TextFieldState, shortBreakTimeInputFieldState: TextFieldState, @@ -287,7 +287,7 @@ private fun SettingsScreen( entry { AlarmSettings( - preferencesState = preferencesState, + settingsState = settingsState, alarmEnabled = alarmEnabled, vibrateEnabled = vibrateEnabled, alarmSound = alarmSound, @@ -297,7 +297,7 @@ private fun SettingsScreen( } entry { AppearanceSettings( - preferencesState = preferencesState, + settingsState = settingsState, isPlus = isPlus, onAction = onAction, setShowPaywall = setShowPaywall, @@ -307,7 +307,7 @@ private fun SettingsScreen( entry { TimerSettings( isPlus = isPlus, - aodEnabled = preferencesState.aodEnabled, + aodEnabled = settingsState.aodEnabled, dndEnabled = dndEnabled, focusTimeInputFieldState = focusTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt index b8ebdb4..186fba8 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt @@ -65,8 +65,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.nsh07.pomodoro.R import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem -import org.nsh07.pomodoro.ui.settingsScreen.viewModel.PreferencesState import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsAction +import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsState import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors @@ -78,7 +78,7 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun AlarmSettings( - preferencesState: PreferencesState, + settingsState: SettingsState, alarmEnabled: Boolean, vibrateEnabled: Boolean, alarmSound: String, @@ -126,8 +126,8 @@ fun AlarmSettings( } val switchItems = remember( - preferencesState.blackTheme, - preferencesState.aodEnabled, + settingsState.blackTheme, + settingsState.aodEnabled, alarmEnabled, vibrateEnabled ) { @@ -242,9 +242,9 @@ fun AlarmSettings( @Preview @Composable fun AlarmSettingsPreview() { - val preferencesState = PreferencesState() + val settingsState = SettingsState() AlarmSettings( - preferencesState = preferencesState, + settingsState = settingsState, alarmEnabled = true, vibrateEnabled = false, alarmSound = "", diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AppearanceSettings.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AppearanceSettings.kt index 755e02d..9b838bf 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AppearanceSettings.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AppearanceSettings.kt @@ -49,8 +49,8 @@ import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem import org.nsh07.pomodoro.ui.settingsScreen.components.ColorSchemePickerListItem import org.nsh07.pomodoro.ui.settingsScreen.components.PlusDivider import org.nsh07.pomodoro.ui.settingsScreen.components.ThemePickerListItem -import org.nsh07.pomodoro.ui.settingsScreen.viewModel.PreferencesState import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsAction +import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsState import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors @@ -62,7 +62,7 @@ import org.nsh07.pomodoro.utils.toColor @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun AppearanceSettings( - preferencesState: PreferencesState, + settingsState: SettingsState, isPlus: Boolean, onAction: (SettingsAction) -> Unit, setShowPaywall: (Boolean) -> Unit, @@ -103,7 +103,7 @@ fun AppearanceSettings( } item { ThemePickerListItem( - theme = preferencesState.theme, + theme = settingsState.theme, onThemeChange = { onAction(SettingsAction.SaveTheme(it)) }, items = if (isPlus) 3 else 1, index = 0 @@ -116,7 +116,7 @@ fun AppearanceSettings( item { ColorSchemePickerListItem( - color = preferencesState.colorScheme.toColor(), + color = settingsState.colorScheme.toColor(), items = 3, index = if (isPlus) 1 else 0, isPlus = isPlus, @@ -125,7 +125,7 @@ fun AppearanceSettings( } item { val item = SettingsSwitchItem( - checked = preferencesState.blackTheme, + checked = settingsState.blackTheme, icon = R.drawable.contrast, label = R.string.black_theme, description = R.string.black_theme_desc, @@ -173,10 +173,10 @@ fun AppearanceSettings( @Preview @Composable fun AppearanceSettingsPreview() { - val preferencesState = PreferencesState() + val settingsState = SettingsState() TomatoTheme(dynamicColor = false) { AppearanceSettings( - preferencesState = preferencesState, + settingsState = settingsState, isPlus = false, onAction = {}, setShowPaywall = {}, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/PreferencesState.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/PreferencesState.kt deleted file mode 100644 index 7231852..0000000 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/PreferencesState.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2025 Nishant Mishra - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.nsh07.pomodoro.ui.settingsScreen.viewModel - -import androidx.compose.runtime.Immutable -import androidx.compose.ui.graphics.Color - -@Immutable -data class PreferencesState( - val theme: String = "auto", - val colorScheme: String = Color.White.toString(), - val blackTheme: Boolean = false, - val aodEnabled: Boolean = false -) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt new file mode 100644 index 0000000..bb43365 --- /dev/null +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 Nishant Mishra + * + * This file is part of Tomato - a minimalist pomodoro timer for Android. + * + * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tomato. + * If not, see . + */ + +package org.nsh07.pomodoro.ui.settingsScreen.viewModel + +import androidx.compose.runtime.Immutable +import androidx.compose.ui.graphics.Color + +@Immutable +data class SettingsState( + val theme: String = "auto", + val colorScheme: String = Color.White.toString(), + val blackTheme: Boolean = false, + val aodEnabled: Boolean = false +) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt index 07aa448..c86bf3f 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt @@ -59,8 +59,8 @@ class SettingsViewModel( private val _isSettingsLoaded = MutableStateFlow(false) val isSettingsLoaded = _isSettingsLoaded.asStateFlow() - private val _preferencesState = MutableStateFlow(PreferencesState()) - val preferencesState = _preferencesState.asStateFlow() + private val _settingsState = MutableStateFlow(SettingsState()) + val settingsState = _settingsState.asStateFlow() val focusTimeTextFieldState by lazy { TextFieldState((timerRepository.focusTime / 60000).toString()) @@ -203,7 +203,7 @@ class SettingsViewModel( private fun saveColorScheme(colorScheme: Color) { viewModelScope.launch { - _preferencesState.update { currentState -> + _settingsState.update { currentState -> currentState.copy(colorScheme = colorScheme.toString()) } preferenceRepository.saveStringPreference("color_scheme", colorScheme.toString()) @@ -212,7 +212,7 @@ class SettingsViewModel( private fun saveTheme(theme: String) { viewModelScope.launch { - _preferencesState.update { currentState -> + _settingsState.update { currentState -> currentState.copy(theme = theme) } preferenceRepository.saveStringPreference("theme", theme) @@ -221,7 +221,7 @@ class SettingsViewModel( private fun saveBlackTheme(blackTheme: Boolean) { viewModelScope.launch { - _preferencesState.update { currentState -> + _settingsState.update { currentState -> currentState.copy(blackTheme = blackTheme) } preferenceRepository.saveBooleanPreference("black_theme", blackTheme) @@ -230,7 +230,7 @@ class SettingsViewModel( private fun saveAodEnabled(aodEnabled: Boolean) { viewModelScope.launch { - _preferencesState.update { currentState -> + _settingsState.update { currentState -> currentState.copy(aodEnabled = aodEnabled) } preferenceRepository.saveBooleanPreference("aod_enabled", aodEnabled) @@ -238,7 +238,7 @@ class SettingsViewModel( } fun resetPaywalledSettings() { - _preferencesState.update { currentState -> + _settingsState.update { currentState -> currentState.copy( aodEnabled = false, blackTheme = false, @@ -257,7 +257,7 @@ class SettingsViewModel( val aodEnabled = preferenceRepository.getBooleanPreference("aod_enabled") ?: preferenceRepository.saveBooleanPreference("aod_enabled", false) - _preferencesState.update { currentState -> + _settingsState.update { currentState -> currentState.copy( theme = theme, colorScheme = colorScheme, From e1fa6c28b9cd76b6e294b0ef3e97e318e208363f Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Sun, 9 Nov 2025 11:47:18 +0530 Subject: [PATCH 8/9] refactor(settings): move state variables to single state class #117 --- .../pomodoro/billing/FossBillingManager.kt | 1 - .../java/org/nsh07/pomodoro/MainActivity.kt | 12 ---- .../nsh07/pomodoro/billing/BillingManager.kt | 1 - .../ui/settingsScreen/SettingsScreen.kt | 18 +----- .../settingsScreen/screens/AlarmSettings.kt | 26 ++++----- .../settingsScreen/screens/TimerSettings.kt | 23 ++++---- .../settingsScreen/viewModel/SettingsState.kt | 6 +- .../viewModel/SettingsViewModel.kt | 58 ++++++++++--------- .../pomodoro/billing/PlayBillingManager.kt | 5 -- 9 files changed, 59 insertions(+), 91 deletions(-) diff --git a/app/src/foss/java/org/nsh07/pomodoro/billing/FossBillingManager.kt b/app/src/foss/java/org/nsh07/pomodoro/billing/FossBillingManager.kt index 529b58c..4a155a3 100644 --- a/app/src/foss/java/org/nsh07/pomodoro/billing/FossBillingManager.kt +++ b/app/src/foss/java/org/nsh07/pomodoro/billing/FossBillingManager.kt @@ -25,7 +25,6 @@ import kotlinx.coroutines.flow.asStateFlow */ class FossBillingManager : BillingManager { override val isPlus = MutableStateFlow(true).asStateFlow() - override val isLoaded = MutableStateFlow(true).asStateFlow() } object BillingManagerProvider { diff --git a/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt index a36bb6d..95e9502 100644 --- a/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt +++ b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt @@ -61,18 +61,6 @@ class MainActivity : ComponentActivity() { val seed = settingsState.colorScheme.toColor() val isPlus by settingsViewModel.isPlus.collectAsStateWithLifecycle() - val isPurchaseStateLoaded by settingsViewModel.isPurchaseStateLoaded.collectAsStateWithLifecycle() - val isSettingsLoaded by settingsViewModel.isSettingsLoaded.collectAsStateWithLifecycle() - - LaunchedEffect(isPurchaseStateLoaded, isPlus, isSettingsLoaded) { - if (isPurchaseStateLoaded && isSettingsLoaded) { - if (!isPlus) { - settingsViewModel.resetPaywalledSettings() - } else { - settingsViewModel.reloadSettings() - } - } - } TomatoTheme( darkTheme = darkTheme, diff --git a/app/src/main/java/org/nsh07/pomodoro/billing/BillingManager.kt b/app/src/main/java/org/nsh07/pomodoro/billing/BillingManager.kt index 4cdc106..66b1659 100644 --- a/app/src/main/java/org/nsh07/pomodoro/billing/BillingManager.kt +++ b/app/src/main/java/org/nsh07/pomodoro/billing/BillingManager.kt @@ -21,5 +21,4 @@ import kotlinx.coroutines.flow.StateFlow interface BillingManager { val isPlus: StateFlow - val isLoaded: StateFlow } \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt index 2827eab..f7c2c44 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt @@ -101,10 +101,6 @@ fun SettingsScreenRoot( val longBreakTimeInputFieldState = viewModel.longBreakTimeTextFieldState val isPlus by viewModel.isPlus.collectAsStateWithLifecycle() - val alarmEnabled by viewModel.alarmEnabled.collectAsStateWithLifecycle(true) - val vibrateEnabled by viewModel.vibrateEnabled.collectAsStateWithLifecycle(true) - val dndEnabled by viewModel.dndEnabled.collectAsStateWithLifecycle(false) - val alarmSound by viewModel.alarmSound.collectAsStateWithLifecycle(viewModel.currentAlarmSound) val settingsState by viewModel.settingsState.collectAsStateWithLifecycle() @@ -125,10 +121,6 @@ fun SettingsScreenRoot( shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, longBreakTimeInputFieldState = longBreakTimeInputFieldState, sessionsSliderState = sessionsSliderState, - alarmEnabled = alarmEnabled, - vibrateEnabled = vibrateEnabled, - dndEnabled = dndEnabled, - alarmSound = alarmSound, onAction = viewModel::onAction, setShowPaywall = setShowPaywall, modifier = modifier @@ -146,10 +138,6 @@ private fun SettingsScreen( shortBreakTimeInputFieldState: TextFieldState, longBreakTimeInputFieldState: TextFieldState, sessionsSliderState: SliderState, - alarmEnabled: Boolean, - vibrateEnabled: Boolean, - dndEnabled: Boolean, - alarmSound: String, onAction: (SettingsAction) -> Unit, setShowPaywall: (Boolean) -> Unit, modifier: Modifier = Modifier @@ -288,9 +276,6 @@ private fun SettingsScreen( entry { AlarmSettings( settingsState = settingsState, - alarmEnabled = alarmEnabled, - vibrateEnabled = vibrateEnabled, - alarmSound = alarmSound, onAction = onAction, onBack = backStack::removeLastOrNull ) @@ -307,8 +292,7 @@ private fun SettingsScreen( entry { TimerSettings( isPlus = isPlus, - aodEnabled = settingsState.aodEnabled, - dndEnabled = dndEnabled, + settingsState = settingsState, focusTimeInputFieldState = focusTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, longBreakTimeInputFieldState = longBreakTimeInputFieldState, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt index 186fba8..7b3fcdd 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt @@ -79,9 +79,6 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape @Composable fun AlarmSettings( settingsState: SettingsState, - alarmEnabled: Boolean, - vibrateEnabled: Boolean, - alarmSound: String, onAction: (SettingsAction) -> Unit, onBack: () -> Unit, modifier: Modifier = Modifier @@ -91,10 +88,11 @@ fun AlarmSettings( var alarmName by remember { mutableStateOf("...") } - LaunchedEffect(alarmSound) { + LaunchedEffect(settingsState.alarmSound) { withContext(Dispatchers.IO) { alarmName = - RingtoneManager.getRingtone(context, alarmSound.toUri())?.getTitle(context) ?: "" + RingtoneManager.getRingtone(context, settingsState.alarmSound.toUri()) + ?.getTitle(context) ?: "" } } @@ -117,30 +115,30 @@ fun AlarmSettings( } @SuppressLint("LocalContextGetResourceValueCall") - val ringtonePickerIntent = remember(alarmSound) { + val ringtonePickerIntent = remember(settingsState.alarmSound) { Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply { putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM) putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, context.getString(R.string.alarm_sound)) - putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, alarmSound.toUri()) + putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, settingsState.alarmSound.toUri()) } } val switchItems = remember( settingsState.blackTheme, settingsState.aodEnabled, - alarmEnabled, - vibrateEnabled + settingsState.alarmEnabled, + settingsState.vibrateEnabled ) { listOf( SettingsSwitchItem( - checked = alarmEnabled, + checked = settingsState.alarmEnabled, icon = R.drawable.alarm_on, label = R.string.sound, description = R.string.alarm_desc, onClick = { onAction(SettingsAction.SaveAlarmEnabled(it)) } ), SettingsSwitchItem( - checked = vibrateEnabled, + checked = settingsState.vibrateEnabled, icon = R.drawable.mobile_vibrate, label = R.string.vibrate, description = R.string.vibrate_desc, @@ -245,9 +243,7 @@ fun AlarmSettingsPreview() { val settingsState = SettingsState() AlarmSettings( settingsState = settingsState, - alarmEnabled = true, - vibrateEnabled = false, - alarmSound = "", onAction = {}, - onBack = {}) + onBack = {} + ) } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt index 74504f0..a7cb380 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt @@ -41,6 +41,7 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.FilledTonalIconToggleButton @@ -57,6 +58,7 @@ import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberSliderState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -78,6 +80,7 @@ import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem import org.nsh07.pomodoro.ui.settingsScreen.components.MinuteInputField import org.nsh07.pomodoro.ui.settingsScreen.components.PlusDivider import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsAction +import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsState import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors @@ -92,14 +95,13 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape @Composable fun TimerSettings( isPlus: Boolean, - aodEnabled: Boolean, - dndEnabled: Boolean, + settingsState: SettingsState, focusTimeInputFieldState: TextFieldState, shortBreakTimeInputFieldState: TextFieldState, longBreakTimeInputFieldState: TextFieldState, sessionsSliderState: SliderState, - setShowPaywall: (Boolean) -> Unit, onAction: (SettingsAction) -> Unit, + setShowPaywall: (Boolean) -> Unit, onBack: () -> Unit, modifier: Modifier = Modifier ) { @@ -116,7 +118,7 @@ fun TimerSettings( val switchItems = listOf( SettingsSwitchItem( - checked = dndEnabled, + checked = settingsState.dndEnabled, icon = R.drawable.dnd, label = R.string.dnd, description = R.string.dnd_desc, @@ -133,7 +135,7 @@ fun TimerSettings( } ), SettingsSwitchItem( - checked = aodEnabled, + checked = settingsState.aodEnabled, icon = R.drawable.aod, label = R.string.always_on_display, description = R.string.always_on_display_desc, @@ -393,18 +395,17 @@ fun TimerSettings( @Preview @Composable private fun TimerSettingsPreview() { - val focusTimeInputFieldState = TextFieldState("25") - val shortBreakTimeInputFieldState = TextFieldState("5") - val longBreakTimeInputFieldState = TextFieldState("15") - val sessionsSliderState = SliderState( + val focusTimeInputFieldState = rememberTextFieldState("25") + val shortBreakTimeInputFieldState = rememberTextFieldState("5") + val longBreakTimeInputFieldState = rememberTextFieldState("15") + val sessionsSliderState = rememberSliderState( value = 4f, valueRange = 1f..8f, steps = 6 ) TimerSettings( isPlus = false, - aodEnabled = true, - dndEnabled = false, + settingsState = remember { SettingsState() }, focusTimeInputFieldState = focusTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, longBreakTimeInputFieldState = longBreakTimeInputFieldState, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt index bb43365..b9d42b4 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt @@ -23,7 +23,11 @@ import androidx.compose.ui.graphics.Color @Immutable data class SettingsState( val theme: String = "auto", + val alarmSound: String = "", val colorScheme: String = Color.White.toString(), val blackTheme: Boolean = false, - val aodEnabled: Boolean = false + val aodEnabled: Boolean = false, + val alarmEnabled: Boolean = true, + val vibrateEnabled: Boolean = true, + val dndEnabled: Boolean = false ) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt index c86bf3f..d412bdd 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsViewModel.kt @@ -18,6 +18,7 @@ package org.nsh07.pomodoro.ui.settingsScreen.viewModel import android.net.Uri +import android.provider.Settings import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SliderState @@ -36,7 +37,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.nsh07.pomodoro.TomatoApplication @@ -54,10 +54,6 @@ class SettingsViewModel( val backStack = mutableStateListOf(Screen.Settings.Main) val isPlus = billingManager.isPlus - val isPurchaseStateLoaded = billingManager.isLoaded - - private val _isSettingsLoaded = MutableStateFlow(false) - val isSettingsLoaded = _isSettingsLoaded.asStateFlow() private val _settingsState = MutableStateFlow(SettingsState()) val settingsState = _settingsState.asStateFlow() @@ -81,25 +77,13 @@ class SettingsViewModel( ) } - val currentAlarmSound = timerRepository.alarmSoundUri.toString() - private var focusFlowCollectionJob: Job? = null private var shortBreakFlowCollectionJob: Job? = null private var longBreakFlowCollectionJob: Job? = null - val alarmSound = - preferenceRepository.getStringPreferenceFlow("alarm_sound").distinctUntilChanged() - val alarmEnabled = - preferenceRepository.getBooleanPreferenceFlow("alarm_enabled").distinctUntilChanged() - val vibrateEnabled = - preferenceRepository.getBooleanPreferenceFlow("vibrate_enabled").distinctUntilChanged() - val dndEnabled = - preferenceRepository.getBooleanPreferenceFlow("dnd_enabled").distinctUntilChanged() - init { viewModelScope.launch { reloadSettings() - _isSettingsLoaded.value = true } } @@ -176,6 +160,9 @@ class SettingsViewModel( private fun saveAlarmEnabled(enabled: Boolean) { viewModelScope.launch { timerRepository.alarmEnabled = enabled + _settingsState.update { currentState -> + currentState.copy(alarmEnabled = enabled) + } preferenceRepository.saveBooleanPreference("alarm_enabled", enabled) } } @@ -183,6 +170,9 @@ class SettingsViewModel( private fun saveVibrateEnabled(enabled: Boolean) { viewModelScope.launch { timerRepository.vibrateEnabled = enabled + _settingsState.update { currentState -> + currentState.copy(vibrateEnabled = enabled) + } preferenceRepository.saveBooleanPreference("vibrate_enabled", enabled) } } @@ -190,6 +180,9 @@ class SettingsViewModel( private fun saveDndEnabled(enabled: Boolean) { viewModelScope.launch { timerRepository.dndEnabled = enabled + _settingsState.update { currentState -> + currentState.copy(dndEnabled = enabled) + } preferenceRepository.saveBooleanPreference("dnd_enabled", enabled) } } @@ -197,6 +190,9 @@ class SettingsViewModel( private fun saveAlarmSound(uri: Uri?) { viewModelScope.launch { timerRepository.alarmSoundUri = uri + _settingsState.update { currentState -> + currentState.copy(alarmSound = uri.toString()) + } preferenceRepository.saveStringPreference("alarm_sound", uri.toString()) } } @@ -237,16 +233,6 @@ class SettingsViewModel( } } - fun resetPaywalledSettings() { - _settingsState.update { currentState -> - currentState.copy( - aodEnabled = false, - blackTheme = false, - colorScheme = Color.White.toString() - ) - } - } - suspend fun reloadSettings() { val theme = preferenceRepository.getStringPreference("theme") ?: preferenceRepository.saveStringPreference("theme", "auto") @@ -256,13 +242,29 @@ class SettingsViewModel( ?: preferenceRepository.saveBooleanPreference("black_theme", false) val aodEnabled = preferenceRepository.getBooleanPreference("aod_enabled") ?: preferenceRepository.saveBooleanPreference("aod_enabled", false) + val alarmSound = preferenceRepository.getStringPreference("alarm_sound") + ?: preferenceRepository.saveStringPreference( + "alarm_sound", + (Settings.System.DEFAULT_ALARM_ALERT_URI + ?: Settings.System.DEFAULT_RINGTONE_URI).toString() + ) + val alarmEnabled = preferenceRepository.getBooleanPreference("alarm_enabled") + ?: preferenceRepository.saveBooleanPreference("alarm_enabled", true) + val vibrateEnabled = preferenceRepository.getBooleanPreference("vibrate_enabled") + ?: preferenceRepository.saveBooleanPreference("vibrate_enabled", true) + val dndEnabled = preferenceRepository.getBooleanPreference("dnd_enabled") + ?: preferenceRepository.saveBooleanPreference("dnd_enabled", false) _settingsState.update { currentState -> currentState.copy( theme = theme, colorScheme = colorScheme, + alarmSound = alarmSound, blackTheme = blackTheme, - aodEnabled = aodEnabled + aodEnabled = aodEnabled, + alarmEnabled = alarmEnabled, + vibrateEnabled = vibrateEnabled, + dndEnabled = dndEnabled ) } } diff --git a/app/src/play/java/org/nsh07/pomodoro/billing/PlayBillingManager.kt b/app/src/play/java/org/nsh07/pomodoro/billing/PlayBillingManager.kt index a658884..94bfcb2 100644 --- a/app/src/play/java/org/nsh07/pomodoro/billing/PlayBillingManager.kt +++ b/app/src/play/java/org/nsh07/pomodoro/billing/PlayBillingManager.kt @@ -33,9 +33,6 @@ class PlayBillingManager : BillingManager { private val _isPlus = MutableStateFlow(false) override val isPlus = _isPlus.asStateFlow() - private val _isLoaded = MutableStateFlow(false) - override val isLoaded = _isLoaded.asStateFlow() - private val purchases by lazy { Purchases.sharedInstance } init { @@ -48,11 +45,9 @@ class PlayBillingManager : BillingManager { purchases.getCustomerInfoWith( onSuccess = { customerInfo -> _isPlus.value = customerInfo.entitlements[ENTITLEMENT_ID]?.isActive == true - _isLoaded.value = true }, onError = { error -> Log.e("GooglePlayPaywallManager", "Error fetching customer info: $error") - _isLoaded.value = true } ) } From 8f2245269135bd22d8db6a30ae54eaf4ebe68931 Mon Sep 17 00:00:00 2001 From: Nishant Mishra Date: Sun, 9 Nov 2025 19:44:47 +0530 Subject: [PATCH 9/9] feat(ui): smoothen timer progress by increasing update rate --- app/src/main/java/org/nsh07/pomodoro/MainActivity.kt | 2 +- app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt | 2 +- app/src/main/java/org/nsh07/pomodoro/ui/AlwaysOnDisplay.kt | 2 +- .../nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt | 3 --- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt index 95e9502..1b28db4 100644 --- a/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt +++ b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt @@ -93,6 +93,6 @@ class MainActivity : ComponentActivity() { override fun onStart() { super.onStart() // Increase the timer loop frequency again when visible to make the progress smoother - appContainer.appTimerRepository.timerFrequency = 10f + appContainer.appTimerRepository.timerFrequency = 60f } } \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt b/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt index 788d1af..d7aea1a 100644 --- a/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt +++ b/app/src/main/java/org/nsh07/pomodoro/data/TimerRepository.kt @@ -54,7 +54,7 @@ class AppTimerRepository : TimerRepository { override var shortBreakTime = 5 * 60 * 1000L override var longBreakTime = 15 * 60 * 1000L override var sessionLength = 4 - override var timerFrequency: Float = 10f + override var timerFrequency: Float = 60f override var alarmEnabled = true override var vibrateEnabled = true override var dndEnabled: Boolean = false diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/AlwaysOnDisplay.kt b/app/src/main/java/org/nsh07/pomodoro/ui/AlwaysOnDisplay.kt index 8ed8dcb..ed82236 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/AlwaysOnDisplay.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/AlwaysOnDisplay.kt @@ -113,7 +113,7 @@ fun SharedTransitionScope.AlwaysOnDisplay( } onDispose { - setTimerFrequency(10f) + setTimerFrequency(60f) window.clearFlags( WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt index 9052bcb..84c6ed4 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/viewModel/TimerViewModel.kt @@ -116,9 +116,6 @@ class TimerViewModel( ) ).toUri() - preferenceRepository.getBooleanPreference("aod_enabled") - ?: preferenceRepository.saveBooleanPreference("aod_enabled", false) - _time.update { timerRepository.focusTime } cycles = 0 startTime = 0L