mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-12 20:19:05 +01:00
@@ -1,196 +0,0 @@
|
||||
package eu.kanade.tachiyomi.widget
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.TimeInterpolator
|
||||
import android.content.Context
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.util.AttributeSet
|
||||
import android.view.ViewPropertyAnimator
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.max
|
||||
import androidx.customview.view.AbsSavedState
|
||||
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
|
||||
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
|
||||
import eu.kanade.tachiyomi.util.system.pxToDp
|
||||
import kotlin.math.max
|
||||
|
||||
class TachiyomiBottomNavigationView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = R.attr.bottomNavigationStyle,
|
||||
defStyleRes: Int = R.style.Widget_Design_BottomNavigationView,
|
||||
) : BottomNavigationView(context, attrs, defStyleAttr, defStyleRes) {
|
||||
|
||||
private var currentAnimator: ViewPropertyAnimator? = null
|
||||
|
||||
private var currentState = STATE_UP
|
||||
|
||||
override fun onSaveInstanceState(): Parcelable {
|
||||
val superState = super.onSaveInstanceState()
|
||||
return SavedState(superState).also {
|
||||
it.currentState = currentState
|
||||
it.translationY = translationY
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(state: Parcelable?) {
|
||||
if (state is SavedState) {
|
||||
super.onRestoreInstanceState(state.superState)
|
||||
super.setTranslationY(state.translationY)
|
||||
currentState = state.currentState
|
||||
} else {
|
||||
super.onRestoreInstanceState(state)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setTranslationY(translationY: Float) {
|
||||
// Disallow translation change when state down
|
||||
if (currentState == STATE_DOWN) return
|
||||
super.setTranslationY(translationY)
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
bottomNavPadding = h.pxToDp.dp
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows this view up.
|
||||
*/
|
||||
fun slideUp() = post {
|
||||
currentAnimator?.cancel()
|
||||
clearAnimation()
|
||||
|
||||
currentState = STATE_UP
|
||||
animateTranslation(
|
||||
0F,
|
||||
SLIDE_UP_ANIMATION_DURATION,
|
||||
LinearOutSlowInInterpolator(),
|
||||
)
|
||||
bottomNavPadding = height.pxToDp.dp
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides this view down. [setTranslationY] won't work until [slideUp] is called.
|
||||
*/
|
||||
fun slideDown() = post {
|
||||
currentAnimator?.cancel()
|
||||
clearAnimation()
|
||||
|
||||
currentState = STATE_DOWN
|
||||
animateTranslation(
|
||||
height.toFloat(),
|
||||
SLIDE_DOWN_ANIMATION_DURATION,
|
||||
FastOutLinearInInterpolator(),
|
||||
)
|
||||
bottomNavPadding = 0.dp
|
||||
}
|
||||
|
||||
private fun animateTranslation(targetY: Float, duration: Long, interpolator: TimeInterpolator) {
|
||||
currentAnimator = animate()
|
||||
.translationY(targetY)
|
||||
.setInterpolator(interpolator)
|
||||
.setDuration(duration)
|
||||
.applySystemAnimatorScale(context)
|
||||
.setListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
currentAnimator = null
|
||||
postInvalidate()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
internal class SavedState : AbsSavedState {
|
||||
var currentState = STATE_UP
|
||||
var translationY = 0F
|
||||
|
||||
constructor(superState: Parcelable) : super(superState)
|
||||
|
||||
constructor(source: Parcel, loader: ClassLoader?) : super(source, loader) {
|
||||
currentState = source.readInt()
|
||||
translationY = source.readFloat()
|
||||
}
|
||||
|
||||
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||
super.writeToParcel(out, flags)
|
||||
out.writeInt(currentState)
|
||||
out.writeFloat(translationY)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
private var bottomNavPadding by mutableStateOf(0.dp)
|
||||
|
||||
/**
|
||||
* Merges [bottomNavPadding] to the origin's [PaddingValues] bottom side.
|
||||
*/
|
||||
@ReadOnlyComposable
|
||||
@Composable
|
||||
fun withBottomNavPadding(origin: PaddingValues = PaddingValues()): PaddingValues {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
return PaddingValues(
|
||||
start = origin.calculateStartPadding(layoutDirection),
|
||||
top = origin.calculateTopPadding(),
|
||||
end = origin.calculateEndPadding(layoutDirection),
|
||||
bottom = max(origin.calculateBottomPadding(), bottomNavPadding),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see withBottomNavPadding
|
||||
*/
|
||||
@ReadOnlyComposable
|
||||
@Composable
|
||||
fun withBottomNavInset(origin: WindowInsets): WindowInsets {
|
||||
val density = LocalDensity.current
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
return WindowInsets(
|
||||
left = origin.getLeft(density, layoutDirection),
|
||||
top = origin.getTop(density),
|
||||
right = origin.getRight(density, layoutDirection),
|
||||
bottom = max(origin.getBottom(density), with(density) { bottomNavPadding.roundToPx() }),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package eu.kanade.tachiyomi.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import com.bluelinelabs.conductor.ChangeHandlerFrameLayout
|
||||
|
||||
/**
|
||||
* [ChangeHandlerFrameLayout] with the ability to draw behind the header sibling in [CoordinatorLayout].
|
||||
* The layout behavior of this view is set to [TachiyomiScrollingViewBehavior] and should not be changed.
|
||||
*/
|
||||
class TachiyomiChangeHandlerFrameLayout(
|
||||
context: Context,
|
||||
attrs: AttributeSet,
|
||||
) : ChangeHandlerFrameLayout(context, attrs), CoordinatorLayout.AttachedBehavior {
|
||||
|
||||
/**
|
||||
* If true, this view will draw behind the header sibling.
|
||||
*
|
||||
* @see TachiyomiScrollingViewBehavior.shouldHeaderOverlap
|
||||
*/
|
||||
var overlapHeader = false
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
(layoutParams as? CoordinatorLayout.LayoutParams)?.behavior = behavior.apply {
|
||||
shouldHeaderOverlap = value
|
||||
}
|
||||
if (!value) {
|
||||
// The behavior doesn't reset translationY when shouldHeaderOverlap is false
|
||||
translationY = 0F
|
||||
}
|
||||
forceLayout()
|
||||
}
|
||||
}
|
||||
|
||||
fun enableScrollingBehavior(enable: Boolean) {
|
||||
(layoutParams as? CoordinatorLayout.LayoutParams)?.behavior = if (enable) {
|
||||
behavior.apply {
|
||||
shouldHeaderOverlap = overlapHeader
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (!enable) {
|
||||
// The behavior doesn't reset translationY when shouldHeaderOverlap is false
|
||||
translationY = 0F
|
||||
}
|
||||
forceLayout()
|
||||
}
|
||||
|
||||
override fun getBehavior() = TachiyomiScrollingViewBehavior()
|
||||
}
|
||||
Reference in New Issue
Block a user