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
+113 -43

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
// 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.
val result = remoteSource.getNetworkStream(LATEST_RELEASE_URL)
val stream = result.stream ?: return@withContext null
try {
val html = stream.bufferedReader().use { it.readText() }
parseRelease(html)
} catch (e: Exception) {
println("UpdateRepository parse failure: $e")
null
// When GitHub is unreachable (common on mainland-China networks without a
// proxy), fall back to community GitHub accelerator mirrors that proxy the
// same page; each mirror resolves the identical tag and asset URLs.
for (baseUrl in LATEST_RELEASE_URLS) {
val result = remoteSource.getNetworkStream(baseUrl)
val stream = result.stream ?: continue
val parsed = try {
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) {
@@ -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
// 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
@@ -53,8 +59,16 @@ class UpdateRepository(
.find(html)?.groupValues?.get(1)?.trim()
?.substringBefore("·")?.removePrefix("Release")?.trim() ?: tag
// The release APK asset follows the fixed naming scheme used by the build:
// Look4Sat-<tag without leading v>-release.apk
val apkUrl = "$DOWNLOAD_BASE_URL/$tag/Look4Sat-${tag.removePrefix("v")}-release.apk"
// Look4Sat-<tag without leading v>-release.apk. When the page was fetched
// 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(
versionTag = tag,
title = title,
@@ -65,8 +79,18 @@ class UpdateRepository(
private companion object {
// Web pages are used instead of api.github.com to avoid the 60 req/hour
// anonymous rate limit (see getLatestRelease above).
const val LATEST_RELEASE_URL = "https://github.com/atsunatsu/Look4Sat/releases/latest"
// anonymous rate limit (see getLatestRelease above). The GitHub URL is
// 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"
}
}