mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01: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:
		@@ -7,6 +7,7 @@ import androidx.core.net.toUri
 | 
			
		||||
import com.bluelinelabs.conductor.Controller
 | 
			
		||||
import com.bluelinelabs.conductor.Router
 | 
			
		||||
import com.bluelinelabs.conductor.RouterTransaction
 | 
			
		||||
import eu.kanade.tachiyomi.ui.main.MainActivity
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.toast
 | 
			
		||||
 | 
			
		||||
fun Router.popControllerWithTag(tag: String): Boolean {
 | 
			
		||||
@@ -41,3 +42,10 @@ fun Controller.openInBrowser(url: String) {
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
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.getPreferenceKey
 | 
			
		||||
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.withFadeTransaction
 | 
			
		||||
import eu.kanade.tachiyomi.util.preference.DSL
 | 
			
		||||
@@ -49,8 +48,7 @@ import uy.kohesive.injekt.injectLazy
 | 
			
		||||
 | 
			
		||||
@SuppressLint("RestrictedApi")
 | 
			
		||||
class ExtensionDetailsController(bundle: Bundle? = null) :
 | 
			
		||||
    NucleusController<ExtensionDetailControllerBinding, ExtensionDetailsPresenter>(bundle),
 | 
			
		||||
    ToolbarLiftOnScrollController {
 | 
			
		||||
    NucleusController<ExtensionDetailControllerBinding, ExtensionDetailsPresenter>(bundle) {
 | 
			
		||||
 | 
			
		||||
    private val preferences: PreferencesHelper by injectLazy()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -245,12 +245,7 @@ class LibraryController(
 | 
			
		||||
        }
 | 
			
		||||
        tabsVisibilitySubscription?.unsubscribe()
 | 
			
		||||
        tabsVisibilitySubscription = tabsVisibilityRelay.subscribe { visible ->
 | 
			
		||||
            val tabAnimator = (activity as? MainActivity)?.tabAnimator
 | 
			
		||||
            if (visible) {
 | 
			
		||||
                tabAnimator?.expand()
 | 
			
		||||
            } else {
 | 
			
		||||
                tabAnimator?.collapse()
 | 
			
		||||
            }
 | 
			
		||||
            tabs.isVisible = visible
 | 
			
		||||
        }
 | 
			
		||||
        mangaCountVisibilitySubscription?.unsubscribe()
 | 
			
		||||
        mangaCountVisibilitySubscription = mangaCountVisibilityRelay.subscribe {
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,7 @@ import eu.kanade.tachiyomi.BuildConfig
 | 
			
		||||
import eu.kanade.tachiyomi.Migrations
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
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.updater.AppUpdateChecker
 | 
			
		||||
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.controller.DialogController
 | 
			
		||||
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.TabbedController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.ToolbarLiftOnScrollController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.BrowseController
 | 
			
		||||
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.launchUI
 | 
			
		||||
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
 | 
			
		||||
@@ -85,7 +86,6 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    lateinit var tabAnimator: ViewHeightAnimator
 | 
			
		||||
    private var bottomNavAnimator: ViewHeightAnimator? = null
 | 
			
		||||
 | 
			
		||||
    private var isConfirmingExit: Boolean = false
 | 
			
		||||
@@ -93,6 +93,11 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
 | 
			
		||||
 | 
			
		||||
    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.
 | 
			
		||||
    var ready = false
 | 
			
		||||
 | 
			
		||||
@@ -117,11 +122,6 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
 | 
			
		||||
 | 
			
		||||
        // Draw edge-to-edge
 | 
			
		||||
        WindowCompat.setDecorFitsSystemWindows(window, false)
 | 
			
		||||
        binding.appbar.applyInsetter {
 | 
			
		||||
            type(navigationBars = true, statusBars = true) {
 | 
			
		||||
                padding(left = true, top = true, right = true)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        binding.fabLayout.rootFab.applyInsetter {
 | 
			
		||||
            type(navigationBars = true) {
 | 
			
		||||
                margin()
 | 
			
		||||
@@ -140,8 +140,6 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
 | 
			
		||||
        }
 | 
			
		||||
        setSplashScreenExitAnimation(splashScreen)
 | 
			
		||||
 | 
			
		||||
        tabAnimator = ViewHeightAnimator(binding.tabs, 0L)
 | 
			
		||||
 | 
			
		||||
        if (binding.bottomNav != null) {
 | 
			
		||||
            bottomNavAnimator = ViewHeightAnimator(binding.bottomNav!!)
 | 
			
		||||
 | 
			
		||||
@@ -218,7 +216,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
 | 
			
		||||
                    container: ViewGroup,
 | 
			
		||||
                    handler: ControllerChangeHandler
 | 
			
		||||
                ) {
 | 
			
		||||
                    syncActivityViewWithController(to, from)
 | 
			
		||||
                    syncActivityViewWithController(to, from, isPush)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                override fun onChangeCompleted(
 | 
			
		||||
@@ -504,7 +502,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
 | 
			
		||||
        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) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
@@ -529,12 +527,11 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
 | 
			
		||||
            from.cleanupTabs(binding.tabs)
 | 
			
		||||
        }
 | 
			
		||||
        if (to is TabbedController) {
 | 
			
		||||
            tabAnimator.expand()
 | 
			
		||||
            to.configureTabs(binding.tabs)
 | 
			
		||||
        } else {
 | 
			
		||||
            tabAnimator.collapse()
 | 
			
		||||
            binding.tabs.setupWithViewPager(null)
 | 
			
		||||
        }
 | 
			
		||||
        binding.tabs.isVisible = to is TabbedController
 | 
			
		||||
 | 
			
		||||
        if (from is FabController) {
 | 
			
		||||
            binding.fabLayout.rootFab.isVisible = false
 | 
			
		||||
@@ -545,16 +542,32 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
 | 
			
		||||
            to.configureFab(binding.fabLayout.rootFab)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        when (to) {
 | 
			
		||||
            is NoToolbarElevationController -> {
 | 
			
		||||
                binding.appbar.disableElevation()
 | 
			
		||||
            }
 | 
			
		||||
            is ToolbarLiftOnScrollController -> {
 | 
			
		||||
                binding.appbar.enableElevation(true)
 | 
			
		||||
            }
 | 
			
		||||
            else -> {
 | 
			
		||||
                binding.appbar.enableElevation(false)
 | 
			
		||||
        if (!isTablet()) {
 | 
			
		||||
            // Save lift state
 | 
			
		||||
            if (isPush) {
 | 
			
		||||
                if (router.backstackSize > 1) {
 | 
			
		||||
                    // Save lift state
 | 
			
		||||
                    from?.let {
 | 
			
		||||
                        backstackLiftState[it.instanceId] = binding.appbar.isLifted
 | 
			
		||||
                    }
 | 
			
		||||
                } 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.MenuItem
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import android.widget.TextView
 | 
			
		||||
import androidx.annotation.FloatRange
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity
 | 
			
		||||
import androidx.appcompat.view.ActionMode
 | 
			
		||||
import androidx.core.os.bundleOf
 | 
			
		||||
import androidx.core.view.WindowInsetsCompat
 | 
			
		||||
import androidx.core.view.children
 | 
			
		||||
import androidx.core.view.doOnLayout
 | 
			
		||||
import androidx.core.view.isVisible
 | 
			
		||||
import androidx.core.view.updateLayoutParams
 | 
			
		||||
import androidx.recyclerview.widget.ConcatAdapter
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
 | 
			
		||||
import coil.imageLoader
 | 
			
		||||
import coil.request.ImageRequest
 | 
			
		||||
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.ui.base.controller.FabController
 | 
			
		||||
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.browse.migration.search.SearchController
 | 
			
		||||
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.onEach
 | 
			
		||||
import reactivecircus.flowbinding.recyclerview.scrollEvents
 | 
			
		||||
import reactivecircus.flowbinding.recyclerview.scrollStateChanges
 | 
			
		||||
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
@@ -99,7 +105,6 @@ import kotlin.math.min
 | 
			
		||||
 | 
			
		||||
class MangaController :
 | 
			
		||||
    NucleusController<MangaControllerBinding, MangaPresenter>,
 | 
			
		||||
    ToolbarLiftOnScrollController,
 | 
			
		||||
    FabController,
 | 
			
		||||
    ActionMode.Callback,
 | 
			
		||||
    FlexibleAdapter.OnItemClickListener,
 | 
			
		||||
@@ -254,6 +259,37 @@ class MangaController :
 | 
			
		||||
                    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
 | 
			
		||||
        binding.infoRecycler?.let {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,9 @@ package eu.kanade.tachiyomi.ui.manga.info
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
import androidx.core.view.isVisible
 | 
			
		||||
import androidx.core.view.updateLayoutParams
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import coil.loadAny
 | 
			
		||||
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.model.SManga
 | 
			
		||||
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.util.system.copyToClipboard
 | 
			
		||||
import eu.kanade.tachiyomi.util.view.setChips
 | 
			
		||||
@@ -47,6 +50,7 @@ class MangaInfoHeaderAdapter(
 | 
			
		||||
 | 
			
		||||
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
 | 
			
		||||
        binding = MangaInfoHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
 | 
			
		||||
        updateCoverPosition()
 | 
			
		||||
        return HeaderViewHolder(binding.root)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -75,6 +79,15 @@ class MangaInfoHeaderAdapter(
 | 
			
		||||
        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) {
 | 
			
		||||
        fun bind() {
 | 
			
		||||
            // For rounded corners
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.BuildConfig
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
 | 
			
		||||
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.withFadeTransaction
 | 
			
		||||
import eu.kanade.tachiyomi.ui.more.licenses.LicensesController
 | 
			
		||||
@@ -25,7 +25,7 @@ import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.Locale
 | 
			
		||||
import java.util.TimeZone
 | 
			
		||||
 | 
			
		||||
class AboutController : SettingsController(), NoToolbarElevationController {
 | 
			
		||||
class AboutController : SettingsController(), NoAppBarElevationController {
 | 
			
		||||
 | 
			
		||||
    private val updateChecker by lazy { AppUpdateChecker() }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ import androidx.preference.PreferenceScreen
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.download.DownloadManager
 | 
			
		||||
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.withFadeTransaction
 | 
			
		||||
import eu.kanade.tachiyomi.ui.category.CategoryController
 | 
			
		||||
@@ -41,7 +41,7 @@ import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
 | 
			
		||||
class MoreController :
 | 
			
		||||
    SettingsController(),
 | 
			
		||||
    RootController,
 | 
			
		||||
    NoToolbarElevationController {
 | 
			
		||||
    NoAppBarElevationController {
 | 
			
		||||
 | 
			
		||||
    private val downloadManager: DownloadManager by injectLazy()
 | 
			
		||||
    private var isDownloading: Boolean = false
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import android.view.Gravity
 | 
			
		||||
import android.view.Menu
 | 
			
		||||
import android.view.MenuItem
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import android.widget.TextView
 | 
			
		||||
import androidx.annotation.MenuRes
 | 
			
		||||
import androidx.annotation.StringRes
 | 
			
		||||
@@ -16,8 +17,11 @@ import androidx.appcompat.view.menu.MenuBuilder
 | 
			
		||||
import androidx.appcompat.widget.PopupMenu
 | 
			
		||||
import androidx.appcompat.widget.TooltipCompat
 | 
			
		||||
import androidx.core.content.ContextCompat
 | 
			
		||||
import androidx.core.view.children
 | 
			
		||||
import androidx.core.view.descendants
 | 
			
		||||
import androidx.core.view.forEach
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import androidx.viewpager.widget.ViewPager
 | 
			
		||||
import com.google.android.material.card.MaterialCardView
 | 
			
		||||
import com.google.android.material.chip.Chip
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
import android.animation.ObjectAnimator
 | 
			
		||||
import android.animation.StateListAnimator
 | 
			
		||||
import android.animation.ValueAnimator
 | 
			
		||||
import android.content.Context
 | 
			
		||||
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.MaterialToolbar
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
 | 
			
		||||
class ElevationAppBarLayout @JvmOverloads constructor(
 | 
			
		||||
    context: Context,
 | 
			
		||||
    attrs: AttributeSet? = null
 | 
			
		||||
) : AppBarLayout(context, attrs) {
 | 
			
		||||
 | 
			
		||||
    private var origStateAnimator: StateListAnimator? = null
 | 
			
		||||
    private var lifted = true
 | 
			
		||||
    private var transparent = false
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        origStateAnimator = stateListAnimator
 | 
			
		||||
    private val toolbar by lazy { findViewById<MaterialToolbar>(R.id.toolbar) }
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
        setElevation(liftOnScroll)
 | 
			
		||||
    }
 | 
			
		||||
    private fun updateBackgroundAlpha() {
 | 
			
		||||
        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) {
 | 
			
		||||
        stateListAnimator = origStateAnimator
 | 
			
		||||
        isLiftOnScroll = liftOnScroll
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun disableElevation() {
 | 
			
		||||
        stateListAnimator = StateListAnimator().apply {
 | 
			
		||||
            val objAnimator = ObjectAnimator.ofFloat(this, "elevation", 0f)
 | 
			
		||||
 | 
			
		||||
            // Enabled and collapsible, but not collapsed means not elevated
 | 
			
		||||
            addState(
 | 
			
		||||
                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)
 | 
			
		||||
            backgroundAlphaAnimator?.cancel()
 | 
			
		||||
            backgroundAlphaAnimator = ValueAnimator.ofInt(fromAlpha, toAlpha).apply {
 | 
			
		||||
                duration = resources.getInteger(R.integer.app_bar_elevation_anim_duration).toLong()
 | 
			
		||||
                interpolator = AnimationUtils.LINEAR_INTERPOLATOR
 | 
			
		||||
                addUpdateListener {
 | 
			
		||||
                    val alpha = it.animatedValue as Int
 | 
			
		||||
                    background.alpha = alpha
 | 
			
		||||
                    toolbar?.background?.alpha = alpha
 | 
			
		||||
                    statusBarForeground?.alpha = alpha
 | 
			
		||||
                }
 | 
			
		||||
                start()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.widget
 | 
			
		||||
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import androidx.viewpager.widget.ViewPager
 | 
			
		||||
import com.nightlynexus.viewstatepageradapter.ViewStatePagerAdapter
 | 
			
		||||
import java.util.Stack
 | 
			
		||||
 | 
			
		||||
@@ -22,7 +23,11 @@ abstract class RecyclerViewPagerAdapter : ViewStatePagerAdapter() {
 | 
			
		||||
    protected open fun recycleView(view: View, position: Int) {}
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
        return view
 | 
			
		||||
    }
 | 
			
		||||
@@ -31,4 +36,25 @@ abstract class RecyclerViewPagerAdapter : ViewStatePagerAdapter() {
 | 
			
		||||
        recycleView(view, position)
 | 
			
		||||
        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"?>
 | 
			
		||||
<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:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:id="@+id/root_coordinator"
 | 
			
		||||
@@ -15,6 +15,7 @@
 | 
			
		||||
            android:id="@+id/appbar"
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:fitsSystemWindows="true"
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent">
 | 
			
		||||
@@ -88,7 +89,7 @@
 | 
			
		||||
            app:layout_constraintStart_toEndOf="@+id/side_nav"
 | 
			
		||||
            app:layout_constraintTop_toBottomOf="@+id/incognito_mode" />
 | 
			
		||||
 | 
			
		||||
        <com.bluelinelabs.conductor.ChangeHandlerFrameLayout
 | 
			
		||||
        <eu.kanade.tachiyomi.widget.TachiyomiChangeHandlerFrameLayout
 | 
			
		||||
            android:id="@+id/controller_container"
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="0dp"
 | 
			
		||||
@@ -103,4 +104,4 @@
 | 
			
		||||
        android:id="@+id/fab_layout"
 | 
			
		||||
        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"?>
 | 
			
		||||
<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:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:id="@+id/root_coordinator"
 | 
			
		||||
@@ -7,11 +7,18 @@
 | 
			
		||||
    android:layout_height="match_parent"
 | 
			
		||||
    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
 | 
			
		||||
        android:id="@+id/appbar"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:fitsSystemWindows="true">
 | 
			
		||||
        android:fitsSystemWindows="true"
 | 
			
		||||
        app:elevation="0dp"
 | 
			
		||||
        app:statusBarForeground="?attr/colorToolbar">
 | 
			
		||||
 | 
			
		||||
        <com.google.android.material.appbar.MaterialToolbar
 | 
			
		||||
            android:id="@+id/toolbar"
 | 
			
		||||
@@ -23,7 +30,8 @@
 | 
			
		||||
        <com.google.android.material.tabs.TabLayout
 | 
			
		||||
            android:id="@+id/tabs"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content" />
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:visibility="gone" />
 | 
			
		||||
 | 
			
		||||
        <FrameLayout
 | 
			
		||||
            android:id="@+id/downloaded_only"
 | 
			
		||||
@@ -63,12 +71,6 @@
 | 
			
		||||
 | 
			
		||||
    </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
 | 
			
		||||
        android:id="@+id/fab_layout"
 | 
			
		||||
        layout="@layout/main_activity_fab" />
 | 
			
		||||
@@ -83,4 +85,4 @@
 | 
			
		||||
        app:menu="@menu/main_nav"
 | 
			
		||||
        tools:ignore="KeyboardInaccessibleWidget" />
 | 
			
		||||
 | 
			
		||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
 | 
			
		||||
</eu.kanade.tachiyomi.widget.TachiyomiCoordinatorLayout>
 | 
			
		||||
 
 | 
			
		||||
@@ -25,17 +25,18 @@
 | 
			
		||||
    <View
 | 
			
		||||
        android:id="@+id/backdrop_overlay"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="160dp"
 | 
			
		||||
        android:layout_height="0dp"
 | 
			
		||||
        android:background="@drawable/manga_info_gradient"
 | 
			
		||||
        android:backgroundTint="?android:attr/colorBackground"
 | 
			
		||||
        app:layout_constraintBottom_toBottomOf="@+id/backdrop" />
 | 
			
		||||
        app:layout_constraintBottom_toBottomOf="@+id/backdrop"
 | 
			
		||||
        app:layout_constraintTop_toTopOf="parent" />
 | 
			
		||||
 | 
			
		||||
    <ImageView
 | 
			
		||||
        android:id="@+id/manga_cover"
 | 
			
		||||
        android:layout_width="100dp"
 | 
			
		||||
        android:layout_height="0dp"
 | 
			
		||||
        android:layout_marginStart="16dp"
 | 
			
		||||
        android:layout_marginTop="48dp"
 | 
			
		||||
        android:layout_marginTop="8dp"
 | 
			
		||||
        android:background="@drawable/rounded_rectangle"
 | 
			
		||||
        android:contentDescription="@string/description_cover"
 | 
			
		||||
        android:maxWidth="100dp"
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@
 | 
			
		||||
 | 
			
		||||
        <!-- Themes -->
 | 
			
		||||
        <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:navigationBarDividerColor" tools:targetApi="o_mr1">@null</item>
 | 
			
		||||
        <item name="android:enforceNavigationBarContrast" tools:targetApi="Q">false</item>
 | 
			
		||||
@@ -186,7 +186,6 @@
 | 
			
		||||
        <!-- Status/Navigation bar -->
 | 
			
		||||
        <item name="android:windowLightStatusBar" tools:targetApi="m">?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>
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user