diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/MainScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/MainScreen.kt index 54d73df3..a341b11e 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/MainScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/MainScreen.kt @@ -25,6 +25,7 @@ import com.rtbishop.look4sat.presentation.entries.EntriesViewModel import com.rtbishop.look4sat.presentation.info.InfoScreen import com.rtbishop.look4sat.presentation.map.MapScreen import com.rtbishop.look4sat.presentation.passes.PassesScreen +import com.rtbishop.look4sat.presentation.passes.PassesViewModel import com.rtbishop.look4sat.presentation.radar.RadarScreen import com.rtbishop.look4sat.presentation.settings.SettingsScreen @@ -73,10 +74,11 @@ private fun MainNavGraph(navController: NavHostController) { EntriesScreen(viewModel.uiState.value, navToPasses) } composable(Screen.Passes.route) { + val viewModel = viewModel(PassesViewModel::class.java, factory = PassesViewModel.Factory) val navToRadar = { catNum: Int, aosTime: Long -> navController.navigate("${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}") } - PassesScreen(navToRadar) + PassesScreen(viewModel.uiState.value, navToRadar) } composable(radarRoute, radarArgs) { RadarScreen() } composable(Screen.Map.route) { MapScreen() } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesDialog.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesDialog.kt index 416099c7..8d4057c2 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesDialog.kt @@ -38,7 +38,7 @@ fun TypesDialog(items: List, selected: String, dismiss: () -> Unit, sele dismiss() } Dialog(onDismissRequest = { dismiss() }) { - ElevatedCard(modifier = Modifier.fillMaxHeight(0.75f)) { + ElevatedCard(modifier = Modifier.fillMaxHeight(0.80f)) { LazyVerticalGrid( columns = GridCells.Adaptive(160.dp), modifier = Modifier.background(MaterialTheme.colorScheme.background), @@ -47,7 +47,8 @@ fun TypesDialog(items: List, selected: String, dismiss: () -> Unit, sele ) { itemsIndexed(items) { index, item -> Surface { - Row(verticalAlignment = Alignment.CenterVertically, + Row( + verticalAlignment = Alignment.CenterVertically, modifier = Modifier .background(MaterialTheme.colorScheme.surface) .clickable { clickAction(item) }) { @@ -64,7 +65,7 @@ fun TypesDialog(items: List, selected: String, dismiss: () -> Unit, sele maxLines = 1, overflow = TextOverflow.Ellipsis ) - RadioButton(selected = item == selected, onClick = { clickAction(item) }) + RadioButton(selected = item == selected, onClick = null, modifier = Modifier.padding(8.dp)) } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesScreen.kt index 49abc3ed..d67591ed 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesScreen.kt @@ -46,18 +46,17 @@ import com.rtbishop.look4sat.presentation.components.CardLoadingIndicator @Composable fun EntriesScreen(uiState: EntriesState, navToPasses: () -> Unit) { - val showDialog = rememberSaveable { mutableStateOf(false) } - val toggleDialog = { showDialog.value = showDialog.value.not() } - if (showDialog.value) { + val toggleDialog = { uiState.takeAction(EntriesAction.ToggleTypesDialog) } + if (uiState.isDialogShown) { TypesDialog(items = uiState.typesList, selected = uiState.currentType, toggleDialog) { uiState.takeAction(EntriesAction.SelectType(it)) } } - val unselectAll = { uiState.takeAction(EntriesAction.UnselectAll) } val selectAll = { uiState.takeAction(EntriesAction.SelectAll) } Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { - TopBar(setQuery = { newQuery: String -> uiState.takeAction(EntriesAction.SearchFor(newQuery)) }, + TopBar( + setQuery = { newQuery: String -> uiState.takeAction(EntriesAction.SearchFor(newQuery)) }, saveSelection = { uiState.takeAction(EntriesAction.SaveSelection) navToPasses() diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesState.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesState.kt index 10cf3a1b..07db7147 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesState.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesState.kt @@ -3,6 +3,7 @@ package com.rtbishop.look4sat.presentation.entries import com.rtbishop.look4sat.domain.model.SatItem data class EntriesState( + val isDialogShown: Boolean, val isLoading: Boolean, val itemsList: List, val currentType: String, @@ -16,5 +17,6 @@ sealed class EntriesAction { data object SelectAll : EntriesAction() data class SelectSingle(val id: Int, val isTicked: Boolean) : EntriesAction() data class SelectType(val type: String) : EntriesAction() + data object ToggleTypesDialog : EntriesAction() data object UnselectAll : EntriesAction() } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesViewModel.kt index 929405b9..014b89e6 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesViewModel.kt @@ -17,6 +17,7 @@ */ package com.rtbishop.look4sat.presentation.entries +import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider @@ -30,20 +31,23 @@ import kotlinx.coroutines.launch class EntriesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel() { - private val defaultUiState = EntriesState( - isLoading = true, - itemsList = emptyList(), - currentType = selectionRepo.getCurrentType(), - typesList = selectionRepo.getTypesList(), - takeAction = ::handleAction + private val _uiState = mutableStateOf( + EntriesState( + isDialogShown = false, + isLoading = true, + itemsList = emptyList(), + currentType = selectionRepo.getCurrentType(), + typesList = selectionRepo.getTypesList(), + takeAction = ::handleAction + ) ) - val uiState = mutableStateOf(defaultUiState) + val uiState: State = _uiState init { viewModelScope.launch { delay(1000) selectionRepo.getEntriesFlow().collect { items -> - uiState.value = uiState.value.copy(isLoading = false, itemsList = items) + _uiState.value = _uiState.value.copy(isLoading = false, itemsList = items) } } } @@ -55,6 +59,7 @@ class EntriesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel() EntriesAction.SelectAll -> selectAll(true) is EntriesAction.SelectSingle -> selectSingle(action.id, action.isTicked) is EntriesAction.SelectType -> selectType(action.type) + EntriesAction.ToggleTypesDialog -> toggleTypesDialog() EntriesAction.UnselectAll -> selectAll(false) } } @@ -73,7 +78,12 @@ class EntriesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel() private fun selectType(type: String) = viewModelScope.launch { selectionRepo.setType(type) - uiState.value = uiState.value.copy(currentType = type) + _uiState.value = _uiState.value.copy(currentType = type) + } + + private fun toggleTypesDialog() { + val currentDialogState = _uiState.value.isDialogShown + _uiState.value = _uiState.value.copy(isDialogShown = currentDialogState.not()) } companion object { diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesDialog.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesDialog.kt index 6c3a9d46..c5570c0e 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesDialog.kt @@ -39,7 +39,8 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -54,27 +55,43 @@ import com.rtbishop.look4sat.R import com.rtbishop.look4sat.presentation.MainTheme import com.rtbishop.look4sat.presentation.components.CardButton +private val allModes = listOf( + "AFSK", "AFSK S-Net", "AFSK SALSAT", "AHRPT", "AM", "APT", "BPSK", "BPSK PMT-A3", + "CERTO", "CW", "DQPSK", "DSTAR", "DUV", "FFSK", "FM", "FMN", "FSK", "FSK AX.100 Mode 5", + "FSK AX.100 Mode 6", "FSK AX.25 G3RUH", "GFSK", "GFSK Rktr", "GMSK", "HRPT", "LoRa", + "LRPT", "LSB", "MFSK", "MSK", "MSK AX.100 Mode 5", "MSK AX.100 Mode 6", "OFDM", "OQPSK", + "PSK", "PSK31", "PSK63", "QPSK", "QPSK31", "QPSK63", "SSTV", "USB", "WSJT" +) + @Preview(showBackground = true) @Composable private fun FilterDialogPreview() { - MainTheme { FilterDialog(24, 16.0, {}) { _, _ -> } } + MainTheme { FilterDialog(24, 16.0, emptyList(), {}) { _, _, _ -> } } } @Composable -fun FilterDialog(hours: Int, elevation: Double, dismiss: () -> Unit, save: (Int, Double) -> Unit) { - val hoursValue = rememberSaveable { mutableIntStateOf(hours) } - val elevationValue = rememberSaveable { mutableIntStateOf(elevation.toInt()) } +fun FilterDialog( + hours: Int, + elev: Double, + modes: List, + dismiss: () -> Unit, + save: (Int, Double, List) -> Unit +) { val maxWidthModifier = Modifier.fillMaxWidth(1f) + val hoursValue = remember { mutableIntStateOf(hours) } + val elevationValue = remember { mutableIntStateOf(elev.toInt()) } + val selected = remember { mutableStateListOf().apply { addAll(modes) } } + val select = { mode: String -> if (selected.contains(mode)) selected.remove(mode) else selected.add(mode) } Dialog(onDismissRequest = { dismiss() }) { - ElevatedCard { + ElevatedCard(modifier = Modifier.fillMaxHeight(0.80f)) { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(6.dp), modifier = maxWidthModifier.padding(12.dp) ) { - Text(text = "Filter passes", fontSize = 20.sp, color = MaterialTheme.colorScheme.primary) + Text(text = "Filter passes", fontSize = 18.sp, color = MaterialTheme.colorScheme.primary) Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.Bottom) { - Text(text = "Show passes within", fontSize = 18.sp, modifier = Modifier.weight(1f)) + Text(text = "Show passes within", fontSize = 16.sp, modifier = Modifier.weight(1f)) Icon( painter = painterResource(id = R.drawable.ic_time), contentDescription = null, @@ -85,7 +102,7 @@ fun FilterDialog(hours: Int, elevation: Double, dismiss: () -> Unit, save: (Int, ) Text( text = "${hoursValue.intValue}h", - fontSize = 20.sp, + fontSize = 18.sp, fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.primary ) @@ -97,7 +114,7 @@ fun FilterDialog(hours: Int, elevation: Double, dismiss: () -> Unit, save: (Int, colors = SliderDefaults.colors(inactiveTrackColor = MaterialTheme.colorScheme.onSurfaceVariant) ) Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.Bottom) { - Text(text = "Show passes above", fontSize = 18.sp, modifier = Modifier.weight(1f)) + Text(text = "Show passes above", fontSize = 16.sp, modifier = Modifier.weight(1f)) Icon( painter = painterResource(id = R.drawable.ic_elevation), contentDescription = null, @@ -108,7 +125,7 @@ fun FilterDialog(hours: Int, elevation: Double, dismiss: () -> Unit, save: (Int, ) Text( text = "${elevationValue.intValue}°", - fontSize = 20.sp, + fontSize = 18.sp, fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.primary ) @@ -119,11 +136,47 @@ fun FilterDialog(hours: Int, elevation: Double, dismiss: () -> Unit, save: (Int, valueRange = 0f..90f, colors = SliderDefaults.colors(inactiveTrackColor = MaterialTheme.colorScheme.onSurfaceVariant) ) + LazyVerticalGrid( + columns = GridCells.Adaptive(160.dp), + modifier = Modifier + .background(MaterialTheme.colorScheme.background) + .weight(1f), + horizontalArrangement = Arrangement.spacedBy(1.dp), + 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).", + modifier = Modifier.padding(start = 8.dp, end = 6.dp), + fontWeight = FontWeight.Normal, + color = MaterialTheme.colorScheme.secondary + ) + 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(8.dp) + ) + } + } + } + } Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { CardButton(onClick = { dismiss() }, text = stringResource(id = R.string.btn_cancel)) CardButton( onClick = { - save(hoursValue.intValue, elevationValue.intValue.toDouble()) + save(hoursValue.intValue, elevationValue.intValue.toDouble(), selected) dismiss() }, text = stringResource(id = R.string.btn_accept) ) @@ -132,54 +185,3 @@ fun FilterDialog(hours: Int, elevation: Double, dismiss: () -> Unit, save: (Int, } } } - -@Preview(showBackground = true) -@Composable -private fun ModesDialogPreview() { - val allModes = listOf( - "AFSK", "AFSK S-Net", "AFSK SALSAT", "AHRPT", "AM", "APT", "BPSK", "BPSK PMT-A3", - "CERTO", "CW", "DQPSK", "DSTAR", "DUV", "FFSK", "FM", "FMN", "FSK", "FSK AX.100 Mode 5", - "FSK AX.100 Mode 6", "FSK AX.25 G3RUH", "GFSK", "GFSK Rktr", "GMSK", "HRPT", "LoRa", - "LRPT", "LSB", "MFSK", "MSK", "MSK AX.100 Mode 5", "MSK AX.100 Mode 6", "OFDM", "OQPSK", - "PSK", "PSK31", "PSK63", "QPSK", "QPSK31", "QPSK63", "SSTV", "USB", "WSJT" - ) - MainTheme { ModesDialog(allModes, listOf("AFSK", "AFSK S-Net"), {}) {} } -} - -@Composable -fun ModesDialog(items: List, selected: List, dismiss: () -> Unit, select: (String) -> Unit) { - Dialog(onDismissRequest = { dismiss() }) { - ElevatedCard(modifier = Modifier.fillMaxHeight(0.75f)) { - LazyVerticalGrid( - columns = GridCells.Adaptive(160.dp), - modifier = Modifier.background(MaterialTheme.colorScheme.background), - horizontalArrangement = Arrangement.spacedBy(1.dp), - verticalArrangement = Arrangement.spacedBy(1.dp) - ) { - itemsIndexed(items) { index, item -> - Surface { - Row(verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .background(MaterialTheme.colorScheme.surface) - .clickable { select(item) }) { - Text( - text = "$index).", - modifier = Modifier.padding(start = 12.dp, end = 6.dp), - fontWeight = FontWeight.Normal, - color = MaterialTheme.colorScheme.secondary - ) - Text( - text = item, - modifier = Modifier.weight(1f), - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Checkbox(checked = selected.contains(item), onCheckedChange = { select(item) }) - } - } - } - } - } - } -} diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt index 84f5c2ca..5029e514 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt @@ -23,9 +23,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -36,9 +33,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.lifecycle.viewmodel.compose.viewModel import com.rtbishop.look4sat.R -import com.rtbishop.look4sat.domain.model.DataState import com.rtbishop.look4sat.domain.predict.NearEarthSatellite import com.rtbishop.look4sat.domain.predict.OrbitalData import com.rtbishop.look4sat.domain.predict.SatPass @@ -53,27 +48,16 @@ import java.util.* private val sdf = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH) @Composable -fun PassesScreen(navToRadar: (Int, Long) -> Unit) { - val viewModel = viewModel(PassesViewModel::class.java, factory = PassesViewModel.Factory) - val state = viewModel.passes.collectAsState(initial = null) - val timerText = viewModel.timerText.collectAsState(initial = null) - val isRefreshing = state.value is DataState.Loading - val refreshState = rememberPullRefreshState(refreshing = isRefreshing, - onRefresh = { viewModel.calculatePasses() }) - - val value = state.value - var passes = emptyList() - if (value is DataState.Success) passes = value.data - - val showDialog = rememberSaveable { mutableStateOf(false) } - val toggleDialog = { showDialog.value = showDialog.value.not() } - if (showDialog.value) { - val (hoursAhead, minElevation) = viewModel.getFilterSettings() - FilterDialog(hoursAhead, minElevation, { toggleDialog() }) { newHours, newElevation -> - viewModel.calculatePasses(System.currentTimeMillis(), newHours, newElevation) +fun PassesScreen(uiState: PassesState, navToRadar: (Int, Long) -> Unit) { + val refreshState = rememberPullRefreshState(refreshing = uiState.isRefreshing, onRefresh = { + uiState.takeAction(PassesAction.RefreshPasses) + }) + val toggleDialog = { uiState.takeAction(PassesAction.ToggleFilterDialog) } + if (uiState.isDialogShown) { + FilterDialog(uiState.hours, uiState.elevation, uiState.modes, toggleDialog) { hours, elevation, modes -> + uiState.takeAction(PassesAction.ApplyFilter(hours, elevation, modes)) } } - Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { ElevatedCard(modifier = Modifier.height(52.dp)) { Row( @@ -88,11 +72,11 @@ fun PassesScreen(navToRadar: (Int, Long) -> Unit) { ) } Column(verticalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.weight(1f)) { - Text(text = timerText.value?.first ?: "Null") - Text(text = timerText.value?.second ?: "Null") + Text(text = "Next - Id:${uiState.nextId}", maxLines = 1, overflow = TextOverflow.Ellipsis) + Text(text = uiState.nextName, maxLines = 1, overflow = TextOverflow.Ellipsis) } Text( - text = timerText.value?.third ?: "00:00:00", + text = uiState.nextTime, fontSize = 36.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary, @@ -103,7 +87,7 @@ fun PassesScreen(navToRadar: (Int, Long) -> Unit) { ) } } - PassesCard(refreshState, isRefreshing, passes, navToRadar) + PassesCard(refreshState, uiState.isRefreshing, uiState.itemsList, navToRadar) } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesState.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesState.kt new file mode 100644 index 00000000..ec0da19e --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesState.kt @@ -0,0 +1,23 @@ +package com.rtbishop.look4sat.presentation.passes + +import com.rtbishop.look4sat.domain.predict.SatPass + +data class PassesState( + val isDialogShown: Boolean, + val isRefreshing: Boolean, + val isUtc: Boolean, + val nextId: Int, + val nextName: String, + val nextTime: String, + val hours: Int, + val elevation: Double, + val modes: List, + val itemsList: List, + val takeAction: (PassesAction) -> Unit +) + +sealed class PassesAction { + data class ApplyFilter(val hoursAhead: Int, val minElevation: Double, val modes: List) : PassesAction() + data object RefreshPasses : PassesAction() + data object ToggleFilterDialog : PassesAction() +} diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesViewModel.kt index b4d92661..d87a2fdb 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesViewModel.kt @@ -17,13 +17,14 @@ */ package com.rtbishop.look4sat.presentation.passes +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import com.rtbishop.look4sat.MainApplication -import com.rtbishop.look4sat.domain.model.DataState import com.rtbishop.look4sat.domain.model.PassesSettings import com.rtbishop.look4sat.domain.predict.SatPass import com.rtbishop.look4sat.domain.repository.ISatelliteRepo @@ -32,8 +33,6 @@ import com.rtbishop.look4sat.domain.utility.toTimerString import kotlinx.coroutines.Job import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.isActive import kotlinx.coroutines.launch @@ -41,46 +40,35 @@ class PassesViewModel( private val satelliteRepo: ISatelliteRepo, private val settingsRepo: ISettingsRepo ) : ViewModel() { - private val _passes = MutableStateFlow>>(DataState.Loading) - private val timerData = Triple("Next - Id:Null", "Name: Null", "00:00:00") - private val _timerText = MutableStateFlow(timerData) - private var passesProcessing: Job? = null - val passes: StateFlow>> = _passes - val timerText: StateFlow?> = _timerText - - fun getFilterSettings() = settingsRepo.passesSettings.value - -// fun shouldUseUTC() = settingsRepo.otherSettings.value.utcState + private val _uiState = mutableStateOf( + PassesState( + isDialogShown = false, + isRefreshing = true, + isUtc = settingsRepo.otherSettings.value.stateOfUtc, + nextId = 0, + nextName = "...", + nextTime = "00:00:00", + hours = settingsRepo.passesSettings.value.hoursAhead, + elevation = settingsRepo.passesSettings.value.minElevation, + modes = settingsRepo.passesSettings.value.selectedModes, + itemsList = emptyList(), + takeAction = ::handleAction + ) + ) + private var processing: Job? = null + val uiState: State = _uiState init { viewModelScope.launch { satelliteRepo.initRepository() } viewModelScope.launch { satelliteRepo.passes.collect { passes -> - passesProcessing?.cancelAndJoin() - passesProcessing = viewModelScope.launch { + processing?.cancelAndJoin() + processing = viewModelScope.launch { while (isActive) { val timeNow = System.currentTimeMillis() val newPasses = satelliteRepo.processPasses(passes, timeNow) - - if (newPasses.isNotEmpty()) { - try { - val nextPass = newPasses.first { it.aosTime.minus(timeNow) > 0 } - val catNum = nextPass.catNum - val name = nextPass.name - val millisBeforeStart = nextPass.aosTime.minus(timeNow) - val timerString = millisBeforeStart.toTimerString() - _timerText.emit(Triple("Next - Id:$catNum", name, timerString)) - } catch (exception: NoSuchElementException) { - val lastPass = newPasses.last() - val catNum = lastPass.catNum - val name = lastPass.name - val millisBeforeEnd = lastPass.losTime.minus(timeNow) - val timerString = millisBeforeEnd.toTimerString() - _timerText.emit(Triple("Next - Id:$catNum", name, timerString)) - } - } else _timerText.emit(timerData) - - _passes.emit(DataState.Success(newPasses)) + setPassInfo(newPasses, timeNow) + _uiState.value = _uiState.value.copy(isRefreshing = false, itemsList = newPasses) delay(1000) } } @@ -88,18 +76,45 @@ class PassesViewModel( } } - fun calculatePasses() = viewModelScope.launch { - _passes.emit(DataState.Loading) - passesProcessing?.cancelAndJoin() + private fun handleAction(action: PassesAction) { + when (action) { + is PassesAction.ApplyFilter -> applyFilter(action.hoursAhead, action.minElevation, action.modes) + PassesAction.RefreshPasses -> refreshPasses() + PassesAction.ToggleFilterDialog -> toggleFilterDialog() + } + } + + private fun applyFilter(hoursAhead: Int, minElevation: Double, modes: List) = viewModelScope.launch { + _uiState.value = _uiState.value.copy(isRefreshing = true) + processing?.cancelAndJoin() + settingsRepo.setPassesSettings(PassesSettings(hoursAhead, minElevation, modes)) + _uiState.value = _uiState.value.copy(hours = hoursAhead, elevation = minElevation, modes = modes) + satelliteRepo.calculatePasses(System.currentTimeMillis(), hoursAhead, minElevation) + } + + private fun refreshPasses() = viewModelScope.launch { + _uiState.value = _uiState.value.copy(isRefreshing = true) + processing?.cancelAndJoin() val (hoursAhead, minElevation) = settingsRepo.passesSettings.value satelliteRepo.calculatePasses(System.currentTimeMillis(), hoursAhead, minElevation) } - fun calculatePasses(timeRef: Long, hoursAhead: Int, minElev: Double) = viewModelScope.launch { - _passes.emit(DataState.Loading) - passesProcessing?.cancelAndJoin() - settingsRepo.updatePassesSettings(PassesSettings(hoursAhead, minElev)) - satelliteRepo.calculatePasses(timeRef, hoursAhead, minElev) + private fun setPassInfo(passes: List, timeNow: Long) { + if (passes.isEmpty()) return + try { + val nextPass = passes.first { it.aosTime.minus(timeNow) > 0 } + val time = nextPass.aosTime.minus(timeNow).toTimerString() + _uiState.value = _uiState.value.copy(nextId = nextPass.catNum, nextName = nextPass.name, nextTime = time) + } catch (exception: NoSuchElementException) { + val lastPass = passes.last() + val time = lastPass.losTime.minus(timeNow).toTimerString() + _uiState.value = _uiState.value.copy(nextId = lastPass.catNum, nextName = lastPass.name, nextTime = time) + } + } + + private fun toggleFilterDialog() { + val currentDialogState = _uiState.value.isDialogShown + _uiState.value = _uiState.value.copy(isDialogShown = currentDialogState.not()) } companion object { diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarViewModel.kt index db5d06e9..ce8556d4 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarViewModel.kt @@ -148,7 +148,7 @@ class RadarViewModel( private fun processRadios(radios: List, satellite: Satellite, time: Long) { viewModelScope.launch { delay(125) - val list = satelliteRepo.processRadios(satellite, stationPos, radios, time) + val list = satelliteRepo.getRadios(satellite, stationPos, radios, time) transmitters.value = list } } diff --git a/data/src/main/java/com/rtbishop/look4sat/data/repository/SatelliteRepo.kt b/data/src/main/java/com/rtbishop/look4sat/data/repository/SatelliteRepo.kt index f893a1b4..b5d5c28e 100644 --- a/data/src/main/java/com/rtbishop/look4sat/data/repository/SatelliteRepo.kt +++ b/data/src/main/java/com/rtbishop/look4sat/data/repository/SatelliteRepo.kt @@ -59,9 +59,7 @@ class SatelliteRepo( return withContext(dispatcher) { sat.getPosition(pos, time) } } - override suspend fun getTrack( - sat: Satellite, pos: GeoPos, start: Long, end: Long - ): List { + override suspend fun getTrack(sat: Satellite, pos: GeoPos, start: Long, end: Long): List { return withContext(dispatcher) { val positions = mutableListOf() var currentTime = start @@ -73,9 +71,7 @@ class SatelliteRepo( } } - override suspend fun processRadios( - sat: Satellite, pos: GeoPos, radios: List, time: Long - ): List { + override suspend fun getRadios(sat: Satellite, pos: GeoPos, radios: List, time: Long): List { return withContext(dispatcher) { val satPos = sat.getPosition(pos, time) val copiedList = radios.map { it.copy() } diff --git a/data/src/main/java/com/rtbishop/look4sat/data/repository/SettingsRepo.kt b/data/src/main/java/com/rtbishop/look4sat/data/repository/SettingsRepo.kt index 799c3de4..9442f65c 100644 --- a/data/src/main/java/com/rtbishop/look4sat/data/repository/SettingsRepo.kt +++ b/data/src/main/java/com/rtbishop/look4sat/data/repository/SettingsRepo.kt @@ -82,26 +82,19 @@ class SettingsRepo(private val manager: LocationManager, private val preferences private val _passesSettings = MutableStateFlow(getPassesSettings()) override val passesSettings: StateFlow = _passesSettings - override fun getSelectedModes(): List { - val selectedModesString = preferences.getString(keySelectedModes, null) - return selectedModesString?.split(separatorComma)?.sorted() ?: emptyList() - } - - override fun setSelectedModes(modes: List) { - val selectedModes = modes.joinToString(separatorComma) - preferences.edit { putString(keySelectedModes, selectedModes) } - } - - override fun updatePassesSettings(settings: PassesSettings) = preferences.edit { - putInt(keyFilterHoursAhead, settings.filterHoursAhead) - putLong(keyFilterMinElevation, settings.filterMinElevation.toRawBits()) + override fun setPassesSettings(settings: PassesSettings) = preferences.edit { + putInt(keyFilterHoursAhead, settings.hoursAhead) + putLong(keyFilterMinElevation, settings.minElevation.toRawBits()) + putString(keySelectedModes, settings.selectedModes.joinToString(separatorComma)) _passesSettings.value = settings } private fun getPassesSettings(): PassesSettings { val hoursAhead = preferences.getInt(keyFilterHoursAhead, 24) val minElevation = Double.fromBits(preferences.getLong(keyFilterMinElevation, 16.0.toRawBits())) - return PassesSettings(hoursAhead, minElevation) + val selectedModesString = preferences.getString(keySelectedModes, null) + val selectedModes = selectedModesString?.split(separatorComma)?.sorted() ?: emptyList() + return PassesSettings(hoursAhead, minElevation, selectedModes) } //endregion diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/model/DataState.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/model/DataState.kt deleted file mode 100644 index d4db9120..00000000 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/model/DataState.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Look4Sat. Amateur radio satellite tracker and pass predictor. - * Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.rtbishop.look4sat.domain.model - -sealed class DataState { - data class Success(val data: T) : DataState() - data class Exception(val message: String?) : DataState() - data object Loading : DataState() -} diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/model/Settings.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/model/Settings.kt index 3ab535e7..71c604df 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/model/Settings.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/model/Settings.kt @@ -2,7 +2,7 @@ package com.rtbishop.look4sat.domain.model data class DatabaseState(val numberOfRadios: Int, val numberOfSatellites: Int, val updateTimestamp: Long) -data class PassesSettings(val filterHoursAhead: Int, val filterMinElevation: Double) +data class PassesSettings(val hoursAhead: Int, val minElevation: Double, val selectedModes: List) data class OtherSettings( val stateOfAutoUpdate: Boolean, diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/repository/IDatabaseRepo.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/repository/IDatabaseRepo.kt index 204549c1..6fc3dd8c 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/repository/IDatabaseRepo.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/repository/IDatabaseRepo.kt @@ -18,10 +18,7 @@ package com.rtbishop.look4sat.domain.repository interface IDatabaseRepo { - suspend fun updateFromFile(uri: String) - suspend fun updateFromRemote() - suspend fun clearAllData() } diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISatelliteRepo.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISatelliteRepo.kt index 4a6ad3c4..a8047716 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISatelliteRepo.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISatelliteRepo.kt @@ -8,24 +8,13 @@ import com.rtbishop.look4sat.domain.predict.Satellite import kotlinx.coroutines.flow.StateFlow interface ISatelliteRepo { - val passes: StateFlow> - val satellites: StateFlow> - suspend fun getRadiosWithId(id: Int): List - suspend fun initRepository() - suspend fun getPosition(sat: Satellite, pos: GeoPos, time: Long): SatPos - suspend fun getTrack(sat: Satellite, pos: GeoPos, start: Long, end: Long): List - - suspend fun processRadios( - sat: Satellite, pos: GeoPos, radios: List, time: Long - ): List - + suspend fun getRadios(sat: Satellite, pos: GeoPos, radios: List, time: Long): List suspend fun processPasses(passList: List, time: Long): List - suspend fun calculatePasses(time: Long, hoursAhead: Int = 8, minElevation: Double = 16.0) } diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISelectionRepo.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISelectionRepo.kt index 7df489e0..ee102448 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISelectionRepo.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISelectionRepo.kt @@ -4,20 +4,12 @@ import com.rtbishop.look4sat.domain.model.SatItem import kotlinx.coroutines.flow.Flow interface ISelectionRepo { - fun getCurrentType(): String - fun getTypesList(): List - suspend fun getEntriesFlow(): Flow> - suspend fun setType(type: String) - suspend fun setQuery(query: String) - suspend fun setSelection(selectAll: Boolean) - suspend fun setSelection(ids: List, isTicked: Boolean) - suspend fun saveSelection() } diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISettingsRepo.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISettingsRepo.kt index b146314b..5496e96f 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISettingsRepo.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISettingsRepo.kt @@ -63,9 +63,7 @@ interface ISettingsRepo { //region # Passes filter settings val passesSettings: StateFlow - fun getSelectedModes(): List - fun setSelectedModes(modes: List) - fun updatePassesSettings(settings: PassesSettings) + fun setPassesSettings(settings: PassesSettings) //endregion //region # Station position settings diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/source/ILocalSource.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/source/ILocalSource.kt index 1152226f..610b229c 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/source/ILocalSource.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/source/ILocalSource.kt @@ -23,22 +23,13 @@ import com.rtbishop.look4sat.domain.model.SatRadio import com.rtbishop.look4sat.domain.predict.Satellite interface ILocalSource { - suspend fun getEntriesTotal(): Int - suspend fun getEntriesList(): List - suspend fun getEntriesWithIds(ids: List): List - suspend fun insertEntries(entries: List) - suspend fun deleteEntries() - suspend fun getRadiosTotal(): Int - suspend fun getRadiosWithId(id: Int): List - suspend fun insertRadios(radios: List) - suspend fun deleteRadios() } diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/source/ISensorSource.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/source/ISensorSource.kt index 92e34f8d..89dcd864 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/source/ISensorSource.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/source/ISensorSource.kt @@ -3,10 +3,7 @@ package com.rtbishop.look4sat.domain.source import kotlinx.coroutines.flow.StateFlow interface ISensorSource { - val orientation: StateFlow> - fun enableSensor() - fun disableSensor() }