Merge branch 'master' of https://github.com/Celeter/legado
	
		
	
				
					
				
			
						commit
						be1fdbdbba
					
				| @ -1,93 +1,264 @@ | ||||
| package io.legado.app.ui.book.searchContent | ||||
| 
 | ||||
| import android.annotation.SuppressLint | ||||
| import android.content.Intent | ||||
| 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 com.hankcs.hanlp.HanLP | ||||
| import io.legado.app.App | ||||
| import io.legado.app.R | ||||
| 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.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.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.gone | ||||
| import io.legado.app.utils.visible | ||||
| import kotlinx.android.synthetic.main.activity_chapter_list.* | ||||
| import kotlinx.android.synthetic.main.view_tab_layout.* | ||||
| import io.legado.app.utils.observeEvent | ||||
| import kotlinx.android.synthetic.main.activity_search_list.* | ||||
| import kotlinx.android.synthetic.main.view_search.* | ||||
| import kotlinx.coroutines.* | ||||
| import org.jetbrains.anko.sdk27.listeners.onClick | ||||
| 
 | ||||
| 
 | ||||
| class SearchListActivity : VMBaseActivity<SearchListViewModel>(R.layout.activity_search_list) { | ||||
|     // todo: 完善搜索界面UI | ||||
| class SearchListActivity : VMBaseActivity<SearchListViewModel>(R.layout.activity_search_list), | ||||
|     SearchListAdapter.Callback { | ||||
| 
 | ||||
|     override val viewModel: SearchListViewModel | ||||
|         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?) { | ||||
|         tab_layout.isTabIndicatorFullWidth = false | ||||
|         tab_layout.setSelectedTabIndicatorColor(accentColor) | ||||
|         val bbg = bottomBackground | ||||
|         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 { | ||||
|             viewModel.initBook(it) { | ||||
|                 view_pager.adapter = TabFragmentPageAdapter(supportFragmentManager) | ||||
|                 tab_layout.setupWithViewPager(view_pager) | ||||
|                 initBook() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     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 { | ||||
|     private fun initSearchView() { | ||||
|         ATH.setTint(search_view, primaryTextColor) | ||||
|         search_view.onActionViewExpanded() | ||||
|         search_view.isSubmitButtonEnabled = true | ||||
|         search_view.queryHint = getString(R.string.search) | ||||
|         search_view.clearFocus() | ||||
|         search_view.setOnQueryTextListener(object : SearchView.OnQueryTextListener { | ||||
|             override fun onQueryTextSubmit(query: String): Boolean { | ||||
|                 if (viewModel.lastQuery != query){ | ||||
|                     viewModel.startContentSearch(query) | ||||
|                 if (viewModel.lastQuery != query) { | ||||
|                     startContentSearch(query) | ||||
|                 } | ||||
|                 return false | ||||
|             } | ||||
| 
 | ||||
|             override fun onQueryTextChange(newText: String): Boolean { | ||||
| 
 | ||||
|             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() | ||||
|     private fun initRecyclerView() { | ||||
|         adapter = SearchListAdapter(this, this) | ||||
|         mLayoutManager = UpLinearLayoutManager(this) | ||||
|         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 { | ||||
|             return 1 | ||||
|     @SuppressLint("SetTextI18n") | ||||
|     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? { | ||||
|             return "Search" | ||||
|     private suspend fun searchChapter(query: String, chapter: BookChapter?): List<SearchResult> { | ||||
|         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() { | ||||
|         if (tab_layout.isGone) { | ||||
|             searchView?.onActionViewCollapsed() | ||||
|             tab_layout.visible() | ||||
|         } else { | ||||
|             super.onBackPressed() | ||||
|     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) | ||||
|         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"?> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|               xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|               android:orientation="vertical" | ||||
|               android:layout_width="match_parent" | ||||
|               android:layout_height="match_parent"> | ||||
| <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"> | ||||
| 
 | ||||
|     <io.legado.app.ui.widget.TitleBar | ||||
|             android:id="@+id/title_bar" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             app:contentLayout="@layout/view_tab_layout"/> | ||||
|         android:id="@+id/title_bar" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         app:contentLayout="@layout/view_search" | ||||
|         app:contentInsetRight="24dp" | ||||
|         app:layout_constraintTop_toTopOf="parent" /> | ||||
| 
 | ||||
|     <androidx.viewpager.widget.ViewPager | ||||
|             android:id="@+id/view_pager" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="match_parent"/> | ||||
|     <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_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