diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt index 1f094cb9de..c13c044483 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt @@ -12,6 +12,9 @@ import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.RestoreViewOnCreateController import kotlinx.android.extensions.LayoutContainer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel import timber.log.Timber abstract class BaseController(bundle: Bundle? = null) : @@ -20,6 +23,8 @@ abstract class BaseController(bundle: Bundle? = null) : lateinit var binding: VB + lateinit var viewScope: CoroutineScope + init { addLifecycleListener( object : LifecycleListener() { @@ -28,6 +33,7 @@ abstract class BaseController(bundle: Bundle? = null) : } override fun preCreateView(controller: Controller) { + viewScope = MainScope() Timber.d("Create view for ${controller.instance()}") } @@ -40,6 +46,7 @@ abstract class BaseController(bundle: Bundle? = null) : } override fun preDestroyView(controller: Controller, view: View) { + viewScope.cancel() Timber.d("Destroy view for ${controller.instance()}") } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/NucleusController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/NucleusController.kt index 247b6d8bd2..0786d5e137 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/NucleusController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/NucleusController.kt @@ -4,9 +4,6 @@ import android.os.Bundle import androidx.viewbinding.ViewBinding import eu.kanade.tachiyomi.ui.base.presenter.NucleusConductorDelegate import eu.kanade.tachiyomi.ui.base.presenter.NucleusConductorLifecycleListener -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import nucleus.factory.PresenterFactory import nucleus.presenter.Presenter @@ -17,8 +14,6 @@ abstract class NucleusController>(val bundle: private val delegate = NucleusConductorDelegate(this) - val scope = CoroutineScope(Job() + Dispatchers.Main) - val presenter: P get() = delegate.presenter!! diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionController.kt index 2b86eb8319..6dbb31f683 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionController.kt @@ -67,7 +67,7 @@ open class ExtensionController : binding.swipeRefresh.isRefreshing = true binding.swipeRefresh.refreshes() .onEach { presenter.findAvailableExtensions() } - .launchIn(scope) + .launchIn(viewScope) // Initialize adapter, scroll listener and recycler views adapter = ExtensionAdapter(this) @@ -142,7 +142,7 @@ open class ExtensionController : query = it.toString() drawExtensions() } - .launchIn(scope) + .launchIn(viewScope) } override fun onItemClick(view: View, position: Int): Boolean { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsController.kt index a4232d3478..a1cc1572f7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsController.kt @@ -134,7 +134,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) : isChecked = enabled sourcePrefs.forEach { pref -> pref.isVisible = enabled } } - .launchIn(scope) + .launchIn(viewScope) } // Source enable/disable diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt index dd9c7c81b7..228938eb60 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt @@ -222,7 +222,7 @@ class SourceController : searchView.queryTextEvents() .filterIsInstance() .onEach { performGlobalSearch(it.queryText.toString()) } - .launchIn(scope) + .launchIn(viewScope) } private fun performGlobalSearch(query: String) { 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 28e2288eea..9d60d70969 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 @@ -187,6 +187,7 @@ open class BrowseSourceController(bundle: Bundle) : } override fun cleanupFab(fab: ExtendedFloatingActionButton) { + fab.setOnClickListener(null) actionFabScrollListener?.let { recycler?.removeOnScrollListener(it) } actionFab = null } @@ -224,7 +225,7 @@ open class BrowseSourceController(bundle: Bundle) : .drop(1) // Set again the adapter to recalculate the covers height .onEach { adapter = this@BrowseSourceController.adapter } - .launchIn(scope) + .launchIn(viewScope) (layoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int { @@ -275,7 +276,7 @@ open class BrowseSourceController(bundle: Bundle) : .filter { router.backstack.lastOrNull()?.controller() == this@BrowseSourceController } .filterIsInstance() .onEach { searchWithQuery(it.queryText.toString()) } - .launchIn(scope) + .launchIn(viewScope) searchItem.fixExpand( onExpand = { invalidateMenuOnExpand() }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt index 012cff7200..d277eff5e8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt @@ -129,7 +129,7 @@ open class GlobalSearchController( searchItem.collapseActionView() setTitle() // Update toolbar title } - .launchIn(scope) + .launchIn(viewScope) } /** 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 dd56920d29..f4fc01a1d3 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 @@ -22,9 +22,6 @@ import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.main.MainActivity 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 /** * Controller to manage the categories for the users' library. @@ -103,14 +100,13 @@ class CategoryController : 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) + fab.setOnClickListener { + CategoryCreateDialog(this@CategoryController).showDialog(router, null) + } } override fun cleanupFab(fab: ExtendedFloatingActionButton) { + fab.setOnClickListener(null) actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) } actionFab = null } 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 2622318b5c..9768fda63a 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 @@ -18,9 +18,6 @@ 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.util.view.shrinkOnScroll -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import reactivecircus.flowbinding.android.view.clicks import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers @@ -104,23 +101,22 @@ class DownloadController : override fun configureFab(fab: ExtendedFloatingActionButton) { actionFab = fab - fab.clicks() - .onEach { - val context = applicationContext ?: return@onEach + fab.setOnClickListener { + val context = applicationContext ?: return@setOnClickListener - if (isRunning) { - DownloadService.stop(context) - presenter.pauseDownloads() - } else { - DownloadService.start(context) - } - - setInformationView() + if (isRunning) { + DownloadService.stop(context) + presenter.pauseDownloads() + } else { + DownloadService.start(context) } - .launchIn(scope) + + setInformationView() + } } override fun cleanupFab(fab: ExtendedFloatingActionButton) { + fab.setOnClickListener(null) actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) } actionFab = null } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index 0881a61efe..35ae240e40 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -169,13 +169,13 @@ class LibraryController( activeCategory = it updateTitle() } - .launchIn(scope) + .launchIn(viewScope) getColumnsPreferenceForCurrentOrientation().asImmediateFlow { mangaPerRow = it } .drop(1) // Set again the adapter to recalculate the covers height .onEach { reattachAdapter() } - .launchIn(scope) + .launchIn(viewScope) if (selectedMangas.isNotEmpty()) { createActionModeIfNeeded() @@ -197,7 +197,7 @@ class LibraryController( GlobalSearchController(query).withFadeTransaction() ) } - .launchIn(scope) + .launchIn(viewScope) (activity!! as MainActivity).fixViewToBottom(binding.actionToolbar) } @@ -212,6 +212,7 @@ class LibraryController( override fun onDestroyView(view: View) { destroyActionModeIfNeeded() + (activity!! as MainActivity).clearFixViewToBottom(binding.actionToolbar) binding.actionToolbar.destroy() adapter?.onDestroy() adapter = null @@ -391,7 +392,7 @@ class LibraryController( query = it.toString() performSearch() } - .launchIn(scope) + .launchIn(viewScope) } private fun performSearch() { 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 b06385cc57..b8a47483c1 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 @@ -67,6 +67,8 @@ class MainActivity : BaseViewBindingActivity() { private var isConfirmingExit: Boolean = false private var isHandlingShortcut: Boolean = false + private var fixedViewsToBottom = mutableMapOf() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -401,12 +403,17 @@ class MainActivity : BaseViewBindingActivity() { * the collapsing AppBarLayout. */ fun fixViewToBottom(view: View) { - binding.appbar.addOnOffsetChangedListener( - AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset -> - val maxAbsOffset = appBarLayout.measuredHeight - binding.tabs.measuredHeight - view.translationY = -maxAbsOffset - verticalOffset.toFloat() - } - ) + val listener = AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset -> + val maxAbsOffset = appBarLayout.measuredHeight - binding.tabs.measuredHeight + view.translationY = -maxAbsOffset - verticalOffset.toFloat() + } + binding.appbar.addOnOffsetChangedListener(listener) + fixedViewsToBottom[view] = listener + } + + fun clearFixViewToBottom(view: View) { + val listener = fixedViewsToBottom.remove(view) + binding.appbar.removeOnOffsetChangedListener(listener) } private fun setBottomNavBehaviorOnScroll() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt index 4a1b048f95..8210f11b7a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt @@ -76,7 +76,6 @@ import eu.kanade.tachiyomi.util.view.shrinkOnScroll import eu.kanade.tachiyomi.util.view.snack import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import reactivecircus.flowbinding.android.view.clicks import reactivecircus.flowbinding.recyclerview.scrollEvents import reactivecircus.flowbinding.swiperefreshlayout.refreshes import timber.log.Timber @@ -228,14 +227,14 @@ class MangaController : binding.recycler.scrollEvents() .onEach { updateToolbarTitleAlpha() } - .launchIn(scope) + .launchIn(viewScope) binding.swipeRefresh.refreshes() .onEach { fetchMangaInfoFromSource(manualFetch = true) fetchChaptersFromSource(manualFetch = true) } - .launchIn(scope) + .launchIn(viewScope) (activity!! as MainActivity).fixViewToBottom(binding.actionToolbar) @@ -279,42 +278,42 @@ class MangaController : actionFab = fab fab.setText(R.string.action_start) fab.setIconResource(R.drawable.ic_play_arrow_24dp) - fab.clicks() - .onEach { - val item = presenter.getNextUnreadChapter() - if (item != null) { - // Create animation listener - val revealAnimationListener: Animator.AnimatorListener = object : AnimatorListenerAdapter() { - override fun onAnimationStart(animation: Animator?) { - openChapter(item.chapter, true) - } + fab.setOnClickListener { + val item = presenter.getNextUnreadChapter() + if (item != null) { + // Create animation listener + val revealAnimationListener: Animator.AnimatorListener = object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?) { + openChapter(item.chapter, true) } - - // Get coordinates and start animation - actionFab?.getCoordinates()?.let { coordinates -> - if (!binding.revealView.showRevealEffect( - coordinates.x, - coordinates.y, - revealAnimationListener - ) - ) { - openChapter(item.chapter) - } - } - } else { - view?.context?.toast(R.string.no_next_chapter) } + + // Get coordinates and start animation + actionFab?.getCoordinates()?.let { coordinates -> + if (!binding.revealView.showRevealEffect( + coordinates.x, + coordinates.y, + revealAnimationListener + ) + ) { + openChapter(item.chapter) + } + } + } else { + view?.context?.toast(R.string.no_next_chapter) } - .launchIn(scope) + } } override fun cleanupFab(fab: ExtendedFloatingActionButton) { + fab.setOnClickListener(null) actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) } actionFab = null } override fun onDestroyView(view: View) { destroyActionModeIfNeeded() + (activity!! as MainActivity).clearFixViewToBottom(binding.actionToolbar) binding.actionToolbar.destroy() mangaInfoAdapter = null chaptersHeaderAdapter = null diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt index 0abbd144ca..2c6c0f20f2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt @@ -77,7 +77,7 @@ class TrackController : binding.swipeRefresh.isEnabled = false binding.swipeRefresh.refreshes() .onEach { presenter.refresh() } - .launchIn(scope) + .launchIn(viewScope) } override fun onDestroyView(view: View) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt index bcbcf3c7de..c3238f3696 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt @@ -77,7 +77,7 @@ class TrackSearchDialog : DialogController { .onEach { position -> selectedItem = adapter.getItem(position) } - .launchIn(trackController.scope) + .launchIn(trackController.viewScope) // Do an initial search based on the manga's title if (savedState == null) { @@ -99,7 +99,7 @@ class TrackSearchDialog : DialogController { .debounce(TimeUnit.SECONDS.toMillis(1)) .filter { it.isNotBlank() } .onEach { search(it.toString()) } - .launchIn(trackController.scope) + .launchIn(trackController.viewScope) } private fun search(query: String) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt index 57dd831591..2db2a53e5d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt @@ -189,7 +189,7 @@ class HistoryController : query = it.toString() presenter.updateList(query) } - .launchIn(scope) + .launchIn(viewScope) // Fixes problem with the overflow icon showing up in lieu of search searchItem.fixExpand( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt index a72a77ad92..73960a2358 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt @@ -97,7 +97,7 @@ class UpdatesController : val firstPos = layoutManager.findFirstCompletelyVisibleItemPosition() binding.swipeRefresh.isEnabled = firstPos <= 0 } - .launchIn(scope) + .launchIn(viewScope) binding.swipeRefresh.setDistanceToTriggerSync((2 * 64 * view.resources.displayMetrics.density).toInt()) binding.swipeRefresh.refreshes() @@ -107,13 +107,14 @@ class UpdatesController : // It can be a very long operation, so we disable swipe refresh and show a toast. binding.swipeRefresh.isRefreshing = false } - .launchIn(scope) + .launchIn(viewScope) (activity!! as MainActivity).fixViewToBottom(binding.actionToolbar) } override fun onDestroyView(view: View) { destroyActionModeIfNeeded() + (activity!! as MainActivity).clearFixViewToBottom(binding.actionToolbar) binding.actionToolbar.destroy() adapter = null super.onDestroyView(view)