mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-26 06:43:45 +03:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
999ba3e592
14 changed files with 279 additions and 226 deletions
|
@ -47,7 +47,7 @@ class KomgaApi(private val client: OkHttpClient) {
|
|||
track.apply {
|
||||
cover_url = "$url/thumbnail"
|
||||
tracking_url = url
|
||||
total_chapters = progress.booksCount
|
||||
total_chapters = progress.maxNumberSort.toInt()
|
||||
status = when (progress.booksCount) {
|
||||
progress.booksUnreadCount -> Komga.UNREAD
|
||||
progress.booksReadCount -> Komga.COMPLETED
|
||||
|
|
|
@ -91,7 +91,8 @@ data class ReadProgressDto(
|
|||
booksReadCount,
|
||||
booksUnreadCount,
|
||||
booksInProgressCount,
|
||||
lastReadContinuousIndex.toFloat()
|
||||
lastReadContinuousIndex.toFloat(),
|
||||
booksCount.toFloat(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -102,4 +103,5 @@ data class ReadProgressV2Dto(
|
|||
val booksUnreadCount: Int,
|
||||
val booksInProgressCount: Int,
|
||||
val lastReadContinuousNumberSort: Float,
|
||||
val maxNumberSort: Float,
|
||||
)
|
||||
|
|
|
@ -102,10 +102,8 @@ import eu.kanade.tachiyomi.util.view.snack
|
|||
import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.recyclerview.scrollEvents
|
||||
import reactivecircus.flowbinding.recyclerview.scrollStateChanges
|
||||
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
|
||||
import rx.schedulers.Schedulers
|
||||
|
@ -200,6 +198,16 @@ class AnimeController :
|
|||
|
||||
private val db: AnimeDatabaseHelper = Injekt.get()
|
||||
|
||||
/**
|
||||
* For [recyclerViewUpdatesToolbarTitleAlpha]
|
||||
*/
|
||||
private var recyclerViewToolbarTitleAlphaUpdaterAdded = false
|
||||
private val recyclerViewToolbarTitleAlphaUpdater = object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
updateToolbarTitleAlpha()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
@ -210,15 +218,12 @@ class AnimeController :
|
|||
|
||||
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
||||
super.onChangeStarted(handler, type)
|
||||
|
||||
// Hide toolbar title on enter
|
||||
if (type.isEnter) {
|
||||
updateToolbarTitleAlpha()
|
||||
} else if (!type.isPush) {
|
||||
// Cancel listeners early
|
||||
viewScope.cancel()
|
||||
updateToolbarTitleAlpha(1F)
|
||||
// No need to update alpha for cover dialog
|
||||
if (dialog == null) {
|
||||
updateToolbarTitleAlpha(if (type.isEnter) 0F else 1F)
|
||||
}
|
||||
recyclerViewUpdatesToolbarTitleAlpha(type.isEnter)
|
||||
}
|
||||
|
||||
override fun onChangeEnded(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
||||
|
@ -269,19 +274,15 @@ class AnimeController :
|
|||
binding.fullRecycler?.let {
|
||||
it.adapter = ConcatAdapter(animeInfoAdapter, episodesHeaderAdapter, episodesAdapter)
|
||||
|
||||
it.scrollEvents()
|
||||
.onEach { updateToolbarTitleAlpha() }
|
||||
.launchIn(viewScope)
|
||||
|
||||
// Skips directly to chapters list if navigated to from the library
|
||||
it.post {
|
||||
if (!fromSource && preferences.jumpToChapters()) {
|
||||
(it.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(1, 0)
|
||||
}
|
||||
|
||||
// Delayed in case we need to jump to chapters
|
||||
it.post {
|
||||
updateToolbarTitleAlpha()
|
||||
val mainActivityAppBar = (activity as? MainActivity)?.binding?.appbar
|
||||
(it.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
|
||||
1,
|
||||
mainActivityAppBar?.height ?: 0
|
||||
)
|
||||
mainActivityAppBar?.isLifted = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -318,22 +319,10 @@ class AnimeController :
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tablet layout
|
||||
binding.infoRecycler?.let {
|
||||
it.adapter = animeInfoAdapter
|
||||
|
||||
it.scrollEvents()
|
||||
.onEach { updateToolbarTitleAlpha() }
|
||||
.launchIn(viewScope)
|
||||
|
||||
// Delayed in case we need to jump to episodes
|
||||
it.post {
|
||||
updateToolbarTitleAlpha()
|
||||
}
|
||||
}
|
||||
binding.chaptersRecycler?.let {
|
||||
it.adapter = ConcatAdapter(episodesHeaderAdapter, episodesAdapter)
|
||||
}
|
||||
binding.infoRecycler?.adapter = animeInfoAdapter
|
||||
binding.chaptersRecycler?.adapter = ConcatAdapter(episodesHeaderAdapter, episodesAdapter)
|
||||
|
||||
episodesAdapter?.fastScroller = binding.fastScroller
|
||||
|
||||
|
@ -358,6 +347,20 @@ class AnimeController :
|
|||
trackSheet = TrackSheet(this, anime!!, (activity as MainActivity).supportFragmentManager)
|
||||
|
||||
updateFilterIconState()
|
||||
recyclerViewUpdatesToolbarTitleAlpha(true)
|
||||
}
|
||||
|
||||
private fun recyclerViewUpdatesToolbarTitleAlpha(enable: Boolean) {
|
||||
val recycler = binding.fullRecycler ?: binding.infoRecycler ?: return
|
||||
if (enable) {
|
||||
if (!recyclerViewToolbarTitleAlphaUpdaterAdded) {
|
||||
recycler.addOnScrollListener(recyclerViewToolbarTitleAlphaUpdater)
|
||||
recyclerViewToolbarTitleAlphaUpdaterAdded = true
|
||||
}
|
||||
} else if (recyclerViewToolbarTitleAlphaUpdaterAdded) {
|
||||
recycler.removeOnScrollListener(recyclerViewToolbarTitleAlphaUpdater)
|
||||
recyclerViewToolbarTitleAlphaUpdaterAdded = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateToolbarTitleAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float? = null) {
|
||||
|
@ -418,6 +421,7 @@ class AnimeController :
|
|||
}
|
||||
|
||||
override fun onDestroyView(view: View) {
|
||||
recyclerViewUpdatesToolbarTitleAlpha(false)
|
||||
destroyActionModeIfNeeded()
|
||||
binding.actionToolbar.destroy()
|
||||
animeInfoAdapter = null
|
||||
|
|
|
@ -383,7 +383,7 @@ class AnimelibController(
|
|||
actionMode!!,
|
||||
R.menu.library_selection
|
||||
) { onActionItemClicked(it!!) }
|
||||
(activity as? MainActivity)?.showBottomNav(visible = false, expand = true)
|
||||
(activity as? MainActivity)?.showBottomNav(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -492,7 +492,7 @@ class AnimelibController(
|
|||
selectionRelay.call(AnimelibSelectionEvent.Cleared())
|
||||
|
||||
binding.actionToolbar.hide()
|
||||
(activity as? MainActivity)?.showBottomNav(visible = true, expand = true)
|
||||
(activity as? MainActivity)?.showBottomNav(true)
|
||||
|
||||
actionMode = null
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ class DownloadTabsController() :
|
|||
override fun createBinding(inflater: LayoutInflater) = PagerControllerBinding.inflate(inflater)
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
(activity as? MainActivity)?.showBottomNav(visible = false, expand = true)
|
||||
(activity as? MainActivity)?.showBottomNav(false)
|
||||
super.onViewCreated(view)
|
||||
|
||||
adapter = DownloadTabsAdapter()
|
||||
|
@ -42,7 +42,7 @@ class DownloadTabsController() :
|
|||
override fun onDestroyView(view: View) {
|
||||
super.onDestroyView(view)
|
||||
adapter = null
|
||||
(activity as? MainActivity)?.showBottomNav(visible = true, expand = true)
|
||||
(activity as? MainActivity)?.showBottomNav(true)
|
||||
}
|
||||
|
||||
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
||||
|
|
|
@ -383,7 +383,7 @@ class LibraryController(
|
|||
actionMode!!,
|
||||
R.menu.library_selection
|
||||
) { onActionItemClicked(it!!) }
|
||||
(activity as? MainActivity)?.showBottomNav(visible = false, expand = true)
|
||||
(activity as? MainActivity)?.showBottomNav(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -492,7 +492,7 @@ class LibraryController(
|
|||
selectionRelay.call(LibrarySelectionEvent.Cleared())
|
||||
|
||||
binding.actionToolbar.hide()
|
||||
(activity as? MainActivity)?.showBottomNav(visible = true, expand = true)
|
||||
(activity as? MainActivity)?.showBottomNav(true)
|
||||
|
||||
actionMode = null
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import android.view.Gravity
|
|||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.core.splashscreen.SplashScreen
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
|
@ -68,7 +67,6 @@ import eu.kanade.tachiyomi.util.system.dpToPx
|
|||
import eu.kanade.tachiyomi.util.system.isTablet
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
|
||||
import eu.kanade.tachiyomi.widget.HideBottomNavigationOnScrollBehavior
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
@ -93,8 +91,6 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
|||
}
|
||||
}
|
||||
|
||||
private var bottomNavAnimator: ViewHeightAnimator? = null
|
||||
|
||||
private var isConfirmingExit: Boolean = false
|
||||
private var isHandlingShortcut: Boolean = false
|
||||
|
||||
|
@ -145,15 +141,6 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
|||
}
|
||||
setSplashScreenExitAnimation(splashScreen)
|
||||
|
||||
if (binding.bottomNav != null) {
|
||||
bottomNavAnimator = ViewHeightAnimator(binding.bottomNav!!)
|
||||
|
||||
// Set behavior of bottom nav
|
||||
preferences.hideBottomBarOnScroll()
|
||||
.asImmediateFlow { setBottomNavBehaviorOnScroll() }
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
if (binding.sideNav != null) {
|
||||
preferences.sideNavIconAlignment()
|
||||
.asImmediateFlow {
|
||||
|
@ -579,11 +566,11 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
|||
binding.appbar.setExpanded(true)
|
||||
|
||||
if ((from == null || from is RootController) && to !is RootController) {
|
||||
showNav(visible = false, expand = true)
|
||||
showNav(false)
|
||||
}
|
||||
if (to is RootController) {
|
||||
// Always show bottom nav again when returning to a RootController
|
||||
showNav(visible = true, expand = from !is RootController)
|
||||
showNav(true)
|
||||
}
|
||||
|
||||
if (from is TabbedController) {
|
||||
|
@ -634,27 +621,22 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun showNav(visible: Boolean, expand: Boolean = false) {
|
||||
showBottomNav(visible, expand)
|
||||
private fun showNav(visible: Boolean) {
|
||||
showBottomNav(visible)
|
||||
showSideNav(visible)
|
||||
}
|
||||
|
||||
// Also used from some controllers to swap bottom nav with action toolbar
|
||||
fun showBottomNav(visible: Boolean, expand: Boolean = false) {
|
||||
fun showBottomNav(visible: Boolean) {
|
||||
if (visible) {
|
||||
binding.bottomNav?.translationY = 0F
|
||||
if (expand) {
|
||||
bottomNavAnimator?.expand()
|
||||
}
|
||||
binding.bottomNav?.slideUp()
|
||||
} else {
|
||||
bottomNavAnimator?.collapse()
|
||||
binding.bottomNav?.slideDown()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSideNav(visible: Boolean) {
|
||||
binding.sideNav?.let {
|
||||
it.isVisible = visible
|
||||
}
|
||||
binding.sideNav?.isVisible = visible
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -669,18 +651,6 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun setBottomNavBehaviorOnScroll() {
|
||||
showNav(visible = true)
|
||||
|
||||
binding.bottomNav?.updateLayoutParams<CoordinatorLayout.LayoutParams> {
|
||||
behavior = when {
|
||||
preferences.hideBottomBarOnScroll().get() -> HideBottomNavigationOnScrollBehavior()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
binding.bottomNav?.translationY = 0F
|
||||
}
|
||||
|
||||
private val nav: NavigationBarView
|
||||
get() = binding.bottomNav ?: binding.sideNav!!
|
||||
|
||||
|
|
|
@ -1,107 +0,0 @@
|
|||
package eu.kanade.tachiyomi.ui.main
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import androidx.annotation.Keep
|
||||
|
||||
class ViewHeightAnimator(val view: View, val duration: Long = 250L) {
|
||||
|
||||
/**
|
||||
* The default height of the view. It's unknown until the view is layout.
|
||||
*/
|
||||
private var height = 0
|
||||
|
||||
/**
|
||||
* Whether the last state of the view is shown or hidden.
|
||||
*/
|
||||
private var isLastStateShown = true
|
||||
|
||||
/**
|
||||
* Animation used to expand and collapse the view.
|
||||
*/
|
||||
private val animation by lazy {
|
||||
ObjectAnimator.ofInt(this, "height", height).apply {
|
||||
duration = this@ViewHeightAnimator.duration
|
||||
interpolator = DecelerateInterpolator()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
view.viewTreeObserver.addOnGlobalLayoutListener(
|
||||
object : ViewTreeObserver.OnGlobalLayoutListener {
|
||||
override fun onGlobalLayout() {
|
||||
if (view.height > 0) {
|
||||
view.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
|
||||
// Save the tabs default height.
|
||||
height = view.height
|
||||
|
||||
// Now that we know the height, set the initial height.
|
||||
if (isLastStateShown) {
|
||||
setHeight(height)
|
||||
} else {
|
||||
setHeight(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the height of the tab layout.
|
||||
*
|
||||
* @param newHeight The new height of the tab layout.
|
||||
*/
|
||||
@Keep
|
||||
fun setHeight(newHeight: Int) {
|
||||
view.layoutParams.height = newHeight
|
||||
view.requestLayout()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height of the tab layout. This method is also called from the animator through
|
||||
* reflection.
|
||||
*/
|
||||
fun getHeight(): Int {
|
||||
return view.layoutParams.height
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands the tab layout with an animation.
|
||||
*/
|
||||
fun expand() {
|
||||
if (isMeasured) {
|
||||
if (getHeight() != height) {
|
||||
animation.setIntValues(height)
|
||||
animation.start()
|
||||
} else {
|
||||
animation.cancel()
|
||||
}
|
||||
}
|
||||
isLastStateShown = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse the tab layout with an animation.
|
||||
*/
|
||||
fun collapse() {
|
||||
if (isMeasured) {
|
||||
if (getHeight() != 0) {
|
||||
animation.setIntValues(0)
|
||||
animation.start()
|
||||
} else {
|
||||
animation.cancel()
|
||||
}
|
||||
}
|
||||
isLastStateShown = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the tab layout has a known height.
|
||||
*/
|
||||
private val isMeasured: Boolean
|
||||
get() = height > 0
|
||||
}
|
|
@ -94,10 +94,8 @@ import eu.kanade.tachiyomi.util.view.getCoordinates
|
|||
import eu.kanade.tachiyomi.util.view.shrinkOnScroll
|
||||
import eu.kanade.tachiyomi.util.view.snack
|
||||
import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.recyclerview.scrollEvents
|
||||
import reactivecircus.flowbinding.recyclerview.scrollStateChanges
|
||||
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
|
||||
import timber.log.Timber
|
||||
|
@ -183,6 +181,16 @@ class MangaController :
|
|||
|
||||
private var dialog: MangaFullCoverDialog? = null
|
||||
|
||||
/**
|
||||
* For [recyclerViewUpdatesToolbarTitleAlpha]
|
||||
*/
|
||||
private var recyclerViewToolbarTitleAlphaUpdaterAdded = false
|
||||
private val recyclerViewToolbarTitleAlphaUpdater = object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
updateToolbarTitleAlpha()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
@ -193,15 +201,12 @@ class MangaController :
|
|||
|
||||
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
||||
super.onChangeStarted(handler, type)
|
||||
|
||||
// Hide toolbar title on enter
|
||||
if (type.isEnter) {
|
||||
updateToolbarTitleAlpha()
|
||||
} else if (!type.isPush) {
|
||||
// Cancel listeners early
|
||||
viewScope.cancel()
|
||||
updateToolbarTitleAlpha(1F)
|
||||
// No need to update alpha for cover dialog
|
||||
if (dialog == null) {
|
||||
updateToolbarTitleAlpha(if (type.isEnter) 0F else 1F)
|
||||
}
|
||||
recyclerViewUpdatesToolbarTitleAlpha(type.isEnter)
|
||||
}
|
||||
|
||||
override fun onChangeEnded(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
||||
|
@ -252,19 +257,15 @@ class MangaController :
|
|||
binding.fullRecycler?.let {
|
||||
it.adapter = ConcatAdapter(mangaInfoAdapter, chaptersHeaderAdapter, chaptersAdapter)
|
||||
|
||||
it.scrollEvents()
|
||||
.onEach { updateToolbarTitleAlpha() }
|
||||
.launchIn(viewScope)
|
||||
|
||||
// Skips directly to chapters list if navigated to from the library
|
||||
it.post {
|
||||
if (!fromSource && preferences.jumpToChapters()) {
|
||||
(it.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(1, 0)
|
||||
}
|
||||
|
||||
// Delayed in case we need to jump to chapters
|
||||
it.post {
|
||||
updateToolbarTitleAlpha()
|
||||
val mainActivityAppBar = (activity as? MainActivity)?.binding?.appbar
|
||||
(it.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
|
||||
1,
|
||||
mainActivityAppBar?.height ?: 0
|
||||
)
|
||||
mainActivityAppBar?.isLifted = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -301,22 +302,10 @@ class MangaController :
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tablet layout
|
||||
binding.infoRecycler?.let {
|
||||
it.adapter = mangaInfoAdapter
|
||||
|
||||
it.scrollEvents()
|
||||
.onEach { updateToolbarTitleAlpha() }
|
||||
.launchIn(viewScope)
|
||||
|
||||
// Delayed in case we need to jump to chapters
|
||||
it.post {
|
||||
updateToolbarTitleAlpha()
|
||||
}
|
||||
}
|
||||
binding.chaptersRecycler?.let {
|
||||
it.adapter = ConcatAdapter(chaptersHeaderAdapter, chaptersAdapter)
|
||||
}
|
||||
binding.infoRecycler?.adapter = mangaInfoAdapter
|
||||
binding.chaptersRecycler?.adapter = ConcatAdapter(chaptersHeaderAdapter, chaptersAdapter)
|
||||
|
||||
chaptersAdapter?.fastScroller = binding.fastScroller
|
||||
|
||||
|
@ -341,6 +330,20 @@ class MangaController :
|
|||
trackSheet = TrackSheet(this, manga!!, (activity as MainActivity).supportFragmentManager)
|
||||
|
||||
updateFilterIconState()
|
||||
recyclerViewUpdatesToolbarTitleAlpha(true)
|
||||
}
|
||||
|
||||
private fun recyclerViewUpdatesToolbarTitleAlpha(enable: Boolean) {
|
||||
val recycler = binding.fullRecycler ?: binding.infoRecycler ?: return
|
||||
if (enable) {
|
||||
if (!recyclerViewToolbarTitleAlphaUpdaterAdded) {
|
||||
recycler.addOnScrollListener(recyclerViewToolbarTitleAlphaUpdater)
|
||||
recyclerViewToolbarTitleAlphaUpdaterAdded = true
|
||||
}
|
||||
} else if (recyclerViewToolbarTitleAlphaUpdaterAdded) {
|
||||
recycler.removeOnScrollListener(recyclerViewToolbarTitleAlphaUpdater)
|
||||
recyclerViewToolbarTitleAlphaUpdaterAdded = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateToolbarTitleAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float? = null) {
|
||||
|
@ -401,6 +404,7 @@ class MangaController :
|
|||
}
|
||||
|
||||
override fun onDestroyView(view: View) {
|
||||
recyclerViewUpdatesToolbarTitleAlpha(false)
|
||||
destroyActionModeIfNeeded()
|
||||
binding.actionToolbar.destroy()
|
||||
mangaInfoAdapter = null
|
||||
|
|
|
@ -180,7 +180,7 @@ class AnimeUpdatesController :
|
|||
actionMode!!,
|
||||
R.menu.updates_episode_selection
|
||||
) { onActionItemClicked(it!!) }
|
||||
(activity as? MainActivity)?.showBottomNav(visible = false, expand = true)
|
||||
(activity as? MainActivity)?.showBottomNav(false)
|
||||
}
|
||||
|
||||
toggleSelection(position)
|
||||
|
@ -405,7 +405,7 @@ class AnimeUpdatesController :
|
|||
adapter?.clearSelection()
|
||||
|
||||
binding.actionToolbar.hide()
|
||||
(activity as? MainActivity)?.showBottomNav(visible = true, expand = true)
|
||||
(activity as? MainActivity)?.showBottomNav(true)
|
||||
|
||||
actionMode = null
|
||||
}
|
||||
|
|
|
@ -180,7 +180,7 @@ class UpdatesController :
|
|||
actionMode!!,
|
||||
R.menu.updates_chapter_selection
|
||||
) { onActionItemClicked(it!!) }
|
||||
(activity as? MainActivity)?.showBottomNav(visible = false, expand = true)
|
||||
(activity as? MainActivity)?.showBottomNav(false)
|
||||
}
|
||||
|
||||
toggleSelection(position)
|
||||
|
@ -386,7 +386,7 @@ class UpdatesController :
|
|||
adapter?.clearSelection()
|
||||
|
||||
binding.actionToolbar.hide()
|
||||
(activity as? MainActivity)?.showBottomNav(visible = true, expand = true)
|
||||
(activity as? MainActivity)?.showBottomNav(true)
|
||||
|
||||
actionMode = null
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
import android.content.Context
|
||||
import android.view.ViewPropertyAnimator
|
||||
import android.view.animation.Animation
|
||||
import androidx.constraintlayout.motion.widget.MotionScene.Transition
|
||||
|
||||
|
@ -14,3 +15,8 @@ fun Transition.applySystemAnimatorScale(context: Context) {
|
|||
// End layout of cover expanding animation tends to break when the transition is less than ~25ms
|
||||
this.duration = (this.duration * context.animatorDurationScale).toInt().coerceAtLeast(25)
|
||||
}
|
||||
|
||||
/** Scale the duration of this [ViewPropertyAnimator] by [Context.animatorDurationScale] */
|
||||
fun ViewPropertyAnimator.applySystemAnimatorScale(context: Context): ViewPropertyAnimator = apply {
|
||||
this.duration = (this.duration * context.animatorDurationScale).toLong()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
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.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.view.doOnLayout
|
||||
import androidx.core.view.doOnNextLayout
|
||||
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.bottomnavigation.BottomNavigationView
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
|
||||
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
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
|
||||
|
||||
init {
|
||||
// Hide on scroll
|
||||
doOnLayout {
|
||||
findViewTreeLifecycleOwner()?.lifecycleScope?.let { scope ->
|
||||
Injekt.get<PreferencesHelper>().hideBottomBarOnScroll()
|
||||
.asImmediateFlow {
|
||||
updateLayoutParams<CoordinatorLayout.LayoutParams> {
|
||||
behavior = if (it) {
|
||||
HideBottomNavigationOnScrollBehavior()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
.launchIn(scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(): Parcelable {
|
||||
val superState = super.onSaveInstanceState()
|
||||
return SavedState(superState).also {
|
||||
it.currentState = currentState
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(state: Parcelable?) {
|
||||
if (state is SavedState) {
|
||||
super.onRestoreInstanceState(state.superState)
|
||||
doOnNextLayout {
|
||||
if (state.currentState == STATE_UP) {
|
||||
slideUp(animate = false)
|
||||
} else if (state.currentState == STATE_DOWN) {
|
||||
slideDown(animate = false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
super.onRestoreInstanceState(state)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setTranslationY(translationY: Float) {
|
||||
// Disallow translation change when state down
|
||||
if (currentState == STATE_DOWN) return
|
||||
super.setTranslationY(translationY)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows this view up.
|
||||
*
|
||||
* @param animate True if slide up should be animated
|
||||
*/
|
||||
fun slideUp(animate: Boolean = true) {
|
||||
currentAnimator?.cancel()
|
||||
clearAnimation()
|
||||
|
||||
currentState = STATE_UP
|
||||
animateTranslation(
|
||||
0F,
|
||||
if (animate) SLIDE_UP_ANIMATION_DURATION else 0,
|
||||
LinearOutSlowInInterpolator()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides this view down. [setTranslationY] won't work until [slideUp] is called.
|
||||
*
|
||||
* @param animate True if slide down should be animated
|
||||
*/
|
||||
fun slideDown(animate: Boolean = true) {
|
||||
currentAnimator?.cancel()
|
||||
clearAnimation()
|
||||
|
||||
currentState = STATE_DOWN
|
||||
animateTranslation(
|
||||
height.toFloat(),
|
||||
if (animate) SLIDE_DOWN_ANIMATION_DURATION else 0,
|
||||
FastOutLinearInInterpolator()
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
constructor(superState: Parcelable) : super(superState)
|
||||
|
||||
constructor(source: Parcel, loader: ClassLoader?) : super(source, loader) {
|
||||
currentState = source.readByte().toInt()
|
||||
}
|
||||
|
||||
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||
super.writeToParcel(out, flags)
|
||||
out.writeByte(currentState.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -75,7 +75,7 @@
|
|||
android:id="@+id/fab_layout"
|
||||
layout="@layout/main_activity_fab" />
|
||||
|
||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
<eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
||||
android:id="@+id/bottom_nav"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
Loading…
Reference in a new issue