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