diff --git a/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/GridTargetChip.kt b/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/GridTargetChip.kt index 8cd54abb..de56f9ff 100644 --- a/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/GridTargetChip.kt +++ b/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/GridTargetChip.kt @@ -53,7 +53,8 @@ fun GridTargetChip( modifier: Modifier = Modifier ) { val configured = grid != null && distanceKm != null && bearingDeg != null - val accent = MaterialTheme.colorScheme.tertiaryContainer + // The app's signature accent yellow (MaterialTheme primary in both themes). + val accent = MaterialTheme.colorScheme.primary val placeholder = MaterialTheme.colorScheme.onSurfaceVariant Row( verticalAlignment = Alignment.CenterVertically, @@ -83,13 +84,8 @@ fun GridTargetChip( Text( text = distanceKm?.let { "${"%,.0f".format(it)} km" } ?: "-- km", fontSize = 14.sp, - color = if (configured) Color.White else placeholder, - modifier = Modifier - .background( - color = if (configured) MaterialTheme.colorScheme.onSurfaceVariant else Color.Transparent, - shape = RoundedCornerShape(8.dp) - ) - .padding(horizontal = 8.dp, vertical = 2.dp) + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp) ) } } 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 9cf7eddf..6b8bc123 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 @@ -94,6 +94,10 @@ class MaidenheadGridOverlay : Overlay() { val firstCol = floor(leftLon / cellLon).toInt() val lastCol = ceil(rightLon / cellLon).toInt() val centerLon = (leftLon + rightLon) / 2.0 + // World width in screen pixels for the current zoom (256 px per tile, + // 2^zoom tiles across the whole 360° world). Used by the custom + // Mercator-X computation in projectionToX. + val worldWidthPx = 256.0 * Math.pow(2.0, zoom) // The station's own grid square (4-char, only meaningful at sub-square zoom) val ownCell = ownGrid?.takeIf { zoom >= GRID_ZOOM_SUB } @@ -110,8 +114,8 @@ class MaidenheadGridOverlay : Overlay() { if (yTop == null || yBottom == null) continue for (col in firstCol..lastCol) { val lon = col * cellLon - val xLeft = projectionToX(projection, lon, centerLon) ?: continue - val xRight = projectionToX(projection, lon + cellLon, centerLon) ?: continue + val xLeft = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue + val xRight = projectionToX(projection, lon + cellLon, centerLon, worldWidthPx) ?: continue if (xRight < 0f || xLeft > canvas.width) continue if (cellLabel(lat, lon, zoom) in workedGrids) { canvas.drawRect(xLeft, yTop, xRight, yBottom, workedPaint) @@ -123,7 +127,7 @@ class MaidenheadGridOverlay : Overlay() { // Vertical lines (meridians) for (col in firstCol..lastCol) { val lon = col * cellLon - val x = projectionToX(projection, lon, centerLon) + val x = projectionToX(projection, lon, centerLon, worldWidthPx) if (x == null) continue canvas.drawLine(x, 0f, x, canvas.height.toFloat(), linePaint) } @@ -147,8 +151,8 @@ class MaidenheadGridOverlay : Overlay() { 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 + val xLeft = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue + val xRight = projectionToX(projection, lon + cellLon, centerLon, worldWidthPx) ?: continue canvas.drawLine(xLeft, yTop, xRight, yTop, ownLinePaint) canvas.drawLine(xLeft, yBottom, xRight, yBottom, ownLinePaint) canvas.drawLine(xLeft, yTop, xLeft, yBottom, ownLinePaint) @@ -187,8 +191,8 @@ class MaidenheadGridOverlay : Overlay() { if (yBottom < 0f || yTop > canvas.height) continue for (col in firstCol..lastCol) { val lon = col * cellLon - val xLeft = projectionToX(projection, lon, centerLon) ?: continue - val xRight = projectionToX(projection, lon + cellLon, centerLon) ?: continue + val xLeft = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue + val xRight = projectionToX(projection, lon + cellLon, centerLon, worldWidthPx) ?: continue if (xRight < 0f || xLeft > canvas.width) continue val label = cellLabel(lat, lon, zoom) canvas.drawText(label, (xLeft + xRight) / 2f, yCenter, labelPaint) @@ -197,22 +201,18 @@ class MaidenheadGridOverlay : Overlay() { } /** X pixel for a longitude (meridians are vertical in Web Mercator). */ - private fun projectionToX(projection: Projection, lon: Double, centerLon: Double): Float? { - // osmdroid clips Mercator X to [0, mapSize], which destroys the projection of - // longitudes outside the [0, 360) window of the current view (visible at low - // zoom where the whole world is narrower than the viewport). Normalizing the - // longitude to the equivalent value closest to the view center keeps every - // meridian's X near the center, safely inside the clip window. - var normalized = lon - while (normalized < centerLon - 180.0) normalized += 360.0 - while (normalized > centerLon + 180.0) normalized -= 360.0 - val geo = org.osmdroid.util.GeoPoint(0.0, normalized) + private fun projectionToX(projection: Projection, lon: Double, centerLon: Double, worldWidthPx: Double): Float? { + // Do NOT use osmdroid's toPixels() here: at low zoom its wrap-around + // logic (getCloserPixel) mis-projects longitudes far from the view + // center, which makes meridians vanish while panning. Mercator X is + // linear in longitude, so compute it directly: + // x = screenCenterX + (lon - centerLon) / 360 * worldWidthPx + var delta = lon - centerLon + while (delta > 180.0) delta -= 360.0 + while (delta < -180.0) delta += 360.0 + val geo = org.osmdroid.util.GeoPoint(0.0, centerLon) val p = projection.toPixels(geo, 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 + return (p.x + delta / 360.0 * worldWidthPx).toFloat() } /** Y pixel for a latitude, or null when outside the viewport. */ diff --git a/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualScreen.kt b/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualScreen.kt index c42e923c..698a17e0 100644 --- a/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualScreen.kt +++ b/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualScreen.kt @@ -89,7 +89,8 @@ fun MutualScreen( Text( text = stringResource(R.string.mutual_title), style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(start = 12.dp) ) }, bottomInfo = { @@ -99,7 +100,9 @@ fun MutualScreen( color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f) + modifier = Modifier + .weight(1f) + .padding(start = 12.dp) ) }, endAction = { diff --git a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsScreen.kt b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsScreen.kt index 7f3be694..a4b2e205 100644 --- a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsScreen.kt +++ b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsScreen.kt @@ -203,18 +203,6 @@ private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) -> onSave = { onAction(SettingsAction.UpdateRadioControl(it)) } ) } - if (dialogs.wavelog) { - WavelogDialog( - initialSettings = uiState.wavelogSettings, - workedGridsCount = uiState.workedGridsCount, - isSyncing = uiState.wavelogSyncing, - message = uiState.wavelogMessage, - dismiss = { dialogs.wavelog = false }, - onSave = { onAction(SettingsAction.UpdateWavelog(it)) }, - onSync = { onAction(SettingsAction.SyncWorkedGrids(it)) } - ) - } - if (dialogs.lotw) { LoTWDialog( initialSettings = uiState.lotwSettings, @@ -330,13 +318,6 @@ private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) -> onRadioControlClick = { dialogs.radioControl = true } ) } - item { - WavelogCard( - settings = uiState.wavelogSettings, - workedGridsCount = uiState.workedGridsCount, - showWavelogDialog = { dialogs.wavelog = true } - ) - } item { LoTWCard( settings = uiState.lotwSettings, @@ -581,44 +562,6 @@ private fun SwitchRow(labelResId: Int, checked: Boolean, onCheckedChange: (Boole } } -@Preview(showBackground = true) -@Composable -private fun WavelogCardPreview() = MainTheme { - WavelogCard(settings = WavelogSettings("http://192.168.1.10", "wl2_demo"), workedGridsCount = 42) {} -} - -@Composable -private fun WavelogCard( - settings: WavelogSettings, - workedGridsCount: Int, - showWavelogDialog: () -> Unit -) { - ElevatedCard(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)) { - Text( - text = stringResource(id = R.string.prefs_wavelog_title), - color = MaterialTheme.colorScheme.primary - ) - Spacer(modifier = Modifier.height(2.dp)) - Text( - text = if (settings.isConfigured) { - stringResource(R.string.prefs_wavelog_configured, workedGridsCount) - } else { - stringResource(R.string.prefs_wavelog_not_configured) - }, - style = MaterialTheme.typography.bodySmall, - maxLines = 2 - ) - Spacer(modifier = Modifier.height(4.dp)) - CardButton( - onClick = showWavelogDialog, - text = stringResource(id = R.string.prefs_wavelog_configure), - modifier = Modifier.fillMaxWidth() - ) - } - } -} - @Composable private fun LoTWCard( settings: com.rtbishop.look4sat.core.domain.model.LoTWSettings, diff --git a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsViewModel.kt b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsViewModel.kt index 619172c7..17c7cac8 100644 --- a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsViewModel.kt +++ b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsViewModel.kt @@ -206,10 +206,10 @@ class SettingsViewModel( if (lotwGrids == null) { state.copy(lotwSyncing = false, lotwMessage = "LoTW sync failed — check callsign/password/network") } else { - // Merge with Wavelog grids: union of both sources, all confirmed/worked - val merged = settingsRepo.getWorkedGrids() + lotwGrids - settingsRepo.setWorkedGrids(merged) - state.copy(lotwSyncing = false, workedGridsCount = merged.size, lotwMessage = null) + // Wavelog entry removed: LoTW is now the only source, so the + // synced set fully replaces the stored worked grids. + settingsRepo.setWorkedGrids(lotwGrids) + state.copy(lotwSyncing = false, workedGridsCount = lotwGrids.size, lotwMessage = null) } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c63e73e0..3169e50d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] #noinspection UnusedVersionCatalogEntry -appVersionCode = "457" +appVersionCode = "458" #noinspection UnusedVersionCatalogEntry -appVersionName = "4.4.6-ba7opf.9.5" +appVersionName = "4.4.6-ba7opf.9.6" #noinspection UnusedVersionCatalogEntry compileSdk = "37" #noinspection UnusedVersionCatalogEntry