From b53b2cdf0473e3eda9c0bd6535bae4b56ba90cd9 Mon Sep 17 00:00:00 2001 From: HapeLee <1321903405@qq.com> Date: Sun, 26 Oct 2025 02:08:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AD=97=E4=BD=93=E9=98=B4=E5=BD=B1=E4=B8=8E?= =?UTF-8?q?=E6=96=9C=E4=BD=93=20#141?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kazusa/help/config/ReadBookConfig.kt | 87 +++++- .../kazusa/ui/book/read/ReadBookActivity.kt | 5 + .../ui/book/read/config/FontSelectDialog.kt | 70 ++++- .../read/page/provider/ChapterProvider.kt | 27 +- .../main/res/drawable/ic_format_italic.xml | 10 + app/src/main/res/drawable/ic_shadow.xml | 10 + .../main/res/layout/dialog_font_select.xml | 289 ++++++++++-------- .../main/res/layout/view_simple_counter.xml | 4 +- app/src/main/res/values-zh/strings.xml | 4 + app/src/main/res/values/strings.xml | 4 + 10 files changed, 361 insertions(+), 149 deletions(-) create mode 100644 app/src/main/res/drawable/ic_format_italic.xml create mode 100644 app/src/main/res/drawable/ic_shadow.xml diff --git a/app/src/main/java/io/legato/kazusa/help/config/ReadBookConfig.kt b/app/src/main/java/io/legato/kazusa/help/config/ReadBookConfig.kt index cc0ada919..87564a500 100644 --- a/app/src/main/java/io/legato/kazusa/help/config/ReadBookConfig.kt +++ b/app/src/main/java/io/legato/kazusa/help/config/ReadBookConfig.kt @@ -32,6 +32,8 @@ import io.legato.kazusa.utils.putPrefInt import io.legato.kazusa.utils.resizeAndRecycle import splitties.init.appCtx import java.io.File +import kotlin.String +import androidx.core.graphics.drawable.toDrawable /** * 阅读界面配置 @@ -58,6 +60,7 @@ object ReadBookConfig { var bg: Drawable? = null var bgMeanColor: Int = 0 val textColor: Int get() = durConfig.curTextColor() + val textShadowColor: Int get() = durConfig.curTextShadowColor() init { initConfigs() @@ -246,6 +249,36 @@ object ReadBookConfig { config.textBold = value } + var textItalic: Boolean + get() = config.textItalic + set(value) { + config.textItalic = value + } + + var textShadow: Boolean + get() = config.textShadow + set(value) { + config.textShadow = value + } + + var shadowRadius: Float + get() = config.shadowRadius + set(value) { + config.shadowRadius = value + } + + var shadowDx: Float + get() = config.shadowDx + set(value) { + config.shadowDx = value + } + + var shadowDy: Float + get() = config.shadowDy + set(value) { + config.shadowDy = value + } + var textSize: Int get() = config.textSize set(value) { @@ -509,6 +542,13 @@ object ReadBookConfig { var textFont: String = "",//字体 var textBold: Int = 500,//是否粗体字 0:正常, 1:粗体, 2:细体 var textSize: Int = 20,//文字大小 + var textItalic: Boolean = false,// 是否启用斜体 + var textShadow: Boolean = false,// 是否启用阴影 + var shadowRadius: Float = 16f,// 阴影模糊半径 + var shadowDx: Float = 1f,// 阴影x偏移 + var shadowDy: Float = 1f,// 阴影y偏移 + private var shadowColor: String = "#3E3D3B", + private var shadowColorN: String = "#3E3D3B", var letterSpacing: Float = 0.1f,//字间距 var lineSpacingExtra: Int = 12,//行间距 var paragraphSpacing: Int = 2,//段距 @@ -553,16 +593,37 @@ object ReadBookConfig { @Transient private var textColorInt = -1 + @Transient + private var textColorSIntNight = -1 + + @Transient + private var textColorSInt = -1 + @Transient private var initColorInt = false private fun initColorInt() { - textColorIntEInk = Color.parseColor(textColorEInk) - textColorIntNight = Color.parseColor(textColorNight) - textColorInt = Color.parseColor(textColor) + textColorIntEInk = textColorEInk.toColorInt() + textColorIntNight = textColorNight.toColorInt() + textColorInt = textColor.toColorInt() + textColorSIntNight = shadowColorN.toColorInt() + textColorSInt = shadowColor.toColorInt() initColorInt = true } + fun setCurShadColor(color: Int){ + when { + AppConfig.isNightTheme -> { + shadowColorN = "#${color.hexString}" + textColorSIntNight = color + } + else -> { + shadowColor = "#${color.hexString}" + textColorSInt = color + } + } + } + fun setCurTextColor(color: Int) { when { AppConfig.isEInkMode -> { @@ -593,6 +654,16 @@ object ReadBookConfig { } } + fun curTextShadowColor(): Int { + if (!initColorInt) { + initColorInt() + } + return when { + AppConfig.isNightTheme -> textColorSIntNight + else -> textColorSInt + } + } + fun setCurStatusIconDark(isDark: Boolean) { when { AppConfig.isEInkMode -> darkStatusIconEInk = isDark @@ -661,18 +732,18 @@ object ReadBookConfig { fun curBgDrawable(width: Int, height: Int): Drawable { if (width == 0 || height == 0) { val backgroundColor = MaterialColors.getColor(appCtx, com.google.android.material.R.attr.colorSurface, Color.WHITE) - return ColorDrawable(backgroundColor) + return backgroundColor.toDrawable() } var bgDrawable: Drawable? = null val resources = appCtx.resources try { bgDrawable = when (curBgType()) { - 0 -> ColorDrawable(Color.parseColor(curBgStr())) + 0 -> curBgStr().toColorInt().toDrawable() 1 -> { val path = "bg" + File.separator + curBgStr() val bitmap = BitmapUtils.decodeAssetsBitmap(appCtx, path, width, height) - BitmapDrawable(resources, bitmap?.resizeAndRecycle(width, height)) + bitmap?.resizeAndRecycle(width, height)?.toDrawable(resources) } else -> { val path = curBgStr().let { @@ -680,7 +751,7 @@ object ReadBookConfig { else FileUtils.getPath(appCtx.externalFiles, "bg", curBgStr()) } val bitmap = BitmapUtils.decodeBitmap(path, width, height) - BitmapDrawable(resources, bitmap?.resizeAndRecycle(width, height)) + bitmap?.resizeAndRecycle(width, height)?.toDrawable(resources) } } } catch (e: OutOfMemoryError) { @@ -691,7 +762,7 @@ object ReadBookConfig { // fallback 使用 MD3 的 colorSurface 作为背景色 val fallbackColor = MaterialColors.getColor(appCtx, com.google.android.material.R.attr.colorSurface, Color.WHITE) - return bgDrawable ?: ColorDrawable(fallbackColor) + return bgDrawable ?: fallbackColor.toDrawable() } } } \ No newline at end of file diff --git a/app/src/main/java/io/legato/kazusa/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legato/kazusa/ui/book/read/ReadBookActivity.kt index fd87dc928..54c5d6274 100644 --- a/app/src/main/java/io/legato/kazusa/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legato/kazusa/ui/book/read/ReadBookActivity.kt @@ -76,6 +76,7 @@ import io.legato.kazusa.ui.book.info.BookInfoActivity import io.legato.kazusa.ui.book.read.config.AutoReadDialog import io.legato.kazusa.ui.book.read.config.BgTextConfigDialog.Companion.BG_COLOR import io.legato.kazusa.ui.book.read.config.BgTextConfigDialog.Companion.TEXT_COLOR +import io.legato.kazusa.ui.book.read.config.FontSelectDialog.Companion.S_COLOR import io.legato.kazusa.ui.book.read.config.ReadAloudDialog import io.legato.kazusa.ui.book.read.config.ReadStyleDialog import io.legato.kazusa.ui.book.read.config.TipConfigDialog.Companion.TIP_COLOR @@ -1504,6 +1505,10 @@ class ReadBookActivity : BaseReadBookActivity(), */ override fun onColorSelected(dialogId: Int, color: Int) = ReadBookConfig.durConfig.run { when (dialogId) { + S_COLOR -> { + setCurShadColor(color) + postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6, 9, 11)) + } TEXT_COLOR -> { setCurTextColor(color) postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6, 9, 11)) diff --git a/app/src/main/java/io/legato/kazusa/ui/book/read/config/FontSelectDialog.kt b/app/src/main/java/io/legato/kazusa/ui/book/read/config/FontSelectDialog.kt index 5ae586e02..eb72ecfaf 100644 --- a/app/src/main/java/io/legato/kazusa/ui/book/read/config/FontSelectDialog.kt +++ b/app/src/main/java/io/legato/kazusa/ui/book/read/config/FontSelectDialog.kt @@ -1,17 +1,19 @@ package io.legato.kazusa.ui.book.read.config -//import io.legado.app.lib.theme.primaryColor import android.content.Context import android.graphics.Typeface import android.os.Bundle import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.widget.ArrayAdapter import androidx.appcompat.widget.Toolbar +import io.legato.kazusa.help.config.ReadBookConfig import androidx.core.net.toUri import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager +import com.jaredrummler.android.colorpicker.ColorPickerDialog import io.legato.kazusa.R import io.legato.kazusa.base.BaseBottomSheetDialogFragment import io.legato.kazusa.base.adapter.ItemViewHolder @@ -22,7 +24,6 @@ import io.legato.kazusa.constant.PreferKey import io.legato.kazusa.databinding.DialogFontSelectBinding import io.legato.kazusa.databinding.ItemFontBinding import io.legato.kazusa.help.config.AppConfig -import io.legato.kazusa.help.config.ReadBookConfig import io.legato.kazusa.lib.dialogs.SelectItem import io.legato.kazusa.lib.dialogs.alert import io.legato.kazusa.lib.permission.Permissions @@ -54,6 +55,10 @@ import java.net.URLDecoder */ class FontSelectDialog : BaseBottomSheetDialogFragment(R.layout.dialog_font_select), Toolbar.OnMenuItemClickListener { + + companion object { + const val S_COLOR = 123 + } private val fontRegex = Regex("(?i).*\\.[ot]tf") private val binding by viewBinding(DialogFontSelectBinding::bind) private val adapter by lazy { @@ -118,21 +123,19 @@ class FontSelectDialog : BaseBottomSheetDialogFragment(R.layout.dialog_font_sele ((it - 50) / 100f).toString() } dsbLineSize.valueFormat = { ((it - 10) / 10f).toString() } + binding.dsbShadowRadius.valueFormat = { "${it.toInt()} px" } + binding.dsbShadowDx.valueFormat = { "${it.toInt()} px" } + binding.dsbShadowDy.valueFormat = { "${it.toInt()} px" } - binding.bgTextIndent.apply { - val currentIndex = ReadBookConfig.paragraphIndent.length.coerceAtMost(childCount - 1) - val defaultButtonId = getChildAt(currentIndex).id - check(defaultButtonId) - - addOnButtonCheckedListener { group, checkedId, isChecked -> - if (isChecked) { - val checkedIndex = (0 until group.childCount) - .firstOrNull { group.getChildAt(it).id == checkedId } - ?: return@addOnButtonCheckedListener - - ReadBookConfig.paragraphIndent = " ".repeat(checkedIndex) - postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5)) - } + binding.textIndentDropdown.apply { + val items = listOf("0", "1", "2", "3", "4") + val adapter = ArrayAdapter(context, android.R.layout.simple_list_item_1, items) + setAdapter(adapter) + val currentIndex = ReadBookConfig.paragraphIndent.length.coerceIn(0, items.lastIndex) + setText(items[currentIndex], false) + setOnItemClickListener { _, _, position, _ -> + ReadBookConfig.paragraphIndent = " ".repeat(position) + postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5)) } } @@ -166,6 +169,9 @@ class FontSelectDialog : BaseBottomSheetDialogFragment(R.layout.dialog_font_sele postEvent(EventBus.UP_CONFIG, arrayListOf(8, 9, 6)) } } + + binding.btnTextItalic.isChecked = ReadBookConfig.textItalic + binding.btnTextShadow.isChecked = ReadBookConfig.textShadow } private fun initViewEvent() = binding.run { @@ -192,6 +198,35 @@ class FontSelectDialog : BaseBottomSheetDialogFragment(R.layout.dialog_font_sele } } } + binding.btnTextItalic.addOnCheckedChangeListener { _, isChecked -> + ReadBookConfig.textItalic = isChecked + postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5)) + } + binding.btnTextShadow.addOnCheckedChangeListener { _, isChecked -> + ReadBookConfig.textShadow = isChecked + postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5)) + } + binding.dsbShadowRadius.onChanged = { + ReadBookConfig.shadowRadius = it.toFloat() + postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5)) + } + binding.dsbShadowDx.onChanged = { + ReadBookConfig.shadowDx = it.toFloat() + postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5)) + } + binding.dsbShadowDy.onChanged = { + ReadBookConfig.shadowDy = it.toFloat() + postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5)) + } + binding.btnShadowColor.setOnClickListener { + ColorPickerDialog.newBuilder() + .setColor(ReadBookConfig.config.curTextShadowColor()) + .setShowAlphaSlider(false) + .setDialogType(ColorPickerDialog.TYPE_CUSTOM) + .setDialogId(S_COLOR) + .show(requireActivity()) + //postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5)) + } } private fun upView() = binding.run { @@ -199,6 +234,9 @@ class FontSelectDialog : BaseBottomSheetDialogFragment(R.layout.dialog_font_sele dsbTextLetterSpacing.progress = (it.letterSpacing * 100).toInt() + 50 dsbLineSize.progress = it.lineSpacingExtra dsbParagraphSpacing.value = it.paragraphSpacing.toFloat() + binding.dsbShadowRadius.progress = it.shadowRadius.toInt() + binding.dsbShadowDx.progress = it.shadowDx.toInt() + binding.dsbShadowDy.progress = it.shadowDy.toInt() } } diff --git a/app/src/main/java/io/legato/kazusa/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legato/kazusa/ui/book/read/page/provider/ChapterProvider.kt index 3de8aa3f8..ae36ce467 100644 --- a/app/src/main/java/io/legato/kazusa/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legato/kazusa/ui/book/read/page/provider/ChapterProvider.kt @@ -1,5 +1,7 @@ package io.legato.kazusa.ui.book.read.page.provider +import android.annotation.SuppressLint +import android.graphics.Color import android.graphics.Paint.FontMetrics import android.graphics.RectF import android.graphics.Typeface @@ -38,6 +40,7 @@ import splitties.init.appCtx import java.io.File import java.util.LinkedList import java.util.Locale +import androidx.core.graphics.toColorInt /** * 解析内容生成章节和页面 @@ -910,6 +913,7 @@ object ChapterProvider { } ?: Typeface.DEFAULT } + @SuppressLint("UseKtx") private fun getPaints(typeface: Typeface?): Pair { val bold = Typeface.create(typeface, Typeface.BOLD) val normal = Typeface.create(typeface, Typeface.NORMAL) @@ -952,6 +956,17 @@ object ChapterProvider { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q && AppConfig.optimizeRender) { tPaint.isLinearText = true } + if (ReadBookConfig.textItalic) { + tPaint.textSkewX = -0.25f + } + if (ReadBookConfig.textShadow) { + tPaint.setShadowLayer( + ReadBookConfig.shadowRadius, + ReadBookConfig.shadowDx, + ReadBookConfig.shadowDy, + ReadBookConfig.textShadowColor + ) + } //正文 val cPaint = TextPaint() cPaint.color = ReadBookConfig.textColor @@ -964,7 +979,17 @@ object ChapterProvider { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q && AppConfig.optimizeRender) { cPaint.isLinearText = true } - + if (ReadBookConfig.textItalic) { + cPaint.textSkewX = -0.25f + } + if (ReadBookConfig.textShadow) { + cPaint.setShadowLayer( + ReadBookConfig.shadowRadius, + ReadBookConfig.shadowDx, + ReadBookConfig.shadowDy, + ReadBookConfig.textShadowColor + ) + } return Pair(tPaint, cPaint) } diff --git a/app/src/main/res/drawable/ic_format_italic.xml b/app/src/main/res/drawable/ic_format_italic.xml new file mode 100644 index 000000000..41527dd47 --- /dev/null +++ b/app/src/main/res/drawable/ic_format_italic.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_shadow.xml b/app/src/main/res/drawable/ic_shadow.xml new file mode 100644 index 000000000..09bf8d62e --- /dev/null +++ b/app/src/main/res/drawable/ic_shadow.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/dialog_font_select.xml b/app/src/main/res/layout/dialog_font_select.xml index a4a4ff03a..45e8ef7d3 100644 --- a/app/src/main/res/layout/dialog_font_select.xml +++ b/app/src/main/res/layout/dialog_font_select.xml @@ -11,28 +11,53 @@ android:layout_height="wrap_content" android:theme="?attr/actionBarStyle" /> + + + android:paddingStart="10dp" + android:paddingEnd="10dp"> - - - + android:gravity="center_vertical"> + + + + + + + + - + android:gravity="center_vertical" + android:orientation="horizontal"> - + + + + + + + + + android:gravity="bottom"> + + + + + style="@style/Widget.Material3Expressive.Button.OutlinedButton" + android:text="@string/text_shadow_color" /> - + - + + android:layout_marginEnd="8dp" + style="@style/Widget.Material3.TextInputLayout.FilledBox.Dense.ExposedDropdownMenu" + android:hint="@string/text_indent"> - + + - - + android:orientation="vertical"> + + + + + + + + + + + + + + + + + + + @@ -141,57 +239,6 @@ - - - - - - - - - - - - - - 长按设置为全局默认模式 滑动条显示样式 书架更新数量限制 + X轴距离 + Y轴距离 + 阴影半径 + 阴影颜色 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9d7e2a76c..3c1fa7d26 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1306,4 +1306,8 @@ 长按设置为全局默认模式 滑动条显示样式 书架更新数量限制 + X轴距离 + Y轴距离 + 阴影半径 + 阴影颜色