package eu.kanade.tachiyomi.ui.extension import android.os.Bundle import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.concurrent.TimeUnit private typealias ExtensionTuple = Triple, List, List> /** * Presenter of [ExtensionController]. */ open class ExtensionPresenter( private val extensionManager: ExtensionManager = Injekt.get() ) : BasePresenter() { private var extensions = emptyList() private var currentDownloads = hashMapOf() override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) bindToExtensionsObservable() } private fun bindToExtensionsObservable(): Subscription { val installedObservable = extensionManager.getInstalledExtensionsObservable() val untrustedObservable = extensionManager.getUntrustedExtensionsObservable() val availableObservable = extensionManager.getAvailableExtensionsObservable() .startWith(emptyList()) return Observable.combineLatest(installedObservable, untrustedObservable, availableObservable) { installed, untrusted, available -> Triple(installed, untrusted, available) } .debounce(100, TimeUnit.MILLISECONDS) .map(::toItems) .observeOn(AndroidSchedulers.mainThread()) .subscribeLatestCache({ view, _ -> view.setExtensions(extensions) }) } @Synchronized private fun toItems(tuple: ExtensionTuple): List { val (installed, untrusted, available) = tuple val items = mutableListOf() val installedSorted = installed.sortedWith(compareBy({ !it.hasUpdate }, { it.name })) val untrustedSorted = untrusted.sortedBy { it.name } val availableSorted = available // Filter out already installed extensions .filter { avail -> installed.none { it.pkgName == avail.pkgName } && untrusted.none { it.pkgName == avail.pkgName } } .sortedBy { it.name } if (installedSorted.isNotEmpty() || untrustedSorted.isNotEmpty()) { val header = ExtensionGroupItem(true, installedSorted.size + untrustedSorted.size) items += installedSorted.map { extension -> ExtensionItem(extension, header, currentDownloads[extension.pkgName]) } items += untrustedSorted.map { extension -> ExtensionItem(extension, header) } } if (availableSorted.isNotEmpty()) { val header = ExtensionGroupItem(false, availableSorted.size) items += availableSorted.map { extension -> ExtensionItem(extension, header, currentDownloads[extension.pkgName]) } } this.extensions = items return items } @Synchronized private fun updateInstallStep(extension: Extension, state: InstallStep): ExtensionItem? { val extensions = extensions.toMutableList() val position = extensions.indexOfFirst { it.extension.pkgName == extension.pkgName } return if (position != -1) { val item = extensions[position].copy(installStep = state) extensions[position] = item this.extensions = extensions item } else { null } } fun installExtension(extension: Extension.Available) { extensionManager.installExtension(extension).subscribeToInstallUpdate(extension) } fun updateExtension(extension: Extension.Installed) { extensionManager.updateExtension(extension).subscribeToInstallUpdate(extension) } private fun Observable.subscribeToInstallUpdate(extension: Extension) { this.doOnNext { currentDownloads[extension.pkgName] = it } .doOnUnsubscribe { currentDownloads.remove(extension.pkgName) } .map { state -> updateInstallStep(extension, state) } .subscribeWithView({ view, item -> if (item != null) { view.downloadUpdate(item) } }) } fun uninstallExtension(pkgName: String) { extensionManager.uninstallExtension(pkgName) } fun findAvailableExtensions() { extensionManager.findAvailableExtensions() } fun trustSignature(signatureHash: String) { extensionManager.trustSignature(signatureHash) } }