kopia lustrzana https://github.com/rt-bishop/Look4Sat
Added multi selection for Satellite types filter dialog
rodzic
95af794572
commit
be82b19b37
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue