mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-03 11:17:25 +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
@@ -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<String>, cancel: () -> Unit, accept: (List<String>) -> Unit) {
|
||||
val selected = remember { mutableStateListOf<String>().apply { addAll(modes) } }
|
||||
val select = { mode: String ->
|
||||
if (selected.contains(mode)) selected.remove(mode) else selected.add(mode)
|
||||
internal fun RadiosDialog(modes: List<String>, cancel: () -> Unit, accept: (List<String>) -> 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<String>, cancel: () -> Unit, accept: (List<String>)
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+122
-115
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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<String>,
|
||||
val itemsList: List<OrbitalPass>,
|
||||
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<String> = emptyList(),
|
||||
val itemsList: List<OrbitalPass> = emptyList(),
|
||||
val shouldSeeWhatsNew: Boolean = false
|
||||
)
|
||||
|
||||
sealed class PassesAction {
|
||||
|
||||
+23
-38
@@ -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<PassesState> = _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<OrbitalPass>, time: Long): List<OrbitalPass> {
|
||||
val result = ArrayList<OrbitalPass>(passList.size)
|
||||
@@ -129,28 +134,17 @@ class PassesViewModel(
|
||||
passes: List<OrbitalPass>,
|
||||
timeNow: Long
|
||||
): Triple<OrbitalPass, String, Boolean> {
|
||||
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<String>) = 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 {
|
||||
|
||||
Reference in new issue
Block a user