Minor fixes regarding leaks
This commit is contained in:
parent
2caecc01b2
commit
36f81b4a62
16 changed files with 81 additions and 78 deletions
|
@ -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()}")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!!
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -134,7 +134,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
|
|||
isChecked = enabled
|
||||
sourcePrefs.forEach { pref -> pref.isVisible = enabled }
|
||||
}
|
||||
.launchIn(scope)
|
||||
.launchIn(viewScope)
|
||||
}
|
||||
|
||||
// Source enable/disable
|
||||
|
|
|
@ -222,7 +222,7 @@ class SourceController :
|
|||
searchView.queryTextEvents()
|
||||
.filterIsInstance<QueryTextEvent.QuerySubmitted>()
|
||||
.onEach { performGlobalSearch(it.queryText.toString()) }
|
||||
.launchIn(scope)
|
||||
.launchIn(viewScope)
|
||||
}
|
||||
|
||||
private fun performGlobalSearch(query: String) {
|
||||
|
|
|
@ -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<QueryTextEvent.QuerySubmitted>()
|
||||
.onEach { searchWithQuery(it.queryText.toString()) }
|
||||
.launchIn(scope)
|
||||
.launchIn(viewScope)
|
||||
|
||||
searchItem.fixExpand(
|
||||
onExpand = { invalidateMenuOnExpand() },
|
||||
|
|
|
@ -129,7 +129,7 @@ open class GlobalSearchController(
|
|||
searchItem.collapseActionView()
|
||||
setTitle() // Update toolbar title
|
||||
}
|
||||
.launchIn(scope)
|
||||
.launchIn(viewScope)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -67,6 +67,8 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
|||
private var isConfirmingExit: Boolean = false
|
||||
private var isHandlingShortcut: Boolean = false
|
||||
|
||||
private var fixedViewsToBottom = mutableMapOf<View, AppBarLayout.OnOffsetChangedListener>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
@ -401,12 +403,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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
Reference in a new issue