diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesDialog.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesDialog.kt index 6d2dcbde..ec4b4642 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesDialog.kt @@ -9,11 +9,13 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.material3.Checkbox import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.RadioButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight @@ -25,16 +27,21 @@ import com.rtbishop.look4sat.presentation.components.SharedDialog @Preview(showBackground = true) @Composable -private fun TypeDialogPreview() { +private fun MultiTypesDialogPreview() { val types = listOf("All", "Amateur", "Geostationary", "Military", "Weather") - MainTheme { TypesDialog(types, "All", {}) {} } + MainTheme { MultiTypesDialog(types, emptyList(), {}) {} } } @Composable -fun TypesDialog( - items: List, selected: String, dismiss: () -> Unit, select: (String) -> Unit +fun MultiTypesDialog( + allTypes: List, types: List, cancel: () -> Unit, accept: (List) -> Unit ) { - SharedDialog(title = "Select category", onCancel = dismiss, onAccept = {}) { + val selected = remember { mutableStateListOf().apply { addAll(types) } } + val select = { type: String -> + if (selected.contains(type)) selected.remove(type) else selected.add(type) + } + val onAccept = { accept(selected.toList()) } + SharedDialog(title = "Select category", onCancel = cancel, onAccept = onAccept) { LazyVerticalGrid( columns = GridCells.Adaptive(240.dp), modifier = Modifier @@ -44,11 +51,13 @@ fun TypesDialog( verticalArrangement = Arrangement.spacedBy(1.dp) ) { item { HorizontalDivider(color = MaterialTheme.colorScheme.surface) } - itemsIndexed(items) { index, item -> - Row(verticalAlignment = Alignment.CenterVertically, + itemsIndexed(allTypes) { index, item -> + Row( + verticalAlignment = Alignment.CenterVertically, modifier = Modifier .background(MaterialTheme.colorScheme.surface) - .clickable { select(item) }) { + .clickable { select(item) } + ) { Text( text = "${index + 1}).", modifier = Modifier.padding(start = 16.dp, end = 8.dp), @@ -62,9 +71,9 @@ fun TypesDialog( maxLines = 1, overflow = TextOverflow.Ellipsis ) - RadioButton( - selected = item == selected, - onClick = null, + Checkbox( + checked = selected.contains(item), + onCheckedChange = null, modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) ) } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesScreen.kt index 557f1bf8..44903f9f 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesScreen.kt @@ -1,6 +1,8 @@ package com.rtbishop.look4sat.presentation.satellites +import androidx.compose.foundation.MarqueeSpacing import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -62,8 +64,8 @@ fun NavGraphBuilder.satellitesDestination(navigateToPasses: () -> Unit) { private fun SatellitesScreen(uiState: SatellitesState, navigateToPasses: () -> Unit) { val toggleDialog = { uiState.takeAction(SatellitesAction.ToggleTypesDialog) } if (uiState.isDialogShown) { - TypesDialog(items = uiState.typesList, selected = uiState.currentType, toggleDialog) { - uiState.takeAction(SatellitesAction.SelectType(it)) + MultiTypesDialog(allTypes = uiState.typesList, types = uiState.currentTypes, toggleDialog) { + uiState.takeAction(SatellitesAction.SelectTypes(it)) } } val unselectAll = { uiState.takeAction(SatellitesAction.UnselectAll) } @@ -75,7 +77,7 @@ private fun SatellitesScreen(uiState: SatellitesState, navigateToPasses: () -> U uiState.takeAction(SatellitesAction.SaveSelection) navigateToPasses() }) - MiddleBar(uiState.currentType, { toggleDialog() }, { unselectAll() }, { selectAll() }) + MiddleBar(uiState.currentTypes, { toggleDialog() }, { unselectAll() }, { selectAll() }) ElevatedCard(modifier = Modifier.fillMaxSize()) { if (uiState.isLoading) { CardLoadingIndicator() @@ -159,19 +161,22 @@ private fun SaveButton(saveSelection: () -> Unit, modifier: Modifier = Modifier) @Preview(showBackground = true) @Composable -private fun MiddleBarPreview() = MainTheme { MiddleBar("Amateur", {}, {}, {}) } +private fun MiddleBarPreview() = MainTheme { MiddleBar(listOf("Amateur"), {}, {}, {}) } @Composable -private fun MiddleBar(type: String, navigate: () -> Unit, uncheck: () -> Unit, check: () -> Unit) { +private fun MiddleBar( + types: List, navigate: () -> Unit, uncheck: () -> Unit, check: () -> Unit +) { Row(horizontalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.height(48.dp)) { - TypeCard(type = type, { navigate() }, modifier = Modifier.weight(1f)) + TypeCard(types = types, { navigate() }, modifier = Modifier.weight(1f)) CardIcon(onClick = { uncheck() }, iconId = R.drawable.ic_check_off) CardIcon(onClick = { check() }, iconId = R.drawable.ic_check_on) } } @Composable -private fun TypeCard(type: String, onClick: () -> Unit, modifier: Modifier = Modifier) { +private fun TypeCard(types: List, onClick: () -> Unit, modifier: Modifier = Modifier) { + val typesText = if (types.isEmpty()) "All" else types.joinToString(", ") ElevatedCard(modifier = modifier) { Row(horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically, @@ -186,10 +191,12 @@ private fun TypeCard(type: String, onClick: () -> Unit, modifier: Modifier = Mod .padding(start = 6.dp) ) Text( - text = "Type: $type", + text = "Types: $typesText", fontSize = 16.sp, fontWeight = FontWeight.Medium, - modifier = modifier.padding(start = 12.dp, end = 6.dp, bottom = 2.dp) + modifier = modifier + .basicMarquee(iterations = Int.MAX_VALUE, spacing = MarqueeSpacing(0.dp)) + .padding(start = 12.dp, end = 6.dp, bottom = 2.dp) ) } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesState.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesState.kt index 0661b5d8..f43b1ac1 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesState.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesState.kt @@ -6,7 +6,7 @@ data class SatellitesState( val isDialogShown: Boolean, val isLoading: Boolean, val itemsList: List, - val currentType: String, + val currentTypes: List, val typesList: List, val takeAction: (SatellitesAction) -> Unit ) @@ -16,7 +16,7 @@ sealed class SatellitesAction { data class SearchFor(val query: String) : SatellitesAction() data object SelectAll : SatellitesAction() data class SelectSingle(val id: Int, val isTicked: Boolean) : SatellitesAction() - data class SelectType(val type: String) : SatellitesAction() + data class SelectTypes(val types: List) : SatellitesAction() data object ToggleTypesDialog : SatellitesAction() data object UnselectAll : SatellitesAction() } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesViewModel.kt index 317b5747..b4865b86 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesViewModel.kt @@ -31,13 +31,13 @@ import kotlinx.coroutines.launch class SatellitesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel() { - private val defaultType = "All" + private val defaultTypes = selectionRepo.getCurrentTypes() private val _uiState = MutableStateFlow( SatellitesState( isDialogShown = false, isLoading = true, itemsList = emptyList(), - currentType = defaultType, + currentTypes = defaultTypes, typesList = selectionRepo.getTypesList(), takeAction = ::handleAction ) @@ -47,7 +47,7 @@ class SatellitesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel init { viewModelScope.launch { delay(1000) - selectionRepo.setType(defaultType) + selectionRepo.setTypes(defaultTypes) selectionRepo.getEntriesFlow().collect { items -> _uiState.value = _uiState.value.copy(isLoading = false, itemsList = items) } @@ -60,7 +60,7 @@ class SatellitesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel is SatellitesAction.SearchFor -> searchFor(action.query) SatellitesAction.SelectAll -> selectAll(true) is SatellitesAction.SelectSingle -> selectSingle(action.id, action.isTicked) - is SatellitesAction.SelectType -> selectType(action.type) + is SatellitesAction.SelectTypes -> selectTypes(action.types) SatellitesAction.ToggleTypesDialog -> toggleTypesDialog() SatellitesAction.UnselectAll -> selectAll(false) } @@ -78,9 +78,9 @@ class SatellitesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel selectionRepo.setSelection(listOf(id), isTicked.not()) } - private fun selectType(type: String) = viewModelScope.launch { - selectionRepo.setType(type) - _uiState.value = _uiState.value.copy(currentType = type, isDialogShown = false) + private fun selectTypes(types: List) = viewModelScope.launch { + selectionRepo.setTypes(types) + _uiState.value = _uiState.value.copy(currentTypes = types, isDialogShown = false) } private fun toggleTypesDialog() { diff --git a/data/src/main/java/com/rtbishop/look4sat/data/repository/SelectionRepo.kt b/data/src/main/java/com/rtbishop/look4sat/data/repository/SelectionRepo.kt index 4ee5d9a6..0bd1c4e3 100644 --- a/data/src/main/java/com/rtbishop/look4sat/data/repository/SelectionRepo.kt +++ b/data/src/main/java/com/rtbishop/look4sat/data/repository/SelectionRepo.kt @@ -21,18 +21,20 @@ class SelectionRepo( ) : ISelectionRepo { private val currentItems = MutableStateFlow>(emptyList()) - private val currentType = MutableStateFlow("All") + private val currentTypes = MutableStateFlow(settingsRepo.selectedTypes.value) private val currentQuery = MutableStateFlow("") - private val itemsWithType = currentType.flatMapLatest { type -> - currentItems.map { items -> items.filterByType(type) } + private val itemsWithTypes = currentTypes.flatMapLatest { types: List -> + currentItems.map { items -> items.filterByTypes(types) } } private val itemsWithQuery = currentQuery.flatMapLatest { query -> - itemsWithType.map { items -> items.filterByQuery(query) } + itemsWithTypes.map { items -> items.filterByQuery(query) } } - override fun getCurrentType() = currentType.value + override fun getCurrentTypes() = currentTypes.value - override fun getTypesList() = Sources.satelliteDataUrls.keys.sorted() + override fun getTypesList() = Sources.satelliteDataUrls.keys.sorted().toMutableList().apply { + removeAt(0) + } override suspend fun getEntriesFlow() = withContext(dispatcher) { val selectedIds = settingsRepo.selectedIds.value @@ -42,8 +44,9 @@ class SelectionRepo( return@withContext itemsWithQuery } - override suspend fun setType(type: String) = withContext(dispatcher) { - currentType.value = type + override suspend fun setTypes(types: List) { + currentTypes.value = types + settingsRepo.setSelectedTypes(types) } override suspend fun setQuery(query: String) = withContext(dispatcher) { @@ -65,11 +68,11 @@ class SelectionRepo( settingsRepo.setSelectedIds(currentSelection) } - private suspend fun List.filterByType(type: String) = withContext(dispatcher) { - if (type == "All") return@withContext this@filterByType - val catnums = settingsRepo.getSatelliteTypeIds(type) - if (catnums.isEmpty()) return@withContext this@filterByType - return@withContext this@filterByType.filter { item -> item.catnum in catnums } + private suspend fun List.filterByTypes(types: List) = withContext(dispatcher) { + if (types.isEmpty()) return@withContext this@filterByTypes + val catnums = settingsRepo.getSatelliteTypesIds(types) + if (catnums.isEmpty()) return@withContext this@filterByTypes + return@withContext this@filterByTypes.filter { item -> item.catnum in catnums } } private suspend fun List.filterByQuery(query: String) = withContext(dispatcher) { 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 1dc142e4..0dd599b8 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 @@ -48,6 +48,7 @@ class SettingsRepo(private val manager: LocationManager, private val preferences private val keyRotatorPort = "rotatorPort" private val keyRotatorState = "rotatorState" private val keySelectedIds = "selectedIds" + private val keySelectedTypes = "selectedTypes" private val keySelectedModes = "selectedModes" private val keyStateOfAutoUpdate = "stateOfAutoUpdate" private val keyStateOfSensors = "stateOfSensors" @@ -64,7 +65,9 @@ class SettingsRepo(private val manager: LocationManager, private val preferences //region # Satellites selection settings private val _satelliteSelection = MutableStateFlow(getSelectedIds()) + private val _typesSelection = MutableStateFlow(getSelectedTypes()) override val selectedIds: StateFlow> = _satelliteSelection + override val selectedTypes: StateFlow> = _typesSelection override fun setSelectedIds(ids: List) { val selectionString = ids.joinToString(separatorComma) @@ -72,11 +75,23 @@ class SettingsRepo(private val manager: LocationManager, private val preferences _satelliteSelection.value = ids } + override fun setSelectedTypes(types: List) { + val typesString = types.joinToString(separatorComma) + preferences.edit { putString(keySelectedTypes, typesString) } + _typesSelection.value = types + } + private fun getSelectedIds(): List { val selectionString = preferences.getString(keySelectedIds, null) if (selectionString.isNullOrEmpty()) return emptyList() return selectionString.split(separatorComma).map { it.toInt() } } + + private fun getSelectedTypes(): List { + val typesString = preferences.getString(keySelectedTypes, null) + if (typesString.isNullOrEmpty()) return emptyList() + return typesString.split(separatorComma) + } //endregion //region # Passes filter settings @@ -171,10 +186,18 @@ class SettingsRepo(private val manager: LocationManager, private val preferences private val _databaseState = MutableStateFlow(getDatabaseState()) override val databaseState: StateFlow = _databaseState - override fun getSatelliteTypeIds(type: String): List { - val typesString = preferences.getString("type$type", null) - if (typesString.isNullOrBlank()) return emptyList() - return typesString.split(separatorComma).map { it.toInt() } + override fun getSatelliteTypesIds(types: List): List { + val idsSet = mutableSetOf() + types.forEach { type -> + val typeString = preferences.getString("type$type", null) + val typeIds = if (typeString.isNullOrBlank()) { + emptyList() + } else { + typeString.split(separatorComma).map { it.toInt() } + } + idsSet.addAll(typeIds) + } + return idsSet.toList() } override fun setSatelliteTypeIds(type: String, ids: List) { 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 ee102448..5e5c235d 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,10 +4,10 @@ import com.rtbishop.look4sat.domain.model.SatItem import kotlinx.coroutines.flow.Flow interface ISelectionRepo { - fun getCurrentType(): String + fun getCurrentTypes(): List fun getTypesList(): List suspend fun getEntriesFlow(): Flow> - suspend fun setType(type: String) + suspend fun setTypes(types: List) suspend fun setQuery(query: String) suspend fun setSelection(selectAll: Boolean) suspend fun setSelection(ids: List, isTicked: Boolean) 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 6beb0da5..a332bd50 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 @@ -27,7 +27,9 @@ interface ISettingsRepo { //region # Satellites selection settings val selectedIds: StateFlow> + val selectedTypes: StateFlow> fun setSelectedIds(ids: List) + fun setSelectedTypes(types: List) //endregion //region # Passes filter settings @@ -44,7 +46,7 @@ interface ISettingsRepo { //region # Database update settings val databaseState: StateFlow - fun getSatelliteTypeIds(type: String): List + fun getSatelliteTypesIds(types: List): List fun setSatelliteTypeIds(type: String, ids: List) fun updateDatabaseState(state: DatabaseState) //endregion