初步用Compose重构关于界面

增加了以后会使用的一些自定义Compose控件
尝试让机器人自动发布预发布版本
修复透明模式下搜索框"删除所有"文字不明显 #354
忙忙忙咩咩咩咩
This commit is contained in:
HapeLee
2025-11-22 02:35:11 +08:00
parent 158b472bef
commit 58893de5c2
13 changed files with 1018 additions and 176 deletions
+152 -58
View File
@@ -33,23 +33,67 @@ jobs:
runs-on: ubuntu-latest
if: ${{ !startsWith(github.event.head_commit.message, 'Merge pull request') }}
outputs:
version: ${{ steps.set-ver.outputs.version }}
versionL: ${{ steps.set-ver.outputs.versionL }}
lanzou: ${{ steps.check.outputs.lanzou }}
telegram: ${{ steps.check.outputs.telegram }}
# versionL: 完整版本号,例如 3.26.3-beta.1
versionL: ${{ steps.set-ver.outputs.full_version_name }}
# version: 基础版本号,例如 3.26.3
version: ${{ steps.set-ver.outputs.current_version }}
# build_number: 增量构建号,例如 1
build_number: ${{ steps.set-ver.outputs.build_number }}
# latest_tag: 最近一个用于版本递增的 tag,例如 v3.26.2-beta.5
latest_tag: ${{ steps.set-ver.outputs.last_tag }}
steps:
- id: set-ver
- uses: actions/checkout@v4
with:
# 确保拉取所有标签,用于版本号计算
fetch-depth: 0
- name: 计算版本号和构建号
id: set-ver
run: |
echo "version=$(date -d "8 hour" -u +3.%y.%m%d%H)" >> $GITHUB_OUTPUT
echo "versionL=$(date -d "8 hour" -u +3.%y.%m%d%H%M)" >> $GITHUB_OUTPUT
- id: check
run: |
if [ ${{ secrets.LANZOU_ID }} ]; then
echo "lanzou=yes" >> $GITHUB_OUTPUT
fi
if [ ${{ secrets.BOT_TOKEN }} ]; then
echo "telegram=yes" >> $GITHUB_OUTPUT
# 1. 读取 version.properties 文件获取基础版本号
MAJOR=$(grep 'VERSION_MAJOR' version.properties | cut -d'=' -f2)
MINOR=$(grep 'VERSION_MINOR' version.properties | cut -d'=' -f2)
PATCH=$(grep 'VERSION_PATCH' version.properties | cut -d'=' -f2)
CURRENT_VERSION="$MAJOR.$MINOR.$PATCH"
# 2. 尝试获取最近一次成功的构建号 (beta build number)
# 查找最近的、格式为 vX.Y.Z-beta.N 的标签,且 X.Y.Z 部分与当前版本匹配
# 只有在推送到 main 分支时才进行自增计算
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then
LAST_TAG=$(git tag --list "v*.*.*-beta.*" --sort=-committerdate | head -n 1)
# 查找匹配当前 CURRENT_VERSION 的上一个 TAG
LAST_MATCHING_TAG=$(git tag --list "v$CURRENT_VERSION-beta.*" --sort=-committerdate | head -n 1)
if [[ $LAST_MATCHING_TAG =~ v[0-9]+\.[0-9]+\.[0-9]+-beta\.([0-9]+) ]]; then
# 如果找到匹配当前 PATCH 版本的 beta 标签,则递增
LAST_BUILD_NUM="${BASH_REMATCH[1]}"
NEW_BUILD_NUM=$((LAST_BUILD_NUM + 1))
LAST_TAG_FOR_LOG="$LAST_MATCHING_TAG"
echo "版本 $CURRENT_VERSION 未变更,构建号自增:$LAST_BUILD_NUM -> $NEW_BUILD_NUM"
else
# 如果找不到匹配的标签 (可能是 PATCH 刚修改,或首次构建),则重置为 1
NEW_BUILD_NUM=1
# 如果是 PATCH 首次构建,则使用仓库中最近的那个 tag 作为日志起点
LAST_TAG_FOR_LOG="$LAST_TAG"
echo "版本 $CURRENT_VERSION 为新版本或首次构建,构建号重置为 $NEW_BUILD_NUM"
fi
else
# 对于 PR 或其他分支,使用一个固定的构建号 0
NEW_BUILD_NUM=0
LAST_TAG_FOR_LOG=$(git tag --sort=-committerdate | head -n 1)
echo "非 main 分支推送,构建号设置为 $NEW_BUILD_NUM"
fi
# 3. 构造最终版本信息
VERSION_SUFFIX_NAME="-beta.$NEW_BUILD_NUM"
FULL_VERSION_NAME="${CURRENT_VERSION}${VERSION_SUFFIX_NAME}"
# 4. 设置输出供后续作业使用
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
echo "full_version_name=$FULL_VERSION_NAME" >> $GITHUB_OUTPUT
echo "build_number=$NEW_BUILD_NUM" >> $GITHUB_OUTPUT
echo "last_tag=$LAST_TAG_FOR_LOG" >> $GITHUB_OUTPUT
build:
needs: prepare
@@ -62,8 +106,10 @@ jobs:
env:
product: ${{ matrix.product }}
type: ${{ matrix.type }}
# 使用 prepare job 的输出
VERSION: ${{ needs.prepare.outputs.version }}
VERSIONL: ${{ needs.prepare.outputs.versionL }}
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
steps:
- uses: actions/checkout@v5
with:
@@ -88,12 +134,24 @@ jobs:
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: 统一版本号
run: |
# 1. 临时修改 version.properties 中的 VERSION_SUFFIX,供 build.gradle 读取 versionName
echo "VERSION_SUFFIX=-beta.${{ env.BUILD_NUMBER }}" >> $GITHUB_WORKSPACE/version.properties
# 2. 覆盖 build.gradle 中复杂的 versionCode 计算逻辑
# 将 versionCode 设为 10000 + BUILD_NUMBER,忽略 gitCommits
# 替换 app/build.gradle 中 versionCode 所在行
sed -i "s/versionCode 10000 + gitCommits/versionCode 10000 + ${{ env.BUILD_NUMBER }}/" $GITHUB_WORKSPACE/app/build.gradle
echo "versionName 已设置为 ${{ env.VERSIONL }}"
echo "versionCode 已设置为 10000 + ${{ env.BUILD_NUMBER }}"
- name: Build With Gradle
run: |
echo "统一版本号"
sed -i "s/ext.versionName = .*/ext.versionName = \"${{ env.VERSION }}\"/" $GITHUB_WORKSPACE/app/build.gradle
echo "开始${{ env.product }}${{ env.type }}构建"
chmod +x gradlew
# Gradle 现在会读取修改后的 version.properties 来获取 versionName
./gradlew assemble${{ env.product }}Release
- name: Move Missing Rules Files
@@ -145,31 +203,6 @@ jobs:
if-no-files-found: ignore
path: ${{ github.workspace }}/mapping/mapping.txt
lanzou:
needs: [ prepare, build ]
if: ${{ github.event_name != 'pull_request' && needs.prepare.outputs.lanzou == 'yes' }}
runs-on: ubuntu-latest
env:
# 登录蓝奏云后在控制台运行document.cookie
ylogin: ${{ secrets.LANZOU_ID }}
phpdisk_info: ${{ secrets.LANZOU_PSD }}
# 蓝奏云里的文件夹ID(阅读3测试版:2670621
LANZOU_FOLDER_ID: ${{ secrets.LANZOU_FOLDER_ID }}
#蓝奏云分享链接
LANZOU_URL: ${{ secrets.LANZOU_URL }}
steps:
- uses: actions/checkout@v5
- uses: actions/download-artifact@v5
with:
path: apk/
- working-directory: apk/
run: mv */*.apk . ;rm -rf */
- name: Upload To Lanzou
continue-on-error: true
run: |
path="$GITHUB_WORKSPACE/apk/"
python3 $GITHUB_WORKSPACE/.github/scripts/lzy_web.py "$path" "$LANZOU_FOLDER_ID"
echo "[$(date -u -d '+8 hour' '+%Y.%m.%d %H:%M:%S')] 分享链接: $LANZOU_URL"
test_Branch:
needs: [ prepare, build ]
@@ -194,26 +227,87 @@ jobs:
git commit -m "${{ needs.prepare.outputs.versionL }}"
git push -f -u origin test
telegram:
create_pre_release:
needs: [ prepare, build ]
if: ${{ github.event_name != 'pull_request' && needs.prepare.outputs.telegram == 'yes' }}
if: |
github.event_name == 'push' &&
github.ref == 'refs/heads/main' &&
needs.prepare.outputs.build_number > 0 &&
success()
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/download-artifact@v5
- uses: actions/checkout@v4
with:
path: apk/
- working-directory: apk/
fetch-depth: 0
- name: 获取所有 Commit 消息作为 Release Body
id: get_commits
run: |
for file in `ls */*.apk`; do
mv "$file" "$(echo "$file"|sed -e 's#.*\/##g' -e "s/_/ /g" -e 's/legado/阅读/')"
done
rm -rf */
- name: Post to channel
uses: xireiki/channel-post@v1
# 获取从上一个 tag 到当前 commit 之间的所有 commit 消息
# 如果 latest_tag 为空,则从第一个 commit 开始获取
LATEST_TAG="${{ needs.prepare.outputs.latest_tag }}"
if [ -z "$LATEST_TAG" ]; then
# 如果没有找到任何 tag,则从第一个 commit 开始
COMMIT_LOG=$(git log --pretty=format:'* %s (%an)' --reverse)
else
# 从最近的 tag 到当前 HEAD
# 使用 ^$LATEST_TAG 来排除 LATEST_TAG 本身的 commit
COMMIT_LOG=$(git log $LATEST_TAG..HEAD --pretty=format:'* %s (%an)')
fi
# 检查是否获取到日志
if [ -z "$COMMIT_LOG" ]; then
RELEASE_BODY="本次发布无新增 Commit。"
else
RELEASE_BODY="# 本次更新内容\n\n$COMMIT_LOG"
fi
# 输出给下一步使用
echo "release_body<<EOF" >> $GITHUB_OUTPUT
echo "$RELEASE_BODY" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name:
创建 Pre-Release Tag: v${{ needs.prepare.outputs.versionL }}
# 创建一个 Tag,供下一轮计算版本号时使用
run: |
git tag v${{ needs.prepare.outputs.versionL }}
git push origin v${{ needs.prepare.outputs.versionL }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: 创建 Pre-Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
chat_id: ${{ secrets.CHANNEL_ID }}
bot_token: ${{ secrets.BOT_TOKEN }}
context: "#阅读 #Legado #Beta ${{ needs.prepare.outputs.versionL }}"
path: apk/*
method: sendFile
# Tag 名称:3.26.3-beta.1
tag_name: ${{ needs.prepare.outputs.versionL }}
# Release 标题:3.26.3-beta.1
release_name: ${{ needs.prepare.outputs.versionL }}
# Release 内容为所有 Commit 消息
body: ${{ steps.get_commits.outputs.release_body }}
draft: false
# 标记为预发布版本
prerelease: true
- name: 下载所有 APK Artifacts
uses: actions/download-artifact@v4
with:
pattern: '*-apk'
path: artifacts
merge-multiple: true
- name: 上传 Release Assets
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: artifacts/*.apk
# 重新命名上传的 APK 文件,使其包含版本号
asset_name: legado-${{ needs.prepare.outputs.versionL }}.apk
asset_content_type: application/zip
+11 -5
View File
@@ -356,9 +356,6 @@ dependencies {
// Activity
implementation libs.androidx.activity.compose
// Navigation(用于弹框或未来扩展)
implementation libs.navigation.compose
// Coil
implementation libs.coil.compose
@@ -366,15 +363,24 @@ dependencies {
implementation libs.accompanist.webview
// Markdown 渲染(Compose MD
implementation libs.markdown.compose
// implementation libs.markdown.compose
// 协程
implementation libs.kotlinx.coroutines.android
implementation libs.androidx.lifecycle.viewmodel.compose
implementation libs.androidx.lifecycle.runtime.compose
implementation libs.androidx.lifecycle.viewmodel.compose
implementation libs.androidx.compose.animation
implementation libs.androidx.compose.foundation
implementation libs.androidx.constraintlayout.compose
implementation libs.androidx.compose.ui.viewbinding
implementation libs.androidx.navigation.compose
implementation libs.androidx.compose.material
implementation libs.compose.materialIcons
implementation(platform(libs.koin.bom))
implementation(libs.koin.core)
implementation(libs.koin.android)
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)
}
@@ -0,0 +1,148 @@
package io.legado.app.base
import android.graphics.BitmapFactory
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.core.graphics.drawable.toDrawable
import androidx.core.graphics.scale
import androidx.core.view.WindowCompat
import com.google.android.material.color.DynamicColors
import com.google.android.material.color.DynamicColorsOptions
import io.legado.app.R
import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey
import io.legado.app.constant.Theme
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ThemeConfig
import io.legado.app.lib.theme.primaryColor
import io.legado.app.utils.disableAutoFill
import io.legado.app.utils.fullScreen
import io.legado.app.utils.getPrefString
import io.legado.app.utils.observeEvent
import io.legado.app.utils.setStatusBarColorAuto
import io.legado.app.utils.themeColor
import io.legado.app.utils.windowSize
import java.io.File
abstract class BaseComposeActivity(
val fullScreen: Boolean = true,
private val toolBarTheme: Theme = Theme.Auto,
private val transparent: Boolean = false,
private val imageBg: Boolean = true
) : AppCompatActivity() {
@Composable
protected abstract fun Content()
override fun onCreate(savedInstanceState: Bundle?) {
initTheme()
window.decorView.disableAutoFill()
AppContextWrapper.applyLocaleAndFont(this)
super.onCreate(savedInstanceState)
// Compose 入口
setContent {
AppTheme {
Surface(color = MaterialTheme.colorScheme.background) {
Content()
}
}
}
setupSystemBar()
if (imageBg) {
upBackgroundImage()
}
observeLiveBus()
}
open fun setupSystemBar() {
WindowCompat.setDecorFitsSystemWindows(window, false)
if (fullScreen) fullScreen()
setStatusBarColorAuto(
themeColor(com.google.android.material.R.attr.colorSurface),
true,
fullScreen
)
}
open fun upBackgroundImage() {
try {
ThemeConfig.getBgImage(this, windowManager.windowSize)?.let {
window.setBackgroundDrawable(it.toDrawable(resources))
}
} catch (_: Exception) {}
}
open fun observeLiveBus() {
observeEvent<String>(EventBus.RECREATE) {
recreate()
}
}
open fun initTheme() {
when (getPrefString("app_theme", "0")) {
"0" -> {
DynamicColors.applyToActivitiesIfAvailable(application)
}
"1" -> setTheme(R.style.Theme_Base_GR)
"2" -> setTheme(R.style.Theme_Base_Lemon)
"3" -> setTheme(R.style.Theme_Base_WH)
"4" -> setTheme(R.style.Theme_Base_Elink)
"5" -> setTheme(R.style.Theme_Base_Sora)
"6" -> setTheme(R.style.Theme_Base_August)
"7" -> setTheme(R.style.Theme_Base_Carlotta)
"8" -> setTheme(R.style.Theme_Base_Koharu)
"9" -> setTheme(R.style.Theme_Base_Yuuka)
"10" -> setTheme(R.style.Theme_Base_Phoebe)
"11" -> setTheme(R.style.Theme_Base_Mujika)
"12" -> {
if (AppConfig.customMode == "accent")
setTheme(R.style.ThemeOverlay_WhiteBackground)
val colorImagePath = getPrefString(PreferKey.colorImage)
if (!colorImagePath.isNullOrBlank()) {
val file = File(colorImagePath)
if (file.exists()) {
val bitmap = BitmapFactory.decodeFile(file.absolutePath)
if (bitmap != null) {
val colorAccuracy = true
val targetWidth = if (colorAccuracy) (bitmap.width / 4).coerceAtMost(256) else 16
val targetHeight = if (colorAccuracy) (bitmap.height / 4).coerceAtMost(256) else 16
val scaledBitmap = bitmap.scale(targetWidth, targetHeight, false)
val options = DynamicColorsOptions.Builder()
.setContentBasedSource(scaledBitmap)
.build()
DynamicColors.applyToActivitiesIfAvailable(application, options)
bitmap.recycle()
}
}
}else{
DynamicColors.applyToActivitiesIfAvailable(
application,
DynamicColorsOptions.Builder()
.setContentBasedSource(application.primaryColor)
.build()
)
}
}
"13" -> setTheme(R.style.AppTheme_Transparent)
}
if (AppConfig.pureBlack)
setTheme(R.style.ThemeOverlay_PureBlack)
}
}
@@ -0,0 +1,37 @@
package io.legado.app.base
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.foundation.isSystemInDarkTheme
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val context = LocalContext.current
val colorScheme = when {
dynamicColor && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S ->
if (darkTheme) dynamicDarkColorScheme(context)
else dynamicLightColorScheme(context)
darkTheme -> darkColorScheme()
else -> lightColorScheme()
}
MaterialTheme(
colorScheme = colorScheme,
typography = AppTypography,
shapes = AppShapes,
content = content
)
}
// Material3 Typography
val AppTypography = Typography()
// Material3 Shapes
val AppShapes = Shapes()
@@ -1,7 +1,5 @@
package io.legado.app.ui.about
//import io.legado.app.lib.theme.accentColor
//import io.legado.app.lib.theme.filletBackground
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
@@ -9,21 +7,16 @@ 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.compose.runtime.Composable
import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope
import io.legado.app.BuildConfig
import io.legado.app.R
import io.legado.app.base.BaseActivity
import io.legado.app.constant.AppConst.appInfo
import io.legado.app.base.BaseComposeActivity
import io.legado.app.constant.AppLog
import io.legado.app.databinding.ActivityAboutBinding
import io.legado.app.help.CrashHandler
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.LocalConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.update.AppUpdate
import io.legado.app.help.update.AppUpdateGitHub
import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.ui.widget.dialog.WaitDialog
import io.legado.app.utils.FileDoc
@@ -40,18 +33,24 @@ import io.legado.app.utils.openUrl
import io.legado.app.utils.share
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.toastOnUi
import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import splitties.init.appCtx
import java.io.File
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class AboutActivity : AppCompatActivity() {
class AboutActivity : BaseComposeActivity() {
@Composable
override fun Content() {
AboutScreen(
onCheckUpdate = { checkUpdate() },
onOpenUrl = { openUrl(it) },
onShowMdFile = { title, file -> showMdFile(title, file) },
onSaveLog = { saveLog() },
onCreateHeapDump = { createHeapDump() },
onShowCrashLogs = { showDialogFragment<CrashLogsDialog>() },
)
}
private val waitDialog by lazy {
WaitDialog(this).setText(R.string.checking_update)
@@ -60,18 +59,6 @@ class AboutActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
AboutScreen(
onCheckUpdate = { checkUpdate() },
onOpenUrl = { openUrl(it) },
onShowMdFile = { title, file -> showMdFile(title, file) },
onSaveLog = { saveLog() },
onCreateHeapDump = { createHeapDump() },
onShowCrashLogs = { showDialogFragment<CrashLogsDialog>() }
)
}
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
@@ -100,9 +87,9 @@ class AboutActivity : AppCompatActivity() {
private fun checkUpdate() {
waitDialog.show()
AppUpdate.gitHubUpdate?.run {
kotlin.check(lifecycleScope)
check(lifecycleScope)
.onSuccess {
showDialogFragment(UpdateDialog(it))
showDialogFragment(UpdateDialog(it, UpdateDialog.Mode.UPDATE))
}.onError {
appCtx.toastOnUi("${getString(R.string.check_update)}\n${it.localizedMessage}")
}.onFinally {
@@ -119,17 +106,17 @@ class AboutActivity : AppCompatActivity() {
private fun saveLog() {
Coroutine.async {
val backupPath = AppConfig.backupPath ?: let {
appCtx.toastOnUi("未设置备份目录")
toastOnUi("未设置备份目录")
return@async
}
if (!AppConfig.recordLog) {
appCtx.toastOnUi("未开启日志记录,请去其他设置里打开记录日志")
toastOnUi("未开启日志记录,请去其他设置里打开记录日志")
delay(3000)
}
val doc = FileDoc.fromUri(backupPath.toUri(), true)
copyLogs(doc)
copyHeapDump(doc)
appCtx.toastOnUi("已保存至备份目录")
toastOnUi("已保存至备份目录")
}.onError {
AppLog.put("保存日志出错\n${it.localizedMessage}", it, true)
}
@@ -138,21 +125,21 @@ class AboutActivity : AppCompatActivity() {
private fun createHeapDump() {
Coroutine.async {
val backupPath = AppConfig.backupPath ?: let {
appCtx.toastOnUi("未设置备份目录")
toastOnUi("未设置备份目录")
return@async
}
if (!AppConfig.recordHeapDump) {
appCtx.toastOnUi("未开启堆转储记录,请去其他设置里打开记录堆转储")
toastOnUi("未开启堆转储记录,请去其他设置里打开记录堆转储")
delay(3000)
}
appCtx.toastOnUi("开始创建堆转储")
toastOnUi("开始创建堆转储")
System.gc()
CrashHandler.doHeapDump(true)
val doc = FileDoc.fromUri(backupPath.toUri(), true)
if (!copyHeapDump(doc)) {
appCtx.toastOnUi("未找到堆转储文件")
toastOnUi("未找到堆转储文件")
} else {
appCtx.toastOnUi("已保存至备份目录")
toastOnUi("已保存至备份目录")
}
}.onError {
AppLog.put("保存堆转储失败\n${it.localizedMessage}", it)
@@ -160,7 +147,7 @@ class AboutActivity : AppCompatActivity() {
}
private fun copyLogs(doc: FileDoc) {
val cacheDir = appCtx.externalCache
val cacheDir = externalCache
val logFiles = File(cacheDir, "logs")
val crashFiles = File(cacheDir, "crash")
val logcatFile = File(cacheDir, "logcat.txt")
@@ -182,7 +169,7 @@ class AboutActivity : AppCompatActivity() {
}
private fun copyHeapDump(doc: FileDoc): Boolean {
val heapFile = FileDoc.fromFile(File(appCtx.externalCache, "heapDump")).list()
val heapFile = FileDoc.fromFile(File(externalCache, "heapDump")).list()
?.firstOrNull() ?: return false
doc.find("heapDump")?.delete()
val heapDumpDoc = doc.createFolderIfNotExist("heapDump")
@@ -1,47 +1,147 @@
package io.legado.app.ui.about
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentWidth
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.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.ButtonGroup
import androidx.compose.material3.ButtonGroupDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme.colorScheme
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
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.base.AppTheme
import io.legado.app.constant.AppConst.appInfo
import io.legado.app.ui.widget.compose.SettingItem
import io.legado.app.ui.widget.compose.SplicedColumnGroup
import io.legado.app.ui.widget.compose.TextCard
@Preview(showBackground = true, name = "Light Mode")
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Dark Mode")
@Composable
fun PreviewAboutScreen() {
AppTheme {
AboutScreen(
versionName = "1.0",
onCheckUpdate = {},
onOpenUrl = {},
onShowMdFile = { _, _ -> },
onSaveLog = {},
onCreateHeapDump = {},
onShowCrashLogs = {}
)
}
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ButtonGroup(
onOpenUrl: (String) -> Unit,
onCheckUpdate: () -> Unit
) {
ButtonGroup(
modifier = Modifier.fillMaxWidth(),
overflowIndicator = { menuState ->
ButtonGroupDefaults.OverflowIndicator(menuState = menuState)
},
verticalAlignment = Alignment.CenterVertically
) {
clickableItem(
onClick = { onOpenUrl("https://example.com") },
weight = 1f,
icon = {
Icon(
painter = painterResource(R.drawable.ic_web_outline),
contentDescription = stringResource(R.string.back),
tint = colorScheme.onSurface
)
},
label = ""
)
clickableItem(
onClick = { onOpenUrl("https://github.com/HapeLee/legado-with-MD3") },
weight = 1f,
icon = {
Icon(
painter = painterResource(R.drawable.ic_github),
contentDescription = stringResource(R.string.back),
tint = colorScheme.onSurface
)
},
label = ""
)
clickableItem(
onClick = { onCheckUpdate() },
weight = 1f,
icon = {
Icon(
painter = painterResource(R.drawable.ic_import),
contentDescription = stringResource(R.string.back),
tint = colorScheme.onSurface
)
},
label = ""
)
}
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun AboutScreen(
versionName: String = appInfo.versionName,
onNavigateBack: () -> Unit = {},
onCheckUpdate: () -> Unit = {},
onOpenUrl: (String) -> Unit = {},
onShowMdFile: (String, String) -> Unit = { _, _ -> },
onSaveLog: () -> Unit = {},
onCreateHeapDump: () -> Unit = {},
onShowCrashLogs: () -> Unit = {},
versionName: String = "1.0.0"
onShowCrashLogs: () -> Unit = {}
) {
val context = LocalContext.current
val version = remember { appInfo.versionName }
LocalContext.current
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.about)) }
title = { Text(stringResource(R.string.about)) },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.back)
)
}
}
)
}
) { padding ->
@@ -51,57 +151,109 @@ fun AboutScreen(
.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
Image(
painter = painterResource(R.drawable.ic_launcher_foreground),
contentDescription = "",
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.CenterHorizontally)
)
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 = stringResource(R.string.app_name), style = MaterialTheme.typography.bodyLarge,
modifier = Modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.CenterHorizontally))
TextCard(
text = versionName,
backgroundColor = colorScheme.tertiaryContainer,
contentColor = colorScheme.onTertiaryContainer,
cornerRadius = 8.dp,
modifier = Modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.CenterHorizontally)
)
Text(
text = stringResource(R.string.about_description), style = MaterialTheme.typography.bodyLarge,
modifier = Modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.CenterHorizontally))
Row (modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center){
FilledTonalIconButton ( onClick = { onOpenUrl("https://example.com") } ) {
Icon(
painter = painterResource(R.drawable.ic_web_outline),
contentDescription = stringResource(R.string.back)
)
}
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))
FilledTonalIconButton (onClick = { onOpenUrl("https://github.com/HapeLee/legado-with-MD3") }) {
Icon(
painter = painterResource(R.drawable.ic_github),
contentDescription = stringResource(R.string.back)
)
}
FilledTonalIconButton (onClick = { onCheckUpdate }) {
Icon(
painter = painterResource(R.drawable.ic_import),
contentDescription = stringResource(R.string.back)
)
}
}
SplicedColumnGroup(
title = stringResource(R.string.about),
content = listOf(
{
SettingItem(
modifier = Modifier.background(colorScheme.surfaceBright),
title = stringResource(R.string.contributors),
description = "",
onClick = {
onOpenUrl("https://github.com/gedoor/legado/graphs/contributors")
}
)
SettingItem(
title = stringResource(R.string.privacy_policy),
description = "",
onClick = {
onShowMdFile("隐私政策", "privacyPolicy.md")
}
)
SettingItem(
title = stringResource(R.string.license),
description = "",
onClick = {
onShowMdFile("许可证", "LICENSE.md")
}
)
SettingItem(
title = stringResource(R.string.disclaimer),
description = "",
onClick = {
onShowMdFile("免责声明", "disclaimer.md")
}
)
SettingItem(
title = stringResource(R.string.crash_log),
description = "",
onClick = onShowCrashLogs
)
SettingItem(
title = stringResource(R.string.save_log),
description = "",
onClick = onSaveLog
)
SettingItem(
title = stringResource(R.string.create_heap_dump),
description = "",
onClick = onCreateHeapDump
)
}
)
)
}
}
}
}
@@ -0,0 +1,152 @@
package io.legado.app.ui.widget.compose
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.TextUnit
@Composable
fun AnimatedText(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
minLines: Int = 1,
onTextLayout: ((TextLayoutResult) -> Unit)? = null,
style: TextStyle = LocalTextStyle.current
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
for (i in text.indices) {
val char = text[i]
Box {
AnimatedContent(
targetState = char,
transitionSpec = {
(slideInVertically(initialOffsetY = { it })).togetherWith(
slideOutVertically(targetOffsetY = { -it })
)
},
label = ""
) {
Text(
style = style,
color = color,
softWrap = softWrap,
text = it.toString(),
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
maxLines = maxLines,
minLines = minLines,
onTextLayout = onTextLayout
)
}
}
}
}
}
/**
* 动画文本控件
*
* 区别于 AnimatedText, 该控件在文本变化时,提供整行的滑动动画效果
*/
@Composable
fun AnimatedTextLine(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
minLines: Int = 1,
onTextLayout: ((TextLayoutResult) -> Unit)? = null,
style: TextStyle = LocalTextStyle.current
) {
var currentText by remember { mutableStateOf(text) }
SideEffect { currentText = text }
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
AnimatedContent(
targetState = currentText,
transitionSpec = {
(slideInVertically(initialOffsetY = { it })).togetherWith(
slideOutVertically(targetOffsetY = { -it })
)
},
label = ""
) { text ->
Text(
text = text,
modifier = modifier,
style = style,
color = color,
softWrap = softWrap,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
maxLines = maxLines,
minLines = minLines,
onTextLayout = onTextLayout
)
}
}
}
@@ -0,0 +1,92 @@
package io.legado.app.ui.widget.compose
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import io.legado.app.base.AppTypography
@Composable
fun SettingItem(
modifier: Modifier = Modifier,
painter: Painter? = null,
title: String,
option: String? = null,
trailingContent: (@Composable () -> Unit)? = null,
description: String,
onClick: () -> Unit
) {
Row(
modifier = Modifier
.clip(RoundedCornerShape(4.dp))
.then(modifier)
.fillMaxWidth()
.clickable { onClick() }
.padding(horizontal = 8.dp)
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
painter?.let {
Icon(
modifier = Modifier.padding(end = 8.dp).size(24.dp),
painter = it,
tint = colorScheme.onSurfaceVariant,
contentDescription = "Icon"
)
}
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(3.dp)
) {
Text(
text = title,
color = colorScheme.onSurface,
style = AppTypography.titleMedium,
fontWeight = FontWeight.Normal
)
Text(
text = description,
color = colorScheme.onSurfaceVariant,
style = AppTypography.labelMedium
)
option?.let {
AnimatedText(
text = it,
style = AppTypography.labelMedium,
color = colorScheme.primary,
maxLines = 3,
overflow = TextOverflow.Ellipsis
)
}
}
trailingContent?.let { composable ->
Box(
modifier = Modifier.fillMaxHeight()
.width(55.dp),
contentAlignment = Alignment.Center
) {
composable()
}
}
}
}
@@ -0,0 +1,84 @@
package io.legado.app.ui.widget.compose
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
/**
* Settings Group Container by https://github.com/wxxsfxyzm/InstallerX-Revived
*
* @param title the title of the group
* @param content a list of composable that will be displayed in the group
* @param modifier Modifier
*/
@Composable
fun SplicedColumnGroup(
modifier: Modifier = Modifier,
title: String = "",
content: List<@Composable () -> Unit>,
) {
if (content.isEmpty()) return
val cornerRadius = 16.dp
val connectionRadius = 5.dp
// Define shapes for different positions.
val topShape = RoundedCornerShape(
topStart = cornerRadius,
topEnd = cornerRadius,
bottomStart = connectionRadius,
bottomEnd = connectionRadius
)
val middleShape = RoundedCornerShape(connectionRadius)
val bottomShape = RoundedCornerShape(
topStart = connectionRadius,
topEnd = connectionRadius,
bottomStart = cornerRadius,
bottomEnd = cornerRadius
)
val singleShape = RoundedCornerShape(cornerRadius)
Column(modifier = modifier.padding(vertical = 8.dp)) {
// Group title
if (title != "")
Text(
text = title,
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(start = 16.dp, bottom = 8.dp)
)
// The container for setting items.
Column(
modifier = Modifier.clip(
// Clip the whole column to ensure content stays within the rounded bounds.
if (content.size == 1) singleShape else RoundedCornerShape(cornerRadius)
),
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
content.forEachIndexed { index, itemContent ->
// Determine the shape based on the pkg's position.
val shape = when {
content.size == 1 -> singleShape
index == 0 -> topShape
index == content.size - 1 -> bottomShape
else -> middleShape
}
// Apply background with the correct shape to the pkg.
Column(
modifier = Modifier.background(MaterialTheme.colorScheme.surfaceBright, shape)
) {
itemContent()
}
}
}
}
}
@@ -0,0 +1,67 @@
package io.legado.app.ui.widget.compose
import androidx.compose.ui.graphics.Color
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Preview(showBackground = true)
@Composable
fun PreviewTextCard() {
MaterialTheme {
TextCard(
text = "v1.0.0"
)
}
}
@Composable
fun TextCard(
text: String,
modifier: Modifier = Modifier,
backgroundColor: Color = colorScheme.tertiaryContainer,
contentColor: Color = colorScheme.onTertiaryContainer,
cornerRadius: Dp = 12.dp,
paddingHorizontal: Dp = 8.dp,
paddingVertical: Dp = 0.dp,
textSize: TextUnit = 10.sp,
bold: Boolean = true,
onClick: (() -> Unit)? = null,
) {
Card(
modifier = modifier
.then(
if (onClick != null) Modifier.clickable { onClick() }
else Modifier
),
colors = CardDefaults.cardColors(
containerColor = backgroundColor,
contentColor = contentColor
),
shape = RoundedCornerShape(cornerRadius),
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp),
) {
Text(
text = text,
modifier = Modifier.padding(
horizontal = paddingHorizontal,
vertical = paddingVertical
),
fontSize = textSize,
fontWeight = if (bold) FontWeight.Bold else FontWeight.Normal
)
}
}
@@ -191,13 +191,13 @@
android:id="@+id/tv_clear_history"
style="@style/Widget.Material3Expressive.Button"
android:text="@string/delete_all"
app:backgroundTint="?attr/colorTertiary"
app:iconTint="?attr/colorOnTertiary"
android:textColor="?attr/colorOnTertiary"
app:backgroundTint="?attr/colorSecondaryContainer"
app:iconTint="?attr/colorOnSecondaryContainer"
android:textColor="?attr/colorOnSecondaryContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/ic_clear_all"
app:strokeColor="?attr/colorTertiary"
app:strokeColor="?attr/colorSecondaryContainer"
app:iconGravity="textStart" />
</LinearLayout>
+1 -1
View File
@@ -1,4 +1,4 @@
VERSION_MAJOR=3
VERSION_MINOR=26
VERSION_PATCH=3
VERSION_PATCH=4
VERSION_SUFFIX=
+33 -10
View File
@@ -1,13 +1,16 @@
[versions]
accompanistWebview = "0.36.0"
animation = "1.9.4"
coilCompose = "2.7.0"
composeBom = "2025.11.00"
coreSplashscreen = "1.2.0-rc01"
composeBom = "2025.11.01"
constraintlayoutCompose = "1.1.1"
coreSplashscreen = "1.2.0"
datastorePreferences = "1.1.7"
kotlin = "2.2.20"
kotlinxCoroutinesAndroid = "1.8.1"
ksp = "2.2.20-2.0.4"
foundation = "1.9.4"
kotlin = "2.2.21"
kotlinxCoroutinesAndroid = "1.10.2"
ksp = "2.2.21-2.0.4"
agp = "8.13.1"
appcompat = "1.7.1"
colorpicker = "1.1.0"
@@ -22,7 +25,7 @@ fragment = "1.8.9"
hutool = "5.8.22"
libarchive = "1.1.6"
lifecycle = "2.9.4"
lifecycle = "2.10.0"
glide = "5.0.5"
gson = "2.13.2"
jsonPath = "2.9.0"
@@ -31,13 +34,18 @@ jsonPath = "2.9.0"
jsoup = "1.16.2"
jsoupxpath = "2.5.3"
coroutines = "1.10.2"
lifecycleViewmodelCompose = "2.10.0"
liveeventbus = "1.8.14"
markdownCompose = "0.5.0"
markwon = "4.6.2"
material = "1.14.0-alpha06"
material = "1.14.0-alpha07"
material3 = "1.5.0-alpha09"
material3IconsExtended = "1.2.0"
materialIconsExtended = "1.7.8"
media = "1.7.1"
media3 = "1.8.0"
nanoHttpd = "2.3.1"
navigationCompose = "2.9.6"
okhttp = "5.2.0"
palette = "1.0.0"
preference = "1.2.1"
@@ -55,13 +63,14 @@ swiperefreshlayout = "1.1.0"
#noinspection GradleDependency
recyclerview = "1.4.0"
#noinspection GradleDependency
uiViewbinding = "1.9.4"
viewpager2 = "1.1.0"
webkit = "1.14.0"
collection = "1.5.0"
zxingLite = "3.3.0"
koin-bom = "4.1.1"
[libraries]
accompanist-webview = { module = "com.google.accompanist:accompanist-webview", version.ref = "accompanistWebview" }
@@ -74,19 +83,33 @@ androidx-annotation = { group = "androidx.annotation", name = "annotation", vers
#androidx-annotation-experimental = { group = "androidx.annotation", name = "annotation-experimental", version = "1.3.1" }
androidx-collection = { module = "androidx.collection:collection", version.ref = "collection" }
androidx-compose-animation = { module = "androidx.compose.animation:animation", version.ref = "animation" }
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
androidx-compose-material3 = { module = "androidx.compose.material3:material3" }
androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "foundation" }
androidx-compose-material = { module = "androidx.compose.material:material" }
compose-materialIcons = { group = "androidx.compose.material", name = "material-icons-extended", version = "1.7.8" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
androidx-compose-material3-icons-extended = { module = "androidx.compose.material3:material3-icons-extended", version.ref = "material3IconsExtended" }
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-compose-ui-viewbinding = { module = "androidx.compose.ui:ui-viewbinding", version.ref = "uiViewbinding" }
androidx-constraintlayout-compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "constraintlayoutCompose" }
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-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
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" }
koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin-bom" }
koin-core = { module = "io.insert-koin:koin-core" }
koin-android = { group = "io.insert-koin", name = "koin-android" }
koin-compose = { group = "io.insert-koin", name = "koin-androidx-compose" }
koin-compose-viewmodel = { group = "io.insert-koin", name = "koin-compose-viewmodel" }
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }
core-core = { module = "androidx.core:core", version.ref = "core" }
@@ -194,7 +217,7 @@ coroutines = ["kotlinx-coroutines-core", "kotlinx-coroutines-android"]
androidTest = ["androidx-espresso-core", "androidx-junit", "androidx-runner"]
[plugins]
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
android-test = { id = "com.android.test", version.ref = "agp" }