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

feat: 朗读脱离提示开关改为双态语义,默认关闭

- 开关语义调整:开启时朗读位置脱离当前页显示「回到朗读位置」悬浮条;
  关闭(默认)时手动翻页后朗读跟随新页面(不脱离、不显示提示条)
- ReadBook 手动导航改为双态处理:开启=脱离,关闭=保持跟随并在翻页后重启朗读到新位置
- 默认值改为 false(ReadSettings/ReadConfigUiState/ReadBookUiState/读方向映射)
- summary 文案更新四语言,ReadSettingsMappingTest 样例同步
- 顺带删除引用已移除类的 DictHtmlParserTest(82d4e4e00 删类未删测试,阻塞测试源集编译)
HapeLee 2 месяцев назад
Родитель
Сommit
320cbd494b

+ 1 - 1
app/src/main/java/io/legado/app/data/repository/ReadSettingsRepository.kt

@@ -382,7 +382,7 @@ class ReadSettingsRepository(
             autoChangeSource = compatDsValue(Keys.AutoChangeSource, true),
             autoSuggestDayNight = compatDsValue(Keys.AutoSuggestDayNight, false),
             readingAnchorEnabled = compatDsValue(Keys.ReadingAnchorEnabled, true),
-            readAloudDetachReminderEnabled = compatDsValue(Keys.ReadAloudDetachReminderEnabled, true),
+            readAloudDetachReminderEnabled = compatDsValue(Keys.ReadAloudDetachReminderEnabled, false),
             selectText = compatDsValue(Keys.SelectText, true),
             noAnimScrollPage = compatDsValue(Keys.NoAnimScrollPage, false),
             clickImgWay = compatDsValue(Keys.ClickImgWay, "2"),

+ 1 - 1
app/src/main/java/io/legado/app/domain/model/settings/ReadSettings.kt

@@ -40,7 +40,7 @@ data class ReadSettings(
     val autoChangeSource: Boolean = true,
     val autoSuggestDayNight: Boolean = false,
     val readingAnchorEnabled: Boolean = true,
-    val readAloudDetachReminderEnabled: Boolean = true,
+    val readAloudDetachReminderEnabled: Boolean = false,
     val selectText: Boolean = true,
     val noAnimScrollPage: Boolean = false,
     val clickImgWay: String = "2",

+ 28 - 12
app/src/main/java/io/legado/app/model/ReadBook.kt

@@ -607,7 +607,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
     }
 
     fun setProgress(progress: BookProgress) {
-        detachReadAloudFollowIfManual()
+        prepareManualNavigation()
         if (progress.durChapterIndex < chapterSize &&
             (durChapterIndex != progress.durChapterIndex
                     || durChapterPos != progress.durChapterPos)
@@ -862,17 +862,31 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
     }
 
     /**
-     * 用户手动导航(翻页/跳章/拖动进度/滚动)时,页面脱离朗读位置。
-     * 朗读服务自身驱动的页面移动([BaseReadAloudService.speechDrivingNavigation])不计入。
+     * 用户手动导航(翻页/跳章/拖动进度/滚动)前的朗读处理:
+     * - 脱离提示开启:页面脱离朗读位置(显示"回到朗读位置"悬浮条);
+     * - 脱离提示关闭:朗读跟随页面,保持跟随状态(翻页后从新页面重新朗读)。
+     * 朗读服务自身驱动的页面移动([BaseReadAloudService.speechDrivingNavigation])不参与。
      */
-    private fun detachReadAloudFollowIfManual() {
-        if (BaseReadAloudService.isRun && !BaseReadAloudService.speechDrivingNavigation) {
+    private fun prepareManualNavigation() {
+        if (!BaseReadAloudService.isRun || BaseReadAloudService.speechDrivingNavigation) return
+        if (ReadBookConfig.readAloudDetachReminderEnabled) {
             readAloudSessionStore.detachReadAloudFollow()
+        } else {
+            readAloudSessionStore.restoreReadAloudFollow()
         }
     }
 
+    /**
+     * 手动导航后:脱离提示关闭时,朗读从新页面重新开始(跟随页面);开启时保持脱离状态,不重启朗读。
+     */
+    private fun followReadAloudAfterManualNavigation() {
+        if (!BaseReadAloudService.isRun || BaseReadAloudService.speechDrivingNavigation) return
+        if (ReadBookConfig.readAloudDetachReminderEnabled) return
+        readAloud(play = !BaseReadAloudService.pause)
+    }
+
     fun moveToNextPage(): Boolean {
-        detachReadAloudFollowIfManual()
+        prepareManualNavigation()
         var hasNextPage = false
         curTextChapter?.let {
             val nextPagePos = it.getNextPageLength(durChapterPos)
@@ -886,11 +900,12 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
                 publishSnapshot()
             }
         }
+        followReadAloudAfterManualNavigation()
         return hasNextPage
     }
 
     fun moveToPrevPage(): Boolean {
-        detachReadAloudFollowIfManual()
+        prepareManualNavigation()
         var hasPrevPage = false
         curTextChapter?.let {
             val prevPagePos = it.getPrevPageLength(durChapterPos)
@@ -902,11 +917,12 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
                 publishSnapshot()
             }
         }
+        followReadAloudAfterManualNavigation()
         return hasPrevPage
     }
 
     fun moveToNextChapter(upContent: Boolean, upContentInPlace: Boolean = true): Boolean {
-        detachReadAloudFollowIfManual()
+        prepareManualNavigation()
         if (durChapterIndex < simulatedChapterSize - 1) {
             durChapterPos = 0
             durChapterIndex++
@@ -972,7 +988,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
         toLast: Boolean = true,
         upContentInPlace: Boolean = true
     ): Boolean {
-        detachReadAloudFollowIfManual()
+        prepareManualNavigation()
         if (durChapterIndex > 0) {
             durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0
             durChapterIndex--
@@ -998,7 +1014,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
     }
 
     fun skipToPage(index: Int, success: (() -> Unit)? = null) {
-        detachReadAloudFollowIfManual()
+        prepareManualNavigation()
         durChapterPos = curTextChapter?.getReadLength(index) ?: index
         renderCallBack?.upContent {
             success?.invoke()
@@ -1009,7 +1025,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
     }
 
     fun setPageIndex(index: Int) {
-        detachReadAloudFollowIfManual()
+        prepareManualNavigation()
         recycleRecorders(durPageIndex, index)
         durChapterPos = curTextChapter?.getReadLength(index) ?: index
         saveRead(true)
@@ -1038,7 +1054,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
         upContent: Boolean = true,
         success: (() -> Unit)? = null
     ) {
-        detachReadAloudFollowIfManual()
+        prepareManualNavigation()
         // 实时读取章节数而不是依赖缓存 chapterSize:更新目录后 chapterSize 若未同步,
         // 新增章节的 index 会超出旧值而被下方守卫静默吞掉,表现为「点击新章节无法跳转」。
         val chapterCount = book?.bookUrl?.let { appDb.bookChapterDao.getChapterCount(it) }

+ 2 - 2
app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt

@@ -230,8 +230,8 @@ data class ReadBookUiState(
     val isReadAloudPaused: Boolean = false,
     /** 朗读位置是否跟随当前显示页;手动翻页/跳章后为 false(显示"回到朗读位置"悬浮条)。 */
     val readAloudFollow: Boolean = true,
-    /** 朗读位置脱离当前页时的悬浮提示开关。 */
-    val readAloudDetachReminderEnabled: Boolean = true,
+    /** 朗读位置脱离当前页时的悬浮提示开关;关闭时手动翻页朗读跟随新页面。 */
+    val readAloudDetachReminderEnabled: Boolean = false,
     val readAloudEngineName: String = "",
     val readAloudCharacterName: String = "",
     val readAloudRoleType: SpeechRoleType = SpeechRoleType.Narrator,

+ 1 - 1
app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigContract.kt

@@ -38,7 +38,7 @@ data class ReadConfigUiState(
     val autoChangeSource: Boolean = true,
     val autoSuggestDayNight: Boolean = false,
     val readingAnchorEnabled: Boolean = true,
-    val readAloudDetachReminderEnabled: Boolean = true,
+    val readAloudDetachReminderEnabled: Boolean = false,
     val selectText: Boolean = true,
     val noAnimScrollPage: Boolean = false,
     val clickImgWay: String = "2",

+ 1 - 1
app/src/main/res/values-zh-rCN/strings.xml

@@ -2538,7 +2538,7 @@
     <string name="reading_anchor">阅读锚点</string>
     <string name="reading_anchor_summary">跳转章节两次及以上后,显示返回原阅读位置的悬浮按钮</string>
     <string name="read_aloud_detach_reminder">朗读脱离提示</string>
-    <string name="read_aloud_detach_reminder_summary">朗读位置离开当前页时,显示"回到朗读位置"悬浮按钮</string>
+    <string name="read_aloud_detach_reminder_summary">开启时朗读位置离开当前页会显示"回到朗读位置"悬浮按钮;关闭时手动翻页后朗读跟随新页面</string>
     <string name="switch_to_dark_mode_tip">当前环境光较暗,是否切换至深色模式?</string>
     <string name="switch_to_light_mode_tip">当前环境光较亮,是否切换至浅色模式?</string>
     <string name="switch_to_dark_mode_action">切换到深色模式</string>

+ 1 - 1
app/src/main/res/values-zh-rHK/strings.xml

@@ -2421,7 +2421,7 @@
     <string name="reading_anchor">閱讀錨點</string>
     <string name="reading_anchor_summary">跳轉章節兩次或以上後,顯示返回原閱讀位置的懸浮按鈕</string>
     <string name="read_aloud_detach_reminder">朗讀脫離提示</string>
-    <string name="read_aloud_detach_reminder_summary">朗讀位置離開當前頁時,顯示"回到朗讀位置"懸浮按鈕</string>
+    <string name="read_aloud_detach_reminder_summary">開啟時朗讀位置離開當前頁會顯示「回到朗讀位置」懸浮按鈕;關閉時手動翻頁後朗讀跟隨新頁面</string>
     <string name="switch_to_dark_mode_tip">當前環境光較暗,是否切換至深色模式?</string>
     <string name="switch_to_light_mode_tip">當前環境光較亮,是否切換至淺色模式?</string>
     <string name="switch_to_dark_mode_action">切換至深色模式</string>

+ 1 - 1
app/src/main/res/values-zh-rTW/strings.xml

@@ -2426,7 +2426,7 @@
     <string name="reading_anchor">閱讀錨點</string>
     <string name="reading_anchor_summary">跳轉章節兩次或以上後,顯示返回原閱讀位置的懸浮按鈕</string>
     <string name="read_aloud_detach_reminder">朗讀脫離提示</string>
-    <string name="read_aloud_detach_reminder_summary">朗讀位置離開目前頁面時,顯示「回到朗讀位置」懸浮按鈕</string>
+    <string name="read_aloud_detach_reminder_summary">開啟時朗讀位置離開目前頁面會顯示「回到朗讀位置」懸浮按鈕;關閉時手動翻頁後朗讀跟隨新頁面</string>
     <string name="switch_to_dark_mode_tip">目前環境光較暗,是否切換至深色模式?</string>
     <string name="switch_to_light_mode_tip">目前環境光較亮,是否切換至淺色模式?</string>
     <string name="switch_to_dark_mode_action">切換至深色模式</string>

+ 1 - 1
app/src/main/res/values/strings.xml

@@ -2543,7 +2543,7 @@
     <string name="reading_anchor">Reading anchor</string>
     <string name="reading_anchor_summary">Show a floating button to return to the original reading position after jumping chapters twice or more</string>
     <string name="read_aloud_detach_reminder">Read-aloud detach reminder</string>
-    <string name="read_aloud_detach_reminder_summary">Show a floating bar to return to the speaking position when the page leaves it</string>
+    <string name="read_aloud_detach_reminder_summary">When enabled, shows a button to return to the speaking position after the page leaves it; when disabled, read-aloud follows manual page turns</string>
     <string name="switch_to_dark_mode_tip">Ambient light is low. Switch to dark mode?</string>
     <string name="switch_to_light_mode_tip">Ambient light is bright. Switch to light mode?</string>
     <string name="switch_to_dark_mode_action">Switch to dark mode</string>

+ 1 - 1
app/src/test/java/io/legado/app/data/repository/ReadSettingsMappingTest.kt

@@ -212,7 +212,7 @@ private fun readSettingsMappingSamples(): List<ReadSettings> {
         base.copy(autoChangeSource = false),
         base.copy(autoSuggestDayNight = true),
         base.copy(readingAnchorEnabled = false),
-        base.copy(readAloudDetachReminderEnabled = false),
+        base.copy(readAloudDetachReminderEnabled = true),
         base.copy(selectText = false),
         base.copy(noAnimScrollPage = true),
         base.copy(optimizeRender = true),

+ 0 - 61
app/src/test/java/io/legado/app/ui/dict/DictHtmlParserTest.kt

@@ -1,61 +0,0 @@
-package io.legado.app.ui.dict
-
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertTrue
-import org.junit.Test
-
-class DictHtmlParserTest {
-
-    @Test
-    fun preservesStyleAndTextAcrossInlineImage() {
-        val document = DictHtmlParser.parse(
-            """<span style="color:#f00;text-decoration:underline">拼音<img src="tone.png">释义</span>"""
-        )
-
-        val content = document.paragraphs.single().content
-        assertEquals("拼音", (content[0] as DictHtmlInline.Text).value)
-        assertEquals("tone.png", (content[1] as DictHtmlInline.Image).source)
-        assertEquals("释义", (content[2] as DictHtmlInline.Text).value)
-        listOf(content[0], content[2]).filterIsInstance<DictHtmlInline.Text>().forEach {
-            assertEquals("#f00", it.style.color)
-            assertTrue(it.style.underline)
-        }
-    }
-
-    @Test
-    fun keepsNestedLinkOnTextAndImage() {
-        val content = DictHtmlParser.parse(
-            """<a href="https://example.com"><b>词条</b><img src="icon.png"></a>"""
-        ).paragraphs.single().content
-
-        val text = content[0] as DictHtmlInline.Text
-        val image = content[1] as DictHtmlInline.Image
-        assertTrue(text.style.bold)
-        assertEquals("https://example.com", text.link)
-        assertEquals("https://example.com", image.link)
-    }
-
-    @Test
-    fun parsesLegacyFontAndInlineCss() {
-        val text = DictHtmlParser.parse(
-            """<font color="blue"><u>A</u></font><span style="background-color:#123456;font-style:italic">B</span>"""
-        ).paragraphs.single().content.filterIsInstance<DictHtmlInline.Text>()
-
-        assertEquals("blue", text[0].style.color)
-        assertTrue(text[0].style.underline)
-        assertEquals("#123456", text[1].style.backgroundColor)
-        assertTrue(text[1].style.italic)
-    }
-
-    @Test
-    fun malformedHtmlRetainsReadableContent() {
-        val document = DictHtmlParser.parse("<div>前<span style='color:red'>中<img src=x>后</div>")
-
-        val content = document.paragraphs.single().content
-        assertEquals(
-            "前中",
-            content.filterIsInstance<DictHtmlInline.Text>().take(2).joinToString("") { it.value })
-        assertEquals("x", content.filterIsInstance<DictHtmlInline.Image>().single().source)
-        assertEquals("后", content.filterIsInstance<DictHtmlInline.Text>().last().value)
-    }
-}