diff --git a/app/src/main/java/com/rtbishop/look4sat/MainScreen.kt b/app/src/main/java/com/rtbishop/look4sat/MainScreen.kt index 2d0d0676..99558b0d 100644 --- a/app/src/main/java/com/rtbishop/look4sat/MainScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/MainScreen.kt @@ -80,7 +80,7 @@ fun MainScreen() { val fadeTransition = fadeIn(animationSpec = tween(350)) togetherWith fadeOut(animationSpec = tween(350)) // val slideInTransition = slideInHorizontally(initialOffsetX = { it }) togetherWith scaleOut(targetScale = 0.9f) // val slideOutTransition = scaleIn(initialScale = 0.9f) togetherWith slideOutHorizontally(targetOffsetX = { it }) - val navItems = listOf(Screen.Satellites, Screen.Passes, Screen.Radar(), Screen.Map, Screen.Settings) + val navItems = listOf(Screen.Satellites, Screen.Passes, Screen.Radar, Screen.Map, Screen.Settings) val context = LocalContext.current val container = (context.applicationContext as IContainerProvider).getMainContainer() @@ -138,25 +138,15 @@ fun MainScreen() { } entry { PassesDestination { catNum, aosTime -> - backStack.add(Screen.Radar(catNum, aosTime)) + container.satelliteRepo.selectPass(catNum, aosTime) + backStack.add(Screen.Radar) } } - entry { route -> - RadarDestination( - catNum = route.catNum, - aosTime = route.aosTime, - navigateUp = navigateBack, - navigateToRadioControl = { catNum, aosTime -> - backStack.add(Screen.RadioControl(catNum, aosTime)) - } - ) + entry { + RadarDestination(navigateToRadioControl = { backStack.add(Screen.RadioControl) }) } - entry { route -> - RadioControlDestination( - catNum = route.catNum, - aosTime = route.aosTime, - navigateUp = navigateBack - ) + entry { + RadioControlDestination(navigateUp = navigateBack) } entry { MapDestination() @@ -184,7 +174,8 @@ fun MainScreen() { .clickable { val pass = trackingState.currentPass if (pass != null) { - backStack.add(Screen.RadioControl(pass.catNum, pass.aosTime)) + container.satelliteRepo.selectPass(pass.catNum, pass.aosTime) + backStack.add(Screen.RadioControl) } } .padding(horizontal = 12.dp, vertical = 6.dp) diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SatelliteRepo.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SatelliteRepo.kt index 33fdaa14..a3aad153 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SatelliteRepo.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SatelliteRepo.kt @@ -52,6 +52,13 @@ class SatelliteRepo( private val _satellites = MutableStateFlow>(emptyList()) override val satellites: StateFlow> = _satellites + private val _selectedPass = MutableStateFlow(0 to 0L) + override val selectedPass: StateFlow> = _selectedPass + + override fun selectPass(catNum: Int, aosTime: Long) { + _selectedPass.value = catNum to aosTime + } + override suspend fun getRadiosWithId(id: Int) = localStorage.getRadiosWithId(id) override suspend fun initRepository() = withContext(dispatcher) { diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/repository/ISatelliteRepo.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/repository/ISatelliteRepo.kt index fe8d4258..5ab4ba17 100644 --- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/repository/ISatelliteRepo.kt +++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/repository/ISatelliteRepo.kt @@ -34,6 +34,12 @@ interface ISatelliteRepo { /** Whether the repo is currently calculating passes. */ val isCalculating: StateFlow + /** Currently selected pass (catNum + aosTime), persisted across screen navigations. */ + val selectedPass: StateFlow> + + /** Set the currently selected pass. */ + fun selectPass(catNum: Int, aosTime: Long) + /** Load satellite objects from DB based on the current selection. */ suspend fun initRepository() diff --git a/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/Navigation.kt b/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/Navigation.kt index a7bae8d0..522251cb 100644 --- a/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/Navigation.kt +++ b/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/Navigation.kt @@ -30,10 +30,10 @@ sealed class Screen(val iconResId: Int, val titleResId: Int) : NavKey { data object Passes : Screen(R.drawable.ic_passes, R.string.nav_pass) @Serializable - data class Radar(val catNum: Int = 0, val aosTime: Long = 0L) : Screen(R.drawable.ic_radar, R.string.nav_radar) + data object Radar : Screen(R.drawable.ic_radar, R.string.nav_radar) @Serializable - data class RadioControl(val catNum: Int = 0, val aosTime: Long = 0L) : Screen(0, 0) + data object RadioControl : Screen(0, 0) @Serializable data object Map : Screen(R.drawable.ic_map, R.string.nav_map) diff --git a/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapScreen.kt b/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapScreen.kt index 042964ab..3ed234d4 100644 --- a/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapScreen.kt +++ b/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapScreen.kt @@ -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) diff --git a/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapViewModel.kt b/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapViewModel.kt index e609919c..bea4f76a 100644 --- a/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapViewModel.kt +++ b/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapViewModel.kt @@ -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) { diff --git a/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesScreen.kt b/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesScreen.kt index b25547c9..40114dfe 100644 --- a/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesScreen.kt +++ b/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesScreen.kt @@ -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) } 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 c48b0aff..52e3c479 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 @@ -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) { 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 a43c9354..cc8f219f 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 @@ -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, diff --git a/feature/radiocontrol/src/main/java/com/rtbishop/look4sat/feature/radiocontrol/RadioControlScreen.kt b/feature/radiocontrol/src/main/java/com/rtbishop/look4sat/feature/radiocontrol/RadioControlScreen.kt index c3d5c112..3603d74a 100644 --- a/feature/radiocontrol/src/main/java/com/rtbishop/look4sat/feature/radiocontrol/RadioControlScreen.kt +++ b/feature/radiocontrol/src/main/java/com/rtbishop/look4sat/feature/radiocontrol/RadioControlScreen.kt @@ -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) } diff --git a/feature/radiocontrol/src/main/java/com/rtbishop/look4sat/feature/radiocontrol/RadioControlViewModel.kt b/feature/radiocontrol/src/main/java/com/rtbishop/look4sat/feature/radiocontrol/RadioControlViewModel.kt index 187950ad..db9a8a0e 100644 --- a/feature/radiocontrol/src/main/java/com/rtbishop/look4sat/feature/radiocontrol/RadioControlViewModel.kt +++ b/feature/radiocontrol/src/main/java/com/rtbishop/look4sat/feature/radiocontrol/RadioControlViewModel.kt @@ -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 diff --git a/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesScreen.kt b/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesScreen.kt index 4d2a1764..d7b9e7d7 100644 --- a/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesScreen.kt +++ b/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesScreen.kt @@ -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) } 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 777f38c8..2909bec9 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 @@ -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) }