diff --git a/app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt b/app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt index 1370fe462..7e932c3e2 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt @@ -131,7 +131,7 @@ class OpenChangelogEvent(val item: Repo) : ViewEvent(), ContextExecutor { } } -class PermissionEvent( +class RxPermissionEvent( val permissions: List, val callback: PublishSubject ) : ViewEvent(), ActivityExecutor { @@ -148,6 +148,22 @@ class PermissionEvent( } } +class PermissionEvent( + private val permissions: List, + 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 { override fun invoke(activity: BaseActivity) { activity.onBackPressed() diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/base/BaseViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/base/BaseViewModel.kt index 78b527f8c..f0b906273 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/base/BaseViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/base/BaseViewModel.kt @@ -1,5 +1,6 @@ package com.topjohnwu.magisk.ui.base +import android.Manifest import androidx.annotation.CallSuper import androidx.core.graphics.Insets import androidx.databinding.Bindable @@ -88,7 +89,15 @@ abstract class BaseViewModel( fun withPermissions(vararg permissions: String): Observable { val subject = PublishSubject.create() - 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() diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/base/Queryable.kt b/app/src/main/java/com/topjohnwu/magisk/ui/base/Queryable.kt index c7dc2d267..db64fbb18 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/base/Queryable.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/base/Queryable.kt @@ -1,23 +1,17 @@ package com.topjohnwu.magisk.ui.base import android.os.Handler -import android.os.Looper +import androidx.core.os.postDelayed +import com.topjohnwu.superuser.internal.UiThreadHandler interface Queryable { val queryDelay: Long - val queryHandler: Handler - val queryRunnable: Runnable + val queryHandler: Handler get() = UiThreadHandler.handler - fun submitQuery() - - companion object { - 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() {} - } + fun submitQuery() { + queryHandler.postDelayed(queryDelay) { query() } } + + fun query() } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt index 605041700..6e8869367 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt @@ -21,16 +21,16 @@ import com.topjohnwu.magisk.utils.KObservableField class HideViewModel( private val magiskRepo: MagiskRepository -) : BaseViewModel(), Queryable by Queryable.impl(1000) { +) : BaseViewModel(), Queryable { - override val queryRunnable = Runnable { query() } + override val queryDelay = 1000L var isShowSystem = false @Bindable get set(value) { field = value notifyPropertyChanged(BR.showSystem) - query() + submitQuery() } var query = "" @@ -81,17 +81,9 @@ class HideViewModel( // --- - override fun submitQuery() { - queryHandler.removeCallbacks(queryRunnable) - queryHandler.postDelayed(queryRunnable, queryDelay) - } - - private fun query( - query: String = this.query, - showSystem: Boolean = isShowSystem - ) = items.filter { + override fun query() = items.filter { 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 { diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt index 7ca8a6d7d..3054dccd8 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt @@ -1,6 +1,5 @@ package com.topjohnwu.magisk.ui.module -import android.Manifest import androidx.databinding.Bindable import androidx.databinding.ObservableArrayList 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.extensions.addOnListChangedCallback 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.recycler.* 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.utils.EndlessRecyclerScrollListener 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 kotlinx.coroutines.* import me.tatarka.bindingcollectionadapter2.collections.MergeObservableList -import java.lang.Runnable import kotlin.math.roundToInt /* @@ -53,9 +47,11 @@ class ModuleViewModel( private val repoName: RepoByNameDao, private val repoUpdated: RepoByUpdatedDao, 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 = "" @Bindable get @@ -68,7 +64,6 @@ class ModuleViewModel( searchLoading.value = true } - private var queryJob: Disposable? = null val searchLoading = KObservableField(false) val itemsSearch = diffListOf() val itemSearchBinding = itemBindingOf { @@ -213,16 +208,15 @@ class ModuleViewModel( state = State.LOADED } - @Synchronized fun loadRemote() { // check for existing jobs - if (isRemoteLoading) + if (remoteJob?.isActive == true) 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) { dao.getRepos(offset).map { RepoItem.Remote(it) } } @@ -236,7 +230,7 @@ class ModuleViewModel( } isRemoteLoading = false refetch = false - UiThreadHandler.handler.post { itemsRemote.addAll(repos) } + queryHandler.post { itemsRemote.addAll(repos) } } } @@ -250,44 +244,44 @@ class ModuleViewModel( // --- - override fun submitQuery() { - queryHandler.removeCallbacks(queryRunnable) - queryHandler.postDelayed(queryRunnable, queryDelay) - } - - private fun queryInternal(query: String, offset: Int): Single> { - if (query.isBlank()) { - return Single.just(listOf()) - .doOnSubscribe { itemsSearch.clear() } - .subscribeOn(AndroidSchedulers.mainThread()) + private suspend fun queryInternal(query: String, offset: Int): List { + return if (query.isBlank()) { + itemsSearch.clear() + listOf() + } else { + withContext(Dispatchers.IO) { + dao.searchRepos(query, offset).map { RepoItem.Remote(it) } + } } - 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) } + override fun query() { + queryJob = viewModelScope.launch { + val searched = queryInternal(query, 0) + val diff = withContext(Dispatchers.Default) { + itemsSearch.calculateDiff(searched) + } + searchLoading.value = false + itemsSearch.update(searched, diff) + } } - @Synchronized fun loadMoreQuery() { - if (queryJob?.isDisposed == false) return - queryJob = queryInternal(query, itemsSearch.size) - .subscribeK { itemsSearch.addAll(it) } + if (queryJob?.isActive == true) return + queryJob = viewModelScope.launch { + val searched = queryInternal(query, itemsSearch.size) + queryHandler.post { itemsSearch.addAll(searched) } + } } // --- - private fun update(repo: Repo, progress: Int) = - Single.fromCallable { itemsRemote + itemsSearch } - .map { it.first { it.item.id == repo.id } } - .subscribeK { it.progress.value = progress } - .add() + private fun update(repo: Repo, progress: Int) = viewModelScope.launch { + val item = withContext(Dispatchers.Default) { + (itemsRemote + itemsSearch).first { it.item.id == repo.id } + } + item.progress.value = progress + } // --- @@ -305,12 +299,14 @@ class ModuleViewModel( // --- - fun updateActiveState() = Single.fromCallable { itemsInstalled.any { it.isModified } } - .subscribeK { sectionActive.hasButton = it } - .add() + fun updateActiveState() = viewModelScope.launch { + sectionActive.hasButton = withContext(Dispatchers.Default) { + itemsInstalled.any { it.isModified } + } + } 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 -> { Config.repoOrder = when (Config.repoOrder) { Config.Value.ORDER_NAME -> Config.Value.ORDER_DATE @@ -318,28 +314,28 @@ class ModuleViewModel( else -> Config.Value.ORDER_NAME } updateOrderIcon() - Single.fromCallable { itemsRemote } - .subscribeK { - itemsRemote.removeAll(it) - loadRemote() - }.add() + queryHandler.post { + itemsRemote.clear() + loadRemote() + } + Unit } else -> Unit } - fun downloadPressed(item: RepoItem) = withPermissions( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ).any { it }.subscribeK(onError = { permissionDenied() }) { - ModuleInstallDialog(item.item).publish() - }.add() + fun downloadPressed(item: RepoItem) = withExternalRW { + if (it) + ModuleInstallDialog(item.item).publish() + else + permissionDenied() + } - fun installPressed() = withPermissions( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ).any { it }.subscribeK(onError = { permissionDenied() }) { - InstallExternalModuleEvent().publish() - }.add() + fun installPressed() = withExternalRW { + if (it) + InstallExternalModuleEvent().publish() + else + permissionDenied() + } fun infoPressed(item: RepoItem) = OpenChangelogEvent(item.item).publish() fun infoPressed(item: ModuleItem) { diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt index 77f14b062..b6d15a2e3 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt @@ -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.DownloadSubject 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.dialog.BiometricDialog import com.topjohnwu.magisk.ui.base.BaseViewModel @@ -137,7 +137,7 @@ class SettingsViewModel( private fun requireRWPermission() { val callback = PublishSubject.create() callback.subscribeK { if (!it) requireRWPermission() } - PermissionEvent( + RxPermissionEvent( listOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE