mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
feat(map): multiple marked stations per grid with pin-to-top
- MarkedStation storage upgraded to an ordered list per grid (Map<String, List<MarkedStation>>); JSON array shape with legacy v14.1 single-object migration on read - MapAction: SetMarkedStation appends (duplicate callsign ignored), RemoveMarkedStation deletes by callsign, PinMarkedStation moves a mark to the top of the list - MaidenheadGridOverlay: red fill for any marked grid unchanged; first-call label mode shows the FIRST (pinned) marked callsign - MapScreen: one row per mark (pale-yellow callsign), pin icon (冖 + stroke ↑, drawn in-app) left of the Marked label — shown only when the grid has 2+ marks; pinned row's icon grey, others pale yellow; delete icon per row - New ic_pin.xml vector drawable; strings grid_mark_pin
This commit is contained in:
1 parent
c8fdd2e93e
commit
62ad995aeb
13 files changed
+154
-62
No files matched your search
+33
-17
@@ -270,22 +270,35 @@ class SettingsRepo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Marked stations in unworked gridsquares, persisted as a single JSON
|
// Marked stations in unworked gridsquares, persisted as a single JSON
|
||||||
// object: {"OL62":{"c":call,"t":epochMs}}. One mark per grid.
|
// object: {"OL62":[{"c":call,"t":epochMs}, ...]}. Each grid holds an
|
||||||
|
// ordered list (first = pinned/top). v14.1 stored a single object per
|
||||||
|
// grid; reads transparently migrate that old shape to a one-element list.
|
||||||
private val keyMarkedGridStations = "markedGridStations"
|
private val keyMarkedGridStations = "markedGridStations"
|
||||||
|
|
||||||
override fun getMarkedGridStations(): Map<String, com.rtbishop.look4sat.core.domain.model.MarkedStation> {
|
override fun getMarkedGridStations(): Map<String, List<com.rtbishop.look4sat.core.domain.model.MarkedStation>> {
|
||||||
val json = preferences.getString(keyMarkedGridStations, null).orEmpty()
|
val json = preferences.getString(keyMarkedGridStations, null).orEmpty()
|
||||||
if (json.isBlank()) return emptyMap()
|
if (json.isBlank()) return emptyMap()
|
||||||
return try {
|
return try {
|
||||||
val root = org.json.JSONObject(json)
|
val root = org.json.JSONObject(json)
|
||||||
val result = mutableMapOf<String, com.rtbishop.look4sat.core.domain.model.MarkedStation>()
|
val result = mutableMapOf<String, List<com.rtbishop.look4sat.core.domain.model.MarkedStation>>()
|
||||||
for (grid in root.keys()) {
|
for (grid in root.keys()) {
|
||||||
val o = root.optJSONObject(grid) ?: continue
|
val value = root.opt(grid)
|
||||||
val call = o.optString("c").ifBlank { continue }
|
val list = when (value) {
|
||||||
result[grid] = com.rtbishop.look4sat.core.domain.model.MarkedStation(
|
// v14.2+ shape: JSON array of station objects.
|
||||||
call = call,
|
is org.json.JSONArray -> (0 until value.length()).mapNotNull { i ->
|
||||||
epochMs = o.optLong("t")
|
val o = value.optJSONObject(i) ?: return@mapNotNull null
|
||||||
)
|
val call = o.optString("c").ifBlank { return@mapNotNull null }
|
||||||
|
com.rtbishop.look4sat.core.domain.model.MarkedStation(call = call, epochMs = o.optLong("t"))
|
||||||
|
}
|
||||||
|
// v14.1 legacy shape: a single station object per grid.
|
||||||
|
is org.json.JSONObject -> {
|
||||||
|
val call = value.optString("c").ifBlank { null }
|
||||||
|
if (call == null) emptyList()
|
||||||
|
else listOf(com.rtbishop.look4sat.core.domain.model.MarkedStation(call = call, epochMs = value.optLong("t")))
|
||||||
|
}
|
||||||
|
else -> emptyList()
|
||||||
|
}
|
||||||
|
if (list.isNotEmpty()) result[grid] = list
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
@@ -293,15 +306,18 @@ class SettingsRepo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setMarkedGridStations(stations: Map<String, com.rtbishop.look4sat.core.domain.model.MarkedStation>) {
|
override fun setMarkedGridStations(stations: Map<String, List<com.rtbishop.look4sat.core.domain.model.MarkedStation>>) {
|
||||||
val root = org.json.JSONObject()
|
val root = org.json.JSONObject()
|
||||||
for ((grid, station) in stations) {
|
for ((grid, list) in stations) {
|
||||||
root.put(
|
val array = org.json.JSONArray()
|
||||||
grid,
|
for (station in list) {
|
||||||
org.json.JSONObject()
|
array.put(
|
||||||
.put("c", station.call)
|
org.json.JSONObject()
|
||||||
.put("t", station.epochMs)
|
.put("c", station.call)
|
||||||
)
|
.put("t", station.epochMs)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
root.put(grid, array)
|
||||||
}
|
}
|
||||||
preferences.edit { putString(keyMarkedGridStations, root.toString()) }
|
preferences.edit { putString(keyMarkedGridStations, root.toString()) }
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -313,9 +313,9 @@ private class FakeSettingsRepo(dataSources: DataSourcesSettings = defaultDataSou
|
|||||||
roamedGrids.value = grids
|
roamedGrids.value = grids
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMarkedGridStations(): Map<String, com.rtbishop.look4sat.core.domain.model.MarkedStation> = emptyMap()
|
override fun getMarkedGridStations(): Map<String, List<com.rtbishop.look4sat.core.domain.model.MarkedStation>> = emptyMap()
|
||||||
|
|
||||||
override fun setMarkedGridStations(stations: Map<String, com.rtbishop.look4sat.core.domain.model.MarkedStation>) = Unit
|
override fun setMarkedGridStations(stations: Map<String, List<com.rtbishop.look4sat.core.domain.model.MarkedStation>>) = Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun defaultDataSourcesSettings(): DataSourcesSettings {
|
private fun defaultDataSourcesSettings(): DataSourcesSettings {
|
||||||
|
|||||||
+2
-2
@@ -189,8 +189,8 @@ private class FakeSettingsRepoForSearch : ISettingsRepo {
|
|||||||
override fun setWorkedGridQsos(qsos: Map<String, List<com.rtbishop.look4sat.core.domain.model.GridQso>>) = Unit
|
override fun setWorkedGridQsos(qsos: Map<String, List<com.rtbishop.look4sat.core.domain.model.GridQso>>) = Unit
|
||||||
override fun getRoamedGrids(): Set<String> = emptySet()
|
override fun getRoamedGrids(): Set<String> = emptySet()
|
||||||
override fun setRoamedGrids(grids: Set<String>) = Unit
|
override fun setRoamedGrids(grids: Set<String>) = Unit
|
||||||
override fun getMarkedGridStations(): Map<String, com.rtbishop.look4sat.core.domain.model.MarkedStation> = emptyMap()
|
override fun getMarkedGridStations(): Map<String, List<com.rtbishop.look4sat.core.domain.model.MarkedStation>> = emptyMap()
|
||||||
override fun setMarkedGridStations(stations: Map<String, com.rtbishop.look4sat.core.domain.model.MarkedStation>) = Unit
|
override fun setMarkedGridStations(stations: Map<String, List<com.rtbishop.look4sat.core.domain.model.MarkedStation>>) = Unit
|
||||||
override fun updateLoTWSettings(settings: com.rtbishop.look4sat.core.domain.model.LoTWSettings) = Unit
|
override fun updateLoTWSettings(settings: com.rtbishop.look4sat.core.domain.model.LoTWSettings) = Unit
|
||||||
override fun getLastLotwSyncDate(): String = ""
|
override fun getLastLotwSyncDate(): String = ""
|
||||||
override fun setLastLotwSyncDate(date: String) = Unit
|
override fun setLastLotwSyncDate(date: String) = Unit
|
||||||
|
|||||||
+4
-4
@@ -106,10 +106,10 @@ interface ISettingsRepo {
|
|||||||
fun getRoamedGrids(): Set<String>
|
fun getRoamedGrids(): Set<String>
|
||||||
fun setRoamedGrids(grids: Set<String>)
|
fun setRoamedGrids(grids: Set<String>)
|
||||||
/** Stations the user marked as "want to contact" in unworked gridsquares
|
/** Stations the user marked as "want to contact" in unworked gridsquares
|
||||||
* (grid -> mark). One mark per grid; cleared automatically when the grid
|
* (grid -> ordered list of marks; the first is the pinned/top one). The
|
||||||
* becomes worked. */
|
* whole grid clears automatically once it becomes worked. */
|
||||||
fun getMarkedGridStations(): Map<String, com.rtbishop.look4sat.core.domain.model.MarkedStation>
|
fun getMarkedGridStations(): Map<String, List<com.rtbishop.look4sat.core.domain.model.MarkedStation>>
|
||||||
fun setMarkedGridStations(stations: Map<String, com.rtbishop.look4sat.core.domain.model.MarkedStation>)
|
fun setMarkedGridStations(stations: Map<String, List<com.rtbishop.look4sat.core.domain.model.MarkedStation>>)
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
//region # LoTW confirmed-grids settings
|
//region # LoTW confirmed-grids settings
|
||||||
|
|||||||
+3
-3
@@ -262,9 +262,9 @@ class LoTWGridSyncTest {
|
|||||||
override fun setWorkedGridQsos(qsos: Map<String, List<GridQso>>) { workedGridQsos = qsos }
|
override fun setWorkedGridQsos(qsos: Map<String, List<GridQso>>) { workedGridQsos = qsos }
|
||||||
override fun getRoamedGrids(): Set<String> = roamedGrids
|
override fun getRoamedGrids(): Set<String> = roamedGrids
|
||||||
override fun setRoamedGrids(grids: Set<String>) { roamedGrids = grids }
|
override fun setRoamedGrids(grids: Set<String>) { roamedGrids = grids }
|
||||||
private var markedGridStations: Map<String, MarkedStation> = emptyMap()
|
private var markedGridStations: Map<String, List<MarkedStation>> = emptyMap()
|
||||||
override fun getMarkedGridStations(): Map<String, MarkedStation> = markedGridStations
|
override fun getMarkedGridStations(): Map<String, List<MarkedStation>> = markedGridStations
|
||||||
override fun setMarkedGridStations(stations: Map<String, MarkedStation>) { markedGridStations = stations }
|
override fun setMarkedGridStations(stations: Map<String, List<MarkedStation>>) { markedGridStations = stations }
|
||||||
override fun getLastLotwSyncDate(): String = lastSyncDate
|
override fun getLastLotwSyncDate(): String = lastSyncDate
|
||||||
override fun setLastLotwSyncDate(date: String) { lastSyncDate = date }
|
override fun setLastLotwSyncDate(date: String) { lastSyncDate = date }
|
||||||
override fun getLastLotwSyncCallsign(): String = lastSyncCallsign
|
override fun getLastLotwSyncCallsign(): String = lastSyncCallsign
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<!-- 冖 (秃宝盖): 顶横 + 两侧短竖, 笔画型 -->
|
||||||
|
<path
|
||||||
|
android:pathData="M5,4 L19,4 M5,4 L5,8.5 M19,4 L19,8.5"
|
||||||
|
android:strokeColor="@android:color/white"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:fillColor="#00000000" />
|
||||||
|
<!-- 笔画型向上箭头 ↑: 竖杆 + 两斜线箭头头 -->
|
||||||
|
<path
|
||||||
|
android:pathData="M12,9 L12,20 M12,9 L7.5,14 M12,9 L16.5,14"
|
||||||
|
android:strokeColor="@android:color/white"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:fillColor="#00000000" />
|
||||||
|
</vector>
|
||||||
@@ -137,6 +137,7 @@
|
|||||||
<string name="grid_mark_confirm">标记</string>
|
<string name="grid_mark_confirm">标记</string>
|
||||||
<string name="grid_mark_label">标记</string>
|
<string name="grid_mark_label">标记</string>
|
||||||
<string name="grid_mark_delete">删除标记</string>
|
<string name="grid_mark_delete">删除标记</string>
|
||||||
|
<string name="grid_mark_pin">置顶</string>
|
||||||
<string name="map_visible">日照中</string>
|
<string name="map_visible">日照中</string>
|
||||||
|
|
||||||
<!-- Settings screen -->
|
<!-- Settings screen -->
|
||||||
|
|||||||
@@ -157,6 +157,7 @@
|
|||||||
<string name="grid_mark_confirm">Mark</string>
|
<string name="grid_mark_confirm">Mark</string>
|
||||||
<string name="grid_mark_label">Marked</string>
|
<string name="grid_mark_label">Marked</string>
|
||||||
<string name="grid_mark_delete">Remove mark</string>
|
<string name="grid_mark_delete">Remove mark</string>
|
||||||
|
<string name="grid_mark_pin">Pin to top</string>
|
||||||
<string name="map_visible">Visible</string>
|
<string name="map_visible">Visible</string>
|
||||||
|
|
||||||
<!-- Settings screen -->
|
<!-- Settings screen -->
|
||||||
|
|||||||
+5
-4
@@ -93,9 +93,10 @@ class MaidenheadGridOverlay : Overlay() {
|
|||||||
var workedGrids: Set<String> = emptySet()
|
var workedGrids: Set<String> = emptySet()
|
||||||
|
|
||||||
/** Marked gridsquares (4-char, uppercase) — unworked grids the user has
|
/** Marked gridsquares (4-char, uppercase) — unworked grids the user has
|
||||||
* marked a station they want to contact in; drawn red, same alpha as the
|
* marked stations they want to contact in; drawn red, same alpha as the
|
||||||
* green worked fill. Value carries the marked callsign for label mode. */
|
* green worked fill. Value is the ordered mark list (first = pinned);
|
||||||
var markedGrids: Map<String, com.rtbishop.look4sat.core.domain.model.MarkedStation> = emptyMap()
|
* label mode shows the FIRST marked callsign. */
|
||||||
|
var markedGrids: Map<String, List<com.rtbishop.look4sat.core.domain.model.MarkedStation>> = emptyMap()
|
||||||
|
|
||||||
/** Grid-mode first-call labels: label worked (green) cells with the first
|
/** Grid-mode first-call labels: label worked (green) cells with the first
|
||||||
* callsign worked in that grid instead of the Maidenhead code; non-worked
|
* callsign worked in that grid instead of the Maidenhead code; non-worked
|
||||||
@@ -449,7 +450,7 @@ class MaidenheadGridOverlay : Overlay() {
|
|||||||
label in workedGrids -> firstCallsByGrid[label]?.let { call ->
|
label in workedGrids -> firstCallsByGrid[label]?.let { call ->
|
||||||
canvas.drawText(call, (xLeft + xRight) / 2f, yCenter, firstCallPaint)
|
canvas.drawText(call, (xLeft + xRight) / 2f, yCenter, firstCallPaint)
|
||||||
}
|
}
|
||||||
label in markedGrids -> markedGrids[label]?.let { station ->
|
label in markedGrids -> markedGrids[label]?.firstOrNull()?.let { station ->
|
||||||
canvas.drawText(station.call, (xLeft + xRight) / 2f, yCenter, firstCallPaint)
|
canvas.drawText(station.call, (xLeft + xRight) / 2f, yCenter, firstCallPaint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -429,13 +429,14 @@ private fun MapScreen(
|
|||||||
WorkedGridQsoDialog(
|
WorkedGridQsoDialog(
|
||||||
grid = grid,
|
grid = grid,
|
||||||
qsos = uiState.workedGridQsos[grid].orEmpty().sortedBy { it.epochMs },
|
qsos = uiState.workedGridQsos[grid].orEmpty().sortedBy { it.epochMs },
|
||||||
marked = uiState.markedGrids[grid],
|
marked = uiState.markedGrids[grid].orEmpty(),
|
||||||
isUtc = uiState.isUtc,
|
isUtc = uiState.isUtc,
|
||||||
matchCalculating = matchCalculating,
|
matchCalculating = matchCalculating,
|
||||||
onDismiss = { selectedGrid = null },
|
onDismiss = { selectedGrid = null },
|
||||||
onMatch = { onMatchGrid(grid) },
|
onMatch = { onMatchGrid(grid) },
|
||||||
onMark = { call -> onAction(MapAction.SetMarkedStation(grid, call)) },
|
onMark = { call -> onAction(MapAction.SetMarkedStation(grid, call)) },
|
||||||
onRemoveMark = { onAction(MapAction.RemoveMarkedStation(grid)) }
|
onRemoveMark = { call -> onAction(MapAction.RemoveMarkedStation(grid, call)) },
|
||||||
|
onPinMark = { call -> onAction(MapAction.PinMarkedStation(grid, call)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -446,13 +447,14 @@ private fun MapScreen(
|
|||||||
private fun WorkedGridQsoDialog(
|
private fun WorkedGridQsoDialog(
|
||||||
grid: String,
|
grid: String,
|
||||||
qsos: List<com.rtbishop.look4sat.core.domain.model.GridQso>,
|
qsos: List<com.rtbishop.look4sat.core.domain.model.GridQso>,
|
||||||
marked: com.rtbishop.look4sat.core.domain.model.MarkedStation?,
|
marked: List<com.rtbishop.look4sat.core.domain.model.MarkedStation>,
|
||||||
isUtc: Boolean,
|
isUtc: Boolean,
|
||||||
matchCalculating: Boolean,
|
matchCalculating: Boolean,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onMatch: () -> Unit,
|
onMatch: () -> Unit,
|
||||||
onMark: (String) -> Unit,
|
onMark: (String) -> Unit,
|
||||||
onRemoveMark: () -> Unit
|
onRemoveMark: (String) -> Unit,
|
||||||
|
onPinMark: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
var showMarkInput by remember { mutableStateOf(false) }
|
var showMarkInput by remember { mutableStateOf(false) }
|
||||||
var callInput by remember { mutableStateOf("") }
|
var callInput by remember { mutableStateOf("") }
|
||||||
@@ -512,11 +514,20 @@ private fun WorkedGridQsoDialog(
|
|||||||
.heightIn(max = 380.dp)
|
.heightIn(max = 380.dp)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
// A marked station shows first as a red reminder row: red
|
// Marked stations show first as red reminder rows: pale-yellow
|
||||||
// callsign, "标记" in the count column, the marking date in
|
// callsign, "标记" in the count column, the marking date in the
|
||||||
// the date column, and a small delete icon to remove it.
|
// date column, a small pin icon (first = pinned, grey; others
|
||||||
if (marked != null) {
|
// pale yellow) and a delete icon to remove the mark.
|
||||||
MarkedStationRow(marked = marked, isUtc = isUtc, onRemoveMark = onRemoveMark)
|
val showPin = marked.size > 1
|
||||||
|
marked.forEachIndexed { index, station ->
|
||||||
|
MarkedStationRow(
|
||||||
|
marked = station,
|
||||||
|
isPinned = index == 0,
|
||||||
|
showPin = showPin,
|
||||||
|
isUtc = isUtc,
|
||||||
|
onRemoveMark = { onRemoveMark(station.call) },
|
||||||
|
onPinMark = { onPinMark(station.call) }
|
||||||
|
)
|
||||||
androidx.compose.material3.HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f))
|
androidx.compose.material3.HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f))
|
||||||
}
|
}
|
||||||
if (qsos.isNotEmpty()) {
|
if (qsos.isNotEmpty()) {
|
||||||
@@ -535,7 +546,7 @@ private fun WorkedGridQsoDialog(
|
|||||||
if (qsos.isEmpty()) {
|
if (qsos.isEmpty()) {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
callInput = marked?.call.orEmpty()
|
callInput = ""
|
||||||
showMarkInput = true
|
showMarkInput = true
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
@@ -585,8 +596,11 @@ private fun WorkedGridQsoDialog(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun MarkedStationRow(
|
private fun MarkedStationRow(
|
||||||
marked: com.rtbishop.look4sat.core.domain.model.MarkedStation,
|
marked: com.rtbishop.look4sat.core.domain.model.MarkedStation,
|
||||||
|
isPinned: Boolean,
|
||||||
|
showPin: Boolean,
|
||||||
isUtc: Boolean,
|
isUtc: Boolean,
|
||||||
onRemoveMark: () -> Unit
|
onRemoveMark: () -> Unit,
|
||||||
|
onPinMark: () -> Unit
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@@ -596,18 +610,37 @@ private fun MarkedStationRow(
|
|||||||
text = marked.call,
|
text = marked.call,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace,
|
fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace,
|
||||||
color = ComposeColor(0xFFFF3B30),
|
color = ComposeColor(0xFFFFE082),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
Text(
|
// "标记" column: pin icon (only when the grid has multiple marks) sits
|
||||||
text = stringResource(R.string.grid_mark_label),
|
// left of the label; pinned (first) row's icon is grey, others pale
|
||||||
style = MaterialTheme.typography.bodySmall,
|
// yellow — tapping moves that mark to the top.
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
Row(
|
||||||
textAlign = androidx.compose.ui.text.style.TextAlign.Center,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
) {
|
||||||
|
if (showPin) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_pin),
|
||||||
|
contentDescription = stringResource(R.string.grid_mark_pin),
|
||||||
|
tint = if (isPinned) ComposeColor(0xFF9E9E9E) else ComposeColor(0xFFFFE082),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(18.dp)
|
||||||
|
.clickable(onClick = onPinMark)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(2.dp))
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.grid_mark_label),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
textAlign = androidx.compose.ui.text.style.TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.End,
|
horizontalArrangement = Arrangement.End,
|
||||||
@@ -1056,7 +1089,7 @@ private fun setGridMode(
|
|||||||
centerOnStation: Boolean = false,
|
centerOnStation: Boolean = false,
|
||||||
showFirstCallLabels: Boolean = false,
|
showFirstCallLabels: Boolean = false,
|
||||||
firstCallsByGrid: Map<String, String> = emptyMap(),
|
firstCallsByGrid: Map<String, String> = emptyMap(),
|
||||||
markedGrids: Map<String, com.rtbishop.look4sat.core.domain.model.MarkedStation> = emptyMap()
|
markedGrids: Map<String, List<com.rtbishop.look4sat.core.domain.model.MarkedStation>> = emptyMap()
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
val gridOverlay = mapView.overlays[OVERLAY_GRID]
|
val gridOverlay = mapView.overlays[OVERLAY_GRID]
|
||||||
|
|||||||
@@ -34,8 +34,9 @@ data class MapState(
|
|||||||
val workedGridQsos: Map<String, List<com.rtbishop.look4sat.core.domain.model.GridQso>> = emptyMap(),
|
val workedGridQsos: Map<String, List<com.rtbishop.look4sat.core.domain.model.GridQso>> = emptyMap(),
|
||||||
/** 4-char gridsquares the station operated from (LoTW MY_GRIDSQUARE) — blue stripes. */
|
/** 4-char gridsquares the station operated from (LoTW MY_GRIDSQUARE) — blue stripes. */
|
||||||
val roamedGrids: Set<String> = emptySet(),
|
val roamedGrids: Set<String> = emptySet(),
|
||||||
/** Stations the user marked in unworked gridsquares (grid -> mark) — red fill. */
|
/** Stations the user marked in unworked gridsquares (grid -> ordered list;
|
||||||
val markedGrids: Map<String, com.rtbishop.look4sat.core.domain.model.MarkedStation> = emptyMap(),
|
* first = pinned/top) — red fill. */
|
||||||
|
val markedGrids: Map<String, List<com.rtbishop.look4sat.core.domain.model.MarkedStation>> = emptyMap(),
|
||||||
val stationPosition: GeoPos? = null,
|
val stationPosition: GeoPos? = null,
|
||||||
val orbitalPass: OrbitalPass,
|
val orbitalPass: OrbitalPass,
|
||||||
val track: List<List<GeoPos>>? = null,
|
val track: List<List<GeoPos>>? = null,
|
||||||
@@ -55,7 +56,8 @@ sealed interface MapAction {
|
|||||||
data class ToggleGridMode(val value: Boolean) : MapAction
|
data class ToggleGridMode(val value: Boolean) : MapAction
|
||||||
data class ToggleFirstCallLabels(val value: Boolean) : MapAction
|
data class ToggleFirstCallLabels(val value: Boolean) : MapAction
|
||||||
data class SetMarkedStation(val grid: String, val call: String) : MapAction
|
data class SetMarkedStation(val grid: String, val call: String) : MapAction
|
||||||
data class RemoveMarkedStation(val grid: String) : MapAction
|
data class RemoveMarkedStation(val grid: String, val call: String) : MapAction
|
||||||
|
data class PinMarkedStation(val grid: String, val call: String) : MapAction
|
||||||
data class SetVisible(val isVisible: Boolean) : MapAction
|
data class SetVisible(val isVisible: Boolean) : MapAction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -121,19 +121,37 @@ class MapViewModel(
|
|||||||
is MapAction.ToggleGridMode -> settingsRepo.updateOtherSettings { it.copy(stateOfMapGrid = action.value) }
|
is MapAction.ToggleGridMode -> settingsRepo.updateOtherSettings { it.copy(stateOfMapGrid = action.value) }
|
||||||
is MapAction.ToggleFirstCallLabels -> settingsRepo.updateOtherSettings { it.copy(stateOfMapFirstCall = action.value) }
|
is MapAction.ToggleFirstCallLabels -> settingsRepo.updateOtherSettings { it.copy(stateOfMapFirstCall = action.value) }
|
||||||
is MapAction.SetMarkedStation -> {
|
is MapAction.SetMarkedStation -> {
|
||||||
val updated = settingsRepo.getMarkedGridStations() +
|
val call = action.call.trim().uppercase()
|
||||||
(action.grid to com.rtbishop.look4sat.core.domain.model.MarkedStation(
|
val existing = settingsRepo.getMarkedGridStations()
|
||||||
call = action.call.trim().uppercase(),
|
val current = existing[action.grid].orEmpty()
|
||||||
epochMs = System.currentTimeMillis()
|
// 重复呼号忽略: 同一网格已标记过的呼号不再重复添加.
|
||||||
))
|
val updated = if (current.any { it.call == call }) existing
|
||||||
|
else existing + (action.grid to current + com.rtbishop.look4sat.core.domain.model.MarkedStation(
|
||||||
|
call = call,
|
||||||
|
epochMs = System.currentTimeMillis()
|
||||||
|
))
|
||||||
settingsRepo.setMarkedGridStations(updated)
|
settingsRepo.setMarkedGridStations(updated)
|
||||||
_uiState.update { it.copy(markedGrids = updated) }
|
_uiState.update { it.copy(markedGrids = updated) }
|
||||||
}
|
}
|
||||||
is MapAction.RemoveMarkedStation -> {
|
is MapAction.RemoveMarkedStation -> {
|
||||||
val updated = settingsRepo.getMarkedGridStations() - action.grid
|
val existing = settingsRepo.getMarkedGridStations()
|
||||||
|
val current = existing[action.grid].orEmpty()
|
||||||
|
val remaining = current.filterNot { it.call == action.call }
|
||||||
|
val updated = if (remaining.isEmpty()) existing - action.grid
|
||||||
|
else existing + (action.grid to remaining)
|
||||||
settingsRepo.setMarkedGridStations(updated)
|
settingsRepo.setMarkedGridStations(updated)
|
||||||
_uiState.update { it.copy(markedGrids = updated) }
|
_uiState.update { it.copy(markedGrids = updated) }
|
||||||
}
|
}
|
||||||
|
is MapAction.PinMarkedStation -> {
|
||||||
|
val existing = settingsRepo.getMarkedGridStations()
|
||||||
|
val current = existing[action.grid].orEmpty()
|
||||||
|
val target = current.firstOrNull { it.call == action.call }
|
||||||
|
if (target != null) {
|
||||||
|
val updated = existing + (action.grid to listOf(target) + current.filterNot { it.call == action.call })
|
||||||
|
settingsRepo.setMarkedGridStations(updated)
|
||||||
|
_uiState.update { it.copy(markedGrids = updated) }
|
||||||
|
}
|
||||||
|
}
|
||||||
is MapAction.SetVisible -> isScreenVisible.value = action.isVisible
|
is MapAction.SetVisible -> isScreenVisible.value = action.isVisible
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,8 +115,8 @@ class FakeSettingsRepo(initialPosition: GeoPos = GeoPos(23.13, 113.26)) : ISetti
|
|||||||
override fun setWorkedGridQsos(qsos: Map<String, List<GridQso>>) = TODO()
|
override fun setWorkedGridQsos(qsos: Map<String, List<GridQso>>) = TODO()
|
||||||
override fun getRoamedGrids(): Set<String> = TODO()
|
override fun getRoamedGrids(): Set<String> = TODO()
|
||||||
override fun setRoamedGrids(grids: Set<String>) = TODO()
|
override fun setRoamedGrids(grids: Set<String>) = TODO()
|
||||||
override fun getMarkedGridStations(): Map<String, com.rtbishop.look4sat.core.domain.model.MarkedStation> = emptyMap()
|
override fun getMarkedGridStations(): Map<String, List<com.rtbishop.look4sat.core.domain.model.MarkedStation>> = emptyMap()
|
||||||
override fun setMarkedGridStations(stations: Map<String, com.rtbishop.look4sat.core.domain.model.MarkedStation>) = Unit
|
override fun setMarkedGridStations(stations: Map<String, List<com.rtbishop.look4sat.core.domain.model.MarkedStation>>) = Unit
|
||||||
override fun updateLoTWSettings(settings: LoTWSettings) = TODO()
|
override fun updateLoTWSettings(settings: LoTWSettings) = TODO()
|
||||||
override fun getLastLotwSyncDate(): String = ""
|
override fun getLastLotwSyncDate(): String = ""
|
||||||
override fun setLastLotwSyncDate(date: String) = Unit
|
override fun setLastLotwSyncDate(date: String) = Unit
|
||||||
|
|||||||
Reference in new issue
Block a user