Просмотр исходного кода

refactor(R4.6): 页眉页脚的静态选项表搬到消费方,E3 护栏取消白名单

Track E·E3 的验收是「sheet/ 下 ReadBookConfig. 直读归零」。此前剩的 4 处
(tipNames/tipValues/getHeaderModes/getFooterModes)挂在护栏白名单里——它们确实
不是配置状态,是静态选项表,读了不会陈旧;但留在 ReadBookConfig 上就逼着护栏开口子。

和 R4.1b 处理 21 个 tipXxx 常量同一手法:搬到唯一消费方 HeaderFooterPage.kt 底部
(tipTypeValues / headerModes / footerModes),tipNames 直接用 stringArrayResource
读 R.array.read_tip——取值表和显示名表本就必须同序,放在一起才看得出这个约束。

顺带堵上护栏的一个洞:`import io.legado.app.help.config.ReadBookConfig.tipNames`
之后成员可以裸写,按 `ReadBookConfig.` 找的正则一个都看不见。现在单独抓 import,
注入回归反证过会红。SystemMenuPage.kt 那个没人用的 import 一并删掉。

sheet/ 下 ReadBookConfig 引用归零,ALLOWED_MEMBERS 整个删除。单测 450 全绿。

未动:ReadBookConfig 里 tipColorNames / tipDividerColorNames 两个资源数组
getter 全仓无人调用,属既有死代码,不在本次范围。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5151561 3 месяцев назад
Родитель
Сommit
5a7050513b

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

@@ -1,6 +1,5 @@
 package io.legado.app.help.config
 
-import android.content.Context
 import android.graphics.Color
 import android.graphics.drawable.Drawable
 import androidx.annotation.Keep
@@ -443,35 +442,9 @@ object ReadBookConfig {
     val tipDividerColor: Int
         get() = config.tipDividerColor
 
-    /** 与 `R.array.read_tip` 一一对应的展示顺序,故留在配置侧而不是 [ReadTipType]。 */
-    val tipValues = with(ReadTipType) {
-        arrayOf(
-            tipNone, tipBookName, tipChapterTitle, tipChapterTitleArrow, tipChapterTitleArrowClassic,
-            tipTime, tipBattery, tipBatteryClassic, tipBatteryInside, tipBatteryIcon,
-            tipBatteryPercentage, tipPage, tipTotalProgress, tipTotalProgress1, tipPageAndTotal,
-            tipTimeBattery, tipTimeBatteryClassic, tipTimeBatteryPercentage, tipWholeBookPage,
-            tipWholeBookPageAndProgress, tipCustom
-        )
-    }
-    val tipNames get() = appCtx.resources.getStringArray(R.array.read_tip).toList()
     val tipColorNames get() = appCtx.resources.getStringArray(R.array.tip_color).toList()
     val tipDividerColorNames get() = appCtx.resources.getStringArray(R.array.tip_divider_color).toList()
 
-    fun getHeaderModes(context: Context): LinkedHashMap<Int, String> {
-        return linkedMapOf(
-            Pair(0, context.getString(R.string.hide_when_status_bar_show)),
-            Pair(1, context.getString(R.string.show)),
-            Pair(2, context.getString(R.string.hide))
-        )
-    }
-
-    fun getFooterModes(context: Context): LinkedHashMap<Int, String> {
-        return linkedMapOf(
-            Pair(0, context.getString(R.string.show)),
-            Pair(1, context.getString(R.string.hide))
-        )
-    }
-
     // endregion
 
     fun getExportConfig(): Config {

+ 0 - 1
app/src/main/java/io/legado/app/ui/book/read/sheet/SystemMenuPage.kt

@@ -58,7 +58,6 @@ import io.legado.app.R
 import io.legado.app.constant.ReadMenuBlurMode
 import io.legado.app.constant.ReadMenuBlurStyle
 import io.legado.app.data.repository.ReadPreferences
-import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.ui.book.read.ConfigUpdate
 import io.legado.app.ui.book.read.ReadBookButtonConfigItem
 import io.legado.app.ui.book.read.ReadBookIntent

+ 36 - 7
app/src/main/java/io/legado/app/ui/book/read/sheet/TypographyTabs.kt

@@ -1,5 +1,6 @@
 package io.legado.app.ui.book.read.sheet
 
+import android.content.Context
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
@@ -38,7 +39,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import io.legado.app.R
 import io.legado.app.data.entities.Book
 import io.legado.app.data.repository.ReadSettingsRepository
-import io.legado.app.help.config.ReadBookConfig
+import io.legado.app.constant.ReadTipType
 import io.legado.app.model.ReadBook
 import io.legado.app.ui.book.read.ConfigUpdate
 import io.legado.app.ui.book.read.ReadBookIntent
@@ -625,8 +626,8 @@ internal fun TypographyHeaderTab(
     modifier: Modifier = Modifier,
 ) {
     val context = LocalContext.current
-    val tipNames = remember { ReadBookConfig.tipNames }
-    val tipValues = remember { ReadBookConfig.tipValues }
+    val tipNames = stringArrayResource(R.array.read_tip).toList()
+    val tipValues = tipTypeValues
 
     Column(
         modifier = modifier
@@ -635,7 +636,7 @@ internal fun TypographyHeaderTab(
     ) {
         // 内容组
         SectionTitle(title = stringResource(R.string.content))
-        val headerModes = ReadBookConfig.getHeaderModes(context)
+        val headerModes = headerModes(context)
         TinyDropdownSettingItem(
             title = stringResource(R.string.header),
             selectedValue = headerMode.toString(),
@@ -733,8 +734,8 @@ internal fun TypographyFooterTab(
     modifier: Modifier = Modifier,
 ) {
     val context = LocalContext.current
-    val tipNames = remember { ReadBookConfig.tipNames }
-    val tipValues = remember { ReadBookConfig.tipValues }
+    val tipNames = stringArrayResource(R.array.read_tip).toList()
+    val tipValues = tipTypeValues
 
     Column(
         modifier = modifier
@@ -743,7 +744,7 @@ internal fun TypographyFooterTab(
     ) {
         // 内容组
         SectionTitle(title = stringResource(R.string.content))
-        val footerModes = ReadBookConfig.getFooterModes(context)
+        val footerModes = footerModes(context)
         TinyDropdownSettingItem(
             title = stringResource(R.string.footer),
             selectedValue = footerMode.toString(),
@@ -1139,3 +1140,31 @@ private fun applyTipValue(
 }
 
 // endregion
+
+/**
+ * 页眉页脚每个位置的可选项取值。**顺序必须与 `R.array.read_tip` 一致**——
+ * 下拉项的显示名从那个数组取、取值从这里取,两者按下标配对。
+ *
+ * 放在唯一消费方旁边而不是 `ReadBookConfig` 里:它是静态选项表,不是配置状态,
+ * 留在那个全局 object 上会让「弹层不得直读 ReadBookConfig」的护栏被迫开白名单。
+ */
+private val tipTypeValues = with(ReadTipType) {
+    arrayOf(
+        tipNone, tipBookName, tipChapterTitle, tipChapterTitleArrow, tipChapterTitleArrowClassic,
+        tipTime, tipBattery, tipBatteryClassic, tipBatteryInside, tipBatteryIcon,
+        tipBatteryPercentage, tipPage, tipTotalProgress, tipTotalProgress1, tipPageAndTotal,
+        tipTimeBattery, tipTimeBatteryClassic, tipTimeBatteryPercentage, tipWholeBookPage,
+        tipWholeBookPageAndProgress, tipCustom
+    )
+}
+
+private fun headerModes(context: Context): LinkedHashMap<Int, String> = linkedMapOf(
+    Pair(0, context.getString(R.string.hide_when_status_bar_show)),
+    Pair(1, context.getString(R.string.show)),
+    Pair(2, context.getString(R.string.hide))
+)
+
+private fun footerModes(context: Context): LinkedHashMap<Int, String> = linkedMapOf(
+    Pair(0, context.getString(R.string.show)),
+    Pair(1, context.getString(R.string.hide))
+)

+ 19 - 18
app/src/test/java/io/legado/app/ui/book/read/sheet/SheetGlobalConfigReadTest.kt

@@ -11,24 +11,31 @@ import java.io.File
  * 否则会出现「改完关掉再打开还是旧值」——组合期直读 `ReadBookConfig` 的可变字段时,
  * 上游变化不会触发重组;即使 seed 进 `remember` 也只在首次组合读一次。
  *
- * 例外是编译期常量与静态枚举元数据(tip 类型码、下拉项名称/取值),它们不随配置变化,
- * 见 [ALLOWED_MEMBERS]。
+ * R4.6 之前这条断言留了白名单,装的全是静态选项表(tip 取值表、页眉页脚模式枚举)——
+ * 它们不随配置变化,读了不会陈旧,但挂在 `ReadBookConfig` 上就逼着护栏开口子。
+ * 现在它们已搬到唯一消费方旁边,**本断言不再有例外**。
  */
 class SheetGlobalConfigReadTest {
 
     @Test
     fun `设置弹层不直读可变的 ReadBookConfig 字段`() {
         val offenders = sheetSources().flatMap { file ->
-            GLOBAL_ACCESS.findAll(file.readText())
-                .map { it.groupValues[1] }
-                .filterNot { it in ALLOWED_MEMBERS }
-                .map { "${file.name} → ReadBookConfig.$it" }
+            val text = file.readText()
+            val qualified = GLOBAL_ACCESS.findAll(text)
+                .map { "${file.name} → ReadBookConfig.${it.groupValues[1]}" }
+            // 单独抓 import:`import ...ReadBookConfig.tipNames` 之后成员可以裸写,
+            // 上面那条按 `ReadBookConfig.` 找的正则一个都看不见。
+            val imported = CONFIG_IMPORT.findAll(text)
+                .map { "${file.name} → import ${it.groupValues[0].removePrefix("import ")}" }
+            (qualified + imported).toList()
         }.distinct().sorted()
 
         assertEquals(
-            "以下弹层代码直读了可变全局配置,请改从 state.sheetConfig / state.styleConfig 取值" +
-                "(若确为常量或静态元数据,加进 ALLOWED_MEMBERS 并说明理由):\n" +
-                offenders.joinToString("\n") { "  - $it" },
+            "以下弹层代码引用了全局排版配置,请改从 state.sheetConfig / state.styleConfig 取值:\n" +
+                offenders.joinToString("\n") { "  - $it" } + "\n" +
+                "静态选项表(下拉项的取值/显示名、模式枚举)不要往 ReadBookConfig 上挂——" +
+                "R4.6 已经把它们搬到唯一消费方旁边(HeaderFooterPage.kt 底部的 tipTypeValues/" +
+                "headerModes/footerModes),本断言不再留白名单。",
             emptyList<String>(),
             offenders,
         )
@@ -54,15 +61,9 @@ class SheetGlobalConfigReadTest {
 
     private companion object {
         val GLOBAL_ACCESS = Regex("""\bReadBookConfig\.([A-Za-z][A-Za-z0-9_]*)""")
-
-        /** 编译期常量与静态元数据——不随配置变化,读它们不会产生陈旧显示。 */
-        val ALLOWED_MEMBERS = setOf(
-            "tipNone",
-            "tipCustom",
-            "tipNames",
-            "tipValues",
-            "getHeaderModes",
-            "getFooterModes",
+        val CONFIG_IMPORT = Regex(
+            """^import io\.legado\.app\.help\.config\.ReadBookConfig\b.*$""",
+            RegexOption.MULTILINE,
         )
     }
 }