Merge branch 'master' of https://github.com/Celeter/legado
commit
be1fdbdbba
@ -1,93 +1,264 @@ |
|||||||
package io.legado.app.ui.book.searchContent |
package io.legado.app.ui.book.searchContent |
||||||
|
|
||||||
|
import android.annotation.SuppressLint |
||||||
|
import android.content.Intent |
||||||
import android.os.Bundle |
import android.os.Bundle |
||||||
import android.view.Menu |
|
||||||
import androidx.appcompat.widget.SearchView |
import androidx.appcompat.widget.SearchView |
||||||
import androidx.core.view.isGone |
import com.hankcs.hanlp.HanLP |
||||||
import androidx.fragment.app.Fragment |
import io.legado.app.App |
||||||
import androidx.fragment.app.FragmentManager |
|
||||||
import androidx.fragment.app.FragmentPagerAdapter |
|
||||||
import io.legado.app.R |
import io.legado.app.R |
||||||
import io.legado.app.base.VMBaseActivity |
import io.legado.app.base.VMBaseActivity |
||||||
|
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.ATH |
import io.legado.app.lib.theme.ATH |
||||||
import io.legado.app.lib.theme.accentColor |
import io.legado.app.lib.theme.bottomBackground |
||||||
|
import io.legado.app.lib.theme.getPrimaryTextColor |
||||||
import io.legado.app.lib.theme.primaryTextColor |
import io.legado.app.lib.theme.primaryTextColor |
||||||
|
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.getViewModel |
import io.legado.app.utils.getViewModel |
||||||
import io.legado.app.utils.gone |
import io.legado.app.utils.observeEvent |
||||||
import io.legado.app.utils.visible |
import kotlinx.android.synthetic.main.activity_search_list.* |
||||||
import kotlinx.android.synthetic.main.activity_chapter_list.* |
import kotlinx.android.synthetic.main.view_search.* |
||||||
import kotlinx.android.synthetic.main.view_tab_layout.* |
import kotlinx.coroutines.* |
||||||
|
import org.jetbrains.anko.sdk27.listeners.onClick |
||||||
|
|
||||||
|
|
||||||
class SearchListActivity : VMBaseActivity<SearchListViewModel>(R.layout.activity_search_list) { |
class SearchListActivity : VMBaseActivity<SearchListViewModel>(R.layout.activity_search_list), |
||||||
// todo: 完善搜索界面UI |
SearchListAdapter.Callback { |
||||||
|
|
||||||
override val viewModel: SearchListViewModel |
override val viewModel: SearchListViewModel |
||||||
get() = getViewModel(SearchListViewModel::class.java) |
get() = getViewModel(SearchListViewModel::class.java) |
||||||
|
|
||||||
private var searchView: SearchView? = null |
lateinit var adapter: SearchListAdapter |
||||||
|
private lateinit var mLayoutManager: UpLinearLayoutManager |
||||||
|
private var searchResultCounts = 0 |
||||||
|
private var durChapterIndex = 0 |
||||||
|
private var searchResultList: MutableList<SearchResult> = mutableListOf() |
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) { |
override fun onActivityCreated(savedInstanceState: Bundle?) { |
||||||
tab_layout.isTabIndicatorFullWidth = false |
val bbg = bottomBackground |
||||||
tab_layout.setSelectedTabIndicatorColor(accentColor) |
val btc = 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) |
||||||
|
initSearchView() |
||||||
|
initRecyclerView() |
||||||
|
initView() |
||||||
intent.getStringExtra("bookUrl")?.let { |
intent.getStringExtra("bookUrl")?.let { |
||||||
viewModel.initBook(it) { |
viewModel.initBook(it) { |
||||||
view_pager.adapter = TabFragmentPageAdapter(supportFragmentManager) |
initBook() |
||||||
tab_layout.setupWithViewPager(view_pager) |
|
||||||
} |
} |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { |
private fun initSearchView() { |
||||||
menuInflater.inflate(R.menu.search_view, menu) |
ATH.setTint(search_view, primaryTextColor) |
||||||
val search = menu.findItem(R.id.menu_search) |
search_view.onActionViewExpanded() |
||||||
searchView = search.actionView as SearchView |
search_view.isSubmitButtonEnabled = true |
||||||
ATH.setTint(searchView!!, primaryTextColor) |
search_view.queryHint = getString(R.string.search) |
||||||
searchView?.maxWidth = resources.displayMetrics.widthPixels |
search_view.clearFocus() |
||||||
searchView?.onActionViewCollapsed() |
search_view.setOnQueryTextListener(object : SearchView.OnQueryTextListener { |
||||||
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 { |
override fun onQueryTextSubmit(query: String): Boolean { |
||||||
if (viewModel.lastQuery != query){ |
if (viewModel.lastQuery != query) { |
||||||
viewModel.startContentSearch(query) |
startContentSearch(query) |
||||||
} |
} |
||||||
return false |
return false |
||||||
} |
} |
||||||
|
|
||||||
override fun onQueryTextChange(newText: String): Boolean { |
override fun onQueryTextChange(newText: String?): Boolean { |
||||||
|
|
||||||
return false |
return false |
||||||
} |
} |
||||||
}) |
}) |
||||||
return super.onCompatCreateOptionsMenu(menu) |
|
||||||
} |
} |
||||||
|
|
||||||
private inner class TabFragmentPageAdapter(fm: FragmentManager) : |
private fun initRecyclerView() { |
||||||
FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { |
adapter = SearchListAdapter(this, this) |
||||||
override fun getItem(position: Int): Fragment { |
mLayoutManager = UpLinearLayoutManager(this) |
||||||
return SearchListFragment() |
recycler_view.layoutManager = mLayoutManager |
||||||
|
recycler_view.addItemDecoration(VerticalDivider(this)) |
||||||
|
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 |
||||||
|
intent.getStringExtra("searchWord")?.let { searchWord -> |
||||||
|
search_view.setQuery(searchWord, true) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun initCacheFileNames(book: Book) { |
||||||
|
launch(Dispatchers.IO) { |
||||||
|
adapter.cacheFileNames.addAll(BookHelp.getChapterFiles(book)) |
||||||
|
withContext(Dispatchers.Main) { |
||||||
|
adapter.notifyItemRangeChanged(0, adapter.getActualItemCount(), true) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun observeLiveBus() { |
||||||
|
observeEvent<BookChapter>(EventBus.SAVE_CONTENT) { chapter -> |
||||||
|
viewModel.book?.bookUrl?.let { bookUrl -> |
||||||
|
if (chapter.bookUrl == bookUrl) { |
||||||
|
adapter.cacheFileNames.add(BookHelp.formatChapterName(chapter)) |
||||||
|
adapter.notifyItemChanged(chapter.index, true) |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
override fun getCount(): Int { |
@SuppressLint("SetTextI18n") |
||||||
return 1 |
fun startContentSearch(newText: String) { |
||||||
|
// 按章节搜索内容 |
||||||
|
if (!newText.isBlank()) { |
||||||
|
adapter.clearItems() |
||||||
|
searchResultList.clear() |
||||||
|
searchResultCounts = 0 |
||||||
|
viewModel.lastQuery = newText |
||||||
|
var searchResults = listOf<SearchResult>() |
||||||
|
launch(Dispatchers.Main) { |
||||||
|
App.db.bookChapterDao().getChapterList(viewModel.bookUrl).map { chapter -> |
||||||
|
val job = async(Dispatchers.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() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
override fun getPageTitle(position: Int): CharSequence? { |
private suspend fun searchChapter(query: String, chapter: BookChapter?): List<SearchResult> { |
||||||
return "Search" |
val searchResults: MutableList<SearchResult> = mutableListOf() |
||||||
|
var positions: List<Int> |
||||||
|
var replaceContents: List<String>? = null |
||||||
|
var totalContents: String |
||||||
|
if (chapter != null) { |
||||||
|
viewModel.book?.let { bookSource -> |
||||||
|
val bookContent = BookHelp.getContent(bookSource, chapter) |
||||||
|
if (bookContent != null) { |
||||||
|
//搜索替换后的正文 |
||||||
|
val job = async(Dispatchers.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<Int> { |
||||||
|
val position: MutableList<Int> = mutableListOf() |
||||||
|
var index = content.indexOf(pattern) |
||||||
|
while (index >= 0) { |
||||||
|
position.add(index) |
||||||
|
index = content.indexOf(pattern, index + 1) |
||||||
|
} |
||||||
|
return position |
||||||
} |
} |
||||||
|
|
||||||
override fun onBackPressed() { |
private fun constructText(content: String, position: Int, query: String): Array<Any> { |
||||||
if (tab_layout.isGone) { |
// 构建关键词周边文字,在搜索结果里显示 |
||||||
searchView?.onActionViewCollapsed() |
// todo: 判断段落,只在关键词所在段落内分割 |
||||||
tab_layout.visible() |
// todo: 利用标点符号分割完整的句 |
||||||
} else { |
// todo: length和设置结合,自由调整周边文字长度 |
||||||
super.onBackPressed() |
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) |
||||||
|
setResult(RESULT_OK, searchData) |
||||||
|
finish() |
||||||
|
} |
||||||
|
|
||||||
|
override fun durChapterIndex(): Int { |
||||||
|
return durChapterIndex |
||||||
|
} |
||||||
|
|
||||||
} |
} |
@ -1,239 +0,0 @@ |
|||||||
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<SearchListViewModel>(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<SearchResult> = 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<BookChapter>(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<SearchResult>() |
|
||||||
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<SearchResult>() |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private suspend fun searchChapter(query: String, chapter: BookChapter?): List<SearchResult> { |
|
||||||
val searchResults: MutableList<SearchResult> = mutableListOf() |
|
||||||
var positions : List<Int> = listOf() |
|
||||||
var replaceContents: List<String>? = 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<Int> { |
|
||||||
val position : MutableList<Int> = 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<Any>{ |
|
||||||
// 构建关键词周边文字,在搜索结果里显示 |
|
||||||
// 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 |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,19 +1,74 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
android:orientation="vertical" |
xmlns:tools="http://schemas.android.com/tools" |
||||||
android:layout_width="match_parent" |
android:layout_width="match_parent" |
||||||
android:layout_height="match_parent"> |
android:layout_height="match_parent"> |
||||||
|
|
||||||
<io.legado.app.ui.widget.TitleBar |
<io.legado.app.ui.widget.TitleBar |
||||||
android:id="@+id/title_bar" |
android:id="@+id/title_bar" |
||||||
android:layout_width="match_parent" |
android:layout_width="match_parent" |
||||||
android:layout_height="wrap_content" |
android:layout_height="wrap_content" |
||||||
app:contentLayout="@layout/view_tab_layout"/> |
app:contentLayout="@layout/view_search" |
||||||
|
app:contentInsetRight="24dp" |
||||||
|
app:layout_constraintTop_toTopOf="parent" /> |
||||||
|
|
||||||
<androidx.viewpager.widget.ViewPager |
<io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView |
||||||
android:id="@+id/view_pager" |
android:id="@+id/recycler_view" |
||||||
android:layout_width="match_parent" |
android:layout_width="match_parent" |
||||||
android:layout_height="match_parent"/> |
android:layout_height="0dp" |
||||||
|
android:overScrollMode="never" |
||||||
|
app:layout_constraintBottom_toTopOf="@+id/ll_search_base_info" |
||||||
|
app:layout_constraintTop_toBottomOf="@id/title_bar" /> |
||||||
|
|
||||||
</LinearLayout> |
<LinearLayout |
||||||
|
android:id="@+id/ll_search_base_info" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="36dp" |
||||||
|
android:background="@color/background" |
||||||
|
android:elevation="5dp" |
||||||
|
android:gravity="center_vertical" |
||||||
|
android:orientation="horizontal" |
||||||
|
android:paddingLeft="10dp" |
||||||
|
android:paddingRight="10dp" |
||||||
|
app:layout_constraintBottom_toBottomOf="parent"> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/tv_current_search_info" |
||||||
|
android:layout_width="0dp" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:layout_weight="1" |
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless" |
||||||
|
android:ellipsize="middle" |
||||||
|
android:gravity="center_vertical" |
||||||
|
android:paddingLeft="10dp" |
||||||
|
android:paddingRight="10dp" |
||||||
|
android:singleLine="true" |
||||||
|
android:textColor="@color/primaryText" |
||||||
|
android:textSize="12sp" /> |
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView |
||||||
|
android:id="@+id/iv_search_content_top" |
||||||
|
android:layout_width="36dp" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless" |
||||||
|
android:contentDescription="@string/go_to_top" |
||||||
|
android:src="@drawable/ic_arrow_drop_up" |
||||||
|
android:tooltipText="@string/go_to_top" |
||||||
|
app:tint="@color/primaryText" |
||||||
|
tools:ignore="UnusedAttribute" /> |
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView |
||||||
|
android:id="@+id/iv_search_content_bottom" |
||||||
|
android:layout_width="36dp" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless" |
||||||
|
android:contentDescription="@string/go_to_bottom" |
||||||
|
android:src="@drawable/ic_arrow_drop_down" |
||||||
|
android:tooltipText="@string/go_to_bottom" |
||||||
|
app:tint="@color/primaryText" |
||||||
|
tools:ignore="UnusedAttribute" /> |
||||||
|
</LinearLayout> |
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout> |
@ -0,0 +1,36 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
xmlns:tools="http://schemas.android.com/tools" |
||||||
|
android:orientation="vertical" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent"> |
||||||
|
|
||||||
|
<io.legado.app.lib.theme.view.ATEProgressBar |
||||||
|
android:id="@+id/ck_progress" |
||||||
|
style="?android:attr/progressBarStyleHorizontal" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:padding="20dp" |
||||||
|
android:layout_marginRight="60dp" |
||||||
|
app:layout_constraintTop_toTopOf="parent" /> |
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView |
||||||
|
android:id="@+id/ck_progress_text" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_marginRight="20dp" |
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/ck_progress" |
||||||
|
app:layout_constraintEnd_toEndOf="@+id/ck_progress" /> |
||||||
|
|
||||||
|
<io.legado.app.ui.widget.text.AccentTextView |
||||||
|
android:id="@+id/tv_footer_left" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:padding="12dp" |
||||||
|
android:text="@string/cancel" |
||||||
|
app:layout_constraintBottom_toBottomOf="parent" |
||||||
|
app:layout_constraintStart_toEndOf="@id/ck_progress" |
||||||
|
app:layout_constraintTop_toTopOf="parent" |
||||||
|
tools:ignore="RtlHardcoded" /> |
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout> |
@ -1,66 +0,0 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
|
||||||
xmlns:tools="http://schemas.android.com/tools" |
|
||||||
android:layout_width="match_parent" |
|
||||||
android:layout_height="match_parent" |
|
||||||
android:descendantFocusability="blocksDescendants"> |
|
||||||
|
|
||||||
<io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView |
|
||||||
android:id="@+id/recycler_view" |
|
||||||
android:layout_width="match_parent" |
|
||||||
android:layout_height="0dp" |
|
||||||
android:overScrollMode="never" |
|
||||||
app:layout_constraintTop_toTopOf="parent" |
|
||||||
app:layout_constraintBottom_toTopOf="@+id/ll_search_base_info" /> |
|
||||||
|
|
||||||
<LinearLayout |
|
||||||
android:id="@+id/ll_search_base_info" |
|
||||||
android:layout_width="match_parent" |
|
||||||
android:layout_height="36dp" |
|
||||||
android:background="@color/background" |
|
||||||
android:paddingLeft="10dp" |
|
||||||
android:paddingRight="10dp" |
|
||||||
android:elevation="5dp" |
|
||||||
android:gravity="center_vertical" |
|
||||||
android:orientation="horizontal" |
|
||||||
app:layout_constraintBottom_toBottomOf="parent"> |
|
||||||
|
|
||||||
<TextView |
|
||||||
android:id="@+id/tv_current_search_info" |
|
||||||
android:layout_width="0dp" |
|
||||||
android:layout_height="match_parent" |
|
||||||
android:layout_weight="1" |
|
||||||
android:background="?android:attr/selectableItemBackgroundBorderless" |
|
||||||
android:ellipsize="middle" |
|
||||||
android:paddingLeft="10dp" |
|
||||||
android:paddingRight="10dp" |
|
||||||
android:singleLine="true" |
|
||||||
android:gravity="center_vertical" |
|
||||||
android:textColor="@color/primaryText" |
|
||||||
android:textSize="12sp" /> |
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView |
|
||||||
android:id="@+id/iv_search_content_top" |
|
||||||
android:layout_width="36dp" |
|
||||||
android:layout_height="match_parent" |
|
||||||
android:background="?android:attr/selectableItemBackgroundBorderless" |
|
||||||
android:contentDescription="@string/go_to_top" |
|
||||||
android:src="@drawable/ic_arrow_drop_up" |
|
||||||
android:tooltipText="@string/go_to_top" |
|
||||||
app:tint="@color/primaryText" |
|
||||||
tools:ignore="UnusedAttribute" /> |
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView |
|
||||||
android:id="@+id/iv_search_content_bottom" |
|
||||||
android:layout_width="36dp" |
|
||||||
android:layout_height="match_parent" |
|
||||||
android:background="?android:attr/selectableItemBackgroundBorderless" |
|
||||||
android:contentDescription="@string/go_to_bottom" |
|
||||||
android:src="@drawable/ic_arrow_drop_down" |
|
||||||
android:tooltipText="@string/go_to_bottom" |
|
||||||
app:tint="@color/primaryText" |
|
||||||
tools:ignore="UnusedAttribute" /> |
|
||||||
</LinearLayout> |
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout> |
|
Loading…
Reference in new issue