HapeLee 1 سال پیش
والد
کامیت
42c7449a8c

+ 35 - 0
app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt

@@ -273,6 +273,12 @@ object ReadBookConfig {
             config.lineSpacingExtra = value
         }
 
+    var titleLineSpacingExtra: Int
+        get() = config.titleLineSpacingExtra
+        set(value) {
+            config.titleLineSpacingExtra = value
+        }
+
     var paragraphSpacing: Int
         get() = config.paragraphSpacing
         set(value) {
@@ -293,6 +299,30 @@ object ReadBookConfig {
             config.titleSize = value
         }
 
+    var titleSegType: Int
+        get() = config.titleSegType
+        set(value) {
+            config.titleSegType = value
+        }
+
+    var titleSegScaling: Float
+        get() = config.titleSegScaling
+        set(value) {
+            config.titleSegScaling = value
+        }
+
+    var titleSegDistance: Int
+        get() = config.titleSegDistance
+        set(value) {
+            config.titleSegDistance = value
+        }
+
+    var titleSegFlag: String
+        get() = config.titleSegFlag
+        set(value) {
+            config.titleSegFlag = value
+        }
+
     /**
      * 是否标题居中
      */
@@ -497,6 +527,11 @@ object ReadBookConfig {
         var titleTopSpacing: Int = 0,
         var titleBottomSpacing: Int = 0,
         var titleBold: Int = 500,
+        var titleLineSpacingExtra: Int = 4,
+        var titleSegType: Int = 0,//分段模式
+        var titleSegScaling: Float = 1.2f,//分段缩放,第二段与第一段的字体大小比例
+        var titleSegDistance: Int = 4,//分段判断,第几个字符开始分段
+        var titleSegFlag: String = "",//分段判断,碰到指定值时分段
         var paragraphIndent: String = "  ",//段落缩进
         var underline: Boolean = false, //下划线
         var paddingBottom: Int = 6,

+ 87 - 1
app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt

@@ -1,12 +1,16 @@
 package io.legado.app.ui.book.read.config
 
 import android.os.Bundle
+import android.text.InputType
 import android.view.View
+import android.widget.EditText
+import androidx.appcompat.app.AlertDialog
 import com.google.android.material.chip.Chip
 import com.jaredrummler.android.colorpicker.ColorPickerDialog
 import io.legado.app.R
 import io.legado.app.base.BaseBottomSheetDialogFragment
 import io.legado.app.constant.EventBus
+import io.legado.app.databinding.DialogEditTextBinding
 import io.legado.app.databinding.DialogTipConfigBinding
 import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.help.config.ReadTipConfig
@@ -15,6 +19,8 @@ import io.legado.app.lib.dialogs.selector
 import io.legado.app.utils.hexString
 import io.legado.app.utils.observeEvent
 import io.legado.app.utils.postEvent
+import io.legado.app.utils.requestInputMethod
+import io.legado.app.utils.toastOnUi
 import io.legado.app.utils.viewbindingdelegate.viewBinding
 
 
@@ -74,10 +80,82 @@ class TipConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_tip_config
             addOnChangeListener { _, newValue, _ ->
                 binding.textFontWeightConverter.text = "自定义"
                 ReadBookConfig.titleBold = newValue.toInt()
-                postEvent(EventBus.UP_CONFIG, arrayListOf(8, 9, 6))
+                postEvent(EventBus.UP_CONFIG, arrayListOf(5))
+            }
+        }
+
+        binding.btnTitleSegType.setOnClickListener {
+            val types = arrayOf("不分段", "按字符数分段", "按标志字符串分段")
+            val current = ReadBookConfig.titleSegType
+
+            alert(title = "选择标题分段模式") {
+                singleChoiceItems(types, current) { _, which ->
+                    ReadBookConfig.titleSegType = which
+                }
+                positiveButton("确定") {
+                    toastOnUi("分段模式已设置为:${types[ReadBookConfig.titleSegType]}")
+                    postEvent(EventBus.UP_CONFIG, arrayListOf(5))
+                }
+                negativeButton("取消")
+            }.show()
+        }
+
+        binding.btnTitleSegConfig.setOnClickListener {
+            when (ReadBookConfig.titleSegType) {
+                1 -> { // 按字符数分段
+                    alert(title = "设置分段字符数") {
+                        val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
+                            editView.inputType = InputType.TYPE_CLASS_NUMBER
+                            editView.setText(ReadBookConfig.titleSegDistance.toString())
+                            editView.hint = "输入分段字符数"
+                        }
+                        customView { alertBinding.root }
+
+                        okButton {
+                            val value = alertBinding.editView.text?.toString()?.toIntOrNull()
+                            if (value != null && value > 0) {
+                                ReadBookConfig.titleSegDistance = value
+                                toastOnUi("分段字符数设置为 $value")
+                                postEvent(EventBus.UP_CONFIG, arrayListOf(5))
+                            } else {
+                                toastOnUi("请输入有效数字")
+                            }
+                        }
+                        cancelButton()
+                    }.requestInputMethod()
+                }
+
+                2 -> { // 按标志字符串分段
+                    alert(title = "设置分段标志") {
+                        val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
+                            editView.inputType = InputType.TYPE_CLASS_TEXT
+                            editView.setText(ReadBookConfig.titleSegFlag)
+                            editLayout.hint = "输入多个标志,用英文逗号分隔,例如:章,回,篇"
+                        }
+                        customView { alertBinding.root }
+
+                        okButton {
+                            val value = alertBinding.editView.text?.toString()?.trim()
+                            if (!value.isNullOrEmpty()) {
+                                ReadBookConfig.titleSegFlag = value
+                                toastOnUi("分段标志设置为 \"$value\"")
+                                postEvent(EventBus.UP_CONFIG, arrayListOf(5))
+                            } else {
+                                toastOnUi("标志不能为空")
+                            }
+                        }
+                        cancelButton()
+                    }.requestInputMethod()
+                }
+
+                else -> {
+                    toastOnUi("当前分段模式无需配置参数")
+                }
             }
         }
 
+        binding.dsbTitleSegScaling.progress = ReadBookConfig.titleSegScaling.toInt() * 10
+        binding.dsbTitleLineSpacingExtra.progress = ReadBookConfig.titleLineSpacingExtra
         binding.dsbTitleSize.progress = ReadBookConfig.titleSize
         binding.dsbTitleTop.progress = ReadBookConfig.titleTopSpacing
         binding.dsbTitleBottom.progress = ReadBookConfig.titleBottomSpacing
@@ -166,6 +244,14 @@ class TipConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_tip_config
                 postEvent(EventBus.UP_CONFIG, arrayListOf(5))
             }
         }
+        binding.dsbTitleSegScaling.onChanged = {
+            ReadBookConfig.titleSegScaling = it / 10f
+            postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))
+        }
+        binding.dsbTitleLineSpacingExtra.onChanged = {
+            ReadBookConfig.titleLineSpacingExtra = it
+            postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))
+        }
         dsbTitleSize.onChanged = {
             ReadBookConfig.titleSize = it
             postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))

+ 4 - 0
app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt

@@ -97,6 +97,9 @@ object ChapterProvider {
     var lineSpacingExtra = 0f
         private set
 
+    var titleLineSpacingExtra  = 0f
+        private set
+
     @JvmStatic
     var paragraphSpacing = 0
         private set
@@ -861,6 +864,7 @@ object ChapterProvider {
         }
         //间距
         lineSpacingExtra = ReadBookConfig.lineSpacingExtra / 10f
+        titleLineSpacingExtra = ReadBookConfig.titleLineSpacingExtra / 10f
         paragraphSpacing = ReadBookConfig.paragraphSpacing
         titleTopSpacing = ReadBookConfig.titleTopSpacing.dpToPx()
         titleBottomSpacing = ReadBookConfig.titleBottomSpacing.dpToPx()

+ 88 - 28
app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt

@@ -65,6 +65,8 @@ class TextChapterLayout(
     private val titleTopSpacing = ChapterProvider.titleTopSpacing
     private val titleBottomSpacing = ChapterProvider.titleBottomSpacing
     private val lineSpacingExtra = ChapterProvider.lineSpacingExtra
+    private val titleLineSpacingExtra = ChapterProvider.titleLineSpacingExtra
+
     private val paragraphSpacing = ChapterProvider.paragraphSpacing
 
     private val visibleHeight = ChapterProvider.visibleHeight
@@ -80,6 +82,11 @@ class TextChapterLayout(
     private val useZhLayout = ReadBookConfig.useZhLayout
     private val isMiddleTitle = ReadBookConfig.isMiddleTitle
     private val textFullJustify = ReadBookConfig.textFullJustify
+    private val titleSegType = ReadBookConfig.titleSegType
+    private val titleSegDistance = ReadBookConfig.titleSegDistance
+    private val titleSegFlag = ReadBookConfig.titleSegFlag
+    private val titleSegScaling = ReadBookConfig.titleSegScaling
+
 
     private var pendingTextPage = TextPage()
 
@@ -189,6 +196,48 @@ class TextChapterLayout(
         }
     }
 
+    private fun parseTitleSegments(
+        rawTitle: String,
+        segType: Int,
+        segDistance: Int,
+        segFlag: String
+    ): List<String> {
+        if (rawTitle.isBlank()) return emptyList()
+
+        return when (segType) {
+            // 0:不分段
+            0 -> listOf(rawTitle)
+
+            // 1:按字符数分段
+            1 -> {
+                if (segDistance <= 0 || segDistance >= rawTitle.length)
+                    listOf(rawTitle)
+                else
+                    listOf(
+                        rawTitle.substring(0, segDistance),
+                        rawTitle.substring(segDistance)
+                    )
+            }
+
+            // 2:按标志字符串分段
+            2 -> {
+                val flags = segFlag.split(",").map { it.trim() }.filter { it.isNotEmpty() }
+                if (flags.isEmpty()) return listOf(rawTitle)
+
+                val pattern = flags.joinToString("|") { Regex.escape(it) }
+                val regex = Regex("(?<=$pattern)") // 在分隔符后断开
+                val segments = rawTitle.split(regex)
+                    .map { it.trim() }
+                    .filter { it.isNotEmpty() }
+
+                segments.ifEmpty { listOf(rawTitle) }
+            }
+
+            else -> listOf(rawTitle)
+        }
+    }
+
+
     /**
      * 获取拆分完的章节数据
      */
@@ -204,37 +253,48 @@ class TextChapterLayout(
         val isTextImageStyle = imageStyle.equals(Book.imgStyleText, true)
 
         if (titleMode != 2 || bookChapter.isVolume || contents.isEmpty()) {
-            //标题非隐藏
-            displayTitle.splitNotBlank("\n").forEach { text ->
-                val srcList = LinkedList<String>()
-                val reviewImg = bookChapter.reviewImg
-                var reviewTxt = ""
-                if (reviewImg != null) {
-                    srcList.add(reviewImg)
-                    reviewTxt = if (reviewImg.contains("TEXT")) {
-                        ChapterProvider.reviewChar
-                    } else {
-                        ChapterProvider.srcReplaceChar
+            val baseTitles = displayTitle.splitNotBlank("\n")
+            baseTitles.forEach { rawTitle ->
+                val titleSegments = parseTitleSegments(
+                    rawTitle,
+                    titleSegType,
+                    titleSegDistance,
+                    titleSegFlag
+                )
+                titleSegments.forEachIndexed { index, segment ->
+                    val srcList = LinkedList<String>()
+                    val reviewImg = bookChapter.reviewImg
+                    var reviewTxt = ""
+                    if (reviewImg != null) {
+                        srcList.add(reviewImg)
+                        reviewTxt = if (reviewImg.contains("TEXT")) {
+                            ChapterProvider.reviewChar
+                        } else {
+                            ChapterProvider.srcReplaceChar
+                        }
                     }
+                    val paint = if (index == 0) titlePaint else {
+                        TextPaint(titlePaint).apply {
+                            textSize = titlePaint.textSize * titleSegScaling
+                        }
+                    }
+                    setTypeText(
+                        book,
+                        segment + reviewTxt,
+                        paint,
+                        titlePaintTextHeight,
+                        titlePaintFontMetrics,
+                        imageStyle,
+                        srcList = srcList.ifEmpty { null },
+                        isTitle = true,
+                        emptyContent = contents.isEmpty(),
+                        isVolumeTitle = bookChapter.isVolume
+                    )
+                    pendingTextPage.lines.last().isParagraphEnd = true
+                    stringBuilder.append("\n")
                 }
-                setTypeText(
-                    book,
-                    text + reviewTxt,
-                    titlePaint,
-                    titlePaintTextHeight,
-                    titlePaintFontMetrics,
-                    imageStyle,
-                    srcList = srcList.ifEmpty { null },
-                    isTitle = true,
-                    emptyContent = contents.isEmpty(),
-                    isVolumeTitle = bookChapter.isVolume
-                )
-                pendingTextPage.lines.last().isParagraphEnd = true
-                stringBuilder.append("\n")
             }
             durY += titleBottomSpacing
-
-            // 如果是单图模式且当前页有内容,强制分页
             if (isSingleImageStyle && pendingTextPage.lines.isNotEmpty() && contents.isNotEmpty()) {
                 prepareNextPageIfNeed()
             }
@@ -598,7 +658,7 @@ class TextChapterLayout(
             textLine.upTopBottom(durY, textHeight, fontMetrics)
             val textPage = pendingTextPage
             textPage.addLine(textLine)
-            durY += textHeight * lineSpacingExtra
+            durY += textHeight * if (isTitle) titleLineSpacingExtra else lineSpacingExtra
             if (textPage.height < durY) {
                 textPage.height = durY
             }

+ 48 - 1
app/src/main/res/layout/dialog_tip_config.xml

@@ -75,11 +75,58 @@
 
         </com.google.android.material.chip.ChipGroup>
 
+        <com.google.android.material.button.MaterialButtonGroup
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="bottom">
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/btn_title_segType"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="标题分段"
+                style="@style/Widget.Material3Expressive.Button.TonalButton"/>
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/btn_title_seg_config"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="分段模式"
+                style="@style/Widget.Material3Expressive.Button.TonalButton"/>
+
+        </com.google.android.material.button.MaterialButtonGroup>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <io.legado.app.ui.widget.SimpleCounterView
+                android:id="@+id/dsb_title_seg_scaling"
+                android:layout_width="120dp"
+                android:layout_height="wrap_content"
+                android:visibility="gone"
+                app:max="20"
+                app:title="次行字体缩放" />
+            <View
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"/>
+
+            <io.legado.app.ui.widget.SimpleCounterView
+                android:id="@+id/dsb_title_line_spacing_extra"
+                android:layout_width="120dp"
+                android:layout_height="wrap_content"
+                app:max="20"
+                app:title="标题段距" />
+
+        </LinearLayout>
+
+
+
         <TextView
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="@string/text_font_weight_converter"
-            android:textSize="16sp"
+            android:textSize="12sp"
             android:textStyle="bold" />
 
         <LinearLayout

+ 1 - 1
app/src/main/res/layout/view_simple_counter.xml

@@ -33,7 +33,7 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginVertical="4dp"
-                android:maxWidth="56sp"
+                android:maxLength="8"
                 android:maxLines="2"
                 android:text="@string/text"
                 android:textColor="?android:attr/textColorSecondary"