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
This commit is contained in:
Ivan Iskandar 2021-12-19 02:16:26 +07:00 committed by GitHub
parent afc80d6a7c
commit 2ed01af723
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 318 additions and 226 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -99,6 +99,12 @@
</androidx.constraintlayout.widget.ConstraintLayout> </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 <include
android:id="@+id/fab_layout" android:id="@+id/fab_layout"
layout="@layout/main_activity_fab" /> layout="@layout/main_activity_fab" />

View file

@ -43,16 +43,6 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/chapters_item" /> 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> </androidx.constraintlayout.widget.ConstraintLayout>
</eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout> </eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout>

View file

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

View file

@ -27,13 +27,6 @@
</LinearLayout> </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 <eu.kanade.tachiyomi.widget.EmptyView
android:id="@+id/empty_view" android:id="@+id/empty_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -74,6 +74,12 @@
android:id="@+id/fab_layout" android:id="@+id/fab_layout"
layout="@layout/main_activity_fab" /> 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 <eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
android:id="@+id/bottom_nav" android:id="@+id/bottom_nav"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -33,11 +33,4 @@
app:fastScrollerBubbleEnabled="false" app:fastScrollerBubbleEnabled="false"
tools:visibility="visible" /> 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> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -27,13 +27,6 @@
app:fastScrollerBubbleEnabled="false" app:fastScrollerBubbleEnabled="false"
tools:visibility="visible" /> 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 <eu.kanade.tachiyomi.widget.EmptyView
android:id="@+id/empty_view" android:id="@+id/empty_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"