diff --git a/app/build.gradle b/app/build.gradle index 157d0aab6..d0eb3d665 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,7 @@ plugins { // //id 'kotlin-kapt' // id 'com.google.devtools.ksp' // id "com.google.gms.google-services" - + alias libs.plugins.compose.compiler alias libs.plugins.android.application alias libs.plugins.kotlin.android alias libs.plugins.kotlin.parcelize @@ -48,6 +48,11 @@ def getGitCommitHash() { android { compileSdk = compile_sdk_version namespace = "io.legado.app" + + buildFeatures { + compose = true + } + kotlin { jvmToolchain { languageVersion.set(JavaLanguageVersion.of(21)) @@ -334,4 +339,42 @@ dependencies { implementation libs.androidx.palette implementation libs.androidx.core.splashscreen 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 + } diff --git a/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt b/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt index 433687efa..724ff870d 100644 --- a/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt +++ b/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt @@ -5,7 +5,11 @@ package io.legado.app.ui.about import android.os.Bundle import android.view.Menu import android.view.MenuItem +import androidx.activity.compose.setContent 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.lifecycle.lifecycleScope import io.legado.app.BuildConfig @@ -47,9 +51,7 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine -class AboutActivity : BaseActivity() { - - override val binding by viewBinding(ActivityAboutBinding::inflate) +class AboutActivity : AppCompatActivity() { private val waitDialog by lazy { WaitDialog(this).setText(R.string.checking_update) @@ -57,96 +59,36 @@ class AboutActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setSupportActionBar(binding.topBar) - binding.tvVersion.text = appInfo.versionName - - binding.btnUpdate.setOnClickListener { - checkUpdate() - } - - 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() + setContent { + MaterialTheme { + AboutScreen( + onCheckUpdate = { checkUpdate() }, + onOpenUrl = { openUrl(it) }, + onShowMdFile = { title, file -> showMdFile(title, file) }, + onSaveLog = { saveLog() }, + onCreateHeapDump = { createHeapDump() }, + onShowCrashLogs = { showDialogFragment() } + ) } } - - binding.llPrivacyPolicy.setOnClickListener { - 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() - } - - binding.llSaveLog.setOnClickListener { - saveLog() - } - - binding.llCreateHeapDump.setOnClickListener { - createHeapDump() - } } - override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { + override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.about, menu) - return super.onCompatCreateOptionsMenu(menu) + return true } - override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.menu_share_it -> share( - getString(R.string.app_share_description), - getString(R.string.app_name) - ) - } - return super.onCompatOptionsItemSelected(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) + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.menu_share_it -> { + share( + getString(R.string.app_share_description), + getString(R.string.app_name) + ) + true } + else -> super.onOptionsItemSelected(item) } } @@ -158,9 +100,9 @@ class AboutActivity : BaseActivity() { private fun checkUpdate() { waitDialog.show() AppUpdate.gitHubUpdate?.run { - check(lifecycleScope) + kotlin.check(lifecycleScope) .onSuccess { - showDialogFragment(UpdateDialog(it, UpdateDialog.Mode.UPDATE)) + showDialogFragment(UpdateDialog(it)) }.onError { appCtx.toastOnUi("${getString(R.string.check_update)}\n${it.localizedMessage}") }.onFinally { diff --git a/app/src/main/java/io/legado/app/ui/about/AboutScreen.kt b/app/src/main/java/io/legado/app/ui/about/AboutScreen.kt new file mode 100644 index 000000000..9f4e1e9b7 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/about/AboutScreen.kt @@ -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)) + } + } + } +} diff --git a/build.gradle b/build.gradle index 173d805a8..ffe9e7a16 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ plugins { //id 'com.google.devtools.ksp' version "$kotlin_version-$ksp_version" apply false //id "de.undercouch.download" version "5.5.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.library apply false alias libs.plugins.kotlin.android apply false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1c43df011..a2236bdb5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,12 @@ [versions] +accompanistWebview = "0.36.0" +coilCompose = "2.7.0" +composeBom = "2025.11.00" coreSplashscreen = "1.2.0-rc01" datastorePreferences = "1.1.7" kotlin = "2.2.20" +kotlinxCoroutinesAndroid = "1.8.1" ksp = "2.2.20-2.0.4" agp = "8.13.1" appcompat = "1.7.1" @@ -28,6 +32,7 @@ jsoup = "1.16.2" jsoupxpath = "2.5.3" coroutines = "1.10.2" liveeventbus = "1.8.14" +markdownCompose = "0.5.0" markwon = "4.6.2" material = "1.14.0-alpha06" media = "1.7.1" @@ -59,22 +64,31 @@ zxingLite = "3.3.0" [libraries] +accompanist-webview = { module = "com.google.accompanist:accompanist-webview", version.ref = "accompanistWebview" } activity-activity = { module = "androidx.activity:activity", version.ref = "activity" } activity-compose = { module = "androidx.activity:activity-compose", 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-experimental = { group = "androidx.annotation", name = "annotation-experimental", version = "1.3.1" } 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-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-startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "startupRuntime" } appcompat-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } 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-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" } +kotlinx-coroutines-android-v181 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" } libarchive = { module = "me.zhanghai.android.libarchive:library", version.ref = "libarchive" } lifecycle-common-java8 = { module = "androidx.lifecycle:lifecycle-common-java8", 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" } media3-datasource-okhttp = { module = "androidx.media3:media3-datasource-okhttp", 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" } +navigation-compose = { module = "androidx.navigation:navigation-compose" } preference-preference = { module = "androidx.preference:preference", version.ref = "preference" } preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preference" }