mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
feat: remember per-satellite doppler offset in calculator
Store the last entered offset (kHz) per satellite in SharedPreferences via SettingsRepo, and restore it when the linear transponder calculator is shown for that satellite. Follows the project's MVI/clean architecture: - ISettingsRepo: getSatelliteOffset / setSatelliteOffset - SettingsRepo: backed by SharedPreferences - RadarState: calculatorOffsetKHz field - RadarAction: ChangeCalculatorOffset - RadarViewModel: loads offset on SelectTransmitter, saves on change - CalculatorPage/DopplerFrequencyCalculator: receive offset via params instead of accessing SharedPreferences directly from the UI layer.
This commit is contained in:
1 parent
8b5960282f
commit
4aada90b76
8 files changed
+70
-6
No files matched your search
@@ -37,6 +37,7 @@ import com.rtbishop.look4sat.core.domain.utility.round
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
class SettingsRepo(
|
class SettingsRepo(
|
||||||
private val locationManager: LocationManager,
|
private val locationManager: LocationManager,
|
||||||
@@ -417,5 +418,27 @@ class SettingsRepo(
|
|||||||
baudRate = preferences.getInt(keyRadioBaudRate, 4800),
|
baudRate = preferences.getInt(keyRadioBaudRate, 4800),
|
||||||
splitMode = preferences.getBoolean(keyRadioSplitMode, false)
|
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) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -213,6 +213,10 @@ private class FakeSettingsRepo(dataSources: DataSourcesSettings = defaultDataSou
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun updateRadioControlSettings(settings: RadioControlSettings) = Unit
|
override fun updateRadioControlSettings(settings: RadioControlSettings) = Unit
|
||||||
|
|
||||||
|
override fun getSatelliteOffset(catnum: Int): String = ""
|
||||||
|
|
||||||
|
override fun setSatelliteOffset(catnum: Int, offset: String) = Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun defaultDataSourcesSettings(): DataSourcesSettings {
|
private fun defaultDataSourcesSettings(): DataSourcesSettings {
|
||||||
|
|||||||
+4
@@ -167,6 +167,10 @@ class SelectionRepoTest {
|
|||||||
override fun updateDataSourcesSettings(settings: DataSourcesSettings) = Unit
|
override fun updateDataSourcesSettings(settings: DataSourcesSettings) = Unit
|
||||||
|
|
||||||
override fun updateRadioControlSettings(settings: RadioControlSettings) = Unit
|
override fun updateRadioControlSettings(settings: RadioControlSettings) = Unit
|
||||||
|
|
||||||
|
override fun getSatelliteOffset(catnum: Int): String = ""
|
||||||
|
|
||||||
|
override fun setSatelliteOffset(catnum: Int, offset: String) = Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
@@ -75,4 +75,9 @@ interface ISettingsRepo {
|
|||||||
val radioControlSettings: StateFlow<RadioControlSettings>
|
val radioControlSettings: StateFlow<RadioControlSettings>
|
||||||
fun updateRadioControlSettings(settings: RadioControlSettings)
|
fun updateRadioControlSettings(settings: RadioControlSettings)
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
//region # Per-satellite calculator offset settings
|
||||||
|
fun getSatelliteOffset(catnum: Int): String
|
||||||
|
fun setSatelliteOffset(catnum: Int, offset: String)
|
||||||
|
//endregion
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,8 @@ private fun PagerCard(
|
|||||||
transceivers = uiState.transceivers.transmitters,
|
transceivers = uiState.transceivers.transmitters,
|
||||||
selectedUuid = uiState.transceivers.selectedUuid,
|
selectedUuid = uiState.transceivers.selectedUuid,
|
||||||
orbitalPos = uiState.orbitalPos,
|
orbitalPos = uiState.orbitalPos,
|
||||||
onAction = onAction
|
onAction = onAction,
|
||||||
|
calculatorOffsetKHz = uiState.calculatorOffsetKHz
|
||||||
)
|
)
|
||||||
RadarPage.Sstv -> SstvPage(
|
RadarPage.Sstv -> SstvPage(
|
||||||
sstv = uiState.sstv,
|
sstv = uiState.sstv,
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ data class RadarState(
|
|||||||
val moonPosition: CelestialComputer.MoonPosition? = null,
|
val moonPosition: CelestialComputer.MoonPosition? = null,
|
||||||
val transceivers: TransceiverSubState = TransceiverSubState(),
|
val transceivers: TransceiverSubState = TransceiverSubState(),
|
||||||
val radioControl: RadioControlSubState = RadioControlSubState(),
|
val radioControl: RadioControlSubState = RadioControlSubState(),
|
||||||
val sstv: SstvSubState = SstvSubState()
|
val sstv: SstvSubState = SstvSubState(),
|
||||||
|
val calculatorOffsetKHz: String = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class SstvStatus { Idle, Recording }
|
enum class SstvStatus { Idle, Recording }
|
||||||
@@ -97,4 +98,7 @@ sealed interface RadarAction {
|
|||||||
data object SstvReset : RadarAction
|
data object SstvReset : RadarAction
|
||||||
data class SstvSelectMode(val modeName: String) : RadarAction
|
data class SstvSelectMode(val modeName: String) : RadarAction
|
||||||
data class SstvPermissionResult(val granted: Boolean) : RadarAction
|
data class SstvPermissionResult(val granted: Boolean) : RadarAction
|
||||||
|
|
||||||
|
// Calculator actions
|
||||||
|
data class ChangeCalculatorOffset(val offsetKHz: String) : RadarAction
|
||||||
}
|
}
|
||||||
@@ -260,7 +260,17 @@ class RadarViewModel(
|
|||||||
// Compute toggle state before the update so we don't read post-update value
|
// Compute toggle state before the update so we don't read post-update value
|
||||||
val isTogglingOff = _uiState.value.transceivers.selectedUuid == action.uuid
|
val isTogglingOff = _uiState.value.transceivers.selectedUuid == action.uuid
|
||||||
val newUuid = if (isTogglingOff) null else 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
|
// Only update the tracking service when selecting a different transponder to
|
||||||
// avoid resetting a user-adjusted TX base on re-expand
|
// avoid resetting a user-adjusted TX base on re-expand
|
||||||
if (!isTogglingOff) {
|
if (!isTogglingOff) {
|
||||||
@@ -309,6 +319,15 @@ class RadarViewModel(
|
|||||||
sstvDecoder?.clearPixels()
|
sstvDecoder?.clearPixels()
|
||||||
_uiState.update { it.copy(sstv = it.sstv.copy(currentFrame = null)) }
|
_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) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ fun CalculatorPage(
|
|||||||
selectedUuid: String?,
|
selectedUuid: String?,
|
||||||
orbitalPos: OrbitalPos?,
|
orbitalPos: OrbitalPos?,
|
||||||
onAction: (RadarAction) -> Unit,
|
onAction: (RadarAction) -> Unit,
|
||||||
|
calculatorOffsetKHz: String = "",
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val calculatorTransceivers = remember(transceivers) {
|
val calculatorTransceivers = remember(transceivers) {
|
||||||
@@ -180,6 +181,8 @@ fun CalculatorPage(
|
|||||||
DopplerFrequencyCalculator(
|
DopplerFrequencyCalculator(
|
||||||
transponder = selectedTransceiver,
|
transponder = selectedTransceiver,
|
||||||
orbitalPos = orbitalPos,
|
orbitalPos = orbitalPos,
|
||||||
|
offsetKHz = calculatorOffsetKHz,
|
||||||
|
onOffsetChange = { onAction(RadarAction.ChangeCalculatorOffset(it)) },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -521,6 +524,8 @@ private enum class EditedField { TX, PASSBAND, RX }
|
|||||||
private fun DopplerFrequencyCalculator(
|
private fun DopplerFrequencyCalculator(
|
||||||
transponder: SatRadio,
|
transponder: SatRadio,
|
||||||
orbitalPos: OrbitalPos?,
|
orbitalPos: OrbitalPos?,
|
||||||
|
offsetKHz: String = "",
|
||||||
|
onOffsetChange: (String) -> Unit = {},
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
if (orbitalPos == null || !DopplerFrequencyCalculator.isLinearTransponder(transponder)) return
|
if (orbitalPos == null || !DopplerFrequencyCalculator.isLinearTransponder(transponder)) return
|
||||||
@@ -529,7 +534,6 @@ private fun DopplerFrequencyCalculator(
|
|||||||
var txFrequencyHz by remember { mutableStateOf(0L) }
|
var txFrequencyHz by remember { mutableStateOf(0L) }
|
||||||
var rxFrequencyHz by remember { mutableStateOf(0L) }
|
var rxFrequencyHz by remember { mutableStateOf(0L) }
|
||||||
var passbandPosition by remember { mutableStateOf(0.5f) }
|
var passbandPosition by remember { mutableStateOf(0.5f) }
|
||||||
var offsetKHz by remember { mutableStateOf("") }
|
|
||||||
var stepSizeKHz by remember { mutableIntStateOf(1) }
|
var stepSizeKHz by remember { mutableIntStateOf(1) }
|
||||||
|
|
||||||
val offsetHz = offsetKHz.toDoubleOrNull()?.let { it * 1000 }?.toLong() ?: 0L
|
val offsetHz = offsetKHz.toDoubleOrNull()?.let { it * 1000 }?.toLong() ?: 0L
|
||||||
@@ -712,7 +716,7 @@ private fun DopplerFrequencyCalculator(
|
|||||||
) {
|
) {
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
value = offsetKHz,
|
value = offsetKHz,
|
||||||
onValueChange = { offsetKHz = it },
|
onValueChange = onOffsetChange,
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
textStyle = TextStyle(
|
textStyle = TextStyle(
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
|
|||||||
Reference in new issue
Block a user