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:
Ivan Iskandar
2021-08-19 20:12:52 +07:00
committed by GitHub
parent 914b686c8e
commit da16110e1c
20 changed files with 490 additions and 90 deletions

View File

@@ -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
}

View File

@@ -1,3 +1,3 @@
package eu.kanade.tachiyomi.ui.base.controller
interface NoToolbarElevationController
interface NoAppBarElevationController

View File

@@ -1,3 +0,0 @@
package eu.kanade.tachiyomi.ui.base.controller
interface ToolbarLiftOnScrollController

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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() }

View File

@@ -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