diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/FabController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/FabController.kt new file mode 100644 index 0000000000..23b8c579ac --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/FabController.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.ui.base.controller + +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton + +interface FabController { + + fun configureFab(fab: ExtendedFloatingActionButton) {} + + fun cleanupFab(fab: ExtendedFloatingActionButton) {} +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt index 93fbe2fe63..31a1108432 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt @@ -15,6 +15,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.list.listItems +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.snackbar.Snackbar import com.tfcporciuncula.flow.Preference import eu.davidea.flexibleadapter.FlexibleAdapter @@ -30,10 +31,10 @@ import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.online.HttpSource +import eu.kanade.tachiyomi.ui.base.controller.FabController import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog -import eu.kanade.tachiyomi.ui.main.offsetAppbarHeight import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.util.system.connectivityManager @@ -62,6 +63,7 @@ import uy.kohesive.injekt.injectLazy */ open class BrowseSourceController(bundle: Bundle) : NucleusController(bundle), + FabController, FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.EndlessScrollListener, @@ -84,6 +86,9 @@ open class BrowseSourceController(bundle: Bundle) : */ private var adapter: FlexibleAdapter>? = null + private var actionFab: ExtendedFloatingActionButton? = null + private var actionFabScrollListener: RecyclerView.OnScrollListener? = null + /** * Snackbar containing an error message when a request fails. */ @@ -162,13 +167,27 @@ open class BrowseSourceController(bundle: Bundle) : filterSheet?.setFilters(presenter.filterItems) // TODO: [ExtendedFloatingActionButton] hide/show methods don't work properly - filterSheet?.setOnShowListener { binding.fabFilter.gone() } - filterSheet?.setOnDismissListener { binding.fabFilter.visible() } + filterSheet?.setOnShowListener { actionFab?.gone() } + filterSheet?.setOnDismissListener { actionFab?.visible() } - binding.fabFilter.setOnClickListener { filterSheet?.show() } + actionFab?.setOnClickListener { filterSheet?.show() } - binding.fabFilter.offsetAppbarHeight(activity!!) - binding.fabFilter.visible() + actionFab?.visible() + } + + override fun configureFab(fab: ExtendedFloatingActionButton) { + actionFab = fab + + // Controlled by initFilterSheet() + fab.gone() + + fab.setText(R.string.action_filter) + fab.setIconResource(R.drawable.ic_filter_list_24dp) + } + + override fun cleanupFab(fab: ExtendedFloatingActionButton) { + actionFabScrollListener?.let { recycler?.removeOnScrollListener(it) } + actionFab = null } override fun onDestroyView(view: View) { @@ -228,7 +247,7 @@ open class BrowseSourceController(bundle: Bundle) : ) recycler.clipToPadding = false - binding.fabFilter.shrinkOnScroll(recycler) + actionFab?.shrinkOnScroll(recycler) } recycler.setHasFixedSize(true) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt index 4ed9d86a38..cce0b0203c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt @@ -9,6 +9,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.snackbar.Snackbar import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.SelectableAdapter @@ -16,9 +17,10 @@ import eu.davidea.flexibleadapter.helpers.UndoHelper import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.databinding.CategoriesControllerBinding +import eu.kanade.tachiyomi.ui.base.controller.FabController import eu.kanade.tachiyomi.ui.base.controller.NucleusController -import eu.kanade.tachiyomi.ui.main.offsetAppbarHeight import eu.kanade.tachiyomi.util.system.toast +import eu.kanade.tachiyomi.util.view.shrinkOnScroll import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import reactivecircus.flowbinding.android.view.clicks @@ -28,6 +30,7 @@ import reactivecircus.flowbinding.android.view.clicks */ class CategoryController : NucleusController(), + FabController, ActionMode.Callback, FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, @@ -46,6 +49,9 @@ class CategoryController : */ private var adapter: CategoryAdapter? = null + private var actionFab: ExtendedFloatingActionButton? = null + private var actionFabScrollListener: RecyclerView.OnScrollListener? = null + /** * Undo helper used for restoring a deleted category. */ @@ -89,13 +95,23 @@ class CategoryController : adapter?.isHandleDragEnabled = true adapter?.isPermanentDelete = false - binding.fab.clicks() + actionFabScrollListener = actionFab?.shrinkOnScroll(binding.recycler) + } + + override fun configureFab(fab: ExtendedFloatingActionButton) { + actionFab = fab + fab.setText(R.string.action_add) + fab.setIconResource(R.drawable.ic_add_24dp) + fab.clicks() .onEach { CategoryCreateDialog(this@CategoryController).showDialog(router, null) } .launchIn(scope) + } - binding.fab.offsetAppbarHeight(activity!!) + override fun cleanupFab(fab: ExtendedFloatingActionButton) { + actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) } + actionFab = null } /** @@ -181,7 +197,7 @@ class CategoryController : R.id.action_delete -> { undoHelper = UndoHelper(adapter, this) undoHelper?.start( - adapter.selectedPositions, view!!, + adapter.selectedPositions, activity!!.findViewById(R.id.root_coordinator), R.string.snack_categories_deleted, R.string.action_undo, 3000 ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt index 26a5d894bc..15ddd84b7c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt @@ -7,13 +7,18 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.databinding.DownloadControllerBinding import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.ui.base.controller.FabController import eu.kanade.tachiyomi.ui.base.controller.NucleusController -import eu.kanade.tachiyomi.ui.main.offsetAppbarHeight +import eu.kanade.tachiyomi.util.view.gone +import eu.kanade.tachiyomi.util.view.shrinkOnScroll +import eu.kanade.tachiyomi.util.view.visible import java.util.concurrent.TimeUnit import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -28,6 +33,7 @@ import rx.android.schedulers.AndroidSchedulers */ class DownloadController : NucleusController(), + FabController, DownloadAdapter.DownloadItemListener { /** @@ -35,6 +41,9 @@ class DownloadController : */ private var adapter: DownloadAdapter? = null + private var actionFab: ExtendedFloatingActionButton? = null + private var actionFabScrollListener: RecyclerView.OnScrollListener? = null + /** * Map of subscriptions for active downloads. */ @@ -78,22 +87,7 @@ class DownloadController : binding.recycler.layoutManager = LinearLayoutManager(view.context) binding.recycler.setHasFixedSize(true) - binding.fab.clicks() - .onEach { - val context = applicationContext ?: return@onEach - - if (isRunning) { - DownloadService.stop(context) - presenter.pauseDownloads() - } else { - DownloadService.start(context) - } - - setInformationView() - } - .launchIn(scope) - - binding.fab.offsetAppbarHeight(activity!!) + actionFabScrollListener = actionFab?.shrinkOnScroll(binding.recycler) // Subscribe to changes DownloadService.runningRelay @@ -109,6 +103,29 @@ class DownloadController : .subscribeUntilDestroy { onUpdateDownloadedPages(it) } } + override fun configureFab(fab: ExtendedFloatingActionButton) { + actionFab = fab + fab.clicks() + .onEach { + val context = applicationContext ?: return@onEach + + if (isRunning) { + DownloadService.stop(context) + presenter.pauseDownloads() + } else { + DownloadService.start(context) + } + + setInformationView() + } + .launchIn(scope) + } + + override fun cleanupFab(fab: ExtendedFloatingActionButton) { + actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) } + actionFab = null + } + override fun onDestroyView(view: View) { for (subscription in progressSubscriptions.values) { subscription.unsubscribe() @@ -267,18 +284,28 @@ class DownloadController : private fun setInformationView() { if (presenter.downloadQueue.isEmpty()) { binding.emptyView.show(R.string.information_no_downloads) - binding.fab.hide() + actionFab?.gone() } else { binding.emptyView.hide() - binding.fab.show() + actionFab?.apply { + visible() - binding.fab.setImageResource( - if (isRunning) { - R.drawable.ic_pause_24dp - } else { - R.drawable.ic_play_arrow_24dp - } - ) + setText( + if (isRunning) { + R.string.action_pause + } else { + R.string.action_resume + } + ) + + setIconResource( + if (isRunning) { + R.drawable.ic_pause_24dp + } else { + R.drawable.ic_play_arrow_24dp + } + ) + } } } 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 8756de8f77..40bfc48875 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 @@ -27,6 +27,7 @@ import eu.kanade.tachiyomi.databinding.MainActivityBinding import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.base.controller.DialogController +import eu.kanade.tachiyomi.ui.base.controller.FabController import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController import eu.kanade.tachiyomi.ui.base.controller.RootController import eu.kanade.tachiyomi.ui.base.controller.TabbedController @@ -42,7 +43,9 @@ import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.system.toast +import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.snack +import eu.kanade.tachiyomi.util.view.visible import java.util.Date import java.util.concurrent.TimeUnit import kotlinx.coroutines.delay @@ -151,20 +154,7 @@ class MainActivity : BaseActivity() { if (savedInstanceState == null) { // Show changelog prompt on update if (Migrations.upgrade(preferences) && !BuildConfig.DEBUG) { - binding.controllerContainer.snack(getString(R.string.updated_version, BuildConfig.VERSION_NAME), Snackbar.LENGTH_INDEFINITE) { - setAction(R.string.whats_new) { - val url = "https://github.com/inorichi/tachiyomi/releases/tag/v${BuildConfig.VERSION_NAME}" - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - startActivity(intent) - } - - // Ensure the snackbar sits above the bottom nav - val layoutParams = view.layoutParams as CoordinatorLayout.LayoutParams - layoutParams.anchorId = binding.bottomNav.id - layoutParams.anchorGravity = Gravity.TOP - layoutParams.gravity = Gravity.TOP - view.layoutParams = layoutParams - } + showUpdateInfoSnackbar() } } @@ -357,6 +347,15 @@ class MainActivity : BaseActivity() { binding.tabs.setupWithViewPager(null) } + if (from is FabController) { + binding.rootFab.gone() + from.cleanupFab(binding.rootFab) + } + if (to is FabController) { + binding.rootFab.visible() + to.configureFab(binding.rootFab) + } + if (to is NoToolbarElevationController) { binding.appbar.disableElevation() } else { @@ -382,6 +381,32 @@ class MainActivity : BaseActivity() { } } + private fun showUpdateInfoSnackbar() { + val snack = binding.rootCoordinator.snack( + getString(R.string.updated_version, BuildConfig.VERSION_NAME), + Snackbar.LENGTH_INDEFINITE + ) { + setAction(R.string.whats_new) { + val url = "https://github.com/inorichi/tachiyomi/releases/tag/v${BuildConfig.VERSION_NAME}" + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + startActivity(intent) + } + + // Ensure the snackbar sits above the bottom nav + val layoutParams = view.layoutParams as CoordinatorLayout.LayoutParams + layoutParams.anchorId = binding.bottomNav.id + layoutParams.anchorGravity = Gravity.TOP + layoutParams.gravity = Gravity.TOP + view.layoutParams = layoutParams + } + + // Manually handle dismiss delay since Snackbar.LENGTH_LONG is a too short + launchIO { + delay(5000) + snack.dismiss() + } + } + companion object { // Shortcut actions const val SHORTCUT_LIBRARY = "eu.kanade.tachiyomi.SHOW_LIBRARY" 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 3abee9bcb1..0f2bf564a0 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 @@ -93,8 +93,8 @@ inline fun View.toggle() { * * @param recycler [RecyclerView] that the FAB should shrink/extend in response to. */ -fun ExtendedFloatingActionButton.shrinkOnScroll(recycler: RecyclerView) { - recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { +fun ExtendedFloatingActionButton.shrinkOnScroll(recycler: RecyclerView): RecyclerView.OnScrollListener { + val listener = object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { if (dy <= 0) { extend() @@ -102,7 +102,9 @@ fun ExtendedFloatingActionButton.shrinkOnScroll(recycler: RecyclerView) { shrink() } } - }) + } + recycler.addOnScrollListener(listener) + return listener } /** diff --git a/app/src/main/res/layout/categories_controller.xml b/app/src/main/res/layout/categories_controller.xml index 90b7151e2e..7e4f65d627 100644 --- a/app/src/main/res/layout/categories_controller.xml +++ b/app/src/main/res/layout/categories_controller.xml @@ -1,6 +1,5 @@ - @@ -14,12 +13,6 @@ android:paddingBottom="@dimen/fab_list_padding" tools:listitem="@layout/categories_item" /> - - - + diff --git a/app/src/main/res/layout/download_controller.xml b/app/src/main/res/layout/download_controller.xml index eab093a2ed..db9867b499 100644 --- a/app/src/main/res/layout/download_controller.xml +++ b/app/src/main/res/layout/download_controller.xml @@ -22,13 +22,6 @@ app:fastScrollerBubbleEnabled="false" tools:visibility="visible" /> - - @@ -32,6 +33,11 @@ android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> + + - - + android:layout_height="match_parent" + android:orientation="vertical" + tools:context=".ui.browse.source.browse.BrowseSourceController"> - - - - - - - - - - + - + + +