mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
feat: satellite page transponder/activity filters as virtual types
The satellite selection screen's 'Select satellite type' dialog now lists three virtual types first (AMSAT Live FM, AMSAT Live Linear, Live SSTV) before the TLE-source types. Selecting one filters the satellite list to that transponder/activity set; multiple selections union. 'All' is kept. - SelectionRepo.getTypesList: virtual types prepended (All preserved) - SelectionRepo.resolveTypeIds: FM/Linear -> AMSAT catnum sets, Live SSTV -> mode=SSTV radios; other types via persisted IDs - Tests: virtual-type filtering, union, clear, list-order assertion
This commit is contained in:
1 parent
85a4b9cfa3
commit
0163c7b217
3 files changed
+102
-9
No files matched your search
+25
-3
@@ -43,11 +43,13 @@ class SelectionRepo(
|
|||||||
|
|
||||||
// Resolve type IDs once when types change, then filter items reactively.
|
// Resolve type IDs once when types change, then filter items reactively.
|
||||||
// The HashSet gives O(1) catnum lookups instead of O(n) with a List.
|
// The HashSet gives O(1) catnum lookups instead of O(n) with a List.
|
||||||
|
// The three virtual types ("AMSAT Live FM", "AMSAT Live Linear", "Live SSTV")
|
||||||
|
// are resolved from live transponder data instead of SharedPreferences.
|
||||||
private val itemsWithTypes = currentTypes.flatMapLatest { types: List<String> ->
|
private val itemsWithTypes = currentTypes.flatMapLatest { types: List<String> ->
|
||||||
val catnumSet: Set<Int>? = if (types.isEmpty()) {
|
val catnumSet: Set<Int>? = if (types.isEmpty()) {
|
||||||
null // null = no filtering
|
null // null = no filtering
|
||||||
} else {
|
} else {
|
||||||
val ids = settingsRepo.getSatelliteTypesIds(types)
|
val ids = resolveTypeIds(types)
|
||||||
if (ids.isEmpty()) null else ids.toHashSet()
|
if (ids.isEmpty()) null else ids.toHashSet()
|
||||||
}
|
}
|
||||||
currentItems.map { items ->
|
currentItems.map { items ->
|
||||||
@@ -61,8 +63,28 @@ class SelectionRepo(
|
|||||||
|
|
||||||
override fun getCurrentTypes() = currentTypes.value
|
override fun getCurrentTypes() = currentTypes.value
|
||||||
|
|
||||||
override fun getTypesList() = Sources.satelliteDataUrls.keys.sorted().toMutableList().apply {
|
/**
|
||||||
removeAt(0)
|
* Resolves a list of type names to satellite catnums. The three virtual
|
||||||
|
* transponder/activity types are resolved from live data; all other types
|
||||||
|
* come from the per-type ID lists persisted by [ISettingsRepo].
|
||||||
|
*/
|
||||||
|
private suspend fun resolveTypeIds(types: List<String>): List<Int> {
|
||||||
|
val idsSet = mutableSetOf<Int>()
|
||||||
|
types.forEach { type ->
|
||||||
|
when (type) {
|
||||||
|
"AMSAT Live FM" -> idsSet.addAll(settingsRepo.getAmSatFmCatnums())
|
||||||
|
"AMSAT Live Linear" -> idsSet.addAll(settingsRepo.getAmSatLinearCatnums())
|
||||||
|
"Live SSTV" -> idsSet.addAll(localSource.getIdsWithModes(listOf("SSTV")))
|
||||||
|
else -> idsSet.addAll(settingsRepo.getSatelliteTypesIds(listOf(type)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return idsSet.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTypesList() = buildList {
|
||||||
|
// 三个转发器/活动虚拟类型排在最前.
|
||||||
|
addAll(listOf("AMSAT Live FM", "AMSAT Live Linear", "Live SSTV"))
|
||||||
|
addAll(Sources.satelliteDataUrls.keys.sorted().toMutableList().apply { removeAt(0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getEntriesFlow() = withContext(dispatcher) {
|
override suspend fun getEntriesFlow() = withContext(dispatcher) {
|
||||||
|
|||||||
+76
-6
@@ -112,22 +112,86 @@ class SelectionRepoSearchTest {
|
|||||||
assertTrue(repo.getEntriesFlow().first().isEmpty())
|
assertTrue(repo.getEntriesFlow().first().isEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRepo(items: List<SatItem>): ISelectionRepo {
|
@Test
|
||||||
|
fun `FM virtual type shows only AMSAT FM list satellites`() = runTest {
|
||||||
|
val repo = createRepo(
|
||||||
|
items = sampleItems,
|
||||||
|
amSatFm = setOf(25544, 39444)
|
||||||
|
)
|
||||||
|
repo.setTypes(listOf("AMSAT Live FM"))
|
||||||
|
val results = repo.getEntriesFlow().first()
|
||||||
|
assertEquals(listOf(25544, 39444), results.map { it.catnum })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `SSTV virtual type shows only satellites with SSTV radios`() = runTest {
|
||||||
|
val repo = createRepo(
|
||||||
|
items = sampleItems,
|
||||||
|
sstvIds = listOf(43803)
|
||||||
|
)
|
||||||
|
repo.setTypes(listOf("Live SSTV"))
|
||||||
|
val results = repo.getEntriesFlow().first()
|
||||||
|
assertEquals(listOf(43803), results.map { it.catnum })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `multiple virtual types union their satellites`() = runTest {
|
||||||
|
val repo = createRepo(
|
||||||
|
items = sampleItems,
|
||||||
|
amSatFm = setOf(25544),
|
||||||
|
amSatLinear = setOf(7530),
|
||||||
|
sstvIds = listOf(43803)
|
||||||
|
)
|
||||||
|
repo.setTypes(listOf("AMSAT Live FM", "Live SSTV"))
|
||||||
|
val results = repo.getEntriesFlow().first()
|
||||||
|
assertEquals(setOf(25544, 43803), results.map { it.catnum }.toSet())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clearing types restores full list`() = runTest {
|
||||||
|
val repo = createRepo(items = sampleItems, amSatFm = setOf(25544))
|
||||||
|
repo.setTypes(listOf("AMSAT Live FM"))
|
||||||
|
assertTrue(repo.getEntriesFlow().first().isNotEmpty())
|
||||||
|
repo.setTypes(emptyList())
|
||||||
|
assertEquals(sampleItems.size, repo.getEntriesFlow().first().size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `types list has virtual transponder types first and keeps All`() {
|
||||||
|
val repo = createRepo(sampleItems)
|
||||||
|
val types = repo.getTypesList()
|
||||||
|
assertTrue(types.size >= 4)
|
||||||
|
assertEquals("AMSAT Live FM", types[0])
|
||||||
|
assertEquals("AMSAT Live Linear", types[1])
|
||||||
|
assertEquals("Live SSTV", types[2])
|
||||||
|
// "All" must be kept in the list (first original type shown after virtual ones).
|
||||||
|
assertTrue("All" in types)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createRepo(
|
||||||
|
items: List<SatItem>,
|
||||||
|
amSatFm: Set<Int> = emptySet(),
|
||||||
|
amSatLinear: Set<Int> = emptySet(),
|
||||||
|
sstvIds: List<Int> = emptyList()
|
||||||
|
): ISelectionRepo {
|
||||||
return SelectionRepo(
|
return SelectionRepo(
|
||||||
dispatcher = Dispatchers.Unconfined,
|
dispatcher = Dispatchers.Unconfined,
|
||||||
localSource = FakeLocalSourceForSearch(items),
|
localSource = FakeLocalSourceForSearch(items, sstvIds),
|
||||||
settingsRepo = FakeSettingsRepoForSearch()
|
settingsRepo = FakeSettingsRepoForSearch(amSatFm, amSatLinear)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FakeLocalSourceForSearch(private val items: List<SatItem>) : ILocalSource {
|
private class FakeLocalSourceForSearch(
|
||||||
|
private val items: List<SatItem>,
|
||||||
|
private val sstvIds: List<Int> = emptyList()
|
||||||
|
) : ILocalSource {
|
||||||
override suspend fun getEntriesTotal(): Int = items.size
|
override suspend fun getEntriesTotal(): Int = items.size
|
||||||
override suspend fun getEntriesList(): List<SatItem> = items
|
override suspend fun getEntriesList(): List<SatItem> = items
|
||||||
override suspend fun getEntriesWithIds(ids: List<Int>): List<OrbitalObject> = emptyList()
|
override suspend fun getEntriesWithIds(ids: List<Int>): List<OrbitalObject> = emptyList()
|
||||||
override suspend fun insertEntries(entries: List<OrbitalData>) = Unit
|
override suspend fun insertEntries(entries: List<OrbitalData>) = Unit
|
||||||
override suspend fun deleteEntries() = Unit
|
override suspend fun deleteEntries() = Unit
|
||||||
override suspend fun getIdsWithModes(modes: List<String>): List<Int> = emptyList()
|
override suspend fun getIdsWithModes(modes: List<String>): List<Int> = sstvIds
|
||||||
override suspend fun getRadiosTotal(): Int = 0
|
override suspend fun getRadiosTotal(): Int = 0
|
||||||
override suspend fun getRadiosWithId(id: Int): List<SatRadio> = emptyList()
|
override suspend fun getRadiosWithId(id: Int): List<SatRadio> = emptyList()
|
||||||
override suspend fun insertRadios(radios: List<SatRadio>, isCustom: Boolean) = Unit
|
override suspend fun insertRadios(radios: List<SatRadio>, isCustom: Boolean) = Unit
|
||||||
@@ -135,7 +199,10 @@ private class FakeLocalSourceForSearch(private val items: List<SatItem>) : ILoca
|
|||||||
override suspend fun deleteRadios() = Unit
|
override suspend fun deleteRadios() = Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FakeSettingsRepoForSearch : ISettingsRepo {
|
private class FakeSettingsRepoForSearch(
|
||||||
|
private val amSatFm: Set<Int> = emptySet(),
|
||||||
|
private val amSatLinear: Set<Int> = emptySet()
|
||||||
|
) : ISettingsRepo {
|
||||||
override val appVersionName: String = "test"
|
override val appVersionName: String = "test"
|
||||||
override val selectedIds: StateFlow<List<Int>> = MutableStateFlow(emptyList())
|
override val selectedIds: StateFlow<List<Int>> = MutableStateFlow(emptyList())
|
||||||
override val selectedTypes: StateFlow<List<String>> = MutableStateFlow(emptyList())
|
override val selectedTypes: StateFlow<List<String>> = MutableStateFlow(emptyList())
|
||||||
@@ -177,6 +244,9 @@ private class FakeSettingsRepoForSearch : ISettingsRepo {
|
|||||||
override fun updateOtherSettings(transform: (OtherSettings) -> OtherSettings) = Unit
|
override fun updateOtherSettings(transform: (OtherSettings) -> OtherSettings) = Unit
|
||||||
override fun updateDataSourcesSettings(settings: DataSourcesSettings) = Unit
|
override fun updateDataSourcesSettings(settings: DataSourcesSettings) = Unit
|
||||||
override fun updateDataSourcesStatus(status: Map<String, Int>) = Unit
|
override fun updateDataSourcesStatus(status: Map<String, Int>) = Unit
|
||||||
|
override fun getAmSatFmCatnums(): Set<Int> = amSatFm
|
||||||
|
override fun getAmSatLinearCatnums(): Set<Int> = amSatLinear
|
||||||
|
override fun setAmSatCatnums(fmCatnums: Set<Int>, linearCatnums: Set<Int>) = Unit
|
||||||
override fun updateRadioControlSettings(settings: RadioControlSettings) = Unit
|
override fun updateRadioControlSettings(settings: RadioControlSettings) = Unit
|
||||||
override fun getSatelliteOffset(catnum: Int): String = ""
|
override fun getSatelliteOffset(catnum: Int): String = ""
|
||||||
override fun setSatelliteOffset(catnum: Int, offset: String) = Unit
|
override fun setSatelliteOffset(catnum: Int, offset: String) = Unit
|
||||||
|
|||||||
+1
@@ -177,6 +177,7 @@ private fun SatellitesScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 转发器/活动筛选已在"卫星类型"对话框前三个选项中体现(AMSAT Live FM/Linear, Live SSTV).
|
||||||
ElevatedCard(modifier = Modifier.fillMaxSize()) {
|
ElevatedCard(modifier = Modifier.fillMaxSize()) {
|
||||||
val emptyMessage = stringResource(R.string.sat_empty_list_message)
|
val emptyMessage = stringResource(R.string.sat_empty_list_message)
|
||||||
when {
|
when {
|
||||||
|
|||||||
Reference in new issue
Block a user