From f9806d7f7f2ad386b1dfac049d83845dcb6d9d47 Mon Sep 17 00:00:00 2001 From: Arty Bishop Date: Fri, 31 Jul 2026 16:04:14 +0200 Subject: [PATCH] Replaced the types selection dialog with modes selection --- .../core/data/database/Look4SatDao.kt | 7 +- .../core/data/repository/DatabaseRepo.kt | 8 +- .../core/data/repository/SatelliteRepo.kt | 2 +- .../core/data/repository/SelectionRepo.kt | 30 +-- .../core/data/repository/SettingsRepo.kt | 48 ++--- .../core/data/repository/DatabaseRepoTest.kt | 15 +- .../core/data/repository/SelectionRepoTest.kt | 173 ++++++++++++++++++ .../look4sat/core/domain/model/Settings.kt | 3 +- .../core/domain/repository/ISelectionRepo.kt | 6 +- .../core/domain/repository/ISettingsRepo.kt | 6 +- .../look4sat/core/domain/source/Sources.kt | 32 +--- .../src/main/res/values-es/strings.xml | 10 +- .../src/main/res/values-ru/strings.xml | 10 +- .../src/main/res/values-si/strings.xml | 10 +- .../src/main/res/values-tr/strings.xml | 10 +- .../src/main/res/values-uk/strings.xml | 10 +- .../src/main/res/values-zh/strings.xml | 10 +- .../src/main/res/values/strings.xml | 10 +- .../look4sat/feature/passes/PassesDialog.kt | 11 +- .../feature/passes/PassesViewModel.kt | 49 ++--- .../feature/satellites/SatellitesDialog.kt | 18 +- .../feature/satellites/SatellitesScreen.kt | 79 ++++++-- .../feature/satellites/SatellitesState.kt | 8 +- .../feature/satellites/SatellitesViewModel.kt | 20 +- 24 files changed, 393 insertions(+), 192 deletions(-) create mode 100644 core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/SelectionRepoTest.kt diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/Look4SatDao.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/Look4SatDao.kt index c8435c88..957c8fae 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/Look4SatDao.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/database/Look4SatDao.kt @@ -45,7 +45,12 @@ interface Look4SatDao { @Query("DELETE FROM entries") suspend fun deleteEntries() - @Query("SELECT catnum FROM radios WHERE downlinkMode IN (:modes)") + @Query( + """ + SELECT DISTINCT catnum FROM radios WHERE isAlive = 1 + AND (downlinkMode IN (:modes) OR uplinkMode IN (:modes)) + """ + ) suspend fun getIdsWithModes(modes: List): List @Query("SELECT COUNT(*) FROM radios") diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepo.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepo.kt index 8decc7a6..0c404ad7 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepo.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepo.kt @@ -47,7 +47,6 @@ class DatabaseRepo( remoteSource.getFileStream(uri)?.let { stream -> val entries = parseSatelliteStream(uri, unwrapIfZipped(uri, stream)) localSource.insertEntries(entries) - settingsRepo.setSatelliteTypeIds(customSourceType, entries.map { it.catnum }) importedCount = entries.size } setUpdateSuccessful(System.currentTimeMillis()) @@ -78,12 +77,9 @@ class DatabaseRepo( // launch all network requests concurrently val tleJobs = tleUrls.values.map { url -> async { url to remoteSource.getNetworkStream(url) } } val radioJobs = radioUrls.values.map { url -> async { url to remoteSource.getNetworkStream(url) } } - // parse fetched data concurrently and associate with types + // parse fetched data concurrently val importedEntries = tleJobs.awaitAll().flatMap { (url, stream) -> - val type = tleUrls.entries.find { it.value == url }?.key ?: customSourceType - stream?.let { parseSatelliteStream(url, unwrapIfZipped(url, it)) }.orEmpty().also { entries -> - settingsRepo.setSatelliteTypeIds(type, entries.map { it.catnum }) - } + stream?.let { parseSatelliteStream(url, unwrapIfZipped(url, it)) }.orEmpty() } val importedRadios = radioJobs.awaitAll().flatMap { (url, stream) -> stream?.let { dataParser.parseJSONStream(unwrapIfZipped(url, it)) }.orEmpty() diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SatelliteRepo.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SatelliteRepo.kt index 6c9c0c01..088ca67d 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SatelliteRepo.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SatelliteRepo.kt @@ -73,7 +73,7 @@ class SatelliteRepo( aosStartMinute = settings.aosStartMinute, aosEndMinute = settings.aosEndMinute, invertAosTimeWindow = settings.invertAosTimeWindow, - modes = settings.selectedModes + modes = settingsRepo.selectedSatModes.value ) } } diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SelectionRepo.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SelectionRepo.kt index 757cccd9..b29e3df0 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SelectionRepo.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SelectionRepo.kt @@ -38,16 +38,16 @@ class SelectionRepo( ) : ISelectionRepo { private val currentItems = MutableStateFlow>(emptyList()) - private val currentTypes = MutableStateFlow(settingsRepo.selectedTypes.value) + private val currentModes = MutableStateFlow(settingsRepo.selectedSatModes.value) private val currentQuery = MutableStateFlow("") - // Resolve type IDs once when types change, then filter items reactively. + // Resolve sat IDs once when modes change, then filter items reactively. // The HashSet gives O(1) catnum lookups instead of O(n) with a List. - private val itemsWithTypes = currentTypes.flatMapLatest { types: List -> - val catnumSet: Set? = if (types.isEmpty()) { + private val itemsWithModes = currentModes.flatMapLatest { modes: List -> + val catnumSet: Set? = if (modes.isEmpty()) { null // null = no filtering } else { - val ids = settingsRepo.getSatelliteTypesIds(types) + val ids = localSource.getIdsWithModes(modes) if (ids.isEmpty()) null else ids.toHashSet() } currentItems.map { items -> @@ -56,14 +56,18 @@ class SelectionRepo( } private val itemsWithQuery = currentQuery.flatMapLatest { query -> - itemsWithTypes.map { items -> filterByQuery(items, query) } + itemsWithModes.map { items -> + filterByQuery(items, query).sortedWith( + compareByDescending { it.isSelected } + .thenBy { it.name } + .thenBy { it.catnum } + ) + } } - override fun getCurrentTypes() = currentTypes.value + override fun getCurrentModes() = currentModes.value - override fun getTypesList() = Sources.satelliteDataUrls.keys.sorted().toMutableList().apply { - remove("All") - } + override fun getModesList() = Sources.satelliteModes override suspend fun getEntriesFlow() = withContext(dispatcher) { val selectedIds = settingsRepo.selectedIds.value.toHashSet() @@ -73,9 +77,9 @@ class SelectionRepo( return@withContext itemsWithQuery } - override suspend fun setTypes(types: List) { - currentTypes.value = types - settingsRepo.setSelectedTypes(types) + override suspend fun setModes(modes: List) { + currentModes.value = modes + settingsRepo.setSelectedSatModes(modes) } override suspend fun setQuery(query: String) { diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SettingsRepo.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SettingsRepo.kt index ccc34102..fb8f863c 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SettingsRepo.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SettingsRepo.kt @@ -68,8 +68,7 @@ class SettingsRepo( private val keyFrequencyPort = "frequencyPort" private val keyFrequencyFormat = "frequencyFormat" private val keySelectedIds = "selectedIds" - private val keySelectedTypes = "selectedTypes" - private val keySelectedModes = "selectedModes" + private val keySelectedSatModes = "selectedSatModes" private val keyStateOfAutoUpdate = "stateOfAutoUpdate" private val keyStateOfSensors = "stateOfSensors" private val keyStateOfSweep = "stateOfSweep" @@ -95,9 +94,9 @@ class SettingsRepo( //region # Satellites selection settings private val _satelliteSelection = MutableStateFlow(getSelectedIds()) - private val _typesSelection = MutableStateFlow(getSelectedTypes()) + private val _satelliteModeSelection = MutableStateFlow(getSelectedSatModes()) override val selectedIds: StateFlow> = _satelliteSelection - override val selectedTypes: StateFlow> = _typesSelection + override val selectedSatModes: StateFlow> = _satelliteModeSelection override fun setSelectedIds(ids: List) { val selectionString = ids.joinToString(separatorComma) @@ -105,10 +104,10 @@ class SettingsRepo( _satelliteSelection.value = ids } - override fun setSelectedTypes(types: List) { - val typesString = types.joinToString(separatorComma) - preferences.edit { putString(keySelectedTypes, typesString) } - _typesSelection.value = types + override fun setSelectedSatModes(modes: List) { + val modesString = modes.joinToString(separatorComma) + preferences.edit { putString(keySelectedSatModes, modesString) } + _satelliteModeSelection.value = modes } private fun getSelectedIds(): List { @@ -117,10 +116,10 @@ class SettingsRepo( return selectionString.split(separatorComma).map { it.toInt() } } - private fun getSelectedTypes(): List { - val typesString = preferences.getString(keySelectedTypes, "Amateur") - if (typesString.isNullOrEmpty()) return emptyList() - return typesString.split(separatorComma) + private fun getSelectedSatModes(): List { + val modesString = preferences.getString(keySelectedSatModes, null) + if (modesString.isNullOrEmpty()) return emptyList() + return modesString.split(separatorComma).sorted() } //endregion @@ -135,7 +134,6 @@ class SettingsRepo( putInt(keyFilterAosStartMinute, settings.aosStartMinute) putInt(keyFilterAosEndMinute, settings.aosEndMinute) putBoolean(keyFilterAosInvert, settings.invertAosTimeWindow) - putString(keySelectedModes, settings.selectedModes.joinToString(separatorComma)) _passesSettings.value = settings } @@ -146,16 +144,13 @@ class SettingsRepo( val aosStartMinute = preferences.getInt(keyFilterAosStartMinute, 0).coerceIn(0, 23 * 60 + 59) val aosEndMinute = preferences.getInt(keyFilterAosEndMinute, 23 * 60 + 59).coerceIn(0, 23 * 60 + 59) val invertAosTimeWindow = preferences.getBoolean(keyFilterAosInvert, false) - val selectedModesString = preferences.getString(keySelectedModes, null) - val selectedModes = selectedModesString?.split(separatorComma)?.sorted() ?: emptyList() return PassesSettings( showDeepSpace, hoursAhead, minElevation, aosStartMinute, aosEndMinute, - invertAosTimeWindow, - selectedModes + invertAosTimeWindow ) } //endregion @@ -235,25 +230,6 @@ class SettingsRepo( private val _databaseState = MutableStateFlow(getDatabaseState()) override val databaseState: StateFlow = _databaseState - 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) { - if (type == "All") return - val typesString = ids.joinToString(separatorComma) - preferences.edit { putString("type$type", typesString) } - } override fun updateDatabaseState(state: DatabaseState) = preferences.edit { putInt(keyNumberOfSatellites, state.numberOfSatellites) diff --git a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt index a14ede56..a57c0ac8 100644 --- a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt +++ b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt @@ -62,7 +62,6 @@ class DatabaseRepoTest { assertEquals(1, localSource.insertedEntries.size) assertEquals(25544, localSource.insertedEntries.first().catnum) - assertEquals(listOf(25544), settingsRepo.satelliteTypeIdsByType["Other"]) assertTrue(settingsRepo.databaseState.value.numberOfSatellites > 0) } @@ -102,7 +101,6 @@ class DatabaseRepoTest { repository.updateFromRemote() assertTrue(localSource.insertedEntries.any { it.catnum == 25544 }) - assertEquals(listOf(25544), settingsRepo.satelliteTypeIdsByType["Other"]) } private fun validCsvStream(): InputStream = """ @@ -165,10 +163,10 @@ private class FakeSettingsRepo(dataSources: DataSourcesSettings = defaultDataSou override val selectedIds: StateFlow> = MutableStateFlow(emptyList()) - override val selectedTypes: StateFlow> = MutableStateFlow(emptyList()) + override val selectedSatModes: StateFlow> = MutableStateFlow(emptyList()) override val passesSettings: StateFlow = MutableStateFlow( - PassesSettings(hoursAhead = 24, minElevation = 0.0, selectedModes = emptyList()) + PassesSettings(hoursAhead = 24, minElevation = 0.0) ) override val stationPosition: StateFlow = MutableStateFlow(GeoPos(0.0, 0.0)) @@ -189,11 +187,9 @@ private class FakeSettingsRepo(dataSources: DataSourcesSettings = defaultDataSou RadioControlSettings(false, RadioControlSettings.MODEL_YAESU_FT817, "", "", "", "", 9600) ) - val satelliteTypeIdsByType = mutableMapOf>() - override fun setSelectedIds(ids: List) = Unit - override fun setSelectedTypes(types: List) = Unit + override fun setSelectedSatModes(modes: List) = Unit override fun setPassesSettings(settings: PassesSettings) = Unit @@ -203,11 +199,6 @@ private class FakeSettingsRepo(dataSources: DataSourcesSettings = defaultDataSou override fun setStationPosition(locator: String): Boolean = true - override fun getSatelliteTypesIds(types: List): List = emptyList() - - override fun setSatelliteTypeIds(type: String, ids: List) { - satelliteTypeIdsByType[type] = ids - } override fun updateDatabaseState(state: DatabaseState) { databaseState.value = state diff --git a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/SelectionRepoTest.kt b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/SelectionRepoTest.kt new file mode 100644 index 00000000..9a240b46 --- /dev/null +++ b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/SelectionRepoTest.kt @@ -0,0 +1,173 @@ +/* + * Look4Sat. Amateur radio satellite tracker and pass predictor. + * Copyright (C) 2019-2026 Arty Bishop and contributors. + * + * 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.core.data.repository + +import com.rtbishop.look4sat.core.domain.model.DataSourcesSettings +import com.rtbishop.look4sat.core.domain.model.DatabaseState +import com.rtbishop.look4sat.core.domain.model.OtherSettings +import com.rtbishop.look4sat.core.domain.model.PassesSettings +import com.rtbishop.look4sat.core.domain.model.RCSettings +import com.rtbishop.look4sat.core.domain.model.RadioControlSettings +import com.rtbishop.look4sat.core.domain.model.SatItem +import com.rtbishop.look4sat.core.domain.model.SatRadio +import com.rtbishop.look4sat.core.domain.predict.GeoPos +import com.rtbishop.look4sat.core.domain.predict.OrbitalObject +import com.rtbishop.look4sat.core.domain.repository.ISettingsRepo +import com.rtbishop.look4sat.core.domain.source.ILocalSource +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class SelectionRepoTest { + + private val dispatcher = StandardTestDispatcher() + + @Test + fun `unknown mode values do not crash and do not filter out entries`() = runTest(dispatcher) { + val localSource = FakeLocalSource( + entries = listOf( + SatItem(25544, "ISS (ZARYA)", false), + SatItem(40967, "TIANGONG", false) + ) + ) + val settingsRepo = FakeSettingsRepo(selectedModes = listOf("REMOVED_MODE")) + val repository = SelectionRepo(dispatcher, localSource, settingsRepo) + + val flow = repository.getEntriesFlow() + repository.setModes(listOf("REMOVED_MODE")) + + val items = flow.first() + + assertEquals(listOf(25544, 40967), items.map { it.catnum }) + assertEquals(listOf("REMOVED_MODE"), repository.getCurrentModes()) + } + + @Test + fun `selected satellites are shown first`() = runTest(dispatcher) { + val localSource = FakeLocalSource( + entries = listOf( + SatItem(44444, "Zeta", false), + SatItem(25544, "Alpha", false), + SatItem(40967, "Beta", false) + ) + ) + val settingsRepo = FakeSettingsRepo(selectedModes = emptyList()) + val repository = SelectionRepo(dispatcher, localSource, settingsRepo) + + val flow = repository.getEntriesFlow() + repository.setSelection(listOf(40967), true) + + val items = flow.first() + + assertEquals(listOf(40967, 25544, 44444), items.map { it.catnum }) + assertEquals(listOf(true, false, false), items.map { it.isSelected }) + } + + private class FakeLocalSource( + private val entries: List + ) : ILocalSource { + override suspend fun getEntriesTotal(): Int = entries.size + + override suspend fun getEntriesList(): List = entries + + override suspend fun getEntriesWithIds(ids: List): List = emptyList() + + override suspend fun insertEntries(entries: List) = Unit + + override suspend fun deleteEntries() = Unit + + override suspend fun getIdsWithModes(modes: List): List = emptyList() + + override suspend fun getRadiosTotal(): Int = 0 + + override suspend fun getRadiosWithId(id: Int): List = emptyList() + + override suspend fun insertRadios(radios: List) = Unit + + override suspend fun deleteRadios() = Unit + } + + private class FakeSettingsRepo( + selectedModes: List + ) : ISettingsRepo { + + override val appVersionName: String = "test" + + override val selectedIds: StateFlow> = MutableStateFlow(emptyList()) + + override val selectedSatModes: MutableStateFlow> = MutableStateFlow(selectedModes) + + override val passesSettings: StateFlow = MutableStateFlow( + PassesSettings(hoursAhead = 24, minElevation = 0.0) + ) + + override val stationPosition: StateFlow = MutableStateFlow(GeoPos(0.0, 0.0)) + + override val databaseState: MutableStateFlow = MutableStateFlow(DatabaseState(0, 0, 0L)) + + override val rcSettings: StateFlow = MutableStateFlow( + RCSettings(false, "", "", "", false, "", "", "", false, "", "", "", false, "", "") + ) + + override val otherSettings: StateFlow = MutableStateFlow( + OtherSettings(false, false, false, false, false, false, false, false) + ) + + override val dataSourcesSettings: StateFlow = MutableStateFlow( + DataSourcesSettings(false, false, "", "") + ) + + override val radioControlSettings: StateFlow = MutableStateFlow( + RadioControlSettings(false, RadioControlSettings.MODEL_YAESU_FT817, "", "", "", "", 9600) + ) + + override fun setSelectedIds(ids: List) = Unit + + override fun setSelectedSatModes(modes: List) { + selectedSatModes.value = modes + } + + override fun setPassesSettings(settings: PassesSettings) = Unit + + override fun setStationPosition(latitude: Double, longitude: Double, altitude: Double): Boolean = true + + override fun setStationPosition(): Boolean = true + + override fun setStationPosition(locator: String): Boolean = true + + override fun updateDatabaseState(state: DatabaseState) { + databaseState.value = state + } + + override fun updateRCSettings(settings: RCSettings) = Unit + + override fun updateOtherSettings(transform: (OtherSettings) -> OtherSettings) = Unit + + override fun updateDataSourcesSettings(settings: DataSourcesSettings) = Unit + + override fun updateRadioControlSettings(settings: RadioControlSettings) = Unit + } +} + + diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/model/Settings.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/model/Settings.kt index ef830eb4..1647073b 100644 --- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/model/Settings.kt +++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/model/Settings.kt @@ -29,8 +29,7 @@ data class PassesSettings( val minElevation: Double, val aosStartMinute: Int = 0, val aosEndMinute: Int = 23 * 60 + 59, - val invertAosTimeWindow: Boolean = false, - val selectedModes: List + val invertAosTimeWindow: Boolean = false ) data class RCSettings( diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/repository/ISelectionRepo.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/repository/ISelectionRepo.kt index 33776225..6111ad9c 100644 --- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/repository/ISelectionRepo.kt +++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/repository/ISelectionRepo.kt @@ -21,10 +21,10 @@ import com.rtbishop.look4sat.core.domain.model.SatItem import kotlinx.coroutines.flow.Flow interface ISelectionRepo { - fun getCurrentTypes(): List - fun getTypesList(): List + fun getCurrentModes(): List + fun getModesList(): List suspend fun getEntriesFlow(): Flow> - suspend fun setTypes(types: List) + suspend fun setModes(modes: List) suspend fun setQuery(query: String) suspend fun setSelection(selectAll: Boolean) suspend fun setSelection(ids: List, isTicked: Boolean) diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/repository/ISettingsRepo.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/repository/ISettingsRepo.kt index 23b726e2..96d0283d 100644 --- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/repository/ISettingsRepo.kt +++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/repository/ISettingsRepo.kt @@ -32,9 +32,9 @@ interface ISettingsRepo { //region # Satellites selection settings val selectedIds: StateFlow> - val selectedTypes: StateFlow> + val selectedSatModes: StateFlow> fun setSelectedIds(ids: List) - fun setSelectedTypes(types: List) + fun setSelectedSatModes(modes: List) //endregion //region # Passes filter settings @@ -51,8 +51,6 @@ interface ISettingsRepo { //region # Database update settings val databaseState: StateFlow - fun getSatelliteTypesIds(types: List): List - fun setSatelliteTypeIds(type: String, ids: List) fun updateDatabaseState(state: DatabaseState) //endregion diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/source/Sources.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/source/Sources.kt index 4eb72da4..52dc76f1 100644 --- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/source/Sources.kt +++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/source/Sources.kt @@ -20,36 +20,24 @@ package com.rtbishop.look4sat.core.domain.source object Sources { val satelliteDataUrls = mapOf( "All" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=csv", - "Amateur" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=amateur&FORMAT=csv", - "Brightest" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=visual&FORMAT=csv", - "Cubesat" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=cubesat&FORMAT=csv", - "Education" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=education&FORMAT=csv", - "Engineer" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=engineering&FORMAT=csv", - "Geostationary" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=geo&FORMAT=csv", - "Globalstar" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=globalstar&FORMAT=csv", - "GNSS" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=gnss&FORMAT=csv", - "Intelsat" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=intelsat&FORMAT=csv", - "Iridium" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=iridium-NEXT&FORMAT=csv", - "Military" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=military&FORMAT=csv", - "New" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=last-30-days&FORMAT=csv", - "OneWeb" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=oneweb&FORMAT=csv", - "Orbcomm" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=orbcomm&FORMAT=csv", - "Resource" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=resource&FORMAT=csv", - "SatNOGS" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=satnogs&FORMAT=csv", - "Science" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=science&FORMAT=csv", - "Spire" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=spire&FORMAT=csv", - "Starlink" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=starlink&FORMAT=csv", - "Swarm" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=swarm&FORMAT=csv", - "Weather" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=weather&FORMAT=csv", - "X-Comm" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=x-comm&FORMAT=csv", "Amsat" to "https://amsat.org/tle/current/nasabare.txt", "Classified" to "https://www.mmccants.org/tles/classfd.zip", "McCants" to "https://www.mmccants.org/tles/inttles.zip", "R4UAB" to "https://r4uab.ru/satonline.txt", + "SatNOGS" to "https://db.satnogs.org/api/tle/?format=3le", "ARISS" to "https://live.ariss.org/iss.txt", "Other" to "" // key for sats filter ) val transceiversDataUrls = mapOf( "SatNOGS" to "https://db.satnogs.org/api/transmitters/?format=json&status=active" ) + val satelliteModes = listOf( + "4FSK", "64-QAM", "AFSK", "AFSK TUBiX10", "AHRPT", "AM", "APT", "ASK", "BPSK", + "BPSK PMT-A3", "CERTO", "CW", "DATV", "DBPSK", "DOKA", "DPSK", "DQPSK", "DSB", "DSTAR", + "DUV", "DVB-S2", "FFSK", "FM", "FMN", "FSK", "FSK AX.100 Mode 5", "FSK AX.100 Mode 6", + "FSK AX.25 G3RUH", "FT8", "GENESIS FSK", "GFSK", "GFSK Pkst", "GFSK Rktr", "GFSK/BPSK", + "GMSK", "GMSK USP", "HRPT", "LoRa", "LRPT", "LSB", "MFSK", "MSK", "MSK AX.100 Mode 5", + "MSK AX.100 Mode 6", "OFDM", "OQPSK", "PPM", "PSK", "PSK31", "PSK63", "QPSK", "QPSK31", + "QPSK63", "SIDLOC", "SQPSK", "SSDV", "SSTV", "UNKNOWN", "USB", "WSJT" + ) } diff --git a/core/presentation/src/main/res/values-es/strings.xml b/core/presentation/src/main/res/values-es/strings.xml index bf6d324d..f2f980ff 100644 --- a/core/presentation/src/main/res/values-es/strings.xml +++ b/core/presentation/src/main/res/values-es/strings.xml @@ -13,12 +13,16 @@ Ajustes - Tipo: %s - Seleccionar tipos + Modos: %s + Seleccionar modos Id - Nombre Vaciar Vaciar todo Seleccionar todo + Seleccionados + Disponibles + Seleccionados (%1$d) + Disponibles (%1$d) Asegúrese de que su consulta de búsqueda sea correcta y que la base de datos esté actualizada Alarma\! @@ -26,7 +30,7 @@ Esta aplicación incluye más de 9000 satélites. No tiene sentido rastrearlos todos a la vez. \n\nIntenta siempre limitar la lista a los que te - interesen mediante la búsqueda y el selector de tipos. + interesen mediante la búsqueda y el selector de modos. Filtrar pases diff --git a/core/presentation/src/main/res/values-ru/strings.xml b/core/presentation/src/main/res/values-ru/strings.xml index 85a42f47..3641e54e 100644 --- a/core/presentation/src/main/res/values-ru/strings.xml +++ b/core/presentation/src/main/res/values-ru/strings.xml @@ -13,19 +13,23 @@ Настройки - Тип: %s - Выберите типы + Режимы: %s + Выберите режимы Id - Название Очистить Очистить Выбрать + Выбранные + Доступные + Выбранные (%1$d) + Доступные (%1$d) Убедитесь, что ваш поисковый запрос верен и база данных обновлена Внимание\! В этом приложении перечислено более 9000 спутников. Отслеживать их все одновременно бессмысленно. \n\nВсегда старайтесь сузить список до тех, - которые вас интересуют, используя поиск и селектор типов. + которые вас интересуют, используя поиск и выбор режимов. Фильтровать пролеты diff --git a/core/presentation/src/main/res/values-si/strings.xml b/core/presentation/src/main/res/values-si/strings.xml index 0310dbb1..11482985 100644 --- a/core/presentation/src/main/res/values-si/strings.xml +++ b/core/presentation/src/main/res/values-si/strings.xml @@ -13,19 +13,23 @@ පසුතල - වර්ගය: %s - ආකාර තෝරන්න + මාදිලි: %s + මාදිලි තෝරන්න Id - නම පිරිසිදු ක° සියල්ල පිරිසිදු ක° සියල්ල තෝරන්න + තෝරාගත් + තිබෙන + තෝරාගත් (%1$d) + තිබෙන (%1$d) ඔබගේ සෙවුම් විමසුම නිවැරදි බවත් දත්ත සමුදාය යාවත්කාලීන කර ඇති බවත් සහතික කර ගන්න අවවාදයයි\! මෙම යෙදුමේ ලැයිස්තුගත කර ඇති චන්ද්‍රිකා 9000 කට වඩා තිබේ. ඒවා සියල්ලම එකවර නිරීක්ෂණය කිරීම තේරුමක් නැති දෙයක්. - \n\nසෙවීම සහ වර්ග තේරීම හරහා ඔබ උනන්දුවක් දක්වන + \n\nසෙවීම සහ මාදිලි තේරීම හරහා ඔබ උනන්දුවක් දක්වන ඒවාට පමණක් ලැයිස්තුව පටු කිරීමට සැමවිටම උත්සාහ කරන්න. diff --git a/core/presentation/src/main/res/values-tr/strings.xml b/core/presentation/src/main/res/values-tr/strings.xml index 4a0fec46..72997597 100644 --- a/core/presentation/src/main/res/values-tr/strings.xml +++ b/core/presentation/src/main/res/values-tr/strings.xml @@ -14,19 +14,23 @@ Ayarlar - Uydu türü: %s - Türleri seçin + Modlar: %s + Modları seçin Id - Ad Temizle Tümünü temizle Tümünü seç + Seçilenler + Kullanılabilir + Seçilenler (%1$d) + Kullanılabilir (%1$d) "Arama sorgunuzun doğru olduğundan ve veritabanının güncellendiğinden emin olun" Uyarı\! Bu uygulamada 9000\'den fazla uydu listelenmiştir. Hepsini aynı anda takip etmek anlamsızdır. \n\nListeyi yalnızca ilgilendiğiniz uydularla - daraltmak için arama ve tür seçiciyi kullanın. + daraltmak için arama ve mod seçiciyi kullanın. Geçişleri filtrele diff --git a/core/presentation/src/main/res/values-uk/strings.xml b/core/presentation/src/main/res/values-uk/strings.xml index cc6fb62c..bde698d7 100644 --- a/core/presentation/src/main/res/values-uk/strings.xml +++ b/core/presentation/src/main/res/values-uk/strings.xml @@ -13,12 +13,16 @@ Налаштування - Тип: %s - Виберіть типи + Режими: %s + Виберіть режими Id - Назва Очистити Очистити всі Вибрати всі + Обрані + Доступні + Обрані (%1$d) + Доступні (%1$d) Переконайтеся, що ваш пошуковий запит правильний, а база даних оновлена Увага\! @@ -26,7 +30,7 @@ У цьому додатку перелічено понад 9000 супутників. Немає сенсу відстежувати їх усі одночасно. \n\nЗавжди намагайтеся звузити список лише до тих, - які вас цікавлять, за допомогою пошуку та селектора типів. + які вас цікавлять, за допомогою пошуку та вибору режимів. Фільтр прольотів diff --git a/core/presentation/src/main/res/values-zh/strings.xml b/core/presentation/src/main/res/values-zh/strings.xml index d97b35aa..d5169543 100644 --- a/core/presentation/src/main/res/values-zh/strings.xml +++ b/core/presentation/src/main/res/values-zh/strings.xml @@ -13,15 +13,19 @@ 设置 - 类型:%s - 选择类型 + 模式:%s + 选择模式 Id - 名称 清空 全部清空 全选 + 已选择 + 可用 + 已选择(%1$d) + 可用(%1$d) 请确保您的搜索查询正确且数据库已更新 警告\! - 此应用收录了超过9000颗卫星信息\n同时追踪所有卫星并不现实\n建议始终通过搜索和分类筛选功能仅勾选您感兴趣的卫星 + 此应用收录了超过9000颗卫星信息\n同时追踪所有卫星并不现实\n建议始终通过搜索和模式筛选功能仅勾选您感兴趣的卫星 筛选过境 diff --git a/core/presentation/src/main/res/values/strings.xml b/core/presentation/src/main/res/values/strings.xml index 380a7f71..492aaa2f 100644 --- a/core/presentation/src/main/res/values/strings.xml +++ b/core/presentation/src/main/res/values/strings.xml @@ -14,19 +14,23 @@ Settings - Type: %s - Select types + Modes: %s + Select modes Id - Name Clear Clear all Select all + Selected + Available + Selected (%1$d) + Available (%1$d) "Make sure your search query is correct and the DB is updated" Warning\! There are over 9000 satellites listed in this app. It makes no sense to track them all at the same time. \n\nAlways try to narrow down the list to only the ones - you\'re interested in via search and types selector. + you\'re interested in via search and the modes selector. Filter passes diff --git a/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesDialog.kt b/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesDialog.kt index cc1c37ef..4e1aa773 100644 --- a/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesDialog.kt +++ b/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesDialog.kt @@ -62,16 +62,9 @@ import com.rtbishop.look4sat.core.presentation.SharedDialog import com.rtbishop.look4sat.core.presentation.ElevationHighColor import com.rtbishop.look4sat.core.presentation.ElevationLowColor import com.rtbishop.look4sat.core.presentation.elevationColor +import com.rtbishop.look4sat.core.domain.source.Sources import kotlin.math.roundToInt -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" -) - private val hourSteps = listOf(1, 2, 4, 8, 12, 24, 48, 72, 96, 120, 144, 168, 192, 216, 240) private const val dayMinutes = 24 * 60 private const val minuteStep = 15 @@ -381,7 +374,7 @@ internal fun RadiosDialog(modes: List, cancel: () -> Unit, accept: (List horizontalArrangement = Arrangement.spacedBy(1.dp), verticalArrangement = Arrangement.spacedBy(1.dp) ) { - itemsIndexed(allModes) { index, item -> + itemsIndexed(Sources.satelliteModes) { index, item -> Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier diff --git a/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesViewModel.kt b/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesViewModel.kt index aaeda056..f8e6b230 100644 --- a/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesViewModel.kt +++ b/feature/passes/src/main/java/com/rtbishop/look4sat/feature/passes/PassesViewModel.kt @@ -60,7 +60,7 @@ class PassesViewModel( aosEndMinute = settingsRepo.passesSettings.value.aosEndMinute, invertAosTimeWindow = settingsRepo.passesSettings.value.invertAosTimeWindow, showDeepSpace = settingsRepo.passesSettings.value.showDeepSpace, - modes = settingsRepo.passesSettings.value.selectedModes, + modes = settingsRepo.selectedSatModes.value, shouldSeeWhatsNew = settingsRepo.otherSettings.value.shouldSeeWhatsNew ) ) @@ -86,6 +86,11 @@ class PassesViewModel( } } } + viewModelScope.launch { + settingsRepo.selectedSatModes.collectLatest { modes -> + _uiState.update { it.copy(modes = modes) } + } + } // Main tick loop: reacts to new passes, then ticks every second viewModelScope.launch { satelliteRepo.passes.collectLatest { allPasses -> @@ -127,21 +132,9 @@ class PassesViewModel( aosStartMinute = action.aosStartMinute, aosEndMinute = action.aosEndMinute, invertAosTimeWindow = action.invertAosTimeWindow, - showDeepSpace = action.showDeepSpace, - modes = _uiState.value.modes - ) - is PassesAction.FilterRadios -> - applyFilter( - hoursAhead = _uiState.value.hours, - minElevation = _uiState.value.elevation, - lowElevation = _uiState.value.lowElevation, - highElevation = _uiState.value.highElevation, - aosStartMinute = _uiState.value.aosStartMinute, - aosEndMinute = _uiState.value.aosEndMinute, - invertAosTimeWindow = _uiState.value.invertAosTimeWindow, - showDeepSpace = _uiState.value.showDeepSpace, - modes = action.modes + showDeepSpace = action.showDeepSpace ) + is PassesAction.FilterRadios -> setModesFilter(action.modes) PassesAction.RefreshPasses -> refreshPasses() PassesAction.TogglePassesDialog -> _uiState.update { it.copy(isPassesDialogShown = !it.isPassesDialogShown) } @@ -235,8 +228,7 @@ class PassesViewModel( aosStartMinute: Int, aosEndMinute: Int, invertAosTimeWindow: Boolean, - showDeepSpace: Boolean, - modes: List + showDeepSpace: Boolean ) = viewModelScope.launch { settingsRepo.setPassesSettings( PassesSettings( @@ -245,8 +237,7 @@ class PassesViewModel( minElevation, aosStartMinute, aosEndMinute, - invertAosTimeWindow, - modes + invertAosTimeWindow ) ) settingsRepo.updateOtherSettings { it.copy(lowElevation = lowElevation, highElevation = highElevation) } @@ -259,10 +250,10 @@ class PassesViewModel( aosStartMinute = aosStartMinute, aosEndMinute = aosEndMinute, invertAosTimeWindow = invertAosTimeWindow, - showDeepSpace = showDeepSpace, - modes = modes + showDeepSpace = showDeepSpace ) } + val modes = settingsRepo.selectedSatModes.value satelliteRepo.calculatePasses( time = System.currentTimeMillis(), hoursAhead = hoursAhead, @@ -274,6 +265,20 @@ class PassesViewModel( ) } + private fun setModesFilter(modes: List) = viewModelScope.launch { + settingsRepo.setSelectedSatModes(modes) + _uiState.update { it.copy(modes = modes) } + satelliteRepo.calculatePasses( + time = System.currentTimeMillis(), + hoursAhead = _uiState.value.hours, + minElevation = _uiState.value.elevation, + aosStartMinute = _uiState.value.aosStartMinute, + aosEndMinute = _uiState.value.aosEndMinute, + invertAosTimeWindow = _uiState.value.invertAosTimeWindow, + modes = modes + ) + } + private fun refreshPasses() = viewModelScope.launch { val settings = settingsRepo.passesSettings.value satelliteRepo.calculatePasses( @@ -283,7 +288,7 @@ class PassesViewModel( aosStartMinute = settings.aosStartMinute, aosEndMinute = settings.aosEndMinute, invertAosTimeWindow = settings.invertAosTimeWindow, - modes = settings.selectedModes + modes = settingsRepo.selectedSatModes.value ) } diff --git a/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesDialog.kt b/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesDialog.kt index 025fd100..2147f9f3 100644 --- a/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesDialog.kt +++ b/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesDialog.kt @@ -45,18 +45,18 @@ import com.rtbishop.look4sat.core.presentation.SharedDialog @Preview(showBackground = true) @Composable -private fun MultiTypesDialogPreview() { - val types = listOf("Amateur", "Geostationary", "Military", "Weather") - MainTheme { MultiTypesDialog(types, emptyList(), {}) {} } +private fun MultiModesDialogPreview() { + val modes = listOf("FM", "APT", "SSTV", "BPSK") + MainTheme { MultiModesDialog(modes, emptyList(), {}) {} } } @Composable -internal fun MultiTypesDialog( - allTypes: List, types: List, cancel: () -> Unit, accept: (List) -> Unit +internal fun MultiModesDialog( + allModes: List, modes: List, cancel: () -> Unit, accept: (List) -> Unit ) { - val selected = remember { mutableStateOf(types.toSet()) } - val toggle = { type: String -> - selected.value = if (type in selected.value) selected.value - type else selected.value + type + val selected = remember { mutableStateOf(modes.toSet()) } + val toggle = { mode: String -> + selected.value = if (mode in selected.value) selected.value - mode else selected.value + mode } val onAccept = { accept(selected.value.toList()) } SharedDialog(title = stringResource(R.string.sat_type_title), onCancel = cancel, onAccept = onAccept) { @@ -68,7 +68,7 @@ internal fun MultiTypesDialog( horizontalArrangement = Arrangement.spacedBy(1.dp), verticalArrangement = Arrangement.spacedBy(1.dp) ) { - itemsIndexed(allTypes) { index, item -> + itemsIndexed(allModes) { index, item -> Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier diff --git a/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesScreen.kt b/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesScreen.kt index c487e3b4..33441d7c 100644 --- a/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesScreen.kt +++ b/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesScreen.kt @@ -87,11 +87,11 @@ private fun SatellitesScreen( navigateUp: () -> Unit ) { if (uiState.isDialogShown) { - MultiTypesDialog( - allTypes = uiState.typesList, - types = uiState.currentTypes, - cancel = { onAction(SatellitesAction.ToggleTypesDialog) }, - accept = { onAction(SatellitesAction.SelectTypes(it)) } + MultiModesDialog( + allModes = uiState.modesList, + modes = uiState.currentModes, + cancel = { onAction(SatellitesAction.ToggleModesDialog) }, + accept = { onAction(SatellitesAction.SelectModes(it)) } ) } if (uiState.shouldSeeWarning) { @@ -123,9 +123,9 @@ private fun SatellitesScreen( Column(modifier = Modifier.layoutPadding(), verticalArrangement = Arrangement.spacedBy(6.dp)) { if (isVerticalLayout()) { TopBar { - TypeCard( - types = uiState.currentTypes, - onClick = { onAction(SatellitesAction.ToggleTypesDialog) }, + ModeCard( + modes = uiState.currentModes, + onClick = { onAction(SatellitesAction.ToggleModesDialog) }, modifier = Modifier.weight(1f) ) PrimaryIconCard( @@ -157,9 +157,9 @@ private fun SatellitesScreen( resId = R.drawable.ic_done, modifier = Modifier.semantics { contentDescription = primCardCd } ) - TypeCard( - types = uiState.currentTypes, - onClick = { onAction(SatellitesAction.ToggleTypesDialog) }, + ModeCard( + modes = uiState.currentModes, + onClick = { onAction(SatellitesAction.ToggleModesDialog) }, modifier = Modifier.weight(1f) ) SearchBar( @@ -241,8 +241,8 @@ private fun SearchBar(onQueryChange: (String) -> Unit, modifier: Modifier = Modi } @Composable -private fun TypeCard(types: List, onClick: () -> Unit, modifier: Modifier = Modifier) { - val typesText = if (types.isEmpty()) "All" else types.joinToString(", ") +private fun ModeCard(modes: List, onClick: () -> Unit, modifier: Modifier = Modifier) { + val modesText = if (modes.isEmpty()) "All" else modes.joinToString(", ") ElevatedCard(modifier = modifier) { Row( horizontalArrangement = Arrangement.spacedBy(12.dp), @@ -252,12 +252,12 @@ private fun TypeCard(types: List, onClick: () -> Unit, modifier: Modifie .clickable { onClick() }) { Spacer(Modifier) Icon( - painter = painterResource(id = R.drawable.ic_tags), + painter = painterResource(id = R.drawable.ic_radios), contentDescription = null, tint = MaterialTheme.colorScheme.primary ) Text( - text = stringResource(R.string.sat_type_hint, typesText), + text = stringResource(R.string.sat_type_hint, modesText), fontSize = 16.sp, fontWeight = FontWeight.Medium, modifier = Modifier @@ -323,9 +323,54 @@ private fun SatellitesPreview() { @Composable private fun SatellitesCard(items: List, onSelected: (Int, Boolean) -> Unit) { + val selectedItems = items.filter { it.isSelected } + val availableItems = items.filterNot { it.isSelected } + LazyVerticalGrid(columns = GridCells.Adaptive(320.dp)) { - items(items = items, key = { item -> item.catnum }) { entry -> - Satellite(entry, onSelected, Modifier.animateItem()) + stickyHeader(key = "sat_summary_header") { + SummaryHeader( + selectedText = stringResource(R.string.sat_group_selected_count, selectedItems.size), + availableText = stringResource(R.string.sat_group_available_count, availableItems.size) + ) + } + + if (selectedItems.isNotEmpty()) { + items(items = selectedItems, key = { item -> item.catnum }) { entry -> + Satellite(entry, onSelected, Modifier.animateItem()) + } + } + + if (availableItems.isNotEmpty()) { + items(items = availableItems, key = { item -> item.catnum }) { entry -> + Satellite(entry, onSelected, Modifier.animateItem()) + } } } } + +@Composable +private fun SummaryHeader(selectedText: String, availableText: String) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surfaceContainerHighest) + .padding(horizontal = 12.dp, vertical = 4.dp) + ) { + Text( + text = selectedText, + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = availableText, + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } +} + + diff --git a/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesState.kt b/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesState.kt index c82bc162..e0714c64 100644 --- a/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesState.kt +++ b/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesState.kt @@ -24,8 +24,8 @@ data class SatellitesState( val isLoading: Boolean = true, val shouldSeeWarning: Boolean = false, val itemsList: List = emptyList(), - val currentTypes: List = emptyList(), - val typesList: List = emptyList() + val currentModes: List = emptyList(), + val modesList: List = emptyList() ) sealed interface SatellitesAction { @@ -34,7 +34,7 @@ sealed interface SatellitesAction { data class SearchFor(val query: String) : SatellitesAction data object SelectAll : SatellitesAction data class SelectSingle(val id: Int, val isTicked: Boolean) : SatellitesAction - data class SelectTypes(val types: List) : SatellitesAction - data object ToggleTypesDialog : SatellitesAction + data class SelectModes(val modes: List) : SatellitesAction + data object ToggleModesDialog : SatellitesAction data object UnselectAll : SatellitesAction } diff --git a/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesViewModel.kt b/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesViewModel.kt index fdc4b9b3..72bf17fa 100644 --- a/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesViewModel.kt +++ b/feature/satellites/src/main/java/com/rtbishop/look4sat/feature/satellites/SatellitesViewModel.kt @@ -35,13 +35,13 @@ class SatellitesViewModel( private val settingsRepo: ISettingsRepo ) : ViewModel() { - private val defaultTypes = selectionRepo.getCurrentTypes() + private val defaultModes = selectionRepo.getCurrentModes() private val _uiState = MutableStateFlow( SatellitesState( isLoading = true, shouldSeeWarning = settingsRepo.otherSettings.value.shouldSeeWarning, - currentTypes = defaultTypes, - typesList = selectionRepo.getTypesList() + currentModes = defaultModes, + modesList = selectionRepo.getModesList() ) ) val uiState: StateFlow = _uiState @@ -49,7 +49,7 @@ class SatellitesViewModel( init { viewModelScope.launch { selectionRepo.setQuery(String()) - selectionRepo.setTypes(defaultTypes) + selectionRepo.setModes(defaultModes) selectionRepo.getEntriesFlow().collectLatest { items -> _uiState.update { it.copy(isLoading = false, itemsList = items) } } @@ -68,8 +68,8 @@ class SatellitesViewModel( is SatellitesAction.SearchFor -> searchFor(action.query) SatellitesAction.SelectAll -> selectAll(true) is SatellitesAction.SelectSingle -> selectSingle(action.id, action.isTicked) - is SatellitesAction.SelectTypes -> selectTypes(action.types) - SatellitesAction.ToggleTypesDialog -> toggleTypesDialog() + is SatellitesAction.SelectModes -> selectModes(action.modes) + SatellitesAction.ToggleModesDialog -> toggleModesDialog() SatellitesAction.UnselectAll -> selectAll(false) } } @@ -86,12 +86,12 @@ class SatellitesViewModel( selectionRepo.setSelection(listOf(id), isTicked.not()) } - private fun selectTypes(types: List) = viewModelScope.launch { - selectionRepo.setTypes(types) - _uiState.update { it.copy(currentTypes = types, isDialogShown = false) } + private fun selectModes(modes: List) = viewModelScope.launch { + selectionRepo.setModes(modes) + _uiState.update { it.copy(currentModes = modes, isDialogShown = false) } } - private fun toggleTypesDialog() { + private fun toggleModesDialog() { _uiState.update { it.copy(isDialogShown = !it.isDialogShown) } }