diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/UpdateRepository.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/UpdateRepository.kt
index 18e6dfb1..bb0d969b 100644
--- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/UpdateRepository.kt
+++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/UpdateRepository.kt
@@ -58,6 +58,14 @@ class UpdateRepository(
val title = Regex("
(.*?)", RegexOption.DOT_MATCHES_ALL)
.find(html)?.groupValues?.get(1)?.trim()
?.substringBefore("ยท")?.removePrefix("Release")?.trim() ?: tag
+ // Release notes live in the page's markdown-body section (first occurrence
+ // is the release description). Strip HTML tags for plain-text display.
+ val raw = Regex("]*class=\"[^\"]*markdown-body[^\"]*\"[^>]*>(.*?)
", RegexOption.DOT_MATCHES_ALL)
+ .find(html)?.groupValues?.get(1) ?: ""
+ val body = raw.replace(Regex("<[^>]+>"), "")
+ .replace("<", "<").replace(">", ">").replace("&", "&")
+ .replace(""", "\"").replace("'", "'")
+ .trim()
// The release APK asset follows the fixed naming scheme used by the build:
// Look4Sat--release.apk. When the page was fetched
// through an accelerator mirror (https:///https://github.com/...),
@@ -72,7 +80,7 @@ class UpdateRepository(
return LatestRelease(
versionTag = tag,
title = title,
- body = "",
+ body = body,
apkUrl = apkUrl
)
}
diff --git a/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MaidenheadGridOverlay.kt b/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MaidenheadGridOverlay.kt
index d0616d61..9cf7eddf 100644
--- a/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MaidenheadGridOverlay.kt
+++ b/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MaidenheadGridOverlay.kt
@@ -56,6 +56,11 @@ class MaidenheadGridOverlay : Overlay() {
style = android.graphics.Paint.Style.FILL
color = Color.argb(90, 76, 217, 100)
}
+ private val ownLinePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ strokeWidth = 4.5f
+ style = Paint.Style.STROKE
+ color = Color.argb(255, 90, 200, 255)
+ }
/** Worked gridsquares (4-char, uppercase) to highlight, e.g. {"OL62", "PM95"}. */
var workedGrids: Set = emptySet()
@@ -63,10 +68,14 @@ class MaidenheadGridOverlay : Overlay() {
/** The station's own 4-char gridsquare, drawn with a distinct outline. */
var ownGrid: String? = null
+ /** Viewport width, refreshed each draw; used by projectionToX bounds. */
+ private var canvasWidthPx = 1080f
+
override fun draw(canvas: Canvas, mapView: MapView, shadow: Boolean) {
if (shadow || !isEnabled) return
val projection = mapView.projection
val zoom = mapView.zoomLevelDouble
+ canvasWidthPx = canvas.width.toFloat()
val cellLat = if (zoom >= GRID_ZOOM_SUB) SUB_SQUARE_LAT else FIELD_LAT
val cellLon = if (zoom >= GRID_ZOOM_SUB) SUB_SQUARE_LON else FIELD_LON
@@ -86,6 +95,9 @@ class MaidenheadGridOverlay : Overlay() {
val lastCol = ceil(rightLon / cellLon).toInt()
val centerLon = (leftLon + rightLon) / 2.0
+ // The station's own grid square (4-char, only meaningful at sub-square zoom)
+ val ownCell = ownGrid?.takeIf { zoom >= GRID_ZOOM_SUB }
+
// Worked-grid highlight fills (only meaningful at sub-square zoom)
if (workedGrids.isNotEmpty() && zoom >= GRID_ZOOM_SUB) {
for (row in firstRow..lastRow) {
@@ -124,6 +136,27 @@ class MaidenheadGridOverlay : Overlay() {
canvas.drawLine(0f, y, canvas.width.toFloat(), y, linePaint)
}
+ // The station's own grid square: redraw its four borders thicker on top.
+ if (ownCell != null) {
+ val ownColIdx = firstCol..lastCol
+ for (row in firstRow..lastRow) {
+ val lat = row * cellLat
+ if (lat < -90.0 || lat >= 90.0) continue
+ for (col in ownColIdx) {
+ val lon = col * cellLon
+ if (cellLabel(lat, lon, zoom) != ownCell) continue
+ val yTop = projectionToY(projection, lat + cellLat) ?: continue
+ val yBottom = projectionToY(projection, lat) ?: continue
+ val xLeft = projectionToX(projection, lon, centerLon) ?: continue
+ val xRight = projectionToX(projection, lon + cellLon, centerLon) ?: continue
+ canvas.drawLine(xLeft, yTop, xRight, yTop, ownLinePaint)
+ canvas.drawLine(xLeft, yBottom, xRight, yBottom, ownLinePaint)
+ canvas.drawLine(xLeft, yTop, xLeft, yBottom, ownLinePaint)
+ canvas.drawLine(xRight, yTop, xRight, yBottom, ownLinePaint)
+ }
+ }
+ }
+
// Labels: centered in each cell, only when the cell is large enough on
// screen to hold a label (avoid clutter at low zoom)
val showLabels = when {
@@ -175,7 +208,11 @@ class MaidenheadGridOverlay : Overlay() {
while (normalized > centerLon + 180.0) normalized -= 360.0
val geo = org.osmdroid.util.GeoPoint(0.0, normalized)
val p = projection.toPixels(geo, null)
- return if (p.x in -MAX_OVERSHOOT_PX..MAX_OVERSHOOT_PX + 4096) p.x.toFloat() else null
+ // Accept generous horizontal overshoot: at low zoom the world repeats and
+ // a meridian just off-screen may still be projected; rejecting too early
+ // makes lines vanish while panning. 2x viewport width is plenty.
+ val limit = canvasWidthPx * 2f + MAX_OVERSHOOT_PX
+ return if (p.x >= -limit && p.x <= limit) p.x.toFloat() else null
}
/** Y pixel for a latitude, or null when outside the viewport. */
diff --git a/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapScreen.kt b/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapScreen.kt
index 18e0328f..8d86c6ed 100644
--- a/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapScreen.kt
+++ b/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapScreen.kt
@@ -161,9 +161,9 @@ private fun MapScreen(uiState: MapState, onAction: (MapAction) -> Unit, mapView:
}
ElevatedCard(modifier = Modifier.weight(1f)) {
Box(contentAlignment = Alignment.BottomCenter) {
- // Track grid-mode transitions so the map only re-centers when the
- // mode is switched ON, not on every recomposition.
- var prevGridMode by remember { mutableStateOf(uiState.isGridMode) }
+ // Track grid-mode transitions; initial value false so that entering
+ // the map page with grid mode already ON also centers the map.
+ var prevGridMode by remember { mutableStateOf(false) }
AndroidView({ mapView }) { view ->
setGridMode(
uiState.isGridMode, uiState.workedGrids, view,
@@ -289,10 +289,12 @@ private fun setGridMode(
if (gridOverlay is MaidenheadGridOverlay) {
gridOverlay.isEnabled = gridMode
gridOverlay.workedGrids = workedGrids
+ gridOverlay.ownGrid = ownGridOf(stationPosition)
} else {
mapView.overlays[OVERLAY_GRID] = MaidenheadGridOverlay().apply {
isEnabled = gridMode
this.workedGrids = workedGrids
+ this.ownGrid = ownGridOf(stationPosition)
}
}
// Satellite-related layers are hidden in grid mode; the grid overlay
@@ -317,6 +319,18 @@ private fun setGridMode(
}
}
+/** The station's 4-char Maidenhead gridsquare, e.g. "OL62", or null without a position. */
+private fun ownGridOf(stationPosition: GeoPos?): String? {
+ val pos = stationPosition ?: return null
+ val lat = pos.latitude
+ val lon = pos.longitude
+ val fieldLat = ((lat + 90.0) / 10.0).toInt().coerceIn(0, 17)
+ val fieldLon = ((lon + 180.0) / 20.0).toInt().coerceIn(0, 17)
+ val subLat = ((lat + 90.0) % 10.0).toInt()
+ val subLon = ((lon + 180.0) % 20.0 / 2.0).toInt()
+ return "${'A' + fieldLon}${'A' + fieldLat}$subLon$subLat"
+}
+
private fun setStationPosition(stationPos: GeoPos, mapView: MapView) {
try {
val overlay = mapView.overlays[OVERLAY_STATION]
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 354ade80..e9643b29 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,8 +1,8 @@
[versions]
#noinspection UnusedVersionCatalogEntry
-appVersionCode = "455"
+appVersionCode = "456"
#noinspection UnusedVersionCatalogEntry
-appVersionName = "4.4.6-ba7opf.9.3"
+appVersionName = "4.4.6-ba7opf.9.4"
#noinspection UnusedVersionCatalogEntry
compileSdk = "37"
#noinspection UnusedVersionCatalogEntry