This commit is contained in:
HapeLee
2025-11-18 20:53:25 +08:00
parent 3aece2eadd
commit 158b472bef
5 changed files with 197 additions and 88 deletions
+44 -1
View File
@@ -5,7 +5,7 @@ plugins {
// //id 'kotlin-kapt' // //id 'kotlin-kapt'
// id 'com.google.devtools.ksp' // id 'com.google.devtools.ksp'
// id "com.google.gms.google-services" // id "com.google.gms.google-services"
alias libs.plugins.compose.compiler
alias libs.plugins.android.application alias libs.plugins.android.application
alias libs.plugins.kotlin.android alias libs.plugins.kotlin.android
alias libs.plugins.kotlin.parcelize alias libs.plugins.kotlin.parcelize
@@ -48,6 +48,11 @@ def getGitCommitHash() {
android { android {
compileSdk = compile_sdk_version compileSdk = compile_sdk_version
namespace = "io.legado.app" namespace = "io.legado.app"
buildFeatures {
compose = true
}
kotlin { kotlin {
jvmToolchain { jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(21)) languageVersion.set(JavaLanguageVersion.of(21))
@@ -334,4 +339,42 @@ dependencies {
implementation libs.androidx.palette implementation libs.androidx.palette
implementation libs.androidx.core.splashscreen implementation libs.androidx.core.splashscreen
implementation libs.androidx.startup.runtime implementation libs.androidx.startup.runtime
// Compose 基础
implementation platform(libs.androidx.compose.bom)
implementation libs.activity.compose
implementation libs.androidx.compose.ui
implementation libs.androidx.compose.ui.tooling.preview
debugImplementation libs.androidx.compose.ui.tooling
// Material3
implementation libs.androidx.compose.material3
// Lifecycle
implementation libs.androidx.lifecycle.runtime.compose
// Activity
implementation libs.androidx.activity.compose
// Navigation(用于弹框或未来扩展)
implementation libs.navigation.compose
// Coil
implementation libs.coil.compose
// accompanist WebView(打开网页)
implementation libs.accompanist.webview
// Markdown 渲染(Compose MD
implementation libs.markdown.compose
// 协程
implementation libs.kotlinx.coroutines.android
implementation libs.androidx.lifecycle.viewmodel.compose
implementation libs.androidx.lifecycle.runtime.compose
implementation libs.androidx.compose.animation
implementation libs.androidx.compose.foundation
implementation libs.androidx.constraintlayout.compose
implementation libs.androidx.compose.ui.viewbinding
} }
@@ -5,7 +5,11 @@ package io.legado.app.ui.about
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.activity.compose.setContent
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.material3.MaterialTheme
import androidx.core.app.ComponentActivity
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import io.legado.app.BuildConfig import io.legado.app.BuildConfig
@@ -47,9 +51,7 @@ import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
class AboutActivity : BaseActivity<ActivityAboutBinding>() { class AboutActivity : AppCompatActivity() {
override val binding by viewBinding(ActivityAboutBinding::inflate)
private val waitDialog by lazy { private val waitDialog by lazy {
WaitDialog(this).setText(R.string.checking_update) WaitDialog(this).setText(R.string.checking_update)
@@ -57,96 +59,36 @@ class AboutActivity : BaseActivity<ActivityAboutBinding>() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setSupportActionBar(binding.topBar)
binding.tvVersion.text = appInfo.versionName setContent {
MaterialTheme {
binding.btnUpdate.setOnClickListener { AboutScreen(
checkUpdate() onCheckUpdate = { checkUpdate() },
onOpenUrl = { openUrl(it) },
onShowMdFile = { title, file -> showMdFile(title, file) },
onSaveLog = { saveLog() },
onCreateHeapDump = { createHeapDump() },
onShowCrashLogs = { showDialogFragment<CrashLogsDialog>() }
)
} }
binding.btnGithub.setOnClickListener {
openUrl(R.string.github_url)
}
binding.btnWeb.setOnClickListener {
openUrl(R.string.legado_url)
}
binding.llContributors.setOnClickListener {
openUrl(R.string.contributors_url)
}
binding.llUpdateLog.setOnClickListener {
lifecycleScope.launch {
upVersion()
} }
} }
binding.llPrivacyPolicy.setOnClickListener { override fun onCreateOptionsMenu(menu: Menu?): Boolean {
showMdFile(getString(R.string.privacy_policy), "privacyPolicy.md")
}
binding.llLicense.setOnClickListener {
showMdFile(getString(R.string.license), "LICENSE.md")
}
binding.llDisclaimer.setOnClickListener {
showMdFile(getString(R.string.disclaimer), "disclaimer.md")
}
binding.llCrashLog.setOnClickListener {
showDialogFragment<CrashLogsDialog>()
}
binding.llSaveLog.setOnClickListener {
saveLog()
}
binding.llCreateHeapDump.setOnClickListener {
createHeapDump()
}
}
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.about, menu) menuInflater.inflate(R.menu.about, menu)
return super.onCompatCreateOptionsMenu(menu) return true
} }
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { return when (item.itemId) {
R.id.menu_share_it -> share( R.id.menu_share_it -> {
share(
getString(R.string.app_share_description), getString(R.string.app_share_description),
getString(R.string.app_name) getString(R.string.app_name)
) )
true
} }
return super.onCompatOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
}
private suspend fun upVersion() {
try {
val info = withContext(Dispatchers.IO) {
AppUpdateGitHub.getReleaseByTag(BuildConfig.VERSION_NAME)
}
withContext(Dispatchers.Main) {
val dialog = if (info != null) {
UpdateDialog(info, UpdateDialog.Mode.VIEW_LOG)
} else {
val fallback = String(assets.open("updateLog.md").readBytes())
TextDialog(getString(R.string.update_log), fallback, TextDialog.Mode.MD)
}
showDialogFragment(dialog)
}
} catch (e: Exception) {
e.printStackTrace()
val fallback = String(assets.open("updateLog.md").readBytes())
val dialog = TextDialog(getString(R.string.update_log), fallback, TextDialog.Mode.MD)
withContext(Dispatchers.Main) {
showDialogFragment(dialog)
}
} }
} }
@@ -158,9 +100,9 @@ class AboutActivity : BaseActivity<ActivityAboutBinding>() {
private fun checkUpdate() { private fun checkUpdate() {
waitDialog.show() waitDialog.show()
AppUpdate.gitHubUpdate?.run { AppUpdate.gitHubUpdate?.run {
check(lifecycleScope) kotlin.check(lifecycleScope)
.onSuccess { .onSuccess {
showDialogFragment(UpdateDialog(it, UpdateDialog.Mode.UPDATE)) showDialogFragment(UpdateDialog(it))
}.onError { }.onError {
appCtx.toastOnUi("${getString(R.string.check_update)}\n${it.localizedMessage}") appCtx.toastOnUi("${getString(R.string.check_update)}\n${it.localizedMessage}")
}.onFinally { }.onFinally {
@@ -0,0 +1,107 @@
package io.legado.app.ui.about
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.DividerDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.constant.AppConst.appInfo
@Composable
fun AboutScreen(
onCheckUpdate: () -> Unit = {},
onOpenUrl: (String) -> Unit = {},
onShowMdFile: (String, String) -> Unit = { _, _ -> },
onSaveLog: () -> Unit = {},
onCreateHeapDump: () -> Unit = {},
onShowCrashLogs: () -> Unit = {},
versionName: String = "1.0.0"
) {
val context = LocalContext.current
val version = remember { appInfo.versionName }
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.about)) }
)
}
) { padding ->
Column(
modifier = Modifier
.padding(padding)
.padding(16.dp)
.verticalScroll(rememberScrollState())
) {
Text(text = "版本:$version", style = MaterialTheme.typography.bodyLarge)
Spacer(Modifier.height(8.dp))
Button(onClick = onCheckUpdate) {
Text(stringResource(R.string.check_update))
}
Spacer(Modifier.height(8.dp))
Button(onClick = { onOpenUrl(context.getString(R.string.github_url)) }) {
Text(stringResource(R.string.github_url))
}
Button(onClick = { onOpenUrl(context.getString(R.string.legado_url)) }) {
Text(stringResource(R.string.web_service))
}
HorizontalDivider(
Modifier.padding(vertical = 8.dp),
DividerDefaults.Thickness,
DividerDefaults.color
)
Text(text = "文档与政策", style = MaterialTheme.typography.titleMedium)
Spacer(Modifier.height(4.dp))
TextButton(onClick = { onShowMdFile(context.getString(R.string.privacy_policy), "privacyPolicy.md") }) {
Text(stringResource(R.string.privacy_policy))
}
TextButton(onClick = { onShowMdFile(context.getString(R.string.license), "LICENSE.md") }) {
Text(stringResource(R.string.license))
}
TextButton(onClick = { onShowMdFile(context.getString(R.string.disclaimer), "disclaimer.md") }) {
Text(stringResource(R.string.disclaimer))
}
HorizontalDivider(
Modifier.padding(vertical = 8.dp),
DividerDefaults.Thickness,
DividerDefaults.color
)
Text(text = "开发工具", style = MaterialTheme.typography.titleMedium)
Spacer(Modifier.height(4.dp))
TextButton(onClick = onShowCrashLogs) {
Text(stringResource(R.string.crash_log))
}
TextButton(onClick = onSaveLog) {
Text(stringResource(R.string.save_log))
}
TextButton(onClick = onCreateHeapDump) {
Text(stringResource(R.string.create_heap_dump))
}
}
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ plugins {
//id 'com.google.devtools.ksp' version "$kotlin_version-$ksp_version" apply false //id 'com.google.devtools.ksp' version "$kotlin_version-$ksp_version" apply false
//id "de.undercouch.download" version "5.5.0" apply false //id "de.undercouch.download" version "5.5.0" apply false
//id "com.google.gms.google-services" version "4.4.0" apply false //id "com.google.gms.google-services" version "4.4.0" apply false
alias libs.plugins.compose.compiler apply false
alias libs.plugins.android.application apply false alias libs.plugins.android.application apply false
alias libs.plugins.android.library apply false alias libs.plugins.android.library apply false
alias libs.plugins.kotlin.android apply false alias libs.plugins.kotlin.android apply false
+17
View File
@@ -1,8 +1,12 @@
[versions] [versions]
accompanistWebview = "0.36.0"
coilCompose = "2.7.0"
composeBom = "2025.11.00"
coreSplashscreen = "1.2.0-rc01" coreSplashscreen = "1.2.0-rc01"
datastorePreferences = "1.1.7" datastorePreferences = "1.1.7"
kotlin = "2.2.20" kotlin = "2.2.20"
kotlinxCoroutinesAndroid = "1.8.1"
ksp = "2.2.20-2.0.4" ksp = "2.2.20-2.0.4"
agp = "8.13.1" agp = "8.13.1"
appcompat = "1.7.1" appcompat = "1.7.1"
@@ -28,6 +32,7 @@ jsoup = "1.16.2"
jsoupxpath = "2.5.3" jsoupxpath = "2.5.3"
coroutines = "1.10.2" coroutines = "1.10.2"
liveeventbus = "1.8.14" liveeventbus = "1.8.14"
markdownCompose = "0.5.0"
markwon = "4.6.2" markwon = "4.6.2"
material = "1.14.0-alpha06" material = "1.14.0-alpha06"
media = "1.7.1" media = "1.7.1"
@@ -59,22 +64,31 @@ zxingLite = "3.3.0"
[libraries] [libraries]
accompanist-webview = { module = "com.google.accompanist:accompanist-webview", version.ref = "accompanistWebview" }
activity-activity = { module = "androidx.activity:activity", version.ref = "activity" } activity-activity = { module = "androidx.activity:activity", version.ref = "activity" }
activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity" } activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity" }
activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activity" } activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activity" }
androidx-activity-compose = { module = "androidx.activity:activity-compose" }
androidx-annotation = { group = "androidx.annotation", name = "annotation", version = "1.9.1" } androidx-annotation = { group = "androidx.annotation", name = "annotation", version = "1.9.1" }
#androidx-annotation-experimental = { group = "androidx.annotation", name = "annotation-experimental", version = "1.3.1" } #androidx-annotation-experimental = { group = "androidx.annotation", name = "annotation-experimental", version = "1.3.1" }
androidx-collection = { module = "androidx.collection:collection", version.ref = "collection" } androidx-collection = { module = "androidx.collection:collection", version.ref = "collection" }
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
androidx-compose-material3 = { module = "androidx.compose.material3:material3" }
androidx-compose-ui = { module = "androidx.compose.ui:ui" }
androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" } androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" } androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose" }
androidx-palette = { module = "androidx.palette:palette", version.ref = "palette" } androidx-palette = { module = "androidx.palette:palette", version.ref = "palette" }
androidx-startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "startupRuntime" } androidx-startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "startupRuntime" }
appcompat-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } appcompat-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" } androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }
core-core = { module = "androidx.core:core", version.ref = "core" } core-core = { module = "androidx.core:core", version.ref = "core" }
core-ktx = { module = "androidx.core:core-ktx", version.ref = "core" } core-ktx = { module = "androidx.core:core-ktx", version.ref = "core" }
@@ -93,10 +107,12 @@ androidx-junit = { module = "androidx.test.ext:junit", version = "1.3.0" }
hutool-crypto = { module = "cn.hutool:hutool-crypto", version.ref = "hutool" } hutool-crypto = { module = "cn.hutool:hutool-crypto", version.ref = "hutool" }
kotlinx-coroutines-android-v181 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" }
libarchive = { module = "me.zhanghai.android.libarchive:library", version.ref = "libarchive" } libarchive = { module = "me.zhanghai.android.libarchive:library", version.ref = "libarchive" }
lifecycle-common-java8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "lifecycle" } lifecycle-common-java8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "lifecycle" }
lifecycle-service = { module = "androidx.lifecycle:lifecycle-service", version.ref = "lifecycle" } lifecycle-service = { module = "androidx.lifecycle:lifecycle-service", version.ref = "lifecycle" }
markdown-compose = { module = "com.mikepenz:markdown-compose", version.ref = "markdownCompose" }
media-media = { module = "androidx.media:media", version.ref = "media" } media-media = { module = "androidx.media:media", version.ref = "media" }
media3-datasource-okhttp = { module = "androidx.media3:media3-datasource-okhttp", version.ref = "media3" } media3-datasource-okhttp = { module = "androidx.media3:media3-datasource-okhttp", version.ref = "media3" }
media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" } media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
@@ -112,6 +128,7 @@ nanohttpd-nanohttpd = { module = "org.nanohttpd:nanohttpd", version.ref = "nanoH
nanohttpd-websocket = { module = "org.nanohttpd:nanohttpd-websocket", version.ref = "nanoHttpd" } nanohttpd-websocket = { module = "org.nanohttpd:nanohttpd-websocket", version.ref = "nanoHttpd" }
navigation-compose = { module = "androidx.navigation:navigation-compose" }
preference-preference = { module = "androidx.preference:preference", version.ref = "preference" } preference-preference = { module = "androidx.preference:preference", version.ref = "preference" }
preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preference" } preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preference" }