mirror of
https://github.com/rt-bishop/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Added consistent pass selection to Radar and Map screens
This commit is contained in:
1 parent
8a2339cfbf
commit
a39b1716e7
13 files changed
+62
-107
No files matched your search
@@ -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<Screen.Passes> {
|
||||
PassesDestination { catNum, aosTime ->
|
||||
backStack.add(Screen.Radar(catNum, aosTime))
|
||||
container.satelliteRepo.selectPass(catNum, aosTime)
|
||||
backStack.add(Screen.Radar)
|
||||
}
|
||||
}
|
||||
entry<Screen.Radar> { route ->
|
||||
RadarDestination(
|
||||
catNum = route.catNum,
|
||||
aosTime = route.aosTime,
|
||||
navigateUp = navigateBack,
|
||||
navigateToRadioControl = { catNum, aosTime ->
|
||||
backStack.add(Screen.RadioControl(catNum, aosTime))
|
||||
entry<Screen.Radar> {
|
||||
RadarDestination(navigateToRadioControl = { backStack.add(Screen.RadioControl) })
|
||||
}
|
||||
)
|
||||
}
|
||||
entry<Screen.RadioControl> { route ->
|
||||
RadioControlDestination(
|
||||
catNum = route.catNum,
|
||||
aosTime = route.aosTime,
|
||||
navigateUp = navigateBack
|
||||
)
|
||||
entry<Screen.RadioControl> {
|
||||
RadioControlDestination(navigateUp = navigateBack)
|
||||
}
|
||||
entry<Screen.Map> {
|
||||
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)
|
||||
|
||||
@@ -52,6 +52,13 @@ class SatelliteRepo(
|
||||
private val _satellites = MutableStateFlow<List<OrbitalObject>>(emptyList())
|
||||
override val satellites: StateFlow<List<OrbitalObject>> = _satellites
|
||||
|
||||
private val _selectedPass = MutableStateFlow(0 to 0L)
|
||||
override val selectedPass: StateFlow<Pair<Int, Long>> = _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) {
|
||||
|
||||
+6
@@ -34,6 +34,12 @@ interface ISatelliteRepo {
|
||||
/** Whether the repo is currently calculating passes. */
|
||||
val isCalculating: StateFlow<Boolean>
|
||||
|
||||
/** Currently selected pass (catNum + aosTime), persisted across screen navigations. */
|
||||
val selectedPass: StateFlow<Pair<Int, Long>>
|
||||
|
||||
/** Set the currently selected pass. */
|
||||
fun selectPass(catNum: Int, aosTime: Long)
|
||||
|
||||
/** Load satellite objects from DB based on the current selection. */
|
||||
suspend fun initRepository()
|
||||
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
+2
-6
@@ -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)
|
||||
}
|
||||
|
||||
+11
-19
@@ -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
|
||||
|
||||
+1
-4
@@ -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)
|
||||
}
|
||||
|
||||
+1
-4
@@ -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)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user