Added multi selection for Satellite types filter dialog

develop
Arty Bishop 2024-12-16 10:27:50 +00:00
rodzic 95af794572
commit be82b19b37
8 zmienionych plików z 94 dodań i 50 usunięć

Wyświetl plik

@ -9,11 +9,13 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.material3.Checkbox
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
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.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@ -25,16 +27,21 @@ import com.rtbishop.look4sat.presentation.components.SharedDialog
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
private fun TypeDialogPreview() { private fun MultiTypesDialogPreview() {
val types = listOf("All", "Amateur", "Geostationary", "Military", "Weather") val types = listOf("All", "Amateur", "Geostationary", "Military", "Weather")
MainTheme { TypesDialog(types, "All", {}) {} } MainTheme { MultiTypesDialog(types, emptyList(), {}) {} }
} }
@Composable @Composable
fun TypesDialog( fun MultiTypesDialog(
items: List<String>, selected: String, dismiss: () -> Unit, select: (String) -> Unit allTypes: List<String>, types: List<String>, cancel: () -> Unit, accept: (List<String>) -> Unit
) { ) {
SharedDialog(title = "Select category", onCancel = dismiss, onAccept = {}) { val selected = remember { mutableStateListOf<String>().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( LazyVerticalGrid(
columns = GridCells.Adaptive(240.dp), columns = GridCells.Adaptive(240.dp),
modifier = Modifier modifier = Modifier
@ -44,11 +51,13 @@ fun TypesDialog(
verticalArrangement = Arrangement.spacedBy(1.dp) verticalArrangement = Arrangement.spacedBy(1.dp)
) { ) {
item { HorizontalDivider(color = MaterialTheme.colorScheme.surface) } item { HorizontalDivider(color = MaterialTheme.colorScheme.surface) }
itemsIndexed(items) { index, item -> itemsIndexed(allTypes) { index, item ->
Row(verticalAlignment = Alignment.CenterVertically, Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier modifier = Modifier
.background(MaterialTheme.colorScheme.surface) .background(MaterialTheme.colorScheme.surface)
.clickable { select(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),
@ -62,9 +71,9 @@ fun TypesDialog(
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
RadioButton( Checkbox(
selected = item == selected, checked = selected.contains(item),
onClick = null, onCheckedChange = null,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
) )
} }

Wyświetl plik

@ -1,6 +1,8 @@
package com.rtbishop.look4sat.presentation.satellites package com.rtbishop.look4sat.presentation.satellites
import androidx.compose.foundation.MarqueeSpacing
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -62,8 +64,8 @@ fun NavGraphBuilder.satellitesDestination(navigateToPasses: () -> Unit) {
private fun SatellitesScreen(uiState: SatellitesState, navigateToPasses: () -> Unit) { private fun SatellitesScreen(uiState: SatellitesState, navigateToPasses: () -> Unit) {
val toggleDialog = { uiState.takeAction(SatellitesAction.ToggleTypesDialog) } val toggleDialog = { uiState.takeAction(SatellitesAction.ToggleTypesDialog) }
if (uiState.isDialogShown) { if (uiState.isDialogShown) {
TypesDialog(items = uiState.typesList, selected = uiState.currentType, toggleDialog) { MultiTypesDialog(allTypes = uiState.typesList, types = uiState.currentTypes, toggleDialog) {
uiState.takeAction(SatellitesAction.SelectType(it)) uiState.takeAction(SatellitesAction.SelectTypes(it))
} }
} }
val unselectAll = { uiState.takeAction(SatellitesAction.UnselectAll) } val unselectAll = { uiState.takeAction(SatellitesAction.UnselectAll) }
@ -75,7 +77,7 @@ private fun SatellitesScreen(uiState: SatellitesState, navigateToPasses: () -> U
uiState.takeAction(SatellitesAction.SaveSelection) uiState.takeAction(SatellitesAction.SaveSelection)
navigateToPasses() navigateToPasses()
}) })
MiddleBar(uiState.currentType, { toggleDialog() }, { unselectAll() }, { selectAll() }) MiddleBar(uiState.currentTypes, { toggleDialog() }, { unselectAll() }, { selectAll() })
ElevatedCard(modifier = Modifier.fillMaxSize()) { ElevatedCard(modifier = Modifier.fillMaxSize()) {
if (uiState.isLoading) { if (uiState.isLoading) {
CardLoadingIndicator() CardLoadingIndicator()
@ -159,19 +161,22 @@ private fun SaveButton(saveSelection: () -> Unit, modifier: Modifier = Modifier)
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
private fun MiddleBarPreview() = MainTheme { MiddleBar("Amateur", {}, {}, {}) } private fun MiddleBarPreview() = MainTheme { MiddleBar(listOf("Amateur"), {}, {}, {}) }
@Composable @Composable
private fun MiddleBar(type: String, navigate: () -> Unit, uncheck: () -> Unit, check: () -> Unit) { private fun MiddleBar(
types: List<String>, navigate: () -> Unit, uncheck: () -> Unit, check: () -> Unit
) {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.height(48.dp)) { 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 = { uncheck() }, iconId = R.drawable.ic_check_off)
CardIcon(onClick = { check() }, iconId = R.drawable.ic_check_on) CardIcon(onClick = { check() }, iconId = R.drawable.ic_check_on)
} }
} }
@Composable @Composable
private fun TypeCard(type: String, onClick: () -> Unit, modifier: Modifier = Modifier) { private fun TypeCard(types: List<String>, onClick: () -> Unit, modifier: Modifier = Modifier) {
val typesText = if (types.isEmpty()) "All" else types.joinToString(", ")
ElevatedCard(modifier = modifier) { ElevatedCard(modifier = modifier) {
Row(horizontalArrangement = Arrangement.Start, Row(horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@ -186,10 +191,12 @@ private fun TypeCard(type: String, onClick: () -> Unit, modifier: Modifier = Mod
.padding(start = 6.dp) .padding(start = 6.dp)
) )
Text( Text(
text = "Type: $type", text = "Types: $typesText",
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.Medium, 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)
) )
} }
} }

Wyświetl plik

@ -6,7 +6,7 @@ data class SatellitesState(
val isDialogShown: Boolean, val isDialogShown: Boolean,
val isLoading: Boolean, val isLoading: Boolean,
val itemsList: List<SatItem>, val itemsList: List<SatItem>,
val currentType: String, val currentTypes: List<String>,
val typesList: List<String>, val typesList: List<String>,
val takeAction: (SatellitesAction) -> Unit val takeAction: (SatellitesAction) -> Unit
) )
@ -16,7 +16,7 @@ sealed class SatellitesAction {
data class SearchFor(val query: String) : SatellitesAction() data class SearchFor(val query: String) : SatellitesAction()
data object SelectAll : SatellitesAction() data object SelectAll : SatellitesAction()
data class SelectSingle(val id: Int, val isTicked: Boolean) : SatellitesAction() data class SelectSingle(val id: Int, val isTicked: Boolean) : SatellitesAction()
data class SelectType(val type: String) : SatellitesAction() data class SelectTypes(val types: List<String>) : SatellitesAction()
data object ToggleTypesDialog : SatellitesAction() data object ToggleTypesDialog : SatellitesAction()
data object UnselectAll : SatellitesAction() data object UnselectAll : SatellitesAction()
} }

Wyświetl plik

@ -31,13 +31,13 @@ import kotlinx.coroutines.launch
class SatellitesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel() { class SatellitesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel() {
private val defaultType = "All" private val defaultTypes = selectionRepo.getCurrentTypes()
private val _uiState = MutableStateFlow( private val _uiState = MutableStateFlow(
SatellitesState( SatellitesState(
isDialogShown = false, isDialogShown = false,
isLoading = true, isLoading = true,
itemsList = emptyList(), itemsList = emptyList(),
currentType = defaultType, currentTypes = defaultTypes,
typesList = selectionRepo.getTypesList(), typesList = selectionRepo.getTypesList(),
takeAction = ::handleAction takeAction = ::handleAction
) )
@ -47,7 +47,7 @@ class SatellitesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel
init { init {
viewModelScope.launch { viewModelScope.launch {
delay(1000) delay(1000)
selectionRepo.setType(defaultType) selectionRepo.setTypes(defaultTypes)
selectionRepo.getEntriesFlow().collect { items -> selectionRepo.getEntriesFlow().collect { items ->
_uiState.value = _uiState.value.copy(isLoading = false, itemsList = 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) is SatellitesAction.SearchFor -> searchFor(action.query)
SatellitesAction.SelectAll -> selectAll(true) SatellitesAction.SelectAll -> selectAll(true)
is SatellitesAction.SelectSingle -> selectSingle(action.id, action.isTicked) is SatellitesAction.SelectSingle -> selectSingle(action.id, action.isTicked)
is SatellitesAction.SelectType -> selectType(action.type) is SatellitesAction.SelectTypes -> selectTypes(action.types)
SatellitesAction.ToggleTypesDialog -> toggleTypesDialog() SatellitesAction.ToggleTypesDialog -> toggleTypesDialog()
SatellitesAction.UnselectAll -> selectAll(false) SatellitesAction.UnselectAll -> selectAll(false)
} }
@ -78,9 +78,9 @@ class SatellitesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel
selectionRepo.setSelection(listOf(id), isTicked.not()) selectionRepo.setSelection(listOf(id), isTicked.not())
} }
private fun selectType(type: String) = viewModelScope.launch { private fun selectTypes(types: List<String>) = viewModelScope.launch {
selectionRepo.setType(type) selectionRepo.setTypes(types)
_uiState.value = _uiState.value.copy(currentType = type, isDialogShown = false) _uiState.value = _uiState.value.copy(currentTypes = types, isDialogShown = false)
} }
private fun toggleTypesDialog() { private fun toggleTypesDialog() {

Wyświetl plik

@ -21,18 +21,20 @@ class SelectionRepo(
) : ISelectionRepo { ) : ISelectionRepo {
private val currentItems = MutableStateFlow<List<SatItem>>(emptyList()) private val currentItems = MutableStateFlow<List<SatItem>>(emptyList())
private val currentType = MutableStateFlow("All") private val currentTypes = MutableStateFlow(settingsRepo.selectedTypes.value)
private val currentQuery = MutableStateFlow("") private val currentQuery = MutableStateFlow("")
private val itemsWithType = currentType.flatMapLatest { type -> private val itemsWithTypes = currentTypes.flatMapLatest { types: List<String> ->
currentItems.map { items -> items.filterByType(type) } currentItems.map { items -> items.filterByTypes(types) }
} }
private val itemsWithQuery = currentQuery.flatMapLatest { query -> 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) { override suspend fun getEntriesFlow() = withContext(dispatcher) {
val selectedIds = settingsRepo.selectedIds.value val selectedIds = settingsRepo.selectedIds.value
@ -42,8 +44,9 @@ class SelectionRepo(
return@withContext itemsWithQuery return@withContext itemsWithQuery
} }
override suspend fun setType(type: String) = withContext(dispatcher) { override suspend fun setTypes(types: List<String>) {
currentType.value = type currentTypes.value = types
settingsRepo.setSelectedTypes(types)
} }
override suspend fun setQuery(query: String) = withContext(dispatcher) { override suspend fun setQuery(query: String) = withContext(dispatcher) {
@ -65,11 +68,11 @@ class SelectionRepo(
settingsRepo.setSelectedIds(currentSelection) settingsRepo.setSelectedIds(currentSelection)
} }
private suspend fun List<SatItem>.filterByType(type: String) = withContext(dispatcher) { private suspend fun List<SatItem>.filterByTypes(types: List<String>) = withContext(dispatcher) {
if (type == "All") return@withContext this@filterByType if (types.isEmpty()) return@withContext this@filterByTypes
val catnums = settingsRepo.getSatelliteTypeIds(type) val catnums = settingsRepo.getSatelliteTypesIds(types)
if (catnums.isEmpty()) return@withContext this@filterByType if (catnums.isEmpty()) return@withContext this@filterByTypes
return@withContext this@filterByType.filter { item -> item.catnum in catnums } return@withContext this@filterByTypes.filter { item -> item.catnum in catnums }
} }
private suspend fun List<SatItem>.filterByQuery(query: String) = withContext(dispatcher) { private suspend fun List<SatItem>.filterByQuery(query: String) = withContext(dispatcher) {

Wyświetl plik

@ -48,6 +48,7 @@ class SettingsRepo(private val manager: LocationManager, private val preferences
private val keyRotatorPort = "rotatorPort" private val keyRotatorPort = "rotatorPort"
private val keyRotatorState = "rotatorState" private val keyRotatorState = "rotatorState"
private val keySelectedIds = "selectedIds" private val keySelectedIds = "selectedIds"
private val keySelectedTypes = "selectedTypes"
private val keySelectedModes = "selectedModes" private val keySelectedModes = "selectedModes"
private val keyStateOfAutoUpdate = "stateOfAutoUpdate" private val keyStateOfAutoUpdate = "stateOfAutoUpdate"
private val keyStateOfSensors = "stateOfSensors" private val keyStateOfSensors = "stateOfSensors"
@ -64,7 +65,9 @@ class SettingsRepo(private val manager: LocationManager, private val preferences
//region # Satellites selection settings //region # Satellites selection settings
private val _satelliteSelection = MutableStateFlow(getSelectedIds()) private val _satelliteSelection = MutableStateFlow(getSelectedIds())
private val _typesSelection = MutableStateFlow(getSelectedTypes())
override val selectedIds: StateFlow<List<Int>> = _satelliteSelection override val selectedIds: StateFlow<List<Int>> = _satelliteSelection
override val selectedTypes: StateFlow<List<String>> = _typesSelection
override fun setSelectedIds(ids: List<Int>) { override fun setSelectedIds(ids: List<Int>) {
val selectionString = ids.joinToString(separatorComma) val selectionString = ids.joinToString(separatorComma)
@ -72,11 +75,23 @@ class SettingsRepo(private val manager: LocationManager, private val preferences
_satelliteSelection.value = ids _satelliteSelection.value = ids
} }
override fun setSelectedTypes(types: List<String>) {
val typesString = types.joinToString(separatorComma)
preferences.edit { putString(keySelectedTypes, typesString) }
_typesSelection.value = types
}
private fun getSelectedIds(): List<Int> { private fun getSelectedIds(): List<Int> {
val selectionString = preferences.getString(keySelectedIds, null) val selectionString = preferences.getString(keySelectedIds, null)
if (selectionString.isNullOrEmpty()) return emptyList() if (selectionString.isNullOrEmpty()) return emptyList()
return selectionString.split(separatorComma).map { it.toInt() } return selectionString.split(separatorComma).map { it.toInt() }
} }
private fun getSelectedTypes(): List<String> {
val typesString = preferences.getString(keySelectedTypes, null)
if (typesString.isNullOrEmpty()) return emptyList()
return typesString.split(separatorComma)
}
//endregion //endregion
//region # Passes filter settings //region # Passes filter settings
@ -171,10 +186,18 @@ class SettingsRepo(private val manager: LocationManager, private val preferences
private val _databaseState = MutableStateFlow(getDatabaseState()) private val _databaseState = MutableStateFlow(getDatabaseState())
override val databaseState: StateFlow<DatabaseState> = _databaseState override val databaseState: StateFlow<DatabaseState> = _databaseState
override fun getSatelliteTypeIds(type: String): List<Int> { override fun getSatelliteTypesIds(types: List<String>): List<Int> {
val typesString = preferences.getString("type$type", null) val idsSet = mutableSetOf<Int>()
if (typesString.isNullOrBlank()) return emptyList() types.forEach { type ->
return typesString.split(separatorComma).map { it.toInt() } 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<Int>) { override fun setSatelliteTypeIds(type: String, ids: List<Int>) {

Wyświetl plik

@ -4,10 +4,10 @@ import com.rtbishop.look4sat.domain.model.SatItem
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface ISelectionRepo { interface ISelectionRepo {
fun getCurrentType(): String fun getCurrentTypes(): List<String>
fun getTypesList(): List<String> fun getTypesList(): List<String>
suspend fun getEntriesFlow(): Flow<List<SatItem>> suspend fun getEntriesFlow(): Flow<List<SatItem>>
suspend fun setType(type: String) suspend fun setTypes(types: List<String>)
suspend fun setQuery(query: String) suspend fun setQuery(query: String)
suspend fun setSelection(selectAll: Boolean) suspend fun setSelection(selectAll: Boolean)
suspend fun setSelection(ids: List<Int>, isTicked: Boolean) suspend fun setSelection(ids: List<Int>, isTicked: Boolean)

Wyświetl plik

@ -27,7 +27,9 @@ interface ISettingsRepo {
//region # Satellites selection settings //region # Satellites selection settings
val selectedIds: StateFlow<List<Int>> val selectedIds: StateFlow<List<Int>>
val selectedTypes: StateFlow<List<String>>
fun setSelectedIds(ids: List<Int>) fun setSelectedIds(ids: List<Int>)
fun setSelectedTypes(types: List<String>)
//endregion //endregion
//region # Passes filter settings //region # Passes filter settings
@ -44,7 +46,7 @@ interface ISettingsRepo {
//region # Database update settings //region # Database update settings
val databaseState: StateFlow<DatabaseState> val databaseState: StateFlow<DatabaseState>
fun getSatelliteTypeIds(type: String): List<Int> fun getSatelliteTypesIds(types: List<String>): List<Int>
fun setSatelliteTypeIds(type: String, ids: List<Int>) fun setSatelliteTypeIds(type: String, ids: List<Int>)
fun updateDatabaseState(state: DatabaseState) fun updateDatabaseState(state: DatabaseState)
//endregion //endregion