Added checks for updatable state on remote repos
This commit is contained in:
parent
a7f4496db7
commit
2926772bba
@ -121,10 +121,10 @@ class SectionTitle(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class RepoItem(val item: Repo) : ComparableRvItem<RepoItem>() {
|
class RepoItem(val item: Repo) : ComparableRvItem<RepoItem>() {
|
||||||
|
|
||||||
override val layoutRes: Int = R.layout.item_repo_md2
|
override val layoutRes: Int = R.layout.item_repo_md2
|
||||||
|
|
||||||
val progress = KObservableField(0)
|
val progress = KObservableField(0)
|
||||||
|
val isUpdate = KObservableField(false)
|
||||||
|
|
||||||
override fun contentSameAs(other: RepoItem): Boolean = item == other.item
|
override fun contentSameAs(other: RepoItem): Boolean = item == other.item
|
||||||
override fun itemSameAs(other: RepoItem): Boolean = item.id == other.item.id
|
override fun itemSameAs(other: RepoItem): Boolean = item.id == other.item.id
|
||||||
|
@ -33,6 +33,7 @@ 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 me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
|
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
|
||||||
|
import org.jetbrains.annotations.NotNull
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@ -132,6 +133,10 @@ class ModuleViewModel(
|
|||||||
items.update(it.first, it.second)
|
items.update(it.first, it.second)
|
||||||
if (!items.contains(sectionRemote)) {
|
if (!items.contains(sectionRemote)) {
|
||||||
loadRemote()
|
loadRemote()
|
||||||
|
} else {
|
||||||
|
Single.fromCallable { itemsRemote }
|
||||||
|
.subscribeK { it.ensureUpdateState() }
|
||||||
|
.add()
|
||||||
}
|
}
|
||||||
moveToState()
|
moveToState()
|
||||||
}
|
}
|
||||||
@ -150,10 +155,7 @@ class ModuleViewModel(
|
|||||||
}
|
}
|
||||||
remoteJob = Single.fromCallable { itemsRemote.size }
|
remoteJob = Single.fromCallable { itemsRemote.size }
|
||||||
.flatMap { loadRemoteInternal(offset = it) }
|
.flatMap { loadRemoteInternal(offset = it) }
|
||||||
.map { it.map { RepoItem(it) } }
|
.subscribeK(onError = Timber::e) {
|
||||||
.subscribeK(onError = {
|
|
||||||
Timber.e(it)
|
|
||||||
}) {
|
|
||||||
if (!items.contains(sectionRemote)) {
|
if (!items.contains(sectionRemote)) {
|
||||||
items.add(sectionRemote)
|
items.add(sectionRemote)
|
||||||
}
|
}
|
||||||
@ -176,6 +178,7 @@ class ModuleViewModel(
|
|||||||
}
|
}
|
||||||
return Single.fromCallable { dao.searchRepos(query, offset) }
|
return Single.fromCallable { dao.searchRepos(query, offset) }
|
||||||
.map { it.map { RepoItem(it) } }
|
.map { it.map { RepoItem(it) } }
|
||||||
|
.doOnSuccess { it.ensureUpdateState() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun query(query: String = this.query, offset: Int = 0) {
|
private fun query(query: String = this.query, offset: Int = 0) {
|
||||||
@ -199,14 +202,17 @@ class ModuleViewModel(
|
|||||||
private fun loadRemoteInternal(
|
private fun loadRemoteInternal(
|
||||||
offset: Int = 0,
|
offset: Int = 0,
|
||||||
downloadRepos: Boolean = offset == 0
|
downloadRepos: Boolean = offset == 0
|
||||||
): Single<List<Repo>> = Single.fromCallable { dao.getRepos(offset) }.flatMap {
|
): Single<List<RepoItem>> = Single.fromCallable { dao.getRepos(offset) }
|
||||||
when {
|
.map { it.map { RepoItem(it) } }
|
||||||
// in case we find result empty and offset is initial we need to refresh the repos.
|
.flatMap {
|
||||||
downloadRepos && it.isEmpty() && offset == 0 -> downloadRepos()
|
when {
|
||||||
.andThen(loadRemoteInternal(downloadRepos = false))
|
// in case we find result empty and offset is initial we need to refresh the repos.
|
||||||
else -> Single.just(it)
|
downloadRepos && it.isEmpty() && offset == 0 -> downloadRepos()
|
||||||
|
.andThen(loadRemoteInternal(downloadRepos = false))
|
||||||
|
else -> Single.just(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.doOnSuccess { it.ensureUpdateState() }
|
||||||
|
|
||||||
private fun downloadRepos() = Single.just(Unit)
|
private fun downloadRepos() = Single.just(Unit)
|
||||||
.flatMap { repoUpdater() }
|
.flatMap { repoUpdater() }
|
||||||
@ -227,12 +233,58 @@ class ModuleViewModel(
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically allocated list of [itemsInstalled]. It might be invalidated any time on any
|
||||||
|
* thread hence it needs to be volatile.
|
||||||
|
*
|
||||||
|
* There might be a state where this field gets assigned `null` whilst being used by another
|
||||||
|
* instance of any job, so the list will be immediately reinstated back.
|
||||||
|
*
|
||||||
|
* ### Note:
|
||||||
|
*
|
||||||
|
* It is caller's responsibility to invalidate this variable at the end of every job to save
|
||||||
|
* memory.
|
||||||
|
* */
|
||||||
|
@Volatile
|
||||||
|
private var cachedItemsInstalled: List<ModuleItem>? = null
|
||||||
|
@WorkerThread @NotNull get() = field ?: itemsInstalled.also { field = it }
|
||||||
|
|
||||||
|
private val Repo.isUpdatable: Boolean
|
||||||
|
@WorkerThread get() {
|
||||||
|
val installed = cachedItemsInstalled!!
|
||||||
|
.firstOrNull { it.item.id == id }
|
||||||
|
?: return false
|
||||||
|
return installed.item.versionCode < versionCode
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously updates state of all repo items so the loading speed is not impaired by this
|
||||||
|
* seemingly unnecessary operation. Because of the nature of this operation, the "update" status
|
||||||
|
* is not guaranteed for all items and can change any time.
|
||||||
|
*
|
||||||
|
* It is permitted running this function in parallel; it will also attempt to run in parallel
|
||||||
|
* by itself to finish the job as quickly as possible.
|
||||||
|
*
|
||||||
|
* No list manipulations should be done in this method whatsoever! By being heavily parallelized
|
||||||
|
* is will inevitably throw exceptions by simultaneously accessing the same list.
|
||||||
|
*
|
||||||
|
* In order to save time it uses helper [cachedItemsInstalled].
|
||||||
|
* */
|
||||||
|
private fun List<RepoItem>.ensureUpdateState() = Single.just(this)
|
||||||
|
.flattenAsFlowable { it }
|
||||||
|
.parallel()
|
||||||
|
.map { it to it.item.isUpdatable }
|
||||||
|
.sequential()
|
||||||
|
.doOnComplete { cachedItemsInstalled = null }
|
||||||
|
.subscribeK { it.first.isUpdate.value = it.second }
|
||||||
|
.add()
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
fun moveToState() = Single.fromCallable { itemsInstalled.any { it.isModified } }
|
fun moveToState() = Single.fromCallable { itemsInstalled.any { it.isModified } }
|
||||||
.subscribeK { sectionActive.hasButton.value = it }
|
.subscribeK { sectionActive.hasButton.value = it }
|
||||||
.add()
|
.add()
|
||||||
|
|
||||||
fun download(item: RepoItem) = ModuleInstallDialog(item.item).publish()
|
|
||||||
|
|
||||||
fun sectionPressed(item: SectionTitle) = when (item) {
|
fun sectionPressed(item: SectionTitle) = when (item) {
|
||||||
sectionActive -> reboot() //TODO add reboot picker, regular reboot is not always preferred
|
sectionActive -> reboot() //TODO add reboot picker, regular reboot is not always preferred
|
||||||
sectionRemote -> {
|
sectionRemote -> {
|
||||||
@ -252,6 +304,7 @@ class ModuleViewModel(
|
|||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun downloadPressed(item: RepoItem) = ModuleInstallDialog(item.item).publish()
|
||||||
fun installPressed() = InstallExternalModuleEvent().publish()
|
fun installPressed() = InstallExternalModuleEvent().publish()
|
||||||
fun infoPressed(item: RepoItem) = OpenChangelogEvent(item.item).publish()
|
fun infoPressed(item: RepoItem) = OpenChangelogEvent(item.item).publish()
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
<data>
|
<data>
|
||||||
|
|
||||||
|
<import type="com.topjohnwu.magisk.R" />
|
||||||
|
|
||||||
<variable
|
<variable
|
||||||
name="item"
|
name="item"
|
||||||
type="com.topjohnwu.magisk.model.entity.recycler.RepoItem" />
|
type="com.topjohnwu.magisk.model.entity.recycler.RepoItem" />
|
||||||
@ -108,11 +110,12 @@
|
|||||||
android:paddingStart="@dimen/l_50"
|
android:paddingStart="@dimen/l_50"
|
||||||
isEnabled="@{!(item.progress == -100 || (item.progress > 0 && item.progress < 100))}"
|
isEnabled="@{!(item.progress == -100 || (item.progress > 0 && item.progress < 100))}"
|
||||||
android:contentDescription="@string/download"
|
android:contentDescription="@string/download"
|
||||||
android:onClick="@{() -> viewModel.download(item)}"
|
srcCompat="@{item.isUpdate() ? R.drawable.ic_update_md2 : R.drawable.ic_download_md2}"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/module_divider"
|
app:layout_constraintTop_toBottomOf="@+id/module_divider"
|
||||||
app:srcCompat="@drawable/ic_download_md2" />
|
android:onClick="@{() -> viewModel.downloadPressed(item)}"
|
||||||
|
tools:srcCompat="@drawable/ic_download_md2" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user