fix: 修复阅读搜索串书和定位问题
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
val start = findSearchTextPoint(pages, contentPosition, preferPreviousLine = false)
|
||||||
var length = pages[pageIndex].text.length
|
?: return arrayOf(-1, 0, 0, 0, 0, 0)
|
||||||
while (length < contentPosition && pageIndex + 1 < pages.size) {
|
val end = findSearchTextPoint(
|
||||||
pageIndex += 1
|
pages,
|
||||||
length += pages[pageIndex].text.length
|
contentPosition + queryLength - 1,
|
||||||
}
|
preferPreviousLine = true
|
||||||
if (length < contentPosition) return arrayOf(-1, 0, 0, 0, 0, 0)
|
) ?: start
|
||||||
|
return arrayOf(
|
||||||
val currentPage = pages[pageIndex]
|
start.pageIndex,
|
||||||
val lines = currentPage.lines
|
start.lineIndex,
|
||||||
if (lines.isEmpty()) return arrayOf(-1, 0, 0, 0, 0, 0)
|
start.charIndex,
|
||||||
|
end.pageIndex - start.pageIndex,
|
||||||
var lineIndex = 0
|
end.lineIndex,
|
||||||
var currentLine = lines[lineIndex]
|
end.charIndex
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 = "",
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user