Simplified SettingsRepo.kt and SettingsScreen.kt classes

This commit is contained in:
Arty Bishop committed 2026-04-04 15:44:51 +01:00
1 parent 9afe2ab69e
commit b59f9a6c17
5 files changed
+192 -433

No files matched your search

@@ -249,79 +249,25 @@ class SettingsRepo(
private val _rcSettings = MutableStateFlow(getRCSettings())
override val rcSettings: StateFlow<RCSettings> = _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> = _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> = _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(
@@ -57,39 +57,18 @@ interface ISettingsRepo {
//region # RC settings
val rcSettings: StateFlow<RCSettings>
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<OtherSettings>
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<DataSourcesSettings>
fun setUseCustomTle(value: Boolean)
fun setUseCustomTransceivers(value: Boolean)
fun setTleUrl(value: String)
fun setTransceiversUrl(value: String)
fun updateDataSourcesSettings(settings: DataSourcesSettings)
//endregion
}
@@ -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)
}
@@ -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()
}
@@ -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<SettingsState> = _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<OrbitalPass>, 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()
)