diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SettingsRepo.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SettingsRepo.kt index 0d6dc0f4..ed33b820 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SettingsRepo.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SettingsRepo.kt @@ -249,79 +249,25 @@ class SettingsRepo( private val _rcSettings = MutableStateFlow(getRCSettings()) override val rcSettings: StateFlow = _rcSettings - override fun setBluetoothRotatorAddress(value: String) { - preferences.edit { putString(keyBluetoothRotatorAddress, value) } - _rcSettings.update { it.copy(bluetoothRotatorAddress = value) } - } - - override fun setBluetoothRotatorFormat(value: String) { - preferences.edit { putString(keyBluetoothRotatorFormat, value) } - _rcSettings.update { it.copy(bluetoothRotatorFormat = value) } - } - - override fun setBluetoothRotatorName(value: String) { - preferences.edit { putString(keyBluetoothRotatorName, value) } - _rcSettings.update { it.copy(bluetoothRotatorName = value) } - } - - override fun setBluetoothRotatorState(value: Boolean) { - preferences.edit { putBoolean(keyBluetoothRotatorState, value) } - _rcSettings.update { it.copy(bluetoothRotatorState = value) } - } - - override fun setBluetoothFrequencyState(value: Boolean) { - preferences.edit { putBoolean(keyBluetoothFrequencyState, value) } - _rcSettings.update { it.copy(bluetoothFrequencyState = value) } - } - - override fun setBluetoothFrequencyAddress(value: String) { - preferences.edit { putString(keyBluetoothFrequencyAddress, value) } - _rcSettings.update { it.copy(bluetoothFrequencyAddress = value) } - } - - override fun setBluetoothFrequencyFormat(value: String) { - preferences.edit { putString(keyBluetoothFrequencyFormat, value) } - _rcSettings.update { it.copy(bluetoothFrequencyFormat = value) } - } - - override fun setRotatorAddress(value: String) { - preferences.edit { putString(keyRotatorAddress, value) } - _rcSettings.update { it.copy(rotatorAddress = value) } - } - - override fun setRotatorPort(value: String) { - preferences.edit { putString(keyRotatorPort, value) } - _rcSettings.update { it.copy(rotatorPort = value) } - } - - override fun setRotatorState(value: Boolean) { - preferences.edit { putBoolean(keyRotatorState, value) } - _rcSettings.update { it.copy(rotatorState = value) } - } - - override fun setRotatorFormat(value: String) { - preferences.edit { putString(keyRotatorFormat, value) } - _rcSettings.update { it.copy(rotatorFormat = value) } - } - - override fun setFrequencyState(value: Boolean) { - preferences.edit { putBoolean(keyFrequencyState, value) } - _rcSettings.update { it.copy(frequencyState = value) } - } - - override fun setFrequencyAddress(value: String) { - preferences.edit { putString(keyFrequencyAddress, value) } - _rcSettings.update { it.copy(frequencyAddress = value) } - } - - override fun setFrequencyPort(value: String) { - preferences.edit { putString(keyFrequencyPort, value) } - _rcSettings.update { it.copy(frequencyPort = value) } - } - - override fun setFrequencyFormat(value: String) { - preferences.edit { putString(keyFrequencyFormat, value) } - _rcSettings.update { it.copy(frequencyFormat = value) } + override fun updateRCSettings(settings: RCSettings) { + preferences.edit { + putBoolean(keyRotatorState, settings.rotatorState) + putString(keyRotatorAddress, settings.rotatorAddress) + putString(keyRotatorPort, settings.rotatorPort) + putString(keyRotatorFormat, settings.rotatorFormat) + putBoolean(keyFrequencyState, settings.frequencyState) + putString(keyFrequencyAddress, settings.frequencyAddress) + putString(keyFrequencyPort, settings.frequencyPort) + putString(keyFrequencyFormat, settings.frequencyFormat) + putBoolean(keyBluetoothRotatorState, settings.bluetoothRotatorState) + putString(keyBluetoothRotatorFormat, settings.bluetoothRotatorFormat) + putString(keyBluetoothRotatorName, settings.bluetoothRotatorName) + putString(keyBluetoothRotatorAddress, settings.bluetoothRotatorAddress) + putBoolean(keyBluetoothFrequencyState, settings.bluetoothFrequencyState) + putString(keyBluetoothFrequencyFormat, settings.bluetoothFrequencyFormat) + putString(keyBluetoothFrequencyAddress, settings.bluetoothFrequencyAddress) + } + _rcSettings.value = settings } private fun getRCSettings(): RCSettings = RCSettings( @@ -347,39 +293,20 @@ class SettingsRepo( private val _otherSettings = MutableStateFlow(getOtherSettings()) override val otherSettings: StateFlow = _otherSettings - override fun setStateOfAutoUpdate(value: Boolean) { - preferences.edit { putBoolean(keyStateOfAutoUpdate, value) } - _otherSettings.update { it.copy(stateOfAutoUpdate = value) } - } - - override fun setStateOfSensors(value: Boolean) { - preferences.edit { putBoolean(keyStateOfSensors, value) } - _otherSettings.update { it.copy(stateOfSensors = value) } - } - - override fun setStateOfSweep(value: Boolean) { - preferences.edit { putBoolean(keyStateOfSweep, value) } - _otherSettings.update { it.copy(stateOfSweep = value) } - } - - override fun setStateOfUtc(value: Boolean) { - preferences.edit { putBoolean(keyStateOfUtc, value) } - _otherSettings.update { it.copy(stateOfUtc = value) } - } - - override fun setStateOfLightTheme(value: Boolean){ - preferences.edit { putBoolean(keyStateOfLightTheme, value) } - _otherSettings.update { it.copy(stateOfLightTheme = value) } - } - - override fun setWarningDismissed() { - preferences.edit { putBoolean(keyShouldSeeWarning, false) } - _otherSettings.update { it.copy(shouldSeeWarning = false) } - } - - override fun setWhatsNewDismissed() { - preferences.edit { putBoolean(keyShouldSeeWhatsNew, false) } - _otherSettings.update { it.copy(shouldSeeWhatsNew = false) } + override fun updateOtherSettings(transform: (OtherSettings) -> OtherSettings) { + _otherSettings.update { current -> + val new = transform(current) + preferences.edit { + putBoolean(keyStateOfAutoUpdate, new.stateOfAutoUpdate) + putBoolean(keyStateOfSensors, new.stateOfSensors) + putBoolean(keyStateOfSweep, new.stateOfSweep) + putBoolean(keyStateOfUtc, new.stateOfUtc) + putBoolean(keyStateOfLightTheme, new.stateOfLightTheme) + putBoolean(keyShouldSeeWarning, new.shouldSeeWarning) + putBoolean(keyShouldSeeWhatsNew, new.shouldSeeWhatsNew) + } + new + } } private fun getOtherSettings(): OtherSettings = OtherSettings( @@ -397,24 +324,14 @@ class SettingsRepo( private val _dataSourcesSettings = MutableStateFlow(getDataSourcesSettings()) override val dataSourcesSettings: StateFlow = _dataSourcesSettings - override fun setUseCustomTle(value: Boolean) { - preferences.edit { putBoolean(keyUseCustomTle, value) } - _dataSourcesSettings.update { it.copy(useCustomTLE = value) } - } - - override fun setUseCustomTransceivers(value: Boolean) { - preferences.edit { putBoolean(keyUseCustomTransceivers, value) } - _dataSourcesSettings.update { it.copy(useCustomTransceivers = value) } - } - - override fun setTleUrl(value: String) { - preferences.edit { putString(keyTleUrl, value) } - _dataSourcesSettings.update { it.copy(tleUrl = value) } - } - - override fun setTransceiversUrl(value: String) { - preferences.edit { putString(keyTransceiversUrl, value) } - _dataSourcesSettings.update { it.copy(transceiversUrl = value) } + override fun updateDataSourcesSettings(settings: DataSourcesSettings) { + preferences.edit { + putBoolean(keyUseCustomTle, settings.useCustomTLE) + putBoolean(keyUseCustomTransceivers, settings.useCustomTransceivers) + putString(keyTleUrl, settings.tleUrl) + putString(keyTransceiversUrl, settings.transceiversUrl) + } + _dataSourcesSettings.value = settings } private fun getDataSourcesSettings(): DataSourcesSettings = DataSourcesSettings( diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/repository/ISettingsRepo.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/repository/ISettingsRepo.kt index bd3370b8..8233e1e5 100644 --- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/repository/ISettingsRepo.kt +++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/repository/ISettingsRepo.kt @@ -57,39 +57,18 @@ interface ISettingsRepo { //region # RC settings val rcSettings: StateFlow - fun setBluetoothRotatorAddress(value: String) - fun setBluetoothRotatorFormat(value: String) - fun setBluetoothRotatorName(value: String) - fun setBluetoothRotatorState(value: Boolean) - fun setBluetoothFrequencyAddress(value: String) - fun setBluetoothFrequencyFormat(value: String) - fun setBluetoothFrequencyState(value: Boolean) - fun setRotatorAddress(value: String) - fun setRotatorPort(value: String) - fun setRotatorState(value: Boolean) - fun setRotatorFormat(value: String) - fun setFrequencyAddress(value: String) - fun setFrequencyPort(value: String) - fun setFrequencyState(value: Boolean) - fun setFrequencyFormat(value: String) + fun updateRCSettings(settings: RCSettings) //endregion //region # Other settings val otherSettings: StateFlow - fun setStateOfAutoUpdate(value: Boolean) - fun setStateOfSensors(value: Boolean) - fun setStateOfSweep(value: Boolean) - fun setStateOfUtc(value: Boolean) - fun setStateOfLightTheme(value: Boolean) - fun setWarningDismissed() - fun setWhatsNewDismissed() + fun updateOtherSettings(transform: (OtherSettings) -> OtherSettings) + fun setWarningDismissed() = updateOtherSettings { it.copy(shouldSeeWarning = false) } + fun setWhatsNewDismissed() = updateOtherSettings { it.copy(shouldSeeWhatsNew = false) } //endregion //region # Transceivers settings val dataSourcesSettings: StateFlow - fun setUseCustomTle(value: Boolean) - fun setUseCustomTransceivers(value: Boolean) - fun setTleUrl(value: String) - fun setTransceiversUrl(value: String) + fun updateDataSourcesSettings(settings: DataSourcesSettings) //endregion } diff --git a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsScreen.kt b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsScreen.kt index 7ea668e8..8f391290 100644 --- a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsScreen.kt +++ b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsScreen.kt @@ -43,6 +43,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -140,15 +141,16 @@ private fun SettingsScreen(uiState: SettingsState) { onImportTle = { contentRequestForTle.launch("*/*") }, onImportTransceivers = { contentRequestForTransceivers.launch("*/*") }, onDismiss = dismissDataSourcesDialog, - onSave = { - useCustomTle, useCustomTransceivers, tleUrl, transceiversUrl -> - if (!useCustomTle || tleUrl.isNotBlank()) { - uiState.sendDataSourcesAction(DataSourcesAction.SetUseCustomTle(useCustomTle)) - uiState.sendDataSourcesAction(DataSourcesAction.SetTleUrl(tleUrl)) - } - if (!useCustomTransceivers || transceiversUrl.isNotBlank()) { - uiState.sendDataSourcesAction(DataSourcesAction.SetUseCustomTransceivers(useCustomTransceivers)) - uiState.sendDataSourcesAction(DataSourcesAction.SetTransceiversUrl(transceiversUrl)) + onSave = { useCustomTle, useCustomTransceivers, tleUrl, transceiversUrl -> + val current = uiState.dataSourcesSettings + val newSettings = current.copy( + useCustomTLE = if (!useCustomTle || tleUrl.isNotBlank()) useCustomTle else current.useCustomTLE, + tleUrl = if (!useCustomTle || tleUrl.isNotBlank()) tleUrl else current.tleUrl, + useCustomTransceivers = if (!useCustomTransceivers || transceiversUrl.isNotBlank()) useCustomTransceivers else current.useCustomTransceivers, + transceiversUrl = if (!useCustomTransceivers || transceiversUrl.isNotBlank()) transceiversUrl else current.transceiversUrl + ) + if (newSettings != current) { + uiState.sendDataSourcesAction(DataSourcesAction.Update(newSettings)) } if (useCustomTle || useCustomTransceivers) { uiState.sendAction(SettingsAction.UpdateFromWeb) @@ -164,20 +166,6 @@ private fun SettingsScreen(uiState: SettingsState) { // RC settings val rcSettings = uiState.rcSettings - val setRotatorState = { value: Boolean -> uiState.sendRCAction(RCAction.SetRotatorState(value)) } - val setRotatorAddress = { value: String -> uiState.sendRCAction(RCAction.SetRotatorAddress(value)) } - val setRotatorPort = { value: String -> uiState.sendRCAction(RCAction.SetRotatorPort(value)) } - val setRotatorFormat = { value: String -> uiState.sendRCAction(RCAction.SetRotatorFormat(value)) } - val setFrequencyState = { value: Boolean -> uiState.sendRCAction(RCAction.SetFrequencyState(value)) } - val setFrequencyAddress = { value: String -> uiState.sendRCAction(RCAction.SetFrequencyAddress(value)) } - val setFrequencyPort = { value: String -> uiState.sendRCAction(RCAction.SetFrequencyPort(value)) } - val setFrequencyFormat = { value: String -> uiState.sendRCAction(RCAction.SetFrequencyFormat(value)) } - val setBluetoothRotatorState = { value: Boolean -> uiState.sendRCAction(RCAction.SetBluetoothRotatorState(value)) } - val setBluetoothRotatorAddress = { value: String -> uiState.sendRCAction(RCAction.SetBluetoothRotatorAddress(value)) } - val setBluetoothRotatorFormat = { value: String -> uiState.sendRCAction(RCAction.SetBluetoothRotatorFormat(value)) } - val setBluetoothFrequencyState = { value: Boolean -> uiState.sendRCAction(RCAction.SetBluetoothFrequencyState(value)) } - val setBluetoothFrequencyAddress = { value: String -> uiState.sendRCAction(RCAction.SetBluetoothFrequencyAddress(value)) } - val setBluetoothFrequencyFormat = { value: String -> uiState.sendRCAction(RCAction.SetBluetoothFrequencyFormat(value)) } // Network data output val networkDialogState = rememberSaveable { mutableStateOf(false) } @@ -187,22 +175,22 @@ private fun SettingsScreen(uiState: SettingsState) { NetworkOutputDialog( initialSettings = rcSettings, onDismiss = dismissNetworkDialog, - onSave = { rotatorState, - rotatorAddress, - rotatorPort, - rotatorFormat, - frequencyState, - frequencyAddress, - frequencyPort, - frequencyFormat -> - setRotatorState(rotatorState) - setRotatorAddress(rotatorAddress) - setRotatorPort(rotatorPort) - setRotatorFormat(rotatorFormat) - setFrequencyState(frequencyState) - setFrequencyAddress(frequencyAddress) - setFrequencyPort(frequencyPort) - setFrequencyFormat(frequencyFormat) + onSave = { rotatorState, rotatorAddress, rotatorPort, rotatorFormat, + frequencyState, frequencyAddress, frequencyPort, frequencyFormat -> + uiState.sendRCAction( + RCAction.Update( + rcSettings.copy( + rotatorState = rotatorState, + rotatorAddress = rotatorAddress, + rotatorPort = rotatorPort, + rotatorFormat = rotatorFormat, + frequencyState = frequencyState, + frequencyAddress = frequencyAddress, + frequencyPort = frequencyPort, + frequencyFormat = frequencyFormat + ) + ) + ) } ) } @@ -218,8 +206,11 @@ private fun SettingsScreen(uiState: SettingsState) { val bluetoothRequest = rememberLauncherForActivityResult(bluetoothContract) { isGranted -> if (!isGranted) { - uiState.sendRCAction(RCAction.SetBluetoothRotatorState(false)) - uiState.sendRCAction(RCAction.SetBluetoothFrequencyState(false)) + uiState.sendRCAction( + RCAction.Update( + rcSettings.copy(bluetoothRotatorState = false, bluetoothFrequencyState = false) + ) + ) uiState.sendSystemAction(SystemAction.ShowToast(bluetoothError)) } else { bluetoothDialogState.value = true } @@ -230,28 +221,26 @@ private fun SettingsScreen(uiState: SettingsState) { BluetoothOutputDialog( initialSettings = rcSettings, onDismiss = dismissBluetoothDialog, - onSave = { rotatorState, - rotatorAddress, - rotatorFormat, - frequencyState, - frequencyAddress, - frequencyFormat -> - setBluetoothRotatorState(rotatorState) - setBluetoothRotatorAddress(rotatorAddress) - setBluetoothRotatorFormat(rotatorFormat) - setBluetoothFrequencyState(frequencyState) - setBluetoothFrequencyAddress(frequencyAddress) - setBluetoothFrequencyFormat(frequencyFormat) + onSave = { rotatorState, rotatorAddress, rotatorFormat, + frequencyState, frequencyAddress, frequencyFormat -> + uiState.sendRCAction( + RCAction.Update( + rcSettings.copy( + bluetoothRotatorState = rotatorState, + bluetoothRotatorAddress = rotatorAddress, + bluetoothRotatorFormat = rotatorFormat, + bluetoothFrequencyState = frequencyState, + bluetoothFrequencyAddress = frequencyAddress, + bluetoothFrequencyFormat = frequencyFormat + ) + ) + ) } ) } // Other settings val otherSettings = uiState.otherSettings - val toggleUtc = { value: Boolean -> uiState.sendAction(SettingsAction.ToggleUtc(value)) } - val toggleUpdate = { value: Boolean -> uiState.sendAction(SettingsAction.ToggleUpdate(value)) } - val toggleSweep = { value: Boolean -> uiState.sendAction(SettingsAction.ToggleSweep(value)) } - val toggleSensor = { value: Boolean -> uiState.sendAction(SettingsAction.ToggleSensor(value)) } val uriHandler = LocalUriHandler.current val appUrl = stringResource(R.string.prefs_app_url) val donateUrl = stringResource(R.string.prefs_donate_url) @@ -303,7 +292,7 @@ private fun SettingsScreen(uiState: SettingsState) { } item { DataCard(dataSettings, updateFromWeb, clearAllData, showDataSourcesDialog) } item(span = { GridItemSpan(maxLineSpan) }) { OutputCard({ showNetworkDialog() }, { showBluetoothDialog() }) } - item { OtherCard(otherSettings, toggleUtc, toggleUpdate, toggleSweep, toggleSensor) } + item { OtherCard(otherSettings, uiState.sendAction) } item { CardCredits() } } } @@ -476,17 +465,11 @@ private fun OtherCardPreview() = MainTheme { shouldSeeWarning = false, shouldSeeWhatsNew = false ) - OtherCard(settings = values, {}, {}, {}, {}) + OtherCard(settings = values) {} } @Composable -private fun OtherCard( - settings: OtherSettings, - toggleUtc: (Boolean) -> Unit, - toggleUpdate: (Boolean) -> Unit, - toggleSweep: (Boolean) -> Unit, - toggleSensor: (Boolean) -> Unit -) { +private fun OtherCard(settings: OtherSettings, sendAction: (SettingsAction) -> Unit) { ElevatedCard( modifier = Modifier .fillMaxWidth() @@ -497,49 +480,44 @@ private fun OtherCard( text = stringResource(id = R.string.prefs_other_title), color = MaterialTheme.colorScheme.primary ) - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Text(text = stringResource(id = R.string.prefs_other_switch_utc)) - Switch(checked = settings.stateOfUtc, onCheckedChange = { toggleUtc(it) }) + SwitchRow(R.string.prefs_other_switch_utc, settings.stateOfUtc) { + sendAction(SettingsAction.ToggleUtc(it)) } - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Text(text = stringResource(id = R.string.prefs_other_switch_update)) - Switch(checked = settings.stateOfAutoUpdate, onCheckedChange = { toggleUpdate(it) }) + SwitchRow(R.string.prefs_other_switch_update, settings.stateOfAutoUpdate) { + sendAction(SettingsAction.ToggleUpdate(it)) } - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Text(text = stringResource(id = R.string.prefs_other_switch_sweep)) - Switch(checked = settings.stateOfSweep, onCheckedChange = { toggleSweep(it) }) + SwitchRow(R.string.prefs_other_switch_sweep, settings.stateOfSweep) { + sendAction(SettingsAction.ToggleSweep(it)) } - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Text(text = stringResource(id = R.string.prefs_other_switch_sensors)) - Switch(checked = settings.stateOfSensors, onCheckedChange = { toggleSensor(it) }) + SwitchRow(R.string.prefs_other_switch_sensors, settings.stateOfSensors) { + sendAction(SettingsAction.ToggleSensor(it)) } } } } +@Composable +private fun SwitchRow(labelResId: Int, checked: Boolean, onCheckedChange: (Boolean) -> Unit) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(id = labelResId)) + Switch(checked = checked, onCheckedChange = onCheckedChange) + } +} + @Composable private fun setUpdateTime(updateTime: Long): String { - val updateDate = if (updateTime != 0L) { - val timePattern = stringResource(id = R.string.prefs_updated_time) - SimpleDateFormat(timePattern, Locale.getDefault()).format(Date(updateTime)) - } else { - stringResource(id = R.string.pass_time_placeholder) + val timePattern = stringResource(id = R.string.prefs_updated_time) + val placeholder = stringResource(id = R.string.pass_time_placeholder) + val updateDate = remember(updateTime) { + if (updateTime != 0L) { + SimpleDateFormat(timePattern, Locale.getDefault()).format(Date(updateTime)) + } else { + placeholder + } } return stringResource(id = R.string.prefs_updated_title, updateDate) } diff --git a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsState.kt b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsState.kt index 2253d665..cb229eac 100644 --- a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsState.kt +++ b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsState.kt @@ -21,7 +21,6 @@ import com.rtbishop.look4sat.core.domain.model.DataSourcesSettings import com.rtbishop.look4sat.core.domain.model.OtherSettings import com.rtbishop.look4sat.core.domain.model.RCSettings import com.rtbishop.look4sat.core.domain.predict.GeoPos -import com.rtbishop.look4sat.core.domain.predict.OrbitalPass data class PositionSettings( val isUpdating: Boolean, val stationPos: GeoPos, val messageResId: Int @@ -32,9 +31,6 @@ data class DataSettings( ) data class SettingsState( - val nextPass: OrbitalPass, - val nextTime: String, - val isNextTimeAos: Boolean, val appVersionName: String, val positionSettings: PositionSettings, val dataSettings: DataSettings, @@ -68,26 +64,9 @@ sealed class SystemAction { } sealed class RCAction { - data class SetRotatorState(val value: Boolean) : RCAction() - data class SetRotatorAddress(val value: String) : RCAction() - data class SetRotatorPort(val value: String) : RCAction() - data class SetRotatorFormat(val value: String) : RCAction() - data class SetFrequencyState(val value: Boolean) : RCAction() - data class SetFrequencyAddress(val value: String) : RCAction() - data class SetFrequencyPort(val value: String) : RCAction() - data class SetFrequencyFormat(val value: String) : RCAction() - data class SetBluetoothRotatorState(val value: Boolean) : RCAction() - data class SetBluetoothRotatorFormat(val value: String) : RCAction() - data class SetBluetoothRotatorName(val value: String) : RCAction() - data class SetBluetoothRotatorAddress(val value: String) : RCAction() - data class SetBluetoothFrequencyState(val value: Boolean) : RCAction() - data class SetBluetoothFrequencyFormat(val value: String) : RCAction() - data class SetBluetoothFrequencyAddress(val value: String) : RCAction() + data class Update(val settings: RCSettings) : RCAction() } sealed class DataSourcesAction { - data class SetUseCustomTle(val value: Boolean) : DataSourcesAction() - data class SetUseCustomTransceivers(val value: Boolean) : DataSourcesAction() - data class SetTleUrl(val value: String) : DataSourcesAction() - data class SetTransceiversUrl(val value: String) : DataSourcesAction() + data class Update(val settings: DataSourcesSettings) : DataSourcesAction() } diff --git a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsViewModel.kt b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsViewModel.kt index e3f184ac..297fb523 100644 --- a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsViewModel.kt +++ b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsViewModel.kt @@ -22,93 +22,61 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory -import com.rtbishop.look4sat.core.domain.model.DataSourcesSettings -import com.rtbishop.look4sat.core.domain.predict.OrbitalPass import com.rtbishop.look4sat.core.domain.repository.IContainerProvider import com.rtbishop.look4sat.core.domain.repository.IDatabaseRepo -import com.rtbishop.look4sat.core.domain.repository.ISatelliteRepo import com.rtbishop.look4sat.core.domain.repository.ISettingsRepo import com.rtbishop.look4sat.core.domain.usecase.IShowToast -import com.rtbishop.look4sat.core.domain.utility.toTimerString import com.rtbishop.look4sat.core.presentation.R -import com.rtbishop.look4sat.core.presentation.getDefaultPass -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancelAndJoin -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.update -import kotlinx.coroutines.isActive import kotlinx.coroutines.launch class SettingsViewModel( private val databaseRepo: IDatabaseRepo, - private val satelliteRepo: ISatelliteRepo, private val settingsRepo: ISettingsRepo, private val showToast: IShowToast ) : ViewModel() { private val defaultPosSettings = PositionSettings(false, settingsRepo.stationPosition.value, 0) private val defaultDataSettings = DataSettings(false, 0, 0, 0L) - private val defaultDataSourcesSettings = DataSourcesSettings( - useCustomTLE = false, - useCustomTransceivers = false, - tleUrl = settingsRepo.dataSourcesSettings.value.tleUrl, - transceiversUrl = settingsRepo.dataSourcesSettings.value.transceiversUrl - ) private val _uiState = MutableStateFlow( SettingsState( - nextTime = "00:00:00", - isNextTimeAos = true, - nextPass = getDefaultPass(), appVersionName = settingsRepo.appVersionName, positionSettings = defaultPosSettings, dataSettings = defaultDataSettings, otherSettings = settingsRepo.otherSettings.value, rcSettings = settingsRepo.rcSettings.value, - dataSourcesSettings = defaultDataSourcesSettings, + dataSourcesSettings = settingsRepo.dataSourcesSettings.value, sendAction = ::handleAction, sendRCAction = ::handleAction, sendSystemAction = ::handleAction, sendDataSourcesAction = ::handleAction ) ) - private var processing: Job? = null val uiState: StateFlow = _uiState init { viewModelScope.launch { - satelliteRepo.passes.collectLatest { passes -> - processing?.cancelAndJoin() - processing = viewModelScope.launch { - while (isActive) { - val timeNow = System.currentTimeMillis() - val newPasses = satelliteRepo.processPasses(passes, timeNow) - setPassInfo(newPasses, timeNow) - delay(1000) - } + settingsRepo.stationPosition.collect { geoPos -> + _uiState.update { + it.copy(positionSettings = it.positionSettings.copy(isUpdating = false, stationPos = geoPos)) } } } - viewModelScope.launch { - settingsRepo.stationPosition.collect { geoPos -> - val newPosSettings = _uiState.value.positionSettings.copy( - isUpdating = false, stationPos = geoPos - ) - _uiState.update { it.copy(positionSettings = newPosSettings) } - } - } viewModelScope.launch { settingsRepo.databaseState.collect { state -> - val newDataSettings = _uiState.value.dataSettings.copy( - isUpdating = false, - entriesTotal = state.numberOfSatellites, - radiosTotal = state.numberOfRadios, - timestamp = state.updateTimestamp - ) - _uiState.update { it.copy(dataSettings = newDataSettings) } + _uiState.update { + it.copy( + dataSettings = DataSettings( + false, + state.numberOfSatellites, + state.numberOfRadios, + state.updateTimestamp + ) + ) + } } } viewModelScope.launch { @@ -128,41 +96,33 @@ class SettingsViewModel( } } + private fun handleAction(action: SettingsAction) { when (action) { SettingsAction.SetGpsPosition -> setGpsPosition() is SettingsAction.SetGeoPosition -> setGeoPosition(action.latitude, action.longitude) is SettingsAction.SetQthPosition -> setQthPosition(action.locator) SettingsAction.DismissPosMessages -> dismissPosMessage() - SettingsAction.UpdateFromWeb -> updateFromWeb() - is SettingsAction.UpdateTLEFromFile -> updateTLEFromFile(action.uri) - is SettingsAction.UpdateTransceiversFromFile -> updateTransceiversFromFile(action.uri) - SettingsAction.ClearAllData -> clearAllData() - is SettingsAction.ToggleUtc -> settingsRepo.setStateOfUtc(action.value) - is SettingsAction.ToggleUpdate -> settingsRepo.setStateOfAutoUpdate(action.value) - is SettingsAction.ToggleSweep -> settingsRepo.setStateOfSweep(action.value) - is SettingsAction.ToggleSensor -> settingsRepo.setStateOfSensors(action.value) - is SettingsAction.ToggleLightTheme -> settingsRepo.setStateOfLightTheme(action.value) + SettingsAction.UpdateFromWeb -> runDataUpdate { databaseRepo.updateFromRemote() } + is SettingsAction.UpdateTLEFromFile -> runDataUpdate { databaseRepo.updateTLEFromFile(action.uri) } + is SettingsAction.UpdateTransceiversFromFile -> runDataUpdate { + databaseRepo.updateTransceiversFromFile( + action.uri + ) + } + + SettingsAction.ClearAllData -> viewModelScope.launch { databaseRepo.clearAllData() } + is SettingsAction.ToggleUtc -> settingsRepo.updateOtherSettings { it.copy(stateOfUtc = action.value) } + is SettingsAction.ToggleUpdate -> settingsRepo.updateOtherSettings { it.copy(stateOfAutoUpdate = action.value) } + is SettingsAction.ToggleSweep -> settingsRepo.updateOtherSettings { it.copy(stateOfSweep = action.value) } + is SettingsAction.ToggleSensor -> settingsRepo.updateOtherSettings { it.copy(stateOfSensors = action.value) } + is SettingsAction.ToggleLightTheme -> settingsRepo.updateOtherSettings { it.copy(stateOfLightTheme = action.value) } } } private fun handleAction(action: RCAction) { when (action) { - is RCAction.SetRotatorState -> settingsRepo.setRotatorState(action.value) - is RCAction.SetRotatorAddress -> settingsRepo.setRotatorAddress(action.value) - is RCAction.SetRotatorPort -> settingsRepo.setRotatorPort(action.value) - is RCAction.SetRotatorFormat -> settingsRepo.setRotatorFormat(action.value) - is RCAction.SetFrequencyState -> settingsRepo.setFrequencyState(action.value) - is RCAction.SetFrequencyAddress -> settingsRepo.setFrequencyAddress(action.value) - is RCAction.SetFrequencyPort -> settingsRepo.setFrequencyPort(action.value) - is RCAction.SetFrequencyFormat -> settingsRepo.setFrequencyFormat(action.value) - is RCAction.SetBluetoothRotatorState -> settingsRepo.setBluetoothRotatorState(action.value) - is RCAction.SetBluetoothRotatorAddress -> settingsRepo.setBluetoothRotatorAddress(action.value) - is RCAction.SetBluetoothRotatorFormat -> settingsRepo.setBluetoothRotatorFormat(action.value) - is RCAction.SetBluetoothRotatorName -> settingsRepo.setBluetoothRotatorName(action.value) - is RCAction.SetBluetoothFrequencyState -> settingsRepo.setBluetoothFrequencyState(action.value) - is RCAction.SetBluetoothFrequencyFormat -> settingsRepo.setBluetoothFrequencyFormat(action.value) - is RCAction.SetBluetoothFrequencyAddress -> settingsRepo.setBluetoothFrequencyAddress(action.value) + is RCAction.Update -> settingsRepo.updateRCSettings(action.settings) } } @@ -174,118 +134,65 @@ class SettingsViewModel( private fun handleAction(action: DataSourcesAction) { when (action) { - is DataSourcesAction.SetUseCustomTle -> settingsRepo.setUseCustomTle(action.value) - is DataSourcesAction.SetUseCustomTransceivers -> settingsRepo.setUseCustomTransceivers(action.value) - is DataSourcesAction.SetTleUrl -> settingsRepo.setTleUrl(action.value) - is DataSourcesAction.SetTransceiversUrl -> settingsRepo.setTransceiversUrl(action.value) + is DataSourcesAction.Update -> settingsRepo.updateDataSourcesSettings(action.settings) } } + // region Position helpers — consolidated from 3 near-identical functions + private fun setGpsPosition() { - if (settingsRepo.setStationPosition()) { - val messageResId = R.string.prefs_loc_success - val newPosSettings = _uiState.value.positionSettings.copy( - isUpdating = true, messageResId = messageResId - ) - _uiState.update { it.copy(positionSettings = newPosSettings) } - } else { - val errorResId = R.string.prefs_loc_gps_error - val newPosSettings = _uiState.value.positionSettings.copy( - isUpdating = false, messageResId = errorResId - ) - _uiState.update { it.copy(positionSettings = newPosSettings) } - } + updatePosition(R.string.prefs_loc_gps_error) { settingsRepo.setStationPosition() } } private fun setGeoPosition(latitude: Double, longitude: Double) { - if (settingsRepo.setStationPosition(latitude, longitude, 0.0)) { - val messageResId = R.string.prefs_loc_success - val newPosSettings = _uiState.value.positionSettings.copy( - isUpdating = false, messageResId = messageResId - ) - _uiState.update { it.copy(positionSettings = newPosSettings) } - } else { - val errorResId = R.string.prefs_loc_input_error - val newPosSettings = _uiState.value.positionSettings.copy( - isUpdating = false, messageResId = errorResId - ) - _uiState.update { it.copy(positionSettings = newPosSettings) } - } + updatePosition(R.string.prefs_loc_input_error) { settingsRepo.setStationPosition(latitude, longitude, 0.0) } } private fun setQthPosition(locator: String) { - if (settingsRepo.setStationPosition(locator)) { - val messageResId = R.string.prefs_loc_success - val newPosSettings = _uiState.value.positionSettings.copy( - isUpdating = false, messageResId = messageResId - ) - _uiState.update { it.copy(positionSettings = newPosSettings) } - } else { - val errorResId = R.string.prefs_loc_qth_error - val newPosSettings = _uiState.value.positionSettings.copy( - isUpdating = false, messageResId = errorResId - ) - _uiState.update { it.copy(positionSettings = newPosSettings) } + updatePosition(R.string.prefs_loc_qth_error) { settingsRepo.setStationPosition(locator) } + } + + /** + * Common position update logic. Calls [action], emits success or error message. + */ + private inline fun updatePosition(errorResId: Int, action: () -> Boolean) { + val success = action() + val resId = if (success) R.string.prefs_loc_success else errorResId + val isUpdating = success // GPS update is async; geo/qth are immediate + _uiState.update { + it.copy(positionSettings = it.positionSettings.copy(isUpdating = isUpdating, messageResId = resId)) } } private fun dismissPosMessage() { - val newPosSettings = _uiState.value.positionSettings.copy( - isUpdating = false, messageResId = 0 - ) - _uiState.update { it.copy(positionSettings = newPosSettings) } + _uiState.update { + it.copy(positionSettings = it.positionSettings.copy(isUpdating = false, messageResId = 0)) + } } - private fun updateFromWeb() = viewModelScope.launch { + // endregion + + // region Data update helpers — consolidated from 3 near-identical functions + + /** + * Common data update logic. Sets isUpdating=true, runs the suspend [block], + * and resets isUpdating=false on failure. + */ + private fun runDataUpdate(block: suspend () -> Unit) = viewModelScope.launch { try { - val newDataSettings = _uiState.value.dataSettings.copy(isUpdating = true) - _uiState.update { it.copy(dataSettings = newDataSettings) } - databaseRepo.updateFromRemote() + _uiState.update { + it.copy(dataSettings = it.dataSettings.copy(isUpdating = true)) + } + block() } catch (exception: Exception) { - val newDataSettings = _uiState.value.dataSettings.copy(isUpdating = false) - _uiState.update { it.copy(dataSettings = newDataSettings) } + _uiState.update { + it.copy(dataSettings = it.dataSettings.copy(isUpdating = false)) + } println(exception) } } - private fun updateTLEFromFile(uri: String) = viewModelScope.launch { - try { - val newDataSettings = _uiState.value.dataSettings.copy(isUpdating = true) - _uiState.update { it.copy(dataSettings = newDataSettings) } - databaseRepo.updateTLEFromFile(uri) - } catch (exception: Exception) { - val newDataSettings = _uiState.value.dataSettings.copy(isUpdating = false) - _uiState.update { it.copy(dataSettings = newDataSettings) } - println(exception) - } - } - - private fun updateTransceiversFromFile(uri: String) = viewModelScope.launch { - try { - val newDataSettings = _uiState.value.dataSettings.copy(isUpdating = true) - _uiState.update { it.copy(dataSettings = newDataSettings) } - databaseRepo.updateTransceiversFromFile(uri) - } catch (exception: Exception) { - val newDataSettings = _uiState.value.dataSettings.copy(isUpdating = false) - _uiState.update { it.copy(dataSettings = newDataSettings) } - println(exception) - } - } - - private fun setPassInfo(passes: List, timeNow: Long) { - if (passes.isEmpty()) return - try { - val nextPass = passes.first { it.aosTime.minus(timeNow) > 0 } - val time = nextPass.aosTime.minus(timeNow).toTimerString() - _uiState.update { it.copy(nextPass = nextPass, nextTime = time, isNextTimeAos = true) } - } catch (_: NoSuchElementException) { - val lastPass = passes.last() - val time = lastPass.losTime.minus(timeNow).toTimerString() - _uiState.update { it.copy(nextPass = lastPass, nextTime = time, isNextTimeAos = false) } - } - } - - private fun clearAllData() = viewModelScope.launch { databaseRepo.clearAllData() } + // endregion companion object { val Factory: ViewModelProvider.Factory = viewModelFactory { @@ -294,7 +201,6 @@ class SettingsViewModel( val container = (this[applicationKey] as IContainerProvider).getMainContainer() SettingsViewModel( container.databaseRepo, - container.satelliteRepo, container.settingsRepo, container.provideShowToast() )