Merge pull request #2072 from 821938089/fix-content-search-bug

优化全文搜索
pull/2076/head
kunfei 2 years ago committed by GitHub
commit 839517e5d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 35
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  2. 38
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  3. 22
      app/src/main/java/io/legado/app/ui/book/read/SearchMenu.kt
  4. 3
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  5. 4
      app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt
  6. 22
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt
  7. 4
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentAdapter.kt
  8. 2
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentViewModel.kt
  9. 11
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt

@ -113,13 +113,15 @@ class ReadBookActivity : BaseReadBookActivity(),
it ?: return@registerForActivityResult it ?: return@registerForActivityResult
it.data?.let { data -> it.data?.let { data ->
val key = data.getLongExtra("key", System.currentTimeMillis()) val key = data.getLongExtra("key", System.currentTimeMillis())
val index = data.getIntExtra("index", 0)
val searchResult = IntentData.get<SearchResult>("searchResult$key") val searchResult = IntentData.get<SearchResult>("searchResult$key")
val searchResultList = IntentData.get<List<SearchResult>>("searchResultList$key") val searchResultList = IntentData.get<List<SearchResult>>("searchResultList$key")
if (searchResult != null && searchResultList != null) { if (searchResult != null && searchResultList != null) {
viewModel.searchContentQuery = searchResult.query viewModel.searchContentQuery = searchResult.query
binding.searchMenu.upSearchResultList(searchResultList) binding.searchMenu.upSearchResultList(searchResultList)
isShowingSearchResult = true isShowingSearchResult = true
binding.searchMenu.updateSearchResultIndex(searchResultList.indexOf(searchResult)) viewModel.searchResultIndex = index
binding.searchMenu.updateSearchResultIndex(index)
binding.searchMenu.selectedSearchResult?.let { currentResult -> binding.searchMenu.selectedSearchResult?.let { currentResult ->
skipToSearch(currentResult) skipToSearch(currentResult)
showActionMenu() showActionMenu()
@ -888,6 +890,12 @@ class ReadBookActivity : BaseReadBookActivity(),
searchContentActivity.launch(Intent(this, SearchContentActivity::class.java).apply { searchContentActivity.launch(Intent(this, SearchContentActivity::class.java).apply {
putExtra("bookUrl", it.bookUrl) putExtra("bookUrl", it.bookUrl)
putExtra("searchWord", searchWord ?: viewModel.searchContentQuery) putExtra("searchWord", searchWord ?: viewModel.searchContentQuery)
putExtra("searchResultIndex", viewModel.searchResultIndex)
viewModel.searchResultList?.first()?.let {
if (it.query == viewModel.searchContentQuery) {
IntentData.put("searchResultList", viewModel.searchResultList)
}
}
}) })
} }
} }
@ -930,6 +938,8 @@ class ReadBookActivity : BaseReadBookActivity(),
isShowingSearchResult = false isShowingSearchResult = false
binding.searchMenu.invalidate() binding.searchMenu.invalidate()
binding.searchMenu.invisible() binding.searchMenu.invisible()
binding.readView.isTextSelected = false
binding.readView.curPage.cancelSelect(true)
} }
} }
@ -1052,7 +1062,8 @@ class ReadBookActivity : BaseReadBookActivity(),
} }
} }
override fun navigateToSearch(searchResult: SearchResult) { override fun navigateToSearch(searchResult: SearchResult, index: Int) {
viewModel.searchResultIndex = index
skipToSearch(searchResult) skipToSearch(searchResult)
} }
@ -1062,22 +1073,23 @@ class ReadBookActivity : BaseReadBookActivity(),
fun jumpToPosition() { fun jumpToPosition() {
ReadBook.curTextChapter?.let { ReadBook.curTextChapter?.let {
binding.searchMenu.updateSearchInfo() binding.searchMenu.updateSearchInfo()
val positions = viewModel.searchResultPositions(it, searchResult) val (pageIndex, lineIndex, charIndex, addLine, charIndex2) =
ReadBook.skipToPage(positions[0]) { viewModel.searchResultPositions(it, searchResult)
ReadBook.skipToPage(pageIndex) {
launch { launch {
isSelectingSearchResult = true isSelectingSearchResult = true
binding.readView.curPage.selectStartMoveIndex(0, positions[1], positions[2]) binding.readView.curPage.selectStartMoveIndex(0, lineIndex, charIndex)
when (positions[3]) { when (addLine) {
0 -> binding.readView.curPage.selectEndMoveIndex( 0 -> binding.readView.curPage.selectEndMoveIndex(
0, 0,
positions[1], lineIndex,
positions[2] + viewModel.searchContentQuery.length - 1 charIndex + viewModel.searchContentQuery.length - 1
) )
1 -> binding.readView.curPage.selectEndMoveIndex( 1 -> binding.readView.curPage.selectEndMoveIndex(
0, positions[1] + 1, positions[4] 0, lineIndex + 1, charIndex2
) )
//consider change page, jump to scroll position //consider change page, jump to scroll position
-1 -> binding.readView.curPage.selectEndMoveIndex(1, 0, positions[4]) -1 -> binding.readView.curPage.selectEndMoveIndex(1, 0, charIndex2)
} }
binding.readView.isTextSelected = true binding.readView.isTextSelected = true
isSelectingSearchResult = false isSelectingSearchResult = false
@ -1196,6 +1208,9 @@ class ReadBookActivity : BaseReadBookActivity(),
observeEvent<String>(PreferKey.showBrightnessView) { observeEvent<String>(PreferKey.showBrightnessView) {
readMenu.upBrightnessState() readMenu.upBrightnessState()
} }
observeEvent<List<SearchResult>>(EventBus.SEARCH_RESULT) {
viewModel.searchResultList = it
}
} }
private fun upScreenTimeOut() { private fun upScreenTimeOut() {

@ -34,7 +34,6 @@ import io.legado.app.utils.toStringArray
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/** /**
* 阅读界面数据处理 * 阅读界面数据处理
@ -43,6 +42,8 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
val permissionDenialLiveData = MutableLiveData<Int>() val permissionDenialLiveData = MutableLiveData<Int>()
var isInitFinish = false var isInitFinish = false
var searchContentQuery = "" var searchContentQuery = ""
var searchResultList: List<SearchResult>? = null
var searchResultIndex: Int = 0
private var changeSourceCoroutine: Coroutine<*>? = null private var changeSourceCoroutine: Coroutine<*>? = null
/** /**
@ -360,53 +361,54 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
// calculate search result's pageIndex // calculate search result's pageIndex
val pages = textChapter.pages val pages = textChapter.pages
val content = textChapter.getContent() val content = textChapter.getContent()
val queryLength = searchContentQuery.length
var count = 0 var count = 0
var index = content.indexOf(searchContentQuery) var index = content.indexOf(searchContentQuery)
while (count != searchResult.resultCountWithinChapter) { while (count != searchResult.resultCountWithinChapter) {
index = content.indexOf(searchContentQuery, index + 1) index = content.indexOf(searchContentQuery, index + queryLength)
count += 1 count += 1
} }
val contentPosition = index val contentPosition = index
var pageIndex = 0 var pageIndex = 0
var length = pages[pageIndex].text.length var length = pages[pageIndex].text.length
while (length < contentPosition) { while (length < contentPosition && pageIndex + 1 < pages.size) {
pageIndex += 1 pageIndex += 1
if (pageIndex > pages.size) {
pageIndex = pages.size
break
}
length += pages[pageIndex].text.length length += pages[pageIndex].text.length
} }
// calculate search result's lineIndex // calculate search result's lineIndex
val currentPage = pages[pageIndex] val currentPage = pages[pageIndex]
val curTextLines = currentPage.textLines
var lineIndex = 0 var lineIndex = 0
length = length - currentPage.text.length + currentPage.textLines[lineIndex].text.length var curLine = curTextLines[lineIndex]
while (length < contentPosition) { length = length - currentPage.text.length + curLine.text.length
if (curLine.isParagraphEnd) length++
while (length < contentPosition && lineIndex + 1 < curTextLines.size) {
lineIndex += 1 lineIndex += 1
if (lineIndex > currentPage.textLines.size) { curLine = curTextLines[lineIndex]
lineIndex = currentPage.textLines.size length += curLine.text.length
break if (curLine.isParagraphEnd) length++
}
length += currentPage.textLines[lineIndex].text.length
} }
// charIndex // charIndex
val currentLine = currentPage.textLines[lineIndex] val currentLine = currentPage.textLines[lineIndex]
length -= currentLine.text.length var curLineLength = currentLine.text.length
if (currentLine.isParagraphEnd) curLineLength++
length -= curLineLength
val charIndex = contentPosition - length val charIndex = contentPosition - length
var addLine = 0 var addLine = 0
var charIndex2 = 0 var charIndex2 = 0
// change line // change line
if ((charIndex + searchContentQuery.length) > currentLine.text.length) { if ((charIndex + queryLength) > curLineLength) {
addLine = 1 addLine = 1
charIndex2 = charIndex + searchContentQuery.length - currentLine.text.length - 1 charIndex2 = charIndex + queryLength - curLineLength - 1
} }
// changePage // changePage
if ((lineIndex + addLine + 1) > currentPage.textLines.size) { if ((lineIndex + addLine + 1) > currentPage.textLines.size) {
addLine = -1 addLine = -1
charIndex2 = charIndex + searchContentQuery.length - currentLine.text.length - 1 charIndex2 = charIndex + queryLength - curLineLength - 1
} }
return arrayOf(pageIndex, lineIndex, charIndex, addLine, charIndex2) return arrayOf(pageIndex, lineIndex, charIndex, addLine, charIndex2)
} }

@ -148,22 +148,34 @@ class SearchMenu @JvmOverloads constructor(
fabLeft.setOnClickListener { fabLeft.setOnClickListener {
updateSearchResultIndex(currentSearchResultIndex - 1) updateSearchResultIndex(currentSearchResultIndex - 1)
callBack.navigateToSearch(searchResultList[currentSearchResultIndex]) callBack.navigateToSearch(
searchResultList[currentSearchResultIndex],
currentSearchResultIndex
)
} }
ivSearchContentUp.setOnClickListener { ivSearchContentUp.setOnClickListener {
updateSearchResultIndex(currentSearchResultIndex - 1) updateSearchResultIndex(currentSearchResultIndex - 1)
callBack.navigateToSearch(searchResultList[currentSearchResultIndex]) callBack.navigateToSearch(
searchResultList[currentSearchResultIndex],
currentSearchResultIndex
)
} }
ivSearchContentDown.setOnClickListener { ivSearchContentDown.setOnClickListener {
updateSearchResultIndex(currentSearchResultIndex + 1) updateSearchResultIndex(currentSearchResultIndex + 1)
callBack.navigateToSearch(searchResultList[currentSearchResultIndex]) callBack.navigateToSearch(
searchResultList[currentSearchResultIndex],
currentSearchResultIndex
)
} }
fabRight.setOnClickListener { fabRight.setOnClickListener {
updateSearchResultIndex(currentSearchResultIndex + 1) updateSearchResultIndex(currentSearchResultIndex + 1)
callBack.navigateToSearch(searchResultList[currentSearchResultIndex]) callBack.navigateToSearch(
searchResultList[currentSearchResultIndex],
currentSearchResultIndex
)
} }
} }
@ -225,7 +237,7 @@ class SearchMenu @JvmOverloads constructor(
fun upSystemUiVisibility() fun upSystemUiVisibility()
fun exitSearchMenu() fun exitSearchMenu()
fun showMenuBar() fun showMenuBar()
fun navigateToSearch(searchResult: SearchResult) fun navigateToSearch(searchResult: SearchResult, index: Int)
} }
} }

@ -397,12 +397,13 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
upSelectedEnd(x, y + headerHeight) upSelectedEnd(x, y + headerHeight)
} }
fun cancelSelect() { fun cancelSelect(fromSearchExit: Boolean = false) {
val last = if (callBack.isScroll) 2 else 0 val last = if (callBack.isScroll) 2 else 0
for (relativePos in 0..last) { for (relativePos in 0..last) {
relativePage(relativePos).textLines.forEach { textLine -> relativePage(relativePos).textLines.forEach { textLine ->
textLine.textChars.forEach { textLine.textChars.forEach {
it.selected = false it.selected = false
if (fromSearchExit) it.isSearchResult = false
} }
} }
} }

@ -294,8 +294,8 @@ class PageView(context: Context) : FrameLayout(context) {
binding.contentTextView.selectEndMoveIndex(relativePagePos, lineIndex, charIndex) binding.contentTextView.selectEndMoveIndex(relativePagePos, lineIndex, charIndex)
} }
fun cancelSelect() { fun cancelSelect(fromSearchExit: Boolean = false) {
binding.contentTextView.cancelSelect() binding.contentTextView.cancelSelect(fromSearchExit)
} }
fun createBookmark(): Bookmark? { fun createBookmark(): Bookmark? {

@ -56,9 +56,18 @@ class SearchContentActivity :
initSearchView() initSearchView()
initRecyclerView() initRecyclerView()
initView() initView()
intent.getStringExtra("bookUrl")?.let { val searchResultList = IntentData.get<List<SearchResult>>("searchResultList")
viewModel.initBook(it) { val submit = searchResultList == null
initBook() intent.getStringExtra("bookUrl")?.let { bookUrl ->
viewModel.initBook(bookUrl) {
searchResultList?.let {
viewModel.searchResultList.addAll(it)
viewModel.searchResultCounts = it.size
adapter.setItems(it)
val position = intent.getIntExtra("searchResultIndex", 0)
binding.recyclerView.scrollToPosition(position)
}
initBook(submit)
} }
} }
} }
@ -101,14 +110,14 @@ class SearchContentActivity :
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun initBook() { private fun initBook(submit: Boolean = true) {
binding.tvCurrentSearchInfo.text = binding.tvCurrentSearchInfo.text =
this.getString(R.string.search_content_size) + ": ${viewModel.searchResultCounts}" this.getString(R.string.search_content_size) + ": ${viewModel.searchResultCounts}"
viewModel.book?.let { viewModel.book?.let {
initCacheFileNames(it) initCacheFileNames(it)
durChapterIndex = it.durChapterIndex durChapterIndex = it.durChapterIndex
intent.getStringExtra("searchWord")?.let { searchWord -> intent.getStringExtra("searchWord")?.let { searchWord ->
searchView.setQuery(searchWord, true) searchView.setQuery(searchWord, submit)
} }
} }
} }
@ -182,13 +191,14 @@ class SearchContentActivity :
val isLocalBook: Boolean val isLocalBook: Boolean
get() = viewModel.book?.isLocalBook() == true get() = viewModel.book?.isLocalBook() == true
override fun openSearchResult(searchResult: SearchResult) { override fun openSearchResult(searchResult: SearchResult, index: Int) {
postEvent(EventBus.SEARCH_RESULT, viewModel.searchResultList as List<SearchResult>) postEvent(EventBus.SEARCH_RESULT, viewModel.searchResultList as List<SearchResult>)
val searchData = Intent() val searchData = Intent()
val key = System.currentTimeMillis() val key = System.currentTimeMillis()
IntentData.put("searchResult$key", searchResult) IntentData.put("searchResult$key", searchResult)
IntentData.put("searchResultList$key", viewModel.searchResultList) IntentData.put("searchResultList$key", viewModel.searchResultList)
searchData.putExtra("key", key) searchData.putExtra("key", key)
searchData.putExtra("index", index)
setResult(RESULT_OK, searchData) setResult(RESULT_OK, searchData)
finish() finish()
} }

@ -39,13 +39,13 @@ class SearchContentAdapter(context: Context, val callback: Callback) :
override fun registerListener(holder: ItemViewHolder, binding: ItemSearchListBinding) { override fun registerListener(holder: ItemViewHolder, binding: ItemSearchListBinding) {
holder.itemView.setOnClickListener { holder.itemView.setOnClickListener {
getItem(holder.layoutPosition)?.let { getItem(holder.layoutPosition)?.let {
if (it.query.isNotBlank()) callback.openSearchResult(it) if (it.query.isNotBlank()) callback.openSearchResult(it, holder.layoutPosition)
} }
} }
} }
interface Callback { interface Callback {
fun openSearchResult(searchResult: SearchResult) fun openSearchResult(searchResult: SearchResult, index: Int)
fun durChapterIndex(): Int fun durChapterIndex(): Int
} }
} }

@ -97,7 +97,7 @@ class SearchContentViewModel(application: Application) : BaseViewModel(applicati
while (index >= 0) { while (index >= 0) {
scope.ensureActive() scope.ensureActive()
position.add(index) position.add(index)
index = mContent.indexOf(pattern, index + 1) index = mContent.indexOf(pattern, index + pattern.length)
} }
} }
return position return position

@ -22,10 +22,13 @@ data class SearchResult(
val leftString = resultText.substring(0, queryIndexInSurrounding) val leftString = resultText.substring(0, queryIndexInSurrounding)
val rightString = val rightString =
resultText.substring(queryIndexInSurrounding + query.length, resultText.length) resultText.substring(queryIndexInSurrounding + query.length, resultText.length)
val html = leftString.colorTextForHtml(textColor) + val html = buildString {
query.colorTextForHtml(accentColor) + append(chapterTitle.colorTextForHtml(accentColor))
rightString.colorTextForHtml(textColor) + append("<br>")
chapterTitle.colorTextForHtml(accentColor) append(leftString.colorTextForHtml(textColor))
append(query.colorTextForHtml(accentColor))
append(rightString.colorTextForHtml(textColor))
}
HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY) HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)
} else { } else {
HtmlCompat.fromHtml( HtmlCompat.fromHtml(

Loading…
Cancel
Save