parent
340b509c72
commit
8af2650f03
@ -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> |
Loading…
Reference in new issue