fix:修复阅读搜索定位问题
This commit is contained in:
@@ -7,6 +7,7 @@ import io.legado.app.help.book.BookHelp
|
||||
import io.legado.app.help.book.ContentProcessor
|
||||
import io.legado.app.help.book.isLocal
|
||||
import io.legado.app.help.config.AppConfig
|
||||
import io.legado.app.help.config.ReadBookConfig
|
||||
import io.legado.app.ui.book.searchContent.SearchResult
|
||||
import io.legado.app.utils.ChineseUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -21,8 +22,13 @@ class SearchContentRepository {
|
||||
private var lastSearchResults: List<SearchResult>? = null
|
||||
private var lastQueryKey: String? = null
|
||||
|
||||
fun getCache(bookUrl: String, query: String): List<SearchResult>? {
|
||||
val key = "$bookUrl-$query"
|
||||
fun getCache(
|
||||
bookUrl: String,
|
||||
query: String,
|
||||
replaceEnabled: Boolean,
|
||||
regexReplace: Boolean
|
||||
): List<SearchResult>? {
|
||||
val key = searchKey(bookUrl, query, replaceEnabled, regexReplace)
|
||||
return if (lastQueryKey == key) lastSearchResults else null
|
||||
}
|
||||
|
||||
@@ -61,10 +67,19 @@ class SearchContentRepository {
|
||||
}
|
||||
}
|
||||
lastSearchResults = allResults
|
||||
lastQueryKey = "${book.bookUrl}-$query"
|
||||
lastQueryKey = searchKey(book.bookUrl, query, replaceEnabled, regexReplace)
|
||||
emit(ArrayList(allResults))
|
||||
}.flowOn(Dispatchers.Default)
|
||||
|
||||
private fun searchKey(
|
||||
bookUrl: String,
|
||||
query: String,
|
||||
replaceEnabled: Boolean,
|
||||
regexReplace: Boolean
|
||||
): String {
|
||||
return "$bookUrl-$query-$replaceEnabled-$regexReplace-${ReadBookConfig.titleMode}"
|
||||
}
|
||||
|
||||
private suspend fun searchChapter(
|
||||
query: String,
|
||||
book: Book,
|
||||
@@ -82,9 +97,25 @@ class SearchContentRepository {
|
||||
else -> chapter.title
|
||||
}
|
||||
|
||||
val mContent = contentProcessor.getContent(
|
||||
book, chapter, chapterContent, useReplace = replaceEnabled
|
||||
).toString()
|
||||
val bodyContent = contentProcessor.getContent(
|
||||
book,
|
||||
chapter,
|
||||
chapterContent,
|
||||
includeTitle = false,
|
||||
useReplace = replaceEnabled
|
||||
)
|
||||
val includeTitle = ReadBookConfig.titleMode != 2 ||
|
||||
chapter.isVolume ||
|
||||
bodyContent.textList.isEmpty()
|
||||
val mContent = if (includeTitle) {
|
||||
val title = chapter.getDisplayTitle(
|
||||
contentProcessor.getTitleReplaceRules(),
|
||||
useReplace = replaceEnabled && book.getUseReplaceRule()
|
||||
)
|
||||
listOf(title).plus(bodyContent.textList).joinToString("\n")
|
||||
} else {
|
||||
bodyContent.toString()
|
||||
}
|
||||
|
||||
val matches = searchPosition(mContent, query, regexReplace)
|
||||
|
||||
|
||||
@@ -258,6 +258,7 @@ sealed interface ReadBookIntent {
|
||||
data class SetSearchResults(val results: List<SearchResult>, val index: Int, val query: String? = null) : ReadBookIntent
|
||||
data class SetSearchResultIndex(val index: Int) : ReadBookIntent
|
||||
data class SetShowingSearchResult(val value: Boolean) : ReadBookIntent
|
||||
data class NavigateSearchResultByOffset(val offset: Int) : ReadBookIntent
|
||||
data class NavigateToSearchResult(val result: SearchResult, val index: Int) : ReadBookIntent
|
||||
data object RestoreLastBookProgress : ReadBookIntent
|
||||
data object KeepCurrentBookProgress : ReadBookIntent
|
||||
@@ -515,7 +516,11 @@ sealed interface ReadBookEffect {
|
||||
|
||||
// ReadView operations (require Activity/View reference)
|
||||
data class UpdateReadViewConfig(val actions: Set<ConfigUpdateAction>) : ReadBookEffect
|
||||
data class UpContent(val relativePosition: Int, val resetPageOffset: Boolean) : ReadBookEffect
|
||||
data class UpContent(
|
||||
val relativePosition: Int,
|
||||
val resetPageOffset: Boolean,
|
||||
val success: (() -> Unit)? = null,
|
||||
) : ReadBookEffect
|
||||
data class UpPageAnim(val upRecorder: Boolean) : ReadBookEffect
|
||||
data object UpTime : ReadBookEffect
|
||||
data class UpBattery(val level: Int) : ReadBookEffect
|
||||
@@ -547,6 +552,7 @@ sealed interface ReadBookEffect {
|
||||
val pageIndex: Int,
|
||||
val lineIndex: Int,
|
||||
val startCharIndex: Int,
|
||||
val endRelativePage: Int,
|
||||
val endLineIndex: Int,
|
||||
val endCharIndex: Int,
|
||||
) : ReadBookEffect
|
||||
|
||||
@@ -116,6 +116,7 @@ class ReadBookController(
|
||||
}
|
||||
private val popupAction by lazy { PopupAction(activity) }
|
||||
private var screenTimeOut: Long = 0
|
||||
private var pendingSearchResultMark: IntArray? = null
|
||||
// justInitData moved to ViewModel (set on InitData intent)
|
||||
|
||||
val isAutoPage: Boolean get() = refs?.readView?.isAutoPage == true
|
||||
@@ -599,6 +600,10 @@ class ReadBookController(
|
||||
|
||||
is ReadBookEffect.UpContent -> {
|
||||
refs?.readView?.upContent(effect.relativePosition, effect.resetPageOffset)
|
||||
effect.success?.invoke()
|
||||
refs?.readView?.post {
|
||||
consumePendingSearchResultMark()
|
||||
}
|
||||
if (effect.relativePosition == 0) onUnhandledEffect(ReadBookEffect.UpSeekBar)
|
||||
}
|
||||
|
||||
@@ -611,7 +616,10 @@ class ReadBookController(
|
||||
ReadBook.loadContent(false)
|
||||
}
|
||||
|
||||
is ReadBookEffect.CancelSelect -> refs?.readView?.cancelSelect()
|
||||
is ReadBookEffect.CancelSelect -> {
|
||||
pendingSearchResultMark = null
|
||||
refs?.readView?.cancelSelect()
|
||||
}
|
||||
is ReadBookEffect.MenuImageStyleChanged -> refs?.readView?.upPageAnim()
|
||||
|
||||
// ── Simple Activity-API effects ──
|
||||
@@ -707,37 +715,35 @@ class ReadBookController(
|
||||
is ReadBookEffect.NavigateToSearchResult -> {
|
||||
if (effect.pageIndex < 0) {
|
||||
// Chapter not loaded — open it, then mark in the success callback
|
||||
ReadBook.openChapter(effect.chapterIndex) {
|
||||
ReadBook.openChapter(
|
||||
effect.chapterIndex,
|
||||
effect.result.queryIndexInChapter
|
||||
) {
|
||||
val tc = ReadBook.curTextChapter ?: return@openChapter
|
||||
val query = effect.result.query
|
||||
val pos = viewModel.searchResultPositions(tc, effect.result, query)
|
||||
val lineIndex = pos[1]
|
||||
val charIndex = pos[2]
|
||||
val endLineIndex = pos[3]
|
||||
val endCharIndex = pos[4]
|
||||
if (pos[0] < 0) return@openChapter
|
||||
activity.lifecycleScope.launch(Main) {
|
||||
navigatePageByPos(tc, pos[0])
|
||||
markSearchResultOnPage(
|
||||
intArrayOf(
|
||||
pos[0], lineIndex, charIndex, endLineIndex, endCharIndex
|
||||
)
|
||||
markSearchResultAfterNavigation(
|
||||
intArrayOf(pos[0], pos[1], pos[2], pos[3], pos[4], pos[5])
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Same chapter — navigate to page, then mark
|
||||
val tc = ReadBook.curTextChapter ?: return
|
||||
navigatePageByPos(tc, effect.pageIndex)
|
||||
markSearchResultOnPage(
|
||||
markSearchResultAfterNavigation(
|
||||
intArrayOf(
|
||||
effect.pageIndex, effect.lineIndex,
|
||||
effect.startCharIndex, effect.endLineIndex, effect.endCharIndex
|
||||
effect.startCharIndex, effect.endRelativePage,
|
||||
effect.endLineIndex, effect.endCharIndex
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is ReadBookEffect.ExitSearch -> {
|
||||
pendingSearchResultMark = null
|
||||
ReadBook.clearSearchResult()
|
||||
refs?.readView?.cancelSelect(clearSearchResult = true)
|
||||
}
|
||||
@@ -1105,33 +1111,46 @@ class ReadBookController(
|
||||
|
||||
// ── Search result navigation helpers ─────────────────────────────
|
||||
|
||||
/**
|
||||
* Navigate to the page at [pageIndex] within the current chapter.
|
||||
*/
|
||||
private fun navigatePageByPos(textChapter: TextChapter, pageIndex: Int) {
|
||||
val pagePos = textChapter.getReadLength(pageIndex)
|
||||
if (ReadBook.durChapterPos != pagePos) {
|
||||
ReadBook.durChapterPos = pagePos
|
||||
ReadBook.callBack?.upContent()
|
||||
ReadBook.callBack?.cancelSelect()
|
||||
private fun markSearchResultAfterNavigation(pos: IntArray) {
|
||||
if (pos[0] < 0) return
|
||||
val readView = refs?.readView ?: return
|
||||
pendingSearchResultMark = pos
|
||||
ReadBook.skipToPage(pos[0]) {
|
||||
readView.post {
|
||||
consumePendingSearchResultMark()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun consumePendingSearchResultMark(): Boolean {
|
||||
val pos = pendingSearchResultMark ?: return false
|
||||
val readView = refs?.readView ?: return false
|
||||
if (ReadBook.durPageIndex != pos[0] || readView.curPage.textPage.index != pos[0]) {
|
||||
return false
|
||||
}
|
||||
pendingSearchResultMark = null
|
||||
markSearchResultOnPage(pos)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark search result columns on the current page for highlighting.
|
||||
* @param pos array of [pageIndex, lineIndex, startCharIndex, endLineIndex, endCharIndex]
|
||||
* @param pos array of [pageIndex, lineIndex, startCharIndex, endRelativePage, endLineIndex, endCharIndex]
|
||||
*/
|
||||
private fun markSearchResultOnPage(pos: IntArray) {
|
||||
val readView = refs?.readView ?: return
|
||||
val lineIndex = pos[1]
|
||||
val startCharIndex = pos[2]
|
||||
val endLineIndex = pos[3]
|
||||
val endCharIndex = pos[4]
|
||||
val endRelativePage = pos[3]
|
||||
val endLineIndex = pos[4]
|
||||
val endCharIndex = pos[5]
|
||||
ReadBook.clearSearchResult()
|
||||
readView.cancelSelect(clearSearchResult = true)
|
||||
isSelectingSearchResult = true
|
||||
try {
|
||||
readView.curPage.selectStartMoveIndex(0, lineIndex, startCharIndex)
|
||||
readView.curPage.selectEndMoveIndex(0, endLineIndex, endCharIndex)
|
||||
readView.curPage.selectEndMoveIndex(endRelativePage, endLineIndex, endCharIndex)
|
||||
readView.isTextSelected = true
|
||||
} finally {
|
||||
isSelectingSearchResult = false
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.kyant.backdrop.backdrops.layerBackdrop
|
||||
import com.kyant.backdrop.backdrops.rememberLayerBackdrop
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
@@ -99,6 +102,7 @@ fun ReadBookRouteScreen(
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
val readPreferences by viewModel.readPreferences.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val effectsReady = remember(viewModel) { CompletableDeferred<Unit>() }
|
||||
val menuBackdrop = rememberLayerBackdrop()
|
||||
val menuHazeState = remember { HazeState() }
|
||||
@@ -375,18 +379,20 @@ fun ReadBookRouteScreen(
|
||||
|
||||
// ── Search result collection (from Navigation3 search route) ──────
|
||||
|
||||
LaunchedEffect(viewModel) {
|
||||
SearchContentResult.results.collect { result ->
|
||||
effectsReady.await()
|
||||
viewModel.onIntent(
|
||||
ReadBookIntent.SetSearchResults(result.searchResults, result.index, result.query)
|
||||
)
|
||||
result.searchResults.getOrNull(result.index)?.let { searchResult ->
|
||||
LaunchedEffect(viewModel, lifecycleOwner) {
|
||||
lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
SearchContentResult.results.collect { result ->
|
||||
effectsReady.await()
|
||||
viewModel.onIntent(
|
||||
ReadBookIntent.NavigateToSearchResult(searchResult, result.index)
|
||||
ReadBookIntent.SetSearchResults(result.searchResults, result.index, result.query)
|
||||
)
|
||||
result.searchResults.getOrNull(result.index)?.let { searchResult ->
|
||||
viewModel.onIntent(
|
||||
ReadBookIntent.NavigateToSearchResult(searchResult, result.index)
|
||||
)
|
||||
}
|
||||
SearchContentResult.resetReplayCache()
|
||||
}
|
||||
SearchContentResult.resetReplayCache()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,14 +44,7 @@ fun ReadBookSearchBar(
|
||||
) {
|
||||
AppFloatingActionButton(
|
||||
onClick = {
|
||||
val prevIndex = currentIndex - 1
|
||||
val result = state.searchResultList.getOrNull(prevIndex)
|
||||
?: return@AppFloatingActionButton
|
||||
onIntent(
|
||||
ReadBookIntent.NavigateToSearchResult(
|
||||
result, prevIndex
|
||||
)
|
||||
)
|
||||
onIntent(ReadBookIntent.NavigateSearchResultByOffset(-1))
|
||||
},
|
||||
tooltipText = "上一个结果",
|
||||
containerColor = LegadoTheme.colorScheme.surfaceContainerLow,
|
||||
@@ -76,14 +69,7 @@ fun ReadBookSearchBar(
|
||||
) {
|
||||
AppFloatingActionButton(
|
||||
onClick = {
|
||||
val nextIndex = currentIndex + 1
|
||||
val result = state.searchResultList.getOrNull(nextIndex)
|
||||
?: return@AppFloatingActionButton
|
||||
onIntent(
|
||||
ReadBookIntent.NavigateToSearchResult(
|
||||
result, nextIndex
|
||||
)
|
||||
)
|
||||
onIntent(ReadBookIntent.NavigateSearchResultByOffset(1))
|
||||
},
|
||||
tooltipText = "下一个结果",
|
||||
containerColor = LegadoTheme.colorScheme.surfaceContainerLow,
|
||||
|
||||
@@ -258,6 +258,10 @@ class ReadBookViewModel(
|
||||
_uiState.update { it.copy(isShowingSearchResult = intent.value) }
|
||||
}
|
||||
|
||||
is ReadBookIntent.NavigateSearchResultByOffset -> {
|
||||
navigateSearchResultByOffset(intent.offset)
|
||||
}
|
||||
|
||||
is ReadBookIntent.NavigateToSearchResult -> {
|
||||
ReadBook.saveCurrentBookProgress()
|
||||
_uiState.update {
|
||||
@@ -1265,9 +1269,8 @@ class ReadBookViewModel(
|
||||
) {
|
||||
_uiState.update { syncFromReadBook(it) }
|
||||
_effects.tryEmit(
|
||||
ReadBookEffect.UpContent(relativePosition, resetPageOffset)
|
||||
ReadBookEffect.UpContent(relativePosition, resetPageOffset, success)
|
||||
)
|
||||
success?.invoke()
|
||||
}
|
||||
|
||||
override suspend fun upContentAwait(
|
||||
@@ -1278,9 +1281,8 @@ class ReadBookViewModel(
|
||||
withContext(Main.immediate) {
|
||||
_uiState.update { syncFromReadBook(it) }
|
||||
_effects.tryEmit(
|
||||
ReadBookEffect.UpContent(relativePosition, resetPageOffset)
|
||||
ReadBookEffect.UpContent(relativePosition, resetPageOffset, success)
|
||||
)
|
||||
success?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2216,102 +2218,86 @@ class ReadBookViewModel(
|
||||
): Array<Int> {
|
||||
val pages = textChapter.pages
|
||||
val content = textChapter.getContent()
|
||||
if (pages.isEmpty()) return arrayOf(0, 0, 0, 0, 0)
|
||||
if (pages.isEmpty()) return arrayOf(-1, 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 match = findSearchResultMatch(content, searchResult, query)
|
||||
?: return arrayOf(-1, 0, 0, 0, 0, 0)
|
||||
val contentPosition = match.first
|
||||
val queryLength = match.second
|
||||
if (contentPosition < 0 || queryLength <= 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 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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findSearchResultPosition(
|
||||
private fun findSearchResultMatch(
|
||||
content: String,
|
||||
searchResult: SearchResult,
|
||||
query: String,
|
||||
): Int {
|
||||
if (query.isEmpty()) return -1
|
||||
): Pair<Int, Int>? {
|
||||
if (query.isEmpty()) return null
|
||||
if (searchResult.isRegex) {
|
||||
return runCatching {
|
||||
Regex(query).findAll(content)
|
||||
.drop(searchResult.resultCountWithinChapter)
|
||||
.firstOrNull()
|
||||
?.range
|
||||
?.first
|
||||
}.getOrNull() ?: -1
|
||||
?.let { it.range.first to it.value.length }
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
var count = 0
|
||||
var index = content.indexOf(query, ignoreCase = true)
|
||||
var index = content.indexOf(query)
|
||||
while (count != searchResult.resultCountWithinChapter && index >= 0) {
|
||||
index = content.indexOf(query, index + query.length, ignoreCase = true)
|
||||
index = content.indexOf(query, index + query.length)
|
||||
count += 1
|
||||
}
|
||||
return index
|
||||
return index.takeIf { it >= 0 }?.let { it to query.length }
|
||||
}
|
||||
|
||||
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]
|
||||
* so the Controller can navigate and highlight.
|
||||
@@ -2325,8 +2311,9 @@ class ReadBookViewModel(
|
||||
val pos = searchResultPositions(textChapter, result, query)
|
||||
val lineIndex = pos[1]
|
||||
val charIndex = pos[2]
|
||||
val endLineIndex = pos[3]
|
||||
val endCharIndex = pos[4]
|
||||
val endRelativePage = pos[3]
|
||||
val endLineIndex = pos[4]
|
||||
val endCharIndex = pos[5]
|
||||
_effects.tryEmit(
|
||||
ReadBookEffect.NavigateToSearchResult(
|
||||
result = result,
|
||||
@@ -2334,6 +2321,7 @@ class ReadBookViewModel(
|
||||
pageIndex = pos[0],
|
||||
lineIndex = lineIndex,
|
||||
startCharIndex = charIndex,
|
||||
endRelativePage = endRelativePage,
|
||||
endLineIndex = endLineIndex,
|
||||
endCharIndex = endCharIndex,
|
||||
)
|
||||
@@ -2347,6 +2335,7 @@ class ReadBookViewModel(
|
||||
pageIndex = -1,
|
||||
lineIndex = 0,
|
||||
startCharIndex = 0,
|
||||
endRelativePage = 0,
|
||||
endLineIndex = 0,
|
||||
endCharIndex = 0,
|
||||
)
|
||||
@@ -3450,6 +3439,18 @@ class ReadBookViewModel(
|
||||
_effects.tryEmit(ReadBookEffect.ExitSearch)
|
||||
}
|
||||
|
||||
private fun navigateSearchResultByOffset(offset: Int) {
|
||||
val state = _uiState.value
|
||||
val currentIndex = state.searchResultIndex.coerceSearchResultIndex(
|
||||
state.searchResultList.size
|
||||
)
|
||||
val targetIndex = currentIndex + offset
|
||||
val result = state.searchResultList.getOrNull(targetIndex) ?: return
|
||||
ReadBook.saveCurrentBookProgress()
|
||||
_uiState.update { it.copy(searchResultIndex = targetIndex) }
|
||||
navigateToSearchResult(result)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
if (BaseReadAloudService.isRun && BaseReadAloudService.pause) {
|
||||
|
||||
@@ -81,7 +81,12 @@ class SearchContentViewModel(
|
||||
private fun initBook() {
|
||||
viewModelScope.launch {
|
||||
val book = bookRepository.getBook(bookUrl)
|
||||
val cachedResults = searchContentRepository.getCache(bookUrl, _searchQuery.value)
|
||||
val cachedResults = searchContentRepository.getCache(
|
||||
bookUrl,
|
||||
_searchQuery.value,
|
||||
_replaceEnabled.value,
|
||||
_regexReplace.value
|
||||
)
|
||||
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
|
||||
Reference in New Issue
Block a user