Show manga list by author

This commit is contained in:
Koitharu
2020-03-14 13:47:24 +02:00
parent 4e7034cd59
commit 3fae457ec6
6 changed files with 357 additions and 4 deletions

View File

@@ -1,13 +1,15 @@
package org.koitharu.kotatsu.ui.common
import android.content.Context
import android.view.View
import androidx.annotation.DrawableRes
import com.google.android.material.chip.Chip
import org.koitharu.kotatsu.utils.ext.getThemeColor
class ChipsFactory(private val context: Context) {
fun create(convertView: Chip? = null, text: CharSequence, @DrawableRes iconRes: Int = 0, tag: Any? = null): Chip {
fun create(convertView: Chip? = null, text: CharSequence, @DrawableRes iconRes: Int = 0,
tag: Any? = null, onClickListener: View.OnClickListener? = null): Chip {
val chip = convertView ?: Chip(context).apply {
setTextColor(context.getThemeColor(android.R.attr.textColorPrimary))
isCloseIconVisible = false
@@ -20,6 +22,7 @@ class ChipsFactory(private val context: Context) {
chip.setChipIconResource(iconRes)
}
chip.tag = tag
chip.setOnClickListener(onClickListener)
return chip
}
}

View File

@@ -1,9 +1,11 @@
package org.koitharu.kotatsu.ui.details
import android.text.Spanned
import android.view.View
import androidx.core.text.parseAsHtml
import androidx.core.view.isVisible
import coil.api.load
import com.google.android.material.chip.Chip
import kotlinx.android.synthetic.main.fragment_details.*
import moxy.ktx.moxyPresenter
import org.koitharu.kotatsu.R
@@ -13,11 +15,12 @@ import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.ui.common.BaseFragment
import org.koitharu.kotatsu.ui.main.list.favourites.categories.FavouriteCategoriesDialog
import org.koitharu.kotatsu.ui.reader.ReaderActivity
import org.koitharu.kotatsu.ui.search.MangaSearchSheet
import org.koitharu.kotatsu.utils.ext.addChips
import org.koitharu.kotatsu.utils.ext.textAndVisible
import kotlin.math.roundToInt
class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetailsView {
class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetailsView, View.OnClickListener {
@Suppress("unused")
private val presenter by moxyPresenter(factory = MangaDetailsPresenter.Companion::getInstance)
@@ -47,7 +50,9 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
chips_tags.addChips(listOf(a)) {
create(
text = it,
iconRes = R.drawable.ic_chip_user
iconRes = R.drawable.ic_chip_user,
tag = it,
onClickListener = this@MangaDetailsFragment
)
}
}
@@ -55,7 +60,8 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
create(
text = it.title,
iconRes = R.drawable.ic_chip_tag,
tag = it
tag = it,
onClickListener = this@MangaDetailsFragment
)
}
imageView_favourite.setOnClickListener {
@@ -87,6 +93,15 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
override fun onMangaRemoved(manga: Manga) = Unit //handled in activity
override fun onClick(v: View) {
if (v is Chip) {
when(val tag = v.tag) {
is String -> MangaSearchSheet.show(activity?.supportFragmentManager ?: childFragmentManager,
manga?.source ?: return, tag)
}
}
}
private fun updateReadButton() {
if (manga?.chapters.isNullOrEmpty()) {
button_read.isEnabled = false

View File

@@ -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
}
}

View File

@@ -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)
}
}
}

View File

@@ -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>

View File

@@ -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>