mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-04 19:47:22 +00:00
fix(map): scope grid QSO detail to the selected operated grid (台址)
The VUCC grid-detail dialog listed every station location's QSOs even when a specific operated grid was selected; only 'All' should combine them. The 台址 grid-set key derivation is now shared between the selector's grouping and the dialog filter (core/domain extensions + unit tests).
This commit is contained in:
1 parent
6a263d8422
commit
7589f3f37d
3 files changed
+134
-5
No files matched your search
@@ -104,3 +104,30 @@ data class GridQso(
|
||||
else -> band.trim().uppercase()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Every MY-side grid of this QSO: [myGrids] (MY_GRIDSQUARE + MY_VUCC_GRIDS)
|
||||
* with a single-grid fallback to [myGrid] for records synced before
|
||||
* multi-grid support. Empty when the record carries no MY-side grid at all.
|
||||
*/
|
||||
fun GridQso.myStationGrids(): Set<String> =
|
||||
myGrids.ifEmpty { myGrid?.let { setOf(it) }.orEmpty() }
|
||||
|
||||
/**
|
||||
* Station-location (台址) grouping key: the QSO's MY grid set, sorted and
|
||||
* comma-joined ("OL62", "OM60,PM01"). Records covering the same grid set
|
||||
* belong to the same 台址 — they are VUCC-equivalent. Null when the record
|
||||
* carries no MY-side grid. Backs both the map's operated-grid selector and
|
||||
* its per-台址 grid-detail filtering, so the two scope by one identity.
|
||||
*/
|
||||
fun GridQso.stationGridSetKey(): String? =
|
||||
myStationGrids().takeIf { it.isNotEmpty() }?.sorted()?.joinToString(",")
|
||||
|
||||
/**
|
||||
* Scopes a per-grid QSO list to the operated-grid selector's choice:
|
||||
* [stationId] == null ("All") keeps the list as-is, otherwise only records
|
||||
* whose [stationGridSetKey] matches stay. Records without a MY-side grid
|
||||
* belong to no 台址 and are dropped under any specific scope.
|
||||
*/
|
||||
fun List<GridQso>.scopedToStation(stationId: String?): List<GridQso> =
|
||||
if (stationId == null) this else filter { it.stationGridSetKey() == stationId }
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.rtbishop.look4sat.core.domain.model
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* The map's VUCC view scopes QSO data to the operated-grid selector's choice
|
||||
* (台址 = station location): [stationGridSetKey] identifies a record's 台址 and
|
||||
* [scopedToStation] applies the choice — the grid-detail dialog relies on both.
|
||||
*/
|
||||
class GridQsoStationScopeTest {
|
||||
|
||||
private fun qso(
|
||||
call: String = "BG7XYZ",
|
||||
myGrid: String? = null,
|
||||
myGrids: Set<String> = emptySet()
|
||||
) = GridQso(
|
||||
call = call, epochMs = 0L, satName = "SO-50", mode = "FM",
|
||||
bandUp = "70CM", bandDown = "2M", myGrid = myGrid, myGrids = myGrids
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `multi-grid record keys by its full sorted grid set`() {
|
||||
assertEquals("OL62,PM01", qso(myGrids = setOf("PM01", "OL62")).stationGridSetKey())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `legacy single-grid record falls back to myGrid`() {
|
||||
assertEquals("OL62", qso(myGrid = "OL62").stationGridSetKey())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `myGrids wins over myGrid when both are present`() {
|
||||
val q = qso(myGrid = "OL62", myGrids = setOf("OM60", "PM01"))
|
||||
assertEquals("OM60,PM01", q.stationGridSetKey())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `record without any MY grid carries no key`() {
|
||||
assertNull(qso().stationGridSetKey())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `key does not depend on the grid set's iteration order`() {
|
||||
assertEquals(
|
||||
qso(myGrids = setOf("PM01", "OL62")).stationGridSetKey(),
|
||||
qso(myGrids = setOf("OL62", "PM01")).stationGridSetKey()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `null scope keeps every record`() {
|
||||
val a = qso("A", myGrid = "OL62")
|
||||
val b = qso("B", myGrid = "PM01")
|
||||
assertEquals(listOf(a, b), listOf(a, b).scopedToStation(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `specific scope keeps only the matching station's records`() {
|
||||
val multi = qso("A", myGrids = setOf("OL62", "PM01"))
|
||||
val single = qso("B", myGrid = "OL62")
|
||||
val list = listOf(multi, single)
|
||||
assertEquals(listOf(multi), list.scopedToStation("OL62,PM01"))
|
||||
assertEquals(listOf(single), list.scopedToStation("OL62"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `records without a MY grid belong to no station scope`() {
|
||||
val orphan = qso("A")
|
||||
val mine = qso("B", myGrid = "OL62")
|
||||
assertEquals(listOf(mine), listOf(orphan, mine).scopedToStation("OL62"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `scoping to a station absent from the list yields empty`() {
|
||||
val list = listOf(qso("A", myGrid = "OL62"))
|
||||
assertEquals(emptyList<GridQso>(), list.scopedToStation("PM01"))
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,9 @@ import com.rtbishop.look4sat.core.domain.model.AwardCalculator
|
||||
import com.rtbishop.look4sat.core.domain.model.AwardProgress
|
||||
import com.rtbishop.look4sat.core.domain.model.AwardType
|
||||
import com.rtbishop.look4sat.core.domain.model.MapSource
|
||||
import com.rtbishop.look4sat.core.domain.model.myStationGrids
|
||||
import com.rtbishop.look4sat.core.domain.model.scopedToStation
|
||||
import com.rtbishop.look4sat.core.domain.model.stationGridSetKey
|
||||
import com.rtbishop.look4sat.core.domain.predict.GeoPos
|
||||
import com.rtbishop.look4sat.core.domain.predict.OrbitalObject
|
||||
import com.rtbishop.look4sat.core.domain.predict.OrbitalPos
|
||||
@@ -321,10 +324,8 @@ private fun MapScreen(
|
||||
val byGridSet = LinkedHashMap<String, Pair<Set<String>, MutableSet<String>>>()
|
||||
for ((grid, qsos) in uiState.workedGridQsos) {
|
||||
for (q in qsos) {
|
||||
val gs = q.myGrids.ifEmpty { q.myGrid?.let { setOf(it) }.orEmpty() }
|
||||
if (gs.isEmpty()) continue
|
||||
val key = gs.sorted().joinToString(",")
|
||||
byGridSet.getOrPut(key) { gs to mutableSetOf() }.second.add(grid)
|
||||
val key = q.stationGridSetKey() ?: continue
|
||||
byGridSet.getOrPut(key) { q.myStationGrids() to mutableSetOf() }.second.add(grid)
|
||||
}
|
||||
}
|
||||
byGridSet.map { (key, v) -> StationGroup(id = key, grids = v.first, worked = v.second) }
|
||||
@@ -539,7 +540,11 @@ private fun MapScreen(
|
||||
selectedGrid?.let { grid ->
|
||||
WorkedGridQsoDialog(
|
||||
grid = grid,
|
||||
qsos = uiState.workedGridQsos[grid].orEmpty().sortedBy { it.epochMs },
|
||||
// Scope to the selected 台址 (null = All): a specific station location
|
||||
// must not show other locations' QSOs (bug report 2026-10-05).
|
||||
qsos = uiState.workedGridQsos[grid].orEmpty()
|
||||
.scopedToStation(selectedStationId)
|
||||
.sortedBy { it.epochMs },
|
||||
marked = uiState.markedGrids[grid].orEmpty(),
|
||||
isUtc = uiState.isUtc,
|
||||
matchCalculating = matchCalculating,
|
||||
|
||||
Reference in new issue
Block a user