From aadc285d7f71028b516ba26d631089d08c944e1b Mon Sep 17 00:00:00 2001 From: Arty Bishop Date: Fri, 10 Apr 2026 11:50:22 +0100 Subject: [PATCH] Added multiple tweaks to Satellites, Passes and Map screens --- .../core/domain/utility/Extensions.kt | 7 +- .../look4sat/feature/map/MapScreen.kt | 19 +- .../rtbishop/look4sat/feature/map/MapState.kt | 23 +- .../look4sat/feature/map/MapViewModel.kt | 31 ++- .../look4sat/feature/passes/PassesDialog.kt | 65 +++-- .../look4sat/feature/passes/PassesScreen.kt | 237 +++++++++--------- .../look4sat/feature/passes/PassesState.kt | 23 +- .../feature/passes/PassesViewModel.kt | 61 ++--- .../feature/satellites/SatellitesDialog.kt | 16 +- .../feature/satellites/SatellitesScreen.kt | 171 ++++++++----- .../feature/satellites/SatellitesState.kt | 13 +- .../feature/satellites/SatellitesViewModel.kt | 14 +- 12 files changed, 352 insertions(+), 328 deletions(-) diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/utility/Extensions.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/utility/Extensions.kt index 0e1eaa8f..410e8bf7 100644 --- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/utility/Extensions.kt +++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/utility/Extensions.kt @@ -21,10 +21,11 @@ import java.util.Locale import java.util.concurrent.TimeUnit fun Long.toTimerString(): String { + val millis = coerceAtLeast(0L) val format = "%02d:%02d:%02d" - val hours = TimeUnit.MILLISECONDS.toHours(this) - val minutes = TimeUnit.MILLISECONDS.toMinutes(this) % 60 - val seconds = TimeUnit.MILLISECONDS.toSeconds(this) % 60 + val hours = TimeUnit.MILLISECONDS.toHours(millis) + val minutes = TimeUnit.MILLISECONDS.toMinutes(millis) % 60 + val seconds = TimeUnit.MILLISECONDS.toSeconds(millis) % 60 return String.format(Locale.ENGLISH, format, hours, minutes, seconds) } 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 cf17593f..51142805 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 @@ -115,15 +115,12 @@ fun NavGraphBuilder.mapDestination() { val viewModel = viewModel(MapViewModel::class.java, factory = MapViewModel.Factory) val uiState by viewModel.uiState.collectAsStateWithLifecycle() val mapView = rememberMapViewWithLifecycle() - MapScreen(uiState, mapView) + MapScreen(uiState, viewModel::onAction, mapView) } } @Composable -private fun MapScreen(uiState: MapState, mapView: MapView) { - val onItemClick = { item: OrbitalObject -> uiState.sendAction(MapAction.SelectItem(item)) } - val selectPrev = { uiState.sendAction(MapAction.SelectPrev) } - val selectNext = { uiState.sendAction(MapAction.SelectNext) } +private fun MapScreen(uiState: MapState, onAction: (MapAction) -> Unit, mapView: MapView) { val rotateMod = Modifier.rotate(180f) val timeString = uiState.mapData?.aosTime ?: "00:00:00" val isTimeAos = uiState.mapData?.isTimeAos ?: true @@ -136,17 +133,17 @@ private fun MapScreen(uiState: MapState, mapView: MapView) { val isVertical = isVerticalLayout() if (isVertical) { TopBar { - IconCard(action = selectPrev, resId = R.drawable.ic_arrow, modifier = rotateMod) + IconCard(action = { onAction(MapAction.SelectPrev) }, resId = R.drawable.ic_arrow, modifier = rotateMod) TimerRow(timeString = timeString, isTimeAos = isTimeAos) - IconCard(action = selectNext, resId = R.drawable.ic_arrow) + IconCard(action = { onAction(MapAction.SelectNext) }, resId = R.drawable.ic_arrow) } TopBar { NextPassRow(pass = uiState.orbitalPass, isUtc = uiState.isUtc) } } else { TopBar { - IconCard(action = selectPrev, resId = R.drawable.ic_arrow, modifier = rotateMod) + IconCard(action = { onAction(MapAction.SelectPrev) }, resId = R.drawable.ic_arrow, modifier = rotateMod) TimerRow(timeString = timeString, isTimeAos = isTimeAos) NextPassRow(pass = uiState.orbitalPass, modifier = Modifier.weight(1f), isUtc = uiState.isUtc) - IconCard(action = selectNext, resId = R.drawable.ic_arrow) + IconCard(action = { onAction(MapAction.SelectNext) }, resId = R.drawable.ic_arrow) } } ElevatedCard(modifier = Modifier.weight(1f)) { @@ -155,7 +152,7 @@ private fun MapScreen(uiState: MapState, mapView: MapView) { uiState.stationPosition?.let { setStationPosition(it, view) } uiState.track?.let { setSatelliteTrack(it, view) } uiState.footprint?.let { setFootprint(it, view) } - uiState.positions?.let { setPositions(it, view, onItemClick) } + uiState.positions?.let { setPositions(it, view) { item -> onAction(MapAction.SelectItem(item)) } } view.invalidate() } uiState.mapData?.let { mapData -> @@ -369,7 +366,7 @@ private var footprintPoints: ArrayList? = null private fun setFootprint(orbitalPos: OrbitalPos, mapView: MapView) { try { val rangeCircle = orbitalPos.getRangeCircle() - // Lazily initialise the reusable point list and polyline + // Lazily initialize the reusable point list and polyline var pts = footprintPoints if (pts == null || pts.size != rangeCircle.size) { pts = ArrayList(rangeCircle.size) diff --git a/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapState.kt b/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapState.kt index 85adefbd..473af52c 100644 --- a/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapState.kt +++ b/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapState.kt @@ -23,22 +23,21 @@ import com.rtbishop.look4sat.core.domain.predict.OrbitalPass import com.rtbishop.look4sat.core.domain.predict.OrbitalPos data class MapState( - val mapData: MapData?, - val isLightUi: Boolean, - val isUtc: Boolean, - val stationPosition: GeoPos?, + val mapData: MapData? = null, + val isLightUi: Boolean = false, + val isUtc: Boolean = false, + val stationPosition: GeoPos? = null, val orbitalPass: OrbitalPass, - val track: List>?, - val footprint: OrbitalPos?, - val positions: Map?, - val sendAction: (MapAction) -> Unit + val track: List>? = null, + val footprint: OrbitalPos? = null, + val positions: Map? = null ) sealed class MapAction { - data object SelectPrev: MapAction() - data object SelectNext: MapAction() - data class SelectItem(val item: OrbitalObject): MapAction() - data class SelectDefaultItem(val catnum: Int): MapAction() + data object SelectPrev : MapAction() + data object SelectNext : MapAction() + data class SelectItem(val item: OrbitalObject) : MapAction() + data class SelectDefaultItem(val catnum: Int) : MapAction() } data class MapData( 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 c573352e..b67b6b6a 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 @@ -56,15 +56,9 @@ class MapViewModel(private val satelliteRepo: ISatelliteRepo, private val settin private val defaultPass = getDefaultPass() private val _uiState = MutableStateFlow( MapState( - mapData = null, isLightUi = settingsRepo.otherSettings.value.stateOfLightTheme, isUtc = settingsRepo.otherSettings.value.stateOfUtc, - stationPosition = null, - orbitalPass = defaultPass, - track = null, - footprint = null, - positions = null, - sendAction = ::handleAction + orbitalPass = defaultPass ) ) private var allPasses = satelliteRepo.passes.value @@ -83,7 +77,7 @@ class MapViewModel(private val satelliteRepo: ISatelliteRepo, private val settin selectDefaultSatellite(-1) } - private fun handleAction(action: MapAction) { + fun onAction(action: MapAction) { when (action) { MapAction.SelectPrev -> scrollSelection(true) MapAction.SelectNext -> scrollSelection(false) @@ -215,14 +209,19 @@ class MapViewModel(private val satelliteRepo: ISatelliteRepo, private val settin ?.let { satPass -> orbitalPass = satPass if (!satPass.isDeepSpace) { - aosTime = if (date.time < satPass.aosTime) { - val millisBeforeStart = satPass.aosTime.minus(date.time) - isTimeAos = true - millisBeforeStart.toTimerString() - } else { - val millisBeforeEnd = satPass.losTime.minus(date.time) - isTimeAos = false - millisBeforeEnd.toTimerString() + when { + date.time < satPass.aosTime -> { + // Pass hasn't started yet — count down to AOS + isTimeAos = true + aosTime = (satPass.aosTime - date.time).toTimerString() + } + + date.time < satPass.losTime -> { + // Pass is in progress — count down to LOS + isTimeAos = false + aosTime = (satPass.losTime - date.time).toTimerString() + } + // else: pass has ended (losTime <= date.time) — keep default "00:00:00" } } } diff --git a/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesDialog.kt b/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesDialog.kt index 2a9f161b..6630263d 100644 --- a/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesDialog.kt +++ b/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesDialog.kt @@ -32,12 +32,11 @@ import androidx.compose.material3.Checkbox import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Slider -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableDoubleStateOf import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -68,7 +67,7 @@ private fun PassesDialogPreview() { } @Composable -fun PassesDialog(hours: Int, elevation: Double, cancel: () -> Unit, accept: (Int, Double) -> Unit) { +internal fun PassesDialog(hours: Int, elevation: Double, cancel: () -> Unit, accept: (Int, Double) -> Unit) { val hoursValue = remember { mutableIntStateOf(hours) } val elevationValueNew = remember { mutableDoubleStateOf(elevation) } val onAccept = { @@ -140,12 +139,12 @@ private fun RadiosDialogPreview() { } @Composable -fun RadiosDialog(modes: List, cancel: () -> Unit, accept: (List) -> Unit) { - val selected = remember { mutableStateListOf().apply { addAll(modes) } } - val select = { mode: String -> - if (selected.contains(mode)) selected.remove(mode) else selected.add(mode) +internal fun RadiosDialog(modes: List, cancel: () -> Unit, accept: (List) -> Unit) { + val selected = remember { mutableStateOf(modes.toSet()) } + val toggle = { mode: String -> + selected.value = if (mode in selected.value) selected.value - mode else selected.value + mode } - val onAccept = { accept(selected.toList()).also { cancel() } } + val onAccept = { accept(selected.value.toList()).also { cancel() } } SharedDialog(title = stringResource(R.string.pass_modes_title), onCancel = cancel, onAccept = onAccept) { LazyVerticalGrid( columns = GridCells.Adaptive(240.dp), @@ -156,32 +155,30 @@ fun RadiosDialog(modes: List, cancel: () -> Unit, accept: (List) verticalArrangement = Arrangement.spacedBy(1.dp) ) { itemsIndexed(allModes) { index, item -> - Surface { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .background(MaterialTheme.colorScheme.surface) - .clickable { select(item) } - ) { - Text( - text = "${index + 1}).", - modifier = Modifier.padding(start = 16.dp, end = 8.dp), - fontWeight = FontWeight.Normal, - color = MaterialTheme.colorScheme.primary - ) - Text( - text = item, - modifier = Modifier.weight(1f), - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Checkbox( - checked = selected.contains(item), - onCheckedChange = null, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) - ) - } + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .background(MaterialTheme.colorScheme.surface) + .clickable { toggle(item) } + ) { + Text( + text = "${index + 1}).", + modifier = Modifier.padding(start = 16.dp, end = 8.dp), + fontWeight = FontWeight.Normal, + color = MaterialTheme.colorScheme.primary + ) + Text( + text = item, + modifier = Modifier.weight(1f), + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Checkbox( + checked = item in selected.value, + onCheckedChange = null, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) + ) } } } 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 e140939f..34e051e0 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 @@ -35,10 +35,10 @@ import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults @@ -86,27 +86,31 @@ fun NavGraphBuilder.passesDestination(navigateToRadar: (Int, Long) -> Unit) { factory = PassesViewModel.Factory ) val uiState = viewModel.uiState.collectAsStateWithLifecycle().value - PassesScreen(uiState, navigateToRadar) + PassesScreen(uiState, viewModel::onAction, navigateToRadar) } } @Composable -private fun PassesScreen(uiState: PassesState, navigateToRadar: (Int, Long) -> Unit) { - val refreshList = { uiState.takeAction(PassesAction.RefreshPasses) } - val showPassesDialog = { uiState.takeAction(PassesAction.TogglePassesDialog) } - val showRadiosDialog = { uiState.takeAction(PassesAction.ToggleRadiosDialog) } +private fun PassesScreen( + uiState: PassesState, + onAction: (PassesAction) -> Unit, + navigateToRadar: (Int, Long) -> Unit +) { if (uiState.isPassesDialogShown) { PassesDialog( hours = uiState.hours, elevation = uiState.elevation, - cancel = showPassesDialog + cancel = { onAction(PassesAction.TogglePassesDialog) } ) { hours, elevation -> - uiState.takeAction(PassesAction.FilterPasses(hours, elevation)) + onAction(PassesAction.FilterPasses(hours, elevation)) } } if (uiState.isRadiosDialogShown) { - RadiosDialog(modes = uiState.modes, cancel = showRadiosDialog) { modes -> - uiState.takeAction(PassesAction.FilterRadios(modes)) + RadiosDialog( + modes = uiState.modes, + cancel = { onAction(PassesAction.ToggleRadiosDialog) } + ) { modes -> + onAction(PassesAction.FilterRadios(modes)) } } if (uiState.shouldSeeWhatsNew) { @@ -114,7 +118,7 @@ private fun PassesScreen(uiState: PassesState, navigateToRadar: (Int, Long) -> U title = stringResource(R.string.pass_whatsnew_title), text = stringResource(R.string.pass_whatsnew_message) ) { - uiState.takeAction(PassesAction.DismissWhatsNew) + onAction(PassesAction.DismissWhatsNew) } } val gridState = rememberLazyGridState() @@ -123,7 +127,7 @@ private fun PassesScreen(uiState: PassesState, navigateToRadar: (Int, Long) -> U TopBar( isVerticalLayout = isVerticalLayout, startAction = { - IconCard(action = showPassesDialog, resId = R.drawable.ic_filter) + IconCard(action = { onAction(PassesAction.TogglePassesDialog) }, resId = R.drawable.ic_filter) }, topInfo = { TimerRow(timeString = uiState.nextTime, isTimeAos = uiState.isNextTimeAos) @@ -132,7 +136,7 @@ private fun PassesScreen(uiState: PassesState, navigateToRadar: (Int, Long) -> U NextPassRow(pass = uiState.nextPass, isUtc = uiState.isUtc) }, endAction = { - IconCard(action = showRadiosDialog, resId = R.drawable.ic_radios) + IconCard(action = { onAction(PassesAction.ToggleRadiosDialog) }, resId = R.drawable.ic_radios) } ) } @@ -142,7 +146,7 @@ private fun PassesScreen(uiState: PassesState, navigateToRadar: (Int, Long) -> U isUtc = uiState.isUtc, passes = uiState.itemsList, navigateToRadar = navigateToRadar, - refreshPasses = refreshList, + refreshPasses = { onAction(PassesAction.RefreshPasses) }, gridState = gridState ) } @@ -184,7 +188,7 @@ private fun PassesList( modifier = Modifier.fillMaxSize() ) { items(items = passes, key = { item -> item.catNum + item.aosTime }) { pass -> - NearEarthPass( + PassItem( pass = pass, navigateToRadar = navigateToRadar, modifier = Modifier.animateItem(), @@ -204,7 +208,7 @@ private fun DeepSpacePassPreview() { val data = OrbitalData("Satellite", 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 45000, 0.0) val satellite = DeepSpaceObject(data) val pass = OrbitalPass(1L, 180.0, 10L, 360.0, 36650, 45.0, satellite, 0.5f) - MainTheme { NearEarthPass(pass = pass, { _, _ -> }) } + MainTheme { PassItem(pass = pass, { _, _ -> }) } } @Preview(showBackground = true) @@ -213,11 +217,11 @@ private fun NearEarthPassPreview() { val data = OrbitalData("Satellite", 0.0, 15.0, 0.0, 0.0, 0.0, 0.0, 0.0, 45000, 0.0) val satellite = NearEarthObject(data) val pass = OrbitalPass(1L, 180.0, 10L, 360.0, 36650, 45.0, satellite, 0.5f) - MainTheme { NearEarthPass(pass = pass, { _, _ -> }) } + MainTheme { PassItem(pass = pass, { _, _ -> }) } } @Composable -private fun NearEarthPass( +private fun PassItem( pass: OrbitalPass, navigateToRadar: (Int, Long) -> Unit, modifier: Modifier = Modifier, @@ -235,118 +239,121 @@ private fun NearEarthPass( val sdfTime = remember(isUtc) { SimpleDateFormat("HH:mm:ss", Locale.ENGLISH).also { it.timeZone = timeZone } } - // Cache formatted date/time strings — aosTime/losTime never change for a given pass val aosDateStr = remember(pass.aosTime, isUtc) { sdfDate.format(Date(pass.aosTime)) } val aosTimeStr = remember(pass.aosTime, isUtc) { sdfTime.format(Date(pass.aosTime)) } val losTimeStr = remember(pass.losTime, isUtc) { sdfTime.format(Date(pass.losTime)) } - Surface(color = MaterialTheme.colorScheme.background, modifier = modifier) { - Surface(modifier = Modifier - .padding(bottom = 2.dp) - .clickable { navigateToRadar(pass.catNum, pass.aosTime) }) { - Column( - verticalArrangement = Arrangement.spacedBy(1.dp), - modifier = Modifier - .background(color = MaterialTheme.colorScheme.surface) - .padding(horizontal = horizontalPadding, vertical = 4.dp) + + Column( + modifier = modifier.clickable { navigateToRadar(pass.catNum, pass.aosTime) } + ) { + Column( + verticalArrangement = Arrangement.spacedBy(1.dp), + modifier = Modifier + .fillMaxWidth() + .background(color = MaterialTheme.colorScheme.surface) + .padding(horizontal = horizontalPadding, vertical = 4.dp) + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = "$passSatId - ", + color = MaterialTheme.colorScheme.primary + ) + Text( + text = pass.name, + modifier = Modifier + .weight(1f) + .padding(end = 6.dp) + .infiniteMarquee(), + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onSurface + ) + Icon( + painter = painterResource(id = R.drawable.ic_elevation), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = "${pass.maxElevation}°", + color = MaterialTheme.colorScheme.primary + ) + } + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() ) { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - text = "$passSatId - ", - color = MaterialTheme.colorScheme.primary - ) - Text( - text = pass.name, - modifier = Modifier - .weight(1f) - .padding(end = 6.dp) - .infiniteMarquee(), - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) + Row( + modifier = Modifier.weight(1f), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + if (pass.isDeepSpace) { + Text( + text = stringResource(R.string.pass_deep_space), + fontSize = 15.sp, + color = MaterialTheme.colorScheme.onSurface + ) + } else { + Text(text = aosDateStr, fontSize = 15.sp, color = MaterialTheme.colorScheme.onSurface) + } + } + Row( + modifier = Modifier.weight(1f), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { Icon( - painter = painterResource(id = R.drawable.ic_elevation), + painter = painterResource(id = R.drawable.ic_altitude), contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier.size(16.dp) + modifier = Modifier.size(16.dp), + tint = MaterialTheme.colorScheme.onSurface ) Spacer(modifier = Modifier.width(4.dp)) - Text( - text = "${pass.maxElevation}°", - color = MaterialTheme.colorScheme.primary - ) + Text(text = "${pass.altitude} km", fontSize = 15.sp, color = MaterialTheme.colorScheme.onSurface) } Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.weight(1f), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically ) { - Row( - modifier = Modifier.weight(1f), - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically - ) { - if (pass.isDeepSpace) { - Text(text = stringResource(R.string.pass_deep_space), fontSize = 15.sp) - } else { - Text( - text = aosDateStr, - fontSize = 15.sp - ) - } - } - Row( - modifier = Modifier.weight(1f), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - painter = painterResource(id = R.drawable.ic_altitude), - contentDescription = null, - modifier = Modifier.size(16.dp) - ) - Spacer(modifier = Modifier.width(4.dp)) - Text( - text = "${pass.altitude} km", - fontSize = 15.sp - ) - } - Row( - modifier = Modifier.weight(1f), - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource( - id = R.string.pass_aosLos, - pass.aosAzimuth.toInt(), - pass.losAzimuth.toInt() - ), - fontSize = 15.sp - ) - } - } - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth() - ) { - val defaultTime = " - - : - - " Text( - text = if (pass.isDeepSpace) defaultTime else aosTimeStr, - fontSize = 15.sp - ) - LinearProgressIndicator( - progress = { if (pass.isDeepSpace) 100f else pass.progress }, - drawStopIndicator = {}, - modifier = modifier.fillMaxWidth(0.75f) - ) - Text( - text = if (pass.isDeepSpace) defaultTime else losTimeStr, - fontSize = 15.sp + text = stringResource( + id = R.string.pass_aosLos, + pass.aosAzimuth.toInt(), + pass.losAzimuth.toInt() + ), + fontSize = 15.sp, + color = MaterialTheme.colorScheme.onSurface ) } } + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + val defaultTime = " - - : - - " + Text( + text = if (pass.isDeepSpace) defaultTime else aosTimeStr, + fontSize = 15.sp, + color = MaterialTheme.colorScheme.onSurface + ) + LinearProgressIndicator( + progress = { if (pass.isDeepSpace) 100f else pass.progress }, + drawStopIndicator = {}, + modifier = Modifier.fillMaxWidth(0.75f) + ) + Text( + text = if (pass.isDeepSpace) defaultTime else losTimeStr, + fontSize = 15.sp, + color = MaterialTheme.colorScheme.onSurface + ) + } } + HorizontalDivider(thickness = 2.dp, color = MaterialTheme.colorScheme.background) } } diff --git a/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesState.kt b/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesState.kt index b5e17974..e9afc8a9 100644 --- a/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesState.kt +++ b/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesState.kt @@ -20,19 +20,18 @@ package com.rtbishop.look4sat.feature.passes import com.rtbishop.look4sat.core.domain.predict.OrbitalPass data class PassesState( - val isPassesDialogShown: Boolean, - val isRadiosDialogShown: Boolean, - val isRefreshing: Boolean, - val isUtc: Boolean, + val isPassesDialogShown: Boolean = false, + val isRadiosDialogShown: Boolean = false, + val isRefreshing: Boolean = true, + val isUtc: Boolean = false, val nextPass: OrbitalPass, - val nextTime: String, - val isNextTimeAos: Boolean, - val hours: Int, - val elevation: Double, - val modes: List, - val itemsList: List, - val shouldSeeWhatsNew: Boolean, - val takeAction: (PassesAction) -> Unit + val nextTime: String = "00:00:00", + val isNextTimeAos: Boolean = true, + val hours: Int = 24, + val elevation: Double = 16.0, + val modes: List = emptyList(), + val itemsList: List = emptyList(), + val shouldSeeWhatsNew: Boolean = false ) sealed class PassesAction { diff --git a/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesViewModel.kt b/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesViewModel.kt index 18331cc2..0633f39f 100644 --- a/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesViewModel.kt +++ b/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesViewModel.kt @@ -46,30 +46,24 @@ class PassesViewModel( private val defaultPass = getDefaultPass() private val _uiState = MutableStateFlow( PassesState( - isPassesDialogShown = false, - isRadiosDialogShown = false, - isRefreshing = true, isUtc = settingsRepo.otherSettings.value.stateOfUtc, - nextTime = "00:00:00", - isNextTimeAos = true, nextPass = defaultPass, hours = settingsRepo.passesSettings.value.hoursAhead, elevation = settingsRepo.passesSettings.value.minElevation, modes = settingsRepo.passesSettings.value.selectedModes, - itemsList = emptyList(), - shouldSeeWhatsNew = settingsRepo.otherSettings.value.shouldSeeWhatsNew, - takeAction = ::handleAction + shouldSeeWhatsNew = settingsRepo.otherSettings.value.shouldSeeWhatsNew ) ) val uiState: StateFlow = _uiState init { - // React to raw pass list changes (initial load, recalculation, filter change) + // React to raw pass list changes — stops the refreshing indicator on first emission viewModelScope.launch { var initialLoadDone = false - satelliteRepo.passes.collectLatest { passes -> - if (!initialLoadDone && passes.isNotEmpty()) { + satelliteRepo.passes.collectLatest { _ -> + if (!initialLoadDone) { initialLoadDone = true + delay(1000) // Artificial delay to show the refreshing state on first load _uiState.update { it.copy(isRefreshing = false) } } } @@ -79,13 +73,13 @@ class PassesViewModel( while (isActive) { val timeNow = System.currentTimeMillis() val processed = computePassProgress(satelliteRepo.passes.value, timeNow) - val nextInfo = resolveNextPass(processed, timeNow) + val (nextPass, nextTime, isAos) = resolveNextPass(processed, timeNow) _uiState.update { it.copy( itemsList = processed, - nextPass = nextInfo.first, - nextTime = nextInfo.second, - isNextTimeAos = nextInfo.third + nextPass = nextPass, + nextTime = nextTime, + isNextTimeAos = isAos ) } delay(1000) @@ -103,6 +97,17 @@ class PassesViewModel( } } + fun onAction(action: PassesAction) { + when (action) { + PassesAction.DismissWhatsNew -> settingsRepo.setWhatsNewDismissed() + is PassesAction.FilterPasses -> applyFilter(action.hoursAhead, action.minElevation, _uiState.value.modes) + is PassesAction.FilterRadios -> applyFilter(_uiState.value.hours, _uiState.value.elevation, action.modes) + PassesAction.RefreshPasses -> refreshPasses() + PassesAction.TogglePassesDialog -> _uiState.update { it.copy(isPassesDialogShown = !it.isPassesDialogShown) } + PassesAction.ToggleRadiosDialog -> _uiState.update { it.copy(isRadiosDialogShown = !it.isRadiosDialogShown) } + } + } + /** Computes live progress for each pass, filtering out expired ones. */ private fun computePassProgress(passList: List, time: Long): List { val result = ArrayList(passList.size) @@ -129,28 +134,17 @@ class PassesViewModel( passes: List, timeNow: Long ): Triple { - val upcoming = passes.firstOrNull { it.aosTime.minus(timeNow) > 0 } + val upcoming = passes.firstOrNull { it.aosTime > timeNow } if (upcoming != null) { - return Triple(upcoming, upcoming.aosTime.minus(timeNow).toTimerString(), true) + return Triple(upcoming, (upcoming.aosTime - timeNow).toTimerString(), true) } if (passes.isNotEmpty()) { val lastPass = passes.last() - return Triple(lastPass, lastPass.losTime.minus(timeNow).toTimerString(), false) + return Triple(lastPass, (lastPass.losTime - timeNow).toTimerString(), false) } return Triple(defaultPass, "00:00:00", true) } - private fun handleAction(action: PassesAction) { - when (action) { - PassesAction.DismissWhatsNew -> settingsRepo.setWhatsNewDismissed() - is PassesAction.FilterPasses -> applyFilter(action.hoursAhead, action.minElevation, uiState.value.modes) - is PassesAction.FilterRadios -> applyFilter(uiState.value.hours, uiState.value.elevation, action.modes) - PassesAction.RefreshPasses -> refreshPasses() - PassesAction.TogglePassesDialog -> toggleFilterDialog() - PassesAction.ToggleRadiosDialog -> toggleRadiosDialog() - } - } - private fun applyFilter(hoursAhead: Int, minElevation: Double, modes: List) = viewModelScope.launch { _uiState.update { it.copy(isRefreshing = true) } settingsRepo.setPassesSettings(PassesSettings(hoursAhead, minElevation, modes)) @@ -166,15 +160,6 @@ class PassesViewModel( _uiState.update { it.copy(isRefreshing = false) } } - private fun toggleFilterDialog() { - val currentDialogState = _uiState.value.isPassesDialogShown - _uiState.update { it.copy(isPassesDialogShown = currentDialogState.not()) } - } - - private fun toggleRadiosDialog() { - val currentDialogState = _uiState.value.isRadiosDialogShown - _uiState.update { it.copy(isRadiosDialogShown = currentDialogState.not()) } - } companion object { val Factory: ViewModelProvider.Factory = viewModelFactory { diff --git a/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesDialog.kt b/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesDialog.kt index 4480839a..025fd100 100644 --- a/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesDialog.kt +++ b/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesDialog.kt @@ -30,7 +30,7 @@ import androidx.compose.material3.Checkbox import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -51,14 +51,14 @@ private fun MultiTypesDialogPreview() { } @Composable -fun MultiTypesDialog( +internal fun MultiTypesDialog( allTypes: List, types: List, cancel: () -> Unit, accept: (List) -> Unit ) { - val selected = remember { mutableStateListOf().apply { addAll(types) } } - val select = { type: String -> - if (selected.contains(type)) selected.remove(type) else selected.add(type) + val selected = remember { mutableStateOf(types.toSet()) } + val toggle = { type: String -> + selected.value = if (type in selected.value) selected.value - type else selected.value + type } - val onAccept = { accept(selected.toList()) } + val onAccept = { accept(selected.value.toList()) } SharedDialog(title = stringResource(R.string.sat_type_title), onCancel = cancel, onAccept = onAccept) { LazyVerticalGrid( columns = GridCells.Adaptive(240.dp), @@ -73,7 +73,7 @@ fun MultiTypesDialog( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .background(MaterialTheme.colorScheme.surface) - .clickable { select(item) } + .clickable { toggle(item) } ) { Text( text = "${index + 1}).", @@ -89,7 +89,7 @@ fun MultiTypesDialog( overflow = TextOverflow.Ellipsis ) Checkbox( - checked = selected.contains(item), + checked = item in selected.value, onCheckedChange = null, modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) ) 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 d7f97f0c..0f9d9ad5 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 @@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells @@ -32,10 +33,10 @@ import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.text.BasicTextField import androidx.compose.material3.Checkbox import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf @@ -77,54 +78,97 @@ fun NavGraphBuilder.satellitesDestination(navigateUp: () -> Unit) { modelClass = SatellitesViewModel::class.java, factory = SatellitesViewModel.Factory ) val uiState = viewModel.uiState.collectAsStateWithLifecycle().value - SatellitesScreen(uiState, navigateUp) + SatellitesScreen(uiState, viewModel::onAction, navigateUp) } } @Composable -private fun SatellitesScreen(uiState: SatellitesState, navigateUp: () -> Unit) { - val toggleDialog = { uiState.takeAction(SatellitesAction.ToggleTypesDialog) } +private fun SatellitesScreen( + uiState: SatellitesState, + onAction: (SatellitesAction) -> Unit, + navigateUp: () -> Unit +) { if (uiState.isDialogShown) { - MultiTypesDialog(allTypes = uiState.typesList, types = uiState.currentTypes, toggleDialog) { - uiState.takeAction(SatellitesAction.SelectTypes(it)) - } + MultiTypesDialog( + allTypes = uiState.typesList, + types = uiState.currentTypes, + cancel = { onAction(SatellitesAction.ToggleTypesDialog) }, + accept = { onAction(SatellitesAction.SelectTypes(it)) } + ) } if (uiState.shouldSeeWarning) { InfoDialog( stringResource(R.string.sat_warning_title), stringResource(R.string.sat_warning_message) ) { - uiState.takeAction(SatellitesAction.DismissWarning) + onAction(SatellitesAction.DismissWarning) } } - val unselectAll = { uiState.takeAction(SatellitesAction.UnselectAll) } - val selectAll = { uiState.takeAction(SatellitesAction.SelectAll) } - val setQuery = { newQuery: String -> uiState.takeAction(SatellitesAction.SearchFor(newQuery)) } - val saveSelection = { uiState.takeAction(SatellitesAction.SaveSelection).also { navigateUp() } } + + val saveAndNavigateUp = { + onAction(SatellitesAction.SaveSelection) + navigateUp() + } val primCardCd = stringResource(R.string.btn_accept) - val primCardMod = Modifier.semantics { contentDescription = primCardCd } val clearAllCd = stringResource(R.string.sat_clear_all) - val clearAllMod = Modifier.semantics { contentDescription = clearAllCd } val selectAllCd = stringResource(R.string.sat_select_all) - val selectAllMod = Modifier.semantics { contentDescription = selectAllCd } + Column(modifier = Modifier.layoutPadding(), verticalArrangement = Arrangement.spacedBy(6.dp)) { if (isVerticalLayout()) { TopBar { - TypeCard(types = uiState.currentTypes, toggleDialog, modifier = Modifier.weight(1f)) - PrimaryIconCard(onClick = saveSelection, resId = R.drawable.ic_done, modifier = primCardMod) + TypeCard( + types = uiState.currentTypes, + onClick = { onAction(SatellitesAction.ToggleTypesDialog) }, + modifier = Modifier.weight(1f) + ) + PrimaryIconCard( + onClick = saveAndNavigateUp, + resId = R.drawable.ic_done, + modifier = Modifier.semantics { contentDescription = primCardCd } + ) } TopBar { - SearchBar(setQuery = { setQuery(it) }, modifier = Modifier.weight(1f)) - IconCard(action = unselectAll, resId = R.drawable.ic_check_off, modifier = clearAllMod) - IconCard(action = selectAll, resId = R.drawable.ic_check_on, modifier = selectAllMod) + SearchBar( + onQueryChange = { onAction(SatellitesAction.SearchFor(it)) }, + modifier = Modifier.weight(1f) + ) + IconCard( + action = { onAction(SatellitesAction.UnselectAll) }, + resId = R.drawable.ic_check_off, + modifier = Modifier.semantics { contentDescription = clearAllCd } + ) + IconCard( + action = { onAction(SatellitesAction.SelectAll) }, + resId = R.drawable.ic_check_on, + modifier = Modifier.semantics { contentDescription = selectAllCd } + ) } } else { TopBar { - PrimaryIconCard(onClick = saveSelection, resId = R.drawable.ic_done, modifier = primCardMod) - TypeCard(types = uiState.currentTypes, toggleDialog, modifier = Modifier.weight(1f)) - SearchBar(setQuery = { setQuery(it) }, modifier = Modifier.weight(1f)) - IconCard(action = unselectAll, resId = R.drawable.ic_check_off, modifier = clearAllMod) - IconCard(action = selectAll, resId = R.drawable.ic_check_on, modifier = selectAllMod) + PrimaryIconCard( + onClick = saveAndNavigateUp, + resId = R.drawable.ic_done, + modifier = Modifier.semantics { contentDescription = primCardCd } + ) + TypeCard( + types = uiState.currentTypes, + onClick = { onAction(SatellitesAction.ToggleTypesDialog) }, + modifier = Modifier.weight(1f) + ) + SearchBar( + onQueryChange = { onAction(SatellitesAction.SearchFor(it)) }, + modifier = Modifier.weight(1f) + ) + IconCard( + action = { onAction(SatellitesAction.UnselectAll) }, + resId = R.drawable.ic_check_off, + modifier = Modifier.semantics { contentDescription = clearAllCd } + ) + IconCard( + action = { onAction(SatellitesAction.SelectAll) }, + resId = R.drawable.ic_check_on, + modifier = Modifier.semantics { contentDescription = selectAllCd } + ) } } ElevatedCard(modifier = Modifier.fillMaxSize()) { @@ -133,7 +177,7 @@ private fun SatellitesScreen(uiState: SatellitesState, navigateUp: () -> Unit) { uiState.isLoading -> CardLoadingIndicator() uiState.itemsList.isEmpty() -> EmptyListCard(message = emptyMessage) else -> SatellitesCard(uiState.itemsList) { id, isTicked -> - uiState.takeAction(SatellitesAction.SelectSingle(id, isTicked)) + onAction(SatellitesAction.SelectSingle(id, isTicked)) } } } @@ -141,11 +185,11 @@ private fun SatellitesScreen(uiState: SatellitesState, navigateUp: () -> Unit) { } @Composable -private fun SearchBar(setQuery: (String) -> Unit, modifier: Modifier = Modifier) { +private fun SearchBar(onQueryChange: (String) -> Unit, modifier: Modifier = Modifier) { val currentQuery = rememberSaveable { mutableStateOf("") } - val setNewQuery = { newValue: String -> + val updateQuery = { newValue: String -> currentQuery.value = newValue - setQuery(newValue) + onQueryChange(newValue) } ElevatedCard(modifier = modifier.height(48.dp)) { Row( @@ -156,9 +200,11 @@ private fun SearchBar(setQuery: (String) -> Unit, modifier: Modifier = Modifier) Icon(painter = painterResource(id = R.drawable.ic_search), contentDescription = null) BasicTextField( value = currentQuery.value, - onValueChange = { setNewQuery(it) }, + onValueChange = updateQuery, singleLine = true, - modifier = modifier.padding(start = 12.dp), + modifier = Modifier + .weight(1f) + .padding(start = 12.dp), textStyle = TextStyle( fontSize = 16.sp, lineHeight = 20.sp, @@ -179,7 +225,7 @@ private fun SearchBar(setQuery: (String) -> Unit, modifier: Modifier = Modifier) }, cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurface) ) - IconButton(onClick = { setNewQuery("") }) { + IconButton(onClick = { updateQuery("") }) { val clearCd = stringResource(R.string.sat_search_clear) Icon(painter = painterResource(id = R.drawable.ic_close), contentDescription = clearCd) } @@ -220,39 +266,40 @@ private fun TypeCard(types: List, onClick: () -> Unit, modifier: Modifie @Composable private fun SatellitePreview() { val satItem = SatItem(44444, "Ultra Super Mega long satellite name", true) - MainTheme { Satellite(item = satItem, onSelected = { _, _ -> run {} }, modifier = Modifier) } + MainTheme { Satellite(item = satItem, onSelected = { _, _ -> }) } } @Composable -private fun Satellite(item: SatItem, onSelected: (Int, Boolean) -> Unit, modifier: Modifier) { +private fun Satellite( + item: SatItem, + onSelected: (Int, Boolean) -> Unit, + modifier: Modifier = Modifier +) { val passSatId = stringResource(id = R.string.pass_satId, item.catnum) - Surface( - color = MaterialTheme.colorScheme.background, - modifier = modifier.clickable { onSelected(item.catnum, item.isSelected) }) { - Surface(modifier = Modifier.padding(bottom = 1.dp)) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .background(MaterialTheme.colorScheme.surface) - .padding(start = 14.dp, top = 8.dp, end = 12.dp, bottom = 8.dp) - ) { - Text( - text = "$passSatId - ", color = MaterialTheme.colorScheme.primary - ) - Text( - text = item.name, - modifier = Modifier.weight(1f), - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Checkbox( - checked = item.isSelected, - onCheckedChange = null, - modifier = Modifier.padding(start = 6.dp) - ) - } + Column(modifier = modifier.clickable { onSelected(item.catnum, item.isSelected) }) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surface) + .padding(start = 14.dp, top = 8.dp, end = 12.dp, bottom = 8.dp) + ) { + Text(text = "$passSatId - ", color = MaterialTheme.colorScheme.primary) + Text( + text = item.name, + modifier = Modifier.weight(1f), + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onSurface + ) + Checkbox( + checked = item.isSelected, + onCheckedChange = null, + modifier = Modifier.padding(start = 6.dp) + ) } + HorizontalDivider(thickness = 1.dp, color = MaterialTheme.colorScheme.background) } } @@ -264,11 +311,11 @@ private fun SatellitesPreview() { SatItem(44444, "ISS", true), SatItem(88888, "Starlink", false) ) - MainTheme { SatellitesCard(entries) { _, _ -> run {} } } + MainTheme { SatellitesCard(entries) { _, _ -> } } } @Composable -fun SatellitesCard(items: List, onSelected: (Int, Boolean) -> Unit) { +private fun SatellitesCard(items: List, onSelected: (Int, Boolean) -> Unit) { LazyVerticalGrid(columns = GridCells.Adaptive(320.dp)) { items(items = items, key = { item -> item.catnum }) { entry -> Satellite(entry, onSelected, Modifier.animateItem()) diff --git a/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesState.kt b/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesState.kt index e03715e3..a6c4bdba 100644 --- a/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesState.kt +++ b/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesState.kt @@ -20,13 +20,12 @@ package com.rtbishop.look4sat.feature.satellites import com.rtbishop.look4sat.core.domain.model.SatItem data class SatellitesState( - val isDialogShown: Boolean, - val isLoading: Boolean, - val shouldSeeWarning: Boolean, - val itemsList: List, - val currentTypes: List, - val typesList: List, - val takeAction: (SatellitesAction) -> Unit + val isDialogShown: Boolean = false, + val isLoading: Boolean = true, + val shouldSeeWarning: Boolean = false, + val itemsList: List = emptyList(), + val currentTypes: List = emptyList(), + val typesList: List = emptyList() ) sealed class SatellitesAction { diff --git a/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesViewModel.kt b/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesViewModel.kt index 15c817aa..72406462 100644 --- a/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesViewModel.kt +++ b/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesViewModel.kt @@ -25,7 +25,6 @@ import androidx.lifecycle.viewmodel.viewModelFactory import com.rtbishop.look4sat.core.domain.repository.IContainerProvider import com.rtbishop.look4sat.core.domain.repository.ISelectionRepo import com.rtbishop.look4sat.core.domain.repository.ISettingsRepo -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest @@ -40,20 +39,16 @@ class SatellitesViewModel( private val defaultTypes = selectionRepo.getCurrentTypes() private val _uiState = MutableStateFlow( SatellitesState( - isDialogShown = false, isLoading = true, shouldSeeWarning = settingsRepo.otherSettings.value.shouldSeeWarning, - itemsList = emptyList(), currentTypes = defaultTypes, - typesList = selectionRepo.getTypesList(), - takeAction = ::handleAction + typesList = selectionRepo.getTypesList() ) ) val uiState: StateFlow = _uiState init { viewModelScope.launch { - delay(1000) selectionRepo.setQuery(String()) selectionRepo.setTypes(defaultTypes) selectionRepo.getEntriesFlow().collectLatest { items -> @@ -67,7 +62,7 @@ class SatellitesViewModel( } } - private fun handleAction(action: SatellitesAction) { + fun onAction(action: SatellitesAction) { when (action) { SatellitesAction.DismissWarning -> settingsRepo.setWarningDismissed() SatellitesAction.SaveSelection -> saveSelection() @@ -94,12 +89,11 @@ class SatellitesViewModel( private fun selectTypes(types: List) = viewModelScope.launch { selectionRepo.setTypes(types) - _uiState.value = _uiState.value.copy(currentTypes = types, isDialogShown = false) + _uiState.update { it.copy(currentTypes = types, isDialogShown = false) } } private fun toggleTypesDialog() { - val currentDialogState = _uiState.value.isDialogShown - _uiState.value = _uiState.value.copy(isDialogShown = currentDialogState.not()) + _uiState.update { it.copy(isDialogShown = !it.isDialogShown) } } companion object {