Mostly migrate rxbinding to Kotlin Flow version

This commit is contained in:
arkon 2020-04-16 23:04:00 -04:00
parent fae763dbb0
commit bdf322ceb0
12 changed files with 238 additions and 134 deletions

View File

@ -232,10 +232,15 @@ dependencies {
// RxBindings // RxBindings
final rxbindings_version = '1.0.1' 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-appcompat-v7-kotlin:$rxbindings_version"
implementation "com.jakewharton.rxbinding:rxbinding-support-v4-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 // Tests
testImplementation 'junit:junit:4.13' testImplementation 'junit:junit:4.13'

View File

@ -10,7 +10,6 @@ import androidx.appcompat.view.ActionMode
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.jakewharton.rxbinding.view.clicks
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter import eu.davidea.flexibleadapter.SelectableAdapter
import eu.davidea.flexibleadapter.helpers.UndoHelper 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.databinding.CategoriesControllerBinding
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.util.system.toast 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. * Controller to manage the categories for the users' library.
@ -47,6 +51,8 @@ class CategoryController : NucleusController<CategoryPresenter>(),
*/ */
private var undoHelper: UndoHelper? = null private var undoHelper: UndoHelper? = null
private val uiScope = CoroutineScope(Dispatchers.Main)
private lateinit var binding: CategoriesControllerBinding private lateinit var binding: CategoriesControllerBinding
/** /**
@ -87,9 +93,11 @@ class CategoryController : NucleusController<CategoryPresenter>(),
adapter?.isHandleDragEnabled = true adapter?.isHandleDragEnabled = true
adapter?.isPermanentDelete = false adapter?.isPermanentDelete = false
binding.fab.clicks().subscribeUntilDestroy { binding.fab.clicks()
.onEach {
CategoryCreateDialog(this@CategoryController).showDialog(router, null) CategoryCreateDialog(this@CategoryController).showDialog(router, null)
} }
.launchIn(uiScope)
} }
/** /**

View File

@ -12,8 +12,6 @@ import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler 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.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R 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.extension.model.Extension
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction 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.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -47,6 +52,8 @@ open class ExtensionController : NucleusController<ExtensionPresenter>(),
private var query = "" private var query = ""
private val uiScope = CoroutineScope(Dispatchers.Main)
private lateinit var binding: ExtensionControllerBinding private lateinit var binding: ExtensionControllerBinding
init { init {
@ -70,9 +77,9 @@ open class ExtensionController : NucleusController<ExtensionPresenter>(),
super.onViewCreated(view) super.onViewCreated(view)
binding.extSwipeRefresh.isRefreshing = true binding.extSwipeRefresh.isRefreshing = true
binding.extSwipeRefresh.refreshes().subscribeUntilDestroy { binding.extSwipeRefresh.refreshes()
presenter.findAvailableExtensions() .onEach { presenter.findAvailableExtensions() }
} .launchIn(uiScope)
// Initialize adapter, scroll listener and recycler views // Initialize adapter, scroll listener and recycler views
adapter = ExtensionAdapter(this) adapter = ExtensionAdapter(this)
@ -147,10 +154,11 @@ open class ExtensionController : NucleusController<ExtensionPresenter>(),
searchView.queryTextChanges() searchView.queryTextChanges()
.filter { router.backstack.lastOrNull()?.controller() == this } .filter { router.backstack.lastOrNull()?.controller() == this }
.subscribeUntilDestroy { .onEach {
query = it.toString() query = it.toString()
drawExtensions() drawExtensions()
} }
.launchIn(uiScope)
// 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(onExpand = { invalidateMenuOnExpand() }) searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() })

View File

@ -22,7 +22,6 @@ import androidx.preference.PreferenceScreen
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.jakewharton.rxbinding.view.clicks
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore
import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore 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.preference.preferenceCategory
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.view.visible 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") @SuppressLint("RestrictedApi")
class ExtensionDetailsController(bundle: Bundle? = null) : class ExtensionDetailsController(bundle: Bundle? = null) :
@ -44,6 +48,8 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
private var preferenceScreen: PreferenceScreen? = null private var preferenceScreen: PreferenceScreen? = null
private val uiScope = CoroutineScope(Dispatchers.Main)
private lateinit var binding: ExtensionDetailControllerBinding private lateinit var binding: ExtensionDetailControllerBinding
constructor(pkgName: String) : this(Bundle().apply { 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.extensionLang.text = context.getString(R.string.ext_language_info, LocaleHelper.getDisplayName(extension.lang, context))
binding.extensionPkg.text = extension.pkgName binding.extensionPkg.text = extension.pkgName
extension.getApplicationIcon(context)?.let { binding.extensionIcon.setImageDrawable(it) } extension.getApplicationIcon(context)?.let { binding.extensionIcon.setImageDrawable(it) }
binding.extensionUninstallButton.clicks().subscribeUntilDestroy { binding.extensionUninstallButton.clicks()
presenter.uninstallExtension() .onEach { presenter.uninstallExtension() }
} .launchIn(uiScope)
if (extension.isObsolete) { if (extension.isObsolete) {
binding.extensionObsolete.visible() binding.extensionObsolete.visible()

View File

@ -20,7 +20,6 @@ import com.bluelinelabs.conductor.ControllerChangeType
import com.f2prateek.rx.preferences.Preference import com.f2prateek.rx.preferences.Preference
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.jakewharton.rxbinding.support.v4.view.pageSelections 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.BehaviorRelay
import com.jakewharton.rxrelay.PublishRelay import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -40,6 +39,12 @@ import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import java.io.IOException import java.io.IOException
import kotlinx.android.synthetic.main.main_activity.tabs 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 rx.Subscription
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -123,7 +128,7 @@ class LibraryController(
private var tabsVisibilitySubscription: Subscription? = null private var tabsVisibilitySubscription: Subscription? = null
private var searchViewSubscription: Subscription? = null private val uiScope = CoroutineScope(Dispatchers.Main)
private lateinit var binding: LibraryControllerBinding 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. // Mutate the filter icon because it needs to be tinted and the resource is shared.
menu.findItem(R.id.action_filter).icon.mutate() menu.findItem(R.id.action_filter).icon.mutate()
searchViewSubscription?.unsubscribe() searchView.queryTextChanges()
searchViewSubscription = searchView.queryTextChanges()
// Ignore events if this controller isn't at the top // Ignore events if this controller isn't at the top
.filter { router.backstack.lastOrNull()?.controller() == this } .filter { router.backstack.lastOrNull()?.controller() == this }
.subscribeUntilDestroy { .onEach {
query = it.toString() query = it.toString()
searchRelay.call(query) searchRelay.call(query)
} }
.launchIn(uiScope)
searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() }) searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() })
} }

View File

@ -16,8 +16,6 @@ import androidx.core.graphics.drawable.DrawableCompat
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar 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.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter import eu.davidea.flexibleadapter.SelectableAdapter
import eu.kanade.tachiyomi.R 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.shrinkOnScroll
import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.visible 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 import timber.log.Timber
class ChaptersController : NucleusController<ChaptersPresenter>(), class ChaptersController : NucleusController<ChaptersPresenter>(),
@ -62,6 +66,8 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
private var lastClickPosition = -1 private var lastClickPosition = -1
private val uiScope = CoroutineScope(Dispatchers.Main)
private lateinit var binding: ChaptersControllerBinding private lateinit var binding: ChaptersControllerBinding
init { init {
@ -91,9 +97,12 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
binding.recycler.setHasFixedSize(true) binding.recycler.setHasFixedSize(true)
adapter?.fastScroller = binding.fastScroller adapter?.fastScroller = binding.fastScroller
binding.swipeRefresh.refreshes().subscribeUntilDestroy { fetchChaptersFromSource() } binding.swipeRefresh.refreshes()
.onEach { fetchChaptersFromSource() }
.launchIn(uiScope)
binding.fab.clicks().subscribeUntilDestroy { binding.fab.clicks()
.onEach {
val item = presenter.getNextUnreadChapter() val item = presenter.getNextUnreadChapter()
if (item != null) { if (item != null) {
// Create animation listener // Create animation listener
@ -112,6 +121,8 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
view.context.toast(R.string.no_next_chapter) view.context.toast(R.string.no_next_chapter)
} }
} }
.launchIn(uiScope)
binding.fab.shrinkOnScroll(binding.recycler) binding.fab.shrinkOnScroll(binding.recycler)
} }

View File

@ -27,9 +27,6 @@ import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.transition.Transition
import com.google.android.material.chip.Chip 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.R
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga 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.snack
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
import jp.wasabeef.glide.transformations.CropSquareTransformation 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.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -70,6 +74,8 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
private val preferences: PreferencesHelper by injectLazy() private val preferences: PreferencesHelper by injectLazy()
private val uiScope = CoroutineScope(Dispatchers.Main)
private lateinit var binding: MangaInfoControllerBinding private lateinit var binding: MangaInfoControllerBinding
init { init {
@ -91,53 +97,79 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
super.onViewCreated(view) super.onViewCreated(view)
// Set onclickListener to toggle favorite when favorite button clicked. // 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. // 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) { if (presenter.source is HttpSource) {
binding.btnWebview.visible() binding.btnWebview.visible()
binding.btnShare.visible() binding.btnShare.visible()
binding.btnWebview.clicks().subscribeUntilDestroy { openInWebView() } binding.btnWebview.clicks()
binding.btnShare.clicks().subscribeUntilDestroy { shareManga() } .onEach { openInWebView() }
.launchIn(uiScope)
binding.btnShare.clicks()
.onEach { shareManga() }
.launchIn(uiScope)
} }
// Set SwipeRefresh to refresh manga data. // Set SwipeRefresh to refresh manga data.
binding.swipeRefresh.refreshes().subscribeUntilDestroy { fetchMangaFromSource() } binding.swipeRefresh.refreshes()
.onEach { fetchMangaFromSource() }
.launchIn(uiScope)
binding.mangaFullTitle.longClicks().subscribeUntilDestroy { binding.mangaFullTitle.longClicks()
.onEach {
copyToClipboard(view.context.getString(R.string.title), binding.mangaFullTitle.text.toString()) copyToClipboard(view.context.getString(R.string.title), binding.mangaFullTitle.text.toString())
} }
.launchIn(uiScope)
binding.mangaFullTitle.clicks().subscribeUntilDestroy { binding.mangaFullTitle.clicks()
.onEach {
performGlobalSearch(binding.mangaFullTitle.text.toString()) performGlobalSearch(binding.mangaFullTitle.text.toString())
} }
.launchIn(uiScope)
binding.mangaArtist.longClicks().subscribeUntilDestroy { binding.mangaArtist.longClicks()
.onEach {
copyToClipboard(binding.mangaArtistLabel.text.toString(), binding.mangaArtist.text.toString()) copyToClipboard(binding.mangaArtistLabel.text.toString(), binding.mangaArtist.text.toString())
} }
.launchIn(uiScope)
binding.mangaArtist.clicks().subscribeUntilDestroy { binding.mangaArtist.clicks()
.onEach {
performGlobalSearch(binding.mangaArtist.text.toString()) performGlobalSearch(binding.mangaArtist.text.toString())
} }
.launchIn(uiScope)
binding.mangaAuthor.longClicks().subscribeUntilDestroy { binding.mangaAuthor.longClicks()
.onEach {
copyToClipboard(binding.mangaAuthor.text.toString(), binding.mangaAuthor.text.toString()) copyToClipboard(binding.mangaAuthor.text.toString(), binding.mangaAuthor.text.toString())
} }
.launchIn(uiScope)
binding.mangaAuthor.clicks().subscribeUntilDestroy { binding.mangaAuthor.clicks()
.onEach {
performGlobalSearch(binding.mangaAuthor.text.toString()) performGlobalSearch(binding.mangaAuthor.text.toString())
} }
.launchIn(uiScope)
binding.mangaSummary.longClicks().subscribeUntilDestroy { binding.mangaSummary.longClicks()
.onEach {
copyToClipboard(view.context.getString(R.string.description), binding.mangaSummary.text.toString()) copyToClipboard(view.context.getString(R.string.description), binding.mangaSummary.text.toString())
} }
.launchIn(uiScope)
binding.mangaCover.longClicks().subscribeUntilDestroy { binding.mangaCover.longClicks()
.onEach {
copyToClipboard(view.context.getString(R.string.title), presenter.manga.title) copyToClipboard(view.context.getString(R.string.title), presenter.manga.title)
} }
.launchIn(uiScope)
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {

View File

@ -6,13 +6,17 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.jakewharton.rxbinding.support.v4.widget.refreshes
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.databinding.TrackControllerBinding import eu.kanade.tachiyomi.databinding.TrackControllerBinding
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.system.toast 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 import timber.log.Timber
class TrackController : NucleusController<TrackPresenter>(), class TrackController : NucleusController<TrackPresenter>(),
@ -23,6 +27,8 @@ class TrackController : NucleusController<TrackPresenter>(),
private var adapter: TrackAdapter? = null private var adapter: TrackAdapter? = null
private val uiScope = CoroutineScope(Dispatchers.Main)
private lateinit var binding: TrackControllerBinding private lateinit var binding: TrackControllerBinding
init { init {
@ -47,7 +53,9 @@ class TrackController : NucleusController<TrackPresenter>(),
binding.trackRecycler.layoutManager = LinearLayoutManager(view.context) binding.trackRecycler.layoutManager = LinearLayoutManager(view.context)
binding.trackRecycler.adapter = adapter binding.trackRecycler.adapter = adapter
binding.swipeRefresh.isEnabled = false binding.swipeRefresh.isEnabled = false
binding.swipeRefresh.refreshes().subscribeUntilDestroy { presenter.refresh() } binding.swipeRefresh.refreshes()
.onEach { presenter.refresh() }
.launchIn(uiScope)
} }
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {

View File

@ -4,24 +4,27 @@ import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import com.afollestad.materialdialogs.MaterialDialog 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.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.ui.base.controller.DialogController 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.invisible
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlinx.android.synthetic.main.track_search_dialog.view.progress 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
import kotlinx.android.synthetic.main.track_search_dialog.view.track_search_list import kotlinx.android.synthetic.main.track_search_dialog.view.track_search_list
import rx.Subscription import kotlinx.coroutines.CoroutineScope
import rx.android.schedulers.AndroidSchedulers import kotlinx.coroutines.Dispatchers
import rx.subscriptions.CompositeSubscription 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.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -35,13 +38,11 @@ class TrackSearchDialog : DialogController {
private val service: TrackService private val service: TrackService
private var subscriptions = CompositeSubscription()
private var searchTextSubscription: Subscription? = null
private val trackController private val trackController
get() = targetController as TrackController get() = targetController as TrackController
private val uiScope = CoroutineScope(Dispatchers.Main)
constructor(target: TrackController, service: TrackService) : super(Bundle().apply { constructor(target: TrackController, service: TrackService) : super(Bundle().apply {
putInt(KEY_SERVICE, service.id) putInt(KEY_SERVICE, service.id)
}) { }) {
@ -64,10 +65,6 @@ class TrackSearchDialog : DialogController {
.onNeutral { _, _ -> onRemoveButtonClick() } .onNeutral { _, _ -> onRemoveButtonClick() }
.build() .build()
if (subscriptions.isUnsubscribed) {
subscriptions = CompositeSubscription()
}
dialogView = dialog.view dialogView = dialog.view
onViewCreated(dialog.view, savedViewState) onViewCreated(dialog.view, savedViewState)
@ -83,9 +80,11 @@ class TrackSearchDialog : DialogController {
// Set listeners // Set listeners
selectedItem = null selectedItem = null
subscriptions += view.track_search_list.itemClicks().subscribe { position -> view.track_search_list.itemClicks()
.onEach { position ->
selectedItem = adapter.getItem(position) selectedItem = adapter.getItem(position)
} }
.launchIn(uiScope)
// 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) {
@ -97,24 +96,18 @@ class TrackSearchDialog : DialogController {
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {
super.onDestroyView(view) super.onDestroyView(view)
subscriptions.unsubscribe()
dialogView = null dialogView = null
adapter = null adapter = null
} }
override fun onAttach(view: View) { override fun onAttach(view: View) {
super.onAttach(view) super.onAttach(view)
searchTextSubscription = dialogView!!.track_search.textChanges() dialogView!!.track_search.textChanges(false)
.skip(1) .debounce(TimeUnit.SECONDS.toMillis(1))
.debounce(1, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
.map { it.toString() } .map { it.toString() }
.filter(String::isNotBlank) .filter { it.isNotBlank() }
.subscribe { search(it) } .onEach { search(it) }
} .launchIn(uiScope)
override fun onDetach(view: View) {
super.onDetach(view)
searchTextSubscription?.unsubscribe()
} }
private fun search(query: String) { private fun search(query: String) {

View File

@ -10,8 +10,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager 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.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter import eu.davidea.flexibleadapter.SelectableAdapter
import eu.davidea.flexibleadapter.items.IFlexible 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.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.toast 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 import timber.log.Timber
/** /**
@ -57,6 +61,8 @@ class UpdatesController : NucleusController<UpdatesPresenter>(),
var adapter: UpdatesAdapter? = null var adapter: UpdatesAdapter? = null
private set private set
private val uiScope = CoroutineScope(Dispatchers.Main)
private lateinit var binding: UpdatesControllerBinding private lateinit var binding: UpdatesControllerBinding
init { init {
@ -92,19 +98,23 @@ class UpdatesController : NucleusController<UpdatesPresenter>(),
adapter = UpdatesAdapter(this@UpdatesController) adapter = UpdatesAdapter(this@UpdatesController)
binding.recycler.adapter = adapter binding.recycler.adapter = adapter
binding.recycler.scrollStateChanges().subscribeUntilDestroy { binding.recycler.scrollStateChanges()
.onEach {
// Disable swipe refresh when view is not at the top // Disable swipe refresh when view is not at the top
val firstPos = layoutManager.findFirstCompletelyVisibleItemPosition() val firstPos = layoutManager.findFirstCompletelyVisibleItemPosition()
binding.swipeRefresh.isEnabled = firstPos <= 0 binding.swipeRefresh.isEnabled = firstPos <= 0
} }
.launchIn(uiScope)
binding.swipeRefresh.setDistanceToTriggerSync((2 * 64 * view.resources.displayMetrics.density).toInt()) binding.swipeRefresh.setDistanceToTriggerSync((2 * 64 * view.resources.displayMetrics.density).toInt())
binding.swipeRefresh.refreshes().subscribeUntilDestroy { binding.swipeRefresh.refreshes()
.onEach {
updateLibrary() updateLibrary()
// 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(uiScope)
} }
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {

View File

@ -14,7 +14,6 @@ import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R 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.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController
import eu.kanade.tachiyomi.ui.source.latest.LatestUpdatesController 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.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -54,6 +60,8 @@ class SourceController : NucleusController<SourcePresenter>(),
*/ */
private var adapter: SourceAdapter? = null private var adapter: SourceAdapter? = null
private val uiScope = CoroutineScope(Dispatchers.Main)
private lateinit var binding: SourceMainControllerBinding private lateinit var binding: SourceMainControllerBinding
init { init {
@ -192,9 +200,10 @@ class SourceController : NucleusController<SourcePresenter>(),
searchView.queryHint = applicationContext?.getString(R.string.action_global_search_hint) searchView.queryHint = applicationContext?.getString(R.string.action_global_search_hint)
// Create query listener which opens the global search view. // Create query listener which opens the global search view.
searchView.queryTextChangeEvents() searchView.queryTextEvents()
.filter { it.isSubmitted } .filter { it is QueryTextEvent.QuerySubmitted }
.subscribeUntilDestroy { performGlobalSearch(it.queryText().toString()) } .onEach { performGlobalSearch(it.queryText.toString()) }
.launchIn(uiScope)
} }
fun performGlobalSearch(query: String) { fun performGlobalSearch(query: String) {

View File

@ -9,7 +9,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.GlobalSearchControllerBinding 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.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController 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. * This controller shows and manages the different search result in global search.
@ -34,6 +40,8 @@ open class GlobalSearchController(
*/ */
protected var adapter: GlobalSearchAdapter? = null protected var adapter: GlobalSearchAdapter? = null
private val uiScope = CoroutineScope(Dispatchers.Main)
private lateinit var binding: GlobalSearchControllerBinding private lateinit var binding: GlobalSearchControllerBinding
/** /**
@ -119,13 +127,14 @@ open class GlobalSearchController(
} }
}) })
searchView.queryTextChangeEvents() searchView.queryTextEvents()
.filter { it.isSubmitted } .filter { it is QueryTextEvent.QuerySubmitted }
.subscribeUntilDestroy { .onEach {
presenter.search(it.queryText().toString()) presenter.search(it.queryText.toString())
searchItem.collapseActionView() searchItem.collapseActionView()
setTitle() // Update toolbar title setTitle() // Update toolbar title
} }
.launchIn(uiScope)
} }
/** /**