mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-05 20:17:29 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7d4a752ed | ||
|
|
6a3ab54226 | ||
|
|
3b3579696b | ||
|
|
826f44829b |
No files matched your search
@@ -122,10 +122,8 @@
|
|||||||
<string name="map_visibility">能见度:%s</string>
|
<string name="map_visibility">能见度:%s</string>
|
||||||
<!-- Worked-grid QSO dialog -->
|
<!-- Worked-grid QSO dialog -->
|
||||||
<string name="grid_qso_calls_count">%d 个呼号 · %d 次通联</string>
|
<string name="grid_qso_calls_count">%d 个呼号 · %d 次通联</string>
|
||||||
<plurals name="grid_qso_count">
|
<string name="grid_qso_count_one">%d QSO</string>
|
||||||
<item quantity="one">%d QSO</item>
|
<string name="grid_qso_count_many">%d QSOs</string>
|
||||||
<item quantity="other">%d QSOs</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="grid_qso_first">首通</string>
|
<string name="grid_qso_first">首通</string>
|
||||||
<string name="grid_qso_no_sat">卫星未知</string>
|
<string name="grid_qso_no_sat">卫星未知</string>
|
||||||
<string name="map_visible">日照中</string>
|
<string name="map_visible">日照中</string>
|
||||||
|
|||||||
@@ -147,10 +147,8 @@
|
|||||||
<string name="map_visibility">Visibility: %s</string>
|
<string name="map_visibility">Visibility: %s</string>
|
||||||
<!-- Worked-grid QSO dialog -->
|
<!-- Worked-grid QSO dialog -->
|
||||||
<string name="grid_qso_calls_count">%d callsigns · %d QSOs</string>
|
<string name="grid_qso_calls_count">%d callsigns · %d QSOs</string>
|
||||||
<plurals name="grid_qso_count">
|
<string name="grid_qso_count_one">%d QSO</string>
|
||||||
<item quantity="one">%d QSO</item>
|
<string name="grid_qso_count_many">%d QSOs</string>
|
||||||
<item quantity="other">%d QSOs</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="grid_qso_first">First</string>
|
<string name="grid_qso_first">First</string>
|
||||||
<string name="grid_qso_no_sat">satellite unknown</string>
|
<string name="grid_qso_no_sat">satellite unknown</string>
|
||||||
<string name="map_visible">Visible</string>
|
<string name="map_visible">Visible</string>
|
||||||
|
|||||||
+12
-1
@@ -428,7 +428,15 @@ class MaidenheadGridOverlay : Overlay() {
|
|||||||
|
|
||||||
/** Y pixel for a latitude, or null when outside the viewport. */
|
/** Y pixel for a latitude, or null when outside the viewport. */
|
||||||
private fun projectionToY(projection: Projection, lat: Double): Float? {
|
private fun projectionToY(projection: Projection, lat: Double): Float? {
|
||||||
val geo = org.osmdroid.util.GeoPoint(lat, 0.0)
|
// Web Mercator is undefined beyond ±85.0511° (osmdroid clamps the world
|
||||||
|
// to TileSystemWebMercator limits). Projecting the polar field rows
|
||||||
|
// (lat=±90: Maidenhead row R = 80..90°N, row A = -90..-80°S) yields
|
||||||
|
// ±9.2e18 pixel coordinates that the canvas cannot rasterize, so the
|
||||||
|
// polar grid lines silently vanish. Clamp to the Mercator limit: the
|
||||||
|
// polar rows then project onto the screen edge and their lines are
|
||||||
|
// drawn (the off-limit sliver beyond 85.05° is invisible anyway).
|
||||||
|
val clamped = lat.coerceIn(-MAX_MERCATOR_LAT, MAX_MERCATOR_LAT)
|
||||||
|
val geo = org.osmdroid.util.GeoPoint(clamped, 0.0)
|
||||||
val p = projection.toPixels(geo, null)
|
val p = projection.toPixels(geo, null)
|
||||||
return p.y.toFloat()
|
return p.y.toFloat()
|
||||||
}
|
}
|
||||||
@@ -484,6 +492,9 @@ class MaidenheadGridOverlay : Overlay() {
|
|||||||
const val FIELD_LON = 20.0
|
const val FIELD_LON = 20.0
|
||||||
const val SUB_SQUARE_LAT = 1.0
|
const val SUB_SQUARE_LAT = 1.0
|
||||||
const val SUB_SQUARE_LON = 2.0
|
const val SUB_SQUARE_LON = 2.0
|
||||||
|
// Web Mercator latitude limit (osmdroid TileSystemWebMercator): projecting
|
||||||
|
// beyond it produces ±9.2e18 pixel coordinates the canvas cannot draw.
|
||||||
|
const val MAX_MERCATOR_LAT = 85.05112877980658
|
||||||
// Zoom at which the 2°x1° sub-square grid lines/fills appear.
|
// Zoom at which the 2°x1° sub-square grid lines/fills appear.
|
||||||
// 5.0 → 6.0: at zoom 5 a full field spans too little screen width and
|
// 5.0 → 6.0: at zoom 5 a full field spans too little screen width and
|
||||||
// the sub-square grid is too dense to read; 6 roughly doubles the
|
// the sub-square grid is too dense to read; 6 roughly doubles the
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ import androidx.compose.ui.draw.rotate
|
|||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.graphics.Color as ComposeColor
|
import androidx.compose.ui.graphics.Color as ComposeColor
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@@ -207,8 +206,13 @@ private fun MapScreen(uiState: MapState, onAction: (MapAction) -> Unit, mapView:
|
|||||||
if (p.type == AwardType.VUCC) p.copy(workedKeys = vuccGrids, count = vuccGrids.size) else p
|
if (p.type == AwardType.VUCC) p.copy(workedKeys = vuccGrids, count = vuccGrids.size) else p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Attach the tap listener whenever grid mode / worked grids change.
|
// Worked grids drawn on the map: filtered by the selected operated grid
|
||||||
val workedGrids = uiState.workedGrids
|
// (null = all operated grids). Drives both the green fills and the tap
|
||||||
|
// listener below, so switching the selector changes which cells are green.
|
||||||
|
val workedGrids = remember(uiState.workedGrids, vuccByMyGrid, selectedMyGrid) {
|
||||||
|
if (selectedMyGrid == null) uiState.workedGrids
|
||||||
|
else vuccByMyGrid[selectedMyGrid] ?: emptySet()
|
||||||
|
}
|
||||||
val isGridMode = uiState.isGridMode
|
val isGridMode = uiState.isGridMode
|
||||||
DisposableEffect(isGridMode, workedGrids) {
|
DisposableEffect(isGridMode, workedGrids) {
|
||||||
val receiver = object : org.osmdroid.events.MapEventsReceiver {
|
val receiver = object : org.osmdroid.events.MapEventsReceiver {
|
||||||
@@ -297,7 +301,7 @@ private fun MapScreen(uiState: MapState, onAction: (MapAction) -> Unit, mapView:
|
|||||||
// stationPosition would swallow the centering forever.
|
// stationPosition would swallow the centering forever.
|
||||||
val shouldCenter = uiState.isGridMode && !prevGridMode
|
val shouldCenter = uiState.isGridMode && !prevGridMode
|
||||||
setGridMode(
|
setGridMode(
|
||||||
uiState.isGridMode, uiState.workedGrids, uiState.roamedGrids, view,
|
uiState.isGridMode, workedGrids, uiState.roamedGrids, view,
|
||||||
uiState.stationPosition,
|
uiState.stationPosition,
|
||||||
centerOnStation = shouldCenter
|
centerOnStation = shouldCenter
|
||||||
)
|
)
|
||||||
@@ -322,11 +326,12 @@ private fun MapScreen(uiState: MapState, onAction: (MapAction) -> Unit, mapView:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Top-left: operated-grid selector for VUCC counting (grid mode only).
|
// Top-left: operated-grid selector for VUCC counting (grid mode only).
|
||||||
val myGrid = selectedMyGrid
|
// Shown whenever per-grid QSO data exists; "All" (null) is available.
|
||||||
if (uiState.isGridMode && selectedAward == AwardType.VUCC && myGrid != null) {
|
if (uiState.isGridMode && selectedAward == AwardType.VUCC && vuccByMyGrid.isNotEmpty()) {
|
||||||
VuccGridSelector(
|
VuccGridSelector(
|
||||||
options = vuccByMyGrid,
|
options = vuccByMyGrid,
|
||||||
selected = myGrid,
|
allCount = uiState.workedGrids.size,
|
||||||
|
selected = selectedMyGrid,
|
||||||
onSelect = { selectedMyGrid = it },
|
onSelect = { selectedMyGrid = it },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.TopStart)
|
.align(Alignment.TopStart)
|
||||||
@@ -440,7 +445,10 @@ private fun WorkedGridCallRow(
|
|||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = pluralStringResource(R.plurals.grid_qso_count, callQsos.size, callQsos.size),
|
// Plurals resources don't work for zh (only 'other' matches), so
|
||||||
|
// pick the singular/plural string explicitly.
|
||||||
|
text = if (callQsos.size == 1) stringResource(R.string.grid_qso_count_one, 1)
|
||||||
|
else stringResource(R.string.grid_qso_count_many, callQsos.size),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
textAlign = androidx.compose.ui.text.style.TextAlign.Center,
|
textAlign = androidx.compose.ui.text.style.TextAlign.Center,
|
||||||
@@ -638,12 +646,13 @@ private fun AwardChipsRow(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun VuccGridSelector(
|
private fun VuccGridSelector(
|
||||||
options: Map<String, Set<String>>,
|
options: Map<String, Set<String>>,
|
||||||
selected: String,
|
allCount: Int,
|
||||||
onSelect: (String) -> Unit,
|
selected: String?,
|
||||||
|
onSelect: (String?) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
// Most-worked grid first; selection is made on tap.
|
// Most-worked grid first; selection is made on tap. "All" sits last.
|
||||||
val sorted = remember(options) { options.entries.sortedByDescending { it.value.size } }
|
val sorted = remember(options) { options.entries.sortedByDescending { it.value.size } }
|
||||||
Surface(
|
Surface(
|
||||||
color = ComposeColor.Black.copy(alpha = 0.45f),
|
color = ComposeColor.Black.copy(alpha = 0.45f),
|
||||||
@@ -658,7 +667,8 @@ private fun VuccGridSelector(
|
|||||||
.padding(start = 10.dp, end = 6.dp, top = 4.dp, bottom = 4.dp)
|
.padding(start = 10.dp, end = 6.dp, top = 4.dp, bottom = 4.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "$selected (${options[selected]?.size ?: 0})",
|
text = if (selected == null) "All ($allCount)"
|
||||||
|
else "$selected (${options[selected]?.size ?: 0})",
|
||||||
color = ComposeColor.White,
|
color = ComposeColor.White,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace
|
fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace
|
||||||
@@ -691,6 +701,24 @@ private fun VuccGridSelector(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// "All" always listed last: shows every worked grid combined.
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
onSelect(null)
|
||||||
|
expanded = false
|
||||||
|
}
|
||||||
|
.padding(horizontal = 10.dp, vertical = 3.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "All ($allCount)",
|
||||||
|
color = if (selected == null) MaterialTheme.colorScheme.primary else ComposeColor.White,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
[versions]
|
[versions]
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
appVersionCode = "471"
|
appVersionCode = "472"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
appVersionName = "4.4.7-ba7opf.1"
|
appVersionName = "4.4.7-ba7opf.2"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
compileSdk = "37"
|
compileSdk = "37"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
|
|||||||
Reference in new issue
Block a user