浏览代码

Attach some FABs and snackbars to root CoordinatorLayout

Fixes some issues around snackbars sometimes being out of view.
arkon 4 年之前
父节点
当前提交
479eb1ba71

+ 10 - 0
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) {}
+}

+ 26 - 7
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<SourceControllerBinding, BrowseSourcePresenter>(bundle),
+    FabController,
     FlexibleAdapter.OnItemClickListener,
     FlexibleAdapter.OnItemLongClickListener,
     FlexibleAdapter.EndlessScrollListener,
@@ -84,6 +86,9 @@ open class BrowseSourceController(bundle: Bundle) :
      */
     private var adapter: FlexibleAdapter<IFlexible<*>>? = 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() }
+
+        actionFab?.setOnClickListener { filterSheet?.show() }
+
+        actionFab?.visible()
+    }
 
-        binding.fabFilter.setOnClickListener { filterSheet?.show() }
+    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)
+    }
 
-        binding.fabFilter.offsetAppbarHeight(activity!!)
-        binding.fabFilter.visible()
+    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)

+ 20 - 4
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<CategoriesControllerBinding, CategoryPresenter>(),
+    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
                 )
 

+ 53 - 26
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<DownloadControllerBinding, DownloadPresenter>(),
+    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,7 +87,25 @@ class DownloadController :
         binding.recycler.layoutManager = LinearLayoutManager(view.context)
         binding.recycler.setHasFixedSize(true)
 
-        binding.fab.clicks()
+        actionFabScrollListener = actionFab?.shrinkOnScroll(binding.recycler)
+
+        // Subscribe to changes
+        DownloadService.runningRelay
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribeUntilDestroy { onQueueStatusChange(it) }
+
+        presenter.getDownloadStatusObservable()
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribeUntilDestroy { onStatusChange(it) }
+
+        presenter.getDownloadProgressObservable()
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribeUntilDestroy { onUpdateDownloadedPages(it) }
+    }
+
+    override fun configureFab(fab: ExtendedFloatingActionButton) {
+        actionFab = fab
+        fab.clicks()
             .onEach {
                 val context = applicationContext ?: return@onEach
 
@@ -92,21 +119,11 @@ class DownloadController :
                 setInformationView()
             }
             .launchIn(scope)
+    }
 
-        binding.fab.offsetAppbarHeight(activity!!)
-
-        // Subscribe to changes
-        DownloadService.runningRelay
-            .observeOn(AndroidSchedulers.mainThread())
-            .subscribeUntilDestroy { onQueueStatusChange(it) }
-
-        presenter.getDownloadStatusObservable()
-            .observeOn(AndroidSchedulers.mainThread())
-            .subscribeUntilDestroy { onStatusChange(it) }
-
-        presenter.getDownloadProgressObservable()
-            .observeOn(AndroidSchedulers.mainThread())
-            .subscribeUntilDestroy { onUpdateDownloadedPages(it) }
+    override fun cleanupFab(fab: ExtendedFloatingActionButton) {
+        actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
+        actionFab = null
     }
 
     override fun onDestroyView(view: View) {
@@ -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()
-
-            binding.fab.setImageResource(
-                if (isRunning) {
-                    R.drawable.ic_pause_24dp
-                } else {
-                    R.drawable.ic_play_arrow_24dp
-                }
-            )
+            actionFab?.apply {
+                visible()
+
+                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
+                    }
+                )
+            }
         }
     }
 

+ 39 - 14
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<MainActivityBinding>() {
         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<MainActivityBinding>() {
             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<MainActivityBinding>() {
         }
     }
 
+    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"

+ 5 - 3
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
 }
 
 /**

+ 2 - 9
app/src/main/res/layout/categories_controller.xml

@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
@@ -14,12 +13,6 @@
         android:paddingBottom="@dimen/fab_list_padding"
         tools:listitem="@layout/categories_item" />
 
-    <com.google.android.material.floatingactionbutton.FloatingActionButton
-        android:id="@+id/fab"
-        style="@style/Theme.Widget.FAB"
-        app:layout_anchor="@id/recycler"
-        app:srcCompat="@drawable/ic_add_24dp" />
-
     <eu.kanade.tachiyomi.widget.EmptyView
         android:id="@+id/empty_view"
         android:layout_width="wrap_content"
@@ -27,4 +20,4 @@
         android:layout_gravity="center"
         android:visibility="gone" />
 
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
+</FrameLayout>

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

@@ -22,13 +22,6 @@
         app:fastScrollerBubbleEnabled="false"
         tools:visibility="visible" />
 
-    <com.google.android.material.floatingactionbutton.FloatingActionButton
-        android:id="@+id/fab"
-        style="@style/Theme.Widget.FAB"
-        android:visibility="gone"
-        app:layout_anchor="@id/recycler"
-        app:srcCompat="@drawable/ic_pause_24dp" />
-
     <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

@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/root_coordinator"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
@@ -32,6 +33,11 @@
         android:layout_height="match_parent"
         app:layout_behavior="@string/appbar_scrolling_view_behavior" />
 
+    <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
+        android:id="@+id/root_fab"
+        style="@style/Theme.Widget.FAB"
+        android:visibility="gone" />
+
     <com.google.android.material.bottomnavigation.BottomNavigationView
         android:id="@+id/bottom_nav"
         style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"

+ 21 - 36
app/src/main/res/layout/source_controller.xml

@@ -1,46 +1,31 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <FrameLayout
+    <LinearLayout
+        android:id="@+id/catalogue_view"
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
-
-        <LinearLayout
-            android:id="@+id/catalogue_view"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:orientation="vertical"
-            tools:context=".ui.browse.source.browse.BrowseSourceController">
-
-            <ProgressBar
-                android:id="@+id/progress"
-                style="?android:attr/progressBarStyleLarge"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:layout_gravity="center"
-                android:visibility="gone" />
-
-        </LinearLayout>
-
-        <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
-            android:id="@+id/fab_filter"
-            style="@style/Theme.Widget.FAB"
-            android:text="@string/action_filter"
-            android:visibility="gone"
-            app:icon="@drawable/ic_filter_list_24dp"
-            app:layout_anchor="@id/catalogue_view" />
-
-        <eu.kanade.tachiyomi.widget.EmptyView
-            android:id="@+id/empty_view"
-            android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        tools:context=".ui.browse.source.browse.BrowseSourceController">
+
+        <ProgressBar
+            android:id="@+id/progress"
+            style="?android:attr/progressBarStyleLarge"
+            android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:gravity="center"
+            android:layout_gravity="center"
             android:visibility="gone" />
 
-    </FrameLayout>
+    </LinearLayout>
+
+    <eu.kanade.tachiyomi.widget.EmptyView
+        android:id="@+id/empty_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:visibility="gone" />
 
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
+</FrameLayout>