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 283142ac..5abc6d63 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 @@ -37,6 +37,7 @@ import com.rtbishop.look4sat.core.domain.utility.round import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update +import org.json.JSONObject class SettingsRepo( private val locationManager: LocationManager, @@ -417,5 +418,27 @@ class SettingsRepo( baudRate = preferences.getInt(keyRadioBaudRate, 4800), splitMode = preferences.getBoolean(keyRadioSplitMode, false) ) - //endregion + + private val keySatelliteOffsets = "satelliteOffsets" + + override fun getSatelliteOffset(catnum: Int): String { + val json = preferences.getString(keySatelliteOffsets, "{}") ?: "{}" + return try { + JSONObject(json).optString(catnum.toString(), "") + } catch (e: Exception) { + "" + } + } + + override fun setSatelliteOffset(catnum: Int, offset: String) { + val json = preferences.getString(keySatelliteOffsets, "{}") ?: "{}" + val updated = try { + val obj = JSONObject(json) + if (offset.isEmpty()) obj.remove(catnum.toString()) else obj.put(catnum.toString(), offset) + obj.toString() + } catch (e: Exception) { + """{"$catnum": "$offset"}""" + } + preferences.edit { putString(keySatelliteOffsets, updated) } + } } diff --git a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt index a57c0ac8..e8b051fe 100644 --- a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt +++ b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt @@ -213,6 +213,10 @@ private class FakeSettingsRepo(dataSources: DataSourcesSettings = defaultDataSou } override fun updateRadioControlSettings(settings: RadioControlSettings) = Unit + + override fun getSatelliteOffset(catnum: Int): String = "" + + override fun setSatelliteOffset(catnum: Int, offset: String) = Unit } private fun defaultDataSourcesSettings(): DataSourcesSettings { diff --git a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/SelectionRepoTest.kt b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/SelectionRepoTest.kt index 9a240b46..8bb71983 100644 --- a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/SelectionRepoTest.kt +++ b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/SelectionRepoTest.kt @@ -167,6 +167,10 @@ class SelectionRepoTest { override fun updateDataSourcesSettings(settings: DataSourcesSettings) = Unit override fun updateRadioControlSettings(settings: RadioControlSettings) = Unit + + override fun getSatelliteOffset(catnum: Int): String = "" + + override fun setSatelliteOffset(catnum: Int, offset: String) = Unit } } 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 96d0283d..b1b11733 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 @@ -75,4 +75,9 @@ interface ISettingsRepo { val radioControlSettings: StateFlow fun updateRadioControlSettings(settings: RadioControlSettings) //endregion + + //region # Per-satellite calculator offset settings + fun getSatelliteOffset(catnum: Int): String + fun setSatelliteOffset(catnum: Int, offset: String) + //endregion } diff --git a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarScreen.kt b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarScreen.kt index b94227ca..53b47670 100644 --- a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarScreen.kt +++ b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarScreen.kt @@ -198,7 +198,8 @@ private fun PagerCard( transceivers = uiState.transceivers.transmitters, selectedUuid = uiState.transceivers.selectedUuid, orbitalPos = uiState.orbitalPos, - onAction = onAction + onAction = onAction, + calculatorOffsetKHz = uiState.calculatorOffsetKHz ) RadarPage.Sstv -> SstvPage( sstv = uiState.sstv, diff --git a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarState.kt b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarState.kt index 78aaadd9..0383b85d 100644 --- a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarState.kt +++ b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarState.kt @@ -63,7 +63,8 @@ data class RadarState( val moonPosition: CelestialComputer.MoonPosition? = null, val transceivers: TransceiverSubState = TransceiverSubState(), val radioControl: RadioControlSubState = RadioControlSubState(), - val sstv: SstvSubState = SstvSubState() + val sstv: SstvSubState = SstvSubState(), + val calculatorOffsetKHz: String = "" ) enum class SstvStatus { Idle, Recording } @@ -97,4 +98,7 @@ sealed interface RadarAction { data object SstvReset : RadarAction data class SstvSelectMode(val modeName: String) : RadarAction data class SstvPermissionResult(val granted: Boolean) : RadarAction + + // Calculator actions + data class ChangeCalculatorOffset(val offsetKHz: String) : RadarAction } diff --git a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarViewModel.kt b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarViewModel.kt index 6ddd57d2..55e4ec45 100644 --- a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarViewModel.kt +++ b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarViewModel.kt @@ -260,7 +260,17 @@ class RadarViewModel( // Compute toggle state before the update so we don't read post-update value val isTogglingOff = _uiState.value.transceivers.selectedUuid == action.uuid val newUuid = if (isTogglingOff) null else action.uuid - _uiState.update { it.copy(transceivers = it.transceivers.copy(selectedUuid = newUuid)) } + // Load the saved offset for the newly selected satellite + val offsetKHz = if (!isTogglingOff) { + transponders.find { it.uuid == action.uuid } + ?.catnum?.let { settingsRepo.getSatelliteOffset(it) } ?: "" + } else "" + _uiState.update { + it.copy( + transceivers = it.transceivers.copy(selectedUuid = newUuid), + calculatorOffsetKHz = offsetKHz + ) + } // Only update the tracking service when selecting a different transponder to // avoid resetting a user-adjusted TX base on re-expand if (!isTogglingOff) { @@ -309,6 +319,15 @@ class RadarViewModel( sstvDecoder?.clearPixels() _uiState.update { it.copy(sstv = it.sstv.copy(currentFrame = null)) } } + is RadarAction.ChangeCalculatorOffset -> { + val catnum = _uiState.value.transceivers.selectedUuid?.let { uuid -> + transponders.find { it.uuid == uuid }?.catnum + } + if (catnum != null) { + settingsRepo.setSatelliteOffset(catnum, action.offsetKHz) + } + _uiState.update { it.copy(calculatorOffsetKHz = action.offsetKHz) } + } } } diff --git a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/TransceiversPage.kt b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/TransceiversPage.kt index cea8114a..81969568 100644 --- a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/TransceiversPage.kt +++ b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/TransceiversPage.kt @@ -128,6 +128,7 @@ fun CalculatorPage( selectedUuid: String?, orbitalPos: OrbitalPos?, onAction: (RadarAction) -> Unit, + calculatorOffsetKHz: String = "", modifier: Modifier = Modifier ) { val calculatorTransceivers = remember(transceivers) { @@ -180,6 +181,8 @@ fun CalculatorPage( DopplerFrequencyCalculator( transponder = selectedTransceiver, orbitalPos = orbitalPos, + offsetKHz = calculatorOffsetKHz, + onOffsetChange = { onAction(RadarAction.ChangeCalculatorOffset(it)) }, modifier = Modifier.fillMaxWidth() ) } @@ -521,6 +524,8 @@ private enum class EditedField { TX, PASSBAND, RX } private fun DopplerFrequencyCalculator( transponder: SatRadio, orbitalPos: OrbitalPos?, + offsetKHz: String = "", + onOffsetChange: (String) -> Unit = {}, modifier: Modifier = Modifier ) { if (orbitalPos == null || !DopplerFrequencyCalculator.isLinearTransponder(transponder)) return @@ -529,7 +534,6 @@ private fun DopplerFrequencyCalculator( var txFrequencyHz by remember { mutableStateOf(0L) } var rxFrequencyHz by remember { mutableStateOf(0L) } var passbandPosition by remember { mutableStateOf(0.5f) } - var offsetKHz by remember { mutableStateOf("") } var stepSizeKHz by remember { mutableIntStateOf(1) } val offsetHz = offsetKHz.toDoubleOrNull()?.let { it * 1000 }?.toLong() ?: 0L @@ -712,7 +716,7 @@ private fun DopplerFrequencyCalculator( ) { BasicTextField( value = offsetKHz, - onValueChange = { offsetKHz = it }, + onValueChange = onOffsetChange, singleLine = true, textStyle = TextStyle( fontSize = 16.sp,