De-Rx ModuleViewModel

This commit is contained in:
topjohnwu 2020-07-08 01:26:45 -07:00
parent 86db0cd2cd
commit 01a43b03bd
6 changed files with 102 additions and 95 deletions

View File

@ -131,7 +131,7 @@ class OpenChangelogEvent(val item: Repo) : ViewEvent(), ContextExecutor {
} }
} }
class PermissionEvent( class RxPermissionEvent(
val permissions: List<String>, val permissions: List<String>,
val callback: PublishSubject<Boolean> val callback: PublishSubject<Boolean>
) : ViewEvent(), ActivityExecutor { ) : ViewEvent(), ActivityExecutor {
@ -148,6 +148,22 @@ class PermissionEvent(
} }
} }
class PermissionEvent(
private val permissions: List<String>,
private val callback: (Boolean) -> Unit
) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseActivity) =
activity.withPermissions(*permissions.toTypedArray()) {
onSuccess {
callback(true)
}
onFailure {
callback(false)
}
}
}
class BackPressEvent : ViewEvent(), ActivityExecutor { class BackPressEvent : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseActivity) { override fun invoke(activity: BaseActivity) {
activity.onBackPressed() activity.onBackPressed()

View File

@ -1,5 +1,6 @@
package com.topjohnwu.magisk.ui.base package com.topjohnwu.magisk.ui.base
import android.Manifest
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.databinding.Bindable import androidx.databinding.Bindable
@ -88,7 +89,15 @@ abstract class BaseViewModel(
fun withPermissions(vararg permissions: String): Observable<Boolean> { fun withPermissions(vararg permissions: String): Observable<Boolean> {
val subject = PublishSubject.create<Boolean>() val subject = PublishSubject.create<Boolean>()
return subject.doOnSubscribeUi { PermissionEvent(permissions.toList(), subject).publish() } return subject.doOnSubscribeUi { RxPermissionEvent(permissions.toList(), subject).publish() }
}
fun withPermissions(vararg permissions: String, callback: (Boolean) -> Unit) {
PermissionEvent(permissions.toList(), callback).publish()
}
fun withExternalRW(callback: (Boolean) -> Unit) {
withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, callback = callback)
} }
fun back() = BackPressEvent().publish() fun back() = BackPressEvent().publish()

View File

@ -1,23 +1,17 @@
package com.topjohnwu.magisk.ui.base package com.topjohnwu.magisk.ui.base
import android.os.Handler import android.os.Handler
import android.os.Looper import androidx.core.os.postDelayed
import com.topjohnwu.superuser.internal.UiThreadHandler
interface Queryable { interface Queryable {
val queryDelay: Long val queryDelay: Long
val queryHandler: Handler val queryHandler: Handler get() = UiThreadHandler.handler
val queryRunnable: Runnable
fun submitQuery() fun submitQuery() {
queryHandler.postDelayed(queryDelay) { query() }
}
companion object { fun query()
fun impl(delay: Long = 1000L) = object : Queryable {
override val queryDelay = delay
override val queryHandler = Handler(Looper.getMainLooper())
override val queryRunnable = Runnable { TODO() }
override fun submitQuery() {}
}
}
} }

View File

@ -21,16 +21,16 @@ import com.topjohnwu.magisk.utils.KObservableField
class HideViewModel( class HideViewModel(
private val magiskRepo: MagiskRepository private val magiskRepo: MagiskRepository
) : BaseViewModel(), Queryable by Queryable.impl(1000) { ) : BaseViewModel(), Queryable {
override val queryRunnable = Runnable { query() } override val queryDelay = 1000L
var isShowSystem = false var isShowSystem = false
@Bindable get @Bindable get
set(value) { set(value) {
field = value field = value
notifyPropertyChanged(BR.showSystem) notifyPropertyChanged(BR.showSystem)
query() submitQuery()
} }
var query = "" var query = ""
@ -81,17 +81,9 @@ class HideViewModel(
// --- // ---
override fun submitQuery() { override fun query() = items.filter {
queryHandler.removeCallbacks(queryRunnable)
queryHandler.postDelayed(queryRunnable, queryDelay)
}
private fun query(
query: String = this.query,
showSystem: Boolean = isShowSystem
) = items.filter {
fun filterSystem(): Boolean { fun filterSystem(): Boolean {
return showSystem || it.item.info.info.flags and ApplicationInfo.FLAG_SYSTEM == 0 return isShowSystem || it.item.info.info.flags and ApplicationInfo.FLAG_SYSTEM == 0
} }
fun filterQuery(): Boolean { fun filterQuery(): Boolean {

View File

@ -1,6 +1,5 @@
package com.topjohnwu.magisk.ui.module package com.topjohnwu.magisk.ui.module
import android.Manifest
import androidx.databinding.Bindable import androidx.databinding.Bindable
import androidx.databinding.ObservableArrayList import androidx.databinding.ObservableArrayList
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@ -16,7 +15,6 @@ import com.topjohnwu.magisk.data.database.RepoByUpdatedDao
import com.topjohnwu.magisk.databinding.ComparableRvItem import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.addOnListChangedCallback import com.topjohnwu.magisk.extensions.addOnListChangedCallback
import com.topjohnwu.magisk.extensions.reboot import com.topjohnwu.magisk.extensions.reboot
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.recycler.* import com.topjohnwu.magisk.model.entity.recycler.*
import com.topjohnwu.magisk.model.events.InstallExternalModuleEvent import com.topjohnwu.magisk.model.events.InstallExternalModuleEvent
@ -26,13 +24,9 @@ import com.topjohnwu.magisk.model.events.dialog.ModuleInstallDialog
import com.topjohnwu.magisk.ui.base.* import com.topjohnwu.magisk.ui.base.*
import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener
import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.superuser.internal.UiThreadHandler
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import kotlinx.coroutines.* import kotlinx.coroutines.*
import me.tatarka.bindingcollectionadapter2.collections.MergeObservableList import me.tatarka.bindingcollectionadapter2.collections.MergeObservableList
import java.lang.Runnable
import kotlin.math.roundToInt import kotlin.math.roundToInt
/* /*
@ -53,9 +47,11 @@ class ModuleViewModel(
private val repoName: RepoByNameDao, private val repoName: RepoByNameDao,
private val repoUpdated: RepoByUpdatedDao, private val repoUpdated: RepoByUpdatedDao,
private val repoUpdater: RepoUpdater private val repoUpdater: RepoUpdater
) : BaseViewModel(), Queryable by Queryable.impl(1000) { ) : BaseViewModel(), Queryable {
override val queryRunnable = Runnable { query() } override val queryDelay = 1000L
private var queryJob: Job? = null
private var remoteJob: Job? = null
var query = "" var query = ""
@Bindable get @Bindable get
@ -68,7 +64,6 @@ class ModuleViewModel(
searchLoading.value = true searchLoading.value = true
} }
private var queryJob: Disposable? = null
val searchLoading = KObservableField(false) val searchLoading = KObservableField(false)
val itemsSearch = diffListOf<RepoItem>() val itemsSearch = diffListOf<RepoItem>()
val itemSearchBinding = itemBindingOf<RepoItem> { val itemSearchBinding = itemBindingOf<RepoItem> {
@ -213,16 +208,15 @@ class ModuleViewModel(
state = State.LOADED state = State.LOADED
} }
@Synchronized
fun loadRemote() { fun loadRemote() {
// check for existing jobs // check for existing jobs
if (isRemoteLoading) if (remoteJob?.isActive == true)
return return
if (itemsRemote.isEmpty()) {
EndlessRecyclerScrollListener.ResetState().publish()
}
viewModelScope.launch { if (itemsRemote.isEmpty())
EndlessRecyclerScrollListener.ResetState().publish()
remoteJob = viewModelScope.launch {
suspend fun loadRemoteDB(offset: Int) = withContext(Dispatchers.IO) { suspend fun loadRemoteDB(offset: Int) = withContext(Dispatchers.IO) {
dao.getRepos(offset).map { RepoItem.Remote(it) } dao.getRepos(offset).map { RepoItem.Remote(it) }
} }
@ -236,7 +230,7 @@ class ModuleViewModel(
} }
isRemoteLoading = false isRemoteLoading = false
refetch = false refetch = false
UiThreadHandler.handler.post { itemsRemote.addAll(repos) } queryHandler.post { itemsRemote.addAll(repos) }
} }
} }
@ -250,44 +244,44 @@ class ModuleViewModel(
// --- // ---
override fun submitQuery() { private suspend fun queryInternal(query: String, offset: Int): List<RepoItem> {
queryHandler.removeCallbacks(queryRunnable) return if (query.isBlank()) {
queryHandler.postDelayed(queryRunnable, queryDelay) itemsSearch.clear()
listOf()
} else {
withContext(Dispatchers.IO) {
dao.searchRepos(query, offset).map { RepoItem.Remote(it) }
}
}
} }
private fun queryInternal(query: String, offset: Int): Single<List<RepoItem>> { override fun query() {
if (query.isBlank()) { queryJob = viewModelScope.launch {
return Single.just(listOf<RepoItem>()) val searched = queryInternal(query, 0)
.doOnSubscribe { itemsSearch.clear() } val diff = withContext(Dispatchers.Default) {
.subscribeOn(AndroidSchedulers.mainThread()) itemsSearch.calculateDiff(searched)
}
searchLoading.value = false
itemsSearch.update(searched, diff)
} }
return Single.fromCallable { dao.searchRepos(query, offset) }
.map { it.map { RepoItem.Remote(it) } }
} }
private fun query(query: String = this.query, offset: Int = 0) {
queryJob?.dispose()
queryJob = queryInternal(query, offset)
.map { it to itemsSearch.calculateDiff(it) }
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess { searchLoading.value = false }
.subscribeK { itemsSearch.update(it.first, it.second) }
}
@Synchronized
fun loadMoreQuery() { fun loadMoreQuery() {
if (queryJob?.isDisposed == false) return if (queryJob?.isActive == true) return
queryJob = queryInternal(query, itemsSearch.size) queryJob = viewModelScope.launch {
.subscribeK { itemsSearch.addAll(it) } val searched = queryInternal(query, itemsSearch.size)
queryHandler.post { itemsSearch.addAll(searched) }
}
} }
// --- // ---
private fun update(repo: Repo, progress: Int) = private fun update(repo: Repo, progress: Int) = viewModelScope.launch {
Single.fromCallable { itemsRemote + itemsSearch } val item = withContext(Dispatchers.Default) {
.map { it.first { it.item.id == repo.id } } (itemsRemote + itemsSearch).first { it.item.id == repo.id }
.subscribeK { it.progress.value = progress } }
.add() item.progress.value = progress
}
// --- // ---
@ -305,9 +299,11 @@ class ModuleViewModel(
// --- // ---
fun updateActiveState() = Single.fromCallable { itemsInstalled.any { it.isModified } } fun updateActiveState() = viewModelScope.launch {
.subscribeK { sectionActive.hasButton = it } sectionActive.hasButton = withContext(Dispatchers.Default) {
.add() itemsInstalled.any { it.isModified }
}
}
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
@ -318,28 +314,28 @@ class ModuleViewModel(
else -> Config.Value.ORDER_NAME else -> Config.Value.ORDER_NAME
} }
updateOrderIcon() updateOrderIcon()
Single.fromCallable { itemsRemote } queryHandler.post {
.subscribeK { itemsRemote.clear()
itemsRemote.removeAll(it)
loadRemote() loadRemote()
}.add() }
Unit
} }
else -> Unit else -> Unit
} }
fun downloadPressed(item: RepoItem) = withPermissions( fun downloadPressed(item: RepoItem) = withExternalRW {
Manifest.permission.READ_EXTERNAL_STORAGE, if (it)
Manifest.permission.WRITE_EXTERNAL_STORAGE
).any { it }.subscribeK(onError = { permissionDenied() }) {
ModuleInstallDialog(item.item).publish() ModuleInstallDialog(item.item).publish()
}.add() else
permissionDenied()
}
fun installPressed() = withPermissions( fun installPressed() = withExternalRW {
Manifest.permission.READ_EXTERNAL_STORAGE, if (it)
Manifest.permission.WRITE_EXTERNAL_STORAGE
).any { it }.subscribeK(onError = { permissionDenied() }) {
InstallExternalModuleEvent().publish() InstallExternalModuleEvent().publish()
}.add() else
permissionDenied()
}
fun infoPressed(item: RepoItem) = OpenChangelogEvent(item.item).publish() fun infoPressed(item: RepoItem) = OpenChangelogEvent(item.item).publish()
fun infoPressed(item: ModuleItem) { fun infoPressed(item: ModuleItem) {

View File

@ -16,7 +16,7 @@ import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.model.entity.internal.Configuration import com.topjohnwu.magisk.model.entity.internal.Configuration
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.recycler.SettingsItem import com.topjohnwu.magisk.model.entity.recycler.SettingsItem
import com.topjohnwu.magisk.model.events.PermissionEvent import com.topjohnwu.magisk.model.events.RxPermissionEvent
import com.topjohnwu.magisk.model.events.RecreateEvent import com.topjohnwu.magisk.model.events.RecreateEvent
import com.topjohnwu.magisk.model.events.dialog.BiometricDialog import com.topjohnwu.magisk.model.events.dialog.BiometricDialog
import com.topjohnwu.magisk.ui.base.BaseViewModel import com.topjohnwu.magisk.ui.base.BaseViewModel
@ -137,7 +137,7 @@ class SettingsViewModel(
private fun requireRWPermission() { private fun requireRWPermission() {
val callback = PublishSubject.create<Boolean>() val callback = PublishSubject.create<Boolean>()
callback.subscribeK { if (!it) requireRWPermission() } callback.subscribeK { if (!it) requireRWPermission() }
PermissionEvent( RxPermissionEvent(
listOf( listOf(
Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE Manifest.permission.WRITE_EXTERNAL_STORAGE