From a7c18535e1f80712c3be8f3e2b7d49e05d3a76bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 16 Feb 2026 11:22:53 +0800 Subject: [PATCH] Fix update version check --- .../sfa/vendor/GitHubUpdateChecker.kt | 61 ++++++++++++++----- .../nekohasekai/sfa/compose/MainActivity.kt | 2 + .../screen/settings/AppSettingsScreen.kt | 33 +++++----- 3 files changed, 64 insertions(+), 32 deletions(-) diff --git a/app/src/github/java/io/nekohasekai/sfa/vendor/GitHubUpdateChecker.kt b/app/src/github/java/io/nekohasekai/sfa/vendor/GitHubUpdateChecker.kt index 6a0c818..d241a2c 100644 --- a/app/src/github/java/io/nekohasekai/sfa/vendor/GitHubUpdateChecker.kt +++ b/app/src/github/java/io/nekohasekai/sfa/vendor/GitHubUpdateChecker.kt @@ -4,7 +4,6 @@ import android.os.Build import io.nekohasekai.libbox.Libbox import io.nekohasekai.sfa.BuildConfig import io.nekohasekai.sfa.ktx.unwrap -import io.nekohasekai.sfa.update.UpdateCheckException import io.nekohasekai.sfa.update.UpdateInfo import io.nekohasekai.sfa.update.UpdateTrack import io.nekohasekai.sfa.utils.HTTPClient @@ -27,18 +26,25 @@ class GitHubUpdateChecker : Closeable { private val json = Json { ignoreUnknownKeys = true } fun checkUpdate(track: UpdateTrack): UpdateInfo? { - val includePrerelease = track == UpdateTrack.BETA - val release = getLatestRelease(includePrerelease) ?: return null + val releases = getReleases() + var selected: ReleaseCandidate? = null - if (!release.assets.any { it.name == METADATA_FILENAME }) { - throw UpdateCheckException.TrackNotSupported() + for (release in releases) { + if (!isReleaseInTrack(release, track)) { + continue + } + val metadata = runCatching { downloadMetadata(release) }.getOrNull() ?: continue + if (!isNewerThanCurrent(metadata.versionName)) { + continue + } + val currentBest = selected + if (currentBest == null || isBetterVersion(metadata, currentBest.metadata)) { + selected = ReleaseCandidate(release, metadata) + } } - val metadata = downloadMetadata(release)!! - - if (metadata.versionCode <= BuildConfig.VERSION_CODE) { - return null - } + val release = selected?.release ?: return null + val metadata = selected.metadata val isLegacy = Build.VERSION.SDK_INT < Build.VERSION_CODES.M val apkAsset = release.assets.find { asset -> @@ -58,7 +64,7 @@ class GitHubUpdateChecker : Closeable { ) } - private fun getLatestRelease(includePrerelease: Boolean): GitHubRelease? { + private fun getReleases(): List { val request = client.newRequest() request.setURL(RELEASES_URL) request.setHeader("Accept", "application/vnd.github.v3+json") @@ -67,13 +73,31 @@ class GitHubUpdateChecker : Closeable { val response = request.execute() val content = response.content.unwrap - val releases = json.decodeFromString>(content) + return json.decodeFromString(content) + } - return if (includePrerelease) { - releases.firstOrNull() - } else { - releases.firstOrNull { !it.prerelease && !it.draft } + private fun isReleaseInTrack(release: GitHubRelease, track: UpdateTrack): Boolean { + if (release.draft) { + return false } + return when (track) { + UpdateTrack.STABLE -> !release.prerelease + UpdateTrack.BETA -> true + } + } + + private fun isNewerThanCurrent(versionName: String): Boolean { + return Libbox.compareSemver(versionName, BuildConfig.VERSION_NAME) + } + + private fun isBetterVersion(version: VersionMetadata, other: VersionMetadata): Boolean { + if (Libbox.compareSemver(version.versionName, other.versionName)) { + return true + } + if (Libbox.compareSemver(other.versionName, version.versionName)) { + return false + } + return version.versionCode > other.versionCode } private fun downloadMetadata(release: GitHubRelease): VersionMetadata? { @@ -117,4 +141,9 @@ class GitHubUpdateChecker : Closeable { @SerialName("version_code") val versionCode: Int = 0, @SerialName("version_name") val versionName: String = "", ) + + private data class ReleaseCandidate( + val release: GitHubRelease, + val metadata: VersionMetadata, + ) } diff --git a/app/src/main/java/io/nekohasekai/sfa/compose/MainActivity.kt b/app/src/main/java/io/nekohasekai/sfa/compose/MainActivity.kt index eb469b8..574422b 100644 --- a/app/src/main/java/io/nekohasekai/sfa/compose/MainActivity.kt +++ b/app/src/main/java/io/nekohasekai/sfa/compose/MainActivity.kt @@ -198,6 +198,7 @@ class MainActivity : val updateInfo = Vendor.checkUpdateAsync() UpdateState.setUpdate(updateInfo) } catch (_: Exception) { + UpdateState.setUpdate(null) } } } @@ -495,6 +496,7 @@ class MainActivity : val result = Vendor.checkUpdateAsync() UpdateState.setUpdate(result) } catch (_: Exception) { + UpdateState.setUpdate(null) } } }) { diff --git a/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/AppSettingsScreen.kt b/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/AppSettingsScreen.kt index 8967f2b..8aa6d21 100644 --- a/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/AppSettingsScreen.kt +++ b/app/src/main/java/io/nekohasekai/sfa/compose/screen/settings/AppSettingsScreen.kt @@ -188,6 +188,7 @@ fun AppSettingsScreen(navController: NavController) { currentTrack = currentTrack, onTrackSelected = { track -> currentTrack = track + UpdateState.clear() scope.launch(Dispatchers.IO) { Settings.updateTrack = track } @@ -932,25 +933,25 @@ fun AppSettingsScreen(navController: NavController) { }, ) .clickable(enabled = !isChecking) { - if (hasUpdate && updateInfo != null) { - showUpdateAvailableDialog = true - } else { - scope.launch { - UpdateState.isChecking.value = true - withContext(Dispatchers.IO) { - try { - val result = Vendor.checkUpdateAsync() - UpdateState.setUpdate(result) - if (result == null) { - showErrorDialog = R.string.no_updates_available - } - } catch (_: UpdateCheckException.TrackNotSupported) { - showErrorDialog = R.string.update_track_not_supported - } catch (_: Exception) { + scope.launch { + UpdateState.isChecking.value = true + withContext(Dispatchers.IO) { + try { + val result = Vendor.checkUpdateAsync() + UpdateState.setUpdate(result) + if (result == null) { + showErrorDialog = R.string.no_updates_available + } else { + showUpdateAvailableDialog = true } + } catch (_: UpdateCheckException.TrackNotSupported) { + UpdateState.setUpdate(null) + showErrorDialog = R.string.update_track_not_supported + } catch (_: Exception) { + UpdateState.setUpdate(null) } - UpdateState.isChecking.value = false } + UpdateState.isChecking.value = false } }, colors =