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 000000000..23b8c579a --- /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 3007cc784..eb94a1b9e 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 @@ -18,6 +18,7 @@ import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.input.input import com.afollestad.materialdialogs.list.listItems import com.elvishew.xlog.XLog +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.snackbar.Snackbar import com.tfcporciuncula.flow.Preference import eu.davidea.flexibleadapter.FlexibleAdapter @@ -34,13 +35,13 @@ import eu.kanade.tachiyomi.source.ConfigurableSource 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.browse.extension.details.SourcePreferencesController import eu.kanade.tachiyomi.ui.browse.source.SourceController import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterSheet.FilterNavigationView.Companion.MAX_SAVED_SEARCHES import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog -import eu.kanade.tachiyomi.ui.main.offsetAppbarHeight import eu.kanade.tachiyomi.ui.manga.MangaAllInOneController import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.webview.WebViewActivity @@ -72,6 +73,7 @@ import uy.kohesive.injekt.injectLazy */ open class BrowseSourceController(bundle: Bundle) : NucleusController(bundle), + FabController, FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.EndlessScrollListener, @@ -113,6 +115,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. */ @@ -189,7 +194,7 @@ open class BrowseSourceController(bundle: Bundle) : if (presenter.sourceFilters.isEmpty()) { // SY --> - binding.fabFilter.text = activity!!.getString(R.string.eh_saved_searches) + actionFab?.text = activity!!.getString(R.string.eh_saved_searches) // SY <-- } @@ -303,13 +308,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) { @@ -369,7 +388,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 4ed9d86a3..cce0b0203 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/category/sources/SourceCategoryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/sources/SourceCategoryController.kt index 96d7badd9..2ff9ddbe7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/sources/SourceCategoryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/sources/SourceCategoryController.kt @@ -9,15 +9,17 @@ 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 import eu.davidea.flexibleadapter.helpers.UndoHelper import eu.kanade.tachiyomi.R 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 SourceCategoryController : NucleusController(), ActionMode.Callback, + FabController, FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, SourceCategoryCreateDialog.Listener, @@ -44,6 +47,9 @@ class SourceCategoryController : */ private var adapter: SourceCategoryAdapter? = null + private var actionFab: ExtendedFloatingActionButton? = null + private var actionFabScrollListener: RecyclerView.OnScrollListener? = null + /** * Undo helper used for restoring a deleted category. */ @@ -86,13 +92,23 @@ class SourceCategoryController : binding.recycler.adapter = adapter 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 { SourceCategoryCreateDialog(this@SourceCategoryController).showDialog(router, null) } .launchIn(scope) + } - binding.fab.offsetAppbarHeight(activity!!) + override fun cleanupFab(fab: ExtendedFloatingActionButton) { + actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) } + actionFab = null } /** @@ -178,7 +194,7 @@ class SourceCategoryController : 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 26a5d894b..15ddd84b7 100755 --- 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 fe79ce97d..32559789a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -3,8 +3,10 @@ package eu.kanade.tachiyomi.ui.main import android.app.Activity import android.app.SearchManager import android.content.Intent +import android.net.Uri import android.os.Bundle import android.os.Looper +import android.view.Gravity import android.view.View import android.view.ViewGroup import android.widget.Toast @@ -16,13 +18,16 @@ import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.RouterTransaction import com.google.android.material.appbar.AppBarLayout import com.google.android.material.behavior.HideBottomViewOnScrollBehavior +import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayout +import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.notification.NotificationReceiver 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 @@ -38,6 +43,10 @@ import eu.kanade.tachiyomi.ui.recent.history.HistoryController 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 exh.EH_SOURCE_ID import exh.EIGHTMUSES_SOURCE_ID import exh.EXHMigrations @@ -176,10 +185,10 @@ class MainActivity : BaseActivity() { syncActivityViewWithController(router.backstack.lastOrNull()?.controller()) if (savedInstanceState == null) { - // Show changelog if needed + // Show changelog prompt on update // TODO -// if (Migrations.upgrade(preferences)) { -// ChangelogDialogController().showDialog(router) +// if (Migrations.upgrade(preferences) && !BuildConfig.DEBUG) { +// showUpdateInfoSnackbar() // } // EXH --> @@ -427,6 +436,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 { @@ -452,6 +470,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 8b94966b7..315b1f58d 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 @@ -82,8 +82,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() @@ -91,7 +91,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 90b7151e2..7e4f65d62 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 eab093a2e..db9867b49 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"> - - - - - - - - - - + - + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f064bf229..1d9f76913 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -388,9 +388,11 @@ Version Build time Changelog + What\'s new Preview build notices Open source licenses Check for updates + Updated to v%1$s Send crash reports