diff --git a/app/build.gradle b/app/build.gradle index 8c677f01b5..026012da10 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -232,10 +232,15 @@ dependencies { // RxBindings final rxbindings_version = '1.0.1' - implementation "com.jakewharton.rxbinding:rxbinding-kotlin:$rxbindings_version" implementation "com.jakewharton.rxbinding:rxbinding-appcompat-v7-kotlin:$rxbindings_version" implementation "com.jakewharton.rxbinding:rxbinding-support-v4-kotlin:$rxbindings_version" - implementation "com.jakewharton.rxbinding:rxbinding-recyclerview-v7-kotlin:$rxbindings_version" + + // FlowBinding + final flowbinding_version = '0.10.2' + implementation "io.github.reactivecircus.flowbinding:flowbinding-android:$flowbinding_version" + implementation "io.github.reactivecircus.flowbinding:flowbinding-appcompat:$flowbinding_version" + implementation "io.github.reactivecircus.flowbinding:flowbinding-recyclerview:$flowbinding_version" + implementation "io.github.reactivecircus.flowbinding:flowbinding-swiperefreshlayout:$flowbinding_version" // Tests testImplementation 'junit:junit:4.13' 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 ffe2706738..6f63956739 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 @@ -10,7 +10,6 @@ import androidx.appcompat.view.ActionMode import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.snackbar.Snackbar -import com.jakewharton.rxbinding.view.clicks import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.SelectableAdapter import eu.davidea.flexibleadapter.helpers.UndoHelper @@ -19,6 +18,11 @@ import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.databinding.CategoriesControllerBinding import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.util.system.toast +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +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. @@ -47,6 +51,8 @@ class CategoryController : NucleusController(), */ private var undoHelper: UndoHelper? = null + private val uiScope = CoroutineScope(Dispatchers.Main) + private lateinit var binding: CategoriesControllerBinding /** @@ -87,9 +93,11 @@ class CategoryController : NucleusController(), adapter?.isHandleDragEnabled = true adapter?.isPermanentDelete = false - binding.fab.clicks().subscribeUntilDestroy { - CategoryCreateDialog(this@CategoryController).showDialog(router, null) - } + binding.fab.clicks() + .onEach { + CategoryCreateDialog(this@CategoryController).showDialog(router, null) + } + .launchIn(uiScope) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionController.kt index ede78ab3bb..79fb114bfd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionController.kt @@ -12,8 +12,6 @@ import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.changehandler.FadeChangeHandler -import com.jakewharton.rxbinding.support.v4.widget.refreshes -import com.jakewharton.rxbinding.support.v7.widget.queryTextChanges import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R @@ -24,6 +22,13 @@ import eu.kanade.tachiyomi.extension.ExtensionUpdateJob import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import reactivecircus.flowbinding.appcompat.queryTextChanges +import reactivecircus.flowbinding.swiperefreshlayout.refreshes import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -47,6 +52,8 @@ open class ExtensionController : NucleusController(), private var query = "" + private val uiScope = CoroutineScope(Dispatchers.Main) + private lateinit var binding: ExtensionControllerBinding init { @@ -70,9 +77,9 @@ open class ExtensionController : NucleusController(), super.onViewCreated(view) binding.extSwipeRefresh.isRefreshing = true - binding.extSwipeRefresh.refreshes().subscribeUntilDestroy { - presenter.findAvailableExtensions() - } + binding.extSwipeRefresh.refreshes() + .onEach { presenter.findAvailableExtensions() } + .launchIn(uiScope) // Initialize adapter, scroll listener and recycler views adapter = ExtensionAdapter(this) @@ -146,11 +153,12 @@ open class ExtensionController : NucleusController(), } searchView.queryTextChanges() - .filter { router.backstack.lastOrNull()?.controller() == this } - .subscribeUntilDestroy { - query = it.toString() - drawExtensions() - } + .filter { router.backstack.lastOrNull()?.controller() == this } + .onEach { + query = it.toString() + drawExtensions() + } + .launchIn(uiScope) // Fixes problem with the overflow icon showing up in lieu of search searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() }) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsController.kt index c7b122add7..748149cd37 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsController.kt @@ -22,7 +22,6 @@ import androidx.preference.PreferenceScreen import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL import androidx.recyclerview.widget.LinearLayoutManager -import com.jakewharton.rxbinding.view.clicks import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore @@ -33,6 +32,11 @@ import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.util.preference.preferenceCategory import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.view.visible +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import reactivecircus.flowbinding.android.view.clicks @SuppressLint("RestrictedApi") class ExtensionDetailsController(bundle: Bundle? = null) : @@ -44,6 +48,8 @@ class ExtensionDetailsController(bundle: Bundle? = null) : private var preferenceScreen: PreferenceScreen? = null + private val uiScope = CoroutineScope(Dispatchers.Main) + private lateinit var binding: ExtensionDetailControllerBinding constructor(pkgName: String) : this(Bundle().apply { @@ -76,9 +82,9 @@ class ExtensionDetailsController(bundle: Bundle? = null) : binding.extensionLang.text = context.getString(R.string.ext_language_info, LocaleHelper.getDisplayName(extension.lang, context)) binding.extensionPkg.text = extension.pkgName extension.getApplicationIcon(context)?.let { binding.extensionIcon.setImageDrawable(it) } - binding.extensionUninstallButton.clicks().subscribeUntilDestroy { - presenter.uninstallExtension() - } + binding.extensionUninstallButton.clicks() + .onEach { presenter.uninstallExtension() } + .launchIn(uiScope) if (extension.isObsolete) { binding.extensionObsolete.visible() 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 541ecee19e..e6c3ac9613 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 @@ -20,7 +20,6 @@ import com.bluelinelabs.conductor.ControllerChangeType import com.f2prateek.rx.preferences.Preference import com.google.android.material.tabs.TabLayout import com.jakewharton.rxbinding.support.v4.view.pageSelections -import com.jakewharton.rxbinding.support.v7.widget.queryTextChanges import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.PublishRelay import eu.kanade.tachiyomi.R @@ -40,6 +39,12 @@ import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.toast import java.io.IOException import kotlinx.android.synthetic.main.main_activity.tabs +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import reactivecircus.flowbinding.appcompat.queryTextChanges import rx.Subscription import timber.log.Timber import uy.kohesive.injekt.Injekt @@ -123,7 +128,7 @@ class LibraryController( private var tabsVisibilitySubscription: Subscription? = null - private var searchViewSubscription: Subscription? = null + private val uiScope = CoroutineScope(Dispatchers.Main) private lateinit var binding: LibraryControllerBinding @@ -335,14 +340,14 @@ class LibraryController( // Mutate the filter icon because it needs to be tinted and the resource is shared. menu.findItem(R.id.action_filter).icon.mutate() - searchViewSubscription?.unsubscribe() - searchViewSubscription = searchView.queryTextChanges() - // Ignore events if this controller isn't at the top - .filter { router.backstack.lastOrNull()?.controller() == this } - .subscribeUntilDestroy { - query = it.toString() - searchRelay.call(query) - } + searchView.queryTextChanges() + // Ignore events if this controller isn't at the top + .filter { router.backstack.lastOrNull()?.controller() == this } + .onEach { + query = it.toString() + searchRelay.call(query) + } + .launchIn(uiScope) searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() }) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt index d62dbf4cf4..8a37afebbb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt @@ -16,8 +16,6 @@ import androidx.core.graphics.drawable.DrawableCompat import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar -import com.jakewharton.rxbinding.support.v4.widget.refreshes -import com.jakewharton.rxbinding.view.clicks import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.SelectableAdapter import eu.kanade.tachiyomi.R @@ -36,6 +34,12 @@ import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.shrinkOnScroll import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.visible +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import reactivecircus.flowbinding.android.view.clicks +import reactivecircus.flowbinding.swiperefreshlayout.refreshes import timber.log.Timber class ChaptersController : NucleusController(), @@ -62,6 +66,8 @@ class ChaptersController : NucleusController(), private var lastClickPosition = -1 + private val uiScope = CoroutineScope(Dispatchers.Main) + private lateinit var binding: ChaptersControllerBinding init { @@ -91,27 +97,32 @@ class ChaptersController : NucleusController(), binding.recycler.setHasFixedSize(true) adapter?.fastScroller = binding.fastScroller - binding.swipeRefresh.refreshes().subscribeUntilDestroy { fetchChaptersFromSource() } + binding.swipeRefresh.refreshes() + .onEach { fetchChaptersFromSource() } + .launchIn(uiScope) - binding.fab.clicks().subscribeUntilDestroy { - 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) + binding.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) + } } - } - // Get coordinates and start animation - val coordinates = binding.fab.getCoordinates() - if (!binding.revealView.showRevealEffect(coordinates.x, coordinates.y, revealAnimationListener)) { - openChapter(item.chapter) + // Get coordinates and start animation + val coordinates = binding.fab.getCoordinates() + if (!binding.revealView.showRevealEffect(coordinates.x, coordinates.y, revealAnimationListener)) { + openChapter(item.chapter) + } + } else { + view.context.toast(R.string.no_next_chapter) } - } else { - view.context.toast(R.string.no_next_chapter) } - } + .launchIn(uiScope) + binding.fab.shrinkOnScroll(binding.recycler) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt index 1cfd5a163b..ad9bdc38eb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt @@ -27,9 +27,6 @@ import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.transition.Transition import com.google.android.material.chip.Chip -import com.jakewharton.rxbinding.support.v4.widget.refreshes -import com.jakewharton.rxbinding.view.clicks -import com.jakewharton.rxbinding.view.longClicks import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Manga @@ -56,6 +53,13 @@ import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.visible import jp.wasabeef.glide.transformations.CropSquareTransformation +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import reactivecircus.flowbinding.android.view.clicks +import reactivecircus.flowbinding.android.view.longClicks +import reactivecircus.flowbinding.swiperefreshlayout.refreshes import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy @@ -70,6 +74,8 @@ class MangaInfoController : NucleusController(), private val preferences: PreferencesHelper by injectLazy() + private val uiScope = CoroutineScope(Dispatchers.Main) + private lateinit var binding: MangaInfoControllerBinding init { @@ -91,53 +97,79 @@ class MangaInfoController : NucleusController(), super.onViewCreated(view) // Set onclickListener to toggle favorite when favorite button clicked. - binding.btnFavorite.clicks().subscribeUntilDestroy { onFavoriteClick() } + binding.btnFavorite.clicks() + .onEach { onFavoriteClick() } + .launchIn(uiScope) // Set onLongClickListener to manage categories when favorite button is clicked. - binding.btnFavorite.longClicks().subscribeUntilDestroy { onFavoriteLongClick() } + binding.btnFavorite.longClicks() + .onEach { onFavoriteLongClick() } + .launchIn(uiScope) if (presenter.source is HttpSource) { binding.btnWebview.visible() binding.btnShare.visible() - binding.btnWebview.clicks().subscribeUntilDestroy { openInWebView() } - binding.btnShare.clicks().subscribeUntilDestroy { shareManga() } + binding.btnWebview.clicks() + .onEach { openInWebView() } + .launchIn(uiScope) + binding.btnShare.clicks() + .onEach { shareManga() } + .launchIn(uiScope) } // Set SwipeRefresh to refresh manga data. - binding.swipeRefresh.refreshes().subscribeUntilDestroy { fetchMangaFromSource() } + binding.swipeRefresh.refreshes() + .onEach { fetchMangaFromSource() } + .launchIn(uiScope) - binding.mangaFullTitle.longClicks().subscribeUntilDestroy { - copyToClipboard(view.context.getString(R.string.title), binding.mangaFullTitle.text.toString()) - } + binding.mangaFullTitle.longClicks() + .onEach { + copyToClipboard(view.context.getString(R.string.title), binding.mangaFullTitle.text.toString()) + } + .launchIn(uiScope) - binding.mangaFullTitle.clicks().subscribeUntilDestroy { - performGlobalSearch(binding.mangaFullTitle.text.toString()) - } + binding.mangaFullTitle.clicks() + .onEach { + performGlobalSearch(binding.mangaFullTitle.text.toString()) + } + .launchIn(uiScope) - binding.mangaArtist.longClicks().subscribeUntilDestroy { - copyToClipboard(binding.mangaArtistLabel.text.toString(), binding.mangaArtist.text.toString()) - } + binding.mangaArtist.longClicks() + .onEach { + copyToClipboard(binding.mangaArtistLabel.text.toString(), binding.mangaArtist.text.toString()) + } + .launchIn(uiScope) - binding.mangaArtist.clicks().subscribeUntilDestroy { - performGlobalSearch(binding.mangaArtist.text.toString()) - } + binding.mangaArtist.clicks() + .onEach { + performGlobalSearch(binding.mangaArtist.text.toString()) + } + .launchIn(uiScope) - binding.mangaAuthor.longClicks().subscribeUntilDestroy { - copyToClipboard(binding.mangaAuthor.text.toString(), binding.mangaAuthor.text.toString()) - } + binding.mangaAuthor.longClicks() + .onEach { + copyToClipboard(binding.mangaAuthor.text.toString(), binding.mangaAuthor.text.toString()) + } + .launchIn(uiScope) - binding.mangaAuthor.clicks().subscribeUntilDestroy { - performGlobalSearch(binding.mangaAuthor.text.toString()) - } + binding.mangaAuthor.clicks() + .onEach { + performGlobalSearch(binding.mangaAuthor.text.toString()) + } + .launchIn(uiScope) - binding.mangaSummary.longClicks().subscribeUntilDestroy { - copyToClipboard(view.context.getString(R.string.description), binding.mangaSummary.text.toString()) - } + binding.mangaSummary.longClicks() + .onEach { + copyToClipboard(view.context.getString(R.string.description), binding.mangaSummary.text.toString()) + } + .launchIn(uiScope) - binding.mangaCover.longClicks().subscribeUntilDestroy { - copyToClipboard(view.context.getString(R.string.title), presenter.manga.title) - } + binding.mangaCover.longClicks() + .onEach { + copyToClipboard(view.context.getString(R.string.title), presenter.manga.title) + } + .launchIn(uiScope) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { 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 1637376fb3..c0cfa5b448 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 @@ -6,13 +6,17 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager -import com.jakewharton.rxbinding.support.v4.widget.refreshes import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.databinding.TrackControllerBinding import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.util.system.toast +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import reactivecircus.flowbinding.swiperefreshlayout.refreshes import timber.log.Timber class TrackController : NucleusController(), @@ -23,6 +27,8 @@ class TrackController : NucleusController(), private var adapter: TrackAdapter? = null + private val uiScope = CoroutineScope(Dispatchers.Main) + private lateinit var binding: TrackControllerBinding init { @@ -47,7 +53,9 @@ class TrackController : NucleusController(), binding.trackRecycler.layoutManager = LinearLayoutManager(view.context) binding.trackRecycler.adapter = adapter binding.swipeRefresh.isEnabled = false - binding.swipeRefresh.refreshes().subscribeUntilDestroy { presenter.refresh() } + binding.swipeRefresh.refreshes() + .onEach { presenter.refresh() } + .launchIn(uiScope) } 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 62879ea0ca..6cc1cdeb7b 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 @@ -4,24 +4,27 @@ import android.app.Dialog import android.os.Bundle import android.view.View import com.afollestad.materialdialogs.MaterialDialog -import com.jakewharton.rxbinding.widget.itemClicks -import com.jakewharton.rxbinding.widget.textChanges import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.ui.base.controller.DialogController -import eu.kanade.tachiyomi.util.lang.plusAssign import eu.kanade.tachiyomi.util.view.invisible import eu.kanade.tachiyomi.util.view.visible import java.util.concurrent.TimeUnit import kotlinx.android.synthetic.main.track_search_dialog.view.progress import kotlinx.android.synthetic.main.track_search_dialog.view.track_search import kotlinx.android.synthetic.main.track_search_dialog.view.track_search_list -import rx.Subscription -import rx.android.schedulers.AndroidSchedulers -import rx.subscriptions.CompositeSubscription +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import reactivecircus.flowbinding.android.widget.itemClicks +import reactivecircus.flowbinding.android.widget.textChanges import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -35,13 +38,11 @@ class TrackSearchDialog : DialogController { private val service: TrackService - private var subscriptions = CompositeSubscription() - - private var searchTextSubscription: Subscription? = null - private val trackController get() = targetController as TrackController + private val uiScope = CoroutineScope(Dispatchers.Main) + constructor(target: TrackController, service: TrackService) : super(Bundle().apply { putInt(KEY_SERVICE, service.id) }) { @@ -64,10 +65,6 @@ class TrackSearchDialog : DialogController { .onNeutral { _, _ -> onRemoveButtonClick() } .build() - if (subscriptions.isUnsubscribed) { - subscriptions = CompositeSubscription() - } - dialogView = dialog.view onViewCreated(dialog.view, savedViewState) @@ -83,9 +80,11 @@ class TrackSearchDialog : DialogController { // Set listeners selectedItem = null - subscriptions += view.track_search_list.itemClicks().subscribe { position -> - selectedItem = adapter.getItem(position) - } + view.track_search_list.itemClicks() + .onEach { position -> + selectedItem = adapter.getItem(position) + } + .launchIn(uiScope) // Do an initial search based on the manga's title if (savedState == null) { @@ -97,24 +96,18 @@ class TrackSearchDialog : DialogController { override fun onDestroyView(view: View) { super.onDestroyView(view) - subscriptions.unsubscribe() dialogView = null adapter = null } override fun onAttach(view: View) { super.onAttach(view) - searchTextSubscription = dialogView!!.track_search.textChanges() - .skip(1) - .debounce(1, TimeUnit.SECONDS, AndroidSchedulers.mainThread()) + dialogView!!.track_search.textChanges(false) + .debounce(TimeUnit.SECONDS.toMillis(1)) .map { it.toString() } - .filter(String::isNotBlank) - .subscribe { search(it) } - } - - override fun onDetach(view: View) { - super.onDetach(view) - searchTextSubscription?.unsubscribe() + .filter { it.isNotBlank() } + .onEach { search(it) } + .launchIn(uiScope) } private fun search(query: String) { 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 bc0535e041..3aec40797e 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 @@ -10,8 +10,6 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager -import com.jakewharton.rxbinding.support.v4.widget.refreshes -import com.jakewharton.rxbinding.support.v7.widget.scrollStateChanges import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.SelectableAdapter import eu.davidea.flexibleadapter.items.IFlexible @@ -29,6 +27,12 @@ import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.toast +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import reactivecircus.flowbinding.recyclerview.scrollStateChanges +import reactivecircus.flowbinding.swiperefreshlayout.refreshes import timber.log.Timber /** @@ -57,6 +61,8 @@ class UpdatesController : NucleusController(), var adapter: UpdatesAdapter? = null private set + private val uiScope = CoroutineScope(Dispatchers.Main) + private lateinit var binding: UpdatesControllerBinding init { @@ -92,19 +98,23 @@ class UpdatesController : NucleusController(), adapter = UpdatesAdapter(this@UpdatesController) binding.recycler.adapter = adapter - binding.recycler.scrollStateChanges().subscribeUntilDestroy { - // Disable swipe refresh when view is not at the top - val firstPos = layoutManager.findFirstCompletelyVisibleItemPosition() - binding.swipeRefresh.isEnabled = firstPos <= 0 - } + binding.recycler.scrollStateChanges() + .onEach { + // Disable swipe refresh when view is not at the top + val firstPos = layoutManager.findFirstCompletelyVisibleItemPosition() + binding.swipeRefresh.isEnabled = firstPos <= 0 + } + .launchIn(uiScope) binding.swipeRefresh.setDistanceToTriggerSync((2 * 64 * view.resources.displayMetrics.density).toInt()) - binding.swipeRefresh.refreshes().subscribeUntilDestroy { - updateLibrary() + binding.swipeRefresh.refreshes() + .onEach { + updateLibrary() - // It can be a very long operation, so we disable swipe refresh and show a toast. - binding.swipeRefresh.isRefreshing = false - } + // It can be a very long operation, so we disable swipe refresh and show a toast. + binding.swipeRefresh.isRefreshing = false + } + .launchIn(uiScope) } override fun onDestroyView(view: View) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceController.kt index ec6688d091..f4ea1b6cfe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceController.kt @@ -14,7 +14,6 @@ import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.changehandler.FadeChangeHandler -import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R @@ -31,6 +30,13 @@ import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController import eu.kanade.tachiyomi.ui.source.latest.LatestUpdatesController +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import reactivecircus.flowbinding.appcompat.QueryTextEvent +import reactivecircus.flowbinding.appcompat.queryTextEvents import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -54,6 +60,8 @@ class SourceController : NucleusController(), */ private var adapter: SourceAdapter? = null + private val uiScope = CoroutineScope(Dispatchers.Main) + private lateinit var binding: SourceMainControllerBinding init { @@ -192,9 +200,10 @@ class SourceController : NucleusController(), searchView.queryHint = applicationContext?.getString(R.string.action_global_search_hint) // Create query listener which opens the global search view. - searchView.queryTextChangeEvents() - .filter { it.isSubmitted } - .subscribeUntilDestroy { performGlobalSearch(it.queryText().toString()) } + searchView.queryTextEvents() + .filter { it is QueryTextEvent.QuerySubmitted } + .onEach { performGlobalSearch(it.queryText.toString()) } + .launchIn(uiScope) } fun performGlobalSearch(query: String) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/global_search/GlobalSearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/global_search/GlobalSearchController.kt index 8d562b93cc..cfd85dc2a5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/global_search/GlobalSearchController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/global_search/GlobalSearchController.kt @@ -9,7 +9,6 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.SearchView import androidx.recyclerview.widget.LinearLayoutManager -import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.databinding.GlobalSearchControllerBinding @@ -17,6 +16,13 @@ import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.manga.MangaController +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import reactivecircus.flowbinding.appcompat.QueryTextEvent +import reactivecircus.flowbinding.appcompat.queryTextEvents /** * This controller shows and manages the different search result in global search. @@ -34,6 +40,8 @@ open class GlobalSearchController( */ protected var adapter: GlobalSearchAdapter? = null + private val uiScope = CoroutineScope(Dispatchers.Main) + private lateinit var binding: GlobalSearchControllerBinding /** @@ -119,13 +127,14 @@ open class GlobalSearchController( } }) - searchView.queryTextChangeEvents() - .filter { it.isSubmitted } - .subscribeUntilDestroy { - presenter.search(it.queryText().toString()) - searchItem.collapseActionView() - setTitle() // Update toolbar title - } + searchView.queryTextEvents() + .filter { it is QueryTextEvent.QuerySubmitted } + .onEach { + presenter.search(it.queryText.toString()) + searchItem.collapseActionView() + setTitle() // Update toolbar title + } + .launchIn(uiScope) } /**