|
@@ -109,7 +109,6 @@ import io.legado.app.model.translation.TranslationManager
|
|
|
import io.legado.app.model.webBook.WebBook
|
|
import io.legado.app.model.webBook.WebBook
|
|
|
import io.legado.app.service.BaseReadAloudService
|
|
import io.legado.app.service.BaseReadAloudService
|
|
|
import io.legado.app.ui.book.read.page.entities.TextChapter
|
|
import io.legado.app.ui.book.read.page.entities.TextChapter
|
|
|
-import io.legado.app.ui.book.read.page.entities.TextPage
|
|
|
|
|
import io.legado.app.ui.book.searchContent.SearchResult
|
|
import io.legado.app.ui.book.searchContent.SearchResult
|
|
|
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
|
|
import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
|
|
|
import io.legado.app.ui.widget.components.importComponents.ImportItemWrapper
|
|
import io.legado.app.ui.widget.components.importComponents.ImportItemWrapper
|
|
@@ -3225,120 +3224,12 @@ class ReadBookViewModel(
|
|
|
reverseContent()
|
|
reverseContent()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /** 纯定位逻辑见 [ReadSearchResultLocator];这里保留转发入口供 ReadBookController 调用。 */
|
|
|
fun searchResultPositions(
|
|
fun searchResultPositions(
|
|
|
textChapter: TextChapter,
|
|
textChapter: TextChapter,
|
|
|
searchResult: SearchResult,
|
|
searchResult: SearchResult,
|
|
|
query: String = _uiState.value.searchContentQuery,
|
|
query: String = _uiState.value.searchContentQuery,
|
|
|
- ): Array<Int> {
|
|
|
|
|
- val pages = textChapter.pages
|
|
|
|
|
- val content = textChapter.getContent()
|
|
|
|
|
- if (pages.isEmpty()) return arrayOf(-1, 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)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- 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(
|
|
|
|
|
- content: String,
|
|
|
|
|
- searchResult: SearchResult,
|
|
|
|
|
- query: String,
|
|
|
|
|
- ): Pair<Int, Int>? {
|
|
|
|
|
- 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) {
|
|
|
|
|
- return runCatching {
|
|
|
|
|
- Regex(query).findAll(content)
|
|
|
|
|
- .drop(searchResult.resultCountWithinChapter)
|
|
|
|
|
- .firstOrNull()
|
|
|
|
|
- ?.let { it.range.first to it.value.length }
|
|
|
|
|
- }.getOrNull()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- var count = 0
|
|
|
|
|
- var index = content.indexOf(query)
|
|
|
|
|
- while (count != searchResult.resultCountWithinChapter && index >= 0) {
|
|
|
|
|
- index = content.indexOf(query, index + query.length)
|
|
|
|
|
- count += 1
|
|
|
|
|
- }
|
|
|
|
|
- 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
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ ): Array<Int> = ReadSearchResultLocator.positions(textChapter, searchResult, query)
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Compute the search result position and emit [ReadBookEffect.NavigateToSearchResult]
|
|
* Compute the search result position and emit [ReadBookEffect.NavigateToSearchResult]
|
|
@@ -5038,12 +4929,6 @@ private const val BRIGHT_LUX_THRESHOLD = 100f
|
|
|
private const val LIGHT_LUMINANCE_THRESHOLD = 0.35
|
|
private const val LIGHT_LUMINANCE_THRESHOLD = 0.35
|
|
|
private const val REMINDER_COOLDOWN_MS = 10 * 60 * 1000L
|
|
private const val REMINDER_COOLDOWN_MS = 10 * 60 * 1000L
|
|
|
|
|
|
|
|
-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)
|
|
|
}
|
|
}
|