From 8af2650f03b859bfdbf72f38a529fa70ace08b09 Mon Sep 17 00:00:00 2001 From: Jason Yao Date: Sat, 12 Sep 2020 09:38:43 -0400 Subject: [PATCH] Search content --- .../main/java/io/legado/app/help/BookHelp.kt | 1 + .../app/ui/book/read/ReadBookActivity.kt | 12 ++ .../io/legado/app/ui/book/read/ReadMenu.kt | 7 + .../book/searchContent/SearchListActivity.kt | 89 ++++++++++ .../book/searchContent/SearchListAdapter.kt | 66 ++++++++ .../book/searchContent/SearchListFragment.kt | 152 ++++++++++++++++++ .../book/searchContent/SearchListViewModel.kt | 32 ++++ .../main/res/layout/activity_search_list.xml | 19 +++ .../main/res/layout/fragment_search_list.xml | 66 ++++++++ app/src/main/res/layout/item_search_list.xml | 31 ++++ app/src/main/res/layout/view_read_menu.xml | 34 ++++ app/src/main/res/values/strings.xml | 1 + 12 files changed, 510 insertions(+) create mode 100644 app/src/main/java/io/legado/app/ui/book/searchContent/SearchListActivity.kt create mode 100644 app/src/main/java/io/legado/app/ui/book/searchContent/SearchListAdapter.kt create mode 100644 app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt create mode 100644 app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt create mode 100644 app/src/main/res/layout/activity_search_list.xml create mode 100644 app/src/main/res/layout/fragment_search_list.xml create mode 100644 app/src/main/res/layout/item_search_list.xml diff --git a/app/src/main/java/io/legado/app/help/BookHelp.kt b/app/src/main/java/io/legado/app/help/BookHelp.kt index 89a2bf41b..f52252f0b 100644 --- a/app/src/main/java/io/legado/app/help/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/BookHelp.kt @@ -145,6 +145,7 @@ object BookHelp { return fileNameList } + // 检测该章节是否下载 fun hasContent(book: Book, bookChapter: BookChapter): Boolean { return if (book.isLocalBook()) { true 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..af4aa111a 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 @@ -667,6 +667,18 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo } } + /** + * 打开搜索界面 + */ + override fun openSearchList() { + ReadBook.book?.let { + startActivityForResult( + requestCodeChapterList, + Pair("bookUrl", it.bookUrl) + ) + } + } + /** * 替换规则变化 */ 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..bb8d0cb49 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListActivity.kt @@ -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(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() + } + } +} \ 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..3c5d28971 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListAdapter.kt @@ -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(context, R.layout.item_search_list) { + + val cacheFileNames = hashSetOf() + + override fun convert(holder: ItemViewHolder, item: BookChapter, payloads: MutableList) { + 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 + } +} \ 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..be80cb9ac --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt @@ -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(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>? = 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(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() + } + +} \ 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..153f61416 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt @@ -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?) + } + +} \ 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..0b8bab809 --- /dev/null +++ b/app/src/main/res/layout/item_search_list.xml @@ -0,0 +1,31 @@ + + + + + + + + \ 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..22a3bc236 100644 --- a/app/src/main/res/layout/view_read_menu.xml +++ b/app/src/main/res/layout/view_read_menu.xml @@ -260,6 +260,40 @@ android:textSize="12sp" /> + + + + + + + + Save, Import, Share theme Share selected sources Sort by update time + Search content \ No newline at end of file