Search content

pull/374/head
Jason Yao 4 years ago
parent 340b509c72
commit 8af2650f03
  1. 1
      app/src/main/java/io/legado/app/help/BookHelp.kt
  2. 12
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  3. 7
      app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
  4. 89
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchListActivity.kt
  5. 66
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchListAdapter.kt
  6. 152
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt
  7. 32
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt
  8. 19
      app/src/main/res/layout/activity_search_list.xml
  9. 66
      app/src/main/res/layout/fragment_search_list.xml
  10. 31
      app/src/main/res/layout/item_search_list.xml
  11. 34
      app/src/main/res/layout/view_read_menu.xml
  12. 1
      app/src/main/res/values/strings.xml

@ -145,6 +145,7 @@ object BookHelp {
return fileNameList return fileNameList
} }
// 检测该章节是否下载
fun hasContent(book: Book, bookChapter: BookChapter): Boolean { fun hasContent(book: Book, bookChapter: BookChapter): Boolean {
return if (book.isLocalBook()) { return if (book.isLocalBook()) {
true true

@ -667,6 +667,18 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
} }
} }
/**
* 打开搜索界面
*/
override fun openSearchList() {
ReadBook.book?.let {
startActivityForResult<ChapterListActivity>(
requestCodeChapterList,
Pair("bookUrl", it.bookUrl)
)
}
}
/** /**
* 替换规则变化 * 替换规则变化
*/ */

@ -200,6 +200,12 @@ class ReadMenu : FrameLayout {
} }
} }
ll_search.onClick {
runMenuOut {
callBack?.openSearchList()
}
}
//朗读 //朗读
ll_read_aloud.onClick { ll_read_aloud.onClick {
runMenuOut { runMenuOut {
@ -291,6 +297,7 @@ class ReadMenu : FrameLayout {
fun autoPage() fun autoPage()
fun openReplaceRule() fun openReplaceRule()
fun openChapterList() fun openChapterList()
fun openSearchList()
fun showReadStyle() fun showReadStyle()
fun showMoreSetting() fun showMoreSetting()
fun showReadAloudDialog() fun showReadAloudDialog()

@ -0,0 +1,89 @@
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<SearchListViewModel>(R.layout.activity_search_list) {
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()
false
}
searchView?.setOnSearchClickListener { tab_layout.gone() }
searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
viewModel.startContentSearch(query)
return false
}
override fun onQueryTextChange(newText: String): Boolean {
return false
}
})
return super.onCompatCreateOptionsMenu(menu)
}
private inner class TabFragmentPageAdapter internal constructor(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()
}
}
}

@ -0,0 +1,66 @@
package io.legado.app.ui.book.searchContent
import android.content.Context
import android.view.View
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.tv_chapter_name
import kotlinx.android.synthetic.main.item_chapter_list.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
class SearchListAdapter(context: Context, val callback: Callback) :
SimpleRecyclerAdapter<BookChapter>(context, R.layout.item_search_list) {
val cacheFileNames = hashSetOf<String>()
override fun convert(holder: ItemViewHolder, item: BookChapter, payloads: MutableList<Any>) {
with(holder.itemView) {
val isDur = callback.durChapterIndex() == item.index
val cached = callback.isLocalBook
|| cacheFileNames.contains(BookHelp.formatChapterName(item))
if (payloads.isEmpty()) {
// set search result color here
if (isDur) {
tv_chapter_name.setTextColor(context.accentColor)
} else {
tv_chapter_name.setTextColor(context.getCompatColor(R.color.primaryText))
}
tv_chapter_name.text = item.title
upHasCache(this, isDur, cached)
} else {
upHasCache(this, isDur, cached)
}
}
}
override fun registerListener(holder: ItemViewHolder) {
holder.itemView.onClick {
getItem(holder.layoutPosition)?.let {
callback.openSearchResult(it)
}
}
}
private fun upHasCache(itemView: View, isDur: Boolean, cached: Boolean) = itemView.apply {
tv_chapter_name.paint.isFakeBoldText = cached
iv_checked.setImageResource(R.drawable.ic_outline_cloud_24)
iv_checked.visible(!cached)
if (isDur) {
iv_checked.setImageResource(R.drawable.ic_check)
iv_checked.visible()
}
}
interface Callback {
val isLocalBook: Boolean
fun openSearchResult(bookChapter: BookChapter)
fun durChapterIndex(): Int
}
}

@ -0,0 +1,152 @@
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.view.View
import androidx.lifecycle.LiveData
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.BookHelp
import io.legado.app.lib.theme.bottomBackground
import io.legado.app.lib.theme.getPrimaryTextColor
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.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.anko.sdk27.listeners.onClick
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 var durChapterIndex = 0
private lateinit var mLayoutManager: UpLinearLayoutManager
private var tocLiveData: LiveData<List<BookChapter>>? = null
private var scrollToDurChapter = false
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
viewModel.searchCallBack = this
/* set color for the bottom bar
val bbg = bottomBackground
val btc = requireContext().getPrimaryTextColor(ColorUtils.isColorLight(bbg))
ll_chapter_base_info.setBackgroundColor(bbg)
tv_current_chapter_info.setTextColor(btc)
iv_chapter_top.setColorFilter(btc)
iv_chapter_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)
}
}
tv_current_search_info.onClick {
mLayoutManager.scrollToPositionWithOffset(durChapterIndex, 0)
}
}
@SuppressLint("SetTextI18n")
private fun initBook() {
launch {
initDoc()
viewModel.book?.let {
durChapterIndex = it.durChapterIndex
tv_current_search_info.text =
"${it.durChapterTitle}(${it.durChapterIndex + 1}/${it.totalChapterNum})"
initCacheFileNames(it)
}
}
}
private fun initDoc() {
tocLiveData?.removeObservers(this@SearchListFragment)
tocLiveData = App.db.bookChapterDao().observeByBook(viewModel.bookUrl)
tocLiveData?.observe(viewLifecycleOwner, {
adapter.setItems(it)
if (!scrollToDurChapter) {
mLayoutManager.scrollToPositionWithOffset(durChapterIndex, 0)
scrollToDurChapter = true
}
})
}
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)
}
}
}
}
override fun startContentSearch(newText: String?) {
if (newText.isNullOrBlank()) {
//initDoc()
} else {
if (isLocalBook){
}
tocLiveData?.removeObservers(this)
tocLiveData = App.db.bookChapterDao().liveDataSearch(viewModel.bookUrl, newText)
tocLiveData?.observe(viewLifecycleOwner, {
adapter.setItems(it)
})
}
}
override val isLocalBook: Boolean
get() = viewModel.book?.isLocalBook() == true
override fun durChapterIndex(): Int {
return durChapterIndex
}
override fun openSearchResult(bookChapter: BookChapter) {
activity?.setResult(RESULT_OK, Intent().putExtra("index", bookChapter.index))
activity?.finish()
}
}

@ -0,0 +1,32 @@
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
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?)
}
}

@ -0,0 +1,19 @@
<?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">
<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"/>
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

@ -0,0 +1,66 @@
<?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>

@ -0,0 +1,31 @@
<?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"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:padding="12dp">
<TextView
android:id="@+id/tv_chapter_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:singleLine="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/iv_checked" />
<ImageView
android:id="@+id/iv_checked"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_check"
android:visibility="invisible"
android:contentDescription="@string/success"
app:tint="@color/secondaryText"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -260,6 +260,40 @@
android:textSize="12sp" /> android:textSize="12sp" />
</LinearLayout> </LinearLayout>
<!--目录按钮-->
<LinearLayout
android:id="@+id/ll_search"
android:layout_width="60dp"
android:layout_height="50dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/search_content"
android:focusable="true"
android:orientation="vertical"
android:paddingBottom="7dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_search"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:contentDescription="@string/search_content"
android:src="@drawable/ic_toc"
app:tint="@color/primaryText"
tools:ignore="NestedWeights" />
<TextView
android:id="@+id/tv_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="3dp"
android:maxLines="1"
android:text="@string/search_content"
android:textColor="@color/primaryText"
android:textSize="12sp" />
</LinearLayout>
<View <View
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"

@ -763,5 +763,6 @@
<string name="theme_list_summary">Save, Import, Share theme</string> <string name="theme_list_summary">Save, Import, Share theme</string>
<string name="share_selected_source">Share selected sources</string> <string name="share_selected_source">Share selected sources</string>
<string name="sort_by_lastUppdateTime">Sort by update time</string> <string name="sort_by_lastUppdateTime">Sort by update time</string>
<string name="search_content">Search content</string>
</resources> </resources>
Loading…
Cancel
Save