Use SideSheet instead of BottomSheet on landscape

pull/377/head
Koitharu 3 years ago
parent 3d05541f61
commit 0c132a521e
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -18,6 +18,10 @@ import org.koitharu.kotatsu.core.util.ext.findActivity
import org.koitharu.kotatsu.core.util.ext.getDisplaySize import org.koitharu.kotatsu.core.util.ext.getDisplaySize
import com.google.android.material.R as materialR 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() { abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
var viewBinding: B? = null var viewBinding: B? = null

@ -0,0 +1,123 @@
package org.koitharu.kotatsu.core.ui.sheet
import android.app.Dialog
import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.sidesheet.SideSheetBehavior
import com.google.android.material.sidesheet.SideSheetCallback
import com.google.android.material.sidesheet.SideSheetDialog
import java.util.LinkedList
sealed class AdaptiveSheetBehavior {
@JvmField
protected val callbacks = LinkedList<AdaptiveSheetCallback>()
abstract var state: Int
abstract var isDraggable: Boolean
open val isHideable: Boolean = true
fun addCallback(callback: AdaptiveSheetCallback) {
callbacks.add(callback)
}
fun removeCallback(callback: AdaptiveSheetCallback) {
callbacks.remove(callback)
}
class Bottom(
private val delegate: BottomSheetBehavior<*>,
) : AdaptiveSheetBehavior() {
override var state: Int
get() = delegate.state
set(value) {
delegate.state = value
}
override var isDraggable: Boolean
get() = delegate.isDraggable
set(value) {
delegate.isDraggable = value
}
override val isHideable: Boolean
get() = delegate.isHideable
var isFitToContents: Boolean
get() = delegate.isFitToContents
set(value) {
delegate.isFitToContents = value
}
init {
delegate.addBottomSheetCallback(
object : BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
callbacks.forEach { it.onStateChanged(bottomSheet, newState) }
}
override fun onSlide(bottomSheet: View, slideOffset: Float) {
callbacks.forEach { it.onSlide(bottomSheet, slideOffset) }
}
},
)
}
}
class Side(
private val delegate: SideSheetBehavior<*>,
) : AdaptiveSheetBehavior() {
override var state: Int
get() = delegate.state
set(value) {
delegate.state = value
}
override var isDraggable: Boolean
get() = delegate.isDraggable
set(value) {
delegate.isDraggable = value
}
init {
delegate.addCallback(
object : SideSheetCallback() {
override fun onStateChanged(sheet: View, newState: Int) {
callbacks.forEach { it.onStateChanged(sheet, newState) }
}
override fun onSlide(sheet: View, slideOffset: Float) {
callbacks.forEach { it.onSlide(sheet, slideOffset) }
}
},
)
}
}
companion object {
const val STATE_EXPANDED = SideSheetBehavior.STATE_EXPANDED
const val STATE_SETTLING = SideSheetBehavior.STATE_SETTLING
const val STATE_DRAGGING = SideSheetBehavior.STATE_DRAGGING
const val STATE_HIDDEN = SideSheetBehavior.STATE_HIDDEN
fun from(dialog: Dialog?): AdaptiveSheetBehavior? = when (dialog) {
is BottomSheetDialog -> Bottom(dialog.behavior)
is SideSheetDialog -> Side(dialog.behavior)
else -> null
}
fun from(lp: CoordinatorLayout.LayoutParams): AdaptiveSheetBehavior? = when (val behavior = lp.behavior) {
is BottomSheetBehavior<*> -> Bottom(behavior)
is SideSheetBehavior<*> -> Side(behavior)
else -> null
}
}
}

@ -0,0 +1,22 @@
package org.koitharu.kotatsu.core.ui.sheet
import android.view.View
interface AdaptiveSheetCallback {
/**
* Called when the sheet changes its state.
*
* @param sheet The sheet view.
* @param newState The new state.
*/
fun onStateChanged(sheet: View, newState: Int)
/**
* Called when the sheet is being dragged.
*
* @param sheet The sheet view.
* @param slideOffset The new offset of this sheet.
*/
fun onSlide(sheet: View, slideOffset: Float) = Unit
}

@ -0,0 +1,101 @@
package org.koitharu.kotatsu.core.ui.sheet
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.WindowInsets
import android.widget.LinearLayout
import androidx.annotation.AttrRes
import androidx.annotation.StringRes
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.withStyledAttributes
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.parents
import org.koitharu.kotatsu.databinding.LayoutSheetHeaderAdaptiveBinding
class AdaptiveSheetHeaderBar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@AttrRes defStyleAttr: Int = 0,
) : LinearLayout(context, attrs, defStyleAttr), AdaptiveSheetCallback {
private val binding = LayoutSheetHeaderAdaptiveBinding.inflate(LayoutInflater.from(context), this)
private var sheetBehavior: AdaptiveSheetBehavior? = null
var title: CharSequence?
get() = binding.textViewTitle.text
set(value) {
binding.textViewTitle.text = value
}
val isExpanded: Boolean
get() = binding.dragHandle.isGone
init {
orientation = VERTICAL
binding.buttonClose.setOnClickListener { dismissSheet() }
context.withStyledAttributes(
attrs,
R.styleable.AdaptiveSheetHeaderBar, defStyleAttr,
) {
title = getText(R.styleable.AdaptiveSheetHeaderBar_title)
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
dispatchInsets(ViewCompat.getRootWindowInsets(this))
setBottomSheetBehavior(findParentSheetBehavior())
}
override fun onDetachedFromWindow() {
setBottomSheetBehavior(null)
super.onDetachedFromWindow()
}
override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets {
dispatchInsets(if (insets != null) WindowInsetsCompat.toWindowInsetsCompat(insets) else null)
return super.onApplyWindowInsets(insets)
}
override fun onStateChanged(sheet: View, newState: Int) {
}
fun setTitle(@StringRes resId: Int) {
binding.textViewTitle.setText(resId)
}
private fun dispatchInsets(insets: WindowInsetsCompat?) {
}
private fun setBottomSheetBehavior(behavior: AdaptiveSheetBehavior?) {
binding.dragHandle.isVisible = behavior is AdaptiveSheetBehavior.Bottom
binding.layoutSidesheet.isVisible = behavior is AdaptiveSheetBehavior.Side
sheetBehavior?.removeCallback(this)
sheetBehavior = behavior
behavior?.addCallback(this)
}
private fun dismissSheet() {
sheetBehavior?.state = AdaptiveSheetBehavior.STATE_HIDDEN
}
private fun findParentSheetBehavior(): AdaptiveSheetBehavior? {
for (p in parents) {
val layoutParams = (p as? View)?.layoutParams
if (layoutParams is CoordinatorLayout.LayoutParams) {
AdaptiveSheetBehavior.from(layoutParams)?.let {
return it
}
}
}
return null
}
}

@ -0,0 +1,161 @@
package org.koitharu.kotatsu.core.ui.sheet
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.ComponentDialog
import androidx.activity.OnBackPressedDispatcher
import androidx.appcompat.app.AppCompatDialogFragment
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.sidesheet.SideSheetDialog
import org.koitharu.kotatsu.R
import com.google.android.material.R as materialR
abstract class BaseAdaptiveSheet<B : ViewBinding> : AppCompatDialogFragment() {
private var waitingForDismissAllowingStateLoss = false
var viewBinding: B? = null
private set
@Deprecated("", ReplaceWith("requireViewBinding()"))
protected val binding: B
get() = requireViewBinding()
protected val behavior: AdaptiveSheetBehavior?
get() = AdaptiveSheetBehavior.from(dialog)
val isExpanded: Boolean
get() = behavior?.state == AdaptiveSheetBehavior.STATE_EXPANDED
val onBackPressedDispatcher: OnBackPressedDispatcher
get() = (requireDialog() as ComponentDialog).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()
onViewBindingCreated(binding, savedInstanceState)
}
override fun onDestroyView() {
viewBinding = null
super.onDestroyView()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = requireContext()
return if (context.resources.getBoolean(R.bool.is_tablet)) {
SideSheetDialog(context, theme)
} else {
BottomSheetDialog(context, theme)
}
}
fun addSheetCallback(callback: AdaptiveSheetCallback) {
val b = behavior ?: return
b.addCallback(callback)
val rootView = dialog?.findViewById<View>(materialR.id.design_bottom_sheet)
?: dialog?.findViewById(materialR.id.coordinator)
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
}
if (b is AdaptiveSheetBehavior.Bottom) {
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()."
}
override fun dismiss() {
if (!tryDismissWithAnimation(false)) {
super.dismiss()
}
}
override fun dismissAllowingStateLoss() {
if (!tryDismissWithAnimation(true)) {
super.dismissAllowingStateLoss()
}
}
/**
* Tries to dismiss the dialog fragment with the bottom sheet animation. Returns true if possible,
* false otherwise.
*/
private fun tryDismissWithAnimation(allowingStateLoss: Boolean): Boolean {
val shouldDismissWithAnimation = when (val dialog = dialog) {
is BottomSheetDialog -> dialog.dismissWithAnimation
is SideSheetDialog -> dialog.isDismissWithSheetAnimationEnabled
else -> false
}
val behavior = behavior ?: return false
return if (shouldDismissWithAnimation && behavior.isHideable) {
dismissWithAnimation(behavior, allowingStateLoss)
true
} else {
false
}
}
private fun dismissWithAnimation(behavior: AdaptiveSheetBehavior, allowingStateLoss: Boolean) {
waitingForDismissAllowingStateLoss = allowingStateLoss
if (behavior.state == AdaptiveSheetBehavior.STATE_HIDDEN) {
dismissAfterAnimation()
} else {
behavior.addCallback(SheetDismissCallback())
behavior.state = AdaptiveSheetBehavior.STATE_HIDDEN
}
}
private fun dismissAfterAnimation() {
if (waitingForDismissAllowingStateLoss) {
super.dismissAllowingStateLoss()
} else {
super.dismiss()
}
}
private inner class SheetDismissCallback : AdaptiveSheetCallback {
override fun onStateChanged(sheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
dismissAfterAnimation()
}
}
override fun onSlide(sheet: View, slideOffset: Float) {}
}
}

@ -30,6 +30,7 @@ import com.google.android.material.R as materialR
private const val THROTTLE_DELAY = 200L private const val THROTTLE_DELAY = 200L
@Deprecated("")
class BottomSheetHeaderBar @JvmOverloads constructor( class BottomSheetHeaderBar @JvmOverloads constructor(
context: Context, context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,

@ -18,11 +18,11 @@ import org.koitharu.kotatsu.browser.BrowserActivity
import org.koitharu.kotatsu.core.os.ShortcutsUpdater import org.koitharu.kotatsu.core.os.ShortcutsUpdater
import org.koitharu.kotatsu.core.util.ShareHelper import org.koitharu.kotatsu.core.util.ShareHelper
import org.koitharu.kotatsu.details.ui.model.MangaBranch import org.koitharu.kotatsu.details.ui.model.MangaBranch
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesSheet
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorBottomSheet import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity
class DetailsMenuProvider( class DetailsMenuProvider(
@ -63,7 +63,7 @@ class DetailsMenuProvider(
R.id.action_favourite -> { R.id.action_favourite -> {
viewModel.manga.value?.let { viewModel.manga.value?.let {
FavouriteCategoriesBottomSheet.show(activity.supportFragmentManager, it) FavouriteCategoriesSheet.show(activity.supportFragmentManager, it)
} }
} }
@ -105,7 +105,7 @@ class DetailsMenuProvider(
R.id.action_scrobbling -> { R.id.action_scrobbling -> {
viewModel.manga.value?.let { viewModel.manga.value?.let {
ScrobblingSelectorBottomSheet.show(activity.supportFragmentManager, it, null) ScrobblingSelectorSheet.show(activity.supportFragmentManager, it, null)
} }
} }

@ -19,7 +19,7 @@ fun scrobblingInfoAD(
{ layoutInflater, parent -> ItemScrobblingInfoBinding.inflate(layoutInflater, parent, false) }, { layoutInflater, parent -> ItemScrobblingInfoBinding.inflate(layoutInflater, parent, false) },
) { ) {
binding.root.setOnClickListener { binding.root.setOnClickListener {
ScrobblingInfoBottomSheet.show(fragmentManager, bindingAdapterPosition) ScrobblingInfoSheet.show(fragmentManager, bindingAdapterPosition)
} }
bind { bind {

@ -17,7 +17,7 @@ import androidx.fragment.app.activityViewModels
import coil.ImageLoader import coil.ImageLoader
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseBottomSheet import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.newImageRequest
@ -30,12 +30,12 @@ import org.koitharu.kotatsu.details.ui.DetailsViewModel
import org.koitharu.kotatsu.image.ui.ImageActivity import org.koitharu.kotatsu.image.ui.ImageActivity
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorBottomSheet import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class ScrobblingInfoBottomSheet : class ScrobblingInfoSheet :
BaseBottomSheet<SheetScrobblingBinding>(), BaseAdaptiveSheet<SheetScrobblingBinding>(),
AdapterView.OnItemSelectedListener, AdapterView.OnItemSelectedListener,
RatingBar.OnRatingBarChangeListener, RatingBar.OnRatingBarChangeListener,
View.OnClickListener, View.OnClickListener,
@ -74,7 +74,7 @@ class ScrobblingInfoBottomSheet :
menu = PopupMenu(binding.root.context, binding.buttonMenu).apply { menu = PopupMenu(binding.root.context, binding.buttonMenu).apply {
inflate(R.menu.opt_scrobbling) inflate(R.menu.opt_scrobbling)
setOnMenuItemClickListener(this@ScrobblingInfoBottomSheet) setOnMenuItemClickListener(this@ScrobblingInfoSheet)
} }
} }
@ -152,7 +152,7 @@ class ScrobblingInfoBottomSheet :
R.id.action_edit -> { R.id.action_edit -> {
val manga = viewModel.manga.value ?: return false val manga = viewModel.manga.value ?: return false
val scrobblerService = viewModel.scrobblingInfo.value.getOrNull(scrobblerIndex)?.scrobbler val scrobblerService = viewModel.scrobblingInfo.value.getOrNull(scrobblerIndex)?.scrobbler
ScrobblingSelectorBottomSheet.show(parentFragmentManager, manga, scrobblerService) ScrobblingSelectorSheet.show(parentFragmentManager, manga, scrobblerService)
dismiss() dismiss()
} }
} }
@ -164,7 +164,7 @@ class ScrobblingInfoBottomSheet :
private const val TAG = "ScrobblingInfoBottomSheet" private const val TAG = "ScrobblingInfoBottomSheet"
private const val ARG_INDEX = "index" private const val ARG_INDEX = "index"
fun show(fm: FragmentManager, index: Int) = ScrobblingInfoBottomSheet().withArgs(1) { fun show(fm: FragmentManager, index: Int) = ScrobblingInfoSheet().withArgs(1) {
putInt(ARG_INDEX, index) putInt(ARG_INDEX, index)
}.show(fm, TAG) }.show(fm, TAG)
} }

@ -2,34 +2,29 @@ package org.koitharu.kotatsu.favourites.ui.categories.select
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.ui.BaseBottomSheet
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.withArgs import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.SheetFavoriteCategoriesBinding import org.koitharu.kotatsu.databinding.SheetFavoriteCategoriesBinding
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
import org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter import org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
@AndroidEntryPoint @AndroidEntryPoint
class FavouriteCategoriesBottomSheet : class FavouriteCategoriesSheet :
BaseBottomSheet<SheetFavoriteCategoriesBinding>(), BaseAdaptiveSheet<SheetFavoriteCategoriesBinding>(),
OnListItemClickListener<MangaCategoryItem>, OnListItemClickListener<MangaCategoryItem> {
View.OnClickListener,
Toolbar.OnMenuItemClickListener {
private val viewModel: MangaCategoriesViewModel by viewModels() private val viewModel: MangaCategoriesViewModel by viewModels()
@ -40,13 +35,13 @@ class FavouriteCategoriesBottomSheet :
container: ViewGroup?, container: ViewGroup?,
) = SheetFavoriteCategoriesBinding.inflate(inflater, container, false) ) = SheetFavoriteCategoriesBinding.inflate(inflater, container, false)
override fun onViewBindingCreated(binding: SheetFavoriteCategoriesBinding, savedInstanceState: Bundle?) { override fun onViewBindingCreated(
binding: SheetFavoriteCategoriesBinding,
savedInstanceState: Bundle?,
) {
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
adapter = MangaCategoriesAdapter(this) adapter = MangaCategoriesAdapter(this)
binding.recyclerViewCategories.adapter = adapter binding.recyclerViewCategories.adapter = adapter
binding.buttonDone.setOnClickListener(this)
binding.headerBar.toolbar.setOnMenuItemClickListener(this)
viewModel.content.observe(viewLifecycleOwner, this::onContentChanged) viewModel.content.observe(viewLifecycleOwner, this::onContentChanged)
viewModel.onError.observeEvent(viewLifecycleOwner, ::onError) viewModel.onError.observeEvent(viewLifecycleOwner, ::onError)
} }
@ -56,25 +51,11 @@ class FavouriteCategoriesBottomSheet :
super.onDestroyView() super.onDestroyView()
} }
override fun onClick(v: View) {
when (v.id) {
R.id.button_done -> dismiss()
}
}
override fun onMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_create -> startActivity(FavouritesCategoryEditActivity.newIntent(requireContext()))
else -> return false
}
return true
}
override fun onItemClick(item: MangaCategoryItem, view: View) { override fun onItemClick(item: MangaCategoryItem, view: View) {
viewModel.setChecked(item.id, !item.isChecked) viewModel.setChecked(item.id, !item.isChecked)
} }
private fun onContentChanged(categories: List<MangaCategoryItem>) { private fun onContentChanged(categories: List<ListModel>) {
adapter?.items = categories adapter?.items = categories
} }
@ -89,11 +70,17 @@ class FavouriteCategoriesBottomSheet :
fun show(fm: FragmentManager, manga: Manga) = Companion.show(fm, listOf(manga)) fun show(fm: FragmentManager, manga: Manga) = Companion.show(fm, listOf(manga))
fun show(fm: FragmentManager, manga: Collection<Manga>) = FavouriteCategoriesBottomSheet().withArgs(1) { fun show(fm: FragmentManager, manga: Collection<Manga>) =
putParcelableArrayList( FavouriteCategoriesSheet().withArgs(1) {
KEY_MANGA_LIST, putParcelableArrayList(
manga.mapTo(ArrayList(manga.size)) { ParcelableManga(it, withChapters = false) }, KEY_MANGA_LIST,
) manga.mapTo(ArrayList(manga.size)) {
}.show(fm, TAG) ParcelableManga(
it,
withChapters = false,
)
},
)
}.show(fm, TAG)
} }
} }

@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
@ -12,8 +13,10 @@ import org.koitharu.kotatsu.core.model.ids
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet.Companion.KEY_MANGA_LIST import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesSheet.Companion.KEY_MANGA_LIST
import org.koitharu.kotatsu.favourites.ui.categories.select.model.CategoriesHeaderItem
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
import org.koitharu.kotatsu.list.ui.model.ListModel
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -23,17 +26,21 @@ class MangaCategoriesViewModel @Inject constructor(
) : BaseViewModel() { ) : BaseViewModel() {
private val manga = requireNotNull(savedStateHandle.get<List<ParcelableManga>>(KEY_MANGA_LIST)).map { it.manga } private val manga = requireNotNull(savedStateHandle.get<List<ParcelableManga>>(KEY_MANGA_LIST)).map { it.manga }
private val header = CategoriesHeaderItem()
val content = combine( val content: StateFlow<List<ListModel>> = combine(
favouritesRepository.observeCategories(), favouritesRepository.observeCategories(),
observeCategoriesIds(), observeCategoriesIds(),
) { all, checked -> ) { all, checked ->
all.map { buildList(all.size + 1) {
MangaCategoryItem( add(header)
id = it.id, all.mapTo(this) {
name = it.title, MangaCategoryItem(
isChecked = it.id in checked, id = it.id,
) name = it.title,
isChecked = it.id in checked,
)
}
} }
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList()) }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())

@ -0,0 +1,27 @@
package org.koitharu.kotatsu.favourites.ui.categories.select.adapter
import android.view.View
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.databinding.ItemCategoriesHeaderBinding
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
import org.koitharu.kotatsu.favourites.ui.categories.select.model.CategoriesHeaderItem
import org.koitharu.kotatsu.list.ui.model.ListModel
fun categoriesHeaderAD() = adapterDelegateViewBinding<CategoriesHeaderItem, ListModel, ItemCategoriesHeaderBinding>(
{ inflater, parent -> ItemCategoriesHeaderBinding.inflate(inflater, parent, false) },
) {
val onClickListener = View.OnClickListener { v ->
val intent = when (v.id) {
R.id.button_create -> FavouritesCategoryEditActivity.newIntent(v.context)
R.id.button_manage -> FavouriteCategoriesActivity.newIntent(v.context)
else -> return@OnClickListener
}
v.context.startActivity(intent)
}
binding.buttonCreate.setOnClickListener(onClickListener)
binding.buttonManage.setOnClickListener(onClickListener)
}

@ -3,32 +3,39 @@ package org.koitharu.kotatsu.favourites.ui.categories.select.adapter
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.favourites.ui.categories.select.model.CategoriesHeaderItem
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
import org.koitharu.kotatsu.list.ui.model.ListModel
class MangaCategoriesAdapter( class MangaCategoriesAdapter(
clickListener: OnListItemClickListener<MangaCategoryItem> clickListener: OnListItemClickListener<MangaCategoryItem>,
) : AsyncListDifferDelegationAdapter<MangaCategoryItem>(DiffCallback()) { ) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) {
init { init {
delegatesManager.addDelegate(mangaCategoryAD(clickListener)) delegatesManager.addDelegate(mangaCategoryAD(clickListener))
.addDelegate(categoriesHeaderAD())
} }
private class DiffCallback : DiffUtil.ItemCallback<MangaCategoryItem>() { private class DiffCallback : DiffUtil.ItemCallback<ListModel>() {
override fun areItemsTheSame( override fun areItemsTheSame(
oldItem: MangaCategoryItem, oldItem: ListModel,
newItem: MangaCategoryItem newItem: ListModel,
): Boolean = oldItem.id == newItem.id ): Boolean = when {
oldItem is MangaCategoryItem && newItem is MangaCategoryItem -> oldItem.id == newItem.id
oldItem is CategoriesHeaderItem && newItem is CategoriesHeaderItem -> oldItem == newItem
else -> false
}
override fun areContentsTheSame( override fun areContentsTheSame(
oldItem: MangaCategoryItem, oldItem: ListModel,
newItem: MangaCategoryItem newItem: ListModel,
): Boolean = oldItem == newItem ): Boolean = oldItem == newItem
override fun getChangePayload( override fun getChangePayload(
oldItem: MangaCategoryItem, oldItem: ListModel,
newItem: MangaCategoryItem newItem: ListModel,
): Any? { ): Any? {
if (oldItem.isChecked != newItem.isChecked) { if (oldItem is MangaCategoryItem && newItem is MangaCategoryItem && oldItem.isChecked != newItem.isChecked) {
return newItem.isChecked return newItem.isChecked
} }
return super.getChangePayload(oldItem, newItem) return super.getChangePayload(oldItem, newItem)

@ -4,10 +4,11 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.databinding.ItemCheckableNewBinding import org.koitharu.kotatsu.databinding.ItemCheckableNewBinding
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
import org.koitharu.kotatsu.list.ui.model.ListModel
fun mangaCategoryAD( fun mangaCategoryAD(
clickListener: OnListItemClickListener<MangaCategoryItem> clickListener: OnListItemClickListener<MangaCategoryItem>,
) = adapterDelegateViewBinding<MangaCategoryItem, MangaCategoryItem, ItemCheckableNewBinding>( ) = adapterDelegateViewBinding<MangaCategoryItem, ListModel, ItemCheckableNewBinding>(
{ inflater, parent -> ItemCheckableNewBinding.inflate(inflater, parent, false) }, { inflater, parent -> ItemCheckableNewBinding.inflate(inflater, parent, false) },
) { ) {

@ -0,0 +1,8 @@
package org.koitharu.kotatsu.favourites.ui.categories.select.model
import org.koitharu.kotatsu.list.ui.model.ListModel
class CategoriesHeaderItem : ListModel {
override fun equals(other: Any?): Boolean = other?.javaClass == CategoriesHeaderItem::class.java
}

@ -1,7 +1,9 @@
package org.koitharu.kotatsu.favourites.ui.categories.select.model package org.koitharu.kotatsu.favourites.ui.categories.select.model
import org.koitharu.kotatsu.list.ui.model.ListModel
data class MangaCategoryItem( data class MangaCategoryItem(
val id: Long, val id: Long,
val name: String, val name: String,
val isChecked: Boolean val isChecked: Boolean,
) ) : ListModel

@ -11,7 +11,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.ui.BaseBottomSheet import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.ext.setValueRounded import org.koitharu.kotatsu.core.util.ext.setValueRounded
import org.koitharu.kotatsu.core.util.progress.IntPercentLabelFormatter import org.koitharu.kotatsu.core.util.progress.IntPercentLabelFormatter
import org.koitharu.kotatsu.databinding.DialogListModeBinding import org.koitharu.kotatsu.databinding.DialogListModeBinding
@ -19,7 +19,7 @@ import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class ListModeBottomSheet : class ListModeBottomSheet :
BaseBottomSheet<DialogListModeBinding>(), BaseAdaptiveSheet<DialogListModeBinding>(),
Slider.OnChangeListener, Slider.OnChangeListener,
MaterialButtonToggleGroup.OnButtonCheckedListener { MaterialButtonToggleGroup.OnButtonCheckedListener {

@ -45,7 +45,7 @@ import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
import org.koitharu.kotatsu.databinding.FragmentListBinding import org.koitharu.kotatsu.databinding.FragmentListBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesSheet
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter 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.MangaListAdapter.Companion.ITEM_TYPE_MANGA_GRID
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
@ -296,7 +296,7 @@ abstract class MangaListFragment :
} }
R.id.action_favourite -> { R.id.action_favourite -> {
FavouriteCategoriesBottomSheet.show(childFragmentManager, selectedItems) FavouriteCategoriesSheet.show(childFragmentManager, selectedItems)
mode.finish() mode.finish()
true true
} }

@ -9,8 +9,8 @@ import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaChapters import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaChapters
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseBottomSheet
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
import org.koitharu.kotatsu.core.util.ext.getParcelableCompat import org.koitharu.kotatsu.core.util.ext.getParcelableCompat
import org.koitharu.kotatsu.core.util.ext.withArgs import org.koitharu.kotatsu.core.util.ext.withArgs
@ -23,7 +23,7 @@ import javax.inject.Inject
import kotlin.math.roundToInt import kotlin.math.roundToInt
@AndroidEntryPoint @AndroidEntryPoint
class ChaptersBottomSheet : BaseBottomSheet<SheetChaptersBinding>(), OnListItemClickListener<ChapterListItem> { class ChaptersSheet : BaseAdaptiveSheet<SheetChaptersBinding>(), OnListItemClickListener<ChapterListItem> {
@Inject @Inject
lateinit var settings: AppSettings lateinit var settings: AppSettings
@ -83,7 +83,7 @@ class ChaptersBottomSheet : BaseBottomSheet<SheetChaptersBinding>(), OnListItemC
fm: FragmentManager, fm: FragmentManager,
chapters: List<MangaChapter>, chapters: List<MangaChapter>,
currentId: Long, currentId: Long,
) = ChaptersBottomSheet().withArgs(2) { ) = ChaptersSheet().withArgs(2) {
putParcelable(ARG_CHAPTERS, ParcelableMangaChapters(chapters)) putParcelable(ARG_CHAPTERS, ParcelableMangaChapters(chapters))
putLong(ARG_CURRENT_ID, currentId) putLong(ARG_CURRENT_ID, currentId)
}.show(fm, TAG) }.show(fm, TAG)

@ -52,7 +52,7 @@ import org.koitharu.kotatsu.core.util.ext.zipWithPrevious
import org.koitharu.kotatsu.databinding.ActivityReaderBinding import org.koitharu.kotatsu.databinding.ActivityReaderBinding
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.reader.ui.config.ReaderConfigBottomSheet import org.koitharu.kotatsu.reader.ui.config.ReaderConfigSheet
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
import org.koitharu.kotatsu.reader.ui.thumbnails.OnPageSelectListener import org.koitharu.kotatsu.reader.ui.thumbnails.OnPageSelectListener
@ -64,10 +64,10 @@ import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class ReaderActivity : class ReaderActivity :
BaseFullscreenActivity<ActivityReaderBinding>(), BaseFullscreenActivity<ActivityReaderBinding>(),
ChaptersBottomSheet.OnChapterChangeListener, ChaptersSheet.OnChapterChangeListener,
GridTouchHelper.OnGridTouchListener, GridTouchHelper.OnGridTouchListener,
OnPageSelectListener, OnPageSelectListener,
ReaderConfigBottomSheet.Callback, ReaderConfigSheet.Callback,
ReaderControlDelegate.OnInteractionListener, ReaderControlDelegate.OnInteractionListener,
OnApplyWindowInsetsListener, OnApplyWindowInsetsListener,
IdlingDetector.Callback { IdlingDetector.Callback {
@ -179,7 +179,7 @@ class ReaderActivity :
} }
R.id.action_chapters -> { R.id.action_chapters -> {
ChaptersBottomSheet.show( ChaptersSheet.show(
supportFragmentManager, supportFragmentManager,
viewModel.manga?.chapters.orEmpty(), viewModel.manga?.chapters.orEmpty(),
viewModel.getCurrentState()?.chapterId ?: 0L, viewModel.getCurrentState()?.chapterId ?: 0L,
@ -207,7 +207,7 @@ class ReaderActivity :
R.id.action_options -> { R.id.action_options -> {
viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState()) viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState())
val currentMode = readerManager.currentMode ?: return false val currentMode = readerManager.currentMode ?: return false
ReaderConfigBottomSheet.show(supportFragmentManager, currentMode) ReaderConfigSheet.show(supportFragmentManager, currentMode)
} }
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)

@ -23,7 +23,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
import org.koitharu.kotatsu.core.ui.BaseBottomSheet import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.ScreenOrientationHelper import org.koitharu.kotatsu.core.util.ScreenOrientationHelper
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
@ -36,8 +36,8 @@ import org.koitharu.kotatsu.settings.SettingsActivity
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class ReaderConfigBottomSheet : class ReaderConfigSheet :
BaseBottomSheet<SheetReaderConfigBinding>(), BaseAdaptiveSheet<SheetReaderConfigBinding>(),
ActivityResultCallback<Uri?>, ActivityResultCallback<Uri?>,
View.OnClickListener, View.OnClickListener,
MaterialButtonToggleGroup.OnButtonCheckedListener, MaterialButtonToggleGroup.OnButtonCheckedListener,
@ -59,11 +59,17 @@ class ReaderConfigBottomSheet :
?: ReaderMode.STANDARD ?: ReaderMode.STANDARD
} }
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetReaderConfigBinding { override fun onCreateViewBinding(
inflater: LayoutInflater,
container: ViewGroup?,
): SheetReaderConfigBinding {
return SheetReaderConfigBinding.inflate(inflater, container, false) return SheetReaderConfigBinding.inflate(inflater, container, false)
} }
override fun onViewBindingCreated(binding: SheetReaderConfigBinding, savedInstanceState: Bundle?) { override fun onViewBindingCreated(
binding: SheetReaderConfigBinding,
savedInstanceState: Bundle?,
) {
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
observeScreenOrientation() observeScreenOrientation()
binding.buttonStandard.isChecked = mode == ReaderMode.STANDARD binding.buttonStandard.isChecked = mode == ReaderMode.STANDARD
@ -127,7 +133,11 @@ class ReaderConfigBottomSheet :
} }
} }
override fun onButtonChecked(group: MaterialButtonToggleGroup?, checkedId: Int, isChecked: Boolean) { override fun onButtonChecked(
group: MaterialButtonToggleGroup?,
checkedId: Int,
isChecked: Boolean,
) {
if (!isChecked) { if (!isChecked) {
return return
} }
@ -180,7 +190,7 @@ class ReaderConfigBottomSheet :
private const val TAG = "ReaderConfigBottomSheet" private const val TAG = "ReaderConfigBottomSheet"
private const val ARG_MODE = "mode" private const val ARG_MODE = "mode"
fun show(fm: FragmentManager, mode: ReaderMode) = ReaderConfigBottomSheet().withArgs(1) { fun show(fm: FragmentManager, mode: ReaderMode) = ReaderConfigSheet().withArgs(1) {
putInt(ARG_MODE, mode.id) putInt(ARG_MODE, mode.id)
}.show(fm, TAG) }.show(fm, TAG)
} }

@ -14,12 +14,13 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseBottomSheet
import org.koitharu.kotatsu.core.ui.list.BoundsScrollListener import org.koitharu.kotatsu.core.ui.list.BoundsScrollListener
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.ScrollListenerInvalidationObserver import org.koitharu.kotatsu.core.ui.list.ScrollListenerInvalidationObserver
import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.core.ui.widgets.BottomSheetHeaderBar import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior
import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetCallback
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
@ -31,14 +32,13 @@ import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.reader.ui.ReaderState import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter
import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.TargetScrollObserver import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.TargetScrollObserver
import org.koitharu.kotatsu.util.LoggingAdapterDataObserver
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class PagesThumbnailsSheet : class PagesThumbnailsSheet :
BaseBottomSheet<SheetPagesBinding>(), BaseAdaptiveSheet<SheetPagesBinding>(),
OnListItemClickListener<PageThumbnail>, AdaptiveSheetCallback,
BottomSheetHeaderBar.OnExpansionChangeListener { OnListItemClickListener<PageThumbnail> {
private val viewModel by viewModels<PagesThumbnailsViewModel>() private val viewModel by viewModels<PagesThumbnailsViewModel>()
@ -64,11 +64,6 @@ class PagesThumbnailsSheet :
override fun onViewBindingCreated(binding: SheetPagesBinding, savedInstanceState: Bundle?) { override fun onViewBindingCreated(binding: SheetPagesBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
spanResolver = MangaListSpanResolver(binding.root.resources) spanResolver = MangaListSpanResolver(binding.root.resources)
with(binding.headerBar) {
title = viewModel.title
subtitle = null
addOnExpansionChangeListener(this@PagesThumbnailsSheet)
}
thumbnailsAdapter = PageThumbnailAdapter( thumbnailsAdapter = PageThumbnailAdapter(
coil = coil, coil = coil,
lifecycleOwner = viewLifecycleOwner, lifecycleOwner = viewLifecycleOwner,
@ -87,14 +82,11 @@ class PagesThumbnailsSheet :
ScrollListenerInvalidationObserver(this, checkNotNull(scrollListener)), ScrollListenerInvalidationObserver(this, checkNotNull(scrollListener)),
) )
thumbnailsAdapter?.registerAdapterDataObserver(TargetScrollObserver(this)) thumbnailsAdapter?.registerAdapterDataObserver(TargetScrollObserver(this))
thumbnailsAdapter?.registerAdapterDataObserver(LoggingAdapterDataObserver("THUMB"))
} }
viewModel.thumbnails.observe(viewLifecycleOwner) { viewModel.thumbnails.observe(viewLifecycleOwner) {
thumbnailsAdapter?.setItems(it, listCommitCallback) thumbnailsAdapter?.setItems(it, listCommitCallback)
} }
viewModel.branch.observe(viewLifecycleOwner) { viewModel.branch.observe(viewLifecycleOwner, ::updateTitle)
onExpansionStateChanged(binding.headerBar, binding.headerBar.isExpanded)
}
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this)) viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
} }
@ -118,13 +110,17 @@ class PagesThumbnailsSheet :
dismiss() dismiss()
} }
override fun onExpansionStateChanged(headerBar: BottomSheetHeaderBar, isExpanded: Boolean) { override fun onStateChanged(sheet: View, newState: Int) {
if (isExpanded) { viewBinding?.recyclerView?.isFastScrollerEnabled = newState == AdaptiveSheetBehavior.STATE_EXPANDED
headerBar.subtitle = viewModel.branch.value }
private fun updateTitle(branch: String?) {
val mangaName = viewModel.manga.title
viewBinding?.headerBar?.title = if (branch != null) {
getString(R.string.manga_branch_title_template, mangaName, branch)
} else { } else {
headerBar.subtitle = null mangaName
} }
viewBinding?.recyclerView?.isFastScrollerEnabled = isExpanded
} }
private inner class ScrollListener : BoundsScrollListener(3, 3) { private inner class ScrollListener : BoundsScrollListener(3, 3) {

@ -42,7 +42,6 @@ class PagesThumbnailsViewModel @Inject constructor(
val thumbnails = MutableStateFlow<List<ListModel>>(emptyList()) val thumbnails = MutableStateFlow<List<ListModel>>(emptyList())
val branch = MutableStateFlow<String?>(null) val branch = MutableStateFlow<String?>(null)
val title = manga.title
init { init {
loadingJob = launchJob(Dispatchers.Default) { loadingJob = launchJob(Dispatchers.Default) {

@ -16,9 +16,9 @@ import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.parser.MangaIntent import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.ui.BaseBottomSheet
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.PaginationScrollListener import org.koitharu.kotatsu.core.ui.list.PaginationScrollListener
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.ui.util.CollapseActionViewCallback import org.koitharu.kotatsu.core.ui.util.CollapseActionViewCallback
import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
@ -35,8 +35,8 @@ import org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter.ScrobblerSelec
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class ScrobblingSelectorBottomSheet : class ScrobblingSelectorSheet :
BaseBottomSheet<SheetScrobblingSelectorBinding>(), BaseAdaptiveSheet<SheetScrobblingSelectorBinding>(),
OnListItemClickListener<ScrobblerManga>, OnListItemClickListener<ScrobblerManga>,
PaginationScrollListener.Callback, PaginationScrollListener.Callback,
View.OnClickListener, View.OnClickListener,
@ -63,7 +63,7 @@ class ScrobblingSelectorBottomSheet :
with(binding.recyclerView) { with(binding.recyclerView) {
adapter = listAdapter adapter = listAdapter
addItemDecoration(decoration) addItemDecoration(decoration)
addOnScrollListener(PaginationScrollListener(4, this@ScrobblingSelectorBottomSheet)) addOnScrollListener(PaginationScrollListener(4, this@ScrobblingSelectorSheet))
} }
binding.buttonDone.setOnClickListener(this) binding.buttonDone.setOnClickListener(this)
initOptionsMenu() initOptionsMenu()
@ -209,7 +209,7 @@ class ScrobblingSelectorBottomSheet :
private const val ARG_SCROBBLER = "scrobbler" private const val ARG_SCROBBLER = "scrobbler"
fun show(fm: FragmentManager, manga: Manga, scrobblerService: ScrobblerService?) = fun show(fm: FragmentManager, manga: Manga, scrobblerService: ScrobblerService?) =
ScrobblingSelectorBottomSheet().withArgs(2) { ScrobblingSelectorSheet().withArgs(2) {
putParcelable(MangaIntent.KEY_MANGA, ParcelableManga(manga, withChapters = false)) putParcelable(MangaIntent.KEY_MANGA, ParcelableManga(manga, withChapters = false))
if (scrobblerService != null) { if (scrobblerService != null) {
putInt(ARG_SCROBBLER, scrobblerService.id) putInt(ARG_SCROBBLER, scrobblerService.id)

@ -27,7 +27,7 @@ import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.databinding.ActivitySearchMultiBinding import org.koitharu.kotatsu.databinding.ActivitySearchMultiBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesSheet
import org.koitharu.kotatsu.list.ui.ItemSizeResolver import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
@ -159,7 +159,7 @@ class MultiSearchActivity :
} }
R.id.action_favourite -> { R.id.action_favourite -> {
FavouriteCategoriesBottomSheet.show(supportFragmentManager, collectSelectedItems()) FavouriteCategoriesSheet.show(supportFragmentManager, collectSelectedItems())
mode.finish() mode.finish()
true true
} }

@ -4,22 +4,20 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.ui.BaseBottomSheet
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.databinding.SheetBaseBinding import org.koitharu.kotatsu.databinding.SheetBaseBinding
@AndroidEntryPoint @AndroidEntryPoint
class TrackerCategoriesConfigSheet : class TrackerCategoriesConfigSheet :
BaseBottomSheet<SheetBaseBinding>(), BaseAdaptiveSheet<SheetBaseBinding>(),
OnListItemClickListener<FavouriteCategory>, OnListItemClickListener<FavouriteCategory> {
View.OnClickListener {
private val viewModel by viewModels<TrackerCategoriesConfigViewModel>() private val viewModel by viewModels<TrackerCategoriesConfigViewModel>()
@ -30,8 +28,6 @@ class TrackerCategoriesConfigSheet :
override fun onViewBindingCreated(binding: SheetBaseBinding, savedInstanceState: Bundle?) { override fun onViewBindingCreated(binding: SheetBaseBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
binding.headerBar.setTitle(R.string.favourites_categories) binding.headerBar.setTitle(R.string.favourites_categories)
binding.buttonDone.isVisible = true
binding.buttonDone.setOnClickListener(this)
val adapter = TrackerCategoriesConfigAdapter(this) val adapter = TrackerCategoriesConfigAdapter(this)
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
@ -42,10 +38,6 @@ class TrackerCategoriesConfigSheet :
viewModel.toggleItem(item) viewModel.toggleItem(item)
} }
override fun onClick(v: View?) {
dismiss()
}
companion object { companion object {
private const val TAG = "TrackerCategoriesConfigSheet" private const val TAG = "TrackerCategoriesConfigSheet"

@ -12,7 +12,7 @@ import org.koitharu.kotatsu.core.ui.list.SectionedSelectionController
import org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration import org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration
import org.koitharu.kotatsu.core.util.ShareHelper import org.koitharu.kotatsu.core.util.ShareHelper
import org.koitharu.kotatsu.core.util.ext.invalidateNestedItemDecorations import org.koitharu.kotatsu.core.util.ext.invalidateNestedItemDecorations
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesSheet
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.flattenTo import org.koitharu.kotatsu.parsers.util.flattenTo
@ -63,7 +63,7 @@ class ShelfSelectionCallback(
} }
R.id.action_favourite -> { R.id.action_favourite -> {
FavouriteCategoriesBottomSheet.show(fragmentManager, collectSelectedItems(controller)) FavouriteCategoriesSheet.show(fragmentManager, collectSelectedItems(controller))
mode.finish() mode.finish()
true true
} }

@ -8,7 +8,7 @@
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:orientation="vertical"> android:orientation="vertical">
<org.koitharu.kotatsu.core.ui.widgets.BottomSheetHeaderBar <org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar
android:id="@+id/headerBar" android:id="@+id/headerBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -28,7 +28,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/margin_normal" android:layout_marginHorizontal="@dimen/margin_normal"
android:layout_marginTop="@dimen/margin_normal"
android:text="@string/list_mode" android:text="@string/list_mode"
android:textAppearance="?textAppearanceTitleSmall" /> android:textAppearance="?textAppearanceTitleSmall" />

@ -0,0 +1,34 @@
<?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:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:paddingHorizontal="12dp"
android:paddingVertical="8dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<Button
android:id="@+id/button_create"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:text="@string/create_category"
app:icon="@drawable/ic_add" />
<Button
android:id="@+id/button_manage"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:singleLine="true"
android:text="@string/manage"
app:icon="@drawable/ic_edit" />
</LinearLayout>

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
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"
tools:orientation="vertical"
tools:parentTag="android.widget.LinearLayout">
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
android:id="@+id/dragHandle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/layout_sidesheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingVertical="8dp"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_weight="1"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?textAppearanceBodyLarge"
tools:text="@string/filter" />
<ImageView
android:id="@+id/button_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:background="?selectableItemBackgroundBorderless"
android:padding="16dp"
app:srcCompat="?actionModeCloseDrawable"
app:tint="?colorControlActivated" />
</LinearLayout>
</merge>

@ -7,23 +7,10 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<org.koitharu.kotatsu.core.ui.widgets.BottomSheetHeaderBar <org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar
android:id="@+id/headerBar" android:id="@+id/headerBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content" />
<Button
android:id="@+id/button_done"
style="@style/Widget.Material3.Button.UnelevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginHorizontal="@dimen/toolbar_button_margin"
android:text="@string/done"
android:visibility="gone"
tools:visibility="visible" />
</org.koitharu.kotatsu.core.ui.widgets.BottomSheetHeaderBar>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView" android:id="@+id/recyclerView"
@ -31,6 +18,8 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
android:orientation="vertical" android:orientation="vertical"
android:scrollIndicators="top"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_checkable_new" /> tools:listitem="@layout/item_checkable_new" />
</LinearLayout> </LinearLayout>

@ -7,7 +7,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<org.koitharu.kotatsu.core.ui.widgets.BottomSheetHeaderBar <org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar
android:id="@+id/headerBar" android:id="@+id/headerBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -19,6 +19,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_below="@id/headerBar" android:layout_below="@id/headerBar"
android:orientation="vertical" android:orientation="vertical"
android:scrollIndicators="top"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:listitem="@layout/item_chapter" /> tools:listitem="@layout/item_chapter" />

@ -7,33 +7,24 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<org.koitharu.kotatsu.core.ui.widgets.BottomSheetHeaderBar <org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar
android:id="@+id/headerBar" android:id="@+id/headerBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:menu="@menu/opt_categories_bs" app:layout_scrollFlags="noScroll"
app:title="@string/add_to_favourites"> app:title="@string/add_to_favourites" />
<Button
android:id="@+id/button_done"
style="@style/Widget.Material3.Button.UnelevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginHorizontal="@dimen/toolbar_button_margin"
android:text="@string/done" />
</org.koitharu.kotatsu.core.ui.widgets.BottomSheetHeaderBar>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView_categories" android:id="@+id/recyclerView_categories"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clipToPadding="false"
android:orientation="vertical" android:orientation="vertical"
android:overScrollMode="never" android:paddingBottom="@dimen/list_spacing"
android:paddingVertical="@dimen/list_spacing" android:scrollIndicators="top"
android:scrollbars="vertical" android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:ignore="UnusedAttribute"
tools:listitem="@layout/item_checkable_new" /> tools:listitem="@layout/item_checkable_new" />
</LinearLayout> </LinearLayout>

@ -7,7 +7,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<org.koitharu.kotatsu.core.ui.widgets.BottomSheetHeaderBar <org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar
android:id="@+id/headerBar" android:id="@+id/headerBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
@ -22,11 +22,13 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clipToPadding="false" android:clipToPadding="false"
android:padding="@dimen/grid_spacing" android:padding="@dimen/grid_spacing"
android:scrollIndicators="top"
app:bubbleSize="small" app:bubbleSize="small"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3" app:spanCount="3"
app:trackColor="?attr/colorOutline" app:trackColor="?attr/colorOutline"
tools:listitem="@layout/item_page_thumb" /> tools:listitem="@layout/item_page_thumb"
tools:targetApi="m" />
</FrameLayout> </FrameLayout>

@ -7,7 +7,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<org.koitharu.kotatsu.core.ui.widgets.BottomSheetHeaderBar <org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar
android:id="@+id/headerBar" android:id="@+id/headerBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -15,7 +15,8 @@
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:scrollIndicators="top">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

@ -11,13 +11,14 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="16dp"> android:paddingBottom="16dp">
<com.google.android.material.bottomsheet.BottomSheetDragHandleView <org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar
android:id="@+id/dragHandle" android:id="@+id/headerBar"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
app:title="@string/tracking" />
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover" android:id="@+id/imageView_cover"
@ -30,7 +31,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0" app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dragHandle" app:layout_constraintTop_toBottomOf="@id/headerBar"
app:layout_constraintWidth_percent="0.3" app:layout_constraintWidth_percent="0.3"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover" app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
tools:background="@tools:sample/backgrounds/scenic" tools:background="@tools:sample/backgrounds/scenic"
@ -40,14 +41,14 @@
android:id="@+id/imageView_logo" android:id="@+id/imageView_logo"
android:layout_width="32dp" android:layout_width="32dp"
android:layout_height="32dp" android:layout_height="32dp"
android:padding="4dp"
android:layout_margin="@dimen/card_indicator_offset" android:layout_margin="@dimen/card_indicator_offset"
android:background="@drawable/bg_badge_accent" android:background="@drawable/bg_badge_accent"
android:padding="4dp"
app:layout_constraintBottom_toBottomOf="@id/imageView_cover" app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
app:layout_constraintEnd_toEndOf="@id/imageView_cover" app:layout_constraintEnd_toEndOf="@id/imageView_cover"
app:tint="?attr/colorOnSecondary"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
tools:src="@drawable/ic_shikimori" tools:src="@drawable/ic_shikimori" />
app:tint="?attr/colorOnSecondary" />
<TextView <TextView
android:id="@+id/textView_title" android:id="@+id/textView_title"
@ -60,7 +61,7 @@
android:textAppearance="?attr/textAppearanceHeadlineSmall" android:textAppearance="?attr/textAppearanceHeadlineSmall"
app:layout_constraintEnd_toStartOf="@id/button_menu" app:layout_constraintEnd_toStartOf="@id/button_menu"
app:layout_constraintStart_toEndOf="@id/imageView_cover" app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toBottomOf="@id/dragHandle" app:layout_constraintTop_toBottomOf="@id/headerBar"
tools:text="@tools:sample/lorem[9]" /> tools:text="@tools:sample/lorem[9]" />
<ImageButton <ImageButton
@ -72,7 +73,7 @@
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/open_in_browser" android:contentDescription="@string/open_in_browser"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/dragHandle" app:layout_constraintTop_toBottomOf="@id/headerBar"
app:tint="?android:colorControlNormal" /> app:tint="?android:colorControlNormal" />
<RatingBar <RatingBar

@ -93,6 +93,10 @@
<attr name="fitStatusBar" format="boolean" /> <attr name="fitStatusBar" format="boolean" />
</declare-styleable> </declare-styleable>
<declare-styleable name="AdaptiveSheetHeaderBar">
<attr name="title" />
</declare-styleable>
<declare-styleable name="ShapeView"> <declare-styleable name="ShapeView">
<attr name="strokeWidth" /> <attr name="strokeWidth" />
<attr name="strokeColor" /> <attr name="strokeColor" />

@ -68,4 +68,5 @@
<dimen name="fastscroll_scrollbar_padding_start">6dp</dimen> <dimen name="fastscroll_scrollbar_padding_start">6dp</dimen>
<dimen name="fastscroll_scrollbar_padding_end">6dp</dimen> <dimen name="fastscroll_scrollbar_padding_end">6dp</dimen>
<dimen name="m3_side_sheet_width">400dp</dimen>
</resources> </resources>

Loading…
Cancel
Save