Minor fixes regarding leaks

This commit is contained in:
inorichi 2021-01-07 15:19:00 +01:00
parent 2caecc01b2
commit 36f81b4a62
16 changed files with 81 additions and 78 deletions

View file

@ -12,6 +12,9 @@ import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.RestoreViewOnCreateController import com.bluelinelabs.conductor.RestoreViewOnCreateController
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import timber.log.Timber import timber.log.Timber
abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) : 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 binding: VB
lateinit var viewScope: CoroutineScope
init { init {
addLifecycleListener( addLifecycleListener(
object : LifecycleListener() { object : LifecycleListener() {
@ -28,6 +33,7 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
} }
override fun preCreateView(controller: Controller) { override fun preCreateView(controller: Controller) {
viewScope = MainScope()
Timber.d("Create view for ${controller.instance()}") 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) { override fun preDestroyView(controller: Controller, view: View) {
viewScope.cancel()
Timber.d("Destroy view for ${controller.instance()}") Timber.d("Destroy view for ${controller.instance()}")
} }
} }

View file

@ -4,9 +4,6 @@ import android.os.Bundle
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import eu.kanade.tachiyomi.ui.base.presenter.NucleusConductorDelegate import eu.kanade.tachiyomi.ui.base.presenter.NucleusConductorDelegate
import eu.kanade.tachiyomi.ui.base.presenter.NucleusConductorLifecycleListener 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.factory.PresenterFactory
import nucleus.presenter.Presenter import nucleus.presenter.Presenter
@ -17,8 +14,6 @@ abstract class NucleusController<VB : ViewBinding, P : Presenter<*>>(val bundle:
private val delegate = NucleusConductorDelegate(this) private val delegate = NucleusConductorDelegate(this)
val scope = CoroutineScope(Job() + Dispatchers.Main)
val presenter: P val presenter: P
get() = delegate.presenter!! get() = delegate.presenter!!

View file

@ -67,7 +67,7 @@ open class ExtensionController :
binding.swipeRefresh.isRefreshing = true binding.swipeRefresh.isRefreshing = true
binding.swipeRefresh.refreshes() binding.swipeRefresh.refreshes()
.onEach { presenter.findAvailableExtensions() } .onEach { presenter.findAvailableExtensions() }
.launchIn(scope) .launchIn(viewScope)
// Initialize adapter, scroll listener and recycler views // Initialize adapter, scroll listener and recycler views
adapter = ExtensionAdapter(this) adapter = ExtensionAdapter(this)
@ -142,7 +142,7 @@ open class ExtensionController :
query = it.toString() query = it.toString()
drawExtensions() drawExtensions()
} }
.launchIn(scope) .launchIn(viewScope)
} }
override fun onItemClick(view: View, position: Int): Boolean { override fun onItemClick(view: View, position: Int): Boolean {

View file

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

View file

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

View file

@ -187,6 +187,7 @@ open class BrowseSourceController(bundle: Bundle) :
} }
override fun cleanupFab(fab: ExtendedFloatingActionButton) { override fun cleanupFab(fab: ExtendedFloatingActionButton) {
fab.setOnClickListener(null)
actionFabScrollListener?.let { recycler?.removeOnScrollListener(it) } actionFabScrollListener?.let { recycler?.removeOnScrollListener(it) }
actionFab = null actionFab = null
} }
@ -224,7 +225,7 @@ open class BrowseSourceController(bundle: Bundle) :
.drop(1) .drop(1)
// Set again the adapter to recalculate the covers height // Set again the adapter to recalculate the covers height
.onEach { adapter = this@BrowseSourceController.adapter } .onEach { adapter = this@BrowseSourceController.adapter }
.launchIn(scope) .launchIn(viewScope)
(layoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { (layoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int { override fun getSpanSize(position: Int): Int {
@ -275,7 +276,7 @@ open class BrowseSourceController(bundle: Bundle) :
.filter { router.backstack.lastOrNull()?.controller() == this@BrowseSourceController } .filter { router.backstack.lastOrNull()?.controller() == this@BrowseSourceController }
.filterIsInstance<QueryTextEvent.QuerySubmitted>() .filterIsInstance<QueryTextEvent.QuerySubmitted>()
.onEach { searchWithQuery(it.queryText.toString()) } .onEach { searchWithQuery(it.queryText.toString()) }
.launchIn(scope) .launchIn(viewScope)
searchItem.fixExpand( searchItem.fixExpand(
onExpand = { invalidateMenuOnExpand() }, onExpand = { invalidateMenuOnExpand() },

View file

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

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.ui.main.MainActivity
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.shrinkOnScroll 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. * Controller to manage the categories for the users' library.
@ -103,14 +100,13 @@ class CategoryController :
actionFab = fab actionFab = fab
fab.setText(R.string.action_add) fab.setText(R.string.action_add)
fab.setIconResource(R.drawable.ic_add_24dp) fab.setIconResource(R.drawable.ic_add_24dp)
fab.clicks() fab.setOnClickListener {
.onEach { CategoryCreateDialog(this@CategoryController).showDialog(router, null)
CategoryCreateDialog(this@CategoryController).showDialog(router, null) }
}
.launchIn(scope)
} }
override fun cleanupFab(fab: ExtendedFloatingActionButton) { override fun cleanupFab(fab: ExtendedFloatingActionButton) {
fab.setOnClickListener(null)
actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) } actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
actionFab = null 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.FabController
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.util.view.shrinkOnScroll 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.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
@ -104,23 +101,22 @@ class DownloadController :
override fun configureFab(fab: ExtendedFloatingActionButton) { override fun configureFab(fab: ExtendedFloatingActionButton) {
actionFab = fab actionFab = fab
fab.clicks() fab.setOnClickListener {
.onEach { val context = applicationContext ?: return@setOnClickListener
val context = applicationContext ?: return@onEach
if (isRunning) { if (isRunning) {
DownloadService.stop(context) DownloadService.stop(context)
presenter.pauseDownloads() presenter.pauseDownloads()
} else { } else {
DownloadService.start(context) DownloadService.start(context)
}
setInformationView()
} }
.launchIn(scope)
setInformationView()
}
} }
override fun cleanupFab(fab: ExtendedFloatingActionButton) { override fun cleanupFab(fab: ExtendedFloatingActionButton) {
fab.setOnClickListener(null)
actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) } actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
actionFab = null actionFab = null
} }

View file

@ -169,13 +169,13 @@ class LibraryController(
activeCategory = it activeCategory = it
updateTitle() updateTitle()
} }
.launchIn(scope) .launchIn(viewScope)
getColumnsPreferenceForCurrentOrientation().asImmediateFlow { mangaPerRow = it } getColumnsPreferenceForCurrentOrientation().asImmediateFlow { mangaPerRow = it }
.drop(1) .drop(1)
// Set again the adapter to recalculate the covers height // Set again the adapter to recalculate the covers height
.onEach { reattachAdapter() } .onEach { reattachAdapter() }
.launchIn(scope) .launchIn(viewScope)
if (selectedMangas.isNotEmpty()) { if (selectedMangas.isNotEmpty()) {
createActionModeIfNeeded() createActionModeIfNeeded()
@ -197,7 +197,7 @@ class LibraryController(
GlobalSearchController(query).withFadeTransaction() GlobalSearchController(query).withFadeTransaction()
) )
} }
.launchIn(scope) .launchIn(viewScope)
(activity!! as MainActivity).fixViewToBottom(binding.actionToolbar) (activity!! as MainActivity).fixViewToBottom(binding.actionToolbar)
} }
@ -212,6 +212,7 @@ class LibraryController(
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
(activity!! as MainActivity).clearFixViewToBottom(binding.actionToolbar)
binding.actionToolbar.destroy() binding.actionToolbar.destroy()
adapter?.onDestroy() adapter?.onDestroy()
adapter = null adapter = null
@ -391,7 +392,7 @@ class LibraryController(
query = it.toString() query = it.toString()
performSearch() performSearch()
} }
.launchIn(scope) .launchIn(viewScope)
} }
private fun performSearch() { private fun performSearch() {

View file

@ -67,6 +67,8 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
private var isConfirmingExit: Boolean = false private var isConfirmingExit: Boolean = false
private var isHandlingShortcut: Boolean = false private var isHandlingShortcut: Boolean = false
private var fixedViewsToBottom = mutableMapOf<View, AppBarLayout.OnOffsetChangedListener>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -401,12 +403,17 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
* the collapsing AppBarLayout. * the collapsing AppBarLayout.
*/ */
fun fixViewToBottom(view: View) { fun fixViewToBottom(view: View) {
binding.appbar.addOnOffsetChangedListener( val listener = AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset -> val maxAbsOffset = appBarLayout.measuredHeight - binding.tabs.measuredHeight
val maxAbsOffset = appBarLayout.measuredHeight - binding.tabs.measuredHeight view.translationY = -maxAbsOffset - verticalOffset.toFloat()
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() { private fun setBottomNavBehaviorOnScroll() {

View file

@ -76,7 +76,6 @@ import eu.kanade.tachiyomi.util.view.shrinkOnScroll
import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.snack
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks
import reactivecircus.flowbinding.recyclerview.scrollEvents import reactivecircus.flowbinding.recyclerview.scrollEvents
import reactivecircus.flowbinding.swiperefreshlayout.refreshes import reactivecircus.flowbinding.swiperefreshlayout.refreshes
import timber.log.Timber import timber.log.Timber
@ -228,14 +227,14 @@ class MangaController :
binding.recycler.scrollEvents() binding.recycler.scrollEvents()
.onEach { updateToolbarTitleAlpha() } .onEach { updateToolbarTitleAlpha() }
.launchIn(scope) .launchIn(viewScope)
binding.swipeRefresh.refreshes() binding.swipeRefresh.refreshes()
.onEach { .onEach {
fetchMangaInfoFromSource(manualFetch = true) fetchMangaInfoFromSource(manualFetch = true)
fetchChaptersFromSource(manualFetch = true) fetchChaptersFromSource(manualFetch = true)
} }
.launchIn(scope) .launchIn(viewScope)
(activity!! as MainActivity).fixViewToBottom(binding.actionToolbar) (activity!! as MainActivity).fixViewToBottom(binding.actionToolbar)
@ -279,42 +278,42 @@ class MangaController :
actionFab = fab actionFab = fab
fab.setText(R.string.action_start) fab.setText(R.string.action_start)
fab.setIconResource(R.drawable.ic_play_arrow_24dp) fab.setIconResource(R.drawable.ic_play_arrow_24dp)
fab.clicks() fab.setOnClickListener {
.onEach { val item = presenter.getNextUnreadChapter()
val item = presenter.getNextUnreadChapter() if (item != null) {
if (item != null) { // Create animation listener
// Create animation listener val revealAnimationListener: Animator.AnimatorListener = object : AnimatorListenerAdapter() {
val revealAnimationListener: Animator.AnimatorListener = object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?) {
override fun onAnimationStart(animation: Animator?) { openChapter(item.chapter, true)
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) { override fun cleanupFab(fab: ExtendedFloatingActionButton) {
fab.setOnClickListener(null)
actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) } actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
actionFab = null actionFab = null
} }
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
(activity!! as MainActivity).clearFixViewToBottom(binding.actionToolbar)
binding.actionToolbar.destroy() binding.actionToolbar.destroy()
mangaInfoAdapter = null mangaInfoAdapter = null
chaptersHeaderAdapter = null chaptersHeaderAdapter = null

View file

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

View file

@ -77,7 +77,7 @@ class TrackSearchDialog : DialogController {
.onEach { position -> .onEach { position ->
selectedItem = adapter.getItem(position) selectedItem = adapter.getItem(position)
} }
.launchIn(trackController.scope) .launchIn(trackController.viewScope)
// Do an initial search based on the manga's title // Do an initial search based on the manga's title
if (savedState == null) { if (savedState == null) {
@ -99,7 +99,7 @@ class TrackSearchDialog : DialogController {
.debounce(TimeUnit.SECONDS.toMillis(1)) .debounce(TimeUnit.SECONDS.toMillis(1))
.filter { it.isNotBlank() } .filter { it.isNotBlank() }
.onEach { search(it.toString()) } .onEach { search(it.toString()) }
.launchIn(trackController.scope) .launchIn(trackController.viewScope)
} }
private fun search(query: String) { private fun search(query: String) {

View file

@ -189,7 +189,7 @@ class HistoryController :
query = it.toString() query = it.toString()
presenter.updateList(query) presenter.updateList(query)
} }
.launchIn(scope) .launchIn(viewScope)
// Fixes problem with the overflow icon showing up in lieu of search // Fixes problem with the overflow icon showing up in lieu of search
searchItem.fixExpand( searchItem.fixExpand(

View file

@ -97,7 +97,7 @@ class UpdatesController :
val firstPos = layoutManager.findFirstCompletelyVisibleItemPosition() val firstPos = layoutManager.findFirstCompletelyVisibleItemPosition()
binding.swipeRefresh.isEnabled = firstPos <= 0 binding.swipeRefresh.isEnabled = firstPos <= 0
} }
.launchIn(scope) .launchIn(viewScope)
binding.swipeRefresh.setDistanceToTriggerSync((2 * 64 * view.resources.displayMetrics.density).toInt()) binding.swipeRefresh.setDistanceToTriggerSync((2 * 64 * view.resources.displayMetrics.density).toInt())
binding.swipeRefresh.refreshes() 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. // It can be a very long operation, so we disable swipe refresh and show a toast.
binding.swipeRefresh.isRefreshing = false binding.swipeRefresh.isRefreshing = false
} }
.launchIn(scope) .launchIn(viewScope)
(activity!! as MainActivity).fixViewToBottom(binding.actionToolbar) (activity!! as MainActivity).fixViewToBottom(binding.actionToolbar)
} }
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
(activity!! as MainActivity).clearFixViewToBottom(binding.actionToolbar)
binding.actionToolbar.destroy() binding.actionToolbar.destroy()
adapter = null adapter = null
super.onDestroyView(view) super.onDestroyView(view)