refactor(settings): move state variables to single state class

#117
This commit is contained in:
Nishant Mishra
2025-11-09 11:47:18 +05:30
parent 538c984d40
commit e1fa6c28b9
9 changed files with 59 additions and 91 deletions

View File

@@ -61,18 +61,6 @@ class MainActivity : ComponentActivity() {
val seed = settingsState.colorScheme.toColor()
val isPlus by settingsViewModel.isPlus.collectAsStateWithLifecycle()
val isPurchaseStateLoaded by settingsViewModel.isPurchaseStateLoaded.collectAsStateWithLifecycle()
val isSettingsLoaded by settingsViewModel.isSettingsLoaded.collectAsStateWithLifecycle()
LaunchedEffect(isPurchaseStateLoaded, isPlus, isSettingsLoaded) {
if (isPurchaseStateLoaded && isSettingsLoaded) {
if (!isPlus) {
settingsViewModel.resetPaywalledSettings()
} else {
settingsViewModel.reloadSettings()
}
}
}
TomatoTheme(
darkTheme = darkTheme,

View File

@@ -21,5 +21,4 @@ import kotlinx.coroutines.flow.StateFlow
interface BillingManager {
val isPlus: StateFlow<Boolean>
val isLoaded: StateFlow<Boolean>
}

View File

@@ -101,10 +101,6 @@ fun SettingsScreenRoot(
val longBreakTimeInputFieldState = viewModel.longBreakTimeTextFieldState
val isPlus by viewModel.isPlus.collectAsStateWithLifecycle()
val alarmEnabled by viewModel.alarmEnabled.collectAsStateWithLifecycle(true)
val vibrateEnabled by viewModel.vibrateEnabled.collectAsStateWithLifecycle(true)
val dndEnabled by viewModel.dndEnabled.collectAsStateWithLifecycle(false)
val alarmSound by viewModel.alarmSound.collectAsStateWithLifecycle(viewModel.currentAlarmSound)
val settingsState by viewModel.settingsState.collectAsStateWithLifecycle()
@@ -125,10 +121,6 @@ fun SettingsScreenRoot(
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
longBreakTimeInputFieldState = longBreakTimeInputFieldState,
sessionsSliderState = sessionsSliderState,
alarmEnabled = alarmEnabled,
vibrateEnabled = vibrateEnabled,
dndEnabled = dndEnabled,
alarmSound = alarmSound,
onAction = viewModel::onAction,
setShowPaywall = setShowPaywall,
modifier = modifier
@@ -146,10 +138,6 @@ private fun SettingsScreen(
shortBreakTimeInputFieldState: TextFieldState,
longBreakTimeInputFieldState: TextFieldState,
sessionsSliderState: SliderState,
alarmEnabled: Boolean,
vibrateEnabled: Boolean,
dndEnabled: Boolean,
alarmSound: String,
onAction: (SettingsAction) -> Unit,
setShowPaywall: (Boolean) -> Unit,
modifier: Modifier = Modifier
@@ -288,9 +276,6 @@ private fun SettingsScreen(
entry<Screen.Settings.Alarm> {
AlarmSettings(
settingsState = settingsState,
alarmEnabled = alarmEnabled,
vibrateEnabled = vibrateEnabled,
alarmSound = alarmSound,
onAction = onAction,
onBack = backStack::removeLastOrNull
)
@@ -307,8 +292,7 @@ private fun SettingsScreen(
entry<Screen.Settings.Timer> {
TimerSettings(
isPlus = isPlus,
aodEnabled = settingsState.aodEnabled,
dndEnabled = dndEnabled,
settingsState = settingsState,
focusTimeInputFieldState = focusTimeInputFieldState,
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
longBreakTimeInputFieldState = longBreakTimeInputFieldState,

View File

@@ -79,9 +79,6 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
@Composable
fun AlarmSettings(
settingsState: SettingsState,
alarmEnabled: Boolean,
vibrateEnabled: Boolean,
alarmSound: String,
onAction: (SettingsAction) -> Unit,
onBack: () -> Unit,
modifier: Modifier = Modifier
@@ -91,10 +88,11 @@ fun AlarmSettings(
var alarmName by remember { mutableStateOf("...") }
LaunchedEffect(alarmSound) {
LaunchedEffect(settingsState.alarmSound) {
withContext(Dispatchers.IO) {
alarmName =
RingtoneManager.getRingtone(context, alarmSound.toUri())?.getTitle(context) ?: ""
RingtoneManager.getRingtone(context, settingsState.alarmSound.toUri())
?.getTitle(context) ?: ""
}
}
@@ -117,30 +115,30 @@ fun AlarmSettings(
}
@SuppressLint("LocalContextGetResourceValueCall")
val ringtonePickerIntent = remember(alarmSound) {
val ringtonePickerIntent = remember(settingsState.alarmSound) {
Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM)
putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, context.getString(R.string.alarm_sound))
putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, alarmSound.toUri())
putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, settingsState.alarmSound.toUri())
}
}
val switchItems = remember(
settingsState.blackTheme,
settingsState.aodEnabled,
alarmEnabled,
vibrateEnabled
settingsState.alarmEnabled,
settingsState.vibrateEnabled
) {
listOf(
SettingsSwitchItem(
checked = alarmEnabled,
checked = settingsState.alarmEnabled,
icon = R.drawable.alarm_on,
label = R.string.sound,
description = R.string.alarm_desc,
onClick = { onAction(SettingsAction.SaveAlarmEnabled(it)) }
),
SettingsSwitchItem(
checked = vibrateEnabled,
checked = settingsState.vibrateEnabled,
icon = R.drawable.mobile_vibrate,
label = R.string.vibrate,
description = R.string.vibrate_desc,
@@ -245,9 +243,7 @@ fun AlarmSettingsPreview() {
val settingsState = SettingsState()
AlarmSettings(
settingsState = settingsState,
alarmEnabled = true,
vibrateEnabled = false,
alarmSound = "",
onAction = {},
onBack = {})
onBack = {}
)
}

View File

@@ -41,6 +41,7 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalIconToggleButton
@@ -57,6 +58,7 @@ import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberSliderState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -78,6 +80,7 @@ import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem
import org.nsh07.pomodoro.ui.settingsScreen.components.MinuteInputField
import org.nsh07.pomodoro.ui.settingsScreen.components.PlusDivider
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsAction
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsState
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors
@@ -92,14 +95,13 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
@Composable
fun TimerSettings(
isPlus: Boolean,
aodEnabled: Boolean,
dndEnabled: Boolean,
settingsState: SettingsState,
focusTimeInputFieldState: TextFieldState,
shortBreakTimeInputFieldState: TextFieldState,
longBreakTimeInputFieldState: TextFieldState,
sessionsSliderState: SliderState,
setShowPaywall: (Boolean) -> Unit,
onAction: (SettingsAction) -> Unit,
setShowPaywall: (Boolean) -> Unit,
onBack: () -> Unit,
modifier: Modifier = Modifier
) {
@@ -116,7 +118,7 @@ fun TimerSettings(
val switchItems = listOf(
SettingsSwitchItem(
checked = dndEnabled,
checked = settingsState.dndEnabled,
icon = R.drawable.dnd,
label = R.string.dnd,
description = R.string.dnd_desc,
@@ -133,7 +135,7 @@ fun TimerSettings(
}
),
SettingsSwitchItem(
checked = aodEnabled,
checked = settingsState.aodEnabled,
icon = R.drawable.aod,
label = R.string.always_on_display,
description = R.string.always_on_display_desc,
@@ -393,18 +395,17 @@ fun TimerSettings(
@Preview
@Composable
private fun TimerSettingsPreview() {
val focusTimeInputFieldState = TextFieldState("25")
val shortBreakTimeInputFieldState = TextFieldState("5")
val longBreakTimeInputFieldState = TextFieldState("15")
val sessionsSliderState = SliderState(
val focusTimeInputFieldState = rememberTextFieldState("25")
val shortBreakTimeInputFieldState = rememberTextFieldState("5")
val longBreakTimeInputFieldState = rememberTextFieldState("15")
val sessionsSliderState = rememberSliderState(
value = 4f,
valueRange = 1f..8f,
steps = 6
)
TimerSettings(
isPlus = false,
aodEnabled = true,
dndEnabled = false,
settingsState = remember { SettingsState() },
focusTimeInputFieldState = focusTimeInputFieldState,
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
longBreakTimeInputFieldState = longBreakTimeInputFieldState,

View File

@@ -23,7 +23,11 @@ import androidx.compose.ui.graphics.Color
@Immutable
data class SettingsState(
val theme: String = "auto",
val alarmSound: String = "",
val colorScheme: String = Color.White.toString(),
val blackTheme: Boolean = false,
val aodEnabled: Boolean = false
val aodEnabled: Boolean = false,
val alarmEnabled: Boolean = true,
val vibrateEnabled: Boolean = true,
val dndEnabled: Boolean = false
)

View File

@@ -18,6 +18,7 @@
package org.nsh07.pomodoro.ui.settingsScreen.viewModel
import android.net.Uri
import android.provider.Settings
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SliderState
@@ -36,7 +37,6 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.nsh07.pomodoro.TomatoApplication
@@ -54,10 +54,6 @@ class SettingsViewModel(
val backStack = mutableStateListOf<Screen.Settings>(Screen.Settings.Main)
val isPlus = billingManager.isPlus
val isPurchaseStateLoaded = billingManager.isLoaded
private val _isSettingsLoaded = MutableStateFlow(false)
val isSettingsLoaded = _isSettingsLoaded.asStateFlow()
private val _settingsState = MutableStateFlow(SettingsState())
val settingsState = _settingsState.asStateFlow()
@@ -81,25 +77,13 @@ class SettingsViewModel(
)
}
val currentAlarmSound = timerRepository.alarmSoundUri.toString()
private var focusFlowCollectionJob: Job? = null
private var shortBreakFlowCollectionJob: Job? = null
private var longBreakFlowCollectionJob: Job? = null
val alarmSound =
preferenceRepository.getStringPreferenceFlow("alarm_sound").distinctUntilChanged()
val alarmEnabled =
preferenceRepository.getBooleanPreferenceFlow("alarm_enabled").distinctUntilChanged()
val vibrateEnabled =
preferenceRepository.getBooleanPreferenceFlow("vibrate_enabled").distinctUntilChanged()
val dndEnabled =
preferenceRepository.getBooleanPreferenceFlow("dnd_enabled").distinctUntilChanged()
init {
viewModelScope.launch {
reloadSettings()
_isSettingsLoaded.value = true
}
}
@@ -176,6 +160,9 @@ class SettingsViewModel(
private fun saveAlarmEnabled(enabled: Boolean) {
viewModelScope.launch {
timerRepository.alarmEnabled = enabled
_settingsState.update { currentState ->
currentState.copy(alarmEnabled = enabled)
}
preferenceRepository.saveBooleanPreference("alarm_enabled", enabled)
}
}
@@ -183,6 +170,9 @@ class SettingsViewModel(
private fun saveVibrateEnabled(enabled: Boolean) {
viewModelScope.launch {
timerRepository.vibrateEnabled = enabled
_settingsState.update { currentState ->
currentState.copy(vibrateEnabled = enabled)
}
preferenceRepository.saveBooleanPreference("vibrate_enabled", enabled)
}
}
@@ -190,6 +180,9 @@ class SettingsViewModel(
private fun saveDndEnabled(enabled: Boolean) {
viewModelScope.launch {
timerRepository.dndEnabled = enabled
_settingsState.update { currentState ->
currentState.copy(dndEnabled = enabled)
}
preferenceRepository.saveBooleanPreference("dnd_enabled", enabled)
}
}
@@ -197,6 +190,9 @@ class SettingsViewModel(
private fun saveAlarmSound(uri: Uri?) {
viewModelScope.launch {
timerRepository.alarmSoundUri = uri
_settingsState.update { currentState ->
currentState.copy(alarmSound = uri.toString())
}
preferenceRepository.saveStringPreference("alarm_sound", uri.toString())
}
}
@@ -237,16 +233,6 @@ class SettingsViewModel(
}
}
fun resetPaywalledSettings() {
_settingsState.update { currentState ->
currentState.copy(
aodEnabled = false,
blackTheme = false,
colorScheme = Color.White.toString()
)
}
}
suspend fun reloadSettings() {
val theme = preferenceRepository.getStringPreference("theme")
?: preferenceRepository.saveStringPreference("theme", "auto")
@@ -256,13 +242,29 @@ class SettingsViewModel(
?: preferenceRepository.saveBooleanPreference("black_theme", false)
val aodEnabled = preferenceRepository.getBooleanPreference("aod_enabled")
?: preferenceRepository.saveBooleanPreference("aod_enabled", false)
val alarmSound = preferenceRepository.getStringPreference("alarm_sound")
?: preferenceRepository.saveStringPreference(
"alarm_sound",
(Settings.System.DEFAULT_ALARM_ALERT_URI
?: Settings.System.DEFAULT_RINGTONE_URI).toString()
)
val alarmEnabled = preferenceRepository.getBooleanPreference("alarm_enabled")
?: preferenceRepository.saveBooleanPreference("alarm_enabled", true)
val vibrateEnabled = preferenceRepository.getBooleanPreference("vibrate_enabled")
?: preferenceRepository.saveBooleanPreference("vibrate_enabled", true)
val dndEnabled = preferenceRepository.getBooleanPreference("dnd_enabled")
?: preferenceRepository.saveBooleanPreference("dnd_enabled", false)
_settingsState.update { currentState ->
currentState.copy(
theme = theme,
colorScheme = colorScheme,
alarmSound = alarmSound,
blackTheme = blackTheme,
aodEnabled = aodEnabled
aodEnabled = aodEnabled,
alarmEnabled = alarmEnabled,
vibrateEnabled = vibrateEnabled,
dndEnabled = dndEnabled
)
}
}