Skip to content

Commit

Permalink
支持获取历史版本更新记录
Browse files Browse the repository at this point in the history
  • Loading branch information
Him188 committed May 30, 2024
1 parent b315825 commit 4761ec9
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 28 deletions.
3 changes: 3 additions & 0 deletions app/shared/pages/main/common/platform/CommonKoinModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ fun createAppRootCoroutineScope(): CoroutineScope {

@Stable
interface AniBuildConfig {
/**
* `3.0.0-rc04`
*/
val versionName: String
val bangumiOauthClientAppId: String
val bangumiOauthClientSecret: String
Expand Down
168 changes: 140 additions & 28 deletions app/shared/pages/update/common/UpdateCheckerState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package me.him188.ani.app.ui.update
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
Expand All @@ -13,6 +15,7 @@ import androidx.compose.material.icons.rounded.Download
import androidx.compose.material.icons.rounded.Update
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
Expand All @@ -27,25 +30,35 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import io.ktor.client.statement.bodyAsChannel
import io.ktor.client.statement.bodyAsText
import io.ktor.http.appendPathSegments
import io.ktor.utils.io.jvm.javaio.toInputStream
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.withContext
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import me.him188.ani.app.navigation.BrowserNavigator
import me.him188.ani.app.platform.LocalContext
import me.him188.ani.app.platform.Platform
import me.him188.ani.app.platform.currentAniBuildConfig
import me.him188.ani.app.platform.currentPlatform
import me.him188.ani.app.tools.TimeFormatter
import me.him188.ani.app.ui.foundation.AbstractViewModel
import me.him188.ani.app.ui.foundation.launchInBackground
import me.him188.ani.app.ui.foundation.widgets.RichDialogLayout
import me.him188.ani.app.ui.profile.update.Release
import me.him188.ani.utils.logging.error
import me.him188.ani.utils.logging.info
import org.koin.core.context.GlobalContext
import java.time.Instant

Expand Down Expand Up @@ -78,36 +91,121 @@ class UpdateCheckerState : AbstractViewModel() {
}
}

suspend fun checkLatestVersion(): NewVersion? {
return kotlin.runCatching {
HttpClient().use { client ->
val json = Json {
ignoreUnknownKeys = true
private val json = Json {
ignoreUnknownKeys = true
}

private suspend fun checkLatestVersion(): NewVersion? {
HttpClient {
expectSuccess = true
}.use { client ->
return kotlin.runCatching {
client.getVersionFromAniServer("https://danmaku-global.myani.org/").also {
logger.info { "Got latest version from global server: ${it?.name}" }
}
}.recoverCatching {
client.getVersionFromAniServer("https://danmaku-cn.myani.org/").also {
logger.info { "Got latest version from CN server: ${it?.name}" }
}
}.recoverCatching {
val body = client.get("https://api.github.com/repos/him188/Ani/releases/latest").bodyAsText()
val release = json.decodeFromString(Release.serializer(), body)
val tag = release.tagName

val distributionSuffix = getDistributionSuffix()
val version = tag.substringAfter("v")
val publishedAt = kotlin.runCatching {
TimeFormatter().format(
Instant.parse(release.publishedAt).toEpochMilli(),
)
}.getOrElse { release.publishedAt }
NewVersion(
name = tag.substringAfter("v"),
changelog = release.body.substringBeforeLast("### 下载").substringBeforeLast("----").trim(),
name = version,
changelogs = listOf(
Changelog(
version,
publishedAt,
release.body.substringBeforeLast("### 下载").substringBeforeLast("----").trim()
)
),
apkUrl = release.assets.firstOrNull {
it.name.endsWith(distributionSuffix)
}?.browserDownloadUrl ?: "",
publishedAt = kotlin.runCatching {
TimeFormatter().format(
Instant.parse(release.publishedAt).toEpochMilli(),
)
}.getOrElse { release.publishedAt }
publishedAt = publishedAt
).also {
lastCheckTime = System.currentTimeMillis()
}.also {
logger.info { "Got latest version from Github: ${it.name}" }
}
}.onFailure {
logger.error(it) { "Failed to get latest version" }
return null
}.getOrNull()
}
}

private fun formatTime(
seconds: Long,
): String = kotlin.runCatching { TimeFormatter().format(seconds * 1000) }.getOrElse { seconds.toString() }

private suspend fun HttpClient.getVersionFromAniServer(baseUrl: String): NewVersion? {
@Serializable
class UpdatesIncrementalResponse(
val versions: List<String>,
)

@Serializable
class Update(
val version: String,
val downloadUrl: String,
@SerialName("publishTime") val publishTimeSeconds: Long,
val description: String,
)

@Serializable
class UpdatesIncrementalDetailsResponse(
val updates: List<Update>,
)

// val versions = get(baseUrl) {
// url {
// appendPathSegments("v1/updates/incremental")
// }
// val platform = currentPlatform
// parameter("clientVersion", currentAniBuildConfig.versionName)
// parameter("clientArch", platform.arch.displayName)
// parameter("releaseClass", "beta")
// }.bodyAsChannel().toInputStream().use {
// json.decodeFromStream(UpdatesIncrementalResponse.serializer(), it)
// }.versions
//
// val newestVersion = versions.lastOrNull() ?: return null
val updates = get(baseUrl) {
url {
appendPathSegments("v1/updates/incremental/details")
}
}.onFailure {
logger.error(it) { "Failed to get latest version" }
val platform = currentPlatform
parameter("clientVersion", currentAniBuildConfig.versionName)
parameter("clientArch", platform.name.lowercase() + "-" + platform.arch.displayName)
parameter("releaseClass", "rc")
}.bodyAsChannel().toInputStream().use {
json.decodeFromStream(UpdatesIncrementalDetailsResponse.serializer(), it)
}.updates

if (updates.isEmpty()) {
return null
}.getOrNull()
}

return updates.last().let { latest ->
NewVersion(
name = latest.version,
changelogs = updates.asReversed().asSequence().take(10).filter { it.version != currentVersion }.map {
Changelog(it.version, formatTime(it.publishTimeSeconds), it.description)
}.toList(),
apkUrl = latest.downloadUrl,
publishedAt = formatTime(latest.publishTimeSeconds)
)
}
}

private fun getDistributionSuffix(): String = when (val platform = Platform.currentPlatform) {
Expand Down Expand Up @@ -159,20 +257,27 @@ fun ChangelogDialog(
color = MaterialTheme.colorScheme.primary,
)

Text(
"发布时间: " + state.latestVersion?.publishedAt.orEmpty(),
style = MaterialTheme.typography.bodyMedium,
)
for (changelog in state.latestVersion?.changelogs.orEmpty()) {
HorizontalDivider()

Text(
"更新内容: ",
style = MaterialTheme.typography.titleMedium,
)
Row(verticalAlignment = Alignment.Bottom) {
Text(
changelog.version,
style = MaterialTheme.typography.titleMedium,
)

Text(
changelog.publishedAt,
Modifier.padding(start = 16.dp),
style = MaterialTheme.typography.labelMedium,
)
}
Text(
changelog.changes,
style = MaterialTheme.typography.bodyMedium,
)
}

Text(
state.latestVersion?.changelog.orEmpty(),
style = MaterialTheme.typography.bodyMedium,
)
}
}
}
Expand All @@ -194,7 +299,14 @@ fun HasUpdateTag(
@Immutable
class NewVersion(
val name: String,
val changelog: String,
val changelogs: List<Changelog>,
val apkUrl: String,
val publishedAt: String,
)

@Immutable
class Changelog(
val version: String,
val publishedAt: String,
val changes: String
)

0 comments on commit 4761ec9

Please sign in to comment.