更新界面

This commit is contained in:
HapeLee
2025-10-01 02:33:23 +08:00
parent 1116ab9695
commit f3918f4a69
4 changed files with 83 additions and 59 deletions
@@ -11,13 +11,9 @@ data class AppReleaseInfo(
val note: String,
val name: String,
val downloadUrl: String,
val assetUrl: String
) {
val versionName: String = Regex("""legado_app_([\d.]+)\.apk""")
.find(name)
?.groupValues?.get(1)
?: ""
}
val assetUrl: String,
val versionName: String
)
enum class AppVariant {
OFFICIAL,
@@ -43,11 +39,14 @@ data class GithubRelease(
val createdAt: String?
) {
fun gitReleaseToAppReleaseInfo(): List<AppReleaseInfo> {
val version = tagName // 直接用 release 的 tag_name 作为版本号
assets ?: throw NoStackTraceException("获取新版本出错")
return assets
.filter { it.isValid }
.map { it.assetToAppReleaseInfo(isPreRelease, body) }
.map { it.assetToAppReleaseInfo(isPreRelease, body, version) }
}
}
@@ -69,17 +68,21 @@ data class Asset(
val isValid: Boolean
get() = (contentType == "application/vnd.android.package-archive") && (state == "uploaded")
fun assetToAppReleaseInfo(preRelease: Boolean, note: String): AppReleaseInfo {
fun assetToAppReleaseInfo(preRelease: Boolean, note: String, version: String): AppReleaseInfo {
val instant = Instant.parse(createdAt)
val timestamp: Long = instant.toEpochMilli()
val appVariant = if (preRelease) {
AppVariant.BETA_RELEASE
} else {
AppVariant.OFFICIAL
}
val appVariant = if (preRelease) AppVariant.BETA_RELEASE else AppVariant.OFFICIAL
return AppReleaseInfo(appVariant, timestamp, note, name, apkUrl, url)
return AppReleaseInfo(
appVariant = appVariant,
createdAt = timestamp,
note = note,
name = name,
downloadUrl = apkUrl,
assetUrl = url,
versionName = version // 直接传入 tagName
)
}
}
@@ -29,16 +29,10 @@ object AppUpdateGitHub : AppUpdate.AppUpdateInterface {
"https://api.github.com/repos/HapeLee/legado-with-MD3/releases/latest"
}
val res = okHttpClient.newCallResponse {
url(lastReleaseUrl)
}
if (!res.isSuccessful) {
throw NoStackTraceException("获取新版本出错(${res.code})")
}
val res = okHttpClient.newCallResponse { url(lastReleaseUrl) }
if (!res.isSuccessful) throw NoStackTraceException("获取新版本出错(${res.code})")
val body = res.body.text()
if (body.isBlank()) {
throw NoStackTraceException("获取新版本出错")
}
if (body.isBlank()) throw NoStackTraceException("获取新版本出错")
return if (checkVariant.isBeta()) {
val releases = GSON.fromJsonArray<GithubRelease>(body)
@@ -60,11 +54,16 @@ object AppUpdateGitHub : AppUpdate.AppUpdateInterface {
override fun check(scope: CoroutineScope): Coroutine<AppUpdate.UpdateInfo> {
return Coroutine.async(scope) {
val currentVersion = AppConst.appInfo.versionName
val releases = getLatestRelease()
.filter { it.appVariant == checkVariant }
val latest = releases.firstOrNull { it.versionName.versionCompare(currentVersion) > 0 }
val latest = releases.firstOrNull { r ->
try {
r.versionName.versionCompare(currentVersion) > 0
} catch (e: Exception) {
false
}
}
if (latest != null) {
return@async AppUpdate.UpdateInfo(
@@ -79,35 +78,57 @@ object AppUpdateGitHub : AppUpdate.AppUpdateInterface {
}.timeout(10000)
}
fun String.versionCompare(other: String): Int {
val regex = Regex("""(\d+)|(\D+)""")
val thisParts = regex.findAll(this).map { it.value }.toList()
val otherParts = regex.findAll(other).map { it.value }.toList()
val maxLength = maxOf(thisParts.size, otherParts.size)
private data class SemVer(
val major: Int,
val minor: Int,
val patch: Int,
val preRelease: String? = null
) : Comparable<SemVer> {
override fun compareTo(other: SemVer): Int {
if (major != other.major) return major - other.major
if (minor != other.minor) return minor - other.minor
if (patch != other.patch) return patch - other.patch
for (i in 0 until maxLength) {
val a = thisParts.getOrNull(i)
val b = otherParts.getOrNull(i)
if (preRelease == null && other.preRelease != null) return 1
if (preRelease != null && other.preRelease == null) return -1
if (preRelease == null && other.preRelease == null) return 0
if (a == null) return -1
if (b == null) return 1
val aParts = preRelease!!.split(".")
val bParts = other.preRelease!!.split(".")
val maxLen = maxOf(aParts.size, bParts.size)
val isANum = a.all { it.isDigit() }
val isBNum = b.all { it.isDigit() }
for (i in 0 until maxLen) {
val a = aParts.getOrNull(i)
val b = bParts.getOrNull(i)
if (a == null) return -1
if (b == null) return 1
if (isANum && a.length > 1 && a.startsWith("0")) return -1
if (isBNum && b.length > 1 && b.startsWith("0")) return 1
val cmp = if (isANum && isBNum) {
a.toInt() - b.toInt()
} else {
a.compareTo(b)
val isANum = a.all { it.isDigit() }
val isBNum = b.all { it.isDigit() }
if (isANum && isBNum) {
val cmp = a.toInt().compareTo(b.toInt())
if (cmp != 0) return cmp
} else {
val cmp = a.compareTo(b)
if (cmp != 0) return cmp
}
}
if (cmp != 0) return cmp
return 0
}
return 0
companion object {
fun parse(version: String): SemVer {
val regex = Regex("""(\d+)\.(\d+)\.(\d+)(?:-([\w.]+))?""")
val match = regex.matchEntire(version)
?: throw IllegalArgumentException("Invalid version: $version")
val (maj, min, pat, pre) = match.destructured
return SemVer(maj.toInt(), min.toInt(), pat.toInt(), pre.ifBlank { null })
}
}
}
fun String.versionCompare(other: String): Int {
return SemVer.parse(this).compareTo(SemVer.parse(other))
}
}