fix: 修复阅读搜索定位和双页同步问题
This commit is contained in:
@@ -86,10 +86,10 @@ class SearchContentRepository {
|
|||||||
book, chapter, chapterContent, useReplace = replaceEnabled
|
book, chapter, chapterContent, useReplace = replaceEnabled
|
||||||
).toString()
|
).toString()
|
||||||
|
|
||||||
val positions = searchPosition(mContent, query, regexReplace)
|
val matches = searchPosition(mContent, query, regexReplace)
|
||||||
|
|
||||||
positions.forEachIndexed { index, position ->
|
matches.forEachIndexed { index, match ->
|
||||||
val construct = getResultAndQueryIndex(mContent, position, query)
|
val construct = getResultAndQueryIndex(mContent, match.position, match.length)
|
||||||
val result = SearchResult(
|
val result = SearchResult(
|
||||||
resultCountWithinChapter = index,
|
resultCountWithinChapter = index,
|
||||||
resultText = construct.second,
|
resultText = construct.second,
|
||||||
@@ -97,7 +97,8 @@ class SearchContentRepository {
|
|||||||
query = query,
|
query = query,
|
||||||
chapterIndex = chapter.index,
|
chapterIndex = chapter.index,
|
||||||
queryIndexInResult = construct.first,
|
queryIndexInResult = construct.first,
|
||||||
queryIndexInChapter = position,
|
queryIndexInChapter = match.position,
|
||||||
|
matchLength = match.length,
|
||||||
isRegex = regexReplace
|
isRegex = regexReplace
|
||||||
)
|
)
|
||||||
searchResultsWithinChapter.add(result)
|
searchResultsWithinChapter.add(result)
|
||||||
@@ -105,34 +106,34 @@ class SearchContentRepository {
|
|||||||
return searchResultsWithinChapter
|
return searchResultsWithinChapter
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun searchPosition(content: String, pattern: String, regexReplace: Boolean): List<Int> {
|
private fun searchPosition(content: String, pattern: String, regexReplace: Boolean): List<SearchMatch> {
|
||||||
val position: MutableList<Int> = mutableListOf()
|
val positions: MutableList<SearchMatch> = mutableListOf()
|
||||||
if (regexReplace) { // 正则表达式搜索
|
if (regexReplace) { // 正则表达式搜索
|
||||||
try {
|
try {
|
||||||
Regex(pattern).findAll(content).forEach { match ->
|
Regex(pattern).findAll(content).forEach { match ->
|
||||||
position.add(match.range.first)
|
positions.add(SearchMatch(match.range.first, match.value.length))
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
return position
|
return positions
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var index = content.indexOf(pattern)
|
var index = content.indexOf(pattern)
|
||||||
while (index >= 0) {
|
while (index >= 0) {
|
||||||
position.add(index)
|
positions.add(SearchMatch(index, pattern.length))
|
||||||
index = content.indexOf(pattern, index + pattern.length)
|
index = content.indexOf(pattern, index + pattern.length)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return position
|
return positions
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getResultAndQueryIndex(
|
private fun getResultAndQueryIndex(
|
||||||
content: String,
|
content: String,
|
||||||
queryIndexInContent: Int,
|
queryIndexInContent: Int,
|
||||||
query: String
|
matchLength: Int
|
||||||
): Pair<Int, String> {
|
): Pair<Int, String> {
|
||||||
val length = 12
|
val length = 12
|
||||||
var po1 = queryIndexInContent - length
|
var po1 = queryIndexInContent - length
|
||||||
var po2 = queryIndexInContent + query.length + length
|
var po2 = queryIndexInContent + matchLength + length
|
||||||
if (po1 < 0) {
|
if (po1 < 0) {
|
||||||
po1 = 0
|
po1 = 0
|
||||||
}
|
}
|
||||||
@@ -144,4 +145,9 @@ class SearchContentRepository {
|
|||||||
return queryIndexInResult to newText
|
return queryIndexInResult to newText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class SearchMatch(
|
||||||
|
val position: Int,
|
||||||
|
val length: Int,
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -713,15 +713,8 @@ class ReadBookController(
|
|||||||
val pos = viewModel.searchResultPositions(tc, effect.result, query)
|
val pos = viewModel.searchResultPositions(tc, effect.result, query)
|
||||||
val lineIndex = pos[1]
|
val lineIndex = pos[1]
|
||||||
val charIndex = pos[2]
|
val charIndex = pos[2]
|
||||||
val addLine = pos[3]
|
val endLineIndex = pos[3]
|
||||||
val charIndex2 = pos[4]
|
val endCharIndex = pos[4]
|
||||||
val queryLength = query.length
|
|
||||||
val endLineIndex = lineIndex + addLine.coerceAtLeast(0)
|
|
||||||
val endCharIndex = if (addLine == 0) {
|
|
||||||
charIndex + queryLength
|
|
||||||
} else {
|
|
||||||
charIndex2 + 1
|
|
||||||
}
|
|
||||||
activity.lifecycleScope.launch(Main) {
|
activity.lifecycleScope.launch(Main) {
|
||||||
navigatePageByPos(tc, pos[0])
|
navigatePageByPos(tc, pos[0])
|
||||||
markSearchResultOnPage(
|
markSearchResultOnPage(
|
||||||
|
|||||||
@@ -2202,55 +2202,102 @@ class ReadBookViewModel(
|
|||||||
): Array<Int> {
|
): Array<Int> {
|
||||||
val pages = textChapter.pages
|
val pages = textChapter.pages
|
||||||
val content = textChapter.getContent()
|
val content = textChapter.getContent()
|
||||||
val queryLength = query.length
|
if (pages.isEmpty()) return arrayOf(0, 0, 0, 0, 0)
|
||||||
|
|
||||||
|
val contentPosition = searchResult.queryIndexInChapter
|
||||||
|
.takeIf { it in content.indices && searchResultMatchesAt(content, it, searchResult, query) }
|
||||||
|
?: findSearchResultPosition(content, searchResult, query)
|
||||||
|
if (contentPosition < 0) return arrayOf(0, 0, 0, 0, 0)
|
||||||
|
|
||||||
|
val pageIndex = textChapter.getPageIndexByCharIndex(contentPosition)
|
||||||
|
.coerceIn(0, pages.lastIndex)
|
||||||
|
val currentPage = pages[pageIndex]
|
||||||
|
val matchLength = searchResult.matchLength.takeIf { it > 0 } ?: query.length
|
||||||
|
val endPosition = (contentPosition + matchLength).coerceAtMost(content.length)
|
||||||
|
val start = linePositionFor(currentPage, contentPosition, preferPreviousAtBoundary = false)
|
||||||
|
val end = linePositionFor(currentPage, endPosition, preferPreviousAtBoundary = true)
|
||||||
|
return arrayOf(pageIndex, start.lineIndex, start.charIndex, end.lineIndex, end.charIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findSearchResultPosition(
|
||||||
|
content: String,
|
||||||
|
searchResult: SearchResult,
|
||||||
|
query: String,
|
||||||
|
): Int {
|
||||||
|
if (query.isEmpty()) return -1
|
||||||
|
if (searchResult.isRegex) {
|
||||||
|
return runCatching {
|
||||||
|
Regex(query).findAll(content)
|
||||||
|
.drop(searchResult.resultCountWithinChapter)
|
||||||
|
.firstOrNull()
|
||||||
|
?.range
|
||||||
|
?.first
|
||||||
|
}.getOrNull() ?: -1
|
||||||
|
}
|
||||||
|
|
||||||
var count = 0
|
var count = 0
|
||||||
var index = content.indexOf(query)
|
var index = content.indexOf(query, ignoreCase = true)
|
||||||
while (count != searchResult.resultCountWithinChapter) {
|
while (count != searchResult.resultCountWithinChapter && index >= 0) {
|
||||||
index = content.indexOf(query, index + queryLength)
|
index = content.indexOf(query, index + query.length, ignoreCase = true)
|
||||||
count += 1
|
count += 1
|
||||||
}
|
}
|
||||||
val contentPosition = index
|
return index
|
||||||
if (contentPosition < 0) return arrayOf(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
|
|
||||||
}
|
|
||||||
|
|
||||||
val currentPage = pages[pageIndex]
|
|
||||||
val curTextLines = currentPage.lines
|
|
||||||
var lineIndex = 0
|
|
||||||
var curLine = curTextLines[lineIndex]
|
|
||||||
length = length - currentPage.text.length + curLine.text.length
|
|
||||||
if (curLine.isParagraphEnd) length++
|
|
||||||
while (length < contentPosition && lineIndex + 1 < curTextLines.size) {
|
|
||||||
lineIndex += 1
|
|
||||||
curLine = curTextLines[lineIndex]
|
|
||||||
length += curLine.text.length
|
|
||||||
if (curLine.isParagraphEnd) length++
|
|
||||||
}
|
|
||||||
|
|
||||||
val currentLine = currentPage.lines[lineIndex]
|
|
||||||
var curLineLength = currentLine.text.length
|
|
||||||
if (currentLine.isParagraphEnd) curLineLength++
|
|
||||||
length -= curLineLength
|
|
||||||
|
|
||||||
val charIndex = contentPosition - length
|
|
||||||
var addLine = 0
|
|
||||||
var charIndex2 = 0
|
|
||||||
if ((charIndex + queryLength) > curLineLength) {
|
|
||||||
addLine = 1
|
|
||||||
charIndex2 = charIndex + queryLength - curLineLength - 1
|
|
||||||
}
|
|
||||||
if ((lineIndex + addLine + 1) > currentPage.lines.size) {
|
|
||||||
addLine = -1
|
|
||||||
charIndex2 = charIndex + queryLength - curLineLength - 1
|
|
||||||
}
|
|
||||||
return arrayOf(pageIndex, lineIndex, charIndex, addLine, charIndex2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun searchResultMatchesAt(
|
||||||
|
content: String,
|
||||||
|
position: Int,
|
||||||
|
searchResult: SearchResult,
|
||||||
|
query: String,
|
||||||
|
): Boolean {
|
||||||
|
if (query.isEmpty()) return false
|
||||||
|
if (searchResult.isRegex) {
|
||||||
|
return runCatching {
|
||||||
|
Regex(query).find(content, position)?.range?.first == position
|
||||||
|
}.getOrDefault(false)
|
||||||
|
}
|
||||||
|
return position + query.length <= content.length &&
|
||||||
|
content.regionMatches(position, query, 0, query.length, ignoreCase = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun linePositionFor(
|
||||||
|
page: TextPage,
|
||||||
|
chapterPosition: Int,
|
||||||
|
preferPreviousAtBoundary: Boolean,
|
||||||
|
): SearchLinePosition {
|
||||||
|
if (page.lines.isEmpty()) return SearchLinePosition(0, 0)
|
||||||
|
var fallbackIndex = 0
|
||||||
|
page.lines.forEachIndexed { index, line ->
|
||||||
|
val lineEnd = line.chapterPosition + line.text.length +
|
||||||
|
if (line.isParagraphEnd) 1 else 0
|
||||||
|
if (chapterPosition < line.chapterPosition) {
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
|
if (preferPreviousAtBoundary && chapterPosition == line.chapterPosition && index > 0) {
|
||||||
|
val previousLine = page.lines[index - 1]
|
||||||
|
return SearchLinePosition(index - 1, previousLine.text.length)
|
||||||
|
}
|
||||||
|
if (chapterPosition <= lineEnd) {
|
||||||
|
return SearchLinePosition(
|
||||||
|
lineIndex = index,
|
||||||
|
charIndex = (chapterPosition - line.chapterPosition).coerceIn(0, line.text.length),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fallbackIndex = index
|
||||||
|
}
|
||||||
|
val lineIndex = fallbackIndex
|
||||||
|
val line = page.lines[lineIndex]
|
||||||
|
return SearchLinePosition(
|
||||||
|
lineIndex = lineIndex,
|
||||||
|
charIndex = (chapterPosition - line.chapterPosition).coerceIn(0, line.text.length),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class SearchLinePosition(
|
||||||
|
val lineIndex: Int,
|
||||||
|
val charIndex: Int,
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@@ -2264,15 +2311,8 @@ class ReadBookViewModel(
|
|||||||
val pos = searchResultPositions(textChapter, result, query)
|
val pos = searchResultPositions(textChapter, result, query)
|
||||||
val lineIndex = pos[1]
|
val lineIndex = pos[1]
|
||||||
val charIndex = pos[2]
|
val charIndex = pos[2]
|
||||||
val addLine = pos[3]
|
val endLineIndex = pos[3]
|
||||||
val charIndex2 = pos[4]
|
val endCharIndex = pos[4]
|
||||||
val queryLength = query.length
|
|
||||||
val endLineIndex = lineIndex + addLine.coerceAtLeast(0)
|
|
||||||
val endCharIndex = if (addLine == 0) {
|
|
||||||
charIndex + queryLength
|
|
||||||
} else {
|
|
||||||
charIndex2 + 1
|
|
||||||
}
|
|
||||||
_effects.tryEmit(
|
_effects.tryEmit(
|
||||||
ReadBookEffect.NavigateToSearchResult(
|
ReadBookEffect.NavigateToSearchResult(
|
||||||
result = result,
|
result = result,
|
||||||
|
|||||||
@@ -1079,7 +1079,7 @@ object ChapterProvider {
|
|||||||
viewWidth = width
|
viewWidth = width
|
||||||
viewHeight = height
|
viewHeight = height
|
||||||
upLayout()
|
upLayout()
|
||||||
postEvent(EventBus.UP_CONFIG, arrayListOf(5))
|
postEvent(EventBus.UP_CONFIG, arrayListOf(10, 5))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ data class SearchResult(
|
|||||||
val pageIndex: Int = 0,
|
val pageIndex: Int = 0,
|
||||||
val queryIndexInResult: Int = 0,
|
val queryIndexInResult: Int = 0,
|
||||||
val queryIndexInChapter: Int = 0,
|
val queryIndexInChapter: Int = 0,
|
||||||
|
val matchLength: Int = query.length,
|
||||||
val isRegex: Boolean = false,
|
val isRegex: Boolean = false,
|
||||||
val progressPercent: Float = 0f
|
val progressPercent: Float = 0f
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in New Issue
Block a user