mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
feat: mutual chip uses theme primary yellow, plain text distance; top bar text indent; low-zoom meridians via direct Mercator math (bypass osmdroid wrap bug); remove Wavelog sync entry — LoTW is the sole grid source (replace instead of merge)
This commit is contained in:
1 parent
a9fe0b74e9
commit
c09f718aae
6 files changed
+37
-95
No files matched your search
+4
-8
@@ -53,7 +53,8 @@ fun GridTargetChip(
|
|||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val configured = grid != null && distanceKm != null && bearingDeg != null
|
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
|
val placeholder = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@@ -83,13 +84,8 @@ fun GridTargetChip(
|
|||||||
Text(
|
Text(
|
||||||
text = distanceKm?.let { "${"%,.0f".format(it)} km" } ?: "-- km",
|
text = distanceKm?.let { "${"%,.0f".format(it)} km" } ?: "-- km",
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color = if (configured) Color.White else placeholder,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp)
|
||||||
.background(
|
|
||||||
color = if (configured) MaterialTheme.colorScheme.onSurfaceVariant else Color.Transparent,
|
|
||||||
shape = RoundedCornerShape(8.dp)
|
|
||||||
)
|
|
||||||
.padding(horizontal = 8.dp, vertical = 2.dp)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-22
@@ -94,6 +94,10 @@ class MaidenheadGridOverlay : Overlay() {
|
|||||||
val firstCol = floor(leftLon / cellLon).toInt()
|
val firstCol = floor(leftLon / cellLon).toInt()
|
||||||
val lastCol = ceil(rightLon / cellLon).toInt()
|
val lastCol = ceil(rightLon / cellLon).toInt()
|
||||||
val centerLon = (leftLon + rightLon) / 2.0
|
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)
|
// The station's own grid square (4-char, only meaningful at sub-square zoom)
|
||||||
val ownCell = ownGrid?.takeIf { zoom >= GRID_ZOOM_SUB }
|
val ownCell = ownGrid?.takeIf { zoom >= GRID_ZOOM_SUB }
|
||||||
@@ -110,8 +114,8 @@ class MaidenheadGridOverlay : Overlay() {
|
|||||||
if (yTop == null || yBottom == null) continue
|
if (yTop == null || yBottom == null) continue
|
||||||
for (col in firstCol..lastCol) {
|
for (col in firstCol..lastCol) {
|
||||||
val lon = col * cellLon
|
val lon = col * cellLon
|
||||||
val xLeft = projectionToX(projection, lon, centerLon) ?: continue
|
val xLeft = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue
|
||||||
val xRight = projectionToX(projection, lon + cellLon, centerLon) ?: continue
|
val xRight = projectionToX(projection, lon + cellLon, centerLon, worldWidthPx) ?: continue
|
||||||
if (xRight < 0f || xLeft > canvas.width) continue
|
if (xRight < 0f || xLeft > canvas.width) continue
|
||||||
if (cellLabel(lat, lon, zoom) in workedGrids) {
|
if (cellLabel(lat, lon, zoom) in workedGrids) {
|
||||||
canvas.drawRect(xLeft, yTop, xRight, yBottom, workedPaint)
|
canvas.drawRect(xLeft, yTop, xRight, yBottom, workedPaint)
|
||||||
@@ -123,7 +127,7 @@ class MaidenheadGridOverlay : Overlay() {
|
|||||||
// Vertical lines (meridians)
|
// Vertical lines (meridians)
|
||||||
for (col in firstCol..lastCol) {
|
for (col in firstCol..lastCol) {
|
||||||
val lon = col * cellLon
|
val lon = col * cellLon
|
||||||
val x = projectionToX(projection, lon, centerLon)
|
val x = projectionToX(projection, lon, centerLon, worldWidthPx)
|
||||||
if (x == null) continue
|
if (x == null) continue
|
||||||
canvas.drawLine(x, 0f, x, canvas.height.toFloat(), linePaint)
|
canvas.drawLine(x, 0f, x, canvas.height.toFloat(), linePaint)
|
||||||
}
|
}
|
||||||
@@ -147,8 +151,8 @@ class MaidenheadGridOverlay : Overlay() {
|
|||||||
if (cellLabel(lat, lon, zoom) != ownCell) continue
|
if (cellLabel(lat, lon, zoom) != ownCell) continue
|
||||||
val yTop = projectionToY(projection, lat + cellLat) ?: continue
|
val yTop = projectionToY(projection, lat + cellLat) ?: continue
|
||||||
val yBottom = projectionToY(projection, lat) ?: continue
|
val yBottom = projectionToY(projection, lat) ?: continue
|
||||||
val xLeft = projectionToX(projection, lon, centerLon) ?: continue
|
val xLeft = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue
|
||||||
val xRight = projectionToX(projection, lon + cellLon, centerLon) ?: continue
|
val xRight = projectionToX(projection, lon + cellLon, centerLon, worldWidthPx) ?: continue
|
||||||
canvas.drawLine(xLeft, yTop, xRight, yTop, ownLinePaint)
|
canvas.drawLine(xLeft, yTop, xRight, yTop, ownLinePaint)
|
||||||
canvas.drawLine(xLeft, yBottom, xRight, yBottom, ownLinePaint)
|
canvas.drawLine(xLeft, yBottom, xRight, yBottom, ownLinePaint)
|
||||||
canvas.drawLine(xLeft, yTop, xLeft, yBottom, ownLinePaint)
|
canvas.drawLine(xLeft, yTop, xLeft, yBottom, ownLinePaint)
|
||||||
@@ -187,8 +191,8 @@ class MaidenheadGridOverlay : Overlay() {
|
|||||||
if (yBottom < 0f || yTop > canvas.height) continue
|
if (yBottom < 0f || yTop > canvas.height) continue
|
||||||
for (col in firstCol..lastCol) {
|
for (col in firstCol..lastCol) {
|
||||||
val lon = col * cellLon
|
val lon = col * cellLon
|
||||||
val xLeft = projectionToX(projection, lon, centerLon) ?: continue
|
val xLeft = projectionToX(projection, lon, centerLon, worldWidthPx) ?: continue
|
||||||
val xRight = projectionToX(projection, lon + cellLon, centerLon) ?: continue
|
val xRight = projectionToX(projection, lon + cellLon, centerLon, worldWidthPx) ?: continue
|
||||||
if (xRight < 0f || xLeft > canvas.width) continue
|
if (xRight < 0f || xLeft > canvas.width) continue
|
||||||
val label = cellLabel(lat, lon, zoom)
|
val label = cellLabel(lat, lon, zoom)
|
||||||
canvas.drawText(label, (xLeft + xRight) / 2f, yCenter, labelPaint)
|
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). */
|
/** X pixel for a longitude (meridians are vertical in Web Mercator). */
|
||||||
private fun projectionToX(projection: Projection, lon: Double, centerLon: Double): Float? {
|
private fun projectionToX(projection: Projection, lon: Double, centerLon: Double, worldWidthPx: Double): Float? {
|
||||||
// osmdroid clips Mercator X to [0, mapSize], which destroys the projection of
|
// Do NOT use osmdroid's toPixels() here: at low zoom its wrap-around
|
||||||
// longitudes outside the [0, 360) window of the current view (visible at low
|
// logic (getCloserPixel) mis-projects longitudes far from the view
|
||||||
// zoom where the whole world is narrower than the viewport). Normalizing the
|
// center, which makes meridians vanish while panning. Mercator X is
|
||||||
// longitude to the equivalent value closest to the view center keeps every
|
// linear in longitude, so compute it directly:
|
||||||
// meridian's X near the center, safely inside the clip window.
|
// x = screenCenterX + (lon - centerLon) / 360 * worldWidthPx
|
||||||
var normalized = lon
|
var delta = lon - centerLon
|
||||||
while (normalized < centerLon - 180.0) normalized += 360.0
|
while (delta > 180.0) delta -= 360.0
|
||||||
while (normalized > centerLon + 180.0) normalized -= 360.0
|
while (delta < -180.0) delta += 360.0
|
||||||
val geo = org.osmdroid.util.GeoPoint(0.0, normalized)
|
val geo = org.osmdroid.util.GeoPoint(0.0, centerLon)
|
||||||
val p = projection.toPixels(geo, null)
|
val p = projection.toPixels(geo, null)
|
||||||
// Accept generous horizontal overshoot: at low zoom the world repeats and
|
return (p.x + delta / 360.0 * worldWidthPx).toFloat()
|
||||||
// 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. */
|
/** Y pixel for a latitude, or null when outside the viewport. */
|
||||||
|
|||||||
@@ -89,7 +89,8 @@ fun MutualScreen(
|
|||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.mutual_title),
|
text = stringResource(R.string.mutual_title),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(start = 12.dp)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottomInfo = {
|
bottomInfo = {
|
||||||
@@ -99,7 +100,9 @@ fun MutualScreen(
|
|||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(start = 12.dp)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
endAction = {
|
endAction = {
|
||||||
|
|||||||
-57
@@ -203,18 +203,6 @@ private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) ->
|
|||||||
onSave = { onAction(SettingsAction.UpdateRadioControl(it)) }
|
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) {
|
if (dialogs.lotw) {
|
||||||
LoTWDialog(
|
LoTWDialog(
|
||||||
initialSettings = uiState.lotwSettings,
|
initialSettings = uiState.lotwSettings,
|
||||||
@@ -330,13 +318,6 @@ private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) ->
|
|||||||
onRadioControlClick = { dialogs.radioControl = true }
|
onRadioControlClick = { dialogs.radioControl = true }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
|
||||||
WavelogCard(
|
|
||||||
settings = uiState.wavelogSettings,
|
|
||||||
workedGridsCount = uiState.workedGridsCount,
|
|
||||||
showWavelogDialog = { dialogs.wavelog = true }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
item {
|
||||||
LoTWCard(
|
LoTWCard(
|
||||||
settings = uiState.lotwSettings,
|
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
|
@Composable
|
||||||
private fun LoTWCard(
|
private fun LoTWCard(
|
||||||
settings: com.rtbishop.look4sat.core.domain.model.LoTWSettings,
|
settings: com.rtbishop.look4sat.core.domain.model.LoTWSettings,
|
||||||
|
|||||||
+4
-4
@@ -206,10 +206,10 @@ class SettingsViewModel(
|
|||||||
if (lotwGrids == null) {
|
if (lotwGrids == null) {
|
||||||
state.copy(lotwSyncing = false, lotwMessage = "LoTW sync failed — check callsign/password/network")
|
state.copy(lotwSyncing = false, lotwMessage = "LoTW sync failed — check callsign/password/network")
|
||||||
} else {
|
} else {
|
||||||
// Merge with Wavelog grids: union of both sources, all confirmed/worked
|
// Wavelog entry removed: LoTW is now the only source, so the
|
||||||
val merged = settingsRepo.getWorkedGrids() + lotwGrids
|
// synced set fully replaces the stored worked grids.
|
||||||
settingsRepo.setWorkedGrids(merged)
|
settingsRepo.setWorkedGrids(lotwGrids)
|
||||||
state.copy(lotwSyncing = false, workedGridsCount = merged.size, lotwMessage = null)
|
state.copy(lotwSyncing = false, workedGridsCount = lotwGrids.size, lotwMessage = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
[versions]
|
[versions]
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
appVersionCode = "457"
|
appVersionCode = "458"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
appVersionName = "4.4.6-ba7opf.9.5"
|
appVersionName = "4.4.6-ba7opf.9.6"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
compileSdk = "37"
|
compileSdk = "37"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
|
|||||||
Reference in new issue
Block a user