Merge pull request #132 from nsh07/architecture-cleanup
Architecture cleanup (part 1)
This commit is contained in:
@@ -61,6 +61,9 @@ android {
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions += "version"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -21,5 +21,4 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface BillingManager {
|
||||
val isPlus: StateFlow<Boolean>
|
||||
val isLoaded: StateFlow<Boolean>
|
||||
}
|
||||
@@ -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<TimerState> by lazy {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Screen.Settings>,
|
||||
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<Screen.Settings.Alarm> {
|
||||
AlarmSettings(
|
||||
preferencesState = preferencesState,
|
||||
alarmEnabled = alarmEnabled,
|
||||
vibrateEnabled = vibrateEnabled,
|
||||
alarmSound = alarmSound,
|
||||
onAlarmEnabledChange = onAlarmEnabledChange,
|
||||
onVibrateEnabledChange = onVibrateEnabledChange,
|
||||
onAlarmSoundChanged = onAlarmSoundChanged,
|
||||
settingsState = settingsState,
|
||||
onAction = onAction,
|
||||
onBack = backStack::removeLastOrNull
|
||||
)
|
||||
}
|
||||
entry<Screen.Settings.Appearance> {
|
||||
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<Screen.Settings.Timer> {
|
||||
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,
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = {}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 = {}
|
||||
)
|
||||
|
||||
@@ -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 = {}
|
||||
)
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
)
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
)
|
||||
@@ -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>(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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> = _timerState.asStateFlow()
|
||||
|
||||
val time: StateFlow<Long> = _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
|
||||
|
||||
26
app/src/main/res/drawable/weblate.xml
Normal file
26
app/src/main/res/drawable/weblate.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<!--
|
||||
~ 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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9.662,3.809c-1.875,1.19 -2.81,3.515 -2.83,5.795 -0.014,2.628 0.666,5.258 1.988,7.305 0.936,1.46 2.238,2.715 3.836,3.412a6.942,6.942 0,0 0,5.647 -0.07c1.997,-0.927 3.523,-2.73 4.463,-4.785 1.606,-3.518 1.643,-7.724 0.12,-11.295 -1.146,0.458 -2.166,-0.271 -2.166,-0.271s0.003,1.122 -1.083,1.685c1.115,2.612 1.088,5.717 -0.03,8.263 -0.538,1.225 -1.358,2.365 -2.498,3.01 -0.917,0.52 -2.04,0.625 -3.052,0.184 -1.342,-0.585 -2.293,-1.864 -2.89,-3.254 -0.466,-1.067 -0.782,-2.447 -0.802,-3.878 -0.037,-1.724 0.728,-3.193 1.635,-3.218 0.622,-0.024 1.427,0.918 1.598,2.435 0.158,1.543 -0.177,3.72 -1.174,5.49 0.677,1.085 1.77,1.98 2.951,1.974 1.386,-2.338 1.827,-4.911 1.793,-6.987 -0.02,-2.28 -0.955,-4.603 -2.83,-5.795 -1.437,-0.907 -3.173,-0.948 -4.676,0zM3.278,3.9s-1.018,0.73 -2.163,0.27c-1.524,3.573 -1.488,7.778 0.12,11.296 0.94,2.056 2.465,3.858 4.462,4.785a6.95,6.95 0,0 0,5.523 0.124,9.12 9.12,0 0,1 -1.75,-1.455 11.18,11.18 0,0 1,-1.267 -1.628c-0.768,-0.08 -1.498,-0.482 -2.003,-0.913 -1.447,-1.213 -2.453,-3.478 -2.632,-5.9 -0.12,-1.635 0.14,-3.354 0.795,-4.894C3.276,5.022 3.278,3.9 3.278,3.9z" />
|
||||
</vector>
|
||||
@@ -92,4 +92,5 @@
|
||||
<string name="rate_on_google_play">Rate on Google Play</string>
|
||||
<string name="bmc">BuyMeACoffee</string>
|
||||
<string name="selected">Selected</string>
|
||||
<string name="help_with_translation">Help with translation</string>
|
||||
</resources>
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user