mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-16 22:17:28 +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
|
||||
|
||||
Reference in New Issue
Block a user