Address more coroutine scope leaks

This commit is contained in:
arkon 2021-01-07 18:15:57 -05:00
parent 36f81b4a62
commit 6fb7a85e8a
23 changed files with 85 additions and 83 deletions

View File

@ -272,7 +272,7 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
// For detecting memory leaks; see https://square.github.io/leakcanary/ // For detecting memory leaks; see https://square.github.io/leakcanary/
// debugImplementation("com.squareup.leakcanary:leakcanary-android:2.4") // debugImplementation("com.squareup.leakcanary:leakcanary-android:2.6")
} }
tasks { tasks {

View File

@ -9,9 +9,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
@ -27,7 +25,7 @@ class DownloadProvider(private val context: Context) {
private val preferences: PreferencesHelper by injectLazy() private val preferences: PreferencesHelper by injectLazy()
private val scope = CoroutineScope(Job() + Dispatchers.Main) private val scope = MainScope()
/** /**
* The root directory for downloads. * The root directory for downloads.

View File

@ -1,21 +1,32 @@
package eu.kanade.tachiyomi.ui.base.presenter package eu.kanade.tachiyomi.ui.base.presenter
import android.os.Bundle import android.os.Bundle
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import nucleus.presenter.RxPresenter import nucleus.presenter.RxPresenter
import nucleus.presenter.delivery.Delivery import nucleus.presenter.delivery.Delivery
import rx.Observable import rx.Observable
open class BasePresenter<V> : RxPresenter<V>() { open class BasePresenter<V> : RxPresenter<V>() {
lateinit var presenterScope: CoroutineScope
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
try { try {
super.onCreate(savedState) super.onCreate(savedState)
presenterScope = MainScope()
} catch (e: NullPointerException) { } catch (e: NullPointerException) {
// Swallow this error. This should be fixed in the library but since it's not critical // Swallow this error. This should be fixed in the library but since it's not critical
// (only used by restartables) it should be enough. It saves me a fork. // (only used by restartables) it should be enough. It saves me a fork.
} }
} }
override fun onDestroy() {
super.onDestroy()
presenterScope.cancel()
}
/** /**
* Subscribes an observable with [deliverFirst] and adds it to the presenter's lifecycle * Subscribes an observable with [deliverFirst] and adds it to the presenter's lifecycle
* subscription list. * subscription list.

View File

@ -9,9 +9,6 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.ExtensionDetailHeaderBinding import eu.kanade.tachiyomi.databinding.ExtensionDetailHeaderBinding
import eu.kanade.tachiyomi.ui.browse.extension.getApplicationIcon import eu.kanade.tachiyomi.ui.browse.extension.getApplicationIcon
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
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.android.view.clicks
@ -19,7 +16,6 @@ import reactivecircus.flowbinding.android.view.clicks
class ExtensionDetailsHeaderAdapter(private val presenter: ExtensionDetailsPresenter) : class ExtensionDetailsHeaderAdapter(private val presenter: ExtensionDetailsPresenter) :
RecyclerView.Adapter<ExtensionDetailsHeaderAdapter.HeaderViewHolder>() { RecyclerView.Adapter<ExtensionDetailsHeaderAdapter.HeaderViewHolder>() {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
private lateinit var binding: ExtensionDetailHeaderBinding private lateinit var binding: ExtensionDetailHeaderBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
@ -47,7 +43,7 @@ class ExtensionDetailsHeaderAdapter(private val presenter: ExtensionDetailsPrese
binding.extensionUninstallButton.clicks() binding.extensionUninstallButton.clicks()
.onEach { presenter.uninstallExtension() } .onEach { presenter.uninstallExtension() }
.launchIn(scope) .launchIn(presenter.presenterScope)
if (extension.isObsolete) { if (extension.isObsolete) {
binding.extensionWarningBanner.isVisible = true binding.extensionWarningBanner.isVisible = true

View File

@ -6,9 +6,6 @@ import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.drop
@ -33,8 +30,6 @@ class SourcePresenter(
private val preferences: PreferencesHelper = Injekt.get() private val preferences: PreferencesHelper = Injekt.get()
) : BasePresenter<SourceController>() { ) : BasePresenter<SourceController>() {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
var sources = getEnabledSources() var sources = getEnabledSources()
/** /**
@ -98,7 +93,7 @@ class SourcePresenter(
.onStart { delay(500) } .onStart { delay(500) }
.distinctUntilChanged() .distinctUntilChanged()
.onEach { updateLastUsedSource(it) } .onEach { updateLastUsedSource(it) }
.launchIn(scope) .launchIn(presenterScope)
} }
private fun updateLastUsedSource(sourceId: Long) { private fun updateLastUsedSource(sourceId: Long) {

View File

@ -10,16 +10,14 @@ import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.widget.textChanges import reactivecircus.flowbinding.android.widget.textChanges
open class TextItem(val filter: Filter.Text) : AbstractFlexibleItem<TextItem.Holder>() { open class TextItem(val filter: Filter.Text) : AbstractFlexibleItem<TextItem.Holder>() {
private val scope = CoroutineScope(Job() + Dispatchers.Main) private val scope = MainScope()
override fun getLayoutRes(): Int { override fun getLayoutRes(): Int {
return R.layout.navigation_view_text return R.layout.navigation_view_text

View File

@ -19,9 +19,7 @@ import eu.kanade.tachiyomi.util.lang.plusAssign
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.inflate import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.widget.AutofitRecyclerView import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.recyclerview.scrollStateChanges import reactivecircus.flowbinding.recyclerview.scrollStateChanges
@ -37,7 +35,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener { FlexibleAdapter.OnItemLongClickListener {
private val scope = CoroutineScope(Job() + Dispatchers.Main) private val scope = MainScope()
private val preferences: PreferencesHelper by injectLazy() private val preferences: PreferencesHelper by injectLazy()

View File

@ -9,9 +9,6 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.MangaChaptersHeaderBinding import eu.kanade.tachiyomi.databinding.MangaChaptersHeaderBinding
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -25,7 +22,6 @@ class MangaChaptersHeaderAdapter(
private var numChapters: Int? = null private var numChapters: Int? = null
private var hasActiveFilters: Boolean = false private var hasActiveFilters: Boolean = false
private val scope = CoroutineScope(Job() + Dispatchers.Main)
private lateinit var binding: MangaChaptersHeaderBinding private lateinit var binding: MangaChaptersHeaderBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
@ -68,7 +64,7 @@ class MangaChaptersHeaderAdapter(
merge(view.clicks(), binding.btnChaptersFilter.clicks()) merge(view.clicks(), binding.btnChaptersFilter.clicks())
.onEach { controller.showSettingsSheet() } .onEach { controller.showSettingsSheet() }
.launchIn(scope) .launchIn(controller.viewScope)
} }
} }
} }

View File

@ -23,9 +23,6 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.setChips import eu.kanade.tachiyomi.util.view.setChips
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -47,7 +44,6 @@ class MangaInfoHeaderAdapter(
private var source: Source = controller.presenter.source private var source: Source = controller.presenter.source
private var trackCount: Int = 0 private var trackCount: Int = 0
private val scope = CoroutineScope(Job() + Dispatchers.Main)
private lateinit var binding: MangaInfoHeaderBinding private lateinit var binding: MangaInfoHeaderBinding
private var initialLoad: Boolean = true private var initialLoad: Boolean = true
@ -90,12 +86,12 @@ class MangaInfoHeaderAdapter(
binding.btnFavorite.clicks() binding.btnFavorite.clicks()
.onEach { controller.onFavoriteClick() } .onEach { controller.onFavoriteClick() }
.launchIn(scope) .launchIn(controller.viewScope)
if (controller.presenter.manga.favorite && controller.presenter.getCategories().isNotEmpty()) { if (controller.presenter.manga.favorite && controller.presenter.getCategories().isNotEmpty()) {
binding.btnFavorite.longClicks() binding.btnFavorite.longClicks()
.onEach { controller.onCategoriesClick() } .onEach { controller.onCategoriesClick() }
.launchIn(scope) .launchIn(controller.viewScope)
} }
with(binding.btnTracking) { with(binding.btnTracking) {
@ -118,7 +114,7 @@ class MangaInfoHeaderAdapter(
clicks() clicks()
.onEach { controller.onTrackingClick() } .onEach { controller.onTrackingClick() }
.launchIn(scope) .launchIn(controller.viewScope)
} else { } else {
isVisible = false isVisible = false
} }
@ -128,7 +124,7 @@ class MangaInfoHeaderAdapter(
binding.btnWebview.isVisible = true binding.btnWebview.isVisible = true
binding.btnWebview.clicks() binding.btnWebview.clicks()
.onEach { controller.openMangaInWebView() } .onEach { controller.openMangaInWebView() }
.launchIn(scope) .launchIn(controller.viewScope)
} }
binding.mangaFullTitle.longClicks() binding.mangaFullTitle.longClicks()
@ -138,13 +134,13 @@ class MangaInfoHeaderAdapter(
binding.mangaFullTitle.text.toString() binding.mangaFullTitle.text.toString()
) )
} }
.launchIn(scope) .launchIn(controller.viewScope)
binding.mangaFullTitle.clicks() binding.mangaFullTitle.clicks()
.onEach { .onEach {
controller.performGlobalSearch(binding.mangaFullTitle.text.toString()) controller.performGlobalSearch(binding.mangaFullTitle.text.toString())
} }
.launchIn(scope) .launchIn(controller.viewScope)
binding.mangaAuthor.longClicks() binding.mangaAuthor.longClicks()
.onEach { .onEach {
@ -153,13 +149,13 @@ class MangaInfoHeaderAdapter(
binding.mangaAuthor.text.toString() binding.mangaAuthor.text.toString()
) )
} }
.launchIn(scope) .launchIn(controller.viewScope)
binding.mangaAuthor.clicks() binding.mangaAuthor.clicks()
.onEach { .onEach {
controller.performGlobalSearch(binding.mangaAuthor.text.toString()) controller.performGlobalSearch(binding.mangaAuthor.text.toString())
} }
.launchIn(scope) .launchIn(controller.viewScope)
binding.mangaArtist.longClicks() binding.mangaArtist.longClicks()
.onEach { .onEach {
@ -168,13 +164,13 @@ class MangaInfoHeaderAdapter(
binding.mangaArtist.text.toString() binding.mangaArtist.text.toString()
) )
} }
.launchIn(scope) .launchIn(controller.viewScope)
binding.mangaArtist.clicks() binding.mangaArtist.clicks()
.onEach { .onEach {
controller.performGlobalSearch(binding.mangaArtist.text.toString()) controller.performGlobalSearch(binding.mangaArtist.text.toString())
} }
.launchIn(scope) .launchIn(controller.viewScope)
binding.mangaSummaryText.longClicks() binding.mangaSummaryText.longClicks()
.onEach { .onEach {
@ -183,7 +179,7 @@ class MangaInfoHeaderAdapter(
binding.mangaSummaryText.text.toString() binding.mangaSummaryText.text.toString()
) )
} }
.launchIn(scope) .launchIn(controller.viewScope)
binding.mangaCover.longClicks() binding.mangaCover.longClicks()
.onEach { .onEach {
@ -192,7 +188,7 @@ class MangaInfoHeaderAdapter(
controller.presenter.manga.title controller.presenter.manga.title
) )
} }
.launchIn(scope) .launchIn(controller.viewScope)
setMangaInfo(manga, source) setMangaInfo(manga, source)
} }
@ -300,7 +296,7 @@ class MangaInfoHeaderAdapter(
binding.mangaInfoToggleLess.clicks() binding.mangaInfoToggleLess.clicks()
) )
.onEach { toggleMangaInfo() } .onEach { toggleMangaInfo() }
.launchIn(scope) .launchIn(controller.viewScope)
// Expand manga info if navigated from source listing // Expand manga info if navigated from source listing
if (initialLoad && fromSource) { if (initialLoad && fromSource) {

View File

@ -4,8 +4,6 @@ import com.tfcporciuncula.flow.Preference
import eu.kanade.tachiyomi.data.preference.PreferenceValues.TappingInvertMode import eu.kanade.tachiyomi.data.preference.PreferenceValues.TappingInvertMode
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -13,9 +11,7 @@ import kotlinx.coroutines.flow.onEach
/** /**
* Common configuration for all viewers. * Common configuration for all viewers.
*/ */
abstract class ViewerConfig(preferences: PreferencesHelper) { abstract class ViewerConfig(preferences: PreferencesHelper, private val scope: CoroutineScope) {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
var imagePropertyChangedListener: (() -> Unit)? = null var imagePropertyChangedListener: (() -> Unit)? = null

View File

@ -6,14 +6,18 @@ import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.EdgeNavigation import eu.kanade.tachiyomi.ui.reader.viewer.navigation.EdgeNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.KindlishNavigation import eu.kanade.tachiyomi.ui.reader.viewer.navigation.KindlishNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.LNavigation import eu.kanade.tachiyomi.ui.reader.viewer.navigation.LNavigation
import kotlinx.coroutines.CoroutineScope
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
/** /**
* Configuration used by pager viewers. * Configuration used by pager viewers.
*/ */
class PagerConfig(private val viewer: PagerViewer, preferences: PreferencesHelper = Injekt.get()) : class PagerConfig(
ViewerConfig(preferences) { private val viewer: PagerViewer,
scope: CoroutineScope,
preferences: PreferencesHelper = Injekt.get()
) : ViewerConfig(preferences, scope) {
var imageScaleType = 1 var imageScaleType = 1
private set private set

View File

@ -16,6 +16,8 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import timber.log.Timber import timber.log.Timber
import kotlin.math.min import kotlin.math.min
@ -25,6 +27,8 @@ import kotlin.math.min
@Suppress("LeakingThis") @Suppress("LeakingThis")
abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
private val scope = MainScope()
/** /**
* View pager used by this viewer. It's abstract to implement L2R, R2L and vertical pagers on * View pager used by this viewer. It's abstract to implement L2R, R2L and vertical pagers on
* top of this class. * top of this class.
@ -34,7 +38,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
/** /**
* Configuration used by the pager, like allow taps, scale mode on images, page transitions... * Configuration used by the pager, like allow taps, scale mode on images, page transitions...
*/ */
val config = PagerConfig(this) val config = PagerConfig(this, scope)
/** /**
* Adapter of the pager. * Adapter of the pager.
@ -114,6 +118,11 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
} }
} }
override fun destroy() {
super.destroy()
scope.cancel()
}
/** /**
* Creates a new ViewPager. * Creates a new ViewPager.
*/ */

View File

@ -6,13 +6,17 @@ import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.EdgeNavigation import eu.kanade.tachiyomi.ui.reader.viewer.navigation.EdgeNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.KindlishNavigation import eu.kanade.tachiyomi.ui.reader.viewer.navigation.KindlishNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.LNavigation import eu.kanade.tachiyomi.ui.reader.viewer.navigation.LNavigation
import kotlinx.coroutines.CoroutineScope
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
/** /**
* Configuration used by webtoon viewers. * Configuration used by webtoon viewers.
*/ */
class WebtoonConfig(preferences: PreferencesHelper = Injekt.get()) : ViewerConfig(preferences) { class WebtoonConfig(
scope: CoroutineScope,
preferences: PreferencesHelper = Injekt.get()
) : ViewerConfig(preferences, scope) {
var imageCropBorders = false var imageCropBorders = false
private set private set

View File

@ -16,6 +16,8 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import rx.subscriptions.CompositeSubscription import rx.subscriptions.CompositeSubscription
import timber.log.Timber import timber.log.Timber
import kotlin.math.max import kotlin.math.max
@ -26,6 +28,8 @@ import kotlin.math.min
*/ */
class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = true) : BaseViewer { class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = true) : BaseViewer {
private val scope = MainScope()
/** /**
* Recycler view used by this viewer. * Recycler view used by this viewer.
*/ */
@ -59,7 +63,7 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
/** /**
* Configuration used by this viewer, like allow taps, or crop image borders. * Configuration used by this viewer, like allow taps, or crop image borders.
*/ */
val config = WebtoonConfig() val config = WebtoonConfig(scope)
/** /**
* Subscriptions to keep while this viewer is used. * Subscriptions to keep while this viewer is used.
@ -168,6 +172,7 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
*/ */
override fun destroy() { override fun destroy() {
super.destroy() super.destroy()
scope.cancel()
subscriptions.unsubscribe() subscriptions.unsubscribe()
} }

View File

@ -133,14 +133,14 @@ class SettingsBackupController : SettingsController() {
} }
preferences.backupInterval().asImmediateFlow { isVisible = it > 0 } preferences.backupInterval().asImmediateFlow { isVisible = it > 0 }
.launchIn(scope) .launchIn(viewScope)
preferences.backupsDirectory().asFlow() preferences.backupsDirectory().asFlow()
.onEach { path -> .onEach { path ->
val dir = UniFile.fromUri(context, path.toUri()) val dir = UniFile.fromUri(context, path.toUri())
summary = dir.filePath + "/automatic" summary = dir.filePath + "/automatic"
} }
.launchIn(scope) .launchIn(viewScope)
} }
intListPreference { intListPreference {
key = Keys.numberOfBackups key = Keys.numberOfBackups
@ -151,7 +151,7 @@ class SettingsBackupController : SettingsController() {
summary = "%s" summary = "%s"
preferences.backupInterval().asImmediateFlow { isVisible = it > 0 } preferences.backupInterval().asImmediateFlow { isVisible = it > 0 }
.launchIn(scope) .launchIn(viewScope)
} }
switchPreference { switchPreference {
key = Keys.createLegacyBackup key = Keys.createLegacyBackup
@ -159,7 +159,7 @@ class SettingsBackupController : SettingsController() {
defaultValue = true defaultValue = true
preferences.backupInterval().asImmediateFlow { isVisible = it > 0 } preferences.backupInterval().asImmediateFlow { isVisible = it > 0 }
.launchIn(scope) .launchIn(viewScope)
} }
} }
} }

View File

@ -64,7 +64,7 @@ class SettingsBrowseController : SettingsController() {
titleRes = R.string.pref_label_nsfw_extension titleRes = R.string.pref_label_nsfw_extension
defaultValue = true defaultValue = true
preferences.showNsfwExtension().asImmediateFlow { isVisible = it }.launchIn(scope) preferences.showNsfwExtension().asImmediateFlow { isVisible = it }.launchIn(viewScope)
} }
infoPreference(R.string.parental_controls_info) infoPreference(R.string.parental_controls_info)

View File

@ -22,9 +22,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.base.controller.BaseController import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.base.controller.RootController import eu.kanade.tachiyomi.ui.base.controller.RootController
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.subscriptions.CompositeSubscription import rx.subscriptions.CompositeSubscription
@ -35,7 +33,7 @@ abstract class SettingsController : PreferenceController() {
var preferenceKey: String? = null var preferenceKey: String? = null
val preferences: PreferencesHelper = Injekt.get() val preferences: PreferencesHelper = Injekt.get()
val scope = CoroutineScope(Job() + Dispatchers.Main) val viewScope = MainScope()
var untilDestroySubscriptions = CompositeSubscription() var untilDestroySubscriptions = CompositeSubscription()
private set private set

View File

@ -57,7 +57,7 @@ class SettingsDownloadController : SettingsController() {
val dir = UniFile.fromUri(context, path.toUri()) val dir = UniFile.fromUri(context, path.toUri())
summary = dir.filePath ?: path summary = dir.filePath ?: path
} }
.launchIn(scope) .launchIn(viewScope)
} }
switchPreference { switchPreference {
key = Keys.downloadOnlyOverWifi key = Keys.downloadOnlyOverWifi
@ -112,7 +112,7 @@ class SettingsDownloadController : SettingsController() {
entryValues = categories.map { it.id.toString() }.toTypedArray() entryValues = categories.map { it.id.toString() }.toTypedArray()
preferences.downloadNew().asImmediateFlow { isVisible = it } preferences.downloadNew().asImmediateFlow { isVisible = it }
.launchIn(scope) .launchIn(viewScope)
preferences.downloadNewCategories().asFlow() preferences.downloadNewCategories().asFlow()
.onEach { mutableSet -> .onEach { mutableSet ->
@ -126,7 +126,7 @@ class SettingsDownloadController : SettingsController() {
selectedCategories.joinToString { it.name } selectedCategories.joinToString { it.name }
} }
} }
.launchIn(scope) .launchIn(viewScope)
} }
} }
} }

View File

@ -116,7 +116,7 @@ class SettingsGeneralController : SettingsController() {
summary = "%s" summary = "%s"
preferences.themeMode().asImmediateFlow { isVisible = it != Values.ThemeMode.dark } preferences.themeMode().asImmediateFlow { isVisible = it != Values.ThemeMode.dark }
.launchIn(scope) .launchIn(viewScope)
onChange { onChange {
if (preferences.themeMode().get() != Values.ThemeMode.dark) { if (preferences.themeMode().get() != Values.ThemeMode.dark) {
@ -142,7 +142,7 @@ class SettingsGeneralController : SettingsController() {
summary = "%s" summary = "%s"
preferences.themeMode().asImmediateFlow { isVisible = it != Values.ThemeMode.light } preferences.themeMode().asImmediateFlow { isVisible = it != Values.ThemeMode.light }
.launchIn(scope) .launchIn(viewScope)
onChange { onChange {
if (preferences.themeMode().get() != Values.ThemeMode.light) { if (preferences.themeMode().get() != Values.ThemeMode.light) {

View File

@ -70,7 +70,7 @@ class SettingsLibraryController : SettingsController() {
summary = "${context.getString(R.string.portrait)}: $portrait, " + summary = "${context.getString(R.string.portrait)}: $portrait, " +
"${context.getString(R.string.landscape)}: $landscape" "${context.getString(R.string.landscape)}: $landscape"
} }
.launchIn(scope) .launchIn(viewScope)
} }
switchPreference { switchPreference {
key = Keys.jumpToChapters key = Keys.jumpToChapters
@ -150,7 +150,7 @@ class SettingsLibraryController : SettingsController() {
defaultValue = setOf("wifi") defaultValue = setOf("wifi")
preferences.libraryUpdateInterval().asImmediateFlow { isVisible = it > 0 } preferences.libraryUpdateInterval().asImmediateFlow { isVisible = it > 0 }
.launchIn(scope) .launchIn(viewScope)
onChange { onChange {
// Post to event looper to allow the preference to be updated. // Post to event looper to allow the preference to be updated.
@ -180,7 +180,7 @@ class SettingsLibraryController : SettingsController() {
selectedCategories.joinToString { it.name } selectedCategories.joinToString { it.name }
} }
} }
.launchIn(scope) .launchIn(viewScope)
} }
intListPreference { intListPreference {
key = Keys.libraryUpdatePrioritization key = Keys.libraryUpdatePrioritization

View File

@ -150,7 +150,7 @@ class SettingsReaderController : SettingsController() {
defaultValue = "0" defaultValue = "0"
summary = "%s" summary = "%s"
preferences.readWithTapping().asImmediateFlow { isVisible = it }.launchIn(scope) preferences.readWithTapping().asImmediateFlow { isVisible = it }.launchIn(viewScope)
} }
listPreference { listPreference {
key = Keys.pagerNavInverted key = Keys.pagerNavInverted
@ -170,7 +170,7 @@ class SettingsReaderController : SettingsController() {
defaultValue = TappingInvertMode.NONE.name defaultValue = TappingInvertMode.NONE.name
summary = "%s" summary = "%s"
preferences.readWithTapping().asImmediateFlow { isVisible = it }.launchIn(scope) preferences.readWithTapping().asImmediateFlow { isVisible = it }.launchIn(viewScope)
} }
intListPreference { intListPreference {
key = Keys.imageScaleType key = Keys.imageScaleType
@ -223,7 +223,7 @@ class SettingsReaderController : SettingsController() {
defaultValue = "0" defaultValue = "0"
summary = "%s" summary = "%s"
preferences.readWithTapping().asImmediateFlow { isVisible = it }.launchIn(scope) preferences.readWithTapping().asImmediateFlow { isVisible = it }.launchIn(viewScope)
} }
listPreference { listPreference {
key = Keys.webtoonNavInverted key = Keys.webtoonNavInverted
@ -243,7 +243,7 @@ class SettingsReaderController : SettingsController() {
defaultValue = TappingInvertMode.NONE.name defaultValue = TappingInvertMode.NONE.name
summary = "%s" summary = "%s"
preferences.readWithTapping().asImmediateFlow { isVisible = it }.launchIn(scope) preferences.readWithTapping().asImmediateFlow { isVisible = it }.launchIn(viewScope)
} }
intListPreference { intListPreference {
key = Keys.webtoonSidePadding key = Keys.webtoonSidePadding
@ -289,7 +289,7 @@ class SettingsReaderController : SettingsController() {
titleRes = R.string.pref_read_with_volume_keys_inverted titleRes = R.string.pref_read_with_volume_keys_inverted
defaultValue = false defaultValue = false
preferences.readWithVolumeKeys().asImmediateFlow { isVisible = it }.launchIn(scope) preferences.readWithVolumeKeys().asImmediateFlow { isVisible = it }.launchIn(viewScope)
} }
} }
} }

View File

@ -39,7 +39,7 @@ class SettingsSecurityController : SettingsController() {
summary = "%s" summary = "%s"
preferences.useBiometricLock().asImmediateFlow { isVisible = it } preferences.useBiometricLock().asImmediateFlow { isVisible = it }
.launchIn(scope) .launchIn(viewScope)
} }
} }

View File

@ -7,9 +7,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import eu.kanade.tachiyomi.databinding.DownloadCustomAmountBinding import eu.kanade.tachiyomi.databinding.DownloadCustomAmountBinding
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.widget.textChanges import reactivecircus.flowbinding.android.widget.textChanges
@ -37,7 +35,7 @@ class DialogCustomDownloadView @JvmOverloads constructor(context: Context, attrs
*/ */
private var max = 0 private var max = 0
private val scope = CoroutineScope(Job() + Dispatchers.Main) private val scope = MainScope()
private val binding: DownloadCustomAmountBinding private val binding: DownloadCustomAmountBinding