> {
return Observable.error(getSourceNotInstalledException())
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt
index 60cf09466e08..683aadd28800 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt
@@ -97,7 +97,7 @@ abstract class HttpSource : CatalogueSource {
}
fun getExtension(extensionManager: ExtensionManager? = null): Extension.Installed? =
- (extensionManager ?: Injekt.get()).installedExtensions.find { it.sources.contains(this) }
+ (extensionManager ?: Injekt.get()).installedExtensionsFlow.value.find { it.sources.contains(this) }
fun extOnlyHasAllLanguage(extensionManager: ExtensionManager? = null) =
getExtension(extensionManager)?.sources?.all { it.lang == "all" } ?: true
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt
deleted file mode 100644
index 8150c2e8dd56..000000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package eu.kanade.tachiyomi.ui.base.activity
-
-import android.content.res.Resources
-import android.os.Bundle
-import androidx.lifecycle.lifecycleScope
-import eu.kanade.tachiyomi.data.preference.PreferencesHelper
-import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
-import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
-import eu.kanade.tachiyomi.util.system.getThemeWithExtras
-import eu.kanade.tachiyomi.util.system.setLocaleByAppCompat
-import eu.kanade.tachiyomi.util.system.setThemeByPref
-import nucleus.view.NucleusAppCompatActivity
-import uy.kohesive.injekt.injectLazy
-
-abstract class BaseRxActivity> : NucleusAppCompatActivity
() {
-
- val scope = lifecycleScope
- private val preferences by injectLazy()
- private var updatedTheme: Resources.Theme? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- setLocaleByAppCompat()
- updatedTheme = null
- setThemeByPref(preferences)
- super.onCreate(savedInstanceState)
- SecureActivityDelegate.setSecure(this)
- }
-
- override fun onResume() {
- super.onResume()
- SecureActivityDelegate.promptLockIfNeeded(this)
- }
-
- override fun getTheme(): Resources.Theme {
- val newTheme = getThemeWithExtras(super.getTheme(), preferences, updatedTheme)
- updatedTheme = newTheme
- return newTheme
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseCoroutineController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseCoroutineController.kt
index 3069d89f4b46..587e4e60365a 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseCoroutineController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseCoroutineController.kt
@@ -18,8 +18,8 @@ abstract class BaseCoroutineController BaseCoroutinePresenter.takeView(view: Any) = attachView(view as? View)
- override fun onDestroyView(view: View) {
- super.onDestroyView(view)
+ override fun onDestroy() {
+ super.onDestroy()
presenter.onDestroy()
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/NucleusController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/NucleusController.kt
deleted file mode 100644
index 0786d5e137c2..000000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/NucleusController.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package eu.kanade.tachiyomi.ui.base.controller
-
-import android.os.Bundle
-import androidx.viewbinding.ViewBinding
-import eu.kanade.tachiyomi.ui.base.presenter.NucleusConductorDelegate
-import eu.kanade.tachiyomi.ui.base.presenter.NucleusConductorLifecycleListener
-import nucleus.factory.PresenterFactory
-import nucleus.presenter.Presenter
-
-@Suppress("LeakingThis")
-abstract class NucleusController>(val bundle: Bundle? = null) :
- RxController(bundle),
- PresenterFactory {
-
- private val delegate = NucleusConductorDelegate(this)
-
- val presenter: P
- get() = delegate.presenter!!
-
- init {
- addLifecycleListener(NucleusConductorLifecycleListener(delegate))
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/BasePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/BasePresenter.kt
deleted file mode 100644
index 981794f9345e..000000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/BasePresenter.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-package eu.kanade.tachiyomi.ui.base.presenter
-
-import android.os.Bundle
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.MainScope
-import kotlinx.coroutines.cancel
-import nucleus.presenter.RxPresenter
-import nucleus.presenter.delivery.Delivery
-import rx.Observable
-
-open class BasePresenter : RxPresenter() {
-
- lateinit var presenterScope: CoroutineScope
-
- /**
- * Query from the view where applicable
- */
- var query: String = ""
- protected set
-
- override fun onCreate(savedState: Bundle?) {
- try {
- super.onCreate(savedState)
- presenterScope = MainScope()
- } catch (e: NullPointerException) {
- // 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.
- }
- }
-
- override fun onDestroy() {
- super.onDestroy()
- presenterScope.cancel()
- }
-
- /**
- * Subscribes an observable with [deliverFirst] and adds it to the presenter's lifecycle
- * subscription list.
- *
- * @param onNext function to execute when the observable emits an item.
- * @param onError function to execute when the observable throws an error.
- */
- fun Observable.subscribeFirst(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) =
- compose(deliverFirst()).subscribe(split(onNext, onError)).apply { add(this) }
-
- /**
- * Subscribes an observable with [deliverLatestCache] and adds it to the presenter's lifecycle
- * subscription list.
- *
- * @param onNext function to execute when the observable emits an item.
- * @param onError function to execute when the observable throws an error.
- */
- fun Observable.subscribeLatestCache(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) =
- compose(deliverLatestCache()).subscribe(split(onNext, onError)).apply { add(this) }
-
- /**
- * Subscribes an observable with [deliverReplay] and adds it to the presenter's lifecycle
- * subscription list.
- *
- * @param onNext function to execute when the observable emits an item.
- * @param onError function to execute when the observable throws an error.
- */
- fun Observable.subscribeReplay(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) =
- compose(deliverReplay()).subscribe(split(onNext, onError)).apply { add(this) }
-
- /**
- * Subscribes an observable with [DeliverWithView] and adds it to the presenter's lifecycle
- * subscription list.
- *
- * @param onNext function to execute when the observable emits an item.
- * @param onError function to execute when the observable throws an error.
- */
- fun Observable.subscribeWithView(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) =
- compose(DeliverWithView(view())).subscribe(split(onNext, onError)).apply { add(this) }
-
- /**
- * A deliverable that only emits to the view if attached, otherwise the event is ignored.
- */
- class DeliverWithView(private val view: Observable) : Observable.Transformer> {
-
- override fun call(observable: Observable): Observable> {
- return observable
- .materialize()
- .filter { notification -> !notification.isOnCompleted }
- .flatMap { notification ->
- view.take(1).filter { it != null }.map { Delivery(it, notification) }
- }
- }
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorDelegate.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorDelegate.kt
deleted file mode 100644
index cd07ed478ae4..000000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorDelegate.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package eu.kanade.tachiyomi.ui.base.presenter
-
-import android.os.Bundle
-import nucleus.factory.PresenterFactory
-import nucleus.presenter.Presenter
-
-class NucleusConductorDelegate>(private val factory: PresenterFactory
) {
-
- var presenter: P? = null
- get() {
- if (field == null) {
- field = factory.createPresenter()
- field!!.create(bundle)
- bundle = null
- }
- return field
- }
-
- private var bundle: Bundle? = null
-
- fun onSaveInstanceState(): Bundle {
- val bundle = Bundle()
- // getPresenter(); // Workaround a crash related to saving instance state with child routers
- presenter?.save(bundle)
- return bundle
- }
-
- fun onRestoreInstanceState(presenterState: Bundle?) {
- bundle = presenterState
- }
-
- @Suppress("UNCHECKED_CAST")
- private fun Presenter.takeView(view: Any) = takeView(view as View)
-
- fun onTakeView(view: Any) {
- presenter?.takeView(view)
- }
-
- fun onDropView() {
- presenter?.dropView()
- }
-
- fun onDestroy() {
- presenter?.destroy()
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorLifecycleListener.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorLifecycleListener.kt
deleted file mode 100644
index f59febccfaaa..000000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorLifecycleListener.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package eu.kanade.tachiyomi.ui.base.presenter
-
-import android.os.Bundle
-import android.view.View
-import com.bluelinelabs.conductor.Controller
-
-class NucleusConductorLifecycleListener(private val delegate: NucleusConductorDelegate<*>) : Controller.LifecycleListener() {
-
- override fun postCreateView(controller: Controller, view: View) {
- delegate.onTakeView(controller)
- }
-
- override fun preDestroyView(controller: Controller, view: View) {
- delegate.onDropView()
- }
-
- override fun preDestroy(controller: Controller) {
- delegate.onDestroy()
- }
-
- override fun onSaveInstanceState(controller: Controller, outState: Bundle) {
- outState.putBundle(PRESENTER_STATE_KEY, delegate.onSaveInstanceState())
- }
-
- override fun onRestoreInstanceState(controller: Controller, savedInstanceState: Bundle) {
- delegate.onRestoreInstanceState(savedInstanceState.getBundle(PRESENTER_STATE_KEY))
- }
-
- companion object {
- private const val PRESENTER_STATE_KEY = "presenter_state"
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt
index 43d6bb3afb55..42f72b31d268 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt
@@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.util.system.withUIContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch
@@ -39,12 +40,12 @@ class ExtensionBottomPresenter() : BaseMigrationPresenter(
super.onCreate()
presenterScope.launch {
val extensionJob = async {
- extensionManager.findAvailableExtensionsAsync()
+ extensionManager.findAvailableExtensions()
extensions = toItems(
Triple(
- extensionManager.installedExtensions,
- extensionManager.untrustedExtensions,
- extensionManager.availableExtensions,
+ extensionManager.installedExtensionsFlow.value,
+ extensionManager.untrustedExtensionsFlow.value,
+ extensionManager.availableExtensionsFlow.value,
),
)
withContext(Dispatchers.Main) { controller?.setExtensions(extensions, false) }
@@ -53,16 +54,16 @@ class ExtensionBottomPresenter() : BaseMigrationPresenter(
listOf(migrationJob, extensionJob).awaitAll()
}
presenterScope.launch {
- extensionManager.downloadRelay
+ extensionManager.downloadRelay.asSharedFlow()
.collect {
if (it.first.startsWith("Finished")) {
firstLoad = true
currentDownloads.clear()
extensions = toItems(
Triple(
- extensionManager.installedExtensions,
- extensionManager.untrustedExtensions,
- extensionManager.availableExtensions,
+ extensionManager.installedExtensionsFlow.value,
+ extensionManager.untrustedExtensionsFlow.value,
+ extensionManager.availableExtensionsFlow.value,
),
)
withUIContext { controller?.setExtensions(extensions) }
@@ -91,9 +92,9 @@ class ExtensionBottomPresenter() : BaseMigrationPresenter(
presenterScope.launch {
extensions = toItems(
Triple(
- extensionManager.installedExtensions,
- extensionManager.untrustedExtensions,
- extensionManager.availableExtensions,
+ extensionManager.installedExtensionsFlow.value,
+ extensionManager.untrustedExtensionsFlow.value,
+ extensionManager.availableExtensionsFlow.value,
),
)
withContext(Dispatchers.Main) { controller?.setExtensions(extensions, false) }
@@ -249,7 +250,7 @@ class ExtensionBottomPresenter() : BaseMigrationPresenter(
fun updateExtension(extension: Extension.Installed) {
val availableExt =
- extensionManager.availableExtensions.find { it.pkgName == extension.pkgName } ?: return
+ extensionManager.availableExtensionsFlow.value.find { it.pkgName == extension.pkgName } ?: return
installExtension(availableExt)
}
@@ -265,7 +266,7 @@ class ExtensionBottomPresenter() : BaseMigrationPresenter(
val intent = ExtensionInstallService.jobIntent(
context,
extensions.mapNotNull { extension ->
- extensionManager.availableExtensions.find { it.pkgName == extension.pkgName }
+ extensionManager.availableExtensionsFlow.value.find { it.pkgName == extension.pkgName }
},
)
ContextCompat.startForegroundService(context, intent)
@@ -276,7 +277,9 @@ class ExtensionBottomPresenter() : BaseMigrationPresenter(
}
fun findAvailableExtensions() {
- extensionManager.findAvailableExtensions()
+ presenterScope.launch {
+ extensionManager.findAvailableExtensions()
+ }
}
fun trustSignature(signatureHash: String) {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionFilterController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionFilterController.kt
index 070dd9a22faa..96e81dab4df2 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionFilterController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionFilterController.kt
@@ -22,7 +22,7 @@ class ExtensionFilterController : SettingsController() {
val activeLangs = preferences.enabledLanguages().get()
- val availableLangs = extensionManager.availableExtensions.groupBy { it.lang }.keys
+ val availableLangs = extensionManager.availableExtensionsFlow.value.groupBy { it.lang }.keys
.sortedWith(compareBy({ it !in activeLangs }, { LocaleHelper.getSourceDisplayName(it, context) }))
availableLangs.forEach {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsController.kt
index 1eea2ecc2983..d84a966945a5 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsController.kt
@@ -36,7 +36,7 @@ import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.getPreferenceKey
import eu.kanade.tachiyomi.source.online.HttpSource
-import eu.kanade.tachiyomi.ui.base.controller.NucleusController
+import eu.kanade.tachiyomi.ui.base.controller.BaseCoroutineController
import eu.kanade.tachiyomi.ui.setting.DSL
import eu.kanade.tachiyomi.ui.setting.onChange
import eu.kanade.tachiyomi.ui.setting.switchPreference
@@ -57,7 +57,7 @@ import uy.kohesive.injekt.injectLazy
@SuppressLint("RestrictedApi")
class ExtensionDetailsController(bundle: Bundle? = null) :
- NucleusController(bundle),
+ BaseCoroutineController(bundle),
PreferenceManager.OnDisplayPreferenceDialogListener,
DialogPreference.TargetFragment {
@@ -81,9 +81,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
override fun createBinding(inflater: LayoutInflater) =
ExtensionDetailControllerBinding.inflate(inflater.cloneInContext(getPreferenceThemeContext()))
- override fun createPresenter(): ExtensionDetailsPresenter {
- return ExtensionDetailsPresenter(args.getString(PKGNAME_KEY)!!)
- }
+ override val presenter = ExtensionDetailsPresenter(args.getString(PKGNAME_KEY)!!)
override fun getTitle(): String? {
return resources?.getString(R.string.extension_info)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsPresenter.kt
index 3f17753219f6..42d42d2540e6 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsPresenter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsPresenter.kt
@@ -1,35 +1,34 @@
package eu.kanade.tachiyomi.ui.extension.details
-import android.os.Bundle
import eu.kanade.tachiyomi.extension.ExtensionManager
-import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
-import rx.android.schedulers.AndroidSchedulers
+import eu.kanade.tachiyomi.ui.base.presenter.BaseCoroutinePresenter
+import eu.kanade.tachiyomi.util.system.launchUI
+import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class ExtensionDetailsPresenter(
val pkgName: String,
private val extensionManager: ExtensionManager = Injekt.get(),
-) : BasePresenter() {
+) : BaseCoroutinePresenter() {
- val extension = extensionManager.installedExtensions.find { it.pkgName == pkgName }
-
- override fun onCreate(savedState: Bundle?) {
- super.onCreate(savedState)
+ val extension = extensionManager.installedExtensionsFlow.value.find { it.pkgName == pkgName }
+ override fun onCreate() {
+ super.onCreate()
bindToUninstalledExtension()
}
private fun bindToUninstalledExtension() {
- extensionManager.getInstalledExtensionsObservable()
- .skip(1)
- .filter { extensions -> extensions.none { it.pkgName == pkgName } }
- .map { Unit }
- .take(1)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribeFirst({ view, _ ->
- view.onExtensionUninstalled()
- },)
+ extensionManager.installedExtensionsFlow
+ .drop(1)
+ .onEach { extensions ->
+ extensions.filter { it.pkgName == pkgName }
+ presenterScope.launchUI { controller?.onExtensionUninstalled() }
+ }
+ .launchIn(presenterScope)
}
fun uninstallExtension() {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
index 06d252a50703..a5a3f81db394 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
@@ -821,15 +821,15 @@ open class MainActivity : BaseActivity(), DownloadServiceLi
}
fun getExtensionUpdates(force: Boolean) {
- if ((force && extensionManager.availableExtensions.isEmpty()) ||
+ if ((force && extensionManager.availableExtensionsFlow.value.isEmpty()) ||
Date().time >= preferences.lastExtCheck().get() + TimeUnit.HOURS.toMillis(6)
) {
lifecycleScope.launch(Dispatchers.IO) {
try {
- extensionManager.findAvailableExtensionsAsync()
+ extensionManager.findAvailableExtensions()
val pendingUpdates = ExtensionGithubApi().checkForUpdates(
this@MainActivity,
- extensionManager.availableExtensions.takeIf { it.isNotEmpty() },
+ extensionManager.availableExtensionsFlow.value.takeIf { it.isNotEmpty() },
)
preferences.extensionUpdatesCount().set(pendingUpdates.size)
preferences.lastExtCheck().set(Date().time)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt
index 1d6da7551c42..7e458931becd 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt
@@ -110,7 +110,7 @@ class EditMangaDialog : DialogController {
languages.add("")
languages.addAll(
- extensionManager.availableExtensions.groupBy { it.lang }.keys
+ extensionManager.availableExtensionsFlow.value.groupBy { it.lang }.keys
.sortedWith(
compareBy(
{ it !in activeLangs },
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/BaseMigrationPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/BaseMigrationPresenter.kt
index d185471094c1..1aa14dfb493b 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/BaseMigrationPresenter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/BaseMigrationPresenter.kt
@@ -54,7 +54,7 @@ abstract class BaseMigrationPresenter(
val header = SelectionHeader()
val sourceGroup = library.groupBy { it.source }
val sortOrder = PreferenceValues.MigrationSourceOrder.fromPreference(preferences)
- val extensions = extensionManager.installedExtensions
+ val extensions = extensionManager.installedExtensionsFlow.value
val obsoleteSources =
extensions.filter { it.isObsolete }.map { it.sources }.flatten().map { it.id }
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt
index c2180a86f86a..2390bcf85d42 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt
@@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.databinding.MigrationControllerBinding
import eu.kanade.tachiyomi.ui.base.controller.BaseCoroutineController
import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController
import eu.kanade.tachiyomi.ui.source.BrowseController
-import eu.kanade.tachiyomi.util.system.await
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.view.activityBinding
@@ -24,7 +23,6 @@ import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.widget.LinearLayoutManagerAccurateOffset
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
-import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -94,9 +92,7 @@ class MigrationController :
val item = adapter?.getItem(position) as? SourceItem ?: return
launchUI {
- val manga = Injekt.get().getFavoriteMangas().asRxSingle().await(
- Schedulers.io(),
- )
+ val manga = Injekt.get().getFavoriteMangas().executeAsBlocking()
val sourceMangas =
manga.asSequence().filter { it.source == item.source.id }.map { it.id!! }.toList()
withContext(Dispatchers.Main) {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt
index 2a2f03629f64..27dbe21972b5 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt
@@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.ui.main.BottomNavBarInterface
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchCardAdapter
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController
-import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchPresenter
import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import uy.kohesive.injekt.Injekt
@@ -51,9 +50,7 @@ class SearchController(
bundle.getLongArray(SOURCES) ?: LongArray(0),
)
- override fun createPresenter(): GlobalSearchPresenter {
- return SearchPresenter(initialQuery, manga!!, sources = sources)
- }
+ override val presenter = SearchPresenter(initialQuery, manga!!, sources = sources)
override fun onMangaClick(manga: Manga) {
if (targetController is MigrationListController) {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt
index 7736fecbc0a7..4d048299e3e3 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt
@@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.ui.setting
import android.app.Activity
-import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Environment
@@ -15,7 +14,6 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.asImmediateFlowIn
-import eu.kanade.tachiyomi.util.system.getFilePicker
import eu.kanade.tachiyomi.util.system.withOriginalWidth
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -167,13 +165,8 @@ class SettingsDownloadController : SettingsController() {
preferences.downloadsDirectory().set(path.toString())
}
- fun customDirectorySelected(currentDir: String) {
- val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
- try {
- startActivityForResult(intent, DOWNLOAD_DIR)
- } catch (e: ActivityNotFoundException) {
- startActivityForResult(preferences.context.getFilePicker(currentDir), DOWNLOAD_DIR)
- }
+ fun customDirectorySelected() {
+ startActivityForResult(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), DOWNLOAD_DIR)
}
class DownloadDirectoriesDialog(val controller: SettingsDownloadController) :
@@ -193,7 +186,7 @@ class SettingsDownloadController : SettingsController() {
setTitle(R.string.download_location)
setSingleChoiceItems(items.toTypedArray(), selectedIndex) { dialog, position ->
if (position == externalDirs.lastIndex) {
- controller.customDirectorySelected(currentDir)
+ controller.customDirectorySelected()
} else {
controller.predefinedDirectorySelected(items[position])
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt
index 5aa03ab05222..e1e1d1888de2 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt
@@ -93,10 +93,7 @@ class SettingsMainController : SettingsController(), FloatingSearchInterface {
}
override fun onActionViewExpand(item: MenuItem?) {
- SettingsSearchController.lastSearch = "" // reset saved search query
- router.pushController(
- RouterTransaction.with(SettingsSearchController()),
- )
+ router.pushController(RouterTransaction.with(SettingsSearchController()))
}
private fun navigateTo(controller: Controller) {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchController.kt
index 6485379a8182..35212cd50f64 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchController.kt
@@ -11,19 +11,20 @@ import androidx.recyclerview.widget.LinearLayoutManager
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.SettingsSearchControllerBinding
import eu.kanade.tachiyomi.ui.base.SmallToolbarInterface
-import eu.kanade.tachiyomi.ui.base.controller.NucleusController
+import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface
import eu.kanade.tachiyomi.ui.setting.SettingsController
import eu.kanade.tachiyomi.util.view.activityBinding
import eu.kanade.tachiyomi.util.view.liftAppbarWith
import eu.kanade.tachiyomi.util.view.withFadeTransaction
+import uy.kohesive.injekt.api.get
/**
* This controller shows and manages the different search result in settings search.
* [SettingsSearchAdapter.OnTitleClickListener] called when preference is clicked in settings search
*/
class SettingsSearchController :
- NucleusController(),
+ BaseController(),
FloatingSearchInterface,
SmallToolbarInterface,
SettingsSearchAdapter.OnTitleClickListener {
@@ -33,6 +34,7 @@ class SettingsSearchController :
*/
private var adapter: SettingsSearchAdapter? = null
private var searchView: SearchView? = null
+ var query: String = ""
init {
setHasOptionsMenu(true)
@@ -40,18 +42,7 @@ class SettingsSearchController :
override fun createBinding(inflater: LayoutInflater) = SettingsSearchControllerBinding.inflate(inflater)
- override fun getTitle(): String {
- return presenter.query
- }
-
- /**
- * Create the [SettingsSearchPresenter] used in controller.
- *
- * @return instance of [SettingsSearchPresenter]
- */
- override fun createPresenter(): SettingsSearchPresenter {
- return SettingsSearchPresenter()
- }
+ override fun getTitle(): String = query
/**
* Adds items to the options menu.
@@ -80,7 +71,7 @@ class SettingsSearchController :
override fun onQueryTextChange(newText: String?): Boolean {
if (!newText.isNullOrBlank()) {
- lastSearch = newText
+ query = newText
}
setItems(getResultSet(newText))
return false
@@ -88,7 +79,7 @@ class SettingsSearchController :
},
)
- searchView?.setQuery(lastSearch, true)
+ searchView?.setQuery(query, true)
}
override fun onActionViewCollapse(item: MenuItem?) {
@@ -151,13 +142,9 @@ class SettingsSearchController :
*/
override fun onTitleClick(ctrl: SettingsController) {
searchView?.query.let {
- lastSearch = it.toString()
+ query = it.toString()
}
router.pushController(ctrl.withFadeTransaction())
}
-
- companion object {
- var lastSearch = ""
- }
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchPresenter.kt
deleted file mode 100644
index 0d03d7561b32..000000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchPresenter.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package eu.kanade.tachiyomi.ui.setting.search
-
-import android.os.Bundle
-import eu.kanade.tachiyomi.data.preference.PreferencesHelper
-import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
-
-/**
- * Presenter of [SettingsSearchController]
- * Function calls should be done from here. UI calls should be done from the controller.
- */
-open class SettingsSearchPresenter : BasePresenter() {
-
- val preferences: PreferencesHelper = Injekt.get()
-
- override fun onCreate(savedState: Bundle?) {
- super.onCreate(savedState)
- query = savedState?.getString(SettingsSearchPresenter::query.name) ?: "" // TODO - Some way to restore previous query?
- }
-
- override fun onSave(state: Bundle) {
- state.putString(SettingsSearchPresenter::query.name, query)
- super.onSave(state)
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt
index d99b70142aab..df3816bc1939 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt
@@ -29,7 +29,7 @@ import eu.kanade.tachiyomi.source.icon
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.online.HttpSource
-import eu.kanade.tachiyomi.ui.base.controller.NucleusController
+import eu.kanade.tachiyomi.ui.base.controller.BaseCoroutineController
import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.main.SearchActivity
@@ -63,7 +63,7 @@ import kotlin.math.roundToInt
* Controller to manage the catalogues available in the app.
*/
open class BrowseSourceController(bundle: Bundle) :
- NucleusController(bundle),
+ BaseCoroutineController(bundle),
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
FloatingSearchInterface,
@@ -146,13 +146,11 @@ open class BrowseSourceController(bundle: Bundle) :
// return presenter.source.icon()
// }
- override fun createPresenter(): BrowseSourcePresenter {
- return BrowseSourcePresenter(
- args.getLong(SOURCE_ID_KEY),
- args.getString(SEARCH_QUERY_KEY),
- args.getBoolean(USE_LATEST_KEY),
- )
- }
+ override val presenter = BrowseSourcePresenter(
+ args.getLong(SOURCE_ID_KEY),
+ args.getString(SEARCH_QUERY_KEY),
+ args.getBoolean(USE_LATEST_KEY),
+ )
override fun createBinding(inflater: LayoutInflater) = BrowseSourceControllerBinding.inflate(inflater)
@@ -165,7 +163,6 @@ open class BrowseSourceController(bundle: Bundle) :
binding.fab.isVisible = presenter.sourceFilters.isNotEmpty()
binding.fab.setOnClickListener { showFilters() }
- binding.progress.isVisible = true
activityBinding?.appBar?.y = 0f
activityBinding?.appBar?.updateAppBarAfterY(recycler)
activityBinding?.appBar?.lockYPos = true
@@ -178,6 +175,11 @@ open class BrowseSourceController(bundle: Bundle) :
}
return
}
+ if (presenter.items.isNotEmpty()) {
+ onAddPage(1, presenter.items)
+ } else {
+ binding.progress.isVisible = true
+ }
requestFilePermissionsSafe(301, preferences, presenter.source is LocalSource)
}
@@ -278,10 +280,9 @@ open class BrowseSourceController(bundle: Bundle) :
val searchView = activityBinding?.searchToolbar?.searchView
activityBinding?.searchToolbar?.setQueryHint("", !isBehindGlobalSearch && presenter.query.isBlank())
- val query = presenter.query
- if (query.isNotBlank()) {
+ if (presenter.query.isNotBlank()) {
searchItem?.expandActionView()
- searchView?.setQuery(query, true)
+ searchView?.setQuery(presenter.query, true)
searchView?.clearFocus()
} else if (activityBinding?.searchToolbar?.isSearchExpanded == true) {
searchItem?.collapseActionView()
@@ -516,7 +517,7 @@ open class BrowseSourceController(bundle: Bundle) :
showProgressBar()
adapter?.clear()
- presenter.restartPager(newQuery, presenter.sourceFilters)
+ presenter.restartPager(newQuery)
updatePopLatestIcons()
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt
index 20e619850dde..65d126f168ec 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.source.browse
-import android.os.Bundle
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
@@ -13,7 +12,7 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga
-import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
+import eu.kanade.tachiyomi.ui.base.presenter.BaseCoroutinePresenter
import eu.kanade.tachiyomi.ui.source.filter.CheckboxItem
import eu.kanade.tachiyomi.ui.source.filter.CheckboxSectionItem
import eu.kanade.tachiyomi.ui.source.filter.GroupItem
@@ -36,9 +35,6 @@ import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import rx.Subscription
-import rx.android.schedulers.AndroidSchedulers
-import rx.schedulers.Schedulers
import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -54,7 +50,7 @@ open class BrowseSourcePresenter(
val db: DatabaseHelper = Injekt.get(),
val prefs: PreferencesHelper = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(),
-) : BasePresenter() {
+) : BaseCoroutinePresenter() {
/**
* Selected source.
@@ -66,6 +62,10 @@ open class BrowseSourcePresenter(
var filtersChanged = false
+ var items = mutableListOf()
+ val page: Int
+ get() = pager.currentPage
+
/**
* Modifiable list of filters.
*/
@@ -87,39 +87,24 @@ open class BrowseSourcePresenter(
* Pager containing a list of manga results.
*/
private lateinit var pager: Pager
-
- /**
- * Subscription for the pager.
- */
- private var pagerSubscription: Subscription? = null
+ private var pagerJob: Job? = null
/**
* Subscription for one request from the pager.
*/
private var nextPageJob: Job? = null
- init {
- query = searchQuery ?: ""
- }
-
- override fun onCreate(savedState: Bundle?) {
- super.onCreate(savedState)
+ var query = searchQuery ?: ""
- source = sourceManager.get(sourceId) as? CatalogueSource ?: return
+ override fun onCreate() {
+ super.onCreate()
+ if (!::pager.isInitialized) {
+ source = sourceManager.get(sourceId) as? CatalogueSource ?: return
- sourceFilters = source.getFilterList()
- filtersChanged = false
-
- if (savedState != null) {
- query = savedState.getString(::query.name, "")
+ sourceFilters = source.getFilterList()
+ filtersChanged = false
+ restartPager()
}
-
- restartPager()
- }
-
- override fun onSave(state: Bundle) {
- state.putString(::query.name, query)
- super.onSave(state)
}
/**
@@ -140,27 +125,27 @@ open class BrowseSourcePresenter(
val browseAsList = prefs.browseAsList()
val sourceListType = prefs.libraryLayout()
val outlineCovers = prefs.outlineOnCovers()
+ items.clear()
// Prepare the pager.
- pagerSubscription?.let { remove(it) }
- pagerSubscription = pager.results()
- .observeOn(Schedulers.io())
- .map { (first, second) ->
- first to second
- .map { networkToLocalManga(it, sourceId) }
- .filter { !prefs.hideInLibraryItems().get() || !it.favorite }
- }
- .doOnNext { initializeMangas(it.second) }
- .map { (first, second) -> first to second.map { BrowseSourceItem(it, browseAsList, sourceListType, outlineCovers) } }
- .observeOn(AndroidSchedulers.mainThread())
- .subscribeReplay(
- { view, (page, mangas) ->
- view.onAddPage(page, mangas)
- },
- { _, error ->
+ pagerJob?.cancel()
+ pagerJob = presenterScope.launchIO {
+ pager.results().onEach { (page, second) ->
+ try {
+ val mangas = second
+ .map { networkToLocalManga(it, sourceId) }
+ .filter { !prefs.hideInLibraryItems().get() || !it.favorite }
+ initializeMangas(mangas)
+ val items = mangas.map {
+ BrowseSourceItem(it, browseAsList, sourceListType, outlineCovers)
+ }
+ this@BrowseSourcePresenter.items.addAll(items)
+ withUIContext { controller?.onAddPage(page, items) }
+ } catch (error: Exception) {
Timber.e(error)
- },
- )
+ }
+ }.collect()
+ }
// Request first page.
requestNext()
@@ -173,14 +158,11 @@ open class BrowseSourcePresenter(
if (!hasNextPage()) return
nextPageJob?.cancel()
- nextPageJob = launchIO {
+ nextPageJob = presenterScope.launchIO {
try {
pager.requestNextPage()
} catch (e: Throwable) {
- withUIContext {
- @Suppress("DEPRECATION")
- view?.onAddPageError(e)
- }
+ withUIContext { controller?.onAddPageError(e) }
}
}
}
@@ -229,10 +211,7 @@ open class BrowseSourcePresenter(
.filter { it.thumbnail_url == null && !it.initialized }
.map { getMangaDetails(it) }
.onEach {
- withUIContext {
- @Suppress("DEPRECATION")
- view?.onMangaInitialized(it)
- }
+ withUIContext { controller?.onMangaInitialized(it) }
}
.catch { e -> Timber.e(e) }
.collect()
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/Pager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/Pager.kt
index 4c714a76192e..5f8004cc7d76 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/Pager.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/Pager.kt
@@ -1,9 +1,10 @@
package eu.kanade.tachiyomi.ui.source.browse
-import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga
-import rx.Observable
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
/**
* A general pager for source requests (latest updates, popular, search)
@@ -13,18 +14,18 @@ abstract class Pager(var currentPage: Int = 1) {
var hasNextPage = true
private set
- protected val results: PublishRelay>> = PublishRelay.create()
+ protected val results = MutableSharedFlow>>()
- fun results(): Observable>> {
- return results.asObservable()
+ fun results(): SharedFlow>> {
+ return results.asSharedFlow()
}
abstract suspend fun requestNextPage()
- fun onPageReceived(mangasPage: MangasPage) {
+ suspend fun onPageReceived(mangasPage: MangasPage) {
val page = currentPage
currentPage++
hasNextPage = mangasPage.hasNextPage && mangasPage.mangas.isNotEmpty()
- results.call(Pair(page, mangasPage.mangas))
+ results.emit(Pair(page, mangasPage.mangas))
}
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchController.kt
index 830bf274760e..3a29ec5170f7 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchController.kt
@@ -17,7 +17,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.SourceGlobalSearchControllerBinding
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.base.SmallToolbarInterface
-import eu.kanade.tachiyomi.ui.base.controller.NucleusController
+import eu.kanade.tachiyomi.ui.base.controller.BaseCoroutineController
import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.main.SearchActivity
@@ -42,7 +42,7 @@ open class GlobalSearchController(
protected val initialQuery: String? = null,
val extensionFilter: String? = null,
bundle: Bundle? = null,
-) : NucleusController(bundle),
+) : BaseCoroutineController(bundle),
FloatingSearchInterface,
SmallToolbarInterface,
GlobalSearchAdapter.OnTitleClickListener,
@@ -78,14 +78,7 @@ open class GlobalSearchController(
return customTitle ?: presenter.query
}
- /**
- * Create the [GlobalSearchPresenter] used in controller.
- *
- * @return instance of [GlobalSearchPresenter]
- */
- override fun createPresenter(): GlobalSearchPresenter {
- return GlobalSearchPresenter(initialQuery, extensionFilter)
- }
+ override val presenter = GlobalSearchPresenter(initialQuery, extensionFilter)
override fun onTitleClick(source: CatalogueSource) {
preferences.lastUsedCatalogueSource().set(source.id)
@@ -108,7 +101,7 @@ open class GlobalSearchController(
/**
* Called when manga in global search is long clicked.
*
- * @param manga clicked item containing manga information.
+ * @param position clicked item containing manga information.
*/
override fun onMangaLongClick(position: Int, adapter: GlobalSearchCardAdapter) {
val manga = adapter.getItem(position)?.manga ?: return
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt
index 0018a65fafbc..ace9ec97a05b 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.source.globalsearch
-import android.os.Bundle
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
@@ -12,15 +11,18 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga
-import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
-import eu.kanade.tachiyomi.ui.source.browse.BrowseSourcePresenter
-import eu.kanade.tachiyomi.util.system.runAsObservable
-import rx.Observable
-import rx.Subscription
-import rx.android.schedulers.AndroidSchedulers
-import rx.schedulers.Schedulers
-import rx.subjects.PublishSubject
-import timber.log.Timber
+import eu.kanade.tachiyomi.ui.base.presenter.BaseCoroutinePresenter
+import eu.kanade.tachiyomi.util.system.awaitSingle
+import eu.kanade.tachiyomi.util.system.launchIO
+import eu.kanade.tachiyomi.util.system.launchUI
+import eu.kanade.tachiyomi.util.system.withUIContext
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Semaphore
+import kotlinx.coroutines.sync.withPermit
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
@@ -43,56 +45,43 @@ open class GlobalSearchPresenter(
val db: DatabaseHelper = Injekt.get(),
private val preferences: PreferencesHelper = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(),
-) : BasePresenter() {
+) : BaseCoroutinePresenter() {
/**
* Enabled sources.
*/
val sources by lazy { getSourcesToQuery() }
- /**
- * Fetches the different sources by user settings.
- */
- private var fetchSourcesSubscription: Subscription? = null
+ private var fetchSourcesJob: Job? = null
private var loadTime = hashMapOf()
- /**
- * Subject which fetches image of given manga.
- */
- private val fetchImageSubject = PublishSubject.create, Source>>()
+ var query = ""
- /**
- * Subscription for fetching images of manga.
- */
- private var fetchImageSubscription: Subscription? = null
+ private val fetchImageFlow = MutableSharedFlow, Source>>()
+
+ private var fetchImageJob: Job? = null
private val extensionManager: ExtensionManager by injectLazy()
private var extensionFilter: String? = null
- override fun onCreate(savedState: Bundle?) {
- super.onCreate(savedState)
+ var items: List = emptyList()
- extensionFilter = savedState?.getString(GlobalSearchPresenter::extensionFilter.name)
- ?: initialExtensionFilter
+ private val semaphore = Semaphore(5)
- // Perform a search with previous or initial state
- search(
- savedState?.getString(BrowseSourcePresenter::query.name) ?: initialQuery.orEmpty(),
- )
- }
+ override fun onCreate() {
+ super.onCreate()
- override fun onDestroy() {
- fetchSourcesSubscription?.unsubscribe()
- fetchImageSubscription?.unsubscribe()
- super.onDestroy()
- }
+ extensionFilter = initialExtensionFilter
- override fun onSave(state: Bundle) {
- state.putString(BrowseSourcePresenter::query.name, query)
- state.putString(GlobalSearchPresenter::extensionFilter.name, extensionFilter)
- super.onSave(state)
+ if (items.isEmpty()) {
+ // Perform a search with previous or initial state
+ search(initialQuery.orEmpty())
+ }
+ presenterScope.launchUI {
+ controller?.setItems(items)
+ }
}
/**
@@ -126,7 +115,7 @@ open class GlobalSearchPresenter(
}
val languages = preferences.enabledLanguages().get()
- val filterSources = extensionManager.installedExtensions
+ val filterSources = extensionManager.installedExtensionsFlow.value
.filter { it.pkgName == filter }
.flatMap { it.sources }
.filter { it.lang in languages }
@@ -174,70 +163,49 @@ open class GlobalSearchPresenter(
// Create items with the initial state
val initialItems = sources.map { createCatalogueSearchItem(it, null) }
- var items = initialItems
-
+ items = initialItems
val pinnedSourceIds = preferences.pinnedCatalogues().get()
- fetchSourcesSubscription?.unsubscribe()
- fetchSourcesSubscription = Observable.from(sources).flatMap(
- { source ->
- Observable.defer { source.fetchSearchManga(1, query, source.getFilterList()) }
- .subscribeOn(Schedulers.io()).onErrorReturn {
- MangasPage(
- emptyList(),
- false,
- )
- } // Ignore timeouts or other exceptions
- .map { it.mangas.take(10) } // Get at most 10 manga from search result.
- .map {
- it.map {
- networkToLocalManga(
- it,
- source.id,
- )
+ fetchSourcesJob?.cancel()
+ fetchSourcesJob = presenterScope.launch {
+ sources.map { source ->
+ launch mainLaunch@{
+ semaphore.withPermit {
+ if (this@GlobalSearchPresenter.items.find { it.source == source }?.results != null) {
+ return@mainLaunch
+ }
+ val mangas = try {
+ source.fetchSearchManga(1, query, source.getFilterList()).awaitSingle()
+ } catch (error: Exception) {
+ MangasPage(emptyList(), false)
}
- } // Convert to local manga.
- .doOnNext { fetchImage(it, source) } // Load manga covers.
- .map {
- if (it.isNotEmpty() && !loadTime.containsKey(source.id)) {
+ .mangas.take(10)
+ .map { networkToLocalManga(it, source.id) }
+ fetchImage(mangas, source)
+ if (mangas.isNotEmpty() && !loadTime.containsKey(source.id)) {
loadTime[source.id] = Date().time
}
- createCatalogueSearchItem(
+ val result = createCatalogueSearchItem(
source,
- it.map { GlobalSearchMangaItem(it) },
+ mangas.map { GlobalSearchMangaItem(it) },
)
+ items = items
+ .map { item -> if (item.source == result.source) result else item }
+ .sortedWith(
+ compareBy(
+ // Bubble up sources that actually have results
+ { it.results.isNullOrEmpty() },
+ // Same as initial sort, i.e. pinned first then alphabetically
+ { it.source.id.toString() !in pinnedSourceIds },
+ { loadTime[it.source.id] ?: 0L },
+ { "${it.source.name.lowercase(Locale.getDefault())} (${it.source.lang})" },
+ ),
+ )
+ withUIContext { controller?.setItems(items) }
}
- },
- 5,
- )
- .observeOn(AndroidSchedulers.mainThread())
- // Update matching source with the obtained results
- .map { result ->
- items
- .map { item -> if (item.source == result.source) result else item }
- .sortedWith(
- compareBy(
- // Bubble up sources that actually have results
- { it.results.isNullOrEmpty() },
- // Same as initial sort, i.e. pinned first then alphabetically
- { it.source.id.toString() !in pinnedSourceIds },
- { loadTime[it.source.id] ?: 0L },
- { "${it.source.name.lowercase(Locale.getDefault())} (${it.source.lang})" },
- ),
- )
+ }
}
- // Update current state
- .doOnNext { items = it }
- // Deliver initial state
- .startWith(initialItems)
- .subscribeLatestCache(
- { view, manga ->
- view.setItems(manga)
- },
- { _, error ->
- Timber.e(error)
- },
- )
+ }
}
/**
@@ -246,33 +214,26 @@ open class GlobalSearchPresenter(
* @param manga the list of manga to initialize.
*/
private fun fetchImage(manga: List, source: Source) {
- fetchImageSubject.onNext(Pair(manga, source))
+ presenterScope.launch {
+ fetchImageFlow.emit(Pair(manga, source))
+ }
}
/**
* Subscribes to the initializer of manga details and updates the view if needed.
*/
private fun initializeFetchImageSubscription() {
- fetchImageSubscription?.unsubscribe()
- fetchImageSubscription = fetchImageSubject.observeOn(Schedulers.io())
- .flatMap { (mangaList, source) ->
- Observable.from(mangaList)
- .filter { it.thumbnail_url == null && !it.initialized }
- .map { Pair(it, source) }
- .concatMap { runAsObservable { getMangaDetails(it.first, it.second) } }
- .map { Pair(source as CatalogueSource, it) }
- }
- .onBackpressureBuffer()
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- { (source, manga) ->
- @Suppress("DEPRECATION")
- view?.onMangaInitialized(source, manga)
- },
- { error ->
- Timber.e(error)
- },
- )
+ fetchImageJob?.cancel()
+ fetchImageJob = fetchImageFlow.onEach { (mangaList, source) ->
+ mangaList
+ .filter { it.thumbnail_url == null && !it.initialized }
+ .map {
+ presenterScope.launchIO {
+ val manga = getMangaDetails(it, source)
+ withUIContext { controller?.onMangaInitialized(source as CatalogueSource, manga) }
+ }
+ }
+ }.launchIn(presenterScope)
}
/**
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt
index 88beb6ff1a0b..51212001e7b3 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt
@@ -36,12 +36,10 @@ import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.net.toUri
import com.hippo.unifile.UniFile
-import com.nononsenseapps.filepicker.FilePickerActivity
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import eu.kanade.tachiyomi.ui.main.MainActivity
-import eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
@@ -83,27 +81,6 @@ inline fun Context.notification(channelId: String, func: NotificationCompat.Buil
return builder.build()
}
-/**
- * Helper method to construct an Intent to use a custom file picker.
- * @param currentDir the path the file picker will open with.
- * @return an Intent to start the file picker activity.
- */
-fun Context.getFilePicker(currentDir: String): Intent {
- return Intent(this, CustomLayoutPickerActivity::class.java)
- .putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
- .putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
- .putExtra(FilePickerActivity.EXTRA_START_PATH, currentDir)
-}
-
-/**
- * Checks if the give permission is granted.
- *
- * @param permission the permission to check.
- * @return true if it has permissions.
- */
-fun Context.hasPermission(permission: String) =
- ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
-
/**
* Returns the color for the given attribute.
*
diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/CustomLayoutPicker.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/CustomLayoutPicker.kt
deleted file mode 100644
index 8713973433ea..000000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/widget/CustomLayoutPicker.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package eu.kanade.tachiyomi.widget
-
-import android.view.ViewGroup
-import androidx.recyclerview.widget.RecyclerView
-import com.nononsenseapps.filepicker.AbstractFilePickerFragment
-import com.nononsenseapps.filepicker.FilePickerActivity
-import com.nononsenseapps.filepicker.FilePickerFragment
-import com.nononsenseapps.filepicker.LogicHandler
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.util.view.inflate
-import java.io.File
-
-class CustomLayoutPickerActivity : FilePickerActivity() {
-
- override fun getFragment(startPath: String?, mode: Int, allowMultiple: Boolean, allowCreateDir: Boolean): AbstractFilePickerFragment {
- val fragment = CustomLayoutFilePickerFragment()
- fragment.setArgs(startPath, mode, allowMultiple, allowCreateDir)
- return fragment
- }
-}
-
-class CustomLayoutFilePickerFragment : FilePickerFragment() {
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
- return when (viewType) {
- LogicHandler.VIEWTYPE_DIR -> {
- val view = parent.inflate(R.layout.common_listitem_dir)
- DirViewHolder(view)
- }
- else -> super.onCreateViewHolder(parent, viewType)
- }
- }
-}
diff --git a/app/src/main/res/drawable/ic_file_open_24dp.xml b/app/src/main/res/drawable/ic_file_open_24dp.xml
new file mode 100644
index 000000000000..e7828748a788
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_open_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/common_listitem_dir.xml b/app/src/main/res/layout/common_listitem_dir.xml
deleted file mode 100644
index 1e1271e31882..000000000000
--- a/app/src/main/res/layout/common_listitem_dir.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index df6f7c7def54..0f23b7279076 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -384,21 +384,4 @@
- 13sp
-
-
-
-
-
-
-
-