Show manga list by author
parent
4e7034cd59
commit
3fae457ec6
@ -0,0 +1,196 @@
|
|||||||
|
package org.koitharu.kotatsu.ui.main.list
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import kotlinx.android.synthetic.main.sheet_list.*
|
||||||
|
import moxy.MvpDelegate
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.model.Manga
|
||||||
|
import org.koitharu.kotatsu.core.model.MangaFilter
|
||||||
|
import org.koitharu.kotatsu.core.model.MangaTag
|
||||||
|
import org.koitharu.kotatsu.core.model.SortOrder
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||||
|
import org.koitharu.kotatsu.ui.common.BaseBottomSheet
|
||||||
|
import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener
|
||||||
|
import org.koitharu.kotatsu.ui.common.list.PaginationScrollListener
|
||||||
|
import org.koitharu.kotatsu.ui.common.list.decor.SpacingItemDecoration
|
||||||
|
import org.koitharu.kotatsu.ui.details.MangaDetailsActivity
|
||||||
|
import org.koitharu.kotatsu.utils.UiUtils
|
||||||
|
import org.koitharu.kotatsu.utils.ext.*
|
||||||
|
|
||||||
|
abstract class MangaListSheet<E> : BaseBottomSheet(R.layout.sheet_list), MangaListView<E>,
|
||||||
|
PaginationScrollListener.Callback, OnRecyclerItemClickListener<Manga>,
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener, Toolbar.OnMenuItemClickListener {
|
||||||
|
|
||||||
|
private val settings by inject<AppSettings>()
|
||||||
|
|
||||||
|
private var adapter: MangaListAdapter? = null
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
adapter = MangaListAdapter(this)
|
||||||
|
initListMode(settings.listMode)
|
||||||
|
recyclerView.adapter = adapter
|
||||||
|
recyclerView.addOnScrollListener(PaginationScrollListener(4, this))
|
||||||
|
settings.subscribe(this)
|
||||||
|
toolbar.inflateMenu(R.menu.opt_list_sheet)
|
||||||
|
toolbar.setOnMenuItemClickListener(this)
|
||||||
|
toolbar.setNavigationOnClickListener {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
if (dialog !is BottomSheetDialog) {
|
||||||
|
toolbar.isVisible = true
|
||||||
|
textView_title.isVisible = false
|
||||||
|
appbar.elevation = resources.getDimension(R.dimen.elevation_large)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
settings.unsubscribe(this)
|
||||||
|
adapter = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
if (savedInstanceState?.containsKey(MvpDelegate.MOXY_DELEGATE_TAGS_KEY) != true) {
|
||||||
|
onRequestMoreItems(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun setTitle(title: CharSequence) {
|
||||||
|
toolbar.title = title
|
||||||
|
textView_title.text = title
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun setSubtitle(subtitle: CharSequence) {
|
||||||
|
toolbar.subtitle = subtitle
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?) =
|
||||||
|
super.onCreateDialog(savedInstanceState).also {
|
||||||
|
val behavior = (it as? BottomSheetDialog)?.behavior ?: return@also
|
||||||
|
behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
|
||||||
|
private val elevation = resources.getDimension(R.dimen.elevation_large)
|
||||||
|
|
||||||
|
override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
|
||||||
|
|
||||||
|
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||||
|
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
|
||||||
|
toolbar.isVisible = true
|
||||||
|
textView_title.isVisible = false
|
||||||
|
appbar.elevation = elevation
|
||||||
|
} else {
|
||||||
|
toolbar.isVisible = false
|
||||||
|
textView_title.isVisible = true
|
||||||
|
appbar.elevation = 0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemClick(item: MenuItem) = when (item.itemId) {
|
||||||
|
R.id.action_list_mode -> {
|
||||||
|
ListModeSelectDialog.show(childFragmentManager)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||||
|
when (key) {
|
||||||
|
getString(R.string.key_list_mode) -> initListMode(settings.listMode)
|
||||||
|
getString(R.string.key_grid_size) -> UiUtils.SpanCountResolver.update(recyclerView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClick(item: Manga, position: Int, view: View) {
|
||||||
|
startActivity(MangaDetailsActivity.newIntent(context ?: return, item))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onListChanged(list: List<Manga>) {
|
||||||
|
adapter?.replaceData(list)
|
||||||
|
textView_holder.isVisible = list.isEmpty()
|
||||||
|
recyclerView.callOnScrollListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onListAppended(list: List<Manga>) {
|
||||||
|
adapter?.appendData(list)
|
||||||
|
if (list.isNotEmpty()) {
|
||||||
|
textView_holder.isVisible = false
|
||||||
|
}
|
||||||
|
recyclerView.callOnScrollListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onListError(e: Throwable) {
|
||||||
|
Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInitFilter(
|
||||||
|
sortOrders: List<SortOrder>,
|
||||||
|
tags: List<MangaTag>,
|
||||||
|
currentFilter: MangaFilter?
|
||||||
|
) = Unit
|
||||||
|
|
||||||
|
override fun onItemRemoved(item: Manga) {
|
||||||
|
adapter?.let {
|
||||||
|
it.removeItem(item)
|
||||||
|
textView_holder.isGone = it.hasItems
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(e: Throwable) {
|
||||||
|
Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadingStateChanged(isLoading: Boolean) {
|
||||||
|
progressBar.isVisible = isLoading && !recyclerView.hasItems
|
||||||
|
if (isLoading) {
|
||||||
|
textView_holder.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initListMode(mode: ListMode) {
|
||||||
|
val ctx = context ?: return
|
||||||
|
val position = recyclerView.firstItem
|
||||||
|
recyclerView.adapter = null
|
||||||
|
recyclerView.layoutManager = null
|
||||||
|
recyclerView.clearItemDecorations()
|
||||||
|
recyclerView.removeOnLayoutChangeListener(UiUtils.SpanCountResolver)
|
||||||
|
adapter?.listMode = mode
|
||||||
|
recyclerView.layoutManager = when (mode) {
|
||||||
|
ListMode.GRID -> GridLayoutManager(ctx, UiUtils.resolveGridSpanCount(ctx))
|
||||||
|
else -> LinearLayoutManager(ctx)
|
||||||
|
}
|
||||||
|
recyclerView.adapter = adapter
|
||||||
|
recyclerView.addItemDecoration(
|
||||||
|
when (mode) {
|
||||||
|
ListMode.LIST -> DividerItemDecoration(ctx, RecyclerView.VERTICAL)
|
||||||
|
ListMode.DETAILED_LIST,
|
||||||
|
ListMode.GRID -> SpacingItemDecoration(
|
||||||
|
resources.getDimensionPixelOffset(R.dimen.grid_spacing)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (mode == ListMode.GRID) {
|
||||||
|
recyclerView.addOnLayoutChangeListener(UiUtils.SpanCountResolver)
|
||||||
|
}
|
||||||
|
adapter?.notifyDataSetChanged()
|
||||||
|
recyclerView.firstItem = position
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
package org.koitharu.kotatsu.ui.search
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import moxy.ktx.moxyPresenter
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.ui.main.list.MangaListSheet
|
||||||
|
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||||
|
|
||||||
|
class MangaSearchSheet : MangaListSheet<Unit>() {
|
||||||
|
|
||||||
|
private val presenter by moxyPresenter(factory = ::SearchPresenter)
|
||||||
|
|
||||||
|
private lateinit var source: MangaSource
|
||||||
|
private lateinit var query: String
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
source = requireArguments().getParcelable(ARG_SOURCE)!!
|
||||||
|
query = requireArguments().getString(ARG_QUERY).orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
setTitle(query)
|
||||||
|
setSubtitle(getString(R.string.search_results_on_s, source.title))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestMoreItems(offset: Int) {
|
||||||
|
presenter.loadList(source, query, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val ARG_SOURCE = "source"
|
||||||
|
private const val ARG_QUERY = "query"
|
||||||
|
|
||||||
|
private const val TAG = "MangaSearchSheet"
|
||||||
|
|
||||||
|
fun show(fm: FragmentManager, source: MangaSource, query: String) {
|
||||||
|
MangaSearchSheet().withArgs(2) {
|
||||||
|
putParcelable(ARG_SOURCE, source)
|
||||||
|
putString(ARG_QUERY, query)
|
||||||
|
}.show(fm, TAG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
<?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"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/appbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="?android:windowBackground"
|
||||||
|
android:elevation="0dp"
|
||||||
|
app:elevation="0dp">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
style="@style/Widget.MaterialComponents.Toolbar.Surface"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:elevation="0dp"
|
||||||
|
android:outlineProvider="@null"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:elevation="0dp"
|
||||||
|
app:navigationIcon="@drawable/ic_cross"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView_title"
|
||||||
|
style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
tools:visibility="gone" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:minHeight="120dp">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:listitem="@layout/item_manga_list" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView_holder"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textAppearance="?android:textAppearanceMedium"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
tools:text="@tools:sample/lorem[3]" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_list_mode"
|
||||||
|
android:orderInCategory="20"
|
||||||
|
android:title="@string/list_mode"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
</menu>
|
||||||
Loading…
Reference in New Issue