diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 648bb7323..ef2c71ab2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -223,6 +223,10 @@
android:name=".ui.book.chapterlist.ChapterListActivity"
android:launchMode="singleTop"
android:screenOrientation="behind" />
+
, indexWithinChapter: Int, query: String): Array{
+ //
+ // calculate search result's pageIndex
+ var content = ""
+ pages.map{
+ content+= it.text
+ }
+ var count = 1
+ var index = content.indexOf(query)
+ while(count != indexWithinChapter){
+ index = content.indexOf(query, index + 1);
+ count += 1
+ }
+ val contentPosition = index
+ var pageIndex = 0
+ var length = pages[pageIndex].text.length
+ while (length < contentPosition){
+ pageIndex += 1
+ if (pageIndex >pages.size){
+ pageIndex = pages.size
+ break
+ }
+ length += pages[pageIndex].text.length
+ }
+
+ // calculate search result's lineIndex
+ val currentPage = pages[pageIndex]
+ var lineIndex = 0
+ length = length - currentPage.text.length + currentPage.textLines[lineIndex].text.length
+ while (length < contentPosition){
+ lineIndex += 1
+ if (lineIndex >currentPage.textLines.size){
+ lineIndex = currentPage.textLines.size
+ break
+ }
+ length += currentPage.textLines[lineIndex].text.length
+ }
+
+ // charIndex
+ val currentLine = currentPage.textLines[lineIndex]
+ length -= currentLine.text.length
+ val charIndex = contentPosition - length
+ return arrayOf(pageIndex, lineIndex, charIndex)
+ }
+
/**
* 内容加载完成
*/
@@ -426,4 +473,5 @@ object ReadBook {
fun pageChanged()
fun contentLoadFinish()
}
+
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
index 7840123db..7120e047a 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
@@ -9,6 +9,7 @@ import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Bundle
import android.os.Handler
+import android.util.Log
import android.view.*
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import androidx.core.view.get
@@ -47,6 +48,8 @@ import io.legado.app.ui.book.read.page.ContentTextView
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.TextPageFactory
import io.legado.app.ui.book.read.page.delegate.PageDelegate
+import io.legado.app.ui.book.searchContent.SearchListActivity
+import io.legado.app.ui.book.searchContent.SearchResult
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.login.SourceLogin
import io.legado.app.ui.replacerule.ReplaceRuleActivity
@@ -57,6 +60,9 @@ import kotlinx.android.synthetic.main.activity_book_read.*
import kotlinx.android.synthetic.main.view_read_menu.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
+import kotlinx.coroutines.Dispatchers.Main
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.jetbrains.anko.sdk27.listeners.onClick
import org.jetbrains.anko.startActivity
@@ -79,6 +85,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo
private val requestCodeChapterList = 568
private val requestCodeEditSource = 111
private val requestCodeReplace = 312
+ private val requestCodeSearchResult = 123
private var menu: Menu? = null
private var textActionMenu: TextActionMenu? = null
@@ -96,6 +103,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo
override var isAutoPage = false
private var screenTimeOut: Long = 0
private var timeBatteryReceiver: TimeBatteryReceiver? = null
+ private var loadStates: Boolean = false
override val pageFactory: TextPageFactory get() = page_view.pageFactory
override val headerHeight: Int get() = page_view.curPage.headerHeight
@@ -532,6 +540,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo
intent.removeExtra("readAloud")
ReadBook.readAloud()
}
+ loadStates = true
}
/**
@@ -543,6 +552,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo
page_view.upContent(relativePosition, resetPageOffset)
seek_read_page.progress = ReadBook.durPageIndex
}
+ loadStates = false
}
/**
@@ -667,6 +677,19 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo
}
}
+ /**
+ * 打开搜索界面
+ */
+ //todo: change request code
+ override fun openSearchList() {
+ ReadBook.book?.let {
+ startActivityForResult(
+ requestCodeSearchResult,
+ Pair("bookUrl", it.bookUrl)
+ )
+ }
+ }
+
/**
* 替换规则变化
*/
@@ -747,11 +770,36 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo
requestCodeChapterList ->
data?.getIntExtra("index", ReadBook.durChapterIndex)?.let { index ->
if (index != ReadBook.durChapterIndex) {
+ val pageIndex = data.getIntExtra("pageIndex", 0)
+ viewModel.openChapter(index, pageIndex)
+ }
+ }
+ requestCodeSearchResult ->
+ data?.getIntExtra("index", ReadBook.durChapterIndex)?.let { index ->
+ launch(IO){
+ val indexWithinChapter = data.getIntExtra("indexWithinChapter", 0)
+ val query = data.getStringExtra("query")
viewModel.openChapter(index)
+ // block until load correct chapter and pages
+ var pages = ReadBook.curTextChapter?.pages
+ while (ReadBook.durChapterIndex != index || pages == null ){
+ delay(100L)
+ pages = ReadBook.curTextChapter?.pages
+ }
+ val positions = ReadBook.searchResultPositions(pages, indexWithinChapter, query!!)
+ //todo: show selected text
+ val job1 = async(Main){
+ ReadBook.skipToPage(positions[0])
+ page_view.curPage.selectStartMoveIndex(positions[0], positions[1], 0)
+ page_view.curPage.selectEndMoveIndex(positions[0], positions[1], 0 + query.length )
+ page_view.isTextSelected = true
+ }
+ job1.await()
}
}
requestCodeReplace -> onReplaceRuleSave()
}
+
}
}
diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
index 6d069ff68..26423e701 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
@@ -200,6 +200,12 @@ class ReadMenu : FrameLayout {
}
}
+ ll_search.onClick {
+ runMenuOut {
+ callBack?.openSearchList()
+ }
+ }
+
//朗读
ll_read_aloud.onClick {
runMenuOut {
@@ -291,6 +297,7 @@ class ReadMenu : FrameLayout {
fun autoPage()
fun openReplaceRule()
fun openChapterList()
+ fun openSearchList()
fun showReadStyle()
fun showMoreSetting()
fun showReadAloudDialog()
diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListActivity.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListActivity.kt
new file mode 100644
index 000000000..ec1cbc0c7
--- /dev/null
+++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListActivity.kt
@@ -0,0 +1,93 @@
+package io.legado.app.ui.book.searchContent
+
+import android.os.Bundle
+import android.view.Menu
+import androidx.appcompat.widget.SearchView
+import androidx.core.view.isGone
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentPagerAdapter
+import io.legado.app.R
+import io.legado.app.base.VMBaseActivity
+import io.legado.app.lib.theme.ATH
+import io.legado.app.lib.theme.accentColor
+import io.legado.app.lib.theme.primaryTextColor
+import io.legado.app.utils.getViewModel
+import io.legado.app.utils.gone
+import io.legado.app.utils.visible
+import kotlinx.android.synthetic.main.activity_chapter_list.*
+import kotlinx.android.synthetic.main.view_tab_layout.*
+
+
+class SearchListActivity : VMBaseActivity(R.layout.activity_search_list) {
+ // todo: 完善搜索界面UI
+ override val viewModel: SearchListViewModel
+ get() = getViewModel(SearchListViewModel::class.java)
+
+ private var searchView: SearchView? = null
+
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ tab_layout.isTabIndicatorFullWidth = false
+ tab_layout.setSelectedTabIndicatorColor(accentColor)
+ intent.getStringExtra("bookUrl")?.let {
+ viewModel.initBook(it) {
+ view_pager.adapter = TabFragmentPageAdapter(supportFragmentManager)
+ tab_layout.setupWithViewPager(view_pager)
+ }
+ }
+ }
+
+ override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.search_view, menu)
+ val search = menu.findItem(R.id.menu_search)
+ searchView = search.actionView as SearchView
+ ATH.setTint(searchView!!, primaryTextColor)
+ searchView?.maxWidth = resources.displayMetrics.widthPixels
+ searchView?.onActionViewCollapsed()
+ searchView?.setOnCloseListener {
+ tab_layout.visible()
+ //to do clean
+ false
+ }
+ searchView?.setOnSearchClickListener { tab_layout.gone() }
+ searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextSubmit(query: String): Boolean {
+ if (viewModel.lastQuery != query){
+ viewModel.startContentSearch(query)
+ }
+ return false
+ }
+
+ override fun onQueryTextChange(newText: String): Boolean {
+
+ return false
+ }
+ })
+ return super.onCompatCreateOptionsMenu(menu)
+ }
+
+ private inner class TabFragmentPageAdapter(fm: FragmentManager) :
+ FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
+ override fun getItem(position: Int): Fragment {
+ return SearchListFragment()
+ }
+
+ override fun getCount(): Int {
+ return 1
+ }
+
+ override fun getPageTitle(position: Int): CharSequence? {
+ return "Search"
+ }
+
+ }
+
+ override fun onBackPressed() {
+ if (tab_layout.isGone) {
+ searchView?.onActionViewCollapsed()
+ tab_layout.visible()
+ } else {
+ super.onBackPressed()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListAdapter.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListAdapter.kt
new file mode 100644
index 000000000..d2c98b955
--- /dev/null
+++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListAdapter.kt
@@ -0,0 +1,51 @@
+package io.legado.app.ui.book.searchContent
+
+import android.content.Context
+import android.os.Build
+import android.text.Html
+import android.util.Log
+import android.view.View
+import androidx.annotation.RequiresApi
+import androidx.core.text.HtmlCompat
+import io.legado.app.R
+import io.legado.app.base.adapter.ItemViewHolder
+import io.legado.app.base.adapter.SimpleRecyclerAdapter
+import io.legado.app.data.entities.BookChapter
+import io.legado.app.help.BookHelp
+import io.legado.app.lib.theme.accentColor
+import io.legado.app.utils.getCompatColor
+import io.legado.app.utils.visible
+import kotlinx.android.synthetic.main.item_bookmark.view.*
+import kotlinx.android.synthetic.main.item_search_list.view.*
+import org.jetbrains.anko.sdk27.listeners.onClick
+
+class SearchListAdapter(context: Context, val callback: Callback) :
+ SimpleRecyclerAdapter(context, R.layout.item_search_list) {
+
+ val cacheFileNames = hashSetOf()
+
+ override fun convert(holder: ItemViewHolder, item: SearchResult, payloads: MutableList) {
+ with(holder.itemView) {
+ val isDur = callback.durChapterIndex() == item.chapterIndex
+ if (payloads.isEmpty()) {
+ tv_search_result.text = item.parseText(item.presentText)
+ if (isDur){
+ tv_search_result.paint.isFakeBoldText = true
+ }
+ }
+ }
+ }
+
+ override fun registerListener(holder: ItemViewHolder) {
+ holder.itemView.onClick {
+ getItem(holder.layoutPosition)?.let {
+ callback.openSearchResult(it)
+ }
+ }
+ }
+
+ interface Callback {
+ fun openSearchResult(searchResult: SearchResult)
+ fun durChapterIndex(): Int
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt
new file mode 100644
index 000000000..38ac013df
--- /dev/null
+++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt
@@ -0,0 +1,239 @@
+package io.legado.app.ui.book.searchContent
+
+import android.annotation.SuppressLint
+import android.app.Activity.RESULT_OK
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import androidx.lifecycle.LiveData
+import com.hankcs.hanlp.HanLP
+import io.legado.app.App
+import io.legado.app.R
+import io.legado.app.base.VMBaseFragment
+import io.legado.app.constant.EventBus
+import io.legado.app.data.entities.Book
+import io.legado.app.data.entities.BookChapter
+import io.legado.app.help.AppConfig
+import io.legado.app.help.BookHelp
+import io.legado.app.lib.theme.bottomBackground
+import io.legado.app.lib.theme.getPrimaryTextColor
+import io.legado.app.service.help.ReadBook
+import io.legado.app.ui.book.read.page.entities.TextPage
+import io.legado.app.ui.book.read.page.provider.ChapterProvider
+import io.legado.app.ui.widget.recycler.UpLinearLayoutManager
+import io.legado.app.ui.widget.recycler.VerticalDivider
+import io.legado.app.utils.ColorUtils
+import io.legado.app.utils.getViewModelOfActivity
+import io.legado.app.utils.observeEvent
+import kotlinx.android.synthetic.main.fragment_search_list.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.Dispatchers.IO
+import kotlinx.coroutines.Dispatchers.Main
+import org.jetbrains.anko.sdk27.listeners.onClick
+import java.util.regex.Pattern
+
+class SearchListFragment : VMBaseFragment(R.layout.fragment_search_list),
+ SearchListAdapter.Callback,
+ SearchListViewModel.SearchListCallBack{
+ override val viewModel: SearchListViewModel
+ get() = getViewModelOfActivity(SearchListViewModel::class.java)
+
+ lateinit var adapter: SearchListAdapter
+ private lateinit var mLayoutManager: UpLinearLayoutManager
+ private var searchResultCounts = 0
+ private var durChapterIndex = 0
+ private var searchResultList: MutableList = mutableListOf()
+
+ override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
+ viewModel.searchCallBack = this
+ val bbg = bottomBackground
+ val btc = requireContext().getPrimaryTextColor(ColorUtils.isColorLight(bbg))
+ ll_search_base_info.setBackgroundColor(bbg)
+ tv_current_search_info.setTextColor(btc)
+ iv_search_content_top.setColorFilter(btc)
+ iv_search_content_bottom.setColorFilter(btc)
+ initRecyclerView()
+ initView()
+ initBook()
+ }
+
+ private fun initRecyclerView() {
+ adapter = SearchListAdapter(requireContext(), this)
+ mLayoutManager = UpLinearLayoutManager(requireContext())
+ recycler_view.layoutManager = mLayoutManager
+ recycler_view.addItemDecoration(VerticalDivider(requireContext()))
+ recycler_view.adapter = adapter
+ }
+
+ private fun initView() {
+ iv_search_content_top.onClick { mLayoutManager.scrollToPositionWithOffset(0, 0) }
+ iv_search_content_bottom.onClick {
+ if (adapter.itemCount > 0) {
+ mLayoutManager.scrollToPositionWithOffset(adapter.itemCount - 1, 0)
+ }
+ }
+ }
+
+ @SuppressLint("SetTextI18n")
+ private fun initBook() {
+ launch {
+
+ tv_current_search_info.text = "搜索结果:$searchResultCounts"
+ viewModel.book?.let {
+ initCacheFileNames(it)
+ durChapterIndex = it.durChapterIndex
+ }
+ }
+ }
+
+ private fun initCacheFileNames(book: Book) {
+ launch(IO) {
+ adapter.cacheFileNames.addAll(BookHelp.getChapterFiles(book))
+ withContext(Main) {
+ adapter.notifyItemRangeChanged(0, adapter.getActualItemCount(), true)
+ }
+ }
+ }
+
+ override fun observeLiveBus() {
+ observeEvent(EventBus.SAVE_CONTENT) { chapter ->
+ viewModel.book?.bookUrl?.let { bookUrl ->
+ if (chapter.bookUrl == bookUrl) {
+ adapter.cacheFileNames.add(BookHelp.formatChapterName(chapter))
+ adapter.notifyItemChanged(chapter.index, true)
+ }
+ }
+ }
+ }
+
+ @SuppressLint("SetTextI18n")
+ override fun startContentSearch(newText: String) {
+ // 按章节搜索内容
+ if (!newText.isBlank()) {
+ adapter.clearItems()
+ searchResultList.clear()
+ searchResultCounts = 0
+ viewModel.lastQuery = newText
+ var searchResults = listOf()
+ launch(Main){
+ App.db.bookChapterDao().getChapterList(viewModel.bookUrl).map{ chapter ->
+ val job = async(IO){
+ if (isLocalBook || adapter.cacheFileNames.contains(BookHelp.formatChapterName(chapter))) {
+ searchResults = searchChapter(newText, chapter)
+ }
+ }
+ job.await()
+ if (searchResults.isNotEmpty()){
+ searchResultList.addAll(searchResults)
+ tv_current_search_info.text = "搜索结果:$searchResultCounts"
+ adapter.addItems(searchResults)
+ searchResults = listOf()
+ }
+ }
+ }
+ }
+ }
+
+ private suspend fun searchChapter(query: String, chapter: BookChapter?): List {
+ val searchResults: MutableList = mutableListOf()
+ var positions : List = listOf()
+ var replaceContents: List? = null
+ var totalContents = ""
+ if (chapter != null){
+ viewModel.book?.let { bookSource ->
+ val bookContent = BookHelp.getContent(bookSource, chapter)
+ if (bookContent != null){
+ //搜索替换后的正文
+ val job = async(IO) {
+ chapter.title = when (AppConfig.chineseConverterType) {
+ 1 -> HanLP.convertToSimplifiedChinese(chapter.title)
+ 2 -> HanLP.convertToTraditionalChinese(chapter.title)
+ else -> chapter.title
+ }
+ replaceContents = BookHelp.disposeContent(
+ chapter.title,
+ bookSource.name,
+ bookSource.bookUrl,
+ bookContent,
+ bookSource.useReplaceRule
+ )
+ }
+ job.await()
+ while (replaceContents == null){
+ delay(100L)
+ }
+ totalContents = replaceContents!!.joinToString("")
+ positions = searchPosition(totalContents, query)
+ var count = 1
+ positions.map{
+ val construct = constructText(totalContents, it, query)
+ val result = SearchResult(index = searchResultCounts,
+ indexWithinChapter = count,
+ text = construct[1] as String,
+ chapterTitle = chapter.title,
+ query = query,
+ chapterIndex = chapter.index,
+ newPosition = construct[0] as Int,
+ contentPosition = it
+ )
+ count += 1
+ searchResultCounts += 1
+ searchResults.add(result)
+ }
+ }
+ }
+ }
+ return searchResults
+ }
+
+ private fun searchPosition(content: String, pattern: String): List {
+ val position : MutableList = mutableListOf()
+ var index = content.indexOf(pattern)
+ while(index >= 0){
+ position.add(index)
+ index = content.indexOf(pattern, index + 1);
+ }
+ return position
+ }
+
+ private fun constructText(content: String, position: Int, query: String): Array{
+ // 构建关键词周边文字,在搜索结果里显示
+ // todo: 判断段落,只在关键词所在段落内分割
+ // todo: 利用标点符号分割完整的句
+ // todo: length和设置结合,自由调整周边文字长度
+ val length = 20
+ var po1 = position - length
+ var po2 = position + query.length + length
+ if (po1 <0) {
+ po1 = 0
+ }
+ if (po2 > content.length){
+ po2 = content.length
+ }
+ val newPosition = position - po1
+ val newText = content.substring(po1, po2)
+ return arrayOf(newPosition, newText)
+ }
+
+ val isLocalBook: Boolean
+ get() = viewModel.book?.isLocalBook() == true
+
+ override fun openSearchResult(searchResult: SearchResult) {
+
+ val searchData = Intent()
+ searchData.putExtra("index", searchResult.chapterIndex)
+ searchData.putExtra("contentPosition", searchResult.contentPosition)
+ searchData.putExtra("query", searchResult.query)
+ searchData.putExtra("indexWithinChapter", searchResult.indexWithinChapter)
+ activity?.setResult(RESULT_OK, searchData)
+ activity?.finish()
+
+
+ }
+
+ override fun durChapterIndex(): Int {
+ return durChapterIndex
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt
new file mode 100644
index 000000000..060d74067
--- /dev/null
+++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt
@@ -0,0 +1,33 @@
+package io.legado.app.ui.book.searchContent
+
+
+import android.app.Application
+import io.legado.app.App
+import io.legado.app.base.BaseViewModel
+import io.legado.app.data.entities.Book
+
+class SearchListViewModel(application: Application) : BaseViewModel(application) {
+ var bookUrl: String = ""
+ var book: Book? = null
+ var searchCallBack: SearchListCallBack? = null
+ var lastQuery: String = ""
+
+ fun initBook(bookUrl: String, success: () -> Unit) {
+ this.bookUrl = bookUrl
+ execute {
+ book = App.db.bookDao().getBook(bookUrl)
+ }.onSuccess {
+ success.invoke()
+ }
+ }
+
+ fun startContentSearch(newText: String) {
+ searchCallBack?.startContentSearch(newText)
+ }
+
+
+ interface SearchListCallBack {
+ fun startContentSearch(newText: String)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt
new file mode 100644
index 000000000..6b0d9df5b
--- /dev/null
+++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt
@@ -0,0 +1,40 @@
+package io.legado.app.ui.book.searchContent
+
+import android.text.Spanned
+import android.util.Log
+import androidx.core.text.HtmlCompat
+import io.legado.app.ui.book.read.page.entities.TextPage
+
+data class SearchResult(
+ var index: Int = 0,
+ var indexWithinChapter: Int = 0,
+ var text: String = "",
+ var chapterTitle: String = "",
+ val query: String,
+ var pageSize: Int = 0,
+ var chapterIndex: Int = 0,
+ var pageIndex: Int = 0,
+ var newPosition: Int = 0,
+ var contentPosition: Int =0
+) {
+ val presentText: String
+ get(){
+ return colorPresentText(newPosition, query, text) +
+ "($chapterTitle)"
+ }
+
+ fun colorPresentText(position: Int, center: String, targetText: String): String{
+ val sub1 = text.substring(0, position)
+ val sub2 = text.substring(position + center.length, targetText.length)
+ return "$sub1" +
+ "$center" +
+ "$sub2"
+ }
+
+ fun parseText(targetText: String): Spanned {
+ return HtmlCompat.fromHtml(targetText, HtmlCompat.FROM_HTML_MODE_LEGACY)
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_search_list.xml b/app/src/main/res/layout/activity_search_list.xml
new file mode 100644
index 000000000..159f77324
--- /dev/null
+++ b/app/src/main/res/layout/activity_search_list.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_search_list.xml b/app/src/main/res/layout/fragment_search_list.xml
new file mode 100644
index 000000000..79fefe11f
--- /dev/null
+++ b/app/src/main/res/layout/fragment_search_list.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_search_list.xml b/app/src/main/res/layout/item_search_list.xml
new file mode 100644
index 000000000..2dcaf1436
--- /dev/null
+++ b/app/src/main/res/layout/item_search_list.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_read_menu.xml b/app/src/main/res/layout/view_read_menu.xml
index 1b9bc2a98..b63fba1ff 100644
--- a/app/src/main/res/layout/view_read_menu.xml
+++ b/app/src/main/res/layout/view_read_menu.xml
@@ -260,6 +260,45 @@
android:textSize="12sp" />
+
+
+
+
+
+
+
+
+
+
切換默認主題
分享選中書源
時間排序
+ 搜索
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index c14a9a485..006b4ff45 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -761,6 +761,7 @@
切換默認主題
分享選中書源
時間排序
+ 搜索
diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml
index 036bcd6f9..78487a2be 100644
--- a/app/src/main/res/values-zh/strings.xml
+++ b/app/src/main/res/values-zh/strings.xml
@@ -761,5 +761,6 @@
使用保存主题,导入,分享主题
切换默认主题
时间排序
+ 搜索
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 92888ae19..893230b30 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -763,5 +763,6 @@
Save, Import, Share theme
Share selected sources
Sort by update time
+ Search
\ No newline at end of file