Refactor MainActivity navigation and AppBars behavior

This commit is contained in:
Koitharu
2022-08-01 10:33:58 +03:00
parent 656405edbc
commit 8b0f221eef
21 changed files with 350 additions and 666 deletions

View File

@@ -1,151 +0,0 @@
package com.google.android.material.appbar
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.widget.TextView
import androidx.annotation.FloatRange
import com.google.android.material.animation.AnimationUtils
import com.google.android.material.appbar.AppBarLayout.OnOffsetChangedListener
import com.google.android.material.shape.MaterialShapeDrawable
import org.koitharu.kotatsu.R
import com.google.android.material.R as materialR
/**
* [AppBarLayout] with our own lift state handler and custom title alpha.
*
* Inside this package to access some package-private methods.
*/
class KotatsuAppBarLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : AppBarLayout(context, attrs) {
private var lifted = true
private val toolbar by lazy { findViewById<MaterialToolbar>(R.id.toolbar) }
@FloatRange(from = 0.0, to = 1.0)
var titleTextAlpha = 1F
set(value) {
field = value
titleTextView?.alpha = field
}
private var titleTextView: TextView? = null
set(value) {
field = value
field?.alpha = titleTextAlpha
}
private var animatorSet: AnimatorSet? = null
private var statusBarForegroundAnimator: ValueAnimator? = null
private val offsetListener = OnOffsetChangedListener { appBarLayout, verticalOffset ->
// Show status bar foreground when offset
val foreground = (appBarLayout?.statusBarForeground as? MaterialShapeDrawable) ?: return@OnOffsetChangedListener
val start = foreground.alpha
val end = if (verticalOffset != 0) 255 else 0
statusBarForegroundAnimator?.cancel()
if (animatorSet?.isRunning == true) {
foreground.alpha = end
return@OnOffsetChangedListener
}
if (start != end) {
statusBarForegroundAnimator = ValueAnimator.ofInt(start, end).apply {
duration = resources.getInteger(materialR.integer.app_bar_elevation_anim_duration).toLong()
interpolator = AnimationUtils.LINEAR_INTERPOLATOR
addUpdateListener {
foreground.alpha = it.animatedValue as Int
}
start()
}
}
}
var isTransparentWhenNotLifted = false
set(value) {
if (field != value) {
field = value
updateStates()
}
}
override fun isLiftOnScroll(): Boolean = false
override fun isLifted(): Boolean = lifted
override fun setLifted(lifted: Boolean): Boolean {
return if (this.lifted != lifted) {
this.lifted = lifted
updateStates()
true
} else {
false
}
}
override fun setLiftedState(lifted: Boolean, force: Boolean): Boolean = false
override fun onAttachedToWindow() {
super.onAttachedToWindow()
addOnOffsetChangedListener(offsetListener)
toolbar.background.alpha = 0 // Use app bar background
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
removeOnOffsetChangedListener(offsetListener)
}
@SuppressLint("Recycle")
private fun updateStates() {
val animators = mutableListOf<ValueAnimator>()
val fromElevation = elevation
val toElevation = if (lifted) {
resources.getDimension(materialR.dimen.design_appbar_elevation)
} else {
0F
}
if (fromElevation != toElevation) {
ValueAnimator.ofFloat(fromElevation, toElevation).apply {
addUpdateListener {
elevation = it.animatedValue as Float
(statusBarForeground as? MaterialShapeDrawable)?.elevation = it.animatedValue as Float
}
animators.add(this)
}
}
val transparent = if (lifted) false else isTransparentWhenNotLifted
val fromAlpha = (background as? MaterialShapeDrawable)?.alpha ?: background.alpha
val toAlpha = if (transparent) 0 else 255
if (fromAlpha != toAlpha) {
ValueAnimator.ofInt(fromAlpha, toAlpha).apply {
addUpdateListener {
val value = it.animatedValue as Int
background.alpha = value
}
animators.add(this)
}
}
if (animators.isNotEmpty()) {
animatorSet?.cancel()
animatorSet = AnimatorSet().apply {
duration = resources.getInteger(materialR.integer.app_bar_elevation_anim_duration).toLong()
interpolator = AnimationUtils.LINEAR_INTERPOLATOR
playTogether(*animators.toTypedArray())
start()
}
}
}
init {
statusBarForeground = MaterialShapeDrawable.createWithElevationOverlay(context)
}
}

View File

@@ -0,0 +1,42 @@
package org.koitharu.kotatsu.base.ui.util
import android.animation.ValueAnimator
import android.view.animation.AccelerateDecelerateInterpolator
import com.google.android.material.R as materialR
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.shape.MaterialShapeDrawable
import org.koitharu.kotatsu.utils.ext.getAnimationDuration
class StatusBarDimHelper : AppBarLayout.OnOffsetChangedListener {
private var animator: ValueAnimator? = null
private val interpolator = AccelerateDecelerateInterpolator()
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
val foreground = appBarLayout.statusBarForeground ?: return
val start = foreground.alpha
val collapsed = verticalOffset != 0
val end = if (collapsed) 255 else 0
animator?.cancel()
if (start == end) {
animator = null
return
}
animator = ValueAnimator.ofInt(start, end).apply {
duration = appBarLayout.context.getAnimationDuration(materialR.integer.app_bar_elevation_anim_duration)
interpolator = this@StatusBarDimHelper.interpolator
addUpdateListener {
foreground.alpha = it.animatedValue as Int
}
start()
}
}
fun attachToAppBar(appBarLayout: AppBarLayout) {
appBarLayout.addOnOffsetChangedListener(this)
appBarLayout.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(appBarLayout.context).apply {
alpha = 0
}
}
}

View File

@@ -1,134 +0,0 @@
/*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.koitharu.kotatsu.base.ui.widgets
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.annotation.ColorInt
import androidx.annotation.StringRes
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.postDelayed
import com.google.android.material.color.MaterialColors
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import com.google.android.material.snackbar.Snackbar
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.databinding.FadingSnackbarLayoutBinding
import org.koitharu.kotatsu.utils.ext.getThemeColorStateList
import com.google.android.material.R as materialR
private const val SHORT_DURATION_MS = 1_500L
private const val LONG_DURATION_MS = 2_750L
/**
* A custom snackbar implementation allowing more control over placement and entry/exit animations.
*
* Xtimms: Well, my sufferings over the Snackbar in [DetailsActivity] will go away forever... Thanks, Google.
*
* https://github.com/google/iosched/blob/main/mobile/src/main/java/com/google/samples/apps/iosched/widget/FadingSnackbar.kt
*/
class FadingSnackbar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr) {
private val binding = FadingSnackbarLayoutBinding.inflate(LayoutInflater.from(context), this)
private val enterDuration = context.resources.getInteger(R.integer.config_defaultAnimTime).toLong()
private val exitDuration = context.resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
init {
binding.snackbarLayout.background = createThemedBackground()
}
fun dismiss() {
if (visibility == VISIBLE && alpha == 1f) {
animate()
.alpha(0f)
.withEndAction { visibility = GONE }
.duration = exitDuration
}
}
fun show(
messageText: CharSequence?,
@StringRes actionId: Int = 0,
duration: Int = Snackbar.LENGTH_SHORT,
onActionClick: (FadingSnackbar.() -> Unit)? = null,
onDismiss: (() -> Unit)? = null,
) {
binding.snackbarText.text = messageText
if (actionId != 0) {
with(binding.snackbarAction) {
visibility = VISIBLE
text = context.getString(actionId)
setOnClickListener {
onActionClick?.invoke(this@FadingSnackbar) ?: dismiss()
}
}
} else {
binding.snackbarAction.visibility = GONE
}
alpha = 0f
visibility = VISIBLE
animate()
.alpha(1f)
.duration = enterDuration
if (duration == Snackbar.LENGTH_INDEFINITE) {
return
}
val durationMs = enterDuration + if (duration == Snackbar.LENGTH_LONG) LONG_DURATION_MS else SHORT_DURATION_MS
postDelayed(durationMs) {
dismiss()
onDismiss?.invoke()
}
}
private fun createThemedBackground(): Drawable {
val backgroundColor = MaterialColors.layer(this, materialR.attr.colorSurface, materialR.attr.colorOnSurface, 1f)
val shapeAppearanceModel = ShapeAppearanceModel.builder(
context,
materialR.style.ShapeAppearance_Material3_Corner_ExtraSmall,
0
).build()
val background = createMaterialShapeDrawableBackground(
backgroundColor,
shapeAppearanceModel,
)
val backgroundTint = context.getThemeColorStateList(materialR.attr.colorSurfaceInverse)
return if (backgroundTint != null) {
val wrappedDrawable = DrawableCompat.wrap(background)
DrawableCompat.setTintList(wrappedDrawable, backgroundTint)
wrappedDrawable
} else {
DrawableCompat.wrap(background)
}
}
private fun createMaterialShapeDrawableBackground(
@ColorInt backgroundColor: Int,
shapeAppearanceModel: ShapeAppearanceModel,
): MaterialShapeDrawable {
val background = MaterialShapeDrawable(shapeAppearanceModel)
background.fillColor = ColorStateList.valueOf(backgroundColor)
return background
}
}

View File

@@ -4,17 +4,14 @@ import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.bottomnavigation.BottomNavigationView
import org.koitharu.kotatsu.utils.ext.animatorDurationScale
import org.koitharu.kotatsu.utils.ext.findChild
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.utils.ext.getAnimationDuration
import org.koitharu.kotatsu.utils.ext.measureHeight
import kotlin.math.roundToLong
class HideBottomNavigationOnScrollBehavior @JvmOverloads constructor(
context: Context? = null,
@@ -90,7 +87,7 @@ class HideBottomNavigationOnScrollBehavior @JvmOverloads constructor(
offsetAnimator?.cancel()
offsetAnimator = ValueAnimator().apply {
interpolator = DecelerateInterpolator()
duration = (150 * child.context.animatorDurationScale).roundToLong()
duration = child.context.getAnimationDuration(R.integer.config_shorterAnimTime)
addUpdateListener {
child.translationY = it.animatedValue as Float
}
@@ -101,4 +98,4 @@ class HideBottomNavigationOnScrollBehavior @JvmOverloads constructor(
)
offsetAnimator?.start()
}
}
}

View File

@@ -1,111 +0,0 @@
package org.koitharu.kotatsu.base.ui.widgets
import android.content.Context
import android.os.Parcel
import android.os.Parcelable
import android.util.AttributeSet
import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.doOnLayout
import androidx.customview.view.AbsSavedState
import com.google.android.material.appbar.AppBarLayout
import org.koitharu.kotatsu.utils.ext.findChild
class KotatsuCoordinatorLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = androidx.coordinatorlayout.R.attr.coordinatorLayoutStyle
) : CoordinatorLayout(context, attrs, defStyleAttr) {
private var appBarLayout: AppBarLayout? = null
/**
* If true, [AppBarLayout] child will be lifted on nested scroll.
*/
var isLiftAppBarOnScroll = true
/**
* Internal check
*/
private val canLiftAppBarOnScroll
get() = isLiftAppBarOnScroll
override fun onNestedScroll(
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
type: Int,
consumed: IntArray
) {
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed)
if (canLiftAppBarOnScroll) {
appBarLayout?.isLifted = dyConsumed != 0 || dyUnconsumed >= 0
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
appBarLayout = findChild()
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
appBarLayout = null
}
override fun onSaveInstanceState(): Parcelable? {
val superState = super.onSaveInstanceState()
return if (superState != null) {
SavedState(superState).also {
it.appBarLifted = appBarLayout?.isLifted ?: false
}
} else {
superState
}
}
override fun onRestoreInstanceState(state: Parcelable?) {
if (state is SavedState) {
super.onRestoreInstanceState(state.superState)
doOnLayout {
appBarLayout?.isLifted = state.appBarLifted
}
} else {
super.onRestoreInstanceState(state)
}
}
internal class SavedState : AbsSavedState {
var appBarLifted = false
constructor(superState: Parcelable) : super(superState)
constructor(source: Parcel, loader: ClassLoader?) : super(source, loader) {
appBarLifted = source.readByte().toInt() == 1
}
override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
out.writeByte((if (appBarLifted) 1 else 0).toByte())
}
companion object {
@JvmField
val CREATOR: Parcelable.ClassLoaderCreator<SavedState> = object : Parcelable.ClassLoaderCreator<SavedState> {
override fun createFromParcel(source: Parcel, loader: ClassLoader): SavedState {
return SavedState(source, loader)
}
override fun createFromParcel(source: Parcel): SavedState {
return SavedState(source, null)
}
override fun newArray(size: Int): Array<SavedState> {
return newArray(size)
}
}
}
}
}

View File

@@ -8,46 +8,42 @@ import android.os.Parcel
import android.os.Parcelable
import android.util.AttributeSet
import android.view.ViewPropertyAnimator
import androidx.annotation.AttrRes
import androidx.annotation.StyleRes
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.doOnLayout
import androidx.core.view.updateLayoutParams
import androidx.customview.view.AbsSavedState
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.google.android.material.R as materialR
import com.google.android.material.bottomnavigation.BottomNavigationView
import org.koitharu.kotatsu.utils.ext.applySystemAnimatorScale
import com.google.android.material.R as materialR
import org.koitharu.kotatsu.utils.ext.measureHeight
class KotatsuBottomNavigationView @JvmOverloads constructor(
private const val STATE_DOWN = 1
private const val STATE_UP = 2
private const val SLIDE_UP_ANIMATION_DURATION = 225L
private const val SLIDE_DOWN_ANIMATION_DURATION = 175L
class SlidingBottomNavigationView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = materialR.attr.bottomNavigationStyle,
defStyleRes: Int = materialR.style.Widget_Design_BottomNavigationView,
) : BottomNavigationView(context, attrs, defStyleAttr, defStyleRes) {
@AttrRes defStyleAttr: Int = materialR.attr.bottomNavigationStyle,
@StyleRes defStyleRes: Int = materialR.style.Widget_Design_BottomNavigationView,
) : BottomNavigationView(context, attrs, defStyleAttr, defStyleRes),
CoordinatorLayout.AttachedBehavior {
private var currentAnimator: ViewPropertyAnimator? = null
private var currentState = STATE_UP
private var behavior = HideBottomNavigationOnScrollBehavior()
init {
// Hide on scroll
doOnLayout {
findViewTreeLifecycleOwner()?.lifecycleScope?.let {
updateLayoutParams<CoordinatorLayout.LayoutParams> {
behavior = HideBottomNavigationOnScrollBehavior()
}
}
}
override fun getBehavior(): CoordinatorLayout.Behavior<*> {
return behavior
}
override fun onSaveInstanceState(): Parcelable {
val superState = super.onSaveInstanceState()
return SavedState(superState).also {
it.currentState = currentState
it.translationY = translationY
}
return SavedState(superState, currentState, translationY)
}
override fun onRestoreInstanceState(state: Parcelable?) {
@@ -62,14 +58,12 @@ class KotatsuBottomNavigationView @JvmOverloads constructor(
override fun setTranslationY(translationY: Float) {
// Disallow translation change when state down
if (currentState == STATE_DOWN) return
super.setTranslationY(translationY)
if (currentState != STATE_DOWN) {
super.setTranslationY(translationY)
}
}
/**
* Shows this view up.
*/
fun slideUp() = post {
fun show() {
currentAnimator?.cancel()
clearAnimation()
@@ -81,16 +75,17 @@ class KotatsuBottomNavigationView @JvmOverloads constructor(
)
}
/**
* Hides this view down. [setTranslationY] won't work until [slideUp] is called.
*/
fun slideDown() = post {
fun hide() {
currentAnimator?.cancel()
clearAnimation()
currentState = STATE_DOWN
val target = measureHeight()
if (target == 0) {
return
}
animateTranslation(
height.toFloat(),
target.toFloat(),
SLIDE_DOWN_ANIMATION_DURATION,
FastOutLinearInInterpolator(),
)
@@ -102,22 +97,26 @@ class KotatsuBottomNavigationView @JvmOverloads constructor(
.setInterpolator(interpolator)
.setDuration(duration)
.applySystemAnimatorScale(context)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
currentAnimator = null
postInvalidate()
}
},
.setListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
currentAnimator = null
postInvalidate()
}
},
)
}
internal class SavedState : AbsSavedState {
internal class SavedState : BaseSavedState {
var currentState = STATE_UP
var translationY = 0F
constructor(superState: Parcelable) : super(superState)
constructor(superState: Parcelable, currentState: Int, translationY: Float) : super(superState) {
this.currentState = currentState
this.translationY = translationY
}
constructor(source: Parcel, loader: ClassLoader?) : super(source, loader) {
constructor(source: Parcel) : super(source) {
currentState = source.readInt()
translationY = source.readFloat()
}
@@ -129,28 +128,14 @@ class KotatsuBottomNavigationView @JvmOverloads constructor(
}
companion object {
@Suppress("unused")
@JvmField
val CREATOR: Parcelable.ClassLoaderCreator<SavedState> = object : Parcelable.ClassLoaderCreator<SavedState> {
override fun createFromParcel(source: Parcel, loader: ClassLoader): SavedState {
return SavedState(source, loader)
}
val CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> {
override fun createFromParcel(`in`: Parcel) = SavedState(`in`)
override fun createFromParcel(source: Parcel): SavedState {
return SavedState(source, null)
}
override fun newArray(size: Int): Array<SavedState> {
return newArray(size)
}
override fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)
}
}
}
companion object {
private const val STATE_DOWN = 1
private const val STATE_UP = 2
private const val SLIDE_UP_ANIMATION_DURATION = 225L
private const val SLIDE_DOWN_ANIMATION_DURATION = 175L
}
}
}

View File

@@ -1,14 +0,0 @@
package org.koitharu.kotatsu.base.ui.widgets
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
class SquareLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
public override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec)
}
}

View File

@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.base.ui.widgets
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
@@ -21,8 +20,7 @@ class WindowInsetHolder @JvmOverloads constructor(
private var desiredHeight = 0
private var desiredWidth = 0
@SuppressLint("RtlHardcoded")
override fun dispatchApplyWindowInsets(insets: WindowInsets): WindowInsets {
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
val barsInsets = WindowInsetsCompat.toWindowInsetsCompat(insets, this)
.getInsets(WindowInsetsCompat.Type.systemBars())
val gravity = getLayoutGravity()
@@ -41,24 +39,26 @@ class WindowInsetHolder @JvmOverloads constructor(
desiredHeight = newHeight
requestLayout()
}
return super.dispatchApplyWindowInsets(insets)
return super.onApplyWindowInsets(insets)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
super.onMeasure(
if (desiredWidth == 0 || widthMode == MeasureSpec.EXACTLY) {
widthMeasureSpec
} else {
MeasureSpec.makeMeasureSpec(desiredWidth, widthMode)
},
if (desiredHeight == 0 || heightMode == MeasureSpec.EXACTLY) {
heightMeasureSpec
} else {
MeasureSpec.makeMeasureSpec(desiredHeight, heightMode)
},
)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
val width: Int = when (widthMode) {
MeasureSpec.EXACTLY -> widthSize
MeasureSpec.AT_MOST -> minOf(desiredWidth, widthSize)
else -> desiredWidth
}
val height = when (heightMode) {
MeasureSpec.EXACTLY -> heightSize
MeasureSpec.AT_MOST -> minOf(desiredHeight, heightSize)
else -> desiredHeight
}
setMeasuredDimension(width, height)
}
private fun getLayoutGravity(): Int {
@@ -69,4 +69,4 @@ class WindowInsetHolder @JvmOverloads constructor(
else -> Gravity.NO_GRAVITY
}
}
}
}

View File

@@ -201,16 +201,11 @@ abstract class MangaListFragment :
}
override fun onWindowInsetsChanged(insets: Insets) {
binding.root.updatePadding(
left = insets.left,
right = insets.right,
)
binding.recyclerView.updatePadding(
bottom = insets.bottom,
)
binding.recyclerView.fastScroller.updateLayoutParams<MarginLayoutParams> {
bottomMargin = insets.bottom
marginEnd = insets.end(binding.recyclerView)
}
if (activity is MainActivity) {
val headerHeight = (activity as? AppBarOwner)?.appBar?.measureHeight() ?: insets.top

View File

@@ -1,8 +1,8 @@
package org.koitharu.kotatsu.main.ui
import org.koitharu.kotatsu.base.ui.widgets.KotatsuBottomNavigationView
import org.koitharu.kotatsu.base.ui.widgets.SlidingBottomNavigationView
interface BottomNavOwner {
val bottomNav: KotatsuBottomNavigationView?
val bottomNav: SlidingBottomNavigationView?
}

View File

@@ -7,9 +7,9 @@ import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import androidx.activity.result.ActivityResultCallback
import androidx.activity.viewModels
import androidx.annotation.IdRes
import androidx.appcompat.view.ActionMode
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets
import androidx.core.util.size
import androidx.core.view.*
@@ -21,7 +21,6 @@ import androidx.transition.TransitionManager
import com.google.android.material.R as materialR
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.AppBarLayout.LayoutParams.*
import com.google.android.material.navigation.NavigationBarView
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
@@ -29,11 +28,9 @@ import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.base.ui.widgets.KotatsuBottomNavigationView
import org.koitharu.kotatsu.base.ui.widgets.SlidingBottomNavigationView
import org.koitharu.kotatsu.databinding.ActivityMainBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.explore.ui.ExploreFragment
import org.koitharu.kotatsu.library.ui.LibraryFragment
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
@@ -46,14 +43,11 @@ import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment
import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment
import org.koitharu.kotatsu.settings.tools.ToolsFragment
import org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker
import org.koitharu.kotatsu.tracker.ui.FeedFragment
import org.koitharu.kotatsu.tracker.work.TrackWorker
import org.koitharu.kotatsu.utils.VoiceInputContract
import org.koitharu.kotatsu.utils.ext.*
private const val TAG_PRIMARY = "primary"
private const val TAG_SEARCH = "search"
@AndroidEntryPoint
@@ -64,25 +58,23 @@ class MainActivity :
View.OnClickListener,
View.OnFocusChangeListener,
SearchSuggestionListener,
NavigationBarView.OnItemSelectedListener,
NavigationBarView.OnItemReselectedListener {
MainNavigationDelegate.OnFragmentChangedListener {
private val viewModel by viewModels<MainViewModel>()
private val searchSuggestionViewModel by viewModels<SearchSuggestionViewModel>()
private val voiceInputLauncher = registerForActivityResult(VoiceInputContract(), VoiceInputCallback())
private lateinit var navBar: NavigationBarView
private lateinit var navigationDelegate: MainNavigationDelegate
override val appBar: AppBarLayout
get() = binding.appbar
override val bottomNav: KotatsuBottomNavigationView?
override val bottomNav: SlidingBottomNavigationView?
get() = binding.bottomNav
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityMainBinding.inflate(layoutInflater))
navBar = checkNotNull(bottomNav ?: binding.navRail)
if (bottomNav != null) {
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) {
@@ -98,18 +90,17 @@ class MainActivity :
onFocusChangeListener = this@MainActivity
searchSuggestionListener = this@MainActivity
}
window.statusBarColor = ContextCompat.getColor(this, R.color.dim_statusbar)
binding.root.isLiftAppBarOnScroll = false
navBar.setOnItemSelectedListener(this)
navBar.setOnItemReselectedListener(this)
binding.fab?.setOnClickListener(this)
binding.navRail?.headerView?.setOnClickListener(this)
binding.searchView.isVoiceSearchEnabled = voiceInputLauncher.resolve(this, null) != null
onBackPressedDispatcher.addCallback(ExitCallback(this, binding.container))
supportFragmentManager.findFragmentByTag(TAG_PRIMARY)?.let {
if (it is LibraryFragment) binding.fab?.show() else binding.fab?.hide()
} ?: onNavigationItemSelected(navBar.selectedItemId)
navigationDelegate = MainNavigationDelegate(checkNotNull(bottomNav ?: binding.navRail), supportFragmentManager)
navigationDelegate.addOnFragmentChangedListener(this)
navigationDelegate.onCreate(savedInstanceState)
if (savedInstanceState == null) {
onFirstStart()
}
@@ -123,17 +114,7 @@ class MainActivity :
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
val isSearchOpened = isSearchOpened()
if (isSearchOpened) {
binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> {
scrollFlags = SCROLL_FLAG_NO_SCROLL
}
binding.toolbarCard.background = null
binding.appbar.setBackgroundColor(getThemeColor(materialR.attr.colorSurfaceVariant))
binding.appbar.updatePadding(left = 0, right = 0)
supportActionBar?.setHomeAsUpIndicator(materialR.drawable.abc_ic_ab_back_material)
}
adjustFabVisibility(isSearchOpened = isSearchOpened)
adjustSearchUI(isSearchOpened(), animate = false)
}
override fun onBackPressed() {
@@ -149,8 +130,15 @@ class MainActivity :
}
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
return onNavigationItemSelected(item.itemId)
override fun onFragmentChanged(fragment: Fragment, fromUser: Boolean) {
if (fragment is LibraryFragment) {
binding.fab?.show()
} else {
binding.fab?.hide()
}
if (fromUser) {
binding.appbar.setExpanded(true)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@@ -248,36 +236,9 @@ class MainActivity :
showNav(true)
}
private fun onNavigationItemSelected(@IdRes itemId: Int): Boolean {
when (itemId) {
R.id.nav_library -> {
setPrimaryFragment(LibraryFragment.newInstance())
}
R.id.nav_explore -> {
setPrimaryFragment(ExploreFragment.newInstance())
}
R.id.nav_feed -> {
setPrimaryFragment(FeedFragment.newInstance())
}
R.id.nav_tools -> {
setPrimaryFragment(ToolsFragment.newInstance())
}
else -> return false
}
appBar.setExpanded(true)
appBar.isLifted = false
return true
}
override fun onNavigationItemReselected(item: MenuItem) {
val fragment = supportFragmentManager.findFragmentById(R.id.container) as? RecyclerViewOwner ?: return
val recyclerView = fragment.recyclerView
recyclerView.smoothScrollToPosition(0)
binding.appbar.isLifted = false
}
private fun onOpenReader(manga: Manga) {
val options = binding.fab?.let {
val fab = binding.fab ?: binding.navRail?.headerView
val options = fab?.let {
scaleUpActivityOptionsOf(it).toBundle()
}
startActivity(ReaderActivity.newIntent(this, manga), options)
@@ -292,17 +253,7 @@ class MainActivity :
repeat(counters.size) { i ->
val id = counters.keyAt(i)
val counter = counters.valueAt(i)
if (counter == 0) {
navBar.getBadge(id)?.isVisible = false
} else {
val badge = navBar.getOrCreateBadge(id)
if (counter < 0) {
badge.clearNumber()
} else {
badge.number = counter
}
badge.isVisible = true
}
navigationDelegate.setCounter(id, counter)
}
}
@@ -314,55 +265,28 @@ class MainActivity :
adjustFabVisibility(isResumeEnabled = isEnabled)
}
private fun setPrimaryFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, fragment, TAG_PRIMARY)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit()
adjustFabVisibility(topFragment = fragment)
}
private fun onSearchOpened() {
TransitionManager.beginDelayedTransition(binding.appbar)
binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> {
scrollFlags = SCROLL_FLAG_NO_SCROLL
}
binding.toolbarCard.background = null
binding.appbar.isLifted = true
binding.appbar.updatePadding(left = 0, right = 0)
adjustFabVisibility(isSearchOpened = true)
supportActionBar?.setHomeAsUpIndicator(materialR.drawable.abc_ic_ab_back_material)
showNav(false)
adjustSearchUI(isOpened = true, animate = true)
}
private fun onSearchClosed() {
binding.searchView.hideKeyboard()
TransitionManager.beginDelayedTransition(binding.appbar)
binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> {
scrollFlags = SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS or SCROLL_FLAG_SNAP
}
binding.toolbarCard.setBackgroundResource(R.drawable.toolbar_background)
binding.appbar.isLifted = false
val padding = resources.getDimensionPixelOffset(R.dimen.margin_normal)
binding.appbar.updatePadding(left = padding, right = padding)
adjustFabVisibility(isSearchOpened = false)
supportActionBar?.setHomeAsUpIndicator(materialR.drawable.abc_ic_search_api_material)
showNav(true)
adjustSearchUI(isOpened = false, animate = true)
}
private fun showNav(visible: Boolean) {
bottomNav?.run {
if (visible) {
slideUp()
show()
} else {
slideDown()
hide()
}
}
binding.navRail?.isVisible = visible
}
private fun isSearchOpened(): Boolean {
return supportFragmentManager.findFragmentByTag(TAG_SEARCH)?.isVisible == true
return supportFragmentManager.findFragmentByTag(TAG_SEARCH) != null
}
private fun onFirstStart() {
@@ -382,7 +306,7 @@ class MainActivity :
private fun adjustFabVisibility(
isResumeEnabled: Boolean = viewModel.isResumeEnabled.value == true,
topFragment: Fragment? = supportFragmentManager.findFragmentByTag(TAG_PRIMARY),
topFragment: Fragment? = navigationDelegate.primaryFragment,
isSearchOpened: Boolean = isSearchOpened(),
) {
val fab = binding.fab
@@ -402,6 +326,31 @@ class MainActivity :
}
}
private fun adjustSearchUI(isOpened: Boolean, animate: Boolean) {
if (animate) {
TransitionManager.beginDelayedTransition(binding.appbar)
}
val appBarScrollFlags = if (isOpened) {
SCROLL_FLAG_NO_SCROLL
} else {
SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS or SCROLL_FLAG_SNAP
}
binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> { scrollFlags = appBarScrollFlags }
binding.insetsHolder.updateLayoutParams<AppBarLayout.LayoutParams> { scrollFlags = appBarScrollFlags }
binding.toolbarCard.background = if (isOpened) {
null
} else {
ContextCompat.getDrawable(this, R.drawable.toolbar_background)
}
val padding = if (isOpened) 0 else resources.getDimensionPixelOffset(R.dimen.margin_normal)
binding.appbar.updatePadding(left = padding, right = padding)
adjustFabVisibility(isSearchOpened = isOpened)
supportActionBar?.setHomeAsUpIndicator(
if (isOpened) materialR.drawable.abc_ic_ab_back_material else materialR.drawable.abc_ic_search_api_material,
)
showNav(!isOpened)
}
private inner class VoiceInputCallback : ActivityResultCallback<String?> {
override fun onActivityResult(result: String?) {

View File

@@ -0,0 +1,102 @@
package org.koitharu.kotatsu.main.ui
import android.os.Bundle
import android.view.MenuItem
import androidx.annotation.IdRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import com.google.android.material.navigation.NavigationBarView
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.explore.ui.ExploreFragment
import org.koitharu.kotatsu.library.ui.LibraryFragment
import org.koitharu.kotatsu.settings.tools.ToolsFragment
import org.koitharu.kotatsu.tracker.ui.FeedFragment
import java.util.*
private const val TAG_PRIMARY = "primary"
class MainNavigationDelegate(
private val navBar: NavigationBarView,
private val fragmentManager: FragmentManager,
) : NavigationBarView.OnItemSelectedListener, NavigationBarView.OnItemReselectedListener {
private val listeners = LinkedList<OnFragmentChangedListener>()
val primaryFragment: Fragment?
get() = fragmentManager.findFragmentByTag(TAG_PRIMARY)
init {
navBar.setOnItemSelectedListener(this)
navBar.setOnItemReselectedListener(this)
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
return onNavigationItemSelected(item.itemId)
}
override fun onNavigationItemReselected(item: MenuItem) {
val fragment = fragmentManager.findFragmentByTag(TAG_PRIMARY) as? RecyclerViewOwner ?: return
val recyclerView = fragment.recyclerView
recyclerView.smoothScrollToPosition(0)
}
fun onCreate(savedInstanceState: Bundle?) {
primaryFragment?.let {
onFragmentChanged(it, fromUser = false)
} ?: onNavigationItemSelected(navBar.selectedItemId)
}
fun setCounter(@IdRes id: Int, counter: Int) {
if (counter == 0) {
navBar.getBadge(id)?.isVisible = false
} else {
val badge = navBar.getOrCreateBadge(id)
if (counter < 0) {
badge.clearNumber()
} else {
badge.number = counter
}
badge.isVisible = true
}
}
fun addOnFragmentChangedListener(listener: OnFragmentChangedListener) {
listeners.add(listener)
}
fun removeOnFragmentChangedListener(listener: OnFragmentChangedListener) {
listeners.remove(listener)
}
private fun onNavigationItemSelected(@IdRes itemId: Int): Boolean {
setPrimaryFragment(
when (itemId) {
R.id.nav_library -> LibraryFragment.newInstance()
R.id.nav_explore -> ExploreFragment.newInstance()
R.id.nav_feed -> FeedFragment.newInstance()
R.id.nav_tools -> ToolsFragment.newInstance()
else -> return false
},
)
return true
}
private fun setPrimaryFragment(fragment: Fragment) {
fragmentManager.beginTransaction()
.replace(R.id.container, fragment, TAG_PRIMARY)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit()
onFragmentChanged(fragment, fromUser = true)
}
private fun onFragmentChanged(fragment: Fragment, fromUser: Boolean) {
listeners.forEach { it.onFragmentChanged(fragment, fromUser) }
}
interface OnFragmentChangedListener {
fun onFragmentChanged(fragment: Fragment, fromUser: Boolean)
}
}

View File

@@ -7,6 +7,7 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.appcompat.view.ActionMode
import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
import coil.ImageLoader
@@ -53,6 +54,7 @@ class MultiSearchActivity :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivitySearchMultiBinding.inflate(layoutInflater))
window.statusBarColor = ContextCompat.getColor(this, R.color.dim_statusbar)
val itemCLickListener = object : OnListItemClickListener<MultiSearchListModel> {
override fun onItemClick(item: MultiSearchListModel, view: View) {

View File

@@ -1,28 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<org.koitharu.kotatsu.base.ui.widgets.KotatsuCoordinatorLayout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context=".main.ui.MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
<com.google.android.material.navigationrail.NavigationRailView
android:id="@+id/navRail"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:elevation="1dp"
app:headerLayout="@layout/navigation_rail_fab"
app:labelVisibilityMode="labeled"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/nav_bottom" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.KotatsuAppBarLayout
<androidx.fragment.app.FragmentContainerView
android:id="@id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:layout="@layout/fragment_list" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:clipToPadding="false"
android:fitsSystemWindows="false"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:clipToPadding="false"
android:stateListAnimator="@null"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/navRail"
app:layout_constraintTop_toTopOf="parent">
app:liftOnScroll="false">
<org.koitharu.kotatsu.base.ui.widgets.WindowInsetHolder
android:id="@+id/insetsHolder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
app:layout_scrollFlags="scroll|enterAlways|snap" />
<FrameLayout
android:id="@+id/toolbar_card"
@@ -30,7 +53,8 @@
android:layout_height="48dp"
android:layout_marginVertical="8dp"
android:background="@drawable/toolbar_background"
android:theme="@style/ThemeOverlay.Kotatsu.MainToolbar">
android:theme="@style/ThemeOverlay.Kotatsu.MainToolbar"
app:layout_scrollFlags="scroll|enterAlways|snap">
<com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar"
@@ -51,7 +75,7 @@
android:background="@null"
android:gravity="center_vertical"
android:hint="@string/search_manga"
android:imeOptions="actionSearch"
android:imeOptions="actionSearch|flagNoFullscreen"
android:importantForAutofill="no"
android:singleLine="true"
tools:drawableEnd="@drawable/abc_ic_clear_material" />
@@ -60,28 +84,8 @@
</FrameLayout>
</com.google.android.material.appbar.KotatsuAppBarLayout>
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.navigationrail.NavigationRailView
android:id="@+id/navRail"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:elevation="1dp"
app:headerLayout="@layout/navigation_rail_fab"
app:labelVisibilityMode="labeled"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/nav_bottom" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@id/container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/navRail"
app:layout_constraintTop_toBottomOf="@id/appbar"
tools:layout="@layout/fragment_list" />
</androidx.constraintlayout.widget.ConstraintLayout>
</org.koitharu.kotatsu.base.ui.widgets.KotatsuCoordinatorLayout>
</LinearLayout>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<org.koitharu.kotatsu.base.ui.widgets.KotatsuCoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
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"
@@ -14,14 +14,22 @@
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:layout="@layout/fragment_list" />
<com.google.android.material.appbar.KotatsuAppBarLayout
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:fitsSystemWindows="true"
android:fitsSystemWindows="false"
android:paddingHorizontal="16dp"
android:stateListAnimator="@null">
android:stateListAnimator="@null"
app:liftOnScroll="false">
<org.koitharu.kotatsu.base.ui.widgets.WindowInsetHolder
android:id="@+id/insetsHolder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
app:layout_scrollFlags="scroll|enterAlways|snap" />
<FrameLayout
android:id="@+id/toolbar_card"
@@ -60,7 +68,7 @@
</FrameLayout>
</com.google.android.material.appbar.KotatsuAppBarLayout>
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/fab"
@@ -68,6 +76,7 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="-4dp"
android:paddingBottom="8dp"
android:text="@string/_continue"
android:visibility="gone"
app:backgroundTint="?attr/colorContainer"
@@ -75,12 +84,11 @@
app:layout_anchor="@id/bottomNav"
app:layout_anchorGravity="top|end"
app:layout_behavior="org.koitharu.kotatsu.base.ui.util.ShrinkOnScrollBehavior"
app:layout_insetEdge="bottom"
app:layout_dodgeInsetEdges="bottom"
android:paddingBottom="8dp"
app:layout_insetEdge="bottom"
tools:visibility="visible" />
<org.koitharu.kotatsu.base.ui.widgets.KotatsuBottomNavigationView
<org.koitharu.kotatsu.base.ui.widgets.SlidingBottomNavigationView
android:id="@+id/bottomNav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -90,4 +98,4 @@
app:menu="@menu/nav_bottom"
tools:ignore="KeyboardInaccessibleWidget" />
</org.koitharu.kotatsu.base.ui.widgets.KotatsuCoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<org.koitharu.kotatsu.base.ui.widgets.KotatsuCoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
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="match_parent">
<com.google.android.material.appbar.KotatsuAppBarLayout
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -29,7 +29,7 @@
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.KotatsuAppBarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@id/container"
@@ -37,4 +37,4 @@
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
</org.koitharu.kotatsu.base.ui.widgets.KotatsuCoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,16 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<org.koitharu.kotatsu.base.ui.widgets.KotatsuCoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
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="match_parent">
<com.google.android.material.appbar.KotatsuAppBarLayout
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:elevation="0dp">
android:fitsSystemWindows="false"
app:elevation="0dp"
app:liftOnScroll="false">
<org.koitharu.kotatsu.base.ui.widgets.WindowInsetHolder
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
app:layout_scrollFlags="scroll|enterAlways|snap" />
<com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar"
@@ -18,7 +25,7 @@
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways" />
</com.google.android.material.appbar.KotatsuAppBarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@id/recyclerView"
@@ -28,4 +35,4 @@
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
</org.koitharu.kotatsu.base.ui.widgets.KotatsuCoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/railFab"
android:theme="@style/ThemeOverlay.Material3.FloatingActionButton.Tertiary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_read" />
android:contentDescription="@string/_continue"
app:srcCompat="@drawable/ic_read" />

View File

@@ -8,5 +8,6 @@
<color name="scrollbar">#66FFFFFF</color>
<color name="selector_foreground">#29FFFFFF</color>
<color name="divider_default">#1FFFFFFF</color>
<color name="dim_statusbar">#99000000</color>
</resources>
</resources>

View File

@@ -18,8 +18,9 @@
<color name="grey">#424242</color>
<color name="grey_dark">#212121</color>
<color name="dim">#99000000</color>
<color name="dim_statusbar">#99FFFFFF</color>
<color name="scrollbar">#66000000</color>
<color name="selector_foreground">#29000000</color>
<color name="divider_default">#1F000000</color>
</resources>
</resources>

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="config_defaultAnimTime">300</integer>
<integer name="config_shorterAnimTime">150</integer>
<integer name="config_tinyAnimTime">50</integer>
<integer name="manga_badge_max_character_count">3</integer>