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.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<String>, selected: String, dismiss: () -> Unit, select: (String) -> Unit
fun MultiTypesDialog(
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(
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)
)
}

Wyświetl plik

@ -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<String>, 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<String>, 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)
)
}
}

Wyświetl plik

@ -6,7 +6,7 @@ data class SatellitesState(
val isDialogShown: Boolean,
val isLoading: Boolean,
val itemsList: List<SatItem>,
val currentType: String,
val currentTypes: List<String>,
val typesList: List<String>,
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<String>) : SatellitesAction()
data object ToggleTypesDialog : SatellitesAction()
data object UnselectAll : SatellitesAction()
}

Wyświetl plik

@ -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<String>) = viewModelScope.launch {
selectionRepo.setTypes(types)
_uiState.value = _uiState.value.copy(currentTypes = types, isDialogShown = false)
}
private fun toggleTypesDialog() {

Wyświetl plik

@ -21,18 +21,20 @@ class SelectionRepo(
) : ISelectionRepo {
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 itemsWithType = currentType.flatMapLatest { type ->
currentItems.map { items -> items.filterByType(type) }
private val itemsWithTypes = currentTypes.flatMapLatest { types: List<String> ->
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<String>) {
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<SatItem>.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<SatItem>.filterByTypes(types: List<String>) = 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<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 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<List<Int>> = _satelliteSelection
override val selectedTypes: StateFlow<List<String>> = _typesSelection
override fun setSelectedIds(ids: List<Int>) {
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<String>) {
val typesString = types.joinToString(separatorComma)
preferences.edit { putString(keySelectedTypes, typesString) }
_typesSelection.value = types
}
private fun getSelectedIds(): List<Int> {
val selectionString = preferences.getString(keySelectedIds, null)
if (selectionString.isNullOrEmpty()) return emptyList()
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
//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> = _databaseState
override fun getSatelliteTypeIds(type: String): List<Int> {
val typesString = preferences.getString("type$type", null)
if (typesString.isNullOrBlank()) return emptyList()
return typesString.split(separatorComma).map { it.toInt() }
override fun getSatelliteTypesIds(types: List<String>): List<Int> {
val idsSet = mutableSetOf<Int>()
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<Int>) {

Wyświetl plik

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

Wyświetl plik

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