feat: support pre-release

This commit is contained in:
Miuzarte
2026-07-07 18:19:39 +08:00
parent 6040d0e2f4
commit 5d03900f60
4 changed files with 47 additions and 22 deletions

View File

@@ -211,3 +211,4 @@ jobs:
name: ${{ github.ref_name }} name: ${{ github.ref_name }}
body_path: dist/RELEASE_NOTES.md body_path: dist/RELEASE_NOTES.md
files: dist/*.apk files: dist/*.apk
prerelease: ${{ contains(github.ref_name, '_pre') }}

View File

@@ -60,7 +60,7 @@ android {
minSdk = 26 minSdk = 26
targetSdk = 37 targetSdk = 37
versionCode = 33 versionCode = 33
versionName = "0.4.2" versionName = "0.4.3_pre1"
externalNativeBuild { externalNativeBuild {
cmake { cmake {

View File

@@ -17,7 +17,7 @@ import java.net.URL
object AppUpdateChecker { object AppUpdateChecker {
private const val TAG = "AppUpdateChecker" private const val TAG = "AppUpdateChecker"
const val RELEASES_API_URL = const val RELEASES_API_URL =
"https://api.github.com/repos/Miuzarte/ScrcpyForAndroid/releases?per_page=1" "https://api.github.com/repos/Miuzarte/ScrcpyForAndroid/releases?per_page=10"
const val RELEASES_URL = const val RELEASES_URL =
"https://github.com/Miuzarte/ScrcpyForAndroid/releases" "https://github.com/Miuzarte/ScrcpyForAndroid/releases"
const val REPO_URL = const val REPO_URL =
@@ -71,44 +71,67 @@ object AppUpdateChecker {
setRequestProperty("User-Agent", "ScrcpyForAndroid/$currentVersion") setRequestProperty("User-Agent", "ScrcpyForAndroid/$currentVersion")
} }
try { try {
val responseCode = connection.responseCode
if (responseCode != HttpURLConnection.HTTP_OK) {
error("HTTP $responseCode from GitHub releases API")
}
val body = connection.inputStream.bufferedReader().use { it.readText() } val body = connection.inputStream.bufferedReader().use { it.readText() }
val release = JSONArray(body).optJSONObject(0) val releases = JSONArray(body)
?: error("GitHub releases response is empty") val currentIsPre = currentVersion.contains("_pre")
val latestVersion = release.optString("tag_name")
.ifBlank { release.optString("name") } val targetRelease = (0 until releases.length()).asSequence()
.mapNotNull { releases.optJSONObject(it) }
.firstOrNull { release ->
currentIsPre || !release.optBoolean("prerelease", false)
} ?: error("No suitable release found")
val latestVersion = targetRelease.optString("tag_name")
.ifBlank { targetRelease.optString("name") }
.ifBlank { error("GitHub release has no tag name") } .ifBlank { error("GitHub release has no tag name") }
ReleaseInfo( ReleaseInfo(
currentVersion = currentVersion, currentVersion = currentVersion,
latestVersion = latestVersion.removePrefix("v").removePrefix("V"), latestVersion = latestVersion.removePrefix("v").removePrefix("V"),
hasUpdate = compareVersions(currentVersion, latestVersion) < 0, hasUpdate = compareVersions(currentVersion, latestVersion) < 0,
htmlUrl = release.optString("html_url").ifBlank { RELEASES_URL }, htmlUrl = targetRelease.optString("html_url").ifBlank { RELEASES_URL },
) )
} finally { } finally {
connection.disconnect() connection.disconnect()
} }
} }
private data class Version(
val parts: List<Int>,
val isPre: Boolean,
val preNum: Int,
)
private fun compareVersions(current: String, latest: String): Int { private fun compareVersions(current: String, latest: String): Int {
val currentParts = normalizeVersion(current) val cur = parseVersion(current)
val latestParts = normalizeVersion(latest) val lte = parseVersion(latest)
val maxSize = maxOf(currentParts.size, latestParts.size) val maxSize = maxOf(cur.parts.size, lte.parts.size)
for (i in 0 until maxSize) { for (i in 0 until maxSize) {
val currentPart = currentParts.getOrElse(i) { 0 } val cp = cur.parts.getOrElse(i) { 0 }
val latestPart = latestParts.getOrElse(i) { 0 } val lp = lte.parts.getOrElse(i) { 0 }
if (currentPart != latestPart) { if (cp != lp) return cp.compareTo(lp)
return currentPart.compareTo(latestPart) }
} return when {
!cur.isPre && !lte.isPre -> 0
!cur.isPre -> 1
!lte.isPre -> -1
else -> cur.preNum.compareTo(lte.preNum)
} }
return 0
} }
private fun normalizeVersion(value: String) = private fun parseVersion(value: String): Version {
value val cleaned = value.trim().removePrefix("v").removePrefix("V")
.trim() val preMatch = Regex("_(?:pre|rc|beta|alpha)(\\d*)$", RegexOption.IGNORE_CASE).find(cleaned)
.removePrefix("v") val mainPart = preMatch?.range?.let { cleaned.substring(0, it.first) } ?: cleaned
.removePrefix("V") val parts = mainPart.split(Regex("[^0-9]+"))
.split(Regex("[^0-9]+"))
.filter { it.isNotBlank() } .filter { it.isNotBlank() }
.mapNotNull { it.toIntOrNull() } .mapNotNull { it.toIntOrNull() }
.ifEmpty { listOf(0) } .ifEmpty { listOf(0) }
val isPre = preMatch != null
val preNum = preMatch?.groupValues?.getOrNull(1)?.toIntOrNull() ?: 0
return Version(parts, isPre, preNum)
}
} }

View File

@@ -142,6 +142,7 @@
<string name="pref_update_found">, found new version %1$s</string> <string name="pref_update_found">, found new version %1$s</string>
<string name="pref_update_latest">, already the latest version</string> <string name="pref_update_latest">, already the latest version</string>
<string name="pref_update_newer">, newer than latest release %1$s</string> <string name="pref_update_newer">, newer than latest release %1$s</string>
<string name="pref_update_prerelease_latest">, on the latest prerelease</string>
<!-- Settings - Theme --> <!-- Settings - Theme -->
<string name="section_theme">Theme</string> <string name="section_theme">Theme</string>