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

fix: 修复阅读搜索串书和定位问题

HapeLee 4 месяцев назад
Родитель
Сommit
f0f7eaf5bc

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

@@ -122,6 +122,7 @@ class SearchContentRepository {
         matches.forEachIndexed { index, match ->
         matches.forEachIndexed { index, match ->
             val construct = getResultAndQueryIndex(mContent, match.position, match.length)
             val construct = getResultAndQueryIndex(mContent, match.position, match.length)
             val result = SearchResult(
             val result = SearchResult(
+                bookUrl = book.bookUrl,
                 resultCountWithinChapter = index,
                 resultCountWithinChapter = index,
                 resultText = construct.second,
                 resultText = construct.second,
                 chapterTitle = chapter.title,
                 chapterTitle = chapter.title,

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt

@@ -717,7 +717,7 @@ class ReadBookController(
                     // Chapter not loaded — open it, then mark in the success callback
                     // Chapter not loaded — open it, then mark in the success callback
                     ReadBook.openChapter(
                     ReadBook.openChapter(
                         effect.chapterIndex,
                         effect.chapterIndex,
-                        effect.result.queryIndexInChapter
+                        0
                     ) {
                     ) {
                         val tc = ReadBook.curTextChapter ?: return@openChapter
                         val tc = ReadBook.curTextChapter ?: return@openChapter
                         val query = effect.result.query
                         val query = effect.result.query

+ 5 - 0
app/src/main/java/io/legado/app/ui/book/read/ReadBookRouteScreen.kt

@@ -31,6 +31,7 @@ import io.legado.app.R
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.ReadMenuBlurMode
 import io.legado.app.constant.ReadMenuBlurMode
 import io.legado.app.help.IntentHelp
 import io.legado.app.help.IntentHelp
+import io.legado.app.model.ReadBook
 import io.legado.app.ui.book.info.BookInfoActivity
 import io.legado.app.ui.book.info.BookInfoActivity
 import io.legado.app.ui.book.read.page.ContentTextView
 import io.legado.app.ui.book.read.page.ContentTextView
 import io.legado.app.ui.book.read.page.ReadView
 import io.legado.app.ui.book.read.page.ReadView
@@ -383,6 +384,10 @@ fun ReadBookRouteScreen(
         lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
         lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
             SearchContentResult.results.collect { result ->
             SearchContentResult.results.collect { result ->
                 effectsReady.await()
                 effectsReady.await()
+                if (result.bookUrl != ReadBook.book?.bookUrl) {
+                    SearchContentResult.resetReplayCache()
+                    return@collect
+                }
                 viewModel.onIntent(
                 viewModel.onIntent(
                     ReadBookIntent.SetSearchResults(result.searchResults, result.index, result.query)
                     ReadBookIntent.SetSearchResults(result.searchResults, result.index, result.query)
                 )
                 )

+ 78 - 44
app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt

@@ -2241,50 +2241,21 @@ class ReadBookViewModel(
             return arrayOf(-1, 0, 0, 0, 0, 0)
             return arrayOf(-1, 0, 0, 0, 0, 0)
         }
         }
 
 
-        var pageIndex = 0
-        var length = pages[pageIndex].text.length
-        while (length < contentPosition && pageIndex + 1 < pages.size) {
-            pageIndex += 1
-            length += pages[pageIndex].text.length
-        }
-        if (length < contentPosition) return arrayOf(-1, 0, 0, 0, 0, 0)
-
-        val currentPage = pages[pageIndex]
-        val lines = currentPage.lines
-        if (lines.isEmpty()) return arrayOf(-1, 0, 0, 0, 0, 0)
-
-        var lineIndex = 0
-        var currentLine = lines[lineIndex]
-        length = length - currentPage.text.length + currentLine.text.length
-        if (currentLine.isParagraphEnd) length++
-        while (length <= contentPosition && lineIndex + 1 < lines.size) {
-            lineIndex += 1
-            currentLine = lines[lineIndex]
-            length += currentLine.text.length
-            if (currentLine.isParagraphEnd) length++
-        }
-
-        var currentLineLength = currentLine.text.length
-        if (currentLine.isParagraphEnd) currentLineLength++
-        length -= currentLineLength
-
-        val charIndex = contentPosition - length
-        var addLine = 0
-        var charIndex2 = 0
-        if ((charIndex + queryLength) > currentLineLength) {
-            addLine = 1
-            charIndex2 = charIndex + queryLength - currentLineLength - 1
-        }
-        if ((lineIndex + addLine + 1) > currentPage.lines.size) {
-            addLine = -1
-            charIndex2 = charIndex + queryLength - currentLineLength - 1
-        }
-        return when (addLine) {
-            0 -> arrayOf(pageIndex, lineIndex, charIndex, 0, lineIndex, charIndex + queryLength - 1)
-            1 -> arrayOf(pageIndex, lineIndex, charIndex, 0, lineIndex + 1, charIndex2)
-            -1 -> arrayOf(pageIndex, lineIndex, charIndex, 1, 0, charIndex2)
-            else -> arrayOf(pageIndex, lineIndex, charIndex, 0, lineIndex, charIndex)
-        }
+        val start = findSearchTextPoint(pages, contentPosition, preferPreviousLine = false)
+            ?: return arrayOf(-1, 0, 0, 0, 0, 0)
+        val end = findSearchTextPoint(
+            pages,
+            contentPosition + queryLength - 1,
+            preferPreviousLine = true
+        ) ?: start
+        return arrayOf(
+            start.pageIndex,
+            start.lineIndex,
+            start.charIndex,
+            end.pageIndex - start.pageIndex,
+            end.lineIndex,
+            end.charIndex
+        )
     }
     }
 
 
     private fun findSearchResultMatch(
     private fun findSearchResultMatch(
@@ -2293,6 +2264,26 @@ class ReadBookViewModel(
         query: String,
         query: String,
     ): Pair<Int, Int>? {
     ): Pair<Int, Int>? {
         if (query.isEmpty()) return null
         if (query.isEmpty()) return null
+        val directLength = if (searchResult.matchLength > 0) searchResult.matchLength else query.length
+        val directIndex = searchResult.queryIndexInChapter
+        if (directIndex >= 0 && directIndex + directLength <= content.length) {
+            val directMatch = if (searchResult.isRegex) {
+                runCatching {
+                    Regex(query).matches(content.substring(directIndex, directIndex + directLength))
+                }.getOrDefault(false)
+            } else {
+                content.regionMatches(
+                    directIndex,
+                    query,
+                    0,
+                    query.length,
+                    ignoreCase = false
+                )
+            }
+            if (directMatch) {
+                return directIndex to directLength
+            }
+        }
         if (searchResult.isRegex) {
         if (searchResult.isRegex) {
             return runCatching {
             return runCatching {
                 Regex(query).findAll(content)
                 Regex(query).findAll(content)
@@ -2311,6 +2302,43 @@ class ReadBookViewModel(
         return index.takeIf { it >= 0 }?.let { it to query.length }
         return index.takeIf { it >= 0 }?.let { it to query.length }
     }
     }
 
 
+    private fun findSearchTextPoint(
+        pages: List<TextPage>,
+        contentPosition: Int,
+        preferPreviousLine: Boolean,
+    ): SearchTextPoint? {
+        var fallback: SearchTextPoint? = null
+        pages.forEachIndexed { pageIndex, page ->
+            page.lines.forEachIndexed { lineIndex, line ->
+                if (line.columns.isEmpty()) return@forEachIndexed
+                val lineStart = line.chapterPosition
+                val lineEndExclusive = lineStart + line.charSize
+                if (contentPosition in lineStart until lineEndExclusive) {
+                    return SearchTextPoint(
+                        pageIndex = pageIndex,
+                        lineIndex = lineIndex,
+                        charIndex = (contentPosition - lineStart).coerceIn(0, line.columns.lastIndex)
+                    )
+                }
+                if (preferPreviousLine && line.isParagraphEnd && contentPosition == lineEndExclusive) {
+                    return SearchTextPoint(
+                        pageIndex = pageIndex,
+                        lineIndex = lineIndex,
+                        charIndex = line.columns.lastIndex
+                    )
+                }
+                if (contentPosition >= lineEndExclusive) {
+                    fallback = SearchTextPoint(
+                        pageIndex = pageIndex,
+                        lineIndex = lineIndex,
+                        charIndex = line.columns.lastIndex
+                    )
+                }
+            }
+        }
+        return fallback
+    }
+
     /**
     /**
      * Compute the search result position and emit [ReadBookEffect.NavigateToSearchResult]
      * Compute the search result position and emit [ReadBookEffect.NavigateToSearchResult]
      * so the Controller can navigate and highlight.
      * so the Controller can navigate and highlight.
@@ -3572,6 +3600,12 @@ private const val TOOL_BUTTON_PREFS = "tool_button_config"
 private const val TOOL_BUTTON_KEY = "tool_buttons"
 private const val TOOL_BUTTON_KEY = "tool_buttons"
 private const val DEFAULT_ENABLED_BUTTON_COUNT = 5
 private const val DEFAULT_ENABLED_BUTTON_COUNT = 5
 
 
+private data class SearchTextPoint(
+    val pageIndex: Int,
+    val lineIndex: Int,
+    val charIndex: Int,
+)
+
 private fun Int.coerceSearchResultIndex(resultSize: Int): Int {
 private fun Int.coerceSearchResultIndex(resultSize: Int): Int {
     return if (resultSize <= 0) 0 else coerceIn(0, resultSize - 1)
     return if (resultSize <= 0) 0 else coerceIn(0, resultSize - 1)
 }
 }

+ 3 - 0
app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentResult.kt

@@ -1,5 +1,6 @@
 package io.legado.app.ui.book.searchContent
 package io.legado.app.ui.book.searchContent
 
 
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asSharedFlow
 
 
@@ -13,6 +14,7 @@ import kotlinx.coroutines.flow.asSharedFlow
 object SearchContentResult {
 object SearchContentResult {
 
 
     data class Result(
     data class Result(
+        val bookUrl: String,
         val searchResults: List<SearchResult>,
         val searchResults: List<SearchResult>,
         val index: Int,
         val index: Int,
         val query: String,
         val query: String,
@@ -25,6 +27,7 @@ object SearchContentResult {
         _results.tryEmit(result)
         _results.tryEmit(result)
     }
     }
 
 
+    @OptIn(ExperimentalCoroutinesApi::class)
     fun resetReplayCache() {
     fun resetReplayCache() {
         _results.resetReplayCache()
         _results.resetReplayCache()
     }
     }

+ 1 - 0
app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentViewModel.kt

@@ -195,6 +195,7 @@ class SearchContentViewModel(
         if (index < 0) return false
         if (index < 0) return false
         SearchContentResult.emitResult(
         SearchContentResult.emitResult(
             SearchContentResult.Result(
             SearchContentResult.Result(
+                bookUrl = bookUrl,
                 searchResults = results,
                 searchResults = results,
                 index = index,
                 index = index,
                 query = searchResult.query
                 query = searchResult.query

+ 1 - 0
app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt

@@ -11,6 +11,7 @@ import android.text.style.UnderlineSpan
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
 
 
 data class SearchResult(
 data class SearchResult(
+    val bookUrl: String = "",
     val resultCount: Int = 0,
     val resultCount: Int = 0,
     val resultCountWithinChapter: Int = 0,
     val resultCountWithinChapter: Int = 0,
     val resultText: String = "",
     val resultText: String = "",

+ 1 - 0
app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt

@@ -354,6 +354,7 @@ fun MainActivity.mainEntryProvider(
 
 
     entry<MainRouteSearchContent> { route ->
     entry<MainRouteSearchContent> { route ->
         val viewModel = koinViewModel<SearchContentViewModel>(
         val viewModel = koinViewModel<SearchContentViewModel>(
+            key = route.bookUrl,
             parameters = { parametersOf(route) }
             parameters = { parametersOf(route) }
         )
         )
         SearchContentScreen(
         SearchContentScreen(