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:
atsunatsu committed 2026-09-07 07:22:41 +08:00
1 parent be6473e692
commit c7ca77a943
10 files changed
+110 -40

No files matched your search

@@ -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)
val stream = result.stream ?: continue
val parsed = try {
parseRelease(stream.bufferedReader().use { it.readText() }, baseUrl)
} catch (e: Exception) { } catch (e: Exception) {
println("UpdateRepository parse failure: $e") println("UpdateRepository parse failure: $e")
null 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>
@@ -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) {
@@ -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))
} }
@@ -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)
}, },
@@ -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
@@ -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)