初步用Compose重构关于界面
增加了以后会使用的一些自定义Compose控件 尝试让机器人自动发布预发布版本 修复透明模式下搜索框"删除所有"文字不明显 #354 忙忙忙咩咩咩咩
This commit is contained in:
+152
-58
@@ -33,23 +33,67 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ !startsWith(github.event.head_commit.message, 'Merge pull request') }}
|
if: ${{ !startsWith(github.event.head_commit.message, 'Merge pull request') }}
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.set-ver.outputs.version }}
|
# versionL: 完整版本号,例如 3.26.3-beta.1
|
||||||
versionL: ${{ steps.set-ver.outputs.versionL }}
|
versionL: ${{ steps.set-ver.outputs.full_version_name }}
|
||||||
lanzou: ${{ steps.check.outputs.lanzou }}
|
# version: 基础版本号,例如 3.26.3
|
||||||
telegram: ${{ steps.check.outputs.telegram }}
|
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:
|
steps:
|
||||||
- id: set-ver
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
# 确保拉取所有标签,用于版本号计算
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: 计算版本号和构建号
|
||||||
|
id: set-ver
|
||||||
run: |
|
run: |
|
||||||
echo "version=$(date -d "8 hour" -u +3.%y.%m%d%H)" >> $GITHUB_OUTPUT
|
# 1. 读取 version.properties 文件获取基础版本号
|
||||||
echo "versionL=$(date -d "8 hour" -u +3.%y.%m%d%H%M)" >> $GITHUB_OUTPUT
|
MAJOR=$(grep 'VERSION_MAJOR' version.properties | cut -d'=' -f2)
|
||||||
- id: check
|
MINOR=$(grep 'VERSION_MINOR' version.properties | cut -d'=' -f2)
|
||||||
run: |
|
PATCH=$(grep 'VERSION_PATCH' version.properties | cut -d'=' -f2)
|
||||||
if [ ${{ secrets.LANZOU_ID }} ]; then
|
CURRENT_VERSION="$MAJOR.$MINOR.$PATCH"
|
||||||
echo "lanzou=yes" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
# 2. 尝试获取最近一次成功的构建号 (beta build number)
|
||||||
if [ ${{ secrets.BOT_TOKEN }} ]; then
|
# 查找最近的、格式为 vX.Y.Z-beta.N 的标签,且 X.Y.Z 部分与当前版本匹配
|
||||||
echo "telegram=yes" >> $GITHUB_OUTPUT
|
# 只有在推送到 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
|
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:
|
build:
|
||||||
needs: prepare
|
needs: prepare
|
||||||
@@ -62,8 +106,10 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
product: ${{ matrix.product }}
|
product: ${{ matrix.product }}
|
||||||
type: ${{ matrix.type }}
|
type: ${{ matrix.type }}
|
||||||
|
# 使用 prepare job 的输出
|
||||||
VERSION: ${{ needs.prepare.outputs.version }}
|
VERSION: ${{ needs.prepare.outputs.version }}
|
||||||
VERSIONL: ${{ needs.prepare.outputs.versionL }}
|
VERSIONL: ${{ needs.prepare.outputs.versionL }}
|
||||||
|
BUILD_NUMBER: ${{ needs.prepare.outputs.build_number }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
@@ -88,12 +134,24 @@ jobs:
|
|||||||
- name: Set up Gradle
|
- name: Set up Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
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
|
- name: Build With Gradle
|
||||||
run: |
|
run: |
|
||||||
echo "统一版本号"
|
|
||||||
sed -i "s/ext.versionName = .*/ext.versionName = \"${{ env.VERSION }}\"/" $GITHUB_WORKSPACE/app/build.gradle
|
|
||||||
echo "开始${{ env.product }}${{ env.type }}构建"
|
echo "开始${{ env.product }}${{ env.type }}构建"
|
||||||
chmod +x gradlew
|
chmod +x gradlew
|
||||||
|
# Gradle 现在会读取修改后的 version.properties 来获取 versionName
|
||||||
./gradlew assemble${{ env.product }}Release
|
./gradlew assemble${{ env.product }}Release
|
||||||
|
|
||||||
- name: Move Missing Rules Files
|
- name: Move Missing Rules Files
|
||||||
@@ -145,31 +203,6 @@ jobs:
|
|||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
path: ${{ github.workspace }}/mapping/mapping.txt
|
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:
|
test_Branch:
|
||||||
needs: [ prepare, build ]
|
needs: [ prepare, build ]
|
||||||
@@ -194,26 +227,87 @@ jobs:
|
|||||||
git commit -m "${{ needs.prepare.outputs.versionL }}"
|
git commit -m "${{ needs.prepare.outputs.versionL }}"
|
||||||
git push -f -u origin test
|
git push -f -u origin test
|
||||||
|
|
||||||
telegram:
|
create_pre_release:
|
||||||
needs: [ prepare, build ]
|
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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/download-artifact@v5
|
|
||||||
with:
|
with:
|
||||||
path: apk/
|
fetch-depth: 0
|
||||||
- working-directory: apk/
|
|
||||||
|
- name: 获取所有 Commit 消息作为 Release Body
|
||||||
|
id: get_commits
|
||||||
run: |
|
run: |
|
||||||
for file in `ls */*.apk`; do
|
# 获取从上一个 tag 到当前 commit 之间的所有 commit 消息
|
||||||
mv "$file" "$(echo "$file"|sed -e 's#.*\/##g' -e "s/_/ /g" -e 's/legado/阅读/')"
|
# 如果 latest_tag 为空,则从第一个 commit 开始获取
|
||||||
done
|
|
||||||
rm -rf */
|
LATEST_TAG="${{ needs.prepare.outputs.latest_tag }}"
|
||||||
- name: Post to channel
|
|
||||||
uses: xireiki/channel-post@v1
|
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:
|
with:
|
||||||
chat_id: ${{ secrets.CHANNEL_ID }}
|
# Tag 名称:3.26.3-beta.1
|
||||||
bot_token: ${{ secrets.BOT_TOKEN }}
|
tag_name: ${{ needs.prepare.outputs.versionL }}
|
||||||
context: "#阅读 #Legado #Beta ${{ needs.prepare.outputs.versionL }}"
|
# Release 标题:3.26.3-beta.1
|
||||||
path: apk/*
|
release_name: ${{ needs.prepare.outputs.versionL }}
|
||||||
method: sendFile
|
# 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
@@ -356,9 +356,6 @@ dependencies {
|
|||||||
// Activity
|
// Activity
|
||||||
implementation libs.androidx.activity.compose
|
implementation libs.androidx.activity.compose
|
||||||
|
|
||||||
// Navigation(用于弹框或未来扩展)
|
|
||||||
implementation libs.navigation.compose
|
|
||||||
|
|
||||||
// Coil
|
// Coil
|
||||||
implementation libs.coil.compose
|
implementation libs.coil.compose
|
||||||
|
|
||||||
@@ -366,15 +363,24 @@ dependencies {
|
|||||||
implementation libs.accompanist.webview
|
implementation libs.accompanist.webview
|
||||||
|
|
||||||
// Markdown 渲染(Compose MD)
|
// Markdown 渲染(Compose MD)
|
||||||
implementation libs.markdown.compose
|
// implementation libs.markdown.compose
|
||||||
|
|
||||||
// 协程
|
// 协程
|
||||||
implementation libs.kotlinx.coroutines.android
|
implementation libs.kotlinx.coroutines.android
|
||||||
implementation libs.androidx.lifecycle.viewmodel.compose
|
|
||||||
implementation libs.androidx.lifecycle.runtime.compose
|
implementation libs.androidx.lifecycle.runtime.compose
|
||||||
|
implementation libs.androidx.lifecycle.viewmodel.compose
|
||||||
implementation libs.androidx.compose.animation
|
implementation libs.androidx.compose.animation
|
||||||
implementation libs.androidx.compose.foundation
|
implementation libs.androidx.compose.foundation
|
||||||
implementation libs.androidx.constraintlayout.compose
|
implementation libs.androidx.constraintlayout.compose
|
||||||
implementation libs.androidx.compose.ui.viewbinding
|
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
|
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.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
@@ -9,21 +7,16 @@ import androidx.activity.compose.setContent
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.core.app.ComponentActivity
|
import androidx.compose.runtime.Composable
|
||||||
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.R
|
import io.legado.app.R
|
||||||
import io.legado.app.base.BaseActivity
|
import io.legado.app.base.BaseComposeActivity
|
||||||
import io.legado.app.constant.AppConst.appInfo
|
|
||||||
import io.legado.app.constant.AppLog
|
import io.legado.app.constant.AppLog
|
||||||
import io.legado.app.databinding.ActivityAboutBinding
|
|
||||||
import io.legado.app.help.CrashHandler
|
import io.legado.app.help.CrashHandler
|
||||||
import io.legado.app.help.config.AppConfig
|
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.coroutine.Coroutine
|
||||||
import io.legado.app.help.update.AppUpdate
|
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.TextDialog
|
||||||
import io.legado.app.ui.widget.dialog.WaitDialog
|
import io.legado.app.ui.widget.dialog.WaitDialog
|
||||||
import io.legado.app.utils.FileDoc
|
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.share
|
||||||
import io.legado.app.utils.showDialogFragment
|
import io.legado.app.utils.showDialogFragment
|
||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import java.io.File
|
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 {
|
private val waitDialog by lazy {
|
||||||
WaitDialog(this).setText(R.string.checking_update)
|
WaitDialog(this).setText(R.string.checking_update)
|
||||||
@@ -60,18 +59,6 @@ class AboutActivity : AppCompatActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
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 {
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||||
@@ -100,9 +87,9 @@ class AboutActivity : AppCompatActivity() {
|
|||||||
private fun checkUpdate() {
|
private fun checkUpdate() {
|
||||||
waitDialog.show()
|
waitDialog.show()
|
||||||
AppUpdate.gitHubUpdate?.run {
|
AppUpdate.gitHubUpdate?.run {
|
||||||
kotlin.check(lifecycleScope)
|
check(lifecycleScope)
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
showDialogFragment(UpdateDialog(it))
|
showDialogFragment(UpdateDialog(it, UpdateDialog.Mode.UPDATE))
|
||||||
}.onError {
|
}.onError {
|
||||||
appCtx.toastOnUi("${getString(R.string.check_update)}\n${it.localizedMessage}")
|
appCtx.toastOnUi("${getString(R.string.check_update)}\n${it.localizedMessage}")
|
||||||
}.onFinally {
|
}.onFinally {
|
||||||
@@ -119,17 +106,17 @@ class AboutActivity : AppCompatActivity() {
|
|||||||
private fun saveLog() {
|
private fun saveLog() {
|
||||||
Coroutine.async {
|
Coroutine.async {
|
||||||
val backupPath = AppConfig.backupPath ?: let {
|
val backupPath = AppConfig.backupPath ?: let {
|
||||||
appCtx.toastOnUi("未设置备份目录")
|
toastOnUi("未设置备份目录")
|
||||||
return@async
|
return@async
|
||||||
}
|
}
|
||||||
if (!AppConfig.recordLog) {
|
if (!AppConfig.recordLog) {
|
||||||
appCtx.toastOnUi("未开启日志记录,请去其他设置里打开记录日志")
|
toastOnUi("未开启日志记录,请去其他设置里打开记录日志")
|
||||||
delay(3000)
|
delay(3000)
|
||||||
}
|
}
|
||||||
val doc = FileDoc.fromUri(backupPath.toUri(), true)
|
val doc = FileDoc.fromUri(backupPath.toUri(), true)
|
||||||
copyLogs(doc)
|
copyLogs(doc)
|
||||||
copyHeapDump(doc)
|
copyHeapDump(doc)
|
||||||
appCtx.toastOnUi("已保存至备份目录")
|
toastOnUi("已保存至备份目录")
|
||||||
}.onError {
|
}.onError {
|
||||||
AppLog.put("保存日志出错\n${it.localizedMessage}", it, true)
|
AppLog.put("保存日志出错\n${it.localizedMessage}", it, true)
|
||||||
}
|
}
|
||||||
@@ -138,21 +125,21 @@ class AboutActivity : AppCompatActivity() {
|
|||||||
private fun createHeapDump() {
|
private fun createHeapDump() {
|
||||||
Coroutine.async {
|
Coroutine.async {
|
||||||
val backupPath = AppConfig.backupPath ?: let {
|
val backupPath = AppConfig.backupPath ?: let {
|
||||||
appCtx.toastOnUi("未设置备份目录")
|
toastOnUi("未设置备份目录")
|
||||||
return@async
|
return@async
|
||||||
}
|
}
|
||||||
if (!AppConfig.recordHeapDump) {
|
if (!AppConfig.recordHeapDump) {
|
||||||
appCtx.toastOnUi("未开启堆转储记录,请去其他设置里打开记录堆转储")
|
toastOnUi("未开启堆转储记录,请去其他设置里打开记录堆转储")
|
||||||
delay(3000)
|
delay(3000)
|
||||||
}
|
}
|
||||||
appCtx.toastOnUi("开始创建堆转储")
|
toastOnUi("开始创建堆转储")
|
||||||
System.gc()
|
System.gc()
|
||||||
CrashHandler.doHeapDump(true)
|
CrashHandler.doHeapDump(true)
|
||||||
val doc = FileDoc.fromUri(backupPath.toUri(), true)
|
val doc = FileDoc.fromUri(backupPath.toUri(), true)
|
||||||
if (!copyHeapDump(doc)) {
|
if (!copyHeapDump(doc)) {
|
||||||
appCtx.toastOnUi("未找到堆转储文件")
|
toastOnUi("未找到堆转储文件")
|
||||||
} else {
|
} else {
|
||||||
appCtx.toastOnUi("已保存至备份目录")
|
toastOnUi("已保存至备份目录")
|
||||||
}
|
}
|
||||||
}.onError {
|
}.onError {
|
||||||
AppLog.put("保存堆转储失败\n${it.localizedMessage}", it)
|
AppLog.put("保存堆转储失败\n${it.localizedMessage}", it)
|
||||||
@@ -160,7 +147,7 @@ class AboutActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun copyLogs(doc: FileDoc) {
|
private fun copyLogs(doc: FileDoc) {
|
||||||
val cacheDir = appCtx.externalCache
|
val cacheDir = externalCache
|
||||||
val logFiles = File(cacheDir, "logs")
|
val logFiles = File(cacheDir, "logs")
|
||||||
val crashFiles = File(cacheDir, "crash")
|
val crashFiles = File(cacheDir, "crash")
|
||||||
val logcatFile = File(cacheDir, "logcat.txt")
|
val logcatFile = File(cacheDir, "logcat.txt")
|
||||||
@@ -182,7 +169,7 @@ class AboutActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun copyHeapDump(doc: FileDoc): Boolean {
|
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
|
?.firstOrNull() ?: return false
|
||||||
doc.find("heapDump")?.delete()
|
doc.find("heapDump")?.delete()
|
||||||
val heapDumpDoc = doc.createFolderIfNotExist("heapDump")
|
val heapDumpDoc = doc.createFolderIfNotExist("heapDump")
|
||||||
|
|||||||
@@ -1,47 +1,147 @@
|
|||||||
package io.legado.app.ui.about
|
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.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.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material3.DividerDefaults
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material3.HorizontalDivider
|
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
|
||||||
|
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
|
import io.legado.app.base.AppTheme
|
||||||
import io.legado.app.constant.AppConst.appInfo
|
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
|
@Composable
|
||||||
fun AboutScreen(
|
fun AboutScreen(
|
||||||
|
versionName: String = appInfo.versionName,
|
||||||
|
onNavigateBack: () -> Unit = {},
|
||||||
onCheckUpdate: () -> Unit = {},
|
onCheckUpdate: () -> Unit = {},
|
||||||
onOpenUrl: (String) -> Unit = {},
|
onOpenUrl: (String) -> Unit = {},
|
||||||
onShowMdFile: (String, String) -> Unit = { _, _ -> },
|
onShowMdFile: (String, String) -> Unit = { _, _ -> },
|
||||||
onSaveLog: () -> Unit = {},
|
onSaveLog: () -> Unit = {},
|
||||||
onCreateHeapDump: () -> Unit = {},
|
onCreateHeapDump: () -> Unit = {},
|
||||||
onShowCrashLogs: () -> Unit = {},
|
onShowCrashLogs: () -> Unit = {}
|
||||||
versionName: String = "1.0.0"
|
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
LocalContext.current
|
||||||
val version = remember { appInfo.versionName }
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
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 ->
|
) { padding ->
|
||||||
@@ -51,57 +151,109 @@ fun AboutScreen(
|
|||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
Text(text = "版本:$version", style = MaterialTheme.typography.bodyLarge)
|
Image(
|
||||||
|
painter = painterResource(R.drawable.ic_launcher_foreground),
|
||||||
Spacer(Modifier.height(8.dp))
|
contentDescription = "",
|
||||||
Button(onClick = onCheckUpdate) {
|
contentScale = ContentScale.Crop,
|
||||||
Text(stringResource(R.string.check_update))
|
modifier = Modifier
|
||||||
}
|
.fillMaxWidth()
|
||||||
|
.wrapContentWidth(Alignment.CenterHorizontally)
|
||||||
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(text = "文档与政策", style = MaterialTheme.typography.titleMedium)
|
text = stringResource(R.string.app_name), style = MaterialTheme.typography.bodyLarge,
|
||||||
Spacer(Modifier.height(4.dp))
|
modifier = Modifier
|
||||||
TextButton(onClick = { onShowMdFile(context.getString(R.string.privacy_policy), "privacyPolicy.md") }) {
|
.fillMaxWidth()
|
||||||
Text(stringResource(R.string.privacy_policy))
|
.wrapContentWidth(Alignment.CenterHorizontally))
|
||||||
}
|
TextCard(
|
||||||
TextButton(onClick = { onShowMdFile(context.getString(R.string.license), "LICENSE.md") }) {
|
text = versionName,
|
||||||
Text(stringResource(R.string.license))
|
backgroundColor = colorScheme.tertiaryContainer,
|
||||||
}
|
contentColor = colorScheme.onTertiaryContainer,
|
||||||
TextButton(onClick = { onShowMdFile(context.getString(R.string.disclaimer), "disclaimer.md") }) {
|
cornerRadius = 8.dp,
|
||||||
Text(stringResource(R.string.disclaimer))
|
modifier = Modifier
|
||||||
}
|
.fillMaxWidth()
|
||||||
|
.wrapContentWidth(Alignment.CenterHorizontally)
|
||||||
HorizontalDivider(
|
|
||||||
Modifier.padding(vertical = 8.dp),
|
|
||||||
DividerDefaults.Thickness,
|
|
||||||
DividerDefaults.color
|
|
||||||
)
|
)
|
||||||
|
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)
|
FilledTonalIconButton (onClick = { onOpenUrl("https://github.com/HapeLee/legado-with-MD3") }) {
|
||||||
Spacer(Modifier.height(4.dp))
|
Icon(
|
||||||
TextButton(onClick = onShowCrashLogs) {
|
painter = painterResource(R.drawable.ic_github),
|
||||||
Text(stringResource(R.string.crash_log))
|
contentDescription = stringResource(R.string.back)
|
||||||
}
|
)
|
||||||
TextButton(onClick = onSaveLog) {
|
}
|
||||||
Text(stringResource(R.string.save_log))
|
|
||||||
}
|
FilledTonalIconButton (onClick = { onCheckUpdate }) {
|
||||||
TextButton(onClick = onCreateHeapDump) {
|
Icon(
|
||||||
Text(stringResource(R.string.create_heap_dump))
|
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"
|
android:id="@+id/tv_clear_history"
|
||||||
style="@style/Widget.Material3Expressive.Button"
|
style="@style/Widget.Material3Expressive.Button"
|
||||||
android:text="@string/delete_all"
|
android:text="@string/delete_all"
|
||||||
app:backgroundTint="?attr/colorTertiary"
|
app:backgroundTint="?attr/colorSecondaryContainer"
|
||||||
app:iconTint="?attr/colorOnTertiary"
|
app:iconTint="?attr/colorOnSecondaryContainer"
|
||||||
android:textColor="?attr/colorOnTertiary"
|
android:textColor="?attr/colorOnSecondaryContainer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:icon="@drawable/ic_clear_all"
|
app:icon="@drawable/ic_clear_all"
|
||||||
app:strokeColor="?attr/colorTertiary"
|
app:strokeColor="?attr/colorSecondaryContainer"
|
||||||
app:iconGravity="textStart" />
|
app:iconGravity="textStart" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
VERSION_MAJOR=3
|
VERSION_MAJOR=3
|
||||||
VERSION_MINOR=26
|
VERSION_MINOR=26
|
||||||
VERSION_PATCH=3
|
VERSION_PATCH=4
|
||||||
VERSION_SUFFIX=
|
VERSION_SUFFIX=
|
||||||
+33
-10
@@ -1,13 +1,16 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
|
||||||
accompanistWebview = "0.36.0"
|
accompanistWebview = "0.36.0"
|
||||||
|
animation = "1.9.4"
|
||||||
coilCompose = "2.7.0"
|
coilCompose = "2.7.0"
|
||||||
composeBom = "2025.11.00"
|
composeBom = "2025.11.01"
|
||||||
coreSplashscreen = "1.2.0-rc01"
|
constraintlayoutCompose = "1.1.1"
|
||||||
|
coreSplashscreen = "1.2.0"
|
||||||
datastorePreferences = "1.1.7"
|
datastorePreferences = "1.1.7"
|
||||||
kotlin = "2.2.20"
|
foundation = "1.9.4"
|
||||||
kotlinxCoroutinesAndroid = "1.8.1"
|
kotlin = "2.2.21"
|
||||||
ksp = "2.2.20-2.0.4"
|
kotlinxCoroutinesAndroid = "1.10.2"
|
||||||
|
ksp = "2.2.21-2.0.4"
|
||||||
agp = "8.13.1"
|
agp = "8.13.1"
|
||||||
appcompat = "1.7.1"
|
appcompat = "1.7.1"
|
||||||
colorpicker = "1.1.0"
|
colorpicker = "1.1.0"
|
||||||
@@ -22,7 +25,7 @@ fragment = "1.8.9"
|
|||||||
hutool = "5.8.22"
|
hutool = "5.8.22"
|
||||||
|
|
||||||
libarchive = "1.1.6"
|
libarchive = "1.1.6"
|
||||||
lifecycle = "2.9.4"
|
lifecycle = "2.10.0"
|
||||||
glide = "5.0.5"
|
glide = "5.0.5"
|
||||||
gson = "2.13.2"
|
gson = "2.13.2"
|
||||||
jsonPath = "2.9.0"
|
jsonPath = "2.9.0"
|
||||||
@@ -31,13 +34,18 @@ jsonPath = "2.9.0"
|
|||||||
jsoup = "1.16.2"
|
jsoup = "1.16.2"
|
||||||
jsoupxpath = "2.5.3"
|
jsoupxpath = "2.5.3"
|
||||||
coroutines = "1.10.2"
|
coroutines = "1.10.2"
|
||||||
|
lifecycleViewmodelCompose = "2.10.0"
|
||||||
liveeventbus = "1.8.14"
|
liveeventbus = "1.8.14"
|
||||||
markdownCompose = "0.5.0"
|
markdownCompose = "0.5.0"
|
||||||
markwon = "4.6.2"
|
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"
|
media = "1.7.1"
|
||||||
media3 = "1.8.0"
|
media3 = "1.8.0"
|
||||||
nanoHttpd = "2.3.1"
|
nanoHttpd = "2.3.1"
|
||||||
|
navigationCompose = "2.9.6"
|
||||||
okhttp = "5.2.0"
|
okhttp = "5.2.0"
|
||||||
palette = "1.0.0"
|
palette = "1.0.0"
|
||||||
preference = "1.2.1"
|
preference = "1.2.1"
|
||||||
@@ -55,13 +63,14 @@ swiperefreshlayout = "1.1.0"
|
|||||||
#noinspection GradleDependency
|
#noinspection GradleDependency
|
||||||
recyclerview = "1.4.0"
|
recyclerview = "1.4.0"
|
||||||
#noinspection GradleDependency
|
#noinspection GradleDependency
|
||||||
|
uiViewbinding = "1.9.4"
|
||||||
viewpager2 = "1.1.0"
|
viewpager2 = "1.1.0"
|
||||||
webkit = "1.14.0"
|
webkit = "1.14.0"
|
||||||
collection = "1.5.0"
|
collection = "1.5.0"
|
||||||
|
|
||||||
zxingLite = "3.3.0"
|
zxingLite = "3.3.0"
|
||||||
|
|
||||||
|
koin-bom = "4.1.1"
|
||||||
[libraries]
|
[libraries]
|
||||||
|
|
||||||
accompanist-webview = { module = "com.google.accompanist:accompanist-webview", version.ref = "accompanistWebview" }
|
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-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-animation = { module = "androidx.compose.animation:animation", version.ref = "animation" }
|
||||||
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
|
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 = { module = "androidx.compose.ui:ui" }
|
||||||
androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
|
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-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-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-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-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" }
|
||||||
|
|
||||||
|
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" }
|
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" }
|
||||||
@@ -194,7 +217,7 @@ coroutines = ["kotlinx-coroutines-core", "kotlinx-coroutines-android"]
|
|||||||
androidTest = ["androidx-espresso-core", "androidx-junit", "androidx-runner"]
|
androidTest = ["androidx-espresso-core", "androidx-junit", "androidx-runner"]
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
|
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||||
android-test = { id = "com.android.test", version.ref = "agp" }
|
android-test = { id = "com.android.test", version.ref = "agp" }
|
||||||
|
|||||||
Reference in New Issue
Block a user