mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
fix: sync dialogs use typed values directly (save+sync in one step); LoTW card wording; center map on station grid when entering grid mode; center grid labels in cells; update-check falls back to GitHub accelerator mirrors
This commit is contained in:
1 parent
be6473e692
commit
c7ca77a943
10 files changed
+113
-43
No files matched your search
+37
-13
@@ -16,15 +16,21 @@ class UpdateRepository(
|
|||||||
// the API endpoint is rate-limited to 60 requests/hour per IP, which is
|
// the API endpoint is rate-limited to 60 requests/hour per IP, which is
|
||||||
// quickly exhausted on shared egress IPs (e.g. VPN proxies), causing 403
|
// quickly exhausted on shared egress IPs (e.g. VPN proxies), causing 403
|
||||||
// failures. The web endpoint redirects to the latest tag with no such limit.
|
// failures. The web endpoint redirects to the latest tag with no such limit.
|
||||||
val result = remoteSource.getNetworkStream(LATEST_RELEASE_URL)
|
// When GitHub is unreachable (common on mainland-China networks without a
|
||||||
val stream = result.stream ?: return@withContext null
|
// proxy), fall back to community GitHub accelerator mirrors that proxy the
|
||||||
try {
|
// same page; each mirror resolves the identical tag and asset URLs.
|
||||||
val html = stream.bufferedReader().use { it.readText() }
|
for (baseUrl in LATEST_RELEASE_URLS) {
|
||||||
parseRelease(html)
|
val result = remoteSource.getNetworkStream(baseUrl)
|
||||||
} catch (e: Exception) {
|
val stream = result.stream ?: continue
|
||||||
println("UpdateRepository parse failure: $e")
|
val parsed = try {
|
||||||
null
|
parseRelease(stream.bufferedReader().use { it.readText() }, baseUrl)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("UpdateRepository parse failure: $e")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
if (parsed != null) return@withContext parsed
|
||||||
}
|
}
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun downloadApk(url: String, dest: File): Boolean = withContext(Dispatchers.IO) {
|
override suspend fun downloadApk(url: String, dest: File): Boolean = withContext(Dispatchers.IO) {
|
||||||
@@ -40,7 +46,7 @@ class UpdateRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseRelease(html: String): LatestRelease? {
|
private fun parseRelease(html: String, sourceUrl: String): LatestRelease? {
|
||||||
// The redirect target is the latest release's tag page. Extract the tag
|
// The redirect target is the latest release's tag page. Extract the tag
|
||||||
// from the og:url meta tag ("…/releases/tag/v4.4.6-ba7opf.8") — stable and
|
// from the og:url meta tag ("…/releases/tag/v4.4.6-ba7opf.8") — stable and
|
||||||
// unambiguous. The <title> also holds the release name, but that is free
|
// unambiguous. The <title> also holds the release name, but that is free
|
||||||
@@ -53,8 +59,16 @@ class UpdateRepository(
|
|||||||
.find(html)?.groupValues?.get(1)?.trim()
|
.find(html)?.groupValues?.get(1)?.trim()
|
||||||
?.substringBefore("·")?.removePrefix("Release")?.trim() ?: tag
|
?.substringBefore("·")?.removePrefix("Release")?.trim() ?: tag
|
||||||
// The release APK asset follows the fixed naming scheme used by the build:
|
// The release APK asset follows the fixed naming scheme used by the build:
|
||||||
// Look4Sat-<tag without leading v>-release.apk
|
// Look4Sat-<tag without leading v>-release.apk. When the page was fetched
|
||||||
val apkUrl = "$DOWNLOAD_BASE_URL/$tag/Look4Sat-${tag.removePrefix("v")}-release.apk"
|
// through an accelerator mirror (https://<mirror>/https://github.com/...),
|
||||||
|
// download through the same mirror — raw github.com is unreachable on the
|
||||||
|
// networks that needed the mirror in the first place.
|
||||||
|
val mirrorPrefix = sourceUrl.substringBefore("https://github.com")
|
||||||
|
val apkUrl = if (mirrorPrefix.isEmpty()) {
|
||||||
|
"$DOWNLOAD_BASE_URL/$tag/Look4Sat-${tag.removePrefix("v")}-release.apk"
|
||||||
|
} else {
|
||||||
|
"${mirrorPrefix}https://github.com/atsunatsu/Look4Sat/releases/download/$tag/Look4Sat-${tag.removePrefix("v")}-release.apk"
|
||||||
|
}
|
||||||
return LatestRelease(
|
return LatestRelease(
|
||||||
versionTag = tag,
|
versionTag = tag,
|
||||||
title = title,
|
title = title,
|
||||||
@@ -65,8 +79,18 @@ class UpdateRepository(
|
|||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
// Web pages are used instead of api.github.com to avoid the 60 req/hour
|
// Web pages are used instead of api.github.com to avoid the 60 req/hour
|
||||||
// anonymous rate limit (see getLatestRelease above).
|
// anonymous rate limit (see getLatestRelease above). The GitHub URL is
|
||||||
const val LATEST_RELEASE_URL = "https://github.com/atsunatsu/Look4Sat/releases/latest"
|
// tried first; if it is unreachable (no proxy on mainland networks) the
|
||||||
|
// accelerator mirrors are tried in order. Download URLs always point at
|
||||||
|
// the mirrors too — raw github.com release downloads are equally blocked
|
||||||
|
// without a proxy, so a mirror-resolved tag must be downloaded via the
|
||||||
|
// same mirror.
|
||||||
|
val LATEST_RELEASE_URLS = listOf(
|
||||||
|
"https://github.com/atsunatsu/Look4Sat/releases/latest",
|
||||||
|
"https://ghfast.top/https://github.com/atsunatsu/Look4Sat/releases/latest",
|
||||||
|
"https://gh.llkk.cc/https://github.com/atsunatsu/Look4Sat/releases/latest",
|
||||||
|
"https://github.moeyy.xyz/https://github.com/atsunatsu/Look4Sat/releases/latest"
|
||||||
|
)
|
||||||
const val DOWNLOAD_BASE_URL = "https://github.com/atsunatsu/Look4Sat/releases/download"
|
const val DOWNLOAD_BASE_URL = "https://github.com/atsunatsu/Look4Sat/releases/download"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,15 +145,16 @@
|
|||||||
<string name="prefs_wavelog_not_configured">未配置 — 连接你的 Wavelog 日志以在地图上高亮已通联网格</string>
|
<string name="prefs_wavelog_not_configured">未配置 — 连接你的 Wavelog 日志以在地图上高亮已通联网格</string>
|
||||||
<string name="prefs_wavelog_url">Wavelog 地址</string>
|
<string name="prefs_wavelog_url">Wavelog 地址</string>
|
||||||
<string name="prefs_wavelog_token">API key :日志 ID</string>
|
<string name="prefs_wavelog_token">API key :日志 ID</string>
|
||||||
<string name="prefs_wavelog_hint">已同步网格数:%1$d。格式:API key:日志ID。请先保存,再点同步。</string>
|
<string name="prefs_wavelog_hint">已同步网格数:%1$d。格式:API key:日志ID。填好直接点同步,会自动保存并拉取。</string>
|
||||||
<string name="prefs_wavelog_sync">立即同步</string>
|
<string name="prefs_wavelog_sync">立即同步</string>
|
||||||
<string name="prefs_wavelog_syncing">同步中…</string>
|
<string name="prefs_wavelog_syncing">同步中…</string>
|
||||||
<string name="prefs_lotw_title">LoTW 已确认网格</string>
|
<string name="prefs_lotw_title">LoTW 已确认网格</string>
|
||||||
<string name="prefs_lotw_callsign">LoTW 呼号</string>
|
<string name="prefs_lotw_callsign">LoTW 呼号</string>
|
||||||
<string name="prefs_lotw_password">LoTW 密码</string>
|
<string name="prefs_lotw_password">LoTW 密码</string>
|
||||||
<string name="prefs_lotw_hint">直接从 ARRL LoTW 拉取已确认 QSO 的网格并与 Wavelog 网格合并(当前 %1$d 个)。密码仅保存在手机本地。请先保存,再点同步。</string>
|
<string name="prefs_lotw_hint">直接从 ARRL LoTW 拉取已确认 QSO 的网格并与 Wavelog 网格合并(当前 %1$d 个)。密码仅保存在手机本地。填好直接点同步,会自动保存并拉取。</string>
|
||||||
<string name="prefs_lotw_sync">立即同步</string>
|
<string name="prefs_lotw_sync">立即同步</string>
|
||||||
<string name="prefs_lotw_syncing">同步中…</string>
|
<string name="prefs_lotw_syncing">同步中…</string>
|
||||||
|
<string name="prefs_lotw_configured">已从 LoTW 同步 — 共 %1$d 个网格</string>
|
||||||
<string name="prefs_lotw_not_configured">未配置 — 直接登录 ARRL LoTW 拉取已确认 QSO 的网格,无需自建服务器</string>
|
<string name="prefs_lotw_not_configured">未配置 — 直接登录 ARRL LoTW 拉取已确认 QSO 的网格,无需自建服务器</string>
|
||||||
<string name="prefs_locator_text">使用梅登黑德网格设置站点位置</string>
|
<string name="prefs_locator_text">使用梅登黑德网格设置站点位置</string>
|
||||||
|
|
||||||
|
|||||||
@@ -180,15 +180,16 @@
|
|||||||
<string name="prefs_wavelog_not_configured">Not configured — connect your Wavelog logbook to highlight worked grids on the map</string>
|
<string name="prefs_wavelog_not_configured">Not configured — connect your Wavelog logbook to highlight worked grids on the map</string>
|
||||||
<string name="prefs_wavelog_url">Wavelog URL</string>
|
<string name="prefs_wavelog_url">Wavelog URL</string>
|
||||||
<string name="prefs_wavelog_token">API key : logbook ID</string>
|
<string name="prefs_wavelog_token">API key : logbook ID</string>
|
||||||
<string name="prefs_wavelog_hint">Worked grids synced: %1$d. Format: API key, colon, logbook ID. Save first, then tap Sync.</string>
|
<string name="prefs_wavelog_hint">Worked grids synced: %1$d. Format: API key, colon, logbook ID. Just tap Sync — credentials are saved automatically.</string>
|
||||||
<string name="prefs_wavelog_sync">Sync now</string>
|
<string name="prefs_wavelog_sync">Sync now</string>
|
||||||
<string name="prefs_wavelog_syncing">Syncing…</string>
|
<string name="prefs_wavelog_syncing">Syncing…</string>
|
||||||
<string name="prefs_lotw_title">LoTW confirmed grids</string>
|
<string name="prefs_lotw_title">LoTW confirmed grids</string>
|
||||||
<string name="prefs_lotw_callsign">LoTW callsign</string>
|
<string name="prefs_lotw_callsign">LoTW callsign</string>
|
||||||
<string name="prefs_lotw_password">LoTW password</string>
|
<string name="prefs_lotw_password">LoTW password</string>
|
||||||
<string name="prefs_lotw_hint">Fetch confirmed-QSO grids directly from ARRL LoTW and merge them with Wavelog grids (currently %1$d). The password is stored on this device only. Save first, then tap Sync.</string>
|
<string name="prefs_lotw_hint">Fetch confirmed-QSO grids directly from ARRL LoTW and merge them with Wavelog grids (currently %1$d). The password is stored on this device only. Just tap Sync — credentials are saved automatically.</string>
|
||||||
<string name="prefs_lotw_sync">Sync now</string>
|
<string name="prefs_lotw_sync">Sync now</string>
|
||||||
<string name="prefs_lotw_syncing">Syncing…</string>
|
<string name="prefs_lotw_syncing">Syncing…</string>
|
||||||
|
<string name="prefs_lotw_configured">Synced from LoTW — %1$d grids total</string>
|
||||||
<string name="prefs_lotw_not_configured">Not configured — pull confirmed grids directly from ARRL LoTW, no self-hosted server needed</string>
|
<string name="prefs_lotw_not_configured">Not configured — pull confirmed grids directly from ARRL LoTW, no self-hosted server needed</string>
|
||||||
<string name="prefs_locator_text">Set station\'s position using locator</string>
|
<string name="prefs_locator_text">Set station\'s position using locator</string>
|
||||||
|
|
||||||
|
|||||||
+13
-8
@@ -124,8 +124,8 @@ class MaidenheadGridOverlay : Overlay() {
|
|||||||
canvas.drawLine(0f, y, canvas.width.toFloat(), y, linePaint)
|
canvas.drawLine(0f, y, canvas.width.toFloat(), y, linePaint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Labels: place at the top-left corner of each cell, only when the cell
|
// Labels: centered in each cell, only when the cell is large enough on
|
||||||
// is large enough on screen to hold a label (avoid clutter at low zoom)
|
// screen to hold a label (avoid clutter at low zoom)
|
||||||
val showLabels = when {
|
val showLabels = when {
|
||||||
zoom >= GRID_ZOOM_SUB -> true
|
zoom >= GRID_ZOOM_SUB -> true
|
||||||
zoom >= FIELD_ZOOM_LABELS -> true
|
zoom >= FIELD_ZOOM_LABELS -> true
|
||||||
@@ -140,20 +140,25 @@ class MaidenheadGridOverlay : Overlay() {
|
|||||||
val pixelsPerDegree = Math.abs(y2 - y1)
|
val pixelsPerDegree = Math.abs(y2 - y1)
|
||||||
if (pixelsPerDegree * cellLat < MIN_LABEL_CELL_PX) return
|
if (pixelsPerDegree * cellLat < MIN_LABEL_CELL_PX) return
|
||||||
|
|
||||||
val labelOffset = 8f
|
labelPaint.textAlign = Paint.Align.CENTER
|
||||||
|
val fontMetrics = labelPaint.fontMetrics
|
||||||
|
val textHalfHeight = (fontMetrics.descent + fontMetrics.ascent) / 2f
|
||||||
for (row in firstRow..lastRow) {
|
for (row in firstRow..lastRow) {
|
||||||
val lat = row * cellLat
|
val lat = row * cellLat
|
||||||
if (lat < -90.0 || lat >= 90.0) continue
|
if (lat < -90.0 || lat >= 90.0) continue
|
||||||
val topLatCell = lat + cellLat
|
val topLatCell = lat + cellLat
|
||||||
if (topLatCell > 90.0) continue
|
if (topLatCell > 90.0) continue
|
||||||
val y = projectionToY(projection, topLatCell) ?: continue
|
val yTop = projectionToY(projection, topLatCell) ?: continue
|
||||||
if (y < -labelPaint.textSize || y > canvas.height) continue
|
val yBottom = projectionToY(projection, lat) ?: continue
|
||||||
|
val yCenter = (yTop + yBottom) / 2f - textHalfHeight
|
||||||
|
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 x = projectionToX(projection, lon, centerLon) ?: continue
|
val xLeft = projectionToX(projection, lon, centerLon) ?: continue
|
||||||
if (x < -200f || x > canvas.width) continue
|
val xRight = projectionToX(projection, lon + cellLon, centerLon) ?: continue
|
||||||
|
if (xRight < 0f || xLeft > canvas.width) continue
|
||||||
val label = cellLabel(lat, lon, zoom)
|
val label = cellLabel(lat, lon, zoom)
|
||||||
canvas.drawText(label, x + labelOffset, y + labelPaint.textSize + labelOffset, labelPaint)
|
canvas.drawText(label, (xLeft + xRight) / 2f, yCenter, labelPaint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
@@ -159,8 +161,15 @@ private fun MapScreen(uiState: MapState, onAction: (MapAction) -> Unit, mapView:
|
|||||||
}
|
}
|
||||||
ElevatedCard(modifier = Modifier.weight(1f)) {
|
ElevatedCard(modifier = Modifier.weight(1f)) {
|
||||||
Box(contentAlignment = Alignment.BottomCenter) {
|
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) }
|
||||||
AndroidView({ mapView }) { view ->
|
AndroidView({ mapView }) { view ->
|
||||||
setGridMode(uiState.isGridMode, uiState.workedGrids, view)
|
setGridMode(
|
||||||
|
uiState.isGridMode, uiState.workedGrids, view,
|
||||||
|
if (uiState.isGridMode && !prevGridMode) uiState.stationPosition else null
|
||||||
|
)
|
||||||
|
prevGridMode = uiState.isGridMode
|
||||||
if (!uiState.isGridMode) {
|
if (!uiState.isGridMode) {
|
||||||
uiState.stationPosition?.let { setStationPosition(it, view) }
|
uiState.stationPosition?.let { setStationPosition(it, view) }
|
||||||
uiState.track?.let { setSatelliteTrack(it, view) }
|
uiState.track?.let { setSatelliteTrack(it, view) }
|
||||||
@@ -269,7 +278,12 @@ private fun MapDataCards(data: MapData) {
|
|||||||
// region Map overlay helpers
|
// region Map overlay helpers
|
||||||
|
|
||||||
/** Toggle the grid-mode layer visibility on/off without recreating any overlay. */
|
/** Toggle the grid-mode layer visibility on/off without recreating any overlay. */
|
||||||
private fun setGridMode(gridMode: Boolean, workedGrids: Set<String>, mapView: MapView) {
|
private fun setGridMode(
|
||||||
|
gridMode: Boolean,
|
||||||
|
workedGrids: Set<String>,
|
||||||
|
mapView: MapView,
|
||||||
|
stationPosition: GeoPos?
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
val gridOverlay = mapView.overlays[OVERLAY_GRID]
|
val gridOverlay = mapView.overlays[OVERLAY_GRID]
|
||||||
if (gridOverlay is MaidenheadGridOverlay) {
|
if (gridOverlay is MaidenheadGridOverlay) {
|
||||||
@@ -286,6 +300,18 @@ private fun setGridMode(gridMode: Boolean, workedGrids: Set<String>, mapView: Ma
|
|||||||
for (index in OVERLAY_STATION..OVERLAY_MOON) {
|
for (index in OVERLAY_STATION..OVERLAY_MOON) {
|
||||||
mapView.overlays.getOrNull(index)?.isEnabled = !gridMode
|
mapView.overlays.getOrNull(index)?.isEnabled = !gridMode
|
||||||
}
|
}
|
||||||
|
// Entering grid mode: center on the station's current grid square,
|
||||||
|
// keeping the current zoom level unchanged.
|
||||||
|
if (gridMode) {
|
||||||
|
val pos = stationPosition ?: return
|
||||||
|
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 centerLat = fieldLat * 10.0 + 5.0
|
||||||
|
val centerLon = fieldLon * 20.0 + 10.0
|
||||||
|
mapView.controller.setCenter(GeoPoint(centerLat, centerLon))
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println(e)
|
println(e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ class MapViewModel(
|
|||||||
}
|
}
|
||||||
val (selectedCatNum, _) = satelliteRepo.selectedPass.value
|
val (selectedCatNum, _) = satelliteRepo.selectedPass.value
|
||||||
selectDefaultSatellite(if (selectedCatNum != 0) selectedCatNum else -1)
|
selectDefaultSatellite(if (selectedCatNum != 0) selectedCatNum else -1)
|
||||||
|
getStationPosition()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onAction(action: MapAction) {
|
fun onAction(action: MapAction) {
|
||||||
|
|||||||
+14
-5
@@ -164,7 +164,7 @@ fun WavelogDialog(
|
|||||||
message: String?,
|
message: String?,
|
||||||
dismiss: () -> Unit,
|
dismiss: () -> Unit,
|
||||||
onSave: (com.rtbishop.look4sat.core.domain.model.WavelogSettings) -> Unit,
|
onSave: (com.rtbishop.look4sat.core.domain.model.WavelogSettings) -> Unit,
|
||||||
onSync: () -> Unit
|
onSync: (com.rtbishop.look4sat.core.domain.model.WavelogSettings) -> Unit
|
||||||
) {
|
) {
|
||||||
val url = rememberSaveable { mutableStateOf(initialSettings.url) }
|
val url = rememberSaveable { mutableStateOf(initialSettings.url) }
|
||||||
val token = rememberSaveable { mutableStateOf(initialSettings.token) }
|
val token = rememberSaveable { mutableStateOf(initialSettings.token) }
|
||||||
@@ -205,12 +205,16 @@ fun WavelogDialog(
|
|||||||
modifier = Modifier.padding(horizontal = LocalSpacing.current.large)
|
modifier = Modifier.padding(horizontal = LocalSpacing.current.large)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// Sync button row (inside dialog content so token must be saved first)
|
// Sync uses the values typed in the fields directly — saving and syncing
|
||||||
|
// happen in one step, no need to close and reopen the dialog.
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.End,
|
horizontalArrangement = Arrangement.End,
|
||||||
modifier = Modifier.fillMaxWidth().padding(horizontal = LocalSpacing.current.large)
|
modifier = Modifier.fillMaxWidth().padding(horizontal = LocalSpacing.current.large)
|
||||||
) {
|
) {
|
||||||
TextButton(onClick = onSync, enabled = !isSyncing) {
|
TextButton(
|
||||||
|
onClick = { onSync(com.rtbishop.look4sat.core.domain.model.WavelogSettings(url.value, token.value)) },
|
||||||
|
enabled = !isSyncing
|
||||||
|
) {
|
||||||
Text(text = if (isSyncing) stringResource(R.string.prefs_wavelog_syncing)
|
Text(text = if (isSyncing) stringResource(R.string.prefs_wavelog_syncing)
|
||||||
else stringResource(R.string.prefs_wavelog_sync))
|
else stringResource(R.string.prefs_wavelog_sync))
|
||||||
}
|
}
|
||||||
@@ -227,7 +231,7 @@ fun LoTWDialog(
|
|||||||
message: String?,
|
message: String?,
|
||||||
dismiss: () -> Unit,
|
dismiss: () -> Unit,
|
||||||
onSave: (com.rtbishop.look4sat.core.domain.model.LoTWSettings) -> Unit,
|
onSave: (com.rtbishop.look4sat.core.domain.model.LoTWSettings) -> Unit,
|
||||||
onSync: () -> Unit
|
onSync: (com.rtbishop.look4sat.core.domain.model.LoTWSettings) -> Unit
|
||||||
) {
|
) {
|
||||||
val call = rememberSaveable { mutableStateOf(initialSettings.callsign) }
|
val call = rememberSaveable { mutableStateOf(initialSettings.callsign) }
|
||||||
val pass = rememberSaveable { mutableStateOf(initialSettings.password) }
|
val pass = rememberSaveable { mutableStateOf(initialSettings.password) }
|
||||||
@@ -269,11 +273,16 @@ fun LoTWDialog(
|
|||||||
modifier = Modifier.padding(horizontal = LocalSpacing.current.large)
|
modifier = Modifier.padding(horizontal = LocalSpacing.current.large)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// Sync uses the values typed in the fields directly — saving and syncing
|
||||||
|
// happen in one step, no need to close and reopen the dialog.
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.End,
|
horizontalArrangement = Arrangement.End,
|
||||||
modifier = Modifier.fillMaxWidth().padding(horizontal = LocalSpacing.current.large)
|
modifier = Modifier.fillMaxWidth().padding(horizontal = LocalSpacing.current.large)
|
||||||
) {
|
) {
|
||||||
TextButton(onClick = onSync, enabled = !isSyncing) {
|
TextButton(
|
||||||
|
onClick = { onSync(com.rtbishop.look4sat.core.domain.model.LoTWSettings(call.value, pass.value)) },
|
||||||
|
enabled = !isSyncing
|
||||||
|
) {
|
||||||
Text(text = if (isSyncing) stringResource(R.string.prefs_lotw_syncing)
|
Text(text = if (isSyncing) stringResource(R.string.prefs_lotw_syncing)
|
||||||
else stringResource(R.string.prefs_lotw_sync))
|
else stringResource(R.string.prefs_lotw_sync))
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -211,7 +211,7 @@ private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) ->
|
|||||||
message = uiState.wavelogMessage,
|
message = uiState.wavelogMessage,
|
||||||
dismiss = { dialogs.wavelog = false },
|
dismiss = { dialogs.wavelog = false },
|
||||||
onSave = { onAction(SettingsAction.UpdateWavelog(it)) },
|
onSave = { onAction(SettingsAction.UpdateWavelog(it)) },
|
||||||
onSync = { onAction(SettingsAction.SyncWorkedGrids) }
|
onSync = { onAction(SettingsAction.SyncWorkedGrids(it)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +223,7 @@ private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) ->
|
|||||||
message = uiState.lotwMessage,
|
message = uiState.lotwMessage,
|
||||||
dismiss = { dialogs.lotw = false },
|
dismiss = { dialogs.lotw = false },
|
||||||
onSave = { onAction(SettingsAction.UpdateLoTW(it)) },
|
onSave = { onAction(SettingsAction.UpdateLoTW(it)) },
|
||||||
onSync = { onAction(SettingsAction.SyncLoTWGrids) }
|
onSync = { onAction(SettingsAction.SyncLoTWGrids(it)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,7 +634,7 @@ private fun LoTWCard(
|
|||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
Text(
|
Text(
|
||||||
text = if (settings.isConfigured) {
|
text = if (settings.isConfigured) {
|
||||||
stringResource(R.string.prefs_wavelog_configured, workedGridsCount)
|
stringResource(R.string.prefs_lotw_configured, workedGridsCount)
|
||||||
} else {
|
} else {
|
||||||
stringResource(R.string.prefs_lotw_not_configured)
|
stringResource(R.string.prefs_lotw_not_configured)
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-2
@@ -96,11 +96,11 @@ sealed interface SettingsAction {
|
|||||||
|
|
||||||
// Wavelog worked grids
|
// Wavelog worked grids
|
||||||
data class UpdateWavelog(val settings: WavelogSettings) : SettingsAction
|
data class UpdateWavelog(val settings: WavelogSettings) : SettingsAction
|
||||||
data object SyncWorkedGrids : SettingsAction
|
data class SyncWorkedGrids(val settings: WavelogSettings) : SettingsAction
|
||||||
|
|
||||||
// LoTW confirmed grids
|
// LoTW confirmed grids
|
||||||
data class UpdateLoTW(val settings: com.rtbishop.look4sat.core.domain.model.LoTWSettings) : SettingsAction
|
data class UpdateLoTW(val settings: com.rtbishop.look4sat.core.domain.model.LoTWSettings) : SettingsAction
|
||||||
data object SyncLoTWGrids : SettingsAction
|
data class SyncLoTWGrids(val settings: com.rtbishop.look4sat.core.domain.model.LoTWSettings) : SettingsAction
|
||||||
|
|
||||||
// Update checker
|
// Update checker
|
||||||
data object CheckForUpdate : SettingsAction
|
data object CheckForUpdate : SettingsAction
|
||||||
|
|||||||
+9
-6
@@ -23,6 +23,7 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import androidx.lifecycle.viewmodel.initializer
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import com.rtbishop.look4sat.core.domain.repository.IDatabaseRepo
|
import com.rtbishop.look4sat.core.domain.repository.IDatabaseRepo
|
||||||
|
import com.rtbishop.look4sat.core.domain.model.WavelogSettings
|
||||||
import com.rtbishop.look4sat.core.domain.repository.IMainContainer
|
import com.rtbishop.look4sat.core.domain.repository.IMainContainer
|
||||||
import com.rtbishop.look4sat.core.domain.repository.ISettingsRepo
|
import com.rtbishop.look4sat.core.domain.repository.ISettingsRepo
|
||||||
import com.rtbishop.look4sat.core.domain.repository.IUpdateRepository
|
import com.rtbishop.look4sat.core.domain.repository.IUpdateRepository
|
||||||
@@ -155,10 +156,10 @@ class SettingsViewModel(
|
|||||||
is SettingsAction.UpdateDataSources -> settingsRepo.updateDataSourcesSettings(action.settings)
|
is SettingsAction.UpdateDataSources -> settingsRepo.updateDataSourcesSettings(action.settings)
|
||||||
// Wavelog worked grids
|
// Wavelog worked grids
|
||||||
is SettingsAction.UpdateWavelog -> settingsRepo.updateWavelogSettings(action.settings)
|
is SettingsAction.UpdateWavelog -> settingsRepo.updateWavelogSettings(action.settings)
|
||||||
SettingsAction.SyncWorkedGrids -> syncWorkedGrids()
|
is SettingsAction.SyncWorkedGrids -> syncWorkedGrids(action.settings)
|
||||||
// LoTW confirmed grids
|
// LoTW confirmed grids
|
||||||
is SettingsAction.UpdateLoTW -> settingsRepo.updateLoTWSettings(action.settings)
|
is SettingsAction.UpdateLoTW -> settingsRepo.updateLoTWSettings(action.settings)
|
||||||
SettingsAction.SyncLoTWGrids -> syncLoTWGrids()
|
is SettingsAction.SyncLoTWGrids -> syncLoTWGrids(action.settings)
|
||||||
// Update checker
|
// Update checker
|
||||||
SettingsAction.CheckForUpdate -> checkForUpdate()
|
SettingsAction.CheckForUpdate -> checkForUpdate()
|
||||||
SettingsAction.DownloadUpdate -> downloadUpdate()
|
SettingsAction.DownloadUpdate -> downloadUpdate()
|
||||||
@@ -170,12 +171,13 @@ class SettingsViewModel(
|
|||||||
|
|
||||||
// region Wavelog worked grids
|
// region Wavelog worked grids
|
||||||
|
|
||||||
private fun syncWorkedGrids() {
|
private fun syncWorkedGrids(settings: WavelogSettings) {
|
||||||
val settings = _uiState.value.wavelogSettings
|
|
||||||
if (!settings.isConfigured) {
|
if (!settings.isConfigured) {
|
||||||
_uiState.update { it.copy(wavelogMessage = "Wavelog URL/token not configured") }
|
_uiState.update { it.copy(wavelogMessage = "Wavelog URL/token not configured") }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Persist the credentials first, then sync with the freshly-typed values.
|
||||||
|
settingsRepo.updateWavelogSettings(settings)
|
||||||
_uiState.update { it.copy(wavelogSyncing = true, wavelogMessage = null) }
|
_uiState.update { it.copy(wavelogSyncing = true, wavelogMessage = null) }
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val grids = wavelogRepo.fetchWorkedGrids(settings.url, settings.token)
|
val grids = wavelogRepo.fetchWorkedGrids(settings.url, settings.token)
|
||||||
@@ -190,12 +192,13 @@ class SettingsViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun syncLoTWGrids() {
|
private fun syncLoTWGrids(settings: com.rtbishop.look4sat.core.domain.model.LoTWSettings) {
|
||||||
val settings = _uiState.value.lotwSettings
|
|
||||||
if (!settings.isConfigured) {
|
if (!settings.isConfigured) {
|
||||||
_uiState.update { it.copy(lotwMessage = "LoTW callsign/password not configured") }
|
_uiState.update { it.copy(lotwMessage = "LoTW callsign/password not configured") }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Persist the credentials first, then sync with the freshly-typed values.
|
||||||
|
settingsRepo.updateLoTWSettings(settings)
|
||||||
_uiState.update { it.copy(lotwSyncing = true, lotwMessage = null) }
|
_uiState.update { it.copy(lotwSyncing = true, lotwMessage = null) }
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val lotwGrids = lotwRepo.fetchConfirmedGrids(settings.callsign, settings.password)
|
val lotwGrids = lotwRepo.fetchConfirmedGrids(settings.callsign, settings.password)
|
||||||
|
|||||||
Reference in new issue
Block a user