mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Added DeepSpace passes filter, fixed refresh issue (#208)
This commit is contained in:
1 parent
d2b3184084
commit
8265b73d75
10 files changed
+112
-68
No files matched your search
@@ -32,12 +32,15 @@ import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableDoubleStateOf
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
@@ -65,17 +68,29 @@ private val hourSteps = listOf(1, 2, 4, 8, 12, 24, 48, 72, 96, 120, 144, 168, 19
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PassesDialogPreview() {
|
||||
MainTheme { PassesDialog(24, 16.0, {}) { _, _ -> } }
|
||||
MainTheme { PassesDialog(24, 16.0, true, {}) { _, _, _ -> } }
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun PassesDialog(hours: Int, elevation: Double, cancel: () -> Unit, accept: (Int, Double) -> Unit) {
|
||||
internal fun PassesDialog(
|
||||
hours: Int,
|
||||
elevation: Double,
|
||||
showDeepSpace: Boolean,
|
||||
cancel: () -> Unit,
|
||||
accept: (Int, Double, Boolean) -> Unit
|
||||
) {
|
||||
val hoursIndex = remember { mutableIntStateOf(hourSteps.indexOfFirst { it >= hours }.coerceAtLeast(0)) }
|
||||
val elevationValueNew = remember { mutableDoubleStateOf(elevation) }
|
||||
var deepSpaceEnabled by remember { mutableStateOf(showDeepSpace) }
|
||||
val onAccept = {
|
||||
accept(hourSteps[hoursIndex.intValue], elevationValueNew.doubleValue).also { cancel() }
|
||||
accept(hourSteps[hoursIndex.intValue], elevationValueNew.doubleValue, deepSpaceEnabled).also { cancel() }
|
||||
}
|
||||
SharedDialog(title = stringResource(R.string.pass_filter_title), onCancel = cancel, onAccept = onAccept) {
|
||||
ToggleRow(
|
||||
title = stringResource(R.string.pass_filter_deep_space),
|
||||
checked = deepSpaceEnabled,
|
||||
onCheckedChange = { deepSpaceEnabled = it }
|
||||
)
|
||||
SliderRow(
|
||||
title = stringResource(R.string.pass_filter_elev),
|
||||
value = elevationValueNew.doubleValue,
|
||||
@@ -188,3 +203,20 @@ internal fun RadiosDialog(modes: List<String>, cancel: () -> Unit, accept: (List
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ToggleRow(title: String, checked: Boolean, onCheckedChange: (Boolean) -> Unit) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(horizontal = LocalSpacing.current.large)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
fontSize = 16.sp,
|
||||
modifier = Modifier.weight(1f),
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Switch(checked = checked, onCheckedChange = onCheckedChange)
|
||||
}
|
||||
}
|
||||
@@ -100,9 +100,10 @@ private fun PassesScreen(
|
||||
PassesDialog(
|
||||
hours = uiState.hours,
|
||||
elevation = uiState.elevation,
|
||||
showDeepSpace = uiState.showDeepSpace,
|
||||
cancel = { onAction(PassesAction.TogglePassesDialog) }
|
||||
) { hours, elevation ->
|
||||
onAction(PassesAction.FilterPasses(hours, elevation))
|
||||
) { hours, elevation, showDeepSpace ->
|
||||
onAction(PassesAction.FilterPasses(hours, elevation, showDeepSpace))
|
||||
}
|
||||
}
|
||||
if (uiState.isRadiosDialogShown) {
|
||||
|
||||
@@ -29,6 +29,7 @@ data class PassesState(
|
||||
val isNextTimeAos: Boolean = true,
|
||||
val hours: Int = 24,
|
||||
val elevation: Double = 16.0,
|
||||
val showDeepSpace: Boolean = true,
|
||||
val modes: List<String> = emptyList(),
|
||||
val itemsList: List<OrbitalPass> = emptyList(),
|
||||
val shouldSeeWhatsNew: Boolean = false
|
||||
@@ -36,7 +37,7 @@ data class PassesState(
|
||||
|
||||
sealed interface PassesAction {
|
||||
data object DismissWhatsNew : PassesAction
|
||||
data class FilterPasses(val hoursAhead: Int, val minElevation: Double) : PassesAction
|
||||
data class FilterPasses(val hoursAhead: Int, val minElevation: Double, val showDeepSpace: Boolean) : PassesAction
|
||||
data class FilterRadios(val modes: List<String>) : PassesAction
|
||||
data object RefreshPasses : PassesAction
|
||||
data object TogglePassesDialog : PassesAction
|
||||
|
||||
+30
-31
@@ -34,7 +34,6 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -51,6 +50,7 @@ class PassesViewModel(
|
||||
nextPass = defaultPass,
|
||||
hours = settingsRepo.passesSettings.value.hoursAhead,
|
||||
elevation = settingsRepo.passesSettings.value.minElevation,
|
||||
showDeepSpace = settingsRepo.passesSettings.value.showDeepSpace,
|
||||
modes = settingsRepo.passesSettings.value.selectedModes,
|
||||
shouldSeeWhatsNew = settingsRepo.otherSettings.value.shouldSeeWhatsNew
|
||||
)
|
||||
@@ -58,29 +58,20 @@ class PassesViewModel(
|
||||
val uiState: StateFlow<PassesState> = _uiState
|
||||
|
||||
init {
|
||||
// Show refreshing indicator whenever the selected satellites list changes
|
||||
// Refresh indicator: mirrors the repo's isCalculating state
|
||||
viewModelScope.launch {
|
||||
settingsRepo.selectedIds.collectLatest { selectedIds ->
|
||||
_uiState.update { it.copy(isRefreshing = true) }
|
||||
if (selectedIds.isEmpty()) {
|
||||
// No satellites selected — show indicator briefly, then stop
|
||||
delay(1000)
|
||||
_uiState.update { it.copy(isRefreshing = false) }
|
||||
}
|
||||
// For non-empty selections, the passes collector below will clear isRefreshing
|
||||
}
|
||||
}
|
||||
// Stop refreshing whenever new passes arrive (from initial load, selection change, or filter)
|
||||
viewModelScope.launch {
|
||||
satelliteRepo.passes.drop(1).collect { _ ->
|
||||
_uiState.update { it.copy(isRefreshing = false) }
|
||||
satelliteRepo.isCalculating.collect { calculating ->
|
||||
_uiState.update { it.copy(isRefreshing = calculating) }
|
||||
}
|
||||
}
|
||||
// Local tick loop — computes pass progress and countdown timer every second
|
||||
viewModelScope.launch {
|
||||
while (isActive) {
|
||||
val timeNow = System.currentTimeMillis()
|
||||
val processed = computePassProgress(satelliteRepo.passes.value, timeNow)
|
||||
val showDeepSpace = _uiState.value.showDeepSpace
|
||||
val allPasses = satelliteRepo.passes.value
|
||||
val filtered = if (showDeepSpace) allPasses else allPasses.filter { !it.isDeepSpace }
|
||||
val processed = computePassProgress(filtered, timeNow)
|
||||
val (nextPass, nextTime, isAos) = resolveNextPass(processed, timeNow)
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
@@ -108,11 +99,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)
|
||||
is PassesAction.FilterPasses ->
|
||||
applyFilter(action.hoursAhead, action.minElevation, action.showDeepSpace, _uiState.value.modes)
|
||||
|
||||
is PassesAction.FilterRadios ->
|
||||
applyFilter(_uiState.value.hours, _uiState.value.elevation, _uiState.value.showDeepSpace, action.modes)
|
||||
PassesAction.RefreshPasses -> refreshPasses()
|
||||
PassesAction.TogglePassesDialog -> _uiState.update { it.copy(isPassesDialogShown = !it.isPassesDialogShown) }
|
||||
PassesAction.ToggleRadiosDialog -> _uiState.update { it.copy(isRadiosDialogShown = !it.isRadiosDialogShown) }
|
||||
PassesAction.TogglePassesDialog ->
|
||||
_uiState.update { it.copy(isPassesDialogShown = !it.isPassesDialogShown) }
|
||||
|
||||
PassesAction.ToggleRadiosDialog ->
|
||||
_uiState.update { it.copy(isRadiosDialogShown = !it.isRadiosDialogShown) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,22 +150,24 @@ class PassesViewModel(
|
||||
return Triple(defaultPass, "00:00:00", true)
|
||||
}
|
||||
|
||||
private fun applyFilter(hoursAhead: Int, minElevation: Double, modes: List<String>) = viewModelScope.launch {
|
||||
_uiState.update { it.copy(isRefreshing = true) }
|
||||
settingsRepo.setPassesSettings(PassesSettings(hoursAhead, minElevation, modes))
|
||||
_uiState.update { it.copy(hours = hoursAhead, elevation = minElevation, modes = modes) }
|
||||
satelliteRepo.calculatePasses(System.currentTimeMillis(), hoursAhead, minElevation, modes)
|
||||
_uiState.update { it.copy(isRefreshing = false) }
|
||||
private fun applyFilter(
|
||||
hoursAhead: Int,
|
||||
minElevation: Double,
|
||||
showDeepSpace: Boolean,
|
||||
modes: List<String>
|
||||
) = viewModelScope.launch {
|
||||
settingsRepo.setPassesSettings(PassesSettings(showDeepSpace, hoursAhead, minElevation, modes))
|
||||
_uiState.update {
|
||||
it.copy(hours = hoursAhead, elevation = minElevation, showDeepSpace = showDeepSpace, modes = modes)
|
||||
}
|
||||
if (showDeepSpace) satelliteRepo.calculatePasses(System.currentTimeMillis(), hoursAhead, minElevation, modes)
|
||||
}
|
||||
|
||||
private fun refreshPasses() = viewModelScope.launch {
|
||||
_uiState.update { it.copy(isRefreshing = true) }
|
||||
val (hoursAhead, minElevation, modes) = settingsRepo.passesSettings.value
|
||||
val (_, hoursAhead, minElevation, modes) = settingsRepo.passesSettings.value
|
||||
satelliteRepo.calculatePasses(System.currentTimeMillis(), hoursAhead, minElevation, modes)
|
||||
_uiState.update { it.copy(isRefreshing = false) }
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||
val applicationKey = ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY
|
||||
|
||||
Reference in new issue
Block a user