Added consistent pass selection to Radar and Map screens

This commit is contained in:
Arty Bishop committed 2026-05-25 18:30:49 +01:00
1 parent 8a2339cfbf
commit a39b1716e7
13 files changed
+62 -107

No files matched your search

@@ -123,10 +123,7 @@ private val moonIconPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
fun MapDestination() {
val context = LocalContext.current
val container = (context.applicationContext as IContainerProvider).getMainContainer()
val viewModel = viewModel(
modelClass = MapViewModel::class.java,
factory = MapViewModel.factory(container)
)
val viewModel: MapViewModel = viewModel(factory = MapViewModel.factory(container))
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val mapView = rememberMapViewWithLifecycle()
MapScreen(uiState, viewModel::onAction, mapView)
@@ -76,7 +76,8 @@ class MapViewModel(
_uiState.update { it.copy(isUtc = settings.stateOfUtc) }
}
}
selectDefaultSatellite(-1)
val (selectedCatNum, _) = satelliteRepo.selectedPass.value
selectDefaultSatellite(if (selectedCatNum != 0) selectedCatNum else -1)
}
fun onAction(action: MapAction) {
@@ -86,10 +86,7 @@ import java.util.TimeZone
fun PassesDestination(navigateToRadar: (Int, Long) -> Unit) {
val context = LocalContext.current
val container = (context.applicationContext as IContainerProvider).getMainContainer()
val viewModel = viewModel(
modelClass = PassesViewModel::class.java,
factory = PassesViewModel.factory(container)
)
val viewModel: PassesViewModel = viewModel(factory = PassesViewModel.factory(container))
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
PassesScreen(uiState, viewModel::onAction, navigateToRadar)
}
@@ -42,7 +42,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -77,42 +76,23 @@ import com.rtbishop.look4sat.core.presentation.isVerticalLayout
import com.rtbishop.look4sat.core.presentation.layoutPadding
@Composable
fun RadarDestination(
catNum: Int = 0,
aosTime: Long = 0L,
navigateUp: () -> Unit,
navigateToRadioControl: (Int, Long) -> Unit = { _, _ -> }
) {
fun RadarDestination(navigateToRadioControl: () -> Unit = {}) {
val context = LocalContext.current
val container = (context.applicationContext as IContainerProvider).getMainContainer()
val viewModel = viewModel(
modelClass = RadarViewModel::class.java,
key = "$catNum-$aosTime",
factory = RadarViewModel.factory(catNum, aosTime, container)
)
val viewModel: RadarViewModel = viewModel(factory = RadarViewModel.factory(container))
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
RadarScreen(uiState, viewModel::onAction, navigateUp, navigateToRadioControl)
RadarScreen(uiState, viewModel::onAction, navigateToRadioControl)
}
@Composable
private fun RadarScreen(
uiState: RadarState,
onAction: (RadarAction) -> Unit,
navigateUp: () -> Unit,
navigateToRadioControl: (Int, Long) -> Unit
navigateToRadioControl: () -> Unit
) {
val upcomingPass = uiState.currentPass ?: getDefaultPass()
LaunchedEffect(uiState.isLos) { if (uiState.isLos) navigateUp() }
val addToCalendar: () -> Unit = {
uiState.currentPass?.let { pass ->
onAction(RadarAction.AddToCalendar(pass.name, pass.aosTime, pass.losTime))
}
}
val openRadioControl: () -> Unit = {
uiState.currentPass?.let { pass ->
navigateToRadioControl(pass.catNum, pass.aosTime)
}
uiState.currentPass?.let { onAction(RadarAction.AddToCalendar(it.name, it.aosTime, it.losTime)) }
}
Column(
modifier = Modifier
@@ -125,7 +105,7 @@ private fun RadarScreen(
TopBar {
IconCard(action = addToCalendar, resId = R.drawable.ic_calendar)
TimerRow(timeString = uiState.currentTime, isTimeAos = uiState.isTimeAos)
IconCard(action = openRadioControl, resId = R.drawable.ic_radios)
IconCard(action = navigateToRadioControl, resId = R.drawable.ic_radios)
}
TopBar { NextPassRow(pass = upcomingPass, isUtc = uiState.isUtc) }
} else {
@@ -133,7 +113,7 @@ private fun RadarScreen(
IconCard(action = addToCalendar, resId = R.drawable.ic_calendar)
TimerRow(timeString = uiState.currentTime, isTimeAos = uiState.isTimeAos)
NextPassRow(pass = upcomingPass, modifier = Modifier.weight(1f), isUtc = uiState.isUtc)
IconCard(action = openRadioControl, resId = R.drawable.ic_radios)
IconCard(action = navigateToRadioControl, resId = R.drawable.ic_radios)
}
}
if (isVertical) {
@@ -43,8 +43,6 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
class RadarViewModel(
private val catNum: Int,
private val aosTime: Long,
private val bluetoothReporter: IReporter,
private val networkReporter: IReporter,
private val satelliteRepo: ISatelliteRepo,
@@ -85,26 +83,24 @@ class RadarViewModel(
// Resolve which pass we're tracking and start the tick loop
viewModelScope.launch {
val passes = satelliteRepo.passes.value
val currentPass = passes.find { it.catNum == catNum && it.aosTime == aosTime }
?: passes.firstOrNull()
currentPass?.let { satPass ->
_uiState.update { it.copy(currentPass = satPass) }
val transmitters = satelliteRepo.getRadiosWithId(satPass.catNum)
val (catNum, aosTime) = satelliteRepo.selectedPass.value
val satPass = passes.find { it.catNum == catNum && it.aosTime == aosTime } ?: passes.firstOrNull()
satPass?.let { pass ->
_uiState.update { it.copy(currentPass = pass) }
val transmitters = satelliteRepo.getRadiosWithId(pass.catNum)
// Compute track once (it doesn't change for a given pass)
if (!satPass.isDeepSpace) {
val track = satelliteRepo.getTrack(
satPass.orbitalObject, stationPos, satPass.aosTime, satPass.losTime
)
if (!pass.isDeepSpace) {
val track = satelliteRepo.getTrack(pass.orbitalObject, stationPos, pass.aosTime, pass.losTime)
_uiState.update { it.copy(satTrack = track) }
}
// Tick loop — position, timer, and radio updates every second
while (isActive) {
val timeNow = System.currentTimeMillis()
val pos = satelliteRepo.getPosition(satPass.orbitalObject, stationPos, timeNow)
val pos = satelliteRepo.getPosition(pass.orbitalObject, stationPos, timeNow)
val sunPos = CelestialComputer.getSunPosition(stationPos, timeNow)
val moonPos = CelestialComputer.getMoonPosition(stationPos, timeNow)
val (time, isAos) = computeTimer(satPass.isDeepSpace, satPass.aosTime, satPass.losTime, timeNow)
val isLos = !satPass.isDeepSpace && timeNow > satPass.losTime
val (time, isAos) = computeTimer(pass.isDeepSpace, pass.aosTime, pass.losTime, timeNow)
val isLos = !pass.isDeepSpace && timeNow > pass.losTime
_uiState.update {
it.copy(
currentTime = time,
@@ -115,7 +111,7 @@ class RadarViewModel(
moonPosition = moonPos
)
}
processRadios(transmitters, satPass.orbitalObject, timeNow)
processRadios(transmitters, pass.orbitalObject, timeNow)
sendPassData(pos)
delay(1000)
}
@@ -213,11 +209,9 @@ class RadarViewModel(
}
companion object {
fun factory(catNum: Int, aosTime: Long, container: IMainContainer) = viewModelFactory {
fun factory(container: IMainContainer) = viewModelFactory {
initializer {
RadarViewModel(
catNum = catNum,
aosTime = aosTime,
bluetoothReporter = container.provideBluetoothReporter(),
networkReporter = container.provideNetworkReporter(),
satelliteRepo = container.satelliteRepo,
@@ -66,14 +66,10 @@ import com.rtbishop.look4sat.core.presentation.layoutPadding
import java.util.Locale
@Composable
fun RadioControlDestination(catNum: Int = 0, aosTime: Long = 0L, navigateUp: () -> Unit) {
fun RadioControlDestination(navigateUp: () -> Unit) {
val context = LocalContext.current
val container = (context.applicationContext as IContainerProvider).getMainContainer()
val viewModel = viewModel(
modelClass = RadioControlViewModel::class.java,
key = "$catNum-$aosTime",
factory = RadioControlViewModel.factory(catNum, aosTime, container)
)
val viewModel: RadioControlViewModel = viewModel(factory = RadioControlViewModel.factory(container))
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
RadioControlScreen(uiState, viewModel::onAction, navigateUp)
}
@@ -38,8 +38,6 @@ import kotlinx.coroutines.launch
import java.util.Locale
class RadioControlViewModel(
private val catNum: Int,
private val aosTime: Long,
private val trackingService: IRadioTrackingService,
private val satelliteRepo: ISatelliteRepo,
settingsRepo: ISettingsRepo
@@ -73,27 +71,23 @@ class RadioControlViewModel(
// Resolve pass and load transponders
viewModelScope.launch {
val passes = satelliteRepo.passes.value
val pass = passes.find { it.catNum == catNum && it.aosTime == aosTime }
?: passes.firstOrNull()
currentPass = pass
pass?.let { satPass ->
val allRadios = satelliteRepo.getRadiosWithId(satPass.catNum)
val (catNum, aosTime) = satelliteRepo.selectedPass.value
val satPass = passes.find { it.catNum == catNum && it.aosTime == aosTime } ?: passes.firstOrNull()
currentPass = satPass
satPass?.let { pass ->
val allRadios = satelliteRepo.getRadiosWithId(pass.catNum)
transponders = allRadios.filter { it.downlinkLow != null }
_uiState.update {
it.copy(currentPass = satPass, transponders = transponders)
}
_uiState.update { it.copy(currentPass = pass, transponders = transponders) }
// If service is already tracking this pass, sync the selected transponder
val svcState = trackingService.state.value
if (svcState.isActive && svcState.currentPass?.catNum == satPass.catNum) {
_uiState.update {
it.copy(selectedTransponderUuid = svcState.selectedTransponder?.uuid)
}
if (svcState.isActive && svcState.currentPass?.catNum == pass.catNum) {
_uiState.update { it.copy(selectedTransponderUuid = svcState.selectedTransponder?.uuid) }
}
// Tick loop — timer and satellite position updates every second
while (isActive) {
val timeNow = System.currentTimeMillis()
val pos = satelliteRepo.getPosition(satPass.orbitalObject, stationPos, timeNow)
val (timeStr, isAos) = computeTimer(satPass.isDeepSpace, satPass.aosTime, satPass.losTime, timeNow)
val pos = satelliteRepo.getPosition(pass.orbitalObject, stationPos, timeNow)
val (timeStr, isAos) = computeTimer(pass.isDeepSpace, pass.aosTime, pass.losTime, timeNow)
_uiState.update { state ->
state.copy(
currentTime = timeStr,
@@ -187,11 +181,9 @@ class RadioControlViewModel(
return String.format(Locale.ENGLISH, "%d.%03d.%03d", mhz, khz, hz)
}
fun factory(catNum: Int, aosTime: Long, container: IMainContainer) = viewModelFactory {
fun factory(container: IMainContainer) = viewModelFactory {
initializer {
RadioControlViewModel(
catNum = catNum,
aosTime = aosTime,
trackingService = container.radioTrackingService,
satelliteRepo = container.satelliteRepo,
settingsRepo = container.settingsRepo
@@ -75,10 +75,7 @@ import com.rtbishop.look4sat.core.presentation.layoutPadding
fun SatellitesDestination(navigateUp: () -> Unit) {
val context = LocalContext.current
val container = (context.applicationContext as IContainerProvider).getMainContainer()
val viewModel = viewModel(
modelClass = SatellitesViewModel::class.java,
factory = SatellitesViewModel.factory(container)
)
val viewModel: SatellitesViewModel = viewModel(factory = SatellitesViewModel.factory(container))
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
SatellitesScreen(uiState, viewModel::onAction, navigateUp)
}
@@ -81,10 +81,7 @@ import java.util.Locale
fun SettingsDestination() {
val context = LocalContext.current
val container = (context.applicationContext as IContainerProvider).getMainContainer()
val viewModel = viewModel(
modelClass = SettingsViewModel::class.java,
factory = SettingsViewModel.factory(container)
)
val viewModel: SettingsViewModel = viewModel(factory = SettingsViewModel.factory(container))
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
SettingsScreen(uiState, viewModel::onAction)
}