Minor fixes regarding leaks

(cherry picked from commit 36f81b4a623ede11f10049f5869de5b6efa7803a)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
This commit is contained in:
inorichi 2021-01-07 15:19:00 +01:00 committed by Jobobby04
parent 1b41546bc9
commit c8dd2190ba
30 changed files with 132 additions and 132 deletions

View File

@ -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<VB : ViewBinding>(bundle: Bundle? = null) :
@ -20,6 +23,8 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
lateinit var binding: VB
lateinit var viewScope: CoroutineScope
init {
addLifecycleListener(
object : LifecycleListener() {
@ -28,6 +33,7 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
}
override fun preCreateView(controller: Controller) {
viewScope = MainScope()
Timber.d("Create view for ${controller.instance()}")
}
@ -40,6 +46,7 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
}
override fun preDestroyView(controller: Controller, view: View) {
viewScope.cancel()
Timber.d("Destroy view for ${controller.instance()}")
}
}

View File

@ -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<VB : ViewBinding, P : Presenter<*>>(val bundle:
private val delegate = NucleusConductorDelegate(this)
val scope = CoroutineScope(Job() + Dispatchers.Main)
val presenter: P
get() = delegate.presenter!!

View File

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

View File

@ -134,7 +134,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
isChecked = enabled
sourcePrefs.forEach { pref -> pref.isVisible = enabled }
}
.launchIn(scope)
.launchIn(viewScope)
}
// Source enable/disable

View File

@ -126,7 +126,7 @@ open class LatestController :
presenter.preferences.latestTabSources()
.asImmediateFlow { presenter.getLatest() }
.launchIn(scope)
.launchIn(viewScope)
}
override fun onDestroyView(view: View) {

View File

@ -83,25 +83,24 @@ class PreMigrationController(bundle: Bundle? = null) :
actionFab = fab
fab.setText(R.string.action_migrate)
fab.setIconResource(R.drawable.ic_arrow_forward_24dp)
fab.clicks()
.onEach {
if (dialog?.isShowing != true) {
dialog = MigrationBottomSheetDialog(activity!!, R.style.SheetDialog, this)
dialog?.show()
val bottomSheet = dialog?.findViewById<FrameLayout>(
com.google.android.material.R.id.design_bottom_sheet
)
if (bottomSheet != null) {
val behavior: BottomSheetBehavior<*> = BottomSheetBehavior.from(bottomSheet)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
behavior.skipCollapsed = true
}
fab.setOnClickListener {
if (dialog?.isShowing != true) {
dialog = MigrationBottomSheetDialog(activity!!, R.style.SheetDialog, this)
dialog?.show()
val bottomSheet = dialog?.findViewById<FrameLayout>(
com.google.android.material.R.id.design_bottom_sheet
)
if (bottomSheet != null) {
val behavior: BottomSheetBehavior<*> = BottomSheetBehavior.from(bottomSheet)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
behavior.skipCollapsed = true
}
}
.launchIn(scope)
}
}
override fun cleanupFab(fab: ExtendedFloatingActionButton) {
fab.setOnClickListener(null)
actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
actionFab = null
}

View File

@ -181,6 +181,6 @@ class SearchController(
searchItem.collapseActionView()
setTitle() // Update toolbar title
}
.launchIn(scope)
.launchIn(viewScope)
}
}

View File

@ -362,7 +362,7 @@ class SourceController(bundle: Bundle? = null) :
searchView.queryTextEvents()
.filterIsInstance<QueryTextEvent.QuerySubmitted>()
.onEach { performGlobalSearch(it.queryText.toString()) }
.launchIn(scope)
.launchIn(viewScope)
}
private fun performGlobalSearch(query: String) {

View File

@ -329,6 +329,7 @@ open class BrowseSourceController(bundle: Bundle) :
}
override fun cleanupFab(fab: ExtendedFloatingActionButton) {
fab.setOnClickListener(null)
actionFabScrollListener?.let { recycler?.removeOnScrollListener(it) }
actionFab = null
}
@ -366,7 +367,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 {
@ -417,7 +418,7 @@ open class BrowseSourceController(bundle: Bundle) :
.filter { router.backstack.lastOrNull()?.controller() == this@BrowseSourceController }
.filterIsInstance<QueryTextEvent.QuerySubmitted>()
.onEach { searchWithQuery(it.queryText.toString()) }
.launchIn(scope)
.launchIn(viewScope)
searchItem.fixExpand(
onExpand = { invalidateMenuOnExpand() },

View File

@ -129,7 +129,7 @@ open class GlobalSearchController(
searchItem.collapseActionView()
setTitle() // Update toolbar title
}
.launchIn(scope)
.launchIn(viewScope)
}
/**

View File

@ -159,7 +159,7 @@ open class IndexController :
onBrowseClick(presenter.query.nullIfBlank())
}
}
.launchIn(scope)
.launchIn(viewScope)
searchItem.fixExpand(
onExpand = { invalidateMenuOnExpand() }
@ -247,7 +247,7 @@ open class IndexController :
actionFab?.clicks()
?.onEach { filterSheet?.show() }
?.launchIn(scope)
?.launchIn(viewScope)
actionFab?.isVisible = true
}
@ -263,6 +263,7 @@ open class IndexController :
}
override fun cleanupFab(fab: ExtendedFloatingActionButton) {
fab.setOnClickListener(null)
actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
actionFab = null
}

View File

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

View File

@ -102,14 +102,13 @@ class BiometricTimesController :
actionFab = fab
fab.setText(R.string.action_add)
fab.setIconResource(R.drawable.ic_add_24dp)
fab.clicks()
.onEach {
BiometricTimesCreateDialog(this@BiometricTimesController).showDialog(router, null)
}
.launchIn(scope)
fab.setOnClickListener {
BiometricTimesCreateDialog(this@BiometricTimesController).showDialog(router, null)
}
}
override fun cleanupFab(fab: ExtendedFloatingActionButton) {
fab.setOnClickListener(null)
actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
actionFab = null
}

View File

@ -109,19 +109,18 @@ class SortTagController :
actionFab = fab
fab.setText(R.string.action_add)
fab.setIconResource(R.drawable.ic_add_24dp)
fab.clicks()
.onEach {
if (!shownHelpDialog) {
shownHelpDialog = true
helpDialog(true)
} else {
SortTagCreateDialog(this@SortTagController).showDialog(router, null)
}
fab.setOnClickListener {
if (!shownHelpDialog) {
shownHelpDialog = true
helpDialog(true)
} else {
SortTagCreateDialog(this@SortTagController).showDialog(router, null)
}
.launchIn(scope)
}
}
override fun cleanupFab(fab: ExtendedFloatingActionButton) {
fab.setOnClickListener(null)
actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
actionFab = null
}

View File

@ -99,14 +99,13 @@ class RepoController :
actionFab = fab
fab.setText(R.string.action_add)
fab.setIconResource(R.drawable.ic_add_24dp)
fab.clicks()
.onEach {
RepoCreateDialog(this@RepoController).showDialog(router, null)
}
.launchIn(scope)
fab.setOnClickListener {
RepoCreateDialog(this@RepoController).showDialog(router, null)
}
}
override fun cleanupFab(fab: ExtendedFloatingActionButton) {
fab.setOnClickListener(null)
actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
actionFab = null
}

View File

@ -100,14 +100,13 @@ class SourceCategoryController :
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)
fab.setOnClickListener {
SourceCategoryCreateDialog(this@SourceCategoryController).showDialog(router, null)
}
}
override fun cleanupFab(fab: ExtendedFloatingActionButton) {
fab.setOnClickListener(null)
actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
actionFab = null
}

View File

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

View File

@ -42,7 +42,7 @@ class LibraryComfortableGridHolder(
.onEach {
playButtonClicked()
}
.launchIn((adapter as LibraryCategoryAdapter).controller.scope)
.launchIn((adapter as LibraryCategoryAdapter).controller.viewScope)
}
// SY <--

View File

@ -41,7 +41,7 @@ class LibraryCompactGridHolder(
.onEach {
playButtonClicked()
}
.launchIn((adapter as LibraryCategoryAdapter).controller.scope)
.launchIn((adapter as LibraryCategoryAdapter).controller.viewScope)
}
// SY <--

View File

@ -200,13 +200,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()
@ -234,7 +234,7 @@ class LibraryController(
GlobalSearchController(query).withFadeTransaction()
)
}
.launchIn(scope)
.launchIn(viewScope)
(activity!! as MainActivity).fixViewToBottom(binding.actionToolbar)
}
@ -249,6 +249,7 @@ class LibraryController(
override fun onDestroyView(view: View) {
destroyActionModeIfNeeded()
(activity!! as MainActivity).clearFixViewToBottom(binding.actionToolbar)
binding.actionToolbar.destroy()
adapter?.onDestroy()
adapter = null
@ -445,7 +446,7 @@ class LibraryController(
query = it.toString()
performSearch()
}
.launchIn(scope)
.launchIn(viewScope)
}
private fun performSearch() {
@ -693,7 +694,7 @@ class LibraryController(
.onEach {
updateSyncStatus(it)
}
.launchIn(scope)
.launchIn(viewScope)
// <-- EXH
}

View File

@ -73,6 +73,8 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
private var isConfirmingExit: Boolean = false
private var isHandlingShortcut: Boolean = false
private var fixedViewsToBottom = mutableMapOf<View, AppBarLayout.OnOffsetChangedListener>()
// SY -->
// Idle-until-urgent
private var firstPaint = false
@ -466,12 +468,17 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
* 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() {

View File

@ -125,17 +125,17 @@ class EditMangaDialog : DialogController {
binding.mangaGenresTags.clearFocus()
binding.coverLayout.clicks()
.onEach { infoController.changeCover() }
.launchIn(infoController.scope)
.launchIn(infoController.viewScope)
binding.resetTags.clicks()
.onEach { resetTags() }
.launchIn(infoController.scope)
.launchIn(infoController.viewScope)
binding.resetCover.isVisible = !isLocal
binding.resetCover.clicks()
.onEach {
binding.root.context.toast(R.string.cover_reset_toast)
customCoverUri = null
willResetCover = true
}.launchIn(infoController.scope)
}.launchIn(infoController.viewScope)
}
private fun resetTags() {
@ -203,7 +203,7 @@ class EditMangaDialog : DialogController {
}
.negativeButton(android.R.string.cancel)
.show()
}.launchIn(infoController.scope)
}.launchIn(infoController.viewScope)
}
addView(addTagChip)
}

View File

@ -327,14 +327,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)
@ -351,7 +351,7 @@ class MangaController :
// Replace self
router?.replaceTopController(MangaController(redirect).withFadeTransaction())
}
.launchIn(scope)
.launchIn(viewScope)
updateFilterIconState()
}
@ -386,42 +386,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
@ -926,7 +926,7 @@ class MangaController :
start()
}
}
.launchIn(scope)
.launchIn(viewScope)
}
// SY <--

View File

@ -78,7 +78,7 @@ class TrackController :
binding.swipeRefresh.isEnabled = false
binding.swipeRefresh.refreshes()
.onEach { presenter.refresh() }
.launchIn(scope)
.launchIn(viewScope)
}
override fun onDestroyView(view: View) {

View File

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

View File

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

View File

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

View File

@ -604,7 +604,7 @@ class SettingsEhController : SettingsController() {
multiSelectListPreference {
key = PreferenceKeys.eh_autoUpdateRestrictions
titleRes = R.string.auto_update_restrictions
entriesRes = arrayOf(R.string.wifi, R.string.charging)
entriesRes = arrayOf(R.string.network_unmetered, R.string.charging)
entryValues = arrayOf("wifi", "ac")
summaryRes = R.string.pref_library_update_restriction_summary

View File

@ -37,13 +37,13 @@ class BatchAddController : NucleusController<EhFragmentBatchAddBinding, BatchAdd
.onEach {
addGalleries(binding.galleriesBox.text.toString())
}
.launchIn(scope)
.launchIn(viewScope)
binding.progressDismissBtn.clicks()
.onEach {
presenter.currentlyAddingRelay.call(BatchAddPresenter.STATE_PROGRESS_TO_INPUT)
}
.launchIn(scope)
.launchIn(viewScope)
val progressSubscriptions = CompositeSubscription()

View File

@ -68,7 +68,7 @@ class SmartSearchController(bundle: Bundle? = null) : NucleusController<EhSmartS
}
}
}
.launchIn(scope + Dispatchers.IO)
.launchIn(viewScope + Dispatchers.IO)
}
companion object {