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