From d82d738f38818701d22561d8cef5fc23a931ad4a Mon Sep 17 00:00:00 2001 From: Jays2Kings Date: Sun, 23 May 2021 00:11:32 -0400 Subject: [PATCH] Initial tablet NavigationRailView implementation Co-Authored-By: arkon <4098258+arkon@users.noreply.github.com> --- .../controller/OneWayFadeChangeHandler.kt | 50 ++++ .../ui/extension/ExtensionBottomSheet.kt | 16 +- .../tachiyomi/ui/library/LibraryController.kt | 28 ++- .../ui/library/display/LibraryDisplayView.kt | 6 +- .../display/TabbedLibraryDisplaySheet.kt | 1 + .../ui/library/filter/FilterBottomSheet.kt | 5 +- .../kanade/tachiyomi/ui/main/MainActivity.kt | 223 ++++++++++-------- .../tachiyomi/ui/main/SearchActivity.kt | 8 +- .../tachiyomi/ui/recents/RecentsController.kt | 11 +- .../util/view/ControllerExtensions.kt | 117 +++++---- .../tachiyomi/util/view/ViewExtensions.kt | 10 +- .../drawable/shape_gradient_start_shadow.xml | 7 + .../main/res/layout-sw600dp/main_activity.xml | 220 +++++++++++++++++ 13 files changed, 535 insertions(+), 167 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/OneWayFadeChangeHandler.kt create mode 100644 app/src/main/res/drawable/shape_gradient_start_shadow.xml create mode 100644 app/src/main/res/layout-sw600dp/main_activity.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/OneWayFadeChangeHandler.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/OneWayFadeChangeHandler.kt new file mode 100644 index 0000000000..76e929df4f --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/OneWayFadeChangeHandler.kt @@ -0,0 +1,50 @@ +package eu.kanade.tachiyomi.ui.base.controller + +import android.animation.Animator +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.view.View +import android.view.ViewGroup +import com.bluelinelabs.conductor.ControllerChangeHandler +import com.bluelinelabs.conductor.changehandler.FadeChangeHandler +import eu.kanade.tachiyomi.util.system.isTablet + +/** + * A variation of [FadeChangeHandler] that only fades in. + */ +class OneWayFadeChangeHandler : FadeChangeHandler { + constructor() + constructor(removesFromViewOnPush: Boolean) : super(removesFromViewOnPush) + constructor(duration: Long) : super(duration) + constructor(duration: Long, removesFromViewOnPush: Boolean) : super( + duration, + removesFromViewOnPush + ) + + override fun getAnimator( + container: ViewGroup, + from: View?, + to: View?, + isPush: Boolean, + toAddedToContainer: Boolean + ): Animator { + val animator = AnimatorSet() + if (to != null) { + val start: Float = if (toAddedToContainer) 0F else to.alpha + animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, start, 1f)) + } + + if (from != null && (!isPush || removesFromViewOnPush())) { + if (!container.context.isTablet()) { + animator.play(ObjectAnimator.ofFloat(from, View.ALPHA, 0f)) + } else { + container.removeView(from) + } + } + return animator + } + + override fun copy(): ControllerChangeHandler { + return OneWayFadeChangeHandler(animationDuration, removesFromViewOnPush()) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt index fcb6ef20f6..5c011af763 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt @@ -91,10 +91,10 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At binding.pager.adapter = TabbedSheetAdapter() binding.tabs.setupWithViewPager(binding.pager) this.controller = controller - binding.pager.doOnApplyWindowInsets { _, _, _ -> + binding.pager.doOnApplyWindowInsets { _, insets, _ -> val bottomBar = controller.activityBinding?.bottomNav - extensionFrameLayout?.binding?.recycler?.updatePaddingRelative(bottom = bottomBar?.height ?: 0) - migrationFrameLayout?.binding?.recycler?.updatePaddingRelative(bottom = bottomBar?.height ?: 0) + extensionFrameLayout?.binding?.recycler?.updatePaddingRelative(bottom = bottomBar?.height ?: insets.systemWindowInsetBottom) + migrationFrameLayout?.binding?.recycler?.updatePaddingRelative(bottom = bottomBar?.height ?: insets.systemWindowInsetBottom) } binding.tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabSelected(tab: TabLayout.Tab?) { @@ -336,9 +336,15 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At * @return a new view. */ override fun createView(container: ViewGroup): View { - val binding = RecyclerWithScrollerBinding.inflate(LayoutInflater.from(container.context), container, false) + val binding = RecyclerWithScrollerBinding.inflate( + LayoutInflater.from(container.context), + container, + false + ) val view: RecyclerWithScrollerView = binding.root - view.setUp(this@ExtensionBottomSheet, binding, this@ExtensionBottomSheet.controller.activityBinding?.bottomNav?.height ?: 0) + val height = this@ExtensionBottomSheet.controller.activityBinding?.bottomNav?.height + ?: view.rootWindowInsets?.systemWindowInsetBottom ?: 0 + view.setUp(this@ExtensionBottomSheet, binding, height) return view } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index 6e90abe616..e98c5f1919 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -200,6 +200,10 @@ class LibraryController( get() = preferences.showCategoryInTitle().get() && presenter.showAllCategories private lateinit var elevateAppBar: ((Boolean) -> Unit) private var hopperOffset = 0f + private val maxHopperOffset: Float + get() = + if (activityBinding?.bottomNav != null) 55f.dpToPx + else (view?.rootWindowInsets?.systemWindowInsetBottom?.toFloat() ?: 0f) + 55f.dpToPx override fun getTitle(): String? { setSubtitle() @@ -222,9 +226,9 @@ class LibraryController( if (!recyclerCover.isClickable && isAnimatingHopper != true) { if (preferences.autohideHopper().get()) { hopperOffset += dy - hopperOffset = hopperOffset.coerceIn(0f, 55f.dpToPx) + hopperOffset = hopperOffset.coerceIn(0f, maxHopperOffset) } - if (!preferences.hideBottomNavOnScroll().get()) { + if (!preferences.hideBottomNavOnScroll().get() || activityBinding?.bottomNav == null) { updateFilterSheetY() } binding.roundedCategoryHopper.upCategory.alpha = if (isAtTop()) 0.25f else 1f @@ -291,6 +295,11 @@ class LibraryController( binding.fastScroller.updateLayoutParams { bottomMargin = -pad.toInt() } + } else { + binding.filterBottomSheet.filterBottomSheet.updatePaddingRelative( + bottom = view?.rootWindowInsets?.getBottomGestureInsets() ?: 0 + ) + updateHopperY() } } @@ -300,12 +309,17 @@ class LibraryController( ) ?: 0 if (preferences.autohideHopper().get()) { // Flow same snap rules as bottom nav - val closerToHopperBottom = hopperOffset > 25f.dpToPx + val closerToHopperBottom = hopperOffset > maxHopperOffset / 2 val halfWayBottom = activityBinding?.bottomNav?.height?.toFloat()?.div(2) ?: 0f val closerToBottom = (activityBinding?.bottomNav?.translationY ?: 0f) > halfWayBottom val atTop = !binding.libraryGridRecycler.recycler.canScrollVertically(-1) - val closerToEdge = if (preferences.hideBottomNavOnScroll().get()) (closerToBottom && !atTop) else closerToHopperBottom - val end = if (closerToEdge) 55f.dpToPx else 0f + val closerToEdge = + if (preferences.hideBottomNavOnScroll().get() && activityBinding?.bottomNav != null) { + closerToBottom && !atTop + } else { + closerToHopperBottom + } + val end = if (closerToEdge) maxHopperOffset else 0f val alphaAnimation = ValueAnimator.ofFloat(hopperOffset, end) alphaAnimation.addUpdateListener { valueAnimator -> hopperOffset = valueAnimator.animatedValue as Float @@ -385,7 +399,7 @@ class LibraryController( if (preferences.shownFilterTutorial().get() || !hasExpanded) return val activityBinding = activityBinding ?: return val activity = activity ?: return - val icon = activityBinding.bottomNav.getItemView(R.id.nav_library) ?: return + val icon = (activityBinding.bottomNav ?: activityBinding.sideNav)?.getItemView(R.id.nav_library) ?: return filterTooltip = ViewTooltip.on(activity, icon).autoHide(false, 0L).align(ViewTooltip.ALIGN.START) .position(ViewTooltip.Position.TOP).text(R.string.tap_library_to_show_filters) @@ -684,7 +698,7 @@ class LibraryController( activityBinding?.bottomNav?.y ?: binding.filterBottomSheet.filterBottomSheet.y ) val insetBottom = view.rootWindowInsets?.systemWindowInsetBottom ?: 0 - if (!preferences.autohideHopper().get()) { + if (!preferences.autohideHopper().get() || activityBinding?.bottomNav == null) { listOfYs.add(view.height - (insetBottom).toFloat()) } binding.categoryHopperFrame.y = -binding.categoryHopperFrame.height + diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryDisplayView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryDisplayView.kt index 2e3247b702..03c9a8facf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryDisplayView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryDisplayView.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.library.display import android.animation.ValueAnimator import android.content.Context import android.util.AttributeSet +import android.view.View import android.view.ViewTreeObserver import android.widget.SeekBar import androidx.core.animation.addListener @@ -27,6 +28,7 @@ import kotlin.math.roundToInt class LibraryDisplayView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : BaseLibraryDisplayView(context, attrs) { + var mainView: View? = null override fun inflateBinding() = LibraryDisplayLayoutBinding.bind(this) override fun initGeneralPreferences() { binding.displayGroup.bindToPreference(preferences.libraryLayout()) @@ -127,7 +129,7 @@ class LibraryDisplayView @JvmOverloads constructor(context: Context, attrs: Attr private fun setGridText(progress: Int) { with(binding.gridSizeText) { - val rows = this@LibraryDisplayView.rowsForValue(progress) + val rows = (mainView ?: this@LibraryDisplayView).rowsForValue(progress) val titleText = context.getString(R.string.grid_size) val subtitleText = context.getString(R.string._per_row, rows) text = titleText.withSubtitle(context, subtitleText) @@ -138,7 +140,7 @@ class LibraryDisplayView @JvmOverloads constructor(context: Context, attrs: Attr with(binding.seekBarTextView.root) { val value = (progress * (seekBar.width - 12.dpToPx - 2 * seekBar.thumbOffset)) / seekBar.max - text = this@LibraryDisplayView.rowsForValue(progress).toString() + text = (mainView ?: this@LibraryDisplayView).rowsForValue(progress).toString() x = seekBar.x + value + seekBar.thumbOffset / 2 + 5.dpToPx y = seekBar.y + binding.gridSizeLayout.y - 6.dpToPx - height } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/TabbedLibraryDisplaySheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/TabbedLibraryDisplaySheet.kt index 37dbdcaef8..34f1c76f3f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/TabbedLibraryDisplaySheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/TabbedLibraryDisplaySheet.kt @@ -25,6 +25,7 @@ open class TabbedLibraryDisplaySheet(val controller: Controller) : badgesView.controller = libraryController categoryView.controller = libraryController } + displayView.mainView = controller.view binding.menu.isVisible = controller !is SettingsLibraryController binding.menu.compatToolTipText = context.getString(R.string.more_library_settings) binding.menu.setImageDrawable( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt index c142202861..d742470886 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt @@ -110,7 +110,10 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri this.controller = controller libraryRecyler = controller.binding.libraryGridRecycler.recycler libraryRecyler?.post { - bottomBarHeight = controller.activityBinding?.bottomNav?.height ?: 0 + bottomBarHeight = + controller.activityBinding?.bottomNav?.height + ?: controller.activityBinding?.root?.rootWindowInsets?.systemWindowInsetBottom + ?: 0 } val shadow2: View = controller.binding.shadow2 val shadow: View = controller.binding.shadow diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index fbb92c706c..a4985592de 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -35,6 +35,7 @@ import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.Router import com.getkeepsafe.taptargetview.TapTarget import com.getkeepsafe.taptargetview.TapTargetView +import com.google.android.material.navigation.NavigationBarView import com.google.android.material.snackbar.Snackbar import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.Migrations @@ -71,6 +72,7 @@ import eu.kanade.tachiyomi.util.system.contextCompatDrawable import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.hasSideNavBar import eu.kanade.tachiyomi.util.system.isBottomTappable +import eu.kanade.tachiyomi.util.system.isTablet import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets @@ -167,7 +169,7 @@ open class MainActivity : BaseActivity(), DownloadServiceLi ) var continueSwitchingTabs = false - binding.bottomNav.getItemView(R.id.nav_library)?.setOnLongClickListener { + nav.getItemView(R.id.nav_library)?.setOnLongClickListener { if (!LibraryUpdateService.isRunning()) { LibraryUpdateService.start(this) binding.mainContent.snack(R.string.updating_library) { @@ -186,9 +188,9 @@ open class MainActivity : BaseActivity(), DownloadServiceLi true } for (id in listOf(R.id.nav_recents, R.id.nav_browse)) { - binding.bottomNav.getItemView(id)?.setOnLongClickListener { - binding.bottomNav.selectedItemId = id - binding.bottomNav.post { + nav.getItemView(id)?.setOnLongClickListener { + nav.selectedItemId = id + nav.post { val controller = router.backstack.firstOrNull()?.controller as? BottomSheetController controller?.showSheet() @@ -196,15 +198,67 @@ open class MainActivity : BaseActivity(), DownloadServiceLi true } } - binding.bottomNav.setOnNavigationItemSelectedListener { item -> + + val container: ViewGroup = binding.controllerContainer + + val content: ViewGroup = binding.mainContent + DownloadService.addListener(this) + content.systemUiVisibility = + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + container.systemUiVisibility = + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + + supportActionBar?.setDisplayShowCustomEnabled(true) + + setNavBarColor(content.rootWindowInsets) + nav.isVisible = false + content.doOnApplyWindowInsets { v, insets, _ -> + setNavBarColor(insets) + val contextView = window?.decorView?.findViewById(R.id.action_mode_bar) + contextView?.updateLayoutParams { + leftMargin = insets.systemWindowInsetLeft + rightMargin = insets.systemWindowInsetRight + } + // Consume any horizontal insets and pad all content in. There's not much we can do + // with horizontal insets + v.updatePadding( + left = insets.systemWindowInsetLeft, + right = insets.systemWindowInsetRight + ) + binding.appBar.updatePadding( + top = insets.systemWindowInsetTop + ) + binding.bottomNav?.updatePadding(bottom = insets.systemWindowInsetBottom) + binding.sideNav?.updatePadding( + left = insets.systemWindowInsetLeft, + right = insets.systemWindowInsetRight + ) + binding.bottomView?.isVisible = insets.systemWindowInsetBottom > 0 + binding.bottomView?.updateLayoutParams { + height = insets.systemWindowInsetBottom + } + } + + router = Conductor.attachRouter(this, container, savedInstanceState) + + if (router.hasRootController()) { + nav.selectedItemId = + when (router.backstack.firstOrNull()?.controller) { + is RecentsController -> R.id.nav_recents + is BrowseController -> R.id.nav_browse + else -> R.id.nav_library + } + } + + nav.setOnItemSelectedListener { item -> val id = item.itemId val currentController = router.backstack.lastOrNull()?.controller if (!continueSwitchingTabs && currentController is BottomNavBarInterface) { if (!currentController.canChangeTabs { continueSwitchingTabs = true - this@MainActivity.binding.bottomNav.selectedItemId = id + this@MainActivity.nav.selectedItemId = id } - ) return@setOnNavigationItemSelectedListener false + ) return@setOnItemSelectedListener false } continueSwitchingTabs = false val currentRoot = router.backstack.firstOrNull() @@ -226,43 +280,7 @@ open class MainActivity : BaseActivity(), DownloadServiceLi } true } - val container: ViewGroup = binding.controllerContainer - val content: ViewGroup = binding.mainContent - DownloadService.addListener(this) - content.systemUiVisibility = - View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - container.systemUiVisibility = - View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - - supportActionBar?.setDisplayShowCustomEnabled(true) - - setNavBarColor(content.rootWindowInsets) - binding.bottomView.isVisible = false - content.doOnApplyWindowInsets { v, insets, _ -> - setNavBarColor(insets) - val contextView = window?.decorView?.findViewById(R.id.action_mode_bar) - contextView?.updateLayoutParams { - leftMargin = insets.systemWindowInsetLeft - rightMargin = insets.systemWindowInsetRight - } - // Consume any horizontal insets and pad all content in. There's not much we can do - // with horizontal insets - v.updatePadding( - left = insets.systemWindowInsetLeft, - right = insets.systemWindowInsetRight - ) - binding.appBar.updatePadding( - top = insets.systemWindowInsetTop - ) - binding.bottomNav.updatePadding(bottom = insets.systemWindowInsetBottom) - binding.bottomView.isVisible = insets.systemWindowInsetBottom > 0 - binding.bottomView.updateLayoutParams { - height = insets.systemWindowInsetBottom - } - } - - router = Conductor.attachRouter(this, container, savedInstanceState) if (!router.hasRootController()) { // Set start screen if (!handleIntentAction(intent)) { @@ -288,9 +306,9 @@ open class MainActivity : BaseActivity(), DownloadServiceLi binding.cardToolbar.menu.findItem(R.id.action_search)?.expandActionView() } - binding.bottomNav.isVisible = !hideBottomNav - binding.bottomView.visibility = if (hideBottomNav) View.GONE else binding.bottomView.visibility - binding.bottomNav.alpha = if (hideBottomNav) 0f else 1f + nav.isVisible = !hideBottomNav + binding.bottomView?.visibility = if (hideBottomNav) View.GONE else binding.bottomView?.visibility ?: View.GONE + nav.alpha = if (hideBottomNav) 0f else 1f router.addChangeListener( object : ControllerChangeHandler.ControllerChangeListener { override fun onChangeStarted( @@ -303,7 +321,7 @@ open class MainActivity : BaseActivity(), DownloadServiceLi syncActivityViewWithController(to, from, isPush) binding.appBar.y = 0f if (!isPush || router.backstackSize == 1) { - binding.bottomNav.translationY = 0f + nav.translationY = 0f } snackBar?.dismiss() } @@ -316,7 +334,7 @@ open class MainActivity : BaseActivity(), DownloadServiceLi handler: ControllerChangeHandler ) { binding.appBar.y = 0f - binding.bottomNav.translationY = 0f + nav.translationY = 0f showDLQueueTutorial() } } @@ -348,10 +366,10 @@ open class MainActivity : BaseActivity(), DownloadServiceLi binding.cardToolbar.setIncognitoMode(it) } setExtensionsBadge() - setFloatingToolbar(canShowFloatingToolbar(router.backstack.lastOrNull()?.controller)) + setFloatingToolbar(canShowFloatingToolbar(router.backstack.lastOrNull()?.controller), changeBG = false) } - open fun setFloatingToolbar(show: Boolean, solidBG: Boolean = false) { + open fun setFloatingToolbar(show: Boolean, solidBG: Boolean = false, changeBG: Boolean = true) { val oldTB = currentToolbar currentToolbar = if (show) { binding.cardToolbar @@ -363,9 +381,11 @@ open class MainActivity : BaseActivity(), DownloadServiceLi } binding.toolbar.isVisible = !show binding.cardFrame.isVisible = show - binding.appBar.setBackgroundColor( - if (show && !solidBG) Color.TRANSPARENT else getResourceColor(R.attr.colorSecondary) - ) + if (changeBG) { + binding.appBar.setBackgroundColor( + if (show && !solidBG) Color.TRANSPARENT else getResourceColor(R.attr.colorSecondary) + ) + } currentToolbar?.setNavigationOnClickListener { val rootSearchController = router.backstack.lastOrNull()?.controller if (rootSearchController is RootSearchInterface) { @@ -435,10 +455,10 @@ open class MainActivity : BaseActivity(), DownloadServiceLi private fun setExtensionsBadge() { val updates = preferences.extensionUpdatesCount().getOrDefault() if (updates > 0) { - val badge = binding.bottomNav.getOrCreateBadge(R.id.nav_browse) + val badge = nav.getOrCreateBadge(R.id.nav_browse) badge.number = updates } else { - binding.bottomNav.removeBadge(R.id.nav_browse) + nav.removeBadge(R.id.nav_browse) } } @@ -455,7 +475,7 @@ open class MainActivity : BaseActivity(), DownloadServiceLi downloadManager.hasQueue() && !preferences.shownDownloadQueueTutorial().get() ) { if (!isBindingInitialized) return - val recentsItem = binding.bottomNav.getItemView(R.id.nav_recents) ?: return + val recentsItem = nav.getItemView(R.id.nav_recents) ?: return preferences.shownDownloadQueueTutorial().set(true) TapTargetView.showFor( this, @@ -474,7 +494,7 @@ open class MainActivity : BaseActivity(), DownloadServiceLi object : TapTargetView.Listener() { override fun onTargetClick(view: TapTargetView) { super.onTargetClick(view) - binding.bottomNav.selectedItemId = R.id.nav_recents + nav.selectedItemId = R.id.nav_recents } } ) @@ -540,14 +560,14 @@ open class MainActivity : BaseActivity(), DownloadServiceLi intent.getIntExtra("groupId", 0) ) when (intent.action) { - SHORTCUT_LIBRARY -> binding.bottomNav.selectedItemId = R.id.nav_library + SHORTCUT_LIBRARY -> nav.selectedItemId = R.id.nav_library SHORTCUT_RECENTLY_UPDATED, SHORTCUT_RECENTLY_READ -> { - if (binding.bottomNav.selectedItemId != R.id.nav_recents) { - binding.bottomNav.selectedItemId = R.id.nav_recents + if (nav.selectedItemId != R.id.nav_recents) { + nav.selectedItemId = R.id.nav_recents } else { router.popToRoot() } - binding.bottomNav.post { + nav.post { val controller = router.backstack.firstOrNull()?.controller as? RecentsController controller?.tempJumpTo( @@ -558,14 +578,14 @@ open class MainActivity : BaseActivity(), DownloadServiceLi ) } } - SHORTCUT_BROWSE -> binding.bottomNav.selectedItemId = R.id.nav_browse + SHORTCUT_BROWSE -> nav.selectedItemId = R.id.nav_browse SHORTCUT_EXTENSIONS -> { - if (binding.bottomNav.selectedItemId != R.id.nav_browse) { - binding.bottomNav.selectedItemId = R.id.nav_browse + if (nav.selectedItemId != R.id.nav_browse) { + nav.selectedItemId = R.id.nav_browse } else { router.popToRoot() } - binding.bottomNav.post { + nav.post { val controller = router.backstack.firstOrNull()?.controller as? BrowseController controller?.showSheet() @@ -573,25 +593,25 @@ open class MainActivity : BaseActivity(), DownloadServiceLi } SHORTCUT_MANGA -> { val extras = intent.extras ?: return false - if (router.backstack.isEmpty()) binding.bottomNav.selectedItemId = R.id.nav_library + if (router.backstack.isEmpty()) nav.selectedItemId = R.id.nav_library router.pushController(MangaDetailsController(extras).withFadeTransaction()) } SHORTCUT_UPDATE_NOTES -> { val extras = intent.extras ?: return false - if (router.backstack.isEmpty()) binding.bottomNav.selectedItemId = R.id.nav_library + if (router.backstack.isEmpty()) nav.selectedItemId = R.id.nav_library if (router.backstack.lastOrNull()?.controller !is AboutController.NewUpdateDialogController) { AboutController.NewUpdateDialogController(extras).showDialog(router) } } SHORTCUT_SOURCE -> { val extras = intent.extras ?: return false - if (router.backstack.isEmpty()) binding.bottomNav.selectedItemId = R.id.nav_library + if (router.backstack.isEmpty()) nav.selectedItemId = R.id.nav_library router.pushController(BrowseSourceController(extras).withFadeTransaction()) } SHORTCUT_DOWNLOADS -> { - binding.bottomNav.selectedItemId = R.id.nav_recents + nav.selectedItemId = R.id.nav_recents router.popToRoot() - binding.bottomNav.post { + nav.post { val controller = router.backstack.firstOrNull()?.controller as? RecentsController controller?.showSheet() @@ -619,7 +639,7 @@ open class MainActivity : BaseActivity(), DownloadServiceLi else !router.handleBack() ) { if (preferences.backReturnsToStart().get() && this !is SearchActivity && - startingTab() != binding.bottomNav.selectedItemId + startingTab() != nav.selectedItemId ) { goToStartingTab() } else { @@ -633,13 +653,16 @@ open class MainActivity : BaseActivity(), DownloadServiceLi } } + protected val nav: NavigationBarView + get() = binding.bottomNav ?: binding.sideNav!! + private fun setStartingTab() { if (this is SearchActivity) return - if (binding.bottomNav.selectedItemId != R.id.nav_browse && + if (nav.selectedItemId != R.id.nav_browse && preferences.startingTab().get() >= 0 ) { preferences.startingTab().set( - when (binding.bottomNav.selectedItemId) { + when (nav.selectedItemId) { R.id.nav_library -> 0 else -> 1 } @@ -658,7 +681,7 @@ open class MainActivity : BaseActivity(), DownloadServiceLi } private fun goToStartingTab() { - binding.bottomNav.selectedItemId = startingTab() + nav.selectedItemId = startingTab() } private fun setRoot(controller: Controller, id: Int) { @@ -750,27 +773,33 @@ open class MainActivity : BaseActivity(), DownloadServiceLi binding.cardToolbar.subtitle = null drawerArrow?.progress = 1f - binding.bottomNav.visibility = if (!hideBottomNav) View.VISIBLE else binding.bottomNav.visibility - animationSet?.cancel() - animationSet = AnimatorSet() - val alphaAnimation = ValueAnimator.ofFloat( - binding.bottomNav.alpha, - if (hideBottomNav) 0f else 1f - ) - alphaAnimation.addUpdateListener { valueAnimator -> - binding.bottomNav.alpha = valueAnimator.animatedValue as Float - } - alphaAnimation.addListener( - EndAnimatorListener { - binding.bottomNav.isVisible = !hideBottomNav - binding.bottomView.visibility = - if (hideBottomNav) View.GONE else binding.bottomView.visibility + nav.visibility = if (!hideBottomNav) View.VISIBLE else nav.visibility + if (isTablet()) { + nav.isVisible = !hideBottomNav + nav.alpha = 1f + } else { + animationSet?.cancel() + animationSet = AnimatorSet() + val alphaAnimation = ValueAnimator.ofFloat( + nav.alpha, + if (hideBottomNav) 0f else 1f + ) + alphaAnimation.addUpdateListener { valueAnimator -> + nav.alpha = valueAnimator.animatedValue as Float } - ) - alphaAnimation.duration = 200 - alphaAnimation.startDelay = 50 - animationSet?.playTogether(alphaAnimation) - animationSet?.start() + alphaAnimation.addListener( + EndAnimatorListener { + nav.isVisible = !hideBottomNav + binding.bottomView?.visibility = + if (hideBottomNav) View.GONE else binding.bottomView?.visibility + ?: View.GONE + } + ) + alphaAnimation.duration = 200 + alphaAnimation.startDelay = 50 + animationSet?.playTogether(alphaAnimation) + animationSet?.start() + } } fun showTabBar(show: Boolean, animate: Boolean = true) { @@ -810,10 +839,10 @@ open class MainActivity : BaseActivity(), DownloadServiceLi val hasQueue = downloading || downloadManager.hasQueue() launchUI { if (hasQueue) { - binding.bottomNav.getOrCreateBadge(R.id.nav_recents) + nav.getOrCreateBadge(R.id.nav_recents) showDLQueueTutorial() } else { - binding.bottomNav.removeBadge(R.id.nav_recents) + nav.removeBadge(R.id.nav_recents) } } } @@ -867,7 +896,7 @@ open class MainActivity : BaseActivity(), DownloadServiceLi val diffX = e2.x - e1.x if (abs(diffX) <= abs(diffY)) { val sheetRect = Rect() - binding.bottomNav.getGlobalVisibleRect(sheetRect) + binding.bottomNav?.getGlobalVisibleRect(sheetRect) if (sheetRect.contains(e1.x.toInt(), e1.y.toInt()) && abs(diffY) > Companion.SWIPE_THRESHOLD && abs(velocityY) > Companion.SWIPE_VELOCITY_THRESHOLD && diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt index 310ad71fe7..8b8a236845 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt @@ -51,8 +51,8 @@ class SearchActivity : MainActivity() { } } - override fun setFloatingToolbar(show: Boolean, solidBG: Boolean) { - super.setFloatingToolbar(show, solidBG) + override fun setFloatingToolbar(show: Boolean, solidBG: Boolean, changeBG: Boolean) { + super.setFloatingToolbar(show, solidBG, changeBG) currentToolbar?.setNavigationOnClickListener { popToRoot() } } @@ -73,8 +73,8 @@ class SearchActivity : MainActivity() { binding.toolbar.navigationIcon = drawerArrow drawerArrow?.progress = 1f - binding.bottomNav.isVisible = false - binding.bottomView.isVisible = false + nav.isVisible = false + binding.bottomView?.isVisible = false } override fun handleIntentAction(intent: Intent): Boolean { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt index d7d51fe93b..6dae64a1e5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt @@ -163,11 +163,11 @@ class RecentsController(bundle: Bundle? = null) : height = it.systemWindowInsetTop + (toolbarHeight ?: appBarHeight) } binding.recycler.updatePaddingRelative( - bottom = activityBinding?.bottomNav?.height ?: 0 + bottom = activityBinding?.bottomNav?.height ?: it.systemWindowInsetBottom ) binding.recentsEmptyView.updateLayoutParams { topMargin = headerHeight - bottomMargin = activityBinding?.bottomNav?.height ?: 0 + bottomMargin = activityBinding?.bottomNav?.height ?: it.systemWindowInsetBottom } }, onBottomNavUpdate = { @@ -182,10 +182,11 @@ class RecentsController(bundle: Bundle? = null) : } ) - activityBinding?.bottomNav?.post { - binding.recycler.updatePaddingRelative(bottom = activityBinding?.bottomNav?.height ?: 0) + activityBinding?.root?.post { + val height = activityBinding?.bottomNav?.height ?: view.rootWindowInsets?.systemWindowInsetBottom ?: 0 + binding.recycler.updatePaddingRelative(bottom = height) binding.downloadBottomSheet.dlRecycler.updatePaddingRelative( - bottom = activityBinding?.bottomNav?.height ?: 0 + bottom = height ) val isExpanded = binding.downloadBottomSheet.root.sheetBehavior.isExpanded() activityBinding?.tabsFrameLayout?.isVisible = !isExpanded diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt index 40834958ce..086bd00d3c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt @@ -22,17 +22,18 @@ import com.bluelinelabs.conductor.Controller import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.RouterTransaction -import com.bluelinelabs.conductor.changehandler.FadeChangeHandler import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.databinding.MainActivityBinding import eu.kanade.tachiyomi.ui.base.MaterialFastScroll +import eu.kanade.tachiyomi.ui.base.controller.OneWayFadeChangeHandler import eu.kanade.tachiyomi.ui.main.BottomSheetController import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.getResourceColor +import eu.kanade.tachiyomi.util.system.isTablet import eu.kanade.tachiyomi.util.system.toast import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy @@ -171,6 +172,9 @@ fun Controller.scrollViewWith( var statusBarHeight = -1 val tabBarHeight = 48.dpToPx activityBinding?.appBar?.y = 0f + activityBinding?.tabsFrameLayout?.elevation = 0f + val isTabletWithTabs = recycler.context.isTablet() && includeTabView + activityBinding?.tabShadow?.isVisible = isTabletWithTabs val attrsArray = intArrayOf(R.attr.actionBarSize) val array = recycler.context.obtainStyledAttributes(attrsArray) var appBarHeight = ( @@ -226,17 +230,33 @@ fun Controller.scrollViewWith( liftOnScroll.invoke(el) } else { elevationAnim?.cancel() - val floatingBar = (this as? FloatingSearchInterface)?.showFloatingBar() == true && !includeTabView + if (isTabletWithTabs && el) { + activityBinding?.tabShadow?.isVisible = true + } + val floatingBar = + (this as? FloatingSearchInterface)?.showFloatingBar() == true && !includeTabView if (floatingBar) { - activityBinding?.appBar?.elevation = 0f + if (isTabletWithTabs) { + activityBinding?.tabShadow?.alpha = 0f + } else { + activityBinding?.appBar?.elevation = 0f + } return@f } elevationAnim = ValueAnimator.ofFloat( - activityBinding?.appBar?.elevation ?: 0f, + if (isTabletWithTabs) { + (activityBinding?.tabShadow?.alpha ?: 0f) * 100 + } else { + activityBinding?.appBar?.elevation ?: 0f + }, if (el) 15f else 0f ) elevationAnim?.addUpdateListener { valueAnimator -> - activityBinding?.appBar?.elevation = valueAnimator.animatedValue as Float + if (isTabletWithTabs) { + activityBinding?.tabShadow?.alpha = valueAnimator.animatedValue as Float / 100 + } else { + activityBinding?.appBar?.elevation = valueAnimator.animatedValue as Float + } } elevationAnim?.start() } @@ -254,6 +274,7 @@ fun Controller.scrollViewWith( super.onChangeStart(controller, changeHandler, changeType) isInView = changeType.isEnter if (changeType.isEnter) { + activityBinding?.tabShadow?.isVisible = isTabletWithTabs elevateFunc(elevate) if (fakeToolbarView?.parent != null) { val parent = fakeToolbarView?.parent as? ViewGroup ?: return @@ -275,6 +296,7 @@ fun Controller.scrollViewWith( } } } else { + activityBinding?.tabShadow?.isVisible = false if (!customPadding && lastY == 0f && ( ( this@scrollViewWith !is FloatingSearchInterface && router.backstack.lastOrNull() @@ -318,6 +340,7 @@ fun Controller.scrollViewWith( recycler.post { elevateFunc(recycler.canScrollVertically(-1)) } + val isTablet = recycler.context.isTablet() recycler.addOnScrollListener( object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { @@ -336,7 +359,7 @@ fun Controller.scrollViewWith( .setDuration(shortAnimationDuration.toLong()) .start() if (router.backstackSize == 1 && isInView) { - activityBinding!!.bottomNav.let { + activityBinding!!.bottomNav?.let { val animator = it.animate()?.translationY(0f) ?.setDuration(shortAnimationDuration.toLong()) animator?.setUpdateListener { @@ -348,36 +371,43 @@ fun Controller.scrollViewWith( lastY = 0f if (elevate) elevateFunc(false) } else { - activityBinding!!.appBar.y -= dy - activityBinding!!.appBar.y = MathUtils.clamp( - activityBinding!!.appBar.y, - -activityBinding!!.appBar.height.toFloat(), - 0f - ) - val tabBar = activityBinding!!.bottomNav - if (tabBar.isVisible && isInView) { - if (preferences.hideBottomNavOnScroll().get()) { - tabBar.translationY += dy - tabBar.translationY = MathUtils.clamp( - tabBar.translationY, - 0f, - tabBar.height.toFloat() - ) - updateViewsNearBottom() - } else if (tabBar.translationY != 0f) { - tabBar.translationY = 0f - activityBinding!!.bottomView?.translationY = 0f - } - } - if (!elevate && ( - dy == 0 || - ( - activityBinding!!.appBar.y <= -activityBinding!!.appBar.height.toFloat() || - dy == 0 && activityBinding!!.appBar.y == 0f - ) + if (!isTablet) { + activityBinding!!.appBar.y -= dy + activityBinding!!.appBar.y = MathUtils.clamp( + activityBinding!!.appBar.y, + -activityBinding!!.appBar.height.toFloat(), + 0f ) - ) { - elevateFunc(true) + activityBinding!!.bottomNav?.let { bottomNav -> + if (bottomNav.isVisible && isInView) { + if (preferences.hideBottomNavOnScroll().get()) { + bottomNav.translationY += dy + bottomNav.translationY = MathUtils.clamp( + bottomNav.translationY, + 0f, + bottomNav.height.toFloat() + ) + updateViewsNearBottom() + } else if (bottomNav.translationY != 0f) { + bottomNav.translationY = 0f + activityBinding!!.bottomView?.translationY = 0f + } + } + } + + if (!elevate && ( + dy == 0 || + ( + activityBinding!!.appBar.y <= -activityBinding!!.appBar.height.toFloat() || + dy == 0 && activityBinding!!.appBar.y == 0f + ) + ) + ) { + elevateFunc(true) + } + } else { + val notAtTop = recycler.canScrollVertically(-1) + if (notAtTop != elevate) elevateFunc(notAtTop) } lastY = activityBinding!!.appBar.y } @@ -387,6 +417,9 @@ fun Controller.scrollViewWith( override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) if (newState == RecyclerView.SCROLL_STATE_IDLE) { + if (isTablet) { + return + } if (router?.backstack?.lastOrNull() ?.controller == this@scrollViewWith && statusBarHeight > -1 && activity != null && activityBinding!!.appBar.height > 0 && @@ -397,18 +430,20 @@ fun Controller.scrollViewWith( android.R.integer.config_shortAnimTime ) ?: 0 val closerToTop = abs(activityBinding!!.appBar.y) > halfWay - val halfWayBottom = activityBinding!!.bottomNav.height.toFloat() / 2 - val closerToBottom = activityBinding!!.bottomNav.translationY > halfWayBottom + val halfWayBottom = (activityBinding!!.bottomNav?.height?.toFloat() ?: 0f) / 2 + val closerToBottom = activityBinding!!.bottomNav?.translationY ?: 0f > halfWayBottom val atTop = !recycler.canScrollVertically(-1) val closerToEdge = - if (activityBinding!!.bottomNav.isVisible && + if (activityBinding!!.bottomNav?.isVisible == true && preferences.hideBottomNavOnScroll().get() ) closerToBottom else closerToTop lastY = if (closerToEdge && !atTop) (-activityBinding!!.appBar.height.toFloat()) else 0f activityBinding!!.appBar.animate().y(lastY) .setDuration(shortAnimationDuration.toLong()).start() - if (activityBinding!!.bottomNav.isVisible && isInView && preferences.hideBottomNavOnScroll().get()) { + if (activityBinding!!.bottomNav?.isVisible == true && + isInView && preferences.hideBottomNavOnScroll().get() + ) { activityBinding!!.bottomNav?.let { val lastBottomY = if (closerToEdge && !atTop) it.height.toFloat() else 0f @@ -447,8 +482,8 @@ fun Controller.requestPermissionsSafe(permissions: Array, requestCode: I fun Controller.withFadeTransaction(): RouterTransaction { return RouterTransaction.with(this) - .pushChangeHandler(FadeChangeHandler()) - .popChangeHandler(FadeChangeHandler()) + .pushChangeHandler(OneWayFadeChangeHandler()) + .popChangeHandler(OneWayFadeChangeHandler()) } fun Controller.openInBrowser(url: String) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index 9d1a94f026..1143f2cc04 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -31,10 +31,10 @@ import androidx.recyclerview.widget.LinearSmoothScroller import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.SmoothScroller import androidx.swiperefreshlayout.widget.SwipeRefreshLayout -import com.google.android.material.bottomnavigation.BottomNavigationItemView -import com.google.android.material.bottomnavigation.BottomNavigationMenuView -import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.button.MaterialButton +import com.google.android.material.navigation.NavigationBarItemView +import com.google.android.material.navigation.NavigationBarMenuView +import com.google.android.material.navigation.NavigationBarView import com.google.android.material.snackbar.Snackbar import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.lang.tintText @@ -259,9 +259,9 @@ fun TextView.setTextColorRes(@ColorRes id: Int) { } @SuppressLint("RestrictedApi") -fun BottomNavigationView.getItemView(@IdRes id: Int): BottomNavigationItemView? { +fun NavigationBarView.getItemView(@IdRes id: Int): NavigationBarItemView? { val order = (menu as MenuBuilder).findItemIndex(id) - return (getChildAt(0) as BottomNavigationMenuView).getChildAt(order) as? BottomNavigationItemView + return (getChildAt(0) as NavigationBarMenuView).getChildAt(order) as? NavigationBarItemView } fun RecyclerView.smoothScrollToTop() { diff --git a/app/src/main/res/drawable/shape_gradient_start_shadow.xml b/app/src/main/res/drawable/shape_gradient_start_shadow.xml new file mode 100644 index 0000000000..ea7f1f9ca4 --- /dev/null +++ b/app/src/main/res/drawable/shape_gradient_start_shadow.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/main_activity.xml b/app/src/main/res/layout-sw600dp/main_activity.xml new file mode 100644 index 0000000000..22a3b95ad3 --- /dev/null +++ b/app/src/main/res/layout-sw600dp/main_activity.xml @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +