Updated the logic that refreshes the modules

Also added empty states for installed and made updatable modules visible all the time to avoid unnecessary transitions
This commit is contained in:
Viktor De Pasquale 2020-01-06 17:46:08 +01:00
parent 13262fdb18
commit ed837ba26f
4 changed files with 89 additions and 47 deletions

View File

@ -0,0 +1,19 @@
package com.topjohnwu.magisk.model.entity.recycler
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
class TextItem(val text: Int) : ComparableRvItem<TextItem>() {
override val layoutRes = R.layout.item_text
override fun onBindingBound(binding: ViewDataBinding) {
super.onBindingBound(binding)
val params = binding.root.layoutParams as? StaggeredGridLayoutManager.LayoutParams
params?.isFullSpan = true
}
override fun contentSameAs(other: TextItem) = text == other.text
override fun itemSameAs(other: TextItem) = contentSameAs(other)
}

View File

@ -22,9 +22,12 @@ import com.topjohnwu.magisk.redesign.compat.*
import com.topjohnwu.magisk.tasks.RepoUpdater import com.topjohnwu.magisk.tasks.RepoUpdater
import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.magisk.utils.currentLocale import com.topjohnwu.magisk.utils.currentLocale
import io.reactivex.Completable
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import me.tatarka.bindingcollectionadapter2.collections.MergeObservableList
import timber.log.Timber import timber.log.Timber
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -54,8 +57,24 @@ class ModuleViewModel(
it.bindExtra(BR.viewModel, this) it.bindExtra(BR.viewModel, this)
} }
private val itemNoneInstalled = TextItem(R.string.module_install_none)
private val itemNoneUpdatable = TextItem(R.string.module_update_none)
private val itemsInstalled = diffListOf<ModuleItem>()
private val itemsUpdatable = diffListOf<RepoItem.Update>()
private val itemsRemote = diffListOf<RepoItem.Remote>()
val adapter = adapterOf<ComparableRvItem<*>>() val adapter = adapterOf<ComparableRvItem<*>>()
val items = diffListOf<ComparableRvItem<*>>() val items = MergeObservableList<ComparableRvItem<*>>()
.insertItem(sectionActive)
.insertItem(itemNoneInstalled)
.insertList(itemsInstalled)
.insertItem(InstallModule)
.insertItem(sectionUpdate)
.insertItem(itemNoneUpdatable)
.insertList(itemsUpdatable)
.insertItem(sectionRemote)
.insertList(itemsRemote)!!
val itemBinding = itemBindingOf<ComparableRvItem<*>> { val itemBinding = itemBindingOf<ComparableRvItem<*>> {
it.bindExtra(BR.viewModel, this) it.bindExtra(BR.viewModel, this)
} }
@ -94,15 +113,6 @@ class ModuleViewModel(
// --- // ---
private val itemsInstalled
@WorkerThread get() = items.filterIsInstance<ModuleItem>()
private val itemsUpdatable
@WorkerThread get() = items.filterIsInstance<RepoItem.Update>()
private val itemsRemote
@WorkerThread get() = items.filterIsInstance<RepoItem.Remote>()
private var remoteJob: Disposable? = null private var remoteJob: Disposable? = null
private val dao private val dao
get() = when (Config.repoOrder) { get() = when (Config.repoOrder) {
@ -126,22 +136,34 @@ class ModuleViewModel(
// --- // ---
override fun refresh() = Single.fromCallable { Module.loadModules() } override fun refresh(): Disposable {
val installedTask = loadInstalled()
val remoteTask = if (itemsRemote.isEmpty()) {
Completable.fromAction { loadRemote() }
} else {
Completable.complete()
}
return Completable.merge(listOf(installedTask, remoteTask)).subscribeK()
}
private fun loadInstalled() = Single.fromCallable { Module.loadModules() }
.map { it.map { ModuleItem(it) } } .map { it.map { ModuleItem(it) } }
.map { it.order() } .map { it.order() }
.map { it.loadDetail() } .map { it.loadDetail() }
.map { build(active = it, updatable = loadUpdates(it)) } .map { it to itemsInstalled.calculateDiff(it) }
.map { it to items.calculateDiff(it) }
.applyViewModel(this) .applyViewModel(this)
.subscribeK { .observeOn(AndroidSchedulers.mainThread())
items.update(it.first, it.second) .doOnSuccess { itemsInstalled.update(it.first, it.second) }
if (!items.contains(sectionRemote)) { .doOnSuccess { if (itemsInstalled.isNotEmpty()) items.remove(itemNoneInstalled) }
loadRemote() .observeOn(Schedulers.io())
} .map { loadUpdates(it.first) }
updateActiveState() .observeOn(AndroidSchedulers.mainThread())
} .map { it to itemsUpdatable.calculateDiff(it) }
.doOnSuccess { itemsUpdatable.update(it.first, it.second) }
.doOnSuccess { if (itemsUpdatable.isNotEmpty()) items.remove(itemNoneUpdatable) }
.ignoreElement()!!
fun loadRemoteImplicit() = let { items.clear(); itemsSearch.clear() } fun loadRemoteImplicit() = let { itemsRemote.clear(); itemsSearch.clear() }
.run { downloadRepos() } .run { downloadRepos() }
.applyViewModel(this, false) .applyViewModel(this, false)
.subscribeK { refresh(); submitQuery() } .subscribeK { refresh(); submitQuery() }
@ -156,10 +178,7 @@ class ModuleViewModel(
remoteJob = Single.fromCallable { itemsRemote.size } remoteJob = Single.fromCallable { itemsRemote.size }
.flatMap { loadRemoteInternal(offset = it) } .flatMap { loadRemoteInternal(offset = it) }
.subscribeK(onError = Timber::e) { .subscribeK(onError = Timber::e) {
if (!items.contains(sectionRemote)) { itemsRemote.addAll(it)
items.add(sectionRemote)
}
items.addAll(it)
} }
} }
@ -201,7 +220,7 @@ class ModuleViewModel(
private fun loadRemoteInternal( private fun loadRemoteInternal(
offset: Int = 0, offset: Int = 0,
downloadRepos: Boolean = offset == 0 downloadRepos: Boolean = offset == 0
): Single<List<RepoItem>> = Single.fromCallable { dao.getRepos(offset) } ): Single<List<RepoItem.Remote>> = Single.fromCallable { dao.getRepos(offset) }
.map { it.map { RepoItem.Remote(it) } } .map { it.map { RepoItem.Remote(it) } }
.flatMap { .flatMap {
when { when {
@ -260,7 +279,7 @@ class ModuleViewModel(
updateOrderIcon() updateOrderIcon()
Single.fromCallable { itemsRemote } Single.fromCallable { itemsRemote }
.subscribeK { .subscribeK {
items.removeAll(it) itemsRemote.removeAll(it)
remoteJob?.dispose() remoteJob?.dispose()
loadRemote() loadRemote()
}.add() }.add()
@ -275,24 +294,4 @@ class ModuleViewModel(
OpenChangelogEvent(item.repo ?: return).publish() OpenChangelogEvent(item.repo ?: return).publish()
} }
// ---
/** Callable only from worker thread because of expensive list filtering */
@WorkerThread
private fun build(
active: List<ModuleItem> = itemsInstalled,
updatable: List<RepoItem.Update> = itemsUpdatable,
remote: List<RepoItem.Remote> = itemsRemote
) = (active + InstallModule)
.prependIfNotEmpty { sectionActive }
.prependIf(Config.coreOnly) { SafeModeNotice } +
updatable.prependIfNotEmpty { sectionUpdate } +
remote.prependIfNotEmpty { sectionRemote }
private fun <T> List<T>.prependIf(condition: Boolean, item: () -> T) =
if (condition) listOf(item()) + this else this
private fun <T> List<T>.prependIfNotEmpty(item: () -> T) =
prependIf(isNotEmpty(), item)
} }

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="item"
type="com.topjohnwu.magisk.model.entity.recycler.TextItem" />
</data>
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="@dimen/l1"
android:text="@{item.text}"
android:textAppearance="@style/AppearanceFoundation.Tiny.Variant"
tools:text="@tools:sample/lorem/random" />
</layout>

View File

@ -85,6 +85,8 @@
<string name="module_state_remove">Remove</string> <string name="module_state_remove">Remove</string>
<string name="module_state_restore">Restore</string> <string name="module_state_restore">Restore</string>
<string name="module_action_install_external">Install from storage</string> <string name="module_action_install_external">Install from storage</string>
<string name="module_update_none">Your modules are up to date!</string>
<string name="module_install_none">No modules detected, try installing one from the list below.</string>
<string name="superuser_toggle_log">Logs</string> <string name="superuser_toggle_log">Logs</string>
<string name="superuser_toggle_notification">Notifications</string> <string name="superuser_toggle_notification">Notifications</string>