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" 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/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/MainActivity.kt b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt index 1917159..1b28db4 100644 --- a/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt +++ b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt @@ -50,34 +50,22 @@ 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() - val isSettingsLoaded by settingsViewModel.isSettingsLoaded.collectAsStateWithLifecycle() - - LaunchedEffect(isPurchaseStateLoaded, isPlus, isSettingsLoaded) { - if (isPurchaseStateLoaded && isSettingsLoaded) { - if (!isPlus) { - settingsViewModel.resetPaywalledSettings() - } else { - settingsViewModel.reloadSettings() - } - } - } TomatoTheme( darkTheme = darkTheme, seedColor = seed, - blackTheme = preferencesState.blackTheme + blackTheme = settingsState.blackTheme ) { val colorScheme = colorScheme LaunchedEffect(colorScheme) { @@ -86,7 +74,7 @@ class MainActivity : ComponentActivity() { AppScreen( isPlus = isPlus, - isAODEnabled = preferencesState.aodEnabled, + isAODEnabled = settingsState.aodEnabled, setTimerFrequency = { appContainer.appTimerRepository.timerFrequency = it } @@ -105,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/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/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 { 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/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/settingsScreen/SettingsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt index c8ae71f..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 @@ -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 @@ -77,7 +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 @@ -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) { @@ -106,12 +101,8 @@ 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 preferencesState by viewModel.preferencesState.collectAsStateWithLifecycle() + val settingsState by viewModel.settingsState.collectAsStateWithLifecycle() val sessionsSliderState = rememberSaveable( saver = SliderState.Saver( @@ -124,30 +115,13 @@ fun SettingsScreenRoot( SettingsScreen( isPlus = isPlus, - preferencesState = preferencesState, + settingsState = settingsState, backStack = backStack, focusTimeInputFieldState = focusTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, longBreakTimeInputFieldState = longBreakTimeInputFieldState, sessionsSliderState = sessionsSliderState, - alarmEnabled = alarmEnabled, - 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 ) @@ -158,24 +132,13 @@ fun SettingsScreenRoot( @Composable private fun SettingsScreen( isPlus: Boolean, - preferencesState: PreferencesState, + settingsState: SettingsState, backStack: SnapshotStateList, focusTimeInputFieldState: TextFieldState, shortBreakTimeInputFieldState: TextFieldState, longBreakTimeInputFieldState: TextFieldState, sessionsSliderState: SliderState, - alarmEnabled: Boolean, - 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 ) { @@ -312,23 +275,16 @@ private fun SettingsScreen( entry { AlarmSettings( - preferencesState = preferencesState, - alarmEnabled = alarmEnabled, - vibrateEnabled = vibrateEnabled, - alarmSound = alarmSound, - onAlarmEnabledChange = onAlarmEnabledChange, - onVibrateEnabledChange = onVibrateEnabledChange, - onAlarmSoundChanged = onAlarmSoundChanged, + settingsState = settingsState, + onAction = onAction, onBack = backStack::removeLastOrNull ) } entry { AppearanceSettings( - preferencesState = preferencesState, + settingsState = settingsState, isPlus = isPlus, - onBlackThemeChange = onBlackThemeChange, - onThemeChange = onThemeChange, - onColorSchemeChange = onColorSchemeChange, + onAction = onAction, setShowPaywall = setShowPaywall, onBack = backStack::removeLastOrNull ) @@ -336,14 +292,12 @@ private fun SettingsScreen( entry { TimerSettings( isPlus = isPlus, - aodEnabled = preferencesState.aodEnabled, - dndEnabled = dndEnabled, + settingsState = settingsState, focusTimeInputFieldState = focusTimeInputFieldState, 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/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/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt index bc46e07..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 @@ -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 @@ -64,7 +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 @@ -76,13 +78,8 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun AlarmSettings( - preferencesState: PreferencesState, - alarmEnabled: Boolean, - vibrateEnabled: Boolean, - alarmSound: String, - onAlarmEnabledChange: (Boolean) -> Unit, - onVibrateEnabledChange: (Boolean) -> Unit, - onAlarmSoundChanged: (Uri?) -> Unit, + settingsState: SettingsState, + 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) ?: "" } } @@ -112,38 +110,39 @@ fun AlarmSettings( @Suppress("DEPRECATION") result.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) } - onAlarmSoundChanged(uri) + onAction(SettingsAction.SaveAlarmSound(uri)) } } - val ringtonePickerIntent = remember(alarmSound) { + @SuppressLint("LocalContextGetResourceValueCall") + 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( - preferencesState.blackTheme, - preferencesState.aodEnabled, - alarmEnabled, - vibrateEnabled + settingsState.blackTheme, + settingsState.aodEnabled, + 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 = onAlarmEnabledChange + 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, - onClick = onVibrateEnabledChange + onClick = { onAction(SettingsAction.SaveVibrateEnabled(it)) } ) ) } @@ -241,14 +240,10 @@ fun AlarmSettings( @Preview @Composable fun AlarmSettingsPreview() { - val preferencesState = PreferencesState() + val settingsState = SettingsState() AlarmSettings( - preferencesState = preferencesState, - alarmEnabled = true, - vibrateEnabled = false, - alarmSound = "", - onAlarmEnabledChange = {}, - onVibrateEnabledChange = {}, - onAlarmSoundChanged = {}, - onBack = {}) + settingsState = settingsState, + 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..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 @@ -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 @@ -50,7 +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,11 +62,9 @@ import org.nsh07.pomodoro.utils.toColor @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun AppearanceSettings( - preferencesState: PreferencesState, + settingsState: SettingsState, isPlus: Boolean, - onBlackThemeChange: (Boolean) -> Unit, - onThemeChange: (String) -> Unit, - onColorSchemeChange: (Color) -> Unit, + onAction: (SettingsAction) -> Unit, setShowPaywall: (Boolean) -> Unit, onBack: () -> Unit, modifier: Modifier = Modifier @@ -105,8 +103,8 @@ fun AppearanceSettings( } item { ThemePickerListItem( - theme = preferencesState.theme, - onThemeChange = onThemeChange, + theme = settingsState.theme, + onThemeChange = { onAction(SettingsAction.SaveTheme(it)) }, items = if (isPlus) 3 else 1, index = 0 ) @@ -118,20 +116,20 @@ fun AppearanceSettings( item { ColorSchemePickerListItem( - color = preferencesState.colorScheme.toColor(), + color = settingsState.colorScheme.toColor(), items = 3, index = if (isPlus) 1 else 0, isPlus = isPlus, - onColorChange = onColorSchemeChange, + onColorChange = { onAction(SettingsAction.SaveColorScheme(it)) }, ) } 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, - onClick = onBlackThemeChange + onClick = { onAction(SettingsAction.SaveBlackTheme(it)) } ) ListItem( leadingContent = { @@ -175,14 +173,12 @@ fun AppearanceSettings( @Preview @Composable fun AppearanceSettingsPreview() { - val preferencesState = PreferencesState() + val settingsState = SettingsState() TomatoTheme(dynamicColor = false) { AppearanceSettings( - preferencesState = preferencesState, + settingsState = settingsState, 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..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 @@ -36,10 +36,12 @@ 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 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 @@ -56,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 @@ -76,6 +79,8 @@ 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.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 @@ -90,17 +95,15 @@ 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, - onAodEnabledChange: (Boolean) -> Unit, - onDndEnabledChange: (Boolean) -> Unit, + onAction: (SettingsAction) -> Unit, + setShowPaywall: (Boolean) -> Unit, onBack: () -> Unit, - modifier: Modifier = Modifier, - setShowPaywall: (Boolean) -> Unit + modifier: Modifier = Modifier ) { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val context = LocalContext.current @@ -110,12 +113,12 @@ fun TimerSettings( LaunchedEffect(Unit) { if (!notificationManagerService.isNotificationPolicyAccessGranted()) - onDndEnabledChange(false) + onAction(SettingsAction.SaveDndEnabled(false)) } val switchItems = listOf( SettingsSwitchItem( - checked = dndEnabled, + checked = settingsState.dndEnabled, icon = R.drawable.dnd, label = R.string.dnd, description = R.string.dnd_desc, @@ -128,15 +131,15 @@ fun TimerSettings( } else if (!it && notificationManagerService.isNotificationPolicyAccessGranted()) { notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL) } - onDndEnabledChange(it) + onAction(SettingsAction.SaveDndEnabled(it)) } ), SettingsSwitchItem( - checked = aodEnabled, + checked = settingsState.aodEnabled, 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 +316,7 @@ fun TimerSettings( item { PlusDivider(setShowPaywall) } - itemsIndexed(switchItems.drop(1)) { index, item -> + items(switchItems.drop(1)) { item -> ListItem( leadingContent = { Icon( @@ -392,24 +395,22 @@ 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, sessionsSliderState = sessionsSliderState, - onAodEnabledChange = {}, - onDndEnabledChange = {}, + onAction = {}, setShowPaywall = {}, onBack = {} ) 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/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/SettingsState.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt new file mode 100644 index 0000000..b9d42b4 --- /dev/null +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/viewModel/SettingsState.kt @@ -0,0 +1,33 @@ +/* + * 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 alarmSound: String = "", + val colorScheme: String = Color.White.toString(), + val blackTheme: 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 94ba2c6..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,13 +54,9 @@ 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 _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()) @@ -81,25 +77,26 @@ 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 + } + } + + 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) } } @@ -160,80 +157,82 @@ class SettingsViewModel( longBreakFlowCollectionJob?.cancel() } - fun saveAlarmEnabled(enabled: Boolean) { + private fun saveAlarmEnabled(enabled: Boolean) { viewModelScope.launch { timerRepository.alarmEnabled = enabled + _settingsState.update { currentState -> + currentState.copy(alarmEnabled = enabled) + } preferenceRepository.saveBooleanPreference("alarm_enabled", enabled) } } - fun saveVibrateEnabled(enabled: Boolean) { + private fun saveVibrateEnabled(enabled: Boolean) { viewModelScope.launch { timerRepository.vibrateEnabled = enabled + _settingsState.update { currentState -> + currentState.copy(vibrateEnabled = enabled) + } preferenceRepository.saveBooleanPreference("vibrate_enabled", enabled) } } - fun saveDndEnabled(enabled: Boolean) { + private fun saveDndEnabled(enabled: Boolean) { viewModelScope.launch { timerRepository.dndEnabled = enabled + _settingsState.update { currentState -> + currentState.copy(dndEnabled = enabled) + } preferenceRepository.saveBooleanPreference("dnd_enabled", enabled) } } - fun saveAlarmSound(uri: Uri?) { + private fun saveAlarmSound(uri: Uri?) { viewModelScope.launch { timerRepository.alarmSoundUri = uri + _settingsState.update { currentState -> + currentState.copy(alarmSound = uri.toString()) + } preferenceRepository.saveStringPreference("alarm_sound", uri.toString()) } } - fun saveColorScheme(colorScheme: Color) { + private fun saveColorScheme(colorScheme: Color) { viewModelScope.launch { - _preferencesState.update { currentState -> + _settingsState.update { currentState -> currentState.copy(colorScheme = colorScheme.toString()) } preferenceRepository.saveStringPreference("color_scheme", colorScheme.toString()) } } - fun saveTheme(theme: String) { + private fun saveTheme(theme: String) { viewModelScope.launch { - _preferencesState.update { currentState -> + _settingsState.update { currentState -> currentState.copy(theme = theme) } preferenceRepository.saveStringPreference("theme", theme) } } - fun saveBlackTheme(blackTheme: Boolean) { + private fun saveBlackTheme(blackTheme: Boolean) { viewModelScope.launch { - _preferencesState.update { currentState -> + _settingsState.update { currentState -> currentState.copy(blackTheme = blackTheme) } preferenceRepository.saveBooleanPreference("black_theme", blackTheme) } } - fun saveAodEnabled(aodEnabled: Boolean) { + private fun saveAodEnabled(aodEnabled: Boolean) { viewModelScope.launch { - _preferencesState.update { currentState -> + _settingsState.update { currentState -> currentState.copy(aodEnabled = aodEnabled) } preferenceRepository.saveBooleanPreference("aod_enabled", aodEnabled) } } - fun resetPaywalledSettings() { - _preferencesState.update { currentState -> - currentState.copy( - aodEnabled = false, - blackTheme = false, - colorScheme = Color.White.toString() - ) - } - } - suspend fun reloadSettings() { val theme = preferenceRepository.getStringPreference("theme") ?: preferenceRepository.saveStringPreference("theme", "auto") @@ -243,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) - _preferencesState.update { currentState -> + _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/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..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 @@ -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.combine +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.nsh07.pomodoro.TomatoApplication @@ -55,6 +58,11 @@ class TimerViewModel( val timerState: StateFlow = _timerState.asStateFlow() val time: StateFlow = _time.asStateFlow() + + val progress = _time.combine(_timerState) { remainingTime, uiState -> + (uiState.totalTime.toFloat() - remainingTime) / uiState.totalTime + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 0f) + private var cycles = 0 private var startTime = 0L @@ -108,9 +116,6 @@ class TimerViewModel( ) ).toUri() - preferenceRepository.getBooleanPreference("aod_enabled") - ?: preferenceRepository.saveBooleanPreference("aod_enabled", false) - _time.update { timerRepository.focusTime } cycles = 0 startTime = 0L 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/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 } ) } 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