Show manga list by author
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
77
app/src/main/res/layout/sheet_list.xml
Normal file
77
app/src/main/res/layout/sheet_list.xml
Normal 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>
|
||||
12
app/src/main/res/menu/opt_list_sheet.xml
Normal file
12
app/src/main/res/menu/opt_list_sheet.xml
Normal 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>
|
||||
Reference in New Issue
Block a user