mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-28 09:15:12 +03:00
Edge-to-edge manga details view (#5613)
* Prepare for edge-to-edge MangaController * Fix derpy liftToScroll with our own implementation * Edge-to-edge MangaController Except when legacy blue theme is used. * Save app bar lift state for controller backstack * Fix expanded cover position after the view recycled * Handle overlap changes when incognito mode disabled * Tablet fixes * Revert "Handle overlap changes when incognito mode disabled" This reverts commit 1f492449 Breaks on rotation changes. * Fix MangaController's swipe refresh position * All controllers are now doing lift app bar on scroll by default They are already doing that before so this pretty much just a cleanups. * TachiyomiCoordinatorLayout: Support ViewPager for app bar lift state check I'm willing to revert this if this minute detail solution is deemed too hacky xD * Fix app bar not lifted when scrolled without fling * Save app bar lift state across configuration changes * Fix MangaController's swipe refresh position after configuration change * TachiyomiCoordinatorLayout: Update ViewPager reference when controller is changed
This commit is contained in:
parent
914b686c8e
commit
da16110e1c
20 changed files with 490 additions and 90 deletions
|
@ -7,6 +7,7 @@ import androidx.core.net.toUri
|
||||||
import com.bluelinelabs.conductor.Controller
|
import com.bluelinelabs.conductor.Controller
|
||||||
import com.bluelinelabs.conductor.Router
|
import com.bluelinelabs.conductor.Router
|
||||||
import com.bluelinelabs.conductor.RouterTransaction
|
import com.bluelinelabs.conductor.RouterTransaction
|
||||||
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
|
||||||
fun Router.popControllerWithTag(tag: String): Boolean {
|
fun Router.popControllerWithTag(tag: String): Boolean {
|
||||||
|
@ -41,3 +42,10 @@ fun Controller.openInBrowser(url: String) {
|
||||||
activity?.toast(e.message)
|
activity?.toast(e.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns [MainActivity]'s app bar height
|
||||||
|
*/
|
||||||
|
fun Controller.getMainAppBarHeight(): Int {
|
||||||
|
return (activity as? MainActivity)?.binding?.appbar?.measuredHeight ?: 0
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
package eu.kanade.tachiyomi.ui.base.controller
|
package eu.kanade.tachiyomi.ui.base.controller
|
||||||
|
|
||||||
interface NoToolbarElevationController
|
interface NoAppBarElevationController
|
|
@ -1,3 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.base.controller
|
|
||||||
|
|
||||||
interface ToolbarLiftOnScrollController
|
|
|
@ -34,7 +34,6 @@ import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.getPreferenceKey
|
import eu.kanade.tachiyomi.source.getPreferenceKey
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.ToolbarLiftOnScrollController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
|
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.util.preference.DSL
|
import eu.kanade.tachiyomi.util.preference.DSL
|
||||||
|
@ -49,8 +48,7 @@ import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
class ExtensionDetailsController(bundle: Bundle? = null) :
|
class ExtensionDetailsController(bundle: Bundle? = null) :
|
||||||
NucleusController<ExtensionDetailControllerBinding, ExtensionDetailsPresenter>(bundle),
|
NucleusController<ExtensionDetailControllerBinding, ExtensionDetailsPresenter>(bundle) {
|
||||||
ToolbarLiftOnScrollController {
|
|
||||||
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
|
|
@ -245,12 +245,7 @@ class LibraryController(
|
||||||
}
|
}
|
||||||
tabsVisibilitySubscription?.unsubscribe()
|
tabsVisibilitySubscription?.unsubscribe()
|
||||||
tabsVisibilitySubscription = tabsVisibilityRelay.subscribe { visible ->
|
tabsVisibilitySubscription = tabsVisibilityRelay.subscribe { visible ->
|
||||||
val tabAnimator = (activity as? MainActivity)?.tabAnimator
|
tabs.isVisible = visible
|
||||||
if (visible) {
|
|
||||||
tabAnimator?.expand()
|
|
||||||
} else {
|
|
||||||
tabAnimator?.collapse()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
mangaCountVisibilitySubscription?.unsubscribe()
|
mangaCountVisibilitySubscription?.unsubscribe()
|
||||||
mangaCountVisibilitySubscription = mangaCountVisibilityRelay.subscribe {
|
mangaCountVisibilitySubscription = mangaCountVisibilityRelay.subscribe {
|
||||||
|
|
|
@ -34,6 +34,7 @@ import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.Migrations
|
import eu.kanade.tachiyomi.Migrations
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
||||||
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
|
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
||||||
|
@ -42,10 +43,9 @@ import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
||||||
import eu.kanade.tachiyomi.ui.base.activity.BaseViewBindingActivity
|
import eu.kanade.tachiyomi.ui.base.activity.BaseViewBindingActivity
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.FabController
|
import eu.kanade.tachiyomi.ui.base.controller.FabController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
|
import eu.kanade.tachiyomi.ui.base.controller.NoAppBarElevationController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
|
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.ToolbarLiftOnScrollController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.ui.browse.BrowseController
|
import eu.kanade.tachiyomi.ui.browse.BrowseController
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||||
|
@ -61,6 +61,7 @@ import eu.kanade.tachiyomi.ui.setting.SettingsMainController
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
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.system.toast
|
||||||
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
|
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
|
||||||
import eu.kanade.tachiyomi.widget.HideBottomNavigationOnScrollBehavior
|
import eu.kanade.tachiyomi.widget.HideBottomNavigationOnScrollBehavior
|
||||||
|
@ -85,7 +86,6 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lateinit var tabAnimator: ViewHeightAnimator
|
|
||||||
private var bottomNavAnimator: ViewHeightAnimator? = null
|
private var bottomNavAnimator: ViewHeightAnimator? = null
|
||||||
|
|
||||||
private var isConfirmingExit: Boolean = false
|
private var isConfirmingExit: Boolean = false
|
||||||
|
@ -93,6 +93,11 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
||||||
|
|
||||||
private var fixedViewsToBottom = mutableMapOf<View, AppBarLayout.OnOffsetChangedListener>()
|
private var fixedViewsToBottom = mutableMapOf<View, AppBarLayout.OnOffsetChangedListener>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App bar lift state for backstack
|
||||||
|
*/
|
||||||
|
private val backstackLiftState = mutableMapOf<String, Boolean>()
|
||||||
|
|
||||||
// To be checked by splash screen. If true then splash screen will be removed.
|
// To be checked by splash screen. If true then splash screen will be removed.
|
||||||
var ready = false
|
var ready = false
|
||||||
|
|
||||||
|
@ -117,11 +122,6 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
||||||
|
|
||||||
// Draw edge-to-edge
|
// Draw edge-to-edge
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
binding.appbar.applyInsetter {
|
|
||||||
type(navigationBars = true, statusBars = true) {
|
|
||||||
padding(left = true, top = true, right = true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
binding.fabLayout.rootFab.applyInsetter {
|
binding.fabLayout.rootFab.applyInsetter {
|
||||||
type(navigationBars = true) {
|
type(navigationBars = true) {
|
||||||
margin()
|
margin()
|
||||||
|
@ -140,8 +140,6 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
||||||
}
|
}
|
||||||
setSplashScreenExitAnimation(splashScreen)
|
setSplashScreenExitAnimation(splashScreen)
|
||||||
|
|
||||||
tabAnimator = ViewHeightAnimator(binding.tabs, 0L)
|
|
||||||
|
|
||||||
if (binding.bottomNav != null) {
|
if (binding.bottomNav != null) {
|
||||||
bottomNavAnimator = ViewHeightAnimator(binding.bottomNav!!)
|
bottomNavAnimator = ViewHeightAnimator(binding.bottomNav!!)
|
||||||
|
|
||||||
|
@ -218,7 +216,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
||||||
container: ViewGroup,
|
container: ViewGroup,
|
||||||
handler: ControllerChangeHandler
|
handler: ControllerChangeHandler
|
||||||
) {
|
) {
|
||||||
syncActivityViewWithController(to, from)
|
syncActivityViewWithController(to, from, isPush)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onChangeCompleted(
|
override fun onChangeCompleted(
|
||||||
|
@ -504,7 +502,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
||||||
router.setRoot(controller.withFadeTransaction().tag(id.toString()))
|
router.setRoot(controller.withFadeTransaction().tag(id.toString()))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun syncActivityViewWithController(to: Controller?, from: Controller? = null) {
|
private fun syncActivityViewWithController(to: Controller?, from: Controller? = null, isPush: Boolean = true) {
|
||||||
if (from is DialogController || to is DialogController) {
|
if (from is DialogController || to is DialogController) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -529,12 +527,11 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
||||||
from.cleanupTabs(binding.tabs)
|
from.cleanupTabs(binding.tabs)
|
||||||
}
|
}
|
||||||
if (to is TabbedController) {
|
if (to is TabbedController) {
|
||||||
tabAnimator.expand()
|
|
||||||
to.configureTabs(binding.tabs)
|
to.configureTabs(binding.tabs)
|
||||||
} else {
|
} else {
|
||||||
tabAnimator.collapse()
|
|
||||||
binding.tabs.setupWithViewPager(null)
|
binding.tabs.setupWithViewPager(null)
|
||||||
}
|
}
|
||||||
|
binding.tabs.isVisible = to is TabbedController
|
||||||
|
|
||||||
if (from is FabController) {
|
if (from is FabController) {
|
||||||
binding.fabLayout.rootFab.isVisible = false
|
binding.fabLayout.rootFab.isVisible = false
|
||||||
|
@ -545,16 +542,32 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
||||||
to.configureFab(binding.fabLayout.rootFab)
|
to.configureFab(binding.fabLayout.rootFab)
|
||||||
}
|
}
|
||||||
|
|
||||||
when (to) {
|
if (!isTablet()) {
|
||||||
is NoToolbarElevationController -> {
|
// Save lift state
|
||||||
binding.appbar.disableElevation()
|
if (isPush) {
|
||||||
}
|
if (router.backstackSize > 1) {
|
||||||
is ToolbarLiftOnScrollController -> {
|
// Save lift state
|
||||||
binding.appbar.enableElevation(true)
|
from?.let {
|
||||||
}
|
backstackLiftState[it.instanceId] = binding.appbar.isLifted
|
||||||
else -> {
|
}
|
||||||
binding.appbar.enableElevation(false)
|
} else {
|
||||||
|
backstackLiftState.clear()
|
||||||
|
}
|
||||||
|
binding.appbar.isLifted = false
|
||||||
|
} else {
|
||||||
|
to?.let {
|
||||||
|
binding.appbar.isLifted = backstackLiftState.getOrElse(it.instanceId) { false }
|
||||||
|
}
|
||||||
|
from?.let {
|
||||||
|
backstackLiftState.remove(it.instanceId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.root.isLiftAppBarOnScroll = to !is NoAppBarElevationController
|
||||||
|
|
||||||
|
binding.appbar.isTransparentWhenNotLifted = to is MangaController &&
|
||||||
|
preferences.appTheme().get() != PreferenceValues.AppTheme.BLUE
|
||||||
|
binding.controllerContainer.overlapHeader = to is MangaController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,16 +13,21 @@ import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.FloatRange
|
import androidx.annotation.FloatRange
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
|
import androidx.core.view.doOnLayout
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
import coil.imageLoader
|
import coil.imageLoader
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||||
|
@ -51,7 +56,7 @@ import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.FabController
|
import eu.kanade.tachiyomi.ui.base.controller.FabController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.ToolbarLiftOnScrollController
|
import eu.kanade.tachiyomi.ui.base.controller.getMainAppBarHeight
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
|
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||||
|
@ -89,6 +94,7 @@ import eu.kanade.tachiyomi.util.view.snack
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import reactivecircus.flowbinding.recyclerview.scrollEvents
|
import reactivecircus.flowbinding.recyclerview.scrollEvents
|
||||||
|
import reactivecircus.flowbinding.recyclerview.scrollStateChanges
|
||||||
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
|
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
|
@ -99,7 +105,6 @@ import kotlin.math.min
|
||||||
|
|
||||||
class MangaController :
|
class MangaController :
|
||||||
NucleusController<MangaControllerBinding, MangaPresenter>,
|
NucleusController<MangaControllerBinding, MangaPresenter>,
|
||||||
ToolbarLiftOnScrollController,
|
|
||||||
FabController,
|
FabController,
|
||||||
ActionMode.Callback,
|
ActionMode.Callback,
|
||||||
FlexibleAdapter.OnItemClickListener,
|
FlexibleAdapter.OnItemClickListener,
|
||||||
|
@ -254,6 +259,37 @@ class MangaController :
|
||||||
updateToolbarTitleAlpha()
|
updateToolbarTitleAlpha()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it.scrollStateChanges()
|
||||||
|
.onEach { _ ->
|
||||||
|
// Disable swipe refresh when view is not at the top
|
||||||
|
val firstPos = (it.layoutManager as LinearLayoutManager)
|
||||||
|
.findFirstCompletelyVisibleItemPosition()
|
||||||
|
binding.swipeRefresh.isEnabled = firstPos <= 0
|
||||||
|
}
|
||||||
|
.launchIn(viewScope)
|
||||||
|
|
||||||
|
binding.fastScroller.doOnLayout { scroller ->
|
||||||
|
scroller.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
topMargin = getMainAppBarHeight()
|
||||||
|
}
|
||||||
|
scroller.applyInsetter {
|
||||||
|
type(navigationBars = true) {
|
||||||
|
margin()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.swipeRefresh.doOnLayout { swipeRefresh ->
|
||||||
|
swipeRefresh as SwipeRefreshLayout
|
||||||
|
swipeRefresh.setOnApplyWindowInsetsListener { _, windowInsets ->
|
||||||
|
val topStatusBarInset = WindowInsetsCompat.toWindowInsetsCompat(windowInsets)
|
||||||
|
.getInsets(WindowInsetsCompat.Type.statusBars())
|
||||||
|
.top
|
||||||
|
swipeRefresh.setProgressViewEndTarget(false, getMainAppBarHeight() + topStatusBarInset)
|
||||||
|
windowInsets
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Tablet layout
|
// Tablet layout
|
||||||
binding.infoRecycler?.let {
|
binding.infoRecycler?.let {
|
||||||
|
|
|
@ -3,7 +3,9 @@ package eu.kanade.tachiyomi.ui.manga.info
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import coil.loadAny
|
import coil.loadAny
|
||||||
import coil.target.ImageViewTarget
|
import coil.target.ImageViewTarget
|
||||||
|
@ -16,6 +18,7 @@ import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.getMainAppBarHeight
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import eu.kanade.tachiyomi.util.view.setChips
|
import eu.kanade.tachiyomi.util.view.setChips
|
||||||
|
@ -47,6 +50,7 @@ class MangaInfoHeaderAdapter(
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
|
||||||
binding = MangaInfoHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
binding = MangaInfoHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
updateCoverPosition()
|
||||||
return HeaderViewHolder(binding.root)
|
return HeaderViewHolder(binding.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +79,15 @@ class MangaInfoHeaderAdapter(
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateCoverPosition() {
|
||||||
|
val appBarHeight = controller.getMainAppBarHeight()
|
||||||
|
binding.mangaCover.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
topMargin += appBarHeight
|
||||||
|
}
|
||||||
|
binding.root.getConstraintSet(R.id.end)
|
||||||
|
?.setMargin(R.id.manga_cover, ConstraintLayout.LayoutParams.TOP, appBarHeight)
|
||||||
|
}
|
||||||
|
|
||||||
inner class HeaderViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
|
inner class HeaderViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
|
||||||
fun bind() {
|
fun bind() {
|
||||||
// For rounded corners
|
// For rounded corners
|
||||||
|
|
|
@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
|
import eu.kanade.tachiyomi.ui.base.controller.NoAppBarElevationController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
|
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.ui.more.licenses.LicensesController
|
import eu.kanade.tachiyomi.ui.more.licenses.LicensesController
|
||||||
|
@ -25,7 +25,7 @@ import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
|
|
||||||
class AboutController : SettingsController(), NoToolbarElevationController {
|
class AboutController : SettingsController(), NoAppBarElevationController {
|
||||||
|
|
||||||
private val updateChecker by lazy { AppUpdateChecker() }
|
private val updateChecker by lazy { AppUpdateChecker() }
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
|
import eu.kanade.tachiyomi.ui.base.controller.NoAppBarElevationController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
import eu.kanade.tachiyomi.ui.category.CategoryController
|
||||||
|
@ -41,7 +41,7 @@ import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
||||||
class MoreController :
|
class MoreController :
|
||||||
SettingsController(),
|
SettingsController(),
|
||||||
RootController,
|
RootController,
|
||||||
NoToolbarElevationController {
|
NoAppBarElevationController {
|
||||||
|
|
||||||
private val downloadManager: DownloadManager by injectLazy()
|
private val downloadManager: DownloadManager by injectLazy()
|
||||||
private var isDownloading: Boolean = false
|
private var isDownloading: Boolean = false
|
||||||
|
|
|
@ -9,6 +9,7 @@ import android.view.Gravity
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.MenuRes
|
import androidx.annotation.MenuRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
@ -16,8 +17,11 @@ import androidx.appcompat.view.menu.MenuBuilder
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.appcompat.widget.TooltipCompat
|
import androidx.appcompat.widget.TooltipCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.children
|
||||||
|
import androidx.core.view.descendants
|
||||||
import androidx.core.view.forEach
|
import androidx.core.view.forEach
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewpager.widget.ViewPager
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.chip.ChipGroup
|
import com.google.android.material.chip.ChipGroup
|
||||||
|
@ -214,3 +218,40 @@ fun RecyclerView.onAnimationsFinished(callback: (RecyclerView) -> Unit) = post(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this ViewGroup's first child of specified class
|
||||||
|
*/
|
||||||
|
inline fun <reified T> ViewGroup.findChild(): T? {
|
||||||
|
return children.find { it is T } as? T
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this ViewGroup's first descendant of specified class
|
||||||
|
*/
|
||||||
|
inline fun <reified T> ViewGroup.findDescendant(): T? {
|
||||||
|
return descendants.find { it is T } as? T
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the active child view of a ViewPager according to the LayoutParams
|
||||||
|
*/
|
||||||
|
fun ViewPager.getActivePageView(): View? {
|
||||||
|
if (null == adapter || adapter?.count == 0 || childCount == 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val positionField = ViewPager.LayoutParams::class.java.getDeclaredField("position")
|
||||||
|
positionField.isAccessible = true
|
||||||
|
return children.find { child ->
|
||||||
|
val layoutParams = child.layoutParams as ViewPager.LayoutParams
|
||||||
|
try {
|
||||||
|
if (!layoutParams.isDecor && positionField.getInt(layoutParams) == currentItem) {
|
||||||
|
return@find true
|
||||||
|
}
|
||||||
|
} catch (e: NoSuchFieldException) {
|
||||||
|
} catch (e: IllegalAccessException) {
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,47 +1,87 @@
|
||||||
package eu.kanade.tachiyomi.widget
|
package eu.kanade.tachiyomi.widget
|
||||||
|
|
||||||
import android.animation.ObjectAnimator
|
import android.animation.ValueAnimator
|
||||||
import android.animation.StateListAnimator
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import com.google.android.material.R
|
import com.google.android.material.animation.AnimationUtils
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
class ElevationAppBarLayout @JvmOverloads constructor(
|
class ElevationAppBarLayout @JvmOverloads constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null
|
attrs: AttributeSet? = null
|
||||||
) : AppBarLayout(context, attrs) {
|
) : AppBarLayout(context, attrs) {
|
||||||
|
|
||||||
private var origStateAnimator: StateListAnimator? = null
|
private var lifted = true
|
||||||
|
private var transparent = false
|
||||||
|
|
||||||
init {
|
private val toolbar by lazy { findViewById<MaterialToolbar>(R.id.toolbar) }
|
||||||
origStateAnimator = stateListAnimator
|
|
||||||
|
private var elevationAnimator: ValueAnimator? = null
|
||||||
|
private var backgroundAlphaAnimator: ValueAnimator? = null
|
||||||
|
|
||||||
|
var isTransparentWhenNotLifted = false
|
||||||
|
set(value) {
|
||||||
|
if (field != value) {
|
||||||
|
field = value
|
||||||
|
updateBackgroundAlpha()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disabled. Lift on scroll is handled manually with [TachiyomiCoordinatorLayout]
|
||||||
|
*/
|
||||||
|
override fun isLiftOnScroll(): Boolean = false
|
||||||
|
|
||||||
|
override fun isLifted(): Boolean = lifted
|
||||||
|
|
||||||
|
override fun setLifted(lifted: Boolean): Boolean {
|
||||||
|
return if (this.lifted != lifted) {
|
||||||
|
this.lifted = lifted
|
||||||
|
val from = elevation
|
||||||
|
val to = if (lifted) {
|
||||||
|
resources.getDimension(R.dimen.design_appbar_elevation)
|
||||||
|
} else {
|
||||||
|
0F
|
||||||
|
}
|
||||||
|
|
||||||
|
elevationAnimator?.cancel()
|
||||||
|
elevationAnimator = ValueAnimator.ofFloat(from, to).apply {
|
||||||
|
duration = resources.getInteger(R.integer.app_bar_elevation_anim_duration).toLong()
|
||||||
|
interpolator = AnimationUtils.LINEAR_INTERPOLATOR
|
||||||
|
addUpdateListener {
|
||||||
|
elevation = it.animatedValue as Float
|
||||||
|
}
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBackgroundAlpha()
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun enableElevation(liftOnScroll: Boolean) {
|
private fun updateBackgroundAlpha() {
|
||||||
setElevation(liftOnScroll)
|
val newTransparent = if (lifted) false else isTransparentWhenNotLifted
|
||||||
}
|
if (transparent != newTransparent) {
|
||||||
|
transparent = newTransparent
|
||||||
|
val fromAlpha = if (transparent) 255 else 0
|
||||||
|
val toAlpha = if (transparent) 0 else 255
|
||||||
|
|
||||||
private fun setElevation(liftOnScroll: Boolean) {
|
backgroundAlphaAnimator?.cancel()
|
||||||
stateListAnimator = origStateAnimator
|
backgroundAlphaAnimator = ValueAnimator.ofInt(fromAlpha, toAlpha).apply {
|
||||||
isLiftOnScroll = liftOnScroll
|
duration = resources.getInteger(R.integer.app_bar_elevation_anim_duration).toLong()
|
||||||
}
|
interpolator = AnimationUtils.LINEAR_INTERPOLATOR
|
||||||
|
addUpdateListener {
|
||||||
fun disableElevation() {
|
val alpha = it.animatedValue as Int
|
||||||
stateListAnimator = StateListAnimator().apply {
|
background.alpha = alpha
|
||||||
val objAnimator = ObjectAnimator.ofFloat(this, "elevation", 0f)
|
toolbar?.background?.alpha = alpha
|
||||||
|
statusBarForeground?.alpha = alpha
|
||||||
// Enabled and collapsible, but not collapsed means not elevated
|
}
|
||||||
addState(
|
start()
|
||||||
intArrayOf(android.R.attr.enabled, R.attr.state_collapsible, -R.attr.state_collapsed),
|
}
|
||||||
objAnimator
|
|
||||||
)
|
|
||||||
|
|
||||||
// Default enabled state
|
|
||||||
addState(intArrayOf(android.R.attr.enabled), objAnimator)
|
|
||||||
|
|
||||||
// Disabled state
|
|
||||||
addState(IntArray(0), objAnimator)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.widget
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.viewpager.widget.ViewPager
|
||||||
import com.nightlynexus.viewstatepageradapter.ViewStatePagerAdapter
|
import com.nightlynexus.viewstatepageradapter.ViewStatePagerAdapter
|
||||||
import java.util.Stack
|
import java.util.Stack
|
||||||
|
|
||||||
|
@ -22,7 +23,11 @@ abstract class RecyclerViewPagerAdapter : ViewStatePagerAdapter() {
|
||||||
protected open fun recycleView(view: View, position: Int) {}
|
protected open fun recycleView(view: View, position: Int) {}
|
||||||
|
|
||||||
override fun createView(container: ViewGroup, position: Int): View {
|
override fun createView(container: ViewGroup, position: Int): View {
|
||||||
val view = if (pool.isNotEmpty()) pool.pop() else createView(container)
|
val view = if (pool.isNotEmpty()) {
|
||||||
|
pool.pop().setViewPagerPositionParam(position)
|
||||||
|
} else {
|
||||||
|
createView(container)
|
||||||
|
}
|
||||||
bindView(view, position)
|
bindView(view, position)
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
@ -31,4 +36,25 @@ abstract class RecyclerViewPagerAdapter : ViewStatePagerAdapter() {
|
||||||
recycleView(view, position)
|
recycleView(view, position)
|
||||||
if (recycle) pool.push(view)
|
if (recycle) pool.push(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Making sure that this ViewPager child view has the correct "position" layout param
|
||||||
|
* after being recycled.
|
||||||
|
*/
|
||||||
|
private fun View.setViewPagerPositionParam(position: Int): View {
|
||||||
|
val params = layoutParams
|
||||||
|
if (params is ViewPager.LayoutParams) {
|
||||||
|
if (!params.isDecor) {
|
||||||
|
try {
|
||||||
|
val positionField = ViewPager.LayoutParams::class.java.getDeclaredField("position")
|
||||||
|
positionField.isAccessible = true
|
||||||
|
positionField.setInt(params, position)
|
||||||
|
layoutParams = params
|
||||||
|
} catch (e: NoSuchFieldException) {
|
||||||
|
} catch (e: IllegalAccessException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBehavior() = TachiyomiScrollingViewBehavior()
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
package eu.kanade.tachiyomi.widget
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.coordinatorlayout.R
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
import androidx.core.view.doOnLayout
|
||||||
|
import androidx.customview.view.AbsSavedState
|
||||||
|
import androidx.lifecycle.coroutineScope
|
||||||
|
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewpager.widget.ViewPager
|
||||||
|
import com.bluelinelabs.conductor.ChangeHandlerFrameLayout
|
||||||
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
import eu.kanade.tachiyomi.util.system.isTablet
|
||||||
|
import eu.kanade.tachiyomi.util.view.findChild
|
||||||
|
import eu.kanade.tachiyomi.util.view.findDescendant
|
||||||
|
import eu.kanade.tachiyomi.util.view.getActivePageView
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import reactivecircus.flowbinding.android.view.HierarchyChangeEvent
|
||||||
|
import reactivecircus.flowbinding.android.view.hierarchyChangeEvents
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [CoordinatorLayout] with its own app bar lift state handler.
|
||||||
|
* This parent view checks for the app bar lift state from the following:
|
||||||
|
*
|
||||||
|
* 1. When nested scroll detected, lift state will be decided from the nested
|
||||||
|
* scroll target. (See [onNestedScroll])
|
||||||
|
*
|
||||||
|
* 2. When a descendant ViewPager active page is changed and the page contains RecyclerView,
|
||||||
|
* lift state will be decided from the said RecyclerView. (See [pageChangeListener])
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* With those conditions, this view expects the following direct child:
|
||||||
|
*
|
||||||
|
* 1. An [AppBarLayout].
|
||||||
|
*
|
||||||
|
* 2. A [ChangeHandlerFrameLayout] that contains an optional [ViewPager].
|
||||||
|
*/
|
||||||
|
class TachiyomiCoordinatorLayout @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = R.attr.coordinatorLayoutStyle
|
||||||
|
) : CoordinatorLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep lifted state and do nothing on tablet UI
|
||||||
|
*/
|
||||||
|
private val isTablet = context.isTablet()
|
||||||
|
|
||||||
|
private var appBarLayout: AppBarLayout? = null
|
||||||
|
private var viewPager: ViewPager? = null
|
||||||
|
set(value) {
|
||||||
|
field?.removeOnPageChangeListener(pageChangeListener)
|
||||||
|
field = value
|
||||||
|
field?.addOnPageChangeListener(pageChangeListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val pageChangeListener = object : ViewPager.SimpleOnPageChangeListener() {
|
||||||
|
override fun onPageScrollStateChanged(state: Int) {
|
||||||
|
// Wait until idle to make sure all the views laid out properly before checked
|
||||||
|
if (canLiftAppBarOnScroll && state == ViewPager.SCROLL_STATE_IDLE) {
|
||||||
|
appBarLayout?.isLifted = (viewPager?.getActivePageView() as? ViewGroup)
|
||||||
|
?.findDescendant<RecyclerView>()
|
||||||
|
?.canScrollVertically(-1) ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, [AppBarLayout] child will be lifted on nested scroll.
|
||||||
|
*/
|
||||||
|
var isLiftAppBarOnScroll = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal check
|
||||||
|
*/
|
||||||
|
private val canLiftAppBarOnScroll
|
||||||
|
get() = !isTablet && 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()
|
||||||
|
viewPager = findChild<ChangeHandlerFrameLayout>()?.findDescendant()
|
||||||
|
|
||||||
|
// Updates ViewPager reference when controller is changed
|
||||||
|
findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.let { scope ->
|
||||||
|
findChild<ChangeHandlerFrameLayout>()?.hierarchyChangeEvents()
|
||||||
|
?.onEach {
|
||||||
|
if (it is HierarchyChangeEvent.ChildRemoved) {
|
||||||
|
viewPager = (it.parent as? ViewGroup)?.findDescendant()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?.launchIn(scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow()
|
||||||
|
appBarLayout = null
|
||||||
|
viewPager = 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package eu.kanade.tachiyomi.widget
|
||||||
|
|
||||||
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [AppBarLayout.ScrollingViewBehavior] that lets the app bar overlaps the scrolling child.
|
||||||
|
*/
|
||||||
|
class TachiyomiScrollingViewBehavior : AppBarLayout.ScrollingViewBehavior() {
|
||||||
|
|
||||||
|
var shouldHeaderOverlap = false
|
||||||
|
|
||||||
|
override fun shouldHeaderOverlapScrollingChild(): Boolean {
|
||||||
|
return shouldHeaderOverlap
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<eu.kanade.tachiyomi.widget.TachiyomiCoordinatorLayout 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:id="@+id/root_coordinator"
|
android:id="@+id/root_coordinator"
|
||||||
|
@ -15,6 +15,7 @@
|
||||||
android:id="@+id/appbar"
|
android:id="@+id/appbar"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
@ -88,7 +89,7 @@
|
||||||
app:layout_constraintStart_toEndOf="@+id/side_nav"
|
app:layout_constraintStart_toEndOf="@+id/side_nav"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/incognito_mode" />
|
app:layout_constraintTop_toBottomOf="@+id/incognito_mode" />
|
||||||
|
|
||||||
<com.bluelinelabs.conductor.ChangeHandlerFrameLayout
|
<eu.kanade.tachiyomi.widget.TachiyomiChangeHandlerFrameLayout
|
||||||
android:id="@+id/controller_container"
|
android:id="@+id/controller_container"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
@ -103,4 +104,4 @@
|
||||||
android:id="@+id/fab_layout"
|
android:id="@+id/fab_layout"
|
||||||
layout="@layout/main_activity_fab" />
|
layout="@layout/main_activity_fab" />
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</eu.kanade.tachiyomi.widget.TachiyomiCoordinatorLayout>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<eu.kanade.tachiyomi.widget.TachiyomiCoordinatorLayout 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:id="@+id/root_coordinator"
|
android:id="@+id/root_coordinator"
|
||||||
|
@ -7,11 +7,18 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<eu.kanade.tachiyomi.widget.TachiyomiChangeHandlerFrameLayout
|
||||||
|
android:id="@+id/controller_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.ElevationAppBarLayout
|
<eu.kanade.tachiyomi.widget.ElevationAppBarLayout
|
||||||
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="true"
|
||||||
|
app:elevation="0dp"
|
||||||
|
app:statusBarForeground="?attr/colorToolbar">
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
|
@ -23,7 +30,8 @@
|
||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
android:id="@+id/tabs"
|
android:id="@+id/tabs"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/downloaded_only"
|
android:id="@+id/downloaded_only"
|
||||||
|
@ -63,12 +71,6 @@
|
||||||
|
|
||||||
</eu.kanade.tachiyomi.widget.ElevationAppBarLayout>
|
</eu.kanade.tachiyomi.widget.ElevationAppBarLayout>
|
||||||
|
|
||||||
<com.bluelinelabs.conductor.ChangeHandlerFrameLayout
|
|
||||||
android:id="@+id/controller_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/fab_layout"
|
android:id="@+id/fab_layout"
|
||||||
layout="@layout/main_activity_fab" />
|
layout="@layout/main_activity_fab" />
|
||||||
|
@ -83,4 +85,4 @@
|
||||||
app:menu="@menu/main_nav"
|
app:menu="@menu/main_nav"
|
||||||
tools:ignore="KeyboardInaccessibleWidget" />
|
tools:ignore="KeyboardInaccessibleWidget" />
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</eu.kanade.tachiyomi.widget.TachiyomiCoordinatorLayout>
|
||||||
|
|
|
@ -25,17 +25,18 @@
|
||||||
<View
|
<View
|
||||||
android:id="@+id/backdrop_overlay"
|
android:id="@+id/backdrop_overlay"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="160dp"
|
android:layout_height="0dp"
|
||||||
android:background="@drawable/manga_info_gradient"
|
android:background="@drawable/manga_info_gradient"
|
||||||
android:backgroundTint="?android:attr/colorBackground"
|
android:backgroundTint="?android:attr/colorBackground"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/backdrop" />
|
app:layout_constraintBottom_toBottomOf="@+id/backdrop"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/manga_cover"
|
android:id="@+id/manga_cover"
|
||||||
android:layout_width="100dp"
|
android:layout_width="100dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginTop="48dp"
|
android:layout_marginTop="8dp"
|
||||||
android:background="@drawable/rounded_rectangle"
|
android:background="@drawable/rounded_rectangle"
|
||||||
android:contentDescription="@string/description_cover"
|
android:contentDescription="@string/description_cover"
|
||||||
android:maxWidth="100dp"
|
android:maxWidth="100dp"
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
|
|
||||||
<!-- Themes -->
|
<!-- Themes -->
|
||||||
<item name="android:windowLightStatusBar">@bool/lightStatusBar</item>
|
<item name="android:windowLightStatusBar">@bool/lightStatusBar</item>
|
||||||
<item name="android:statusBarColor">?attr/colorSurface</item>
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
<item name="android:navigationBarColor">@color/surface_amoled</item>
|
<item name="android:navigationBarColor">@color/surface_amoled</item>
|
||||||
<item name="android:navigationBarDividerColor" tools:targetApi="o_mr1">@null</item>
|
<item name="android:navigationBarDividerColor" tools:targetApi="o_mr1">@null</item>
|
||||||
<item name="android:enforceNavigationBarContrast" tools:targetApi="Q">false</item>
|
<item name="android:enforceNavigationBarContrast" tools:targetApi="Q">false</item>
|
||||||
|
@ -186,7 +186,6 @@
|
||||||
<!-- Status/Navigation bar -->
|
<!-- Status/Navigation bar -->
|
||||||
<item name="android:windowLightStatusBar" tools:targetApi="m">?attr/lightSystemBarsOnPrimary</item>
|
<item name="android:windowLightStatusBar" tools:targetApi="m">?attr/lightSystemBarsOnPrimary</item>
|
||||||
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">?attr/lightSystemBarsOnPrimary</item>
|
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">?attr/lightSystemBarsOnPrimary</item>
|
||||||
<item name="android:statusBarColor">?attr/colorPrimary</item>
|
|
||||||
<item name="android:navigationBarColor">?attr/colorPrimary</item>
|
<item name="android:navigationBarColor">?attr/colorPrimary</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue