Unify list spacing approach

This commit is contained in:
Koitharu
2023-07-26 16:37:53 +03:00
parent 61a7f1c830
commit 2342594885
25 changed files with 166 additions and 327 deletions

View File

@@ -7,6 +7,7 @@ import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD
import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
import org.koitharu.kotatsu.list.ui.model.ListHeader
@@ -19,10 +20,9 @@ class BookmarksAdapter(
) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {
init {
delegatesManager
.addDelegate(ITEM_TYPE_THUMBNAIL, bookmarkLargeAD(coil, lifecycleOwner, clickListener))
.addDelegate(ITEM_TYPE_HEADER, listHeaderAD(null))
.addDelegate(ITEM_LOADING, loadingFooterAD())
addDelegate(ListItemType.PAGE_THUMB, bookmarkLargeAD(coil, lifecycleOwner, clickListener))
addDelegate(ListItemType.HEADER, listHeaderAD(null))
addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())
}
override fun getSectionText(context: Context, position: Int): CharSequence? {
@@ -35,11 +35,4 @@ class BookmarksAdapter(
}
return null
}
companion object {
const val ITEM_TYPE_THUMBNAIL = 0
const val ITEM_TYPE_HEADER = 1
const val ITEM_LOADING = 2
}
}

View File

@@ -15,7 +15,6 @@ import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetCallback
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
@@ -28,13 +27,14 @@ import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.SheetPagesBinding
import org.koitharu.kotatsu.list.ui.MangaListSpanResolver
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import org.koitharu.kotatsu.reader.ui.thumbnails.OnPageSelectListener
import org.koitharu.kotatsu.reader.ui.thumbnails.PageThumbnail
import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter
import javax.inject.Inject
import kotlin.math.roundToInt
@@ -75,9 +75,7 @@ class BookmarksSheet :
)
viewBinding?.headerBar?.setTitle(R.string.bookmarks)
with(binding.recyclerView) {
addItemDecoration(
SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.grid_spacing)),
)
addItemDecoration(TypedListSpacingDecoration(context))
adapter = bookmarksAdapter
addOnLayoutChangeListener(spanResolver)
spanResolver?.setGridSize(settings.gridSize / 100f, this)
@@ -145,7 +143,7 @@ class BookmarksSheet :
override fun getSpanSize(position: Int): Int {
val total = (viewBinding?.recyclerView?.layoutManager as? GridLayoutManager)?.spanCount ?: return 1
return when (bookmarksAdapter?.getItemViewType(position)) {
PageThumbnailAdapter.ITEM_TYPE_THUMBNAIL -> 1
ListItemType.PAGE_THUMB.ordinal -> 1
else -> total
}
}

View File

@@ -1,106 +0,0 @@
package org.koitharu.kotatsu.core.ui
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
import androidx.activity.OnBackPressedDispatcher
import androidx.core.view.updateLayoutParams
import androidx.viewbinding.ViewBinding
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.dialog.AppBottomSheetDialog
import org.koitharu.kotatsu.core.util.ext.findActivity
import org.koitharu.kotatsu.core.util.ext.getDisplaySize
import com.google.android.material.R as materialR
@Deprecated(
"Use BaseAdaptiveSheet",
replaceWith = ReplaceWith("BaseAdaptiveSheet<B>", "org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet"),
)
abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
var viewBinding: B? = null
private set
@Deprecated("", ReplaceWith("requireViewBinding()"))
protected val binding: B
get() = requireViewBinding()
protected val behavior: BottomSheetBehavior<*>?
get() = (dialog as? BottomSheetDialog)?.behavior
val isExpanded: Boolean
get() = behavior?.state == BottomSheetBehavior.STATE_EXPANDED
val onBackPressedDispatcher: OnBackPressedDispatcher
get() = (requireDialog() as AppBottomSheetDialog).onBackPressedDispatcher
final override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
val binding = onCreateViewBinding(inflater, container)
viewBinding = binding
return binding.root
}
final override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = requireViewBinding()
// Enforce max width for tablets
val width = resources.getDimensionPixelSize(R.dimen.bottom_sheet_width)
if (width > 0) {
behavior?.maxWidth = width
}
// Set peek height to 40% display height
binding.root.context.findActivity()?.getDisplaySize()?.let {
behavior?.peekHeight = (it.height() * 0.4).toInt()
}
onViewBindingCreated(binding, savedInstanceState)
}
override fun onDestroyView() {
viewBinding = null
super.onDestroyView()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return AppBottomSheetDialog(requireContext(), theme)
}
fun addBottomSheetCallback(callback: BottomSheetBehavior.BottomSheetCallback) {
val b = behavior ?: return
b.addBottomSheetCallback(callback)
val rootView = dialog?.findViewById<View>(materialR.id.design_bottom_sheet)
if (rootView != null) {
callback.onStateChanged(rootView, b.state)
}
}
protected abstract fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): B
protected open fun onViewBindingCreated(binding: B, savedInstanceState: Bundle?) = Unit
protected fun setExpanded(isExpanded: Boolean, isLocked: Boolean) {
val b = behavior ?: return
if (isExpanded) {
b.state = BottomSheetBehavior.STATE_EXPANDED
}
b.isFitToContents = !isExpanded
val rootView = dialog?.findViewById<View>(materialR.id.design_bottom_sheet)
rootView?.updateLayoutParams {
height = if (isExpanded) LayoutParams.MATCH_PARENT else LayoutParams.WRAP_CONTENT
}
b.isDraggable = !isLocked
}
fun requireViewBinding(): B = checkNotNull(viewBinding) {
"Fragment $this did not return a ViewBinding from onCreateView() or this was called before onCreateView()."
}
}

View File

@@ -9,6 +9,7 @@ import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.flow.FlowCollector
import org.koitharu.kotatsu.core.util.ContinuationResumeRunnable
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.model.ListModel
import kotlin.coroutines.suspendCoroutine
@@ -25,6 +26,10 @@ open class BaseListAdapter<T : ListModel>(
setItems(value, ContinuationResumeRunnable(cont))
}
fun addDelegate(type: ListItemType, delegate: AdapterDelegate<List<T>>) {
delegatesManager.addDelegate(type.ordinal, delegate)
}
fun addListListener(listListener: ListListener<T>) {
differ.addListListener(listListener)
}

View File

@@ -1,35 +0,0 @@
package org.koitharu.kotatsu.core.ui.list.decor
import android.graphics.Rect
import android.util.SparseIntArray
import android.view.View
import androidx.core.util.getOrDefault
import androidx.core.util.set
import androidx.recyclerview.widget.RecyclerView
class TypedSpacingItemDecoration(
vararg spacingMapping: Pair<Int, Int>,
private val fallbackSpacing: Int = 0,
) : RecyclerView.ItemDecoration() {
private val mapping = SparseIntArray(spacingMapping.size)
init {
spacingMapping.forEach { (k, v) -> mapping[k] = v }
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val itemType = parent.getChildViewHolder(view)?.itemViewType
val spacing = if (itemType == null) {
fallbackSpacing
} else {
mapping.getOrDefault(itemType, fallbackSpacing)
}
outRect.set(spacing, spacing, spacing, spacing)
}
}

View File

@@ -17,13 +17,13 @@ import kotlinx.coroutines.flow.FlowCollector
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.databinding.ActivityDownloadsBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.ui.worker.PausingReceiver
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import javax.inject.Inject
@AndroidEntryPoint
@@ -46,7 +46,7 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>(),
listSpacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val downloadsAdapter = DownloadsAdapter(this, coil, this)
val decoration = SpacingItemDecoration(listSpacing)
val decoration = TypedListSpacingDecoration(this)
selectionController = ListSelectionController(
activity = this,
decoration = DownloadsSelectionDecoration(this),

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.download.ui.list
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
@@ -15,13 +16,9 @@ class DownloadsAdapter(
) : BaseListAdapter<ListModel>() {
init {
delegatesManager.addDelegate(ITEM_TYPE_DOWNLOAD, downloadItemAD(lifecycleOwner, coil, listener))
.addDelegate(loadingStateAD())
.addDelegate(emptyStateListAD(coil, lifecycleOwner, null))
.addDelegate(listHeaderAD(null))
}
companion object {
const val ITEM_TYPE_DOWNLOAD = 0
addDelegate(ListItemType.DOWNLOAD, downloadItemAD(lifecycleOwner, coil, listener))
addDelegate(ListItemType.STATE_LOADING, loadingStateAD())
addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, null))
addDelegate(ListItemType.HEADER, listHeaderAD(null))
}
}

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.explore.ui
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup
import org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
class ExploreGridSpanSizeLookup(
private val adapter: ExploreAdapter,
@@ -11,6 +12,6 @@ class ExploreGridSpanSizeLookup(
override fun getSpanSize(position: Int): Int {
val itemType = adapter.getItemViewType(position)
return if (itemType == ExploreAdapter.ITEM_TYPE_SOURCE_GRID) 1 else layoutManager.spanCount
return if (itemType == ListItemType.EXPLORE_SOURCE_GRID.ordinal) 1 else layoutManager.spanCount
}
}

View File

@@ -6,6 +6,7 @@ import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.widgets.TipView
import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.emptyHintAD
import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
@@ -23,29 +24,16 @@ class ExploreAdapter(
) : BaseListAdapter<ListModel>() {
init {
delegatesManager
.addDelegate(ITEM_TYPE_BUTTONS, exploreButtonsAD(listener))
.addDelegate(
ITEM_TYPE_RECOMMENDATION,
exploreRecommendationItemAD(coil, listener, mangaClickListener, lifecycleOwner),
)
.addDelegate(ITEM_TYPE_HEADER, listHeaderAD(listener))
.addDelegate(ITEM_TYPE_SOURCE_LIST, exploreSourceListItemAD(coil, clickListener, lifecycleOwner))
.addDelegate(ITEM_TYPE_SOURCE_GRID, exploreSourceGridItemAD(coil, clickListener, lifecycleOwner))
.addDelegate(ITEM_TYPE_HINT, emptyHintAD(coil, lifecycleOwner, listener))
.addDelegate(ITEM_TYPE_LOADING, loadingStateAD())
.addDelegate(ITEM_TIP, tipAD(tipClickListener))
}
companion object {
const val ITEM_TYPE_BUTTONS = 0
const val ITEM_TYPE_HEADER = 1
const val ITEM_TYPE_SOURCE_LIST = 2
const val ITEM_TYPE_SOURCE_GRID = 3
const val ITEM_TYPE_HINT = 4
const val ITEM_TYPE_LOADING = 5
const val ITEM_TYPE_RECOMMENDATION = 6
const val ITEM_TIP = 7
addDelegate(ListItemType.EXPLORE_BUTTONS, exploreButtonsAD(listener))
addDelegate(
ListItemType.EXPLORE_SUGGESTION,
exploreRecommendationItemAD(coil, listener, mangaClickListener, lifecycleOwner),
)
addDelegate(ListItemType.HEADER, listHeaderAD(listener))
addDelegate(ListItemType.EXPLORE_SOURCE_LIST, exploreSourceListItemAD(coil, clickListener, lifecycleOwner))
addDelegate(ListItemType.EXPLORE_SOURCE_GRID, exploreSourceGridItemAD(coil, clickListener, lifecycleOwner))
addDelegate(ListItemType.HINT_EMPTY, emptyHintAD(coil, lifecycleOwner, listener))
addDelegate(ListItemType.STATE_LOADING, loadingStateAD())
addDelegate(ListItemType.TIP, tipAD(tipClickListener))
}
}

View File

@@ -5,6 +5,7 @@ import androidx.recyclerview.widget.AsyncListDiffer.ListListener
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
import org.koitharu.kotatsu.filter.ui.model.FilterItem
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD
import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
@@ -16,13 +17,12 @@ class FilterAdapter(
) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {
init {
delegatesManager
.addDelegate(ITEM_TYPE_SORT, filterSortDelegate(listener))
.addDelegate(ITEM_TYPE_TAG, filterTagDelegate(listener))
.addDelegate(ITEM_TYPE_HEADER, listHeaderAD(listener))
.addDelegate(loadingStateAD())
.addDelegate(loadingFooterAD())
.addDelegate(filterErrorDelegate())
addDelegate(ListItemType.FILTER_SORT, filterSortDelegate(listener))
addDelegate(ListItemType.FILTER_TAG, filterTagDelegate(listener))
addDelegate(ListItemType.HEADER, listHeaderAD(listener))
addDelegate(ListItemType.STATE_LOADING, loadingStateAD())
addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())
addDelegate(ListItemType.FOOTER_ERROR, filterErrorDelegate())
differ.addListListener(listListener)
}
@@ -36,11 +36,4 @@ class FilterAdapter(
}
return null
}
companion object {
const val ITEM_TYPE_SORT = 0
const val ITEM_TYPE_TAG = 1
const val ITEM_TYPE_HEADER = 2
}
}

View File

@@ -1,28 +0,0 @@
package org.koitharu.kotatsu.filter.ui
import android.content.Context
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.R
class FilterItemDecoration(
context: Context,
) : RecyclerView.ItemDecoration() {
private val spacing = context.resources.getDimensionPixelOffset(R.dimen.list_spacing)
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val itemType = parent.getChildViewHolder(view)?.itemViewType ?: -1
if (itemType == FilterAdapter.ITEM_TYPE_HEADER) {
outRect.set(spacing, 0, spacing, 0)
} else {
outRect.set(0, 0, 0, 0)
}
}
}

View File

@@ -14,6 +14,7 @@ import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.databinding.SheetFilterBinding
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListModel
class FilterSheetFragment :
@@ -32,7 +33,7 @@ class FilterSheetFragment :
val adapter = FilterAdapter(filter, this)
binding.recyclerView.adapter = adapter
filter.filterItems.observe(viewLifecycleOwner, adapter)
binding.recyclerView.addItemDecoration(FilterItemDecoration(binding.root.context))
binding.recyclerView.addItemDecoration(TypedListSpacingDecoration(binding.root.context))
if (dialog == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
binding.recyclerView.scrollIndicators = 0

View File

@@ -21,6 +21,7 @@ open class ListModelDiffCallback<T : ListModel> : DiffUtil.ItemCallback<T>() {
val PAYLOAD_CHECKED_CHANGED = Any()
val PAYLOAD_NESTED_LIST_CHANGED = Any()
val PAYLOAD_PROGRESS_CHANGED = Any()
val PAYLOAD_ANYTHING_CHANGED = Any()
}
}

View File

@@ -28,13 +28,10 @@ import org.koitharu.kotatsu.core.ui.list.FitHeightGridLayoutManager
import org.koitharu.kotatsu.core.ui.list.FitHeightLinearLayoutManager
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.PaginationScrollListener
import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.core.ui.list.decor.TypedSpacingItemDecoration
import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.util.ShareHelper
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
import org.koitharu.kotatsu.core.util.ext.clearItemDecorations
import org.koitharu.kotatsu.core.util.ext.measureHeight
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
@@ -45,9 +42,10 @@ import org.koitharu.kotatsu.databinding.FragmentListBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesSheet
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter.Companion.ITEM_TYPE_MANGA_GRID
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
@@ -106,6 +104,7 @@ abstract class MangaListFragment :
setHasFixedSize(true)
adapter = listAdapter
checkNotNull(selectionController).attachToRecyclerView(binding.recyclerView)
addItemDecoration(TypedListSpacingDecoration(context))
addOnScrollListener(paginationListener!!)
fastScroller.setFastScrollListener(this@MangaListFragment)
}
@@ -238,24 +237,17 @@ abstract class MangaListFragment :
private fun onListModeChanged(mode: ListMode) {
spanSizeLookup.invalidateCache()
with(requireViewBinding().recyclerView) {
clearItemDecorations()
removeOnLayoutChangeListener(spanResolver)
when (mode) {
ListMode.LIST -> {
layoutManager = FitHeightLinearLayoutManager(context)
val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
val decoration = TypedSpacingItemDecoration(
MangaListAdapter.ITEM_TYPE_MANGA_LIST to 0,
fallbackSpacing = spacing,
)
addItemDecoration(decoration)
updatePadding(left = 0, right = 0)
}
ListMode.DETAILED_LIST -> {
layoutManager = FitHeightLinearLayoutManager(context)
val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
updatePadding(left = spacing, right = spacing)
addItemDecoration(SpacingItemDecoration(spacing))
}
ListMode.GRID -> {
@@ -263,12 +255,10 @@ abstract class MangaListFragment :
it.spanSizeLookup = spanSizeLookup
}
val spacing = resources.getDimensionPixelOffset(R.dimen.grid_spacing)
addItemDecoration(SpacingItemDecoration(spacing))
updatePadding(left = spacing, right = spacing)
addOnLayoutChangeListener(spanResolver)
}
}
selectionController?.attachToRecyclerView(requireViewBinding().recyclerView)
}
}
@@ -343,7 +333,7 @@ abstract class MangaListFragment :
override fun getSpanSize(position: Int): Int {
val total = (viewBinding?.recyclerView?.layoutManager as? GridLayoutManager)?.spanCount ?: return 1
return when (listAdapter?.getItemViewType(position)) {
ITEM_TYPE_MANGA_GRID -> 1
ListItemType.MANGA_GRID.ordinal -> 1
else -> total
}
}

View File

@@ -0,0 +1,25 @@
package org.koitharu.kotatsu.list.ui.adapter
enum class ListItemType {
FILTER_SORT,
FILTER_TAG,
HEADER,
MANGA_LIST,
MANGA_LIST_DETAILED,
MANGA_GRID,
FOOTER_LOADING,
FOOTER_ERROR,
STATE_LOADING,
STATE_ERROR,
STATE_EMPTY,
EXPLORE_BUTTONS,
EXPLORE_SOURCE_GRID,
EXPLORE_SOURCE_LIST,
EXPLORE_SUGGESTION,
TIP,
HINT_EMPTY,
PAGE_THUMB,
FEED,
DOWNLOAD,
}

View File

@@ -14,6 +14,7 @@ import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemMangaGridBinding
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaGridModel
import org.koitharu.kotatsu.list.ui.size.ItemSizeResolver
@@ -39,7 +40,7 @@ fun mangaGridItemAD(
bind { payloads ->
binding.textViewTitle.text = item.title
binding.progressView.setPercent(item.progress, MangaListAdapter.PAYLOAD_PROGRESS in payloads)
binding.progressView.setPercent(item.progress, ListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED in payloads)
binding.imageViewCover.newImageRequest(lifecycleOwner, item.coverUrl)?.run {
size(CoverSizeResolver(binding.imageViewCover))
placeholder(R.drawable.ic_placeholder)

View File

@@ -12,30 +12,14 @@ open class MangaListAdapter(
) : BaseListAdapter<ListModel>() {
init {
delegatesManager
.addDelegate(ITEM_TYPE_MANGA_LIST, mangaListItemAD(coil, lifecycleOwner, listener))
.addDelegate(ITEM_TYPE_MANGA_LIST_DETAILED, mangaListDetailedItemAD(coil, lifecycleOwner, listener))
.addDelegate(ITEM_TYPE_MANGA_GRID, mangaGridItemAD(coil, lifecycleOwner, null, listener))
.addDelegate(ITEM_TYPE_LOADING_FOOTER, loadingFooterAD())
.addDelegate(ITEM_TYPE_LOADING_STATE, loadingStateAD())
.addDelegate(ITEM_TYPE_ERROR_STATE, errorStateListAD(listener))
.addDelegate(ITEM_TYPE_ERROR_FOOTER, errorFooterAD(listener))
.addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listener))
.addDelegate(ITEM_TYPE_HEADER, listHeaderAD(listener))
}
companion object {
const val ITEM_TYPE_MANGA_LIST = 0
const val ITEM_TYPE_MANGA_LIST_DETAILED = 1
const val ITEM_TYPE_MANGA_GRID = 2
const val ITEM_TYPE_LOADING_FOOTER = 3
const val ITEM_TYPE_LOADING_STATE = 4
const val ITEM_TYPE_ERROR_STATE = 6
const val ITEM_TYPE_ERROR_FOOTER = 7
const val ITEM_TYPE_EMPTY = 8
const val ITEM_TYPE_HEADER = 9
val PAYLOAD_PROGRESS = Any()
addDelegate(ListItemType.MANGA_LIST, mangaListItemAD(coil, lifecycleOwner, listener))
addDelegate(ListItemType.MANGA_LIST_DETAILED, mangaListDetailedItemAD(coil, lifecycleOwner, listener))
addDelegate(ListItemType.MANGA_GRID, mangaGridItemAD(coil, lifecycleOwner, null, listener))
addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())
addDelegate(ListItemType.STATE_LOADING, loadingStateAD())
addDelegate(ListItemType.STATE_ERROR, errorStateListAD(listener))
addDelegate(ListItemType.FOOTER_ERROR, errorFooterAD(listener))
addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listener))
addDelegate(ListItemType.HEADER, listHeaderAD(listener))
}
}

View File

@@ -18,6 +18,7 @@ import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ItemMangaListDetailsBinding
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaListDetailedModel
import org.koitharu.kotatsu.parsers.model.MangaTag
@@ -52,7 +53,7 @@ fun mangaListDetailedItemAD(
bind { payloads ->
binding.textViewTitle.text = item.title
binding.textViewSubtitle.textAndVisible = item.subtitle
binding.progressView.setPercent(item.progress, MangaListAdapter.PAYLOAD_PROGRESS in payloads)
binding.progressView.setPercent(item.progress, ListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED in payloads)
binding.imageViewCover.newImageRequest(lifecycleOwner, item.coverUrl)?.run {
size(CoverSizeResolver(binding.imageViewCover))
placeholder(R.drawable.ic_placeholder)

View File

@@ -0,0 +1,56 @@
package org.koitharu.kotatsu.list.ui.adapter
import android.content.Context
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
import org.koitharu.kotatsu.R
class TypedListSpacingDecoration(
context: Context,
) : ItemDecoration() {
private val spacingList = context.resources.getDimensionPixelOffset(R.dimen.list_spacing)
private val spacingGrid = context.resources.getDimensionPixelOffset(R.dimen.grid_spacing)
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val itemType = parent.getChildViewHolder(view)?.itemViewType?.let {
ListItemType.values().getOrNull(it)
}
when (itemType) {
ListItemType.FILTER_SORT,
ListItemType.FILTER_TAG -> outRect.set(0)
ListItemType.HEADER -> outRect.set(spacingList, 0, spacingList, 0)
ListItemType.MANGA_LIST -> outRect.set(0)
ListItemType.DOWNLOAD,
ListItemType.MANGA_LIST_DETAILED -> outRect.set(spacingList)
ListItemType.PAGE_THUMB,
ListItemType.MANGA_GRID -> outRect.set(spacingGrid)
ListItemType.FOOTER_LOADING,
ListItemType.FOOTER_ERROR,
ListItemType.STATE_LOADING,
ListItemType.STATE_ERROR,
ListItemType.STATE_EMPTY,
ListItemType.EXPLORE_BUTTONS,
ListItemType.EXPLORE_SOURCE_GRID,
ListItemType.EXPLORE_SOURCE_LIST,
ListItemType.EXPLORE_SUGGESTION,
null -> outRect.set(0)
ListItemType.TIP -> outRect.set(0) // TODO
ListItemType.HINT_EMPTY -> outRect.set(0) // TODO
ListItemType.FEED -> outRect.set(0) // TODO
}
}
private fun Rect.set(spacing: Int) = set(spacing, spacing, spacing, spacing)
}

View File

@@ -1,6 +1,6 @@
package org.koitharu.kotatsu.list.ui.model
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
@@ -23,8 +23,8 @@ sealed class MangaItemModel : ListModel {
override fun getChangePayload(previousState: ListModel): Any? {
return when {
previousState !is MangaItemModel -> super.getChangePayload(previousState)
progress != previousState.progress -> MangaListAdapter.PAYLOAD_PROGRESS
counter != previousState.counter -> Unit
progress != previousState.progress -> ListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED
counter != previousState.counter -> ListModelDiffCallback.PAYLOAD_ANYTHING_CHANGED
else -> null
}
}

View File

@@ -16,7 +16,6 @@ import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.list.BoundsScrollListener
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetCallback
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
@@ -29,6 +28,8 @@ import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.SheetPagesBinding
import org.koitharu.kotatsu.list.ui.MangaListSpanResolver
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
@@ -74,9 +75,7 @@ class PagesThumbnailsSheet :
clickListener = this@PagesThumbnailsSheet,
)
with(binding.recyclerView) {
addItemDecoration(
SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.grid_spacing)),
)
addItemDecoration(TypedListSpacingDecoration(context))
adapter = thumbnailsAdapter
addOnLayoutChangeListener(spanResolver)
spanResolver?.setGridSize(settings.gridSize / 100f, this)
@@ -171,7 +170,7 @@ class PagesThumbnailsSheet :
override fun getSpanSize(position: Int): Int {
val total = (viewBinding?.recyclerView?.layoutManager as? GridLayoutManager)?.spanCount ?: return 1
return when (thumbnailsAdapter?.getItemViewType(position)) {
PageThumbnailAdapter.ITEM_TYPE_THUMBNAIL -> 1
ListItemType.PAGE_THUMB.ordinal -> 1
else -> total
}
}

View File

@@ -6,6 +6,7 @@ import coil.ImageLoader
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD
import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
import org.koitharu.kotatsu.list.ui.model.ListHeader
@@ -19,9 +20,9 @@ class PageThumbnailAdapter(
) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {
init {
delegatesManager.addDelegate(ITEM_TYPE_THUMBNAIL, pageThumbnailAD(coil, lifecycleOwner, clickListener))
.addDelegate(ITEM_TYPE_HEADER, listHeaderAD(null))
.addDelegate(ITEM_LOADING, loadingFooterAD())
addDelegate(ListItemType.PAGE_THUMB, pageThumbnailAD(coil, lifecycleOwner, clickListener))
addDelegate(ListItemType.HEADER, listHeaderAD(null))
addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())
}
override fun getSectionText(context: Context, position: Int): CharSequence? {
@@ -34,11 +35,4 @@ class PageThumbnailAdapter(
}
return null
}
companion object {
const val ITEM_TYPE_THUMBNAIL = 0
const val ITEM_TYPE_HEADER = 1
const val ITEM_LOADING = 2
}
}

View File

@@ -14,7 +14,6 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.decor.TypedSpacingItemDecoration
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest
@@ -22,11 +21,11 @@ import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.databinding.ActivityScrobblerConfigBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.common.ui.config.adapter.ScrobblingMangaAdapter
import org.koitharu.kotatsu.tracker.ui.feed.adapter.FeedAdapter
import javax.inject.Inject
import com.google.android.material.R as materialR
@@ -55,10 +54,7 @@ class ScrobblerConfigActivity : BaseActivity<ActivityScrobblerConfigBinding>(),
val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
paddingHorizontal = spacing
paddingVertical = resources.getDimensionPixelOffset(R.dimen.grid_spacing_outer)
val decoration = TypedSpacingItemDecoration(
FeedAdapter.ITEM_TYPE_FEED to 0,
fallbackSpacing = spacing,
)
val decoration = TypedListSpacingDecoration(context)
addItemDecoration(decoration)
}
viewBinding.imageViewAvatar.setOnClickListener(this)

View File

@@ -15,13 +15,13 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.list.PaginationScrollListener
import org.koitharu.kotatsu.core.ui.list.decor.TypedSpacingItemDecoration
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.databinding.FragmentFeedBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
@@ -55,12 +55,7 @@ class FeedFragment :
adapter = feedAdapter
setHasFixedSize(true)
addOnScrollListener(PaginationScrollListener(4, this@FeedFragment))
val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
val decoration = TypedSpacingItemDecoration(
FeedAdapter.ITEM_TYPE_FEED to 0,
fallbackSpacing = spacing,
)
addItemDecoration(decoration)
addItemDecoration(TypedListSpacingDecoration(context))
}
binding.swipeRefreshLayout.setOnRefreshListener(this)
addMenuProvider(

View File

@@ -5,6 +5,7 @@ import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
import org.koitharu.kotatsu.list.ui.adapter.errorFooterAD
@@ -22,13 +23,13 @@ class FeedAdapter(
) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {
init {
delegatesManager.addDelegate(ITEM_TYPE_FEED, feedItemAD(coil, lifecycleOwner, listener))
.addDelegate(ITEM_TYPE_LOADING_FOOTER, loadingFooterAD())
.addDelegate(ITEM_TYPE_LOADING_STATE, loadingStateAD())
.addDelegate(ITEM_TYPE_ERROR_FOOTER, errorFooterAD(listener))
.addDelegate(ITEM_TYPE_ERROR_STATE, errorStateListAD(listener))
.addDelegate(ITEM_TYPE_HEADER, listHeaderAD(listener))
.addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listener))
addDelegate(ListItemType.FEED, feedItemAD(coil, lifecycleOwner, listener))
addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD())
addDelegate(ListItemType.STATE_LOADING, loadingStateAD())
addDelegate(ListItemType.FOOTER_ERROR, errorFooterAD(listener))
addDelegate(ListItemType.STATE_ERROR, errorStateListAD(listener))
addDelegate(ListItemType.HEADER, listHeaderAD(listener))
addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listener))
}
override fun getSectionText(context: Context, position: Int): CharSequence? {
@@ -41,15 +42,4 @@ class FeedAdapter(
}
return null
}
companion object {
const val ITEM_TYPE_FEED = 0
const val ITEM_TYPE_LOADING_FOOTER = 1
const val ITEM_TYPE_LOADING_STATE = 2
const val ITEM_TYPE_ERROR_STATE = 3
const val ITEM_TYPE_ERROR_FOOTER = 4
const val ITEM_TYPE_EMPTY = 5
const val ITEM_TYPE_HEADER = 6
}
}