Refactor MainActivity navigation and AppBars behavior
This commit is contained in:
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,17 +4,14 @@ import android.animation.ValueAnimator
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.animation.DecelerateInterpolator
|
import android.view.animation.DecelerateInterpolator
|
||||||
import androidx.appcompat.widget.Toolbar
|
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
import org.koitharu.kotatsu.utils.ext.animatorDurationScale
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.utils.ext.findChild
|
import org.koitharu.kotatsu.utils.ext.getAnimationDuration
|
||||||
import org.koitharu.kotatsu.utils.ext.measureHeight
|
import org.koitharu.kotatsu.utils.ext.measureHeight
|
||||||
import kotlin.math.roundToLong
|
|
||||||
|
|
||||||
class HideBottomNavigationOnScrollBehavior @JvmOverloads constructor(
|
class HideBottomNavigationOnScrollBehavior @JvmOverloads constructor(
|
||||||
context: Context? = null,
|
context: Context? = null,
|
||||||
@@ -90,7 +87,7 @@ class HideBottomNavigationOnScrollBehavior @JvmOverloads constructor(
|
|||||||
offsetAnimator?.cancel()
|
offsetAnimator?.cancel()
|
||||||
offsetAnimator = ValueAnimator().apply {
|
offsetAnimator = ValueAnimator().apply {
|
||||||
interpolator = DecelerateInterpolator()
|
interpolator = DecelerateInterpolator()
|
||||||
duration = (150 * child.context.animatorDurationScale).roundToLong()
|
duration = child.context.getAnimationDuration(R.integer.config_shorterAnimTime)
|
||||||
addUpdateListener {
|
addUpdateListener {
|
||||||
child.translationY = it.animatedValue as Float
|
child.translationY = it.animatedValue as Float
|
||||||
}
|
}
|
||||||
@@ -101,4 +98,4 @@ class HideBottomNavigationOnScrollBehavior @JvmOverloads constructor(
|
|||||||
)
|
)
|
||||||
offsetAnimator?.start()
|
offsetAnimator?.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,46 +8,42 @@ import android.os.Parcel
|
|||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.ViewPropertyAnimator
|
import android.view.ViewPropertyAnimator
|
||||||
|
import androidx.annotation.AttrRes
|
||||||
|
import androidx.annotation.StyleRes
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
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.FastOutLinearInInterpolator
|
||||||
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
||||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
import com.google.android.material.R as materialR
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
import org.koitharu.kotatsu.utils.ext.applySystemAnimatorScale
|
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,
|
context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = materialR.attr.bottomNavigationStyle,
|
@AttrRes defStyleAttr: Int = materialR.attr.bottomNavigationStyle,
|
||||||
defStyleRes: Int = materialR.style.Widget_Design_BottomNavigationView,
|
@StyleRes defStyleRes: Int = materialR.style.Widget_Design_BottomNavigationView,
|
||||||
) : BottomNavigationView(context, attrs, defStyleAttr, defStyleRes) {
|
) : BottomNavigationView(context, attrs, defStyleAttr, defStyleRes),
|
||||||
|
CoordinatorLayout.AttachedBehavior {
|
||||||
|
|
||||||
private var currentAnimator: ViewPropertyAnimator? = null
|
private var currentAnimator: ViewPropertyAnimator? = null
|
||||||
|
|
||||||
private var currentState = STATE_UP
|
private var currentState = STATE_UP
|
||||||
|
private var behavior = HideBottomNavigationOnScrollBehavior()
|
||||||
|
|
||||||
init {
|
override fun getBehavior(): CoordinatorLayout.Behavior<*> {
|
||||||
// Hide on scroll
|
return behavior
|
||||||
doOnLayout {
|
|
||||||
findViewTreeLifecycleOwner()?.lifecycleScope?.let {
|
|
||||||
updateLayoutParams<CoordinatorLayout.LayoutParams> {
|
|
||||||
behavior = HideBottomNavigationOnScrollBehavior()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(): Parcelable {
|
override fun onSaveInstanceState(): Parcelable {
|
||||||
val superState = super.onSaveInstanceState()
|
val superState = super.onSaveInstanceState()
|
||||||
return SavedState(superState).also {
|
return SavedState(superState, currentState, translationY)
|
||||||
it.currentState = currentState
|
|
||||||
it.translationY = translationY
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRestoreInstanceState(state: Parcelable?) {
|
override fun onRestoreInstanceState(state: Parcelable?) {
|
||||||
@@ -62,14 +58,12 @@ class KotatsuBottomNavigationView @JvmOverloads constructor(
|
|||||||
|
|
||||||
override fun setTranslationY(translationY: Float) {
|
override fun setTranslationY(translationY: Float) {
|
||||||
// Disallow translation change when state down
|
// Disallow translation change when state down
|
||||||
if (currentState == STATE_DOWN) return
|
if (currentState != STATE_DOWN) {
|
||||||
super.setTranslationY(translationY)
|
super.setTranslationY(translationY)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun show() {
|
||||||
* Shows this view up.
|
|
||||||
*/
|
|
||||||
fun slideUp() = post {
|
|
||||||
currentAnimator?.cancel()
|
currentAnimator?.cancel()
|
||||||
clearAnimation()
|
clearAnimation()
|
||||||
|
|
||||||
@@ -81,16 +75,17 @@ class KotatsuBottomNavigationView @JvmOverloads constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun hide() {
|
||||||
* Hides this view down. [setTranslationY] won't work until [slideUp] is called.
|
|
||||||
*/
|
|
||||||
fun slideDown() = post {
|
|
||||||
currentAnimator?.cancel()
|
currentAnimator?.cancel()
|
||||||
clearAnimation()
|
clearAnimation()
|
||||||
|
|
||||||
currentState = STATE_DOWN
|
currentState = STATE_DOWN
|
||||||
|
val target = measureHeight()
|
||||||
|
if (target == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
animateTranslation(
|
animateTranslation(
|
||||||
height.toFloat(),
|
target.toFloat(),
|
||||||
SLIDE_DOWN_ANIMATION_DURATION,
|
SLIDE_DOWN_ANIMATION_DURATION,
|
||||||
FastOutLinearInInterpolator(),
|
FastOutLinearInInterpolator(),
|
||||||
)
|
)
|
||||||
@@ -102,22 +97,26 @@ class KotatsuBottomNavigationView @JvmOverloads constructor(
|
|||||||
.setInterpolator(interpolator)
|
.setInterpolator(interpolator)
|
||||||
.setDuration(duration)
|
.setDuration(duration)
|
||||||
.applySystemAnimatorScale(context)
|
.applySystemAnimatorScale(context)
|
||||||
.setListener(object : AnimatorListenerAdapter() {
|
.setListener(
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
object : AnimatorListenerAdapter() {
|
||||||
currentAnimator = null
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
postInvalidate()
|
currentAnimator = null
|
||||||
}
|
postInvalidate()
|
||||||
},
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class SavedState : AbsSavedState {
|
internal class SavedState : BaseSavedState {
|
||||||
var currentState = STATE_UP
|
var currentState = STATE_UP
|
||||||
var translationY = 0F
|
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()
|
currentState = source.readInt()
|
||||||
translationY = source.readFloat()
|
translationY = source.readFloat()
|
||||||
}
|
}
|
||||||
@@ -129,28 +128,14 @@ class KotatsuBottomNavigationView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
@JvmField
|
@JvmField
|
||||||
val CREATOR: Parcelable.ClassLoaderCreator<SavedState> = object : Parcelable.ClassLoaderCreator<SavedState> {
|
val CREATOR: Parcelable.Creator<SavedState> = object : Parcelable.Creator<SavedState> {
|
||||||
override fun createFromParcel(source: Parcel, loader: ClassLoader): SavedState {
|
override fun createFromParcel(`in`: Parcel) = SavedState(`in`)
|
||||||
return SavedState(source, loader)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createFromParcel(source: Parcel): SavedState {
|
override fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)
|
||||||
return SavedState(source, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun newArray(size: Int): Array<SavedState> {
|
|
||||||
return newArray(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.koitharu.kotatsu.base.ui.widgets
|
package org.koitharu.kotatsu.base.ui.widgets
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
@@ -21,8 +20,7 @@ class WindowInsetHolder @JvmOverloads constructor(
|
|||||||
private var desiredHeight = 0
|
private var desiredHeight = 0
|
||||||
private var desiredWidth = 0
|
private var desiredWidth = 0
|
||||||
|
|
||||||
@SuppressLint("RtlHardcoded")
|
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
|
||||||
override fun dispatchApplyWindowInsets(insets: WindowInsets): WindowInsets {
|
|
||||||
val barsInsets = WindowInsetsCompat.toWindowInsetsCompat(insets, this)
|
val barsInsets = WindowInsetsCompat.toWindowInsetsCompat(insets, this)
|
||||||
.getInsets(WindowInsetsCompat.Type.systemBars())
|
.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
val gravity = getLayoutGravity()
|
val gravity = getLayoutGravity()
|
||||||
@@ -41,24 +39,26 @@ class WindowInsetHolder @JvmOverloads constructor(
|
|||||||
desiredHeight = newHeight
|
desiredHeight = newHeight
|
||||||
requestLayout()
|
requestLayout()
|
||||||
}
|
}
|
||||||
return super.dispatchApplyWindowInsets(insets)
|
return super.onApplyWindowInsets(insets)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
|
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
|
||||||
|
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
|
||||||
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
|
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
|
||||||
super.onMeasure(
|
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
|
||||||
if (desiredWidth == 0 || widthMode == MeasureSpec.EXACTLY) {
|
|
||||||
widthMeasureSpec
|
val width: Int = when (widthMode) {
|
||||||
} else {
|
MeasureSpec.EXACTLY -> widthSize
|
||||||
MeasureSpec.makeMeasureSpec(desiredWidth, widthMode)
|
MeasureSpec.AT_MOST -> minOf(desiredWidth, widthSize)
|
||||||
},
|
else -> desiredWidth
|
||||||
if (desiredHeight == 0 || heightMode == MeasureSpec.EXACTLY) {
|
}
|
||||||
heightMeasureSpec
|
val height = when (heightMode) {
|
||||||
} else {
|
MeasureSpec.EXACTLY -> heightSize
|
||||||
MeasureSpec.makeMeasureSpec(desiredHeight, heightMode)
|
MeasureSpec.AT_MOST -> minOf(desiredHeight, heightSize)
|
||||||
},
|
else -> desiredHeight
|
||||||
)
|
}
|
||||||
|
setMeasuredDimension(width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getLayoutGravity(): Int {
|
private fun getLayoutGravity(): Int {
|
||||||
@@ -69,4 +69,4 @@ class WindowInsetHolder @JvmOverloads constructor(
|
|||||||
else -> Gravity.NO_GRAVITY
|
else -> Gravity.NO_GRAVITY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,16 +201,11 @@ abstract class MangaListFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onWindowInsetsChanged(insets: Insets) {
|
override fun onWindowInsetsChanged(insets: Insets) {
|
||||||
binding.root.updatePadding(
|
|
||||||
left = insets.left,
|
|
||||||
right = insets.right,
|
|
||||||
)
|
|
||||||
binding.recyclerView.updatePadding(
|
binding.recyclerView.updatePadding(
|
||||||
bottom = insets.bottom,
|
bottom = insets.bottom,
|
||||||
)
|
)
|
||||||
binding.recyclerView.fastScroller.updateLayoutParams<MarginLayoutParams> {
|
binding.recyclerView.fastScroller.updateLayoutParams<MarginLayoutParams> {
|
||||||
bottomMargin = insets.bottom
|
bottomMargin = insets.bottom
|
||||||
marginEnd = insets.end(binding.recyclerView)
|
|
||||||
}
|
}
|
||||||
if (activity is MainActivity) {
|
if (activity is MainActivity) {
|
||||||
val headerHeight = (activity as? AppBarOwner)?.appBar?.measureHeight() ?: insets.top
|
val headerHeight = (activity as? AppBarOwner)?.appBar?.measureHeight() ?: insets.top
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package org.koitharu.kotatsu.main.ui
|
package org.koitharu.kotatsu.main.ui
|
||||||
|
|
||||||
import org.koitharu.kotatsu.base.ui.widgets.KotatsuBottomNavigationView
|
import org.koitharu.kotatsu.base.ui.widgets.SlidingBottomNavigationView
|
||||||
|
|
||||||
interface BottomNavOwner {
|
interface BottomNavOwner {
|
||||||
|
|
||||||
val bottomNav: KotatsuBottomNavigationView?
|
val bottomNav: SlidingBottomNavigationView?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import android.view.View
|
|||||||
import android.view.ViewGroup.MarginLayoutParams
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
import androidx.activity.result.ActivityResultCallback
|
import androidx.activity.result.ActivityResultCallback
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.IdRes
|
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.core.app.ActivityOptionsCompat
|
import androidx.core.app.ActivityOptionsCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
import androidx.core.util.size
|
import androidx.core.util.size
|
||||||
import androidx.core.view.*
|
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.R as materialR
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.appbar.AppBarLayout.LayoutParams.*
|
import com.google.android.material.appbar.AppBarLayout.LayoutParams.*
|
||||||
import com.google.android.material.navigation.NavigationBarView
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -29,11 +28,9 @@ import kotlinx.coroutines.withContext
|
|||||||
import kotlinx.coroutines.yield
|
import kotlinx.coroutines.yield
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||||
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
|
import org.koitharu.kotatsu.base.ui.widgets.SlidingBottomNavigationView
|
||||||
import org.koitharu.kotatsu.base.ui.widgets.KotatsuBottomNavigationView
|
|
||||||
import org.koitharu.kotatsu.databinding.ActivityMainBinding
|
import org.koitharu.kotatsu.databinding.ActivityMainBinding
|
||||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
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.library.ui.LibraryFragment
|
||||||
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
|
||||||
@@ -46,14 +43,11 @@ import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
|
|||||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
|
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
|
||||||
import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment
|
import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment
|
||||||
import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment
|
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.suggestions.ui.SuggestionsWorker
|
||||||
import org.koitharu.kotatsu.tracker.ui.FeedFragment
|
|
||||||
import org.koitharu.kotatsu.tracker.work.TrackWorker
|
import org.koitharu.kotatsu.tracker.work.TrackWorker
|
||||||
import org.koitharu.kotatsu.utils.VoiceInputContract
|
import org.koitharu.kotatsu.utils.VoiceInputContract
|
||||||
import org.koitharu.kotatsu.utils.ext.*
|
import org.koitharu.kotatsu.utils.ext.*
|
||||||
|
|
||||||
private const val TAG_PRIMARY = "primary"
|
|
||||||
private const val TAG_SEARCH = "search"
|
private const val TAG_SEARCH = "search"
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -64,25 +58,23 @@ class MainActivity :
|
|||||||
View.OnClickListener,
|
View.OnClickListener,
|
||||||
View.OnFocusChangeListener,
|
View.OnFocusChangeListener,
|
||||||
SearchSuggestionListener,
|
SearchSuggestionListener,
|
||||||
NavigationBarView.OnItemSelectedListener,
|
MainNavigationDelegate.OnFragmentChangedListener {
|
||||||
NavigationBarView.OnItemReselectedListener {
|
|
||||||
|
|
||||||
private val viewModel by viewModels<MainViewModel>()
|
private val viewModel by viewModels<MainViewModel>()
|
||||||
private val searchSuggestionViewModel by viewModels<SearchSuggestionViewModel>()
|
private val searchSuggestionViewModel by viewModels<SearchSuggestionViewModel>()
|
||||||
private val voiceInputLauncher = registerForActivityResult(VoiceInputContract(), VoiceInputCallback())
|
private val voiceInputLauncher = registerForActivityResult(VoiceInputContract(), VoiceInputCallback())
|
||||||
private lateinit var navBar: NavigationBarView
|
private lateinit var navigationDelegate: MainNavigationDelegate
|
||||||
|
|
||||||
override val appBar: AppBarLayout
|
override val appBar: AppBarLayout
|
||||||
get() = binding.appbar
|
get() = binding.appbar
|
||||||
|
|
||||||
override val bottomNav: KotatsuBottomNavigationView?
|
override val bottomNav: SlidingBottomNavigationView?
|
||||||
get() = binding.bottomNav
|
get() = binding.bottomNav
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(ActivityMainBinding.inflate(layoutInflater))
|
setContentView(ActivityMainBinding.inflate(layoutInflater))
|
||||||
|
|
||||||
navBar = checkNotNull(bottomNav ?: binding.navRail)
|
|
||||||
if (bottomNav != null) {
|
if (bottomNav != null) {
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
|
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
|
||||||
if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) {
|
if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) {
|
||||||
@@ -98,18 +90,17 @@ class MainActivity :
|
|||||||
onFocusChangeListener = this@MainActivity
|
onFocusChangeListener = this@MainActivity
|
||||||
searchSuggestionListener = 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.fab?.setOnClickListener(this)
|
||||||
binding.navRail?.headerView?.setOnClickListener(this)
|
binding.navRail?.headerView?.setOnClickListener(this)
|
||||||
binding.searchView.isVoiceSearchEnabled = voiceInputLauncher.resolve(this, null) != null
|
binding.searchView.isVoiceSearchEnabled = voiceInputLauncher.resolve(this, null) != null
|
||||||
|
|
||||||
onBackPressedDispatcher.addCallback(ExitCallback(this, binding.container))
|
onBackPressedDispatcher.addCallback(ExitCallback(this, binding.container))
|
||||||
supportFragmentManager.findFragmentByTag(TAG_PRIMARY)?.let {
|
navigationDelegate = MainNavigationDelegate(checkNotNull(bottomNav ?: binding.navRail), supportFragmentManager)
|
||||||
if (it is LibraryFragment) binding.fab?.show() else binding.fab?.hide()
|
navigationDelegate.addOnFragmentChangedListener(this)
|
||||||
} ?: onNavigationItemSelected(navBar.selectedItemId)
|
navigationDelegate.onCreate(savedInstanceState)
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
onFirstStart()
|
onFirstStart()
|
||||||
}
|
}
|
||||||
@@ -123,17 +114,7 @@ class MainActivity :
|
|||||||
|
|
||||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||||
super.onRestoreInstanceState(savedInstanceState)
|
super.onRestoreInstanceState(savedInstanceState)
|
||||||
val isSearchOpened = isSearchOpened()
|
adjustSearchUI(isSearchOpened(), animate = false)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
@@ -149,8 +130,15 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNavigationItemSelected(item: MenuItem): Boolean {
|
override fun onFragmentChanged(fragment: Fragment, fromUser: Boolean) {
|
||||||
return onNavigationItemSelected(item.itemId)
|
if (fragment is LibraryFragment) {
|
||||||
|
binding.fab?.show()
|
||||||
|
} else {
|
||||||
|
binding.fab?.hide()
|
||||||
|
}
|
||||||
|
if (fromUser) {
|
||||||
|
binding.appbar.setExpanded(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
@@ -248,36 +236,9 @@ class MainActivity :
|
|||||||
showNav(true)
|
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) {
|
private fun onOpenReader(manga: Manga) {
|
||||||
val options = binding.fab?.let {
|
val fab = binding.fab ?: binding.navRail?.headerView
|
||||||
|
val options = fab?.let {
|
||||||
scaleUpActivityOptionsOf(it).toBundle()
|
scaleUpActivityOptionsOf(it).toBundle()
|
||||||
}
|
}
|
||||||
startActivity(ReaderActivity.newIntent(this, manga), options)
|
startActivity(ReaderActivity.newIntent(this, manga), options)
|
||||||
@@ -292,17 +253,7 @@ class MainActivity :
|
|||||||
repeat(counters.size) { i ->
|
repeat(counters.size) { i ->
|
||||||
val id = counters.keyAt(i)
|
val id = counters.keyAt(i)
|
||||||
val counter = counters.valueAt(i)
|
val counter = counters.valueAt(i)
|
||||||
if (counter == 0) {
|
navigationDelegate.setCounter(id, counter)
|
||||||
navBar.getBadge(id)?.isVisible = false
|
|
||||||
} else {
|
|
||||||
val badge = navBar.getOrCreateBadge(id)
|
|
||||||
if (counter < 0) {
|
|
||||||
badge.clearNumber()
|
|
||||||
} else {
|
|
||||||
badge.number = counter
|
|
||||||
}
|
|
||||||
badge.isVisible = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,55 +265,28 @@ class MainActivity :
|
|||||||
adjustFabVisibility(isResumeEnabled = isEnabled)
|
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() {
|
private fun onSearchOpened() {
|
||||||
TransitionManager.beginDelayedTransition(binding.appbar)
|
adjustSearchUI(isOpened = true, animate = true)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSearchClosed() {
|
private fun onSearchClosed() {
|
||||||
binding.searchView.hideKeyboard()
|
binding.searchView.hideKeyboard()
|
||||||
TransitionManager.beginDelayedTransition(binding.appbar)
|
adjustSearchUI(isOpened = false, animate = true)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showNav(visible: Boolean) {
|
private fun showNav(visible: Boolean) {
|
||||||
bottomNav?.run {
|
bottomNav?.run {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
slideUp()
|
show()
|
||||||
} else {
|
} else {
|
||||||
slideDown()
|
hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.navRail?.isVisible = visible
|
binding.navRail?.isVisible = visible
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isSearchOpened(): Boolean {
|
private fun isSearchOpened(): Boolean {
|
||||||
return supportFragmentManager.findFragmentByTag(TAG_SEARCH)?.isVisible == true
|
return supportFragmentManager.findFragmentByTag(TAG_SEARCH) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onFirstStart() {
|
private fun onFirstStart() {
|
||||||
@@ -382,7 +306,7 @@ class MainActivity :
|
|||||||
|
|
||||||
private fun adjustFabVisibility(
|
private fun adjustFabVisibility(
|
||||||
isResumeEnabled: Boolean = viewModel.isResumeEnabled.value == true,
|
isResumeEnabled: Boolean = viewModel.isResumeEnabled.value == true,
|
||||||
topFragment: Fragment? = supportFragmentManager.findFragmentByTag(TAG_PRIMARY),
|
topFragment: Fragment? = navigationDelegate.primaryFragment,
|
||||||
isSearchOpened: Boolean = isSearchOpened(),
|
isSearchOpened: Boolean = isSearchOpened(),
|
||||||
) {
|
) {
|
||||||
val fab = binding.fab
|
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?> {
|
private inner class VoiceInputCallback : ActivityResultCallback<String?> {
|
||||||
|
|
||||||
override fun onActivityResult(result: String?) {
|
override fun onActivityResult(result: String?) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import android.view.Menu
|
|||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
@@ -53,6 +54,7 @@ class MultiSearchActivity :
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(ActivitySearchMultiBinding.inflate(layoutInflater))
|
setContentView(ActivitySearchMultiBinding.inflate(layoutInflater))
|
||||||
|
window.statusBarColor = ContextCompat.getColor(this, R.color.dim_statusbar)
|
||||||
|
|
||||||
val itemCLickListener = object : OnListItemClickListener<MultiSearchListModel> {
|
val itemCLickListener = object : OnListItemClickListener<MultiSearchListModel> {
|
||||||
override fun onItemClick(item: MultiSearchListModel, view: View) {
|
override fun onItemClick(item: MultiSearchListModel, view: View) {
|
||||||
|
|||||||
@@ -1,28 +1,51 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="horizontal"
|
||||||
tools:context=".main.ui.MainActivity">
|
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_width="match_parent"
|
||||||
android:layout_height="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:id="@+id/appbar"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fitsSystemWindows="true"
|
android:clipToPadding="false"
|
||||||
|
android:fitsSystemWindows="false"
|
||||||
android:paddingLeft="16dp"
|
android:paddingLeft="16dp"
|
||||||
android:paddingRight="16dp"
|
android:paddingRight="16dp"
|
||||||
android:clipToPadding="false"
|
|
||||||
android:stateListAnimator="@null"
|
android:stateListAnimator="@null"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:liftOnScroll="false">
|
||||||
app:layout_constraintStart_toEndOf="@id/navRail"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
<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
|
<FrameLayout
|
||||||
android:id="@+id/toolbar_card"
|
android:id="@+id/toolbar_card"
|
||||||
@@ -30,7 +53,8 @@
|
|||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:layout_marginVertical="8dp"
|
android:layout_marginVertical="8dp"
|
||||||
android:background="@drawable/toolbar_background"
|
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
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@id/toolbar"
|
android:id="@id/toolbar"
|
||||||
@@ -51,7 +75,7 @@
|
|||||||
android:background="@null"
|
android:background="@null"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:hint="@string/search_manga"
|
android:hint="@string/search_manga"
|
||||||
android:imeOptions="actionSearch"
|
android:imeOptions="actionSearch|flagNoFullscreen"
|
||||||
android:importantForAutofill="no"
|
android:importantForAutofill="no"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
tools:drawableEnd="@drawable/abc_ic_clear_material" />
|
tools:drawableEnd="@drawable/abc_ic_clear_material" />
|
||||||
@@ -60,28 +84,8 @@
|
|||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
</com.google.android.material.appbar.KotatsuAppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<com.google.android.material.navigationrail.NavigationRailView
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
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.fragment.app.FragmentContainerView
|
</LinearLayout>
|
||||||
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>
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
@@ -14,14 +14,22 @@
|
|||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||||
tools:layout="@layout/fragment_list" />
|
tools:layout="@layout/fragment_list" />
|
||||||
|
|
||||||
<com.google.android.material.appbar.KotatsuAppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/appbar"
|
android:id="@+id/appbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:fitsSystemWindows="true"
|
android:fitsSystemWindows="false"
|
||||||
android:paddingHorizontal="16dp"
|
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
|
<FrameLayout
|
||||||
android:id="@+id/toolbar_card"
|
android:id="@+id/toolbar_card"
|
||||||
@@ -60,7 +68,7 @@
|
|||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
</com.google.android.material.appbar.KotatsuAppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
android:id="@+id/fab"
|
android:id="@+id/fab"
|
||||||
@@ -68,6 +76,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:layout_marginBottom="-4dp"
|
android:layout_marginBottom="-4dp"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
android:text="@string/_continue"
|
android:text="@string/_continue"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:backgroundTint="?attr/colorContainer"
|
app:backgroundTint="?attr/colorContainer"
|
||||||
@@ -75,12 +84,11 @@
|
|||||||
app:layout_anchor="@id/bottomNav"
|
app:layout_anchor="@id/bottomNav"
|
||||||
app:layout_anchorGravity="top|end"
|
app:layout_anchorGravity="top|end"
|
||||||
app:layout_behavior="org.koitharu.kotatsu.base.ui.util.ShrinkOnScrollBehavior"
|
app:layout_behavior="org.koitharu.kotatsu.base.ui.util.ShrinkOnScrollBehavior"
|
||||||
app:layout_insetEdge="bottom"
|
|
||||||
app:layout_dodgeInsetEdges="bottom"
|
app:layout_dodgeInsetEdges="bottom"
|
||||||
android:paddingBottom="8dp"
|
app:layout_insetEdge="bottom"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<org.koitharu.kotatsu.base.ui.widgets.KotatsuBottomNavigationView
|
<org.koitharu.kotatsu.base.ui.widgets.SlidingBottomNavigationView
|
||||||
android:id="@+id/bottomNav"
|
android:id="@+id/bottomNav"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -90,4 +98,4 @@
|
|||||||
app:menu="@menu/nav_bottom"
|
app:menu="@menu/nav_bottom"
|
||||||
tools:ignore="KeyboardInaccessibleWidget" />
|
tools:ignore="KeyboardInaccessibleWidget" />
|
||||||
|
|
||||||
</org.koitharu.kotatsu.base.ui.widgets.KotatsuCoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<com.google.android.material.appbar.KotatsuAppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/appbar"
|
android:id="@+id/appbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
</com.google.android.material.appbar.MaterialToolbar>
|
</com.google.android.material.appbar.MaterialToolbar>
|
||||||
|
|
||||||
</com.google.android.material.appbar.KotatsuAppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@id/container"
|
android:id="@id/container"
|
||||||
@@ -37,4 +37,4 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||||
|
|
||||||
</org.koitharu.kotatsu.base.ui.widgets.KotatsuCoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<com.google.android.material.appbar.KotatsuAppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/appbar"
|
android:id="@+id/appbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fitsSystemWindows="true"
|
android:fitsSystemWindows="false"
|
||||||
app:elevation="0dp">
|
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
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@id/toolbar"
|
android:id="@id/toolbar"
|
||||||
@@ -18,7 +25,7 @@
|
|||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
app:layout_scrollFlags="scroll|enterAlways" />
|
app:layout_scrollFlags="scroll|enterAlways" />
|
||||||
|
|
||||||
</com.google.android.material.appbar.KotatsuAppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@id/recyclerView"
|
android:id="@id/recyclerView"
|
||||||
@@ -28,4 +35,4 @@
|
|||||||
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" />
|
||||||
|
|
||||||
</org.koitharu.kotatsu.base.ui.widgets.KotatsuCoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/railFab"
|
android:id="@+id/railFab"
|
||||||
android:theme="@style/ThemeOverlay.Material3.FloatingActionButton.Tertiary"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:srcCompat="@drawable/ic_read" />
|
android:contentDescription="@string/_continue"
|
||||||
|
app:srcCompat="@drawable/ic_read" />
|
||||||
|
|||||||
@@ -8,5 +8,6 @@
|
|||||||
<color name="scrollbar">#66FFFFFF</color>
|
<color name="scrollbar">#66FFFFFF</color>
|
||||||
<color name="selector_foreground">#29FFFFFF</color>
|
<color name="selector_foreground">#29FFFFFF</color>
|
||||||
<color name="divider_default">#1FFFFFFF</color>
|
<color name="divider_default">#1FFFFFFF</color>
|
||||||
|
<color name="dim_statusbar">#99000000</color>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -18,8 +18,9 @@
|
|||||||
<color name="grey">#424242</color>
|
<color name="grey">#424242</color>
|
||||||
<color name="grey_dark">#212121</color>
|
<color name="grey_dark">#212121</color>
|
||||||
<color name="dim">#99000000</color>
|
<color name="dim">#99000000</color>
|
||||||
|
<color name="dim_statusbar">#99FFFFFF</color>
|
||||||
<color name="scrollbar">#66000000</color>
|
<color name="scrollbar">#66000000</color>
|
||||||
<color name="selector_foreground">#29000000</color>
|
<color name="selector_foreground">#29000000</color>
|
||||||
<color name="divider_default">#1F000000</color>
|
<color name="divider_default">#1F000000</color>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<integer name="config_defaultAnimTime">300</integer>
|
<integer name="config_defaultAnimTime">300</integer>
|
||||||
|
<integer name="config_shorterAnimTime">150</integer>
|
||||||
<integer name="config_tinyAnimTime">50</integer>
|
<integer name="config_tinyAnimTime">50</integer>
|
||||||
|
|
||||||
<integer name="manga_badge_max_character_count">3</integer>
|
<integer name="manga_badge_max_character_count">3</integer>
|
||||||
|
|||||||
Reference in New Issue
Block a user