Bläddra i källkod

Action toolbar adjustments (#6353)

* Pair ActionToolbar with ActionMode

This makes ActionToolbar an activity object that can be configured in the
similar way as ActionMode

* Remove action toolbar workaround now that it stays in activity layout

5924

* Set status bar color when action mode is active

6256

* Adjust fab show timing after action mode finished

* Adjust action toolbar layout and animation

Default corner size and use bottom sheet animation

6069

* Adjust action toolbar layout on large screen

Right half of the screen
Ivan Iskandar 3 år sedan
förälder
incheckning
2ed01af723

+ 18 - 26
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt

@@ -7,7 +7,6 @@ import android.view.Menu
 import android.view.MenuInflater
 import android.view.MenuItem
 import android.view.View
-import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.view.ActionMode
 import androidx.core.view.isVisible
 import com.bluelinelabs.conductor.ControllerChangeHandler
@@ -16,7 +15,6 @@ import com.google.android.material.tabs.TabLayout
 import com.jakewharton.rxrelay.BehaviorRelay
 import com.jakewharton.rxrelay.PublishRelay
 import com.tfcporciuncula.flow.Preference
-import dev.chrisbanes.insetter.applyInsetter
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Category
 import eu.kanade.tachiyomi.data.database.models.Manga
@@ -35,6 +33,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
 import eu.kanade.tachiyomi.util.system.getResourceColor
 import eu.kanade.tachiyomi.util.system.openInBrowser
 import eu.kanade.tachiyomi.util.system.toast
+import eu.kanade.tachiyomi.widget.ActionModeWithToolbar
 import eu.kanade.tachiyomi.widget.EmptyView
 import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
 import kotlinx.coroutines.flow.drop
@@ -55,7 +54,7 @@ class LibraryController(
 ) : SearchableNucleusController<LibraryControllerBinding, LibraryPresenter>(bundle),
     RootController,
     TabbedController,
-    ActionMode.Callback,
+    ActionModeWithToolbar.Callback,
     ChangeMangaCategoriesDialog.Listener,
     DeleteLibraryMangasDialog.Listener {
 
@@ -67,7 +66,7 @@ class LibraryController(
     /**
      * Action mode for selections.
      */
-    private var actionMode: ActionMode? = null
+    private var actionMode: ActionModeWithToolbar? = null
 
     /**
      * Currently selected mangas.
@@ -170,12 +169,6 @@ class LibraryController(
     override fun onViewCreated(view: View) {
         super.onViewCreated(view)
 
-        binding.actionToolbar.applyInsetter {
-            type(navigationBars = true) {
-                margin(bottom = true, horizontal = true)
-            }
-        }
-
         adapter = LibraryAdapter(this)
         binding.libraryPager.adapter = adapter
         binding.libraryPager.pageSelections()
@@ -233,7 +226,6 @@ class LibraryController(
 
     override fun onDestroyView(view: View) {
         destroyActionModeIfNeeded()
-        binding.actionToolbar.destroy()
         adapter?.onDestroy()
         adapter = null
         settingsSheet = null
@@ -377,13 +369,10 @@ class LibraryController(
      * Creates the action mode if it's not created already.
      */
     fun createActionModeIfNeeded() {
-        if (actionMode == null) {
-            actionMode = (activity as AppCompatActivity).startSupportActionMode(this)
-            binding.actionToolbar.show(
-                actionMode!!,
-                R.menu.library_selection
-            ) { onActionItemClicked(it!!) }
-            (activity as? MainActivity)?.showBottomNav(false)
+        val activity = activity
+        if (actionMode == null && activity is MainActivity) {
+            actionMode = activity.startActionModeAndToolbar(this)
+            activity.showBottomNav(false)
         }
     }
 
@@ -455,6 +444,10 @@ class LibraryController(
         return true
     }
 
+    override fun onCreateActionToolbar(menuInflater: MenuInflater, menu: Menu) {
+        menuInflater.inflate(R.menu.library_selection, menu)
+    }
+
     override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
         val count = selectedMangas.size
         if (count == 0) {
@@ -462,17 +455,17 @@ class LibraryController(
             destroyActionModeIfNeeded()
         } else {
             mode.title = count.toString()
-
-            binding.actionToolbar.findItem(R.id.action_download_unread)?.isVisible = selectedMangas.any { it.source != LocalSource.ID }
         }
-        return false
+        return true
     }
 
-    override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
-        return onActionItemClicked(item)
+    override fun onPrepareActionToolbar(toolbar: ActionModeWithToolbar, menu: Menu) {
+        if (selectedMangas.isEmpty()) return
+        toolbar.findToolbarItem(R.id.action_download_unread)?.isVisible =
+            selectedMangas.any { it.source != LocalSource.ID }
     }
 
-    private fun onActionItemClicked(item: MenuItem): Boolean {
+    override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
         when (item.itemId) {
             R.id.action_move_to_category -> showChangeMangaCategoriesDialog()
             R.id.action_download_unread -> downloadUnreadChapters()
@@ -486,12 +479,11 @@ class LibraryController(
         return true
     }
 
-    override fun onDestroyActionMode(mode: ActionMode?) {
+    override fun onDestroyActionMode(mode: ActionMode) {
         // Clear all the manga selections and notify child views.
         selectedMangas.clear()
         selectionRelay.call(LibrarySelectionEvent.Cleared())
 
-        binding.actionToolbar.hide()
         (activity as? MainActivity)?.showBottomNav(true)
 
         actionMode = null

+ 14 - 16
app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt

@@ -12,13 +12,13 @@ import android.view.Window
 import android.widget.Toast
 import androidx.appcompat.view.ActionMode
 import androidx.core.animation.doOnEnd
+import androidx.core.graphics.ColorUtils
 import androidx.core.splashscreen.SplashScreen
 import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
 import androidx.core.view.ViewCompat
 import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.isVisible
-import androidx.core.view.updateLayoutParams
 import androidx.interpolator.view.animation.FastOutSlowInInterpolator
 import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
 import androidx.lifecycle.lifecycleScope
@@ -27,7 +27,6 @@ import com.bluelinelabs.conductor.Conductor
 import com.bluelinelabs.conductor.Controller
 import com.bluelinelabs.conductor.ControllerChangeHandler
 import com.bluelinelabs.conductor.Router
-import com.google.android.material.appbar.AppBarLayout
 import com.google.android.material.navigation.NavigationBarView
 import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
 import dev.chrisbanes.insetter.applyInsetter
@@ -63,10 +62,12 @@ 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.getThemeColor
 import eu.kanade.tachiyomi.util.system.isTablet
 import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.system.toast
 import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
+import eu.kanade.tachiyomi.widget.ActionModeWithToolbar
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.drop
 import kotlinx.coroutines.flow.launchIn
@@ -482,7 +483,11 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
             tag = isTransparentWhenNotLifted
             isTransparentWhenNotLifted = false
         }
-        setToolbarScrolls(false)
+        // Color taken from m3_appbar_background
+        window.statusBarColor = ColorUtils.compositeColors(
+            getColor(R.color.m3_appbar_overlay_color),
+            getThemeColor(R.attr.colorSurface)
+        )
         super.onSupportActionModeStarted(mode)
     }
 
@@ -491,10 +496,15 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
             isTransparentWhenNotLifted = (tag as? Boolean) ?: false
             tag = null
         }
-        setToolbarScrolls(true)
+        window.statusBarColor = getThemeColor(android.R.attr.statusBarColor)
         super.onSupportActionModeFinished(mode)
     }
 
+    fun startActionModeAndToolbar(modeCallback: ActionModeWithToolbar.Callback): ActionModeWithToolbar {
+        binding.actionToolbar.start(modeCallback)
+        return binding.actionToolbar
+    }
+
     private suspend fun resetExitConfirmation() {
         isConfirmingExit = true
         val toast = toast(R.string.confirm_exit, Toast.LENGTH_LONG)
@@ -606,18 +616,6 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
         binding.sideNav?.isVisible = visible
     }
 
-    /**
-     * Sets toolbar CoordinatorLayout scroll flags
-     */
-    private fun setToolbarScrolls(enabled: Boolean) = binding.toolbar.updateLayoutParams<AppBarLayout.LayoutParams> {
-        if (isTablet()) return@updateLayoutParams
-        scrollFlags = if (enabled) {
-            AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
-        } else {
-            0
-        }
-    }
-
     private val nav: NavigationBarView
         get() = binding.bottomNav ?: binding.sideNav!!
 

+ 28 - 29
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt

@@ -14,7 +14,6 @@ import android.view.MenuItem
 import android.view.View
 import android.view.ViewGroup
 import androidx.annotation.FloatRange
-import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.view.ActionMode
 import androidx.core.os.bundleOf
 import androidx.core.view.ViewCompat
@@ -91,6 +90,7 @@ import eu.kanade.tachiyomi.util.system.toShareIntent
 import eu.kanade.tachiyomi.util.system.toast
 import eu.kanade.tachiyomi.util.view.shrinkOnScroll
 import eu.kanade.tachiyomi.util.view.snack
+import eu.kanade.tachiyomi.widget.ActionModeWithToolbar
 import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -106,7 +106,7 @@ import kotlin.math.min
 class MangaController :
     NucleusController<MangaControllerBinding, MangaPresenter>,
     FabController,
-    ActionMode.Callback,
+    ActionModeWithToolbar.Callback,
     FlexibleAdapter.OnItemClickListener,
     FlexibleAdapter.OnItemLongClickListener,
     BaseChaptersAdapter.OnChapterClickListener,
@@ -161,7 +161,7 @@ class MangaController :
     /**
      * Action mode for multiple selection.
      */
-    private var actionMode: ActionMode? = null
+    private var actionMode: ActionModeWithToolbar? = null
 
     /**
      * Selected items. Used to restore selections after a rotation.
@@ -238,11 +238,6 @@ class MangaController :
                 it.layoutManager = LinearLayoutManager(view.context)
                 it.setHasFixedSize(true)
             }
-        binding.actionToolbar.applyInsetter {
-            type(navigationBars = true) {
-                margin(bottom = true, horizontal = true)
-            }
-        }
 
         if (manga == null || source == null) return
 
@@ -382,16 +377,19 @@ class MangaController :
         val context = view?.context ?: return
         val adapter = chaptersAdapter ?: return
         val fab = actionFab ?: return
-        fab.isVisible = adapter.items.any { !it.read }
         if (adapter.items.any { it.read }) {
             fab.text = context.getString(R.string.action_resume)
         }
+        if (adapter.items.any { !it.read }) {
+            fab.show()
+        } else {
+            fab.hide()
+        }
     }
 
     override fun onDestroyView(view: View) {
         recyclerViewUpdatesToolbarTitleAlpha(false)
         destroyActionModeIfNeeded()
-        binding.actionToolbar.destroy()
         mangaInfoAdapter = null
         chaptersHeaderAdapter = null
         chaptersAdapter = null
@@ -978,11 +976,7 @@ class MangaController :
 
     private fun createActionModeIfNeeded() {
         if (actionMode == null) {
-            actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this)
-            binding.actionToolbar.show(
-                actionMode!!,
-                R.menu.chapter_selection
-            ) { onActionItemClicked(it!!) }
+            actionMode = (activity as MainActivity).startActionModeAndToolbar(this)
         }
     }
 
@@ -998,6 +992,10 @@ class MangaController :
         return true
     }
 
+    override fun onCreateActionToolbar(menuInflater: MenuInflater, menu: Menu) {
+        menuInflater.inflate(R.menu.chapter_selection, menu)
+    }
+
     override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
         val count = chaptersAdapter?.selectedItemCount ?: 0
         if (count == 0) {
@@ -1006,25 +1004,24 @@ class MangaController :
         } else {
             mode.title = count.toString()
 
-            val chapters = getSelectedChapters()
-            binding.actionToolbar.findItem(R.id.action_download)?.isVisible = !isLocalSource && chapters.any { !it.isDownloaded }
-            binding.actionToolbar.findItem(R.id.action_delete)?.isVisible = !isLocalSource && chapters.any { it.isDownloaded }
-            binding.actionToolbar.findItem(R.id.action_bookmark)?.isVisible = chapters.any { !it.chapter.bookmark }
-            binding.actionToolbar.findItem(R.id.action_remove_bookmark)?.isVisible = chapters.all { it.chapter.bookmark }
-            binding.actionToolbar.findItem(R.id.action_mark_as_read)?.isVisible = chapters.any { !it.chapter.read }
-            binding.actionToolbar.findItem(R.id.action_mark_as_unread)?.isVisible = chapters.all { it.chapter.read }
-
             // Hide FAB to avoid interfering with the bottom action toolbar
-            actionFab?.isVisible = false
+            actionFab?.hide()
         }
-        return false
+        return true
     }
 
-    override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
-        return onActionItemClicked(item)
+    override fun onPrepareActionToolbar(toolbar: ActionModeWithToolbar, menu: Menu) {
+        val chapters = getSelectedChapters()
+        if (chapters.isEmpty()) return
+        toolbar.findToolbarItem(R.id.action_download)?.isVisible = !isLocalSource && chapters.any { !it.isDownloaded }
+        toolbar.findToolbarItem(R.id.action_delete)?.isVisible = !isLocalSource && chapters.any { it.isDownloaded }
+        toolbar.findToolbarItem(R.id.action_bookmark)?.isVisible = chapters.any { !it.chapter.bookmark }
+        toolbar.findToolbarItem(R.id.action_remove_bookmark)?.isVisible = chapters.all { it.chapter.bookmark }
+        toolbar.findToolbarItem(R.id.action_mark_as_read)?.isVisible = chapters.any { !it.chapter.read }
+        toolbar.findToolbarItem(R.id.action_mark_as_unread)?.isVisible = chapters.all { it.chapter.read }
     }
 
-    private fun onActionItemClicked(item: MenuItem): Boolean {
+    override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
         when (item.itemId) {
             R.id.action_select_all -> selectAll()
             R.id.action_select_inverse -> selectInverse()
@@ -1041,11 +1038,13 @@ class MangaController :
     }
 
     override fun onDestroyActionMode(mode: ActionMode) {
-        binding.actionToolbar.hide()
         chaptersAdapter?.mode = SelectableAdapter.Mode.SINGLE
         chaptersAdapter?.clearSelection()
         selectedChapters.clear()
         actionMode = null
+    }
+
+    override fun onDestroyActionToolbar() {
         updateFabVisibility()
     }
 

+ 23 - 28
app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt

@@ -5,7 +5,6 @@ import android.view.Menu
 import android.view.MenuInflater
 import android.view.MenuItem
 import android.view.View
-import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.view.ActionMode
 import androidx.recyclerview.widget.LinearLayoutManager
 import dev.chrisbanes.insetter.applyInsetter
@@ -29,6 +28,7 @@ import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.system.notificationManager
 import eu.kanade.tachiyomi.util.system.toast
 import eu.kanade.tachiyomi.util.view.onAnimationsFinished
+import eu.kanade.tachiyomi.widget.ActionModeWithToolbar
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import logcat.LogPriority
@@ -41,7 +41,7 @@ import reactivecircus.flowbinding.swiperefreshlayout.refreshes
 class UpdatesController :
     NucleusController<UpdatesControllerBinding, UpdatesPresenter>(),
     RootController,
-    ActionMode.Callback,
+    ActionModeWithToolbar.Callback,
     FlexibleAdapter.OnItemClickListener,
     FlexibleAdapter.OnItemLongClickListener,
     FlexibleAdapter.OnUpdateListener,
@@ -52,7 +52,7 @@ class UpdatesController :
     /**
      * Action mode for multiple selection.
      */
-    private var actionMode: ActionMode? = null
+    private var actionMode: ActionModeWithToolbar? = null
 
     /**
      * Adapter containing the recent chapters.
@@ -81,11 +81,6 @@ class UpdatesController :
                 padding()
             }
         }
-        binding.actionToolbar.applyInsetter {
-            type(navigationBars = true) {
-                margin(bottom = true, horizontal = true)
-            }
-        }
 
         view.context.notificationManager.cancel(Notifications.ID_NEW_CHAPTERS)
 
@@ -118,7 +113,6 @@ class UpdatesController :
 
     override fun onDestroyView(view: View) {
         destroyActionModeIfNeeded()
-        binding.actionToolbar.destroy()
         adapter = null
         super.onDestroyView(view)
     }
@@ -175,15 +169,11 @@ class UpdatesController :
      * @param position position of clicked item
      */
     override fun onItemLongClick(position: Int) {
-        if (actionMode == null) {
-            actionMode = (activity as AppCompatActivity).startSupportActionMode(this)
-            binding.actionToolbar.show(
-                actionMode!!,
-                R.menu.updates_chapter_selection
-            ) { onActionItemClicked(it!!) }
-            (activity as? MainActivity)?.showBottomNav(false)
+        val activity = activity
+        if (actionMode == null && activity is MainActivity) {
+            actionMode = activity.startActionModeAndToolbar(this)
+            activity.showBottomNav(false)
         }
-
         toggleSelection(position)
     }
 
@@ -341,6 +331,10 @@ class UpdatesController :
         return true
     }
 
+    override fun onCreateActionToolbar(menuInflater: MenuInflater, menu: Menu) {
+        menuInflater.inflate(R.menu.updates_chapter_selection, menu)
+    }
+
     override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
         val count = adapter?.selectedItemCount ?: 0
         if (count == 0) {
@@ -348,17 +342,19 @@ class UpdatesController :
             destroyActionModeIfNeeded()
         } else {
             mode.title = count.toString()
-
-            val chapters = getSelectedChapters()
-            binding.actionToolbar.findItem(R.id.action_download)?.isVisible = chapters.any { !it.isDownloaded }
-            binding.actionToolbar.findItem(R.id.action_delete)?.isVisible = chapters.any { it.isDownloaded }
-            binding.actionToolbar.findItem(R.id.action_bookmark)?.isVisible = chapters.any { !it.bookmark }
-            binding.actionToolbar.findItem(R.id.action_remove_bookmark)?.isVisible = chapters.all { it.bookmark }
-            binding.actionToolbar.findItem(R.id.action_mark_as_read)?.isVisible = chapters.any { !it.chapter.read }
-            binding.actionToolbar.findItem(R.id.action_mark_as_unread)?.isVisible = chapters.all { it.chapter.read }
         }
+        return true
+    }
 
-        return false
+    override fun onPrepareActionToolbar(toolbar: ActionModeWithToolbar, menu: Menu) {
+        val chapters = getSelectedChapters()
+        if (chapters.isEmpty()) return
+        toolbar.findToolbarItem(R.id.action_download)?.isVisible = chapters.any { !it.isDownloaded }
+        toolbar.findToolbarItem(R.id.action_delete)?.isVisible = chapters.any { it.isDownloaded }
+        toolbar.findToolbarItem(R.id.action_bookmark)?.isVisible = chapters.any { !it.bookmark }
+        toolbar.findToolbarItem(R.id.action_remove_bookmark)?.isVisible = chapters.all { it.bookmark }
+        toolbar.findToolbarItem(R.id.action_mark_as_read)?.isVisible = chapters.any { !it.chapter.read }
+        toolbar.findToolbarItem(R.id.action_mark_as_unread)?.isVisible = chapters.all { it.chapter.read }
     }
 
     /**
@@ -391,11 +387,10 @@ class UpdatesController :
      * Called when ActionMode destroyed
      * @param mode the ActionMode object
      */
-    override fun onDestroyActionMode(mode: ActionMode?) {
+    override fun onDestroyActionMode(mode: ActionMode) {
         adapter?.mode = SelectableAdapter.Mode.IDLE
         adapter?.clearSelection()
 
-        binding.actionToolbar.hide()
         (activity as? MainActivity)?.showBottomNav(true)
 
         actionMode = null

+ 168 - 0
app/src/main/java/eu/kanade/tachiyomi/widget/ActionModeWithToolbar.kt

@@ -0,0 +1,168 @@
+package eu.kanade.tachiyomi.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.animation.Animation
+import android.view.animation.AnimationUtils
+import android.widget.FrameLayout
+import androidx.annotation.IdRes
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.view.ActionMode
+import androidx.core.view.isVisible
+import dev.chrisbanes.insetter.applyInsetter
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.databinding.ActionToolbarBinding
+import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
+import eu.kanade.tachiyomi.widget.listener.SimpleAnimationListener
+
+/**
+ * A toolbar holding only menu items. This view is supposed to be paired with [AppCompatActivity]'s [ActionMode].
+ *
+ * @see Callback
+ */
+class ActionModeWithToolbar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+    FrameLayout(context, attrs) {
+
+    init {
+        clipToPadding = false
+        applyInsetter {
+            type(navigationBars = true) {
+                padding(bottom = true, horizontal = true)
+            }
+        }
+    }
+
+    private val binding = ActionToolbarBinding.inflate(LayoutInflater.from(context), this, true)
+
+    private var callback: Callback? = null
+
+    private var actionMode: ActionMode? = null
+    private val actionModeCallback = object : ActionMode.Callback {
+        override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
+            callback?.onCreateActionToolbar(mode.menuInflater, binding.menu.menu)
+            binding.menu.setOnMenuItemClickListener { onActionItemClicked(mode, it) }
+            binding.root.isVisible = true
+            val bottomAnimation = AnimationUtils.loadAnimation(context, R.anim.bottom_sheet_slide_in)
+            bottomAnimation.applySystemAnimatorScale(context)
+            binding.root.startAnimation(bottomAnimation)
+
+            return callback?.onCreateActionMode(mode, menu) ?: false
+        }
+
+        override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
+            callback?.onPrepareActionToolbar(this@ActionModeWithToolbar, binding.menu.menu)
+            return callback?.onPrepareActionMode(mode, menu) ?: false
+        }
+
+        override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
+            return callback?.onActionItemClicked(mode, item) ?: false
+        }
+
+        override fun onDestroyActionMode(mode: ActionMode) {
+            callback?.onDestroyActionMode(mode)
+
+            val bottomAnimation = AnimationUtils.loadAnimation(context, R.anim.bottom_sheet_slide_out).apply {
+                applySystemAnimatorScale(context)
+                setAnimationListener(
+                    object : SimpleAnimationListener() {
+                        override fun onAnimationEnd(animation: Animation) {
+                            binding.root.isVisible = false
+                            binding.menu.menu.clear()
+                            binding.menu.setOnMenuItemClickListener(null)
+
+                            callback?.onDestroyActionToolbar()
+                            callback = null
+                            actionMode = null
+                        }
+                    }
+                )
+            }
+            binding.root.startAnimation(bottomAnimation)
+        }
+    }
+
+    fun start(callback: Callback) {
+        val context = context
+        if (context !is AppCompatActivity) {
+            throw IllegalStateException("AppCompatActivity is needed to start this view")
+        }
+        if (actionMode == null) {
+            this.callback = callback
+            actionMode = context.startSupportActionMode(actionModeCallback)
+        }
+    }
+
+    fun finish() {
+        actionMode?.finish()
+    }
+
+    /**
+     * Gets a menu item if found.
+     */
+    fun findToolbarItem(@IdRes itemId: Int): MenuItem? {
+        return binding.menu.menu.findItem(itemId)
+    }
+
+    override fun invalidate() {
+        super.invalidate()
+        actionMode?.invalidate()
+    }
+
+    interface Callback {
+        /**
+         * Called when action mode is first created. The menu supplied will be used to
+         * generate action buttons for the action mode.
+         *
+         * @param mode ActionMode being created
+         * @param menu Menu used to populate action buttons
+         * @return true if the action mode should be created, false if entering this
+         * mode should be aborted.
+         */
+        fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean
+
+        /**
+         * [onCreateActionMode] but for the bottom toolbar
+         */
+        fun onCreateActionToolbar(menuInflater: MenuInflater, menu: Menu)
+
+        /**
+         * Called to refresh an action mode's action menu whenever it is invalidated.
+         *
+         * @param mode ActionMode being prepared
+         * @param menu Menu used to populate action buttons
+         * @return true if the menu or action mode was updated, false otherwise.
+         */
+        fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean
+
+        /**
+         * [onPrepareActionMode] but for the bottom toolbar
+         */
+        fun onPrepareActionToolbar(toolbar: ActionModeWithToolbar, menu: Menu)
+
+        /**
+         * Called to report a user click on an action button.
+         *
+         * @param mode The current ActionMode
+         * @param item The item that was clicked
+         * @return true if this callback handled the event, false if the standard MenuItem
+         * invocation should continue.
+         */
+        fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean
+
+        /**
+         * Called when an action mode is about to be exited and destroyed.
+         *
+         * @param mode The current ActionMode being destroyed
+         */
+        fun onDestroyActionMode(mode: ActionMode)
+
+        /**
+         * Called when the action toolbar is finished exiting
+         */
+        fun onDestroyActionToolbar() {}
+    }
+}

+ 0 - 73
app/src/main/java/eu/kanade/tachiyomi/widget/ActionToolbar.kt

@@ -1,73 +0,0 @@
-package eu.kanade.tachiyomi.widget
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.LayoutInflater
-import android.view.MenuItem
-import android.view.animation.Animation
-import android.view.animation.AnimationUtils
-import android.widget.FrameLayout
-import androidx.annotation.IdRes
-import androidx.annotation.MenuRes
-import androidx.appcompat.view.ActionMode
-import androidx.core.view.isVisible
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.databinding.ActionToolbarBinding
-import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
-import eu.kanade.tachiyomi.widget.listener.SimpleAnimationListener
-
-/**
- * A toolbar holding only menu items.
- */
-class ActionToolbar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
-    FrameLayout(context, attrs) {
-
-    private val binding = ActionToolbarBinding.inflate(LayoutInflater.from(context), this, true)
-
-    /**
-     * Remove menu items and remove listener.
-     */
-    fun destroy() {
-        binding.menu.menu.clear()
-        binding.menu.setOnMenuItemClickListener(null)
-    }
-
-    /**
-     * Gets a menu item if found.
-     */
-    fun findItem(@IdRes itemId: Int): MenuItem? {
-        return binding.menu.menu.findItem(itemId)
-    }
-
-    /**
-     * Show the menu toolbar using the provided ActionMode's context to inflate the items.
-     */
-    fun show(mode: ActionMode, @MenuRes menuRes: Int, listener: (item: MenuItem?) -> Boolean) {
-        // Avoid re-inflating the menu
-        if (binding.menu.menu.size() == 0) {
-            mode.menuInflater.inflate(menuRes, binding.menu.menu)
-            binding.menu.setOnMenuItemClickListener { listener(it) }
-        }
-
-        binding.actionToolbar.isVisible = true
-        val bottomAnimation = AnimationUtils.loadAnimation(context, R.anim.enter_from_bottom)
-        bottomAnimation.applySystemAnimatorScale(context)
-        binding.actionToolbar.startAnimation(bottomAnimation)
-    }
-
-    /**
-     * Hide the menu toolbar.
-     */
-    fun hide() {
-        val bottomAnimation = AnimationUtils.loadAnimation(context, R.anim.exit_to_bottom)
-        bottomAnimation.applySystemAnimatorScale(context)
-        bottomAnimation.setAnimationListener(
-            object : SimpleAnimationListener() {
-                override fun onAnimationEnd(animation: Animation) {
-                    binding.actionToolbar.isVisible = false
-                }
-            }
-        )
-        binding.actionToolbar.startAnimation(bottomAnimation)
-    }
-}

+ 40 - 0
app/src/main/res/layout-sw720dp/action_toolbar.xml

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clipToPadding="false"
+    android:visibility="gone"
+    tools:visibility="visible">
+
+    <com.google.android.material.card.MaterialCardView
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_margin="12dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="1.0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintWidth_default="percent"
+        app:layout_constraintWidth_percent=".5">
+
+        <com.google.android.material.appbar.MaterialToolbar
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:layout_gravity="bottom"
+            app:contentInsetEnd="8dp"
+            app:contentInsetStart="8dp">
+
+            <androidx.appcompat.widget.ActionMenuView
+                android:id="@+id/menu"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_gravity="center" />
+
+        </com.google.android.material.appbar.MaterialToolbar>
+
+    </com.google.android.material.card.MaterialCardView>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 6 - 0
app/src/main/res/layout-sw720dp/main_activity.xml

@@ -99,6 +99,12 @@
 
     </androidx.constraintlayout.widget.ConstraintLayout>
 
+    <eu.kanade.tachiyomi.widget.ActionModeWithToolbar
+        android:id="@+id/action_toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom" />
+
     <include
         android:id="@+id/fab_layout"
         layout="@layout/main_activity_fab" />

+ 0 - 10
app/src/main/res/layout-sw720dp/manga_controller.xml

@@ -43,16 +43,6 @@
                 app:layout_constraintTop_toTopOf="parent"
                 tools:listitem="@layout/chapters_item" />
 
-            <eu.kanade.tachiyomi.widget.ActionToolbar
-                android:id="@+id/action_toolbar"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_gravity="bottom"
-                app:layout_constraintBottom_toBottomOf="parent"
-                app:layout_constraintEnd_toEndOf="@+id/chapters_recycler"
-                app:layout_constraintStart_toStartOf="@+id/chapters_recycler"
-                app:layout_dodgeInsetEdges="bottom" />
-
         </androidx.constraintlayout.widget.ConstraintLayout>
 
     </eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout>

+ 13 - 21
app/src/main/res/layout/action_toolbar.xml

@@ -1,35 +1,27 @@
 <?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.google.android.material.card.MaterialCardView 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/action_toolbar"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:layout_margin="12dp"
     android:clipToPadding="false"
-    android:padding="8dp"
     android:visibility="gone"
     tools:visibility="visible">
 
-    <com.google.android.material.card.MaterialCardView
+    <com.google.android.material.appbar.MaterialToolbar
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        app:cardCornerRadius="@dimen/dialog_radius">
+        android:layout_height="?attr/actionBarSize"
+        android:layout_gravity="bottom"
+        app:contentInsetEnd="8dp"
+        app:contentInsetStart="8dp">
 
-        <com.google.android.material.appbar.MaterialToolbar
+        <androidx.appcompat.widget.ActionMenuView
+            android:id="@+id/menu"
             android:layout_width="match_parent"
-            android:layout_height="?attr/actionBarSize"
-            android:layout_gravity="bottom"
-            app:contentInsetEnd="8dp"
-            app:contentInsetStart="8dp">
+            android:layout_height="match_parent"
+            android:layout_gravity="center" />
 
-            <androidx.appcompat.widget.ActionMenuView
-                android:id="@+id/menu"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_gravity="center" />
+    </com.google.android.material.appbar.MaterialToolbar>
 
-        </com.google.android.material.appbar.MaterialToolbar>
-
-    </com.google.android.material.card.MaterialCardView>
-
-</FrameLayout>
+</com.google.android.material.card.MaterialCardView>

+ 0 - 7
app/src/main/res/layout/library_controller.xml

@@ -27,13 +27,6 @@
 
     </LinearLayout>
 
-    <eu.kanade.tachiyomi.widget.ActionToolbar
-        android:id="@+id/action_toolbar"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="bottom"
-        app:layout_dodgeInsetEdges="bottom" />
-
     <eu.kanade.tachiyomi.widget.EmptyView
         android:id="@+id/empty_view"
         android:layout_width="wrap_content"

+ 6 - 0
app/src/main/res/layout/main_activity.xml

@@ -74,6 +74,12 @@
         android:id="@+id/fab_layout"
         layout="@layout/main_activity_fab" />
 
+    <eu.kanade.tachiyomi.widget.ActionModeWithToolbar
+        android:id="@+id/action_toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom" />
+
     <eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
         android:id="@+id/bottom_nav"
         android:layout_width="match_parent"

+ 0 - 7
app/src/main/res/layout/manga_controller.xml

@@ -33,11 +33,4 @@
         app:fastScrollerBubbleEnabled="false"
         tools:visibility="visible" />
 
-    <eu.kanade.tachiyomi.widget.ActionToolbar
-        android:id="@+id/action_toolbar"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="bottom"
-        app:layout_dodgeInsetEdges="bottom" />
-
 </androidx.coordinatorlayout.widget.CoordinatorLayout>

+ 0 - 7
app/src/main/res/layout/updates_controller.xml

@@ -27,13 +27,6 @@
             app:fastScrollerBubbleEnabled="false"
             tools:visibility="visible" />
 
-        <eu.kanade.tachiyomi.widget.ActionToolbar
-            android:id="@+id/action_toolbar"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_gravity="bottom"
-            app:layout_dodgeInsetEdges="bottom" />
-
         <eu.kanade.tachiyomi.widget.EmptyView
             android:id="@+id/empty_view"
             android:layout_width="wrap_content"