Decouple state from BaseViewModel

This commit is contained in:
topjohnwu 2022-06-10 02:13:25 -07:00
parent aabc36f86b
commit 46d4708386
13 changed files with 57 additions and 113 deletions

View File

@ -4,17 +4,14 @@ import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.annotation.SuppressLint
import android.os.Bundle
import androidx.databinding.Bindable
import androidx.databinding.PropertyChangeRegistry
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ObservableHost
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.events.BackPressEvent
import com.topjohnwu.magisk.events.NavigationEvent
import com.topjohnwu.magisk.events.PermissionEvent
@ -25,22 +22,8 @@ abstract class BaseViewModel : ViewModel(), ObservableHost {
override var callbacks: PropertyChangeRegistry? = null
enum class State {
LOADED, LOADING, LOADING_FAILED
}
@get:Bindable
val loading get() = state == State.LOADING
@get:Bindable
val loaded get() = state == State.LOADED
@get:Bindable
val loadFailed get() = state == State.LOADING_FAILED
val viewEvents: LiveData<ViewEvent> get() = _viewEvents
var state = State.LOADING
set(value) = set(value, field, { field = it }, BR.loading, BR.loaded, BR.loadFailed)
private val _viewEvents = MutableLiveData<ViewEvent>()
private var runningJob: Job? = null

View File

@ -2,14 +2,15 @@ package com.topjohnwu.magisk.ui.deny
import android.annotation.SuppressLint
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
import androidx.databinding.Bindable
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.filterableListOf
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.ktx.concurrentMap
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.asFlow
@ -43,13 +44,13 @@ class DenyListViewModel : BaseViewModel() {
it.put(BR.viewModel, this)
}
@get:Bindable
var loading = true
private set(value) = set(value, field, { field = it }, BR.loading)
@SuppressLint("InlinedApi")
override fun refresh() = viewModelScope.launch {
if (!Utils.showSuperUser()) {
state = State.LOADING_FAILED
return@launch
}
state = State.LOADING
loading = true
val (apps, diff) = withContext(Dispatchers.Default) {
val pm = AppContext.packageManager
val denyList = Shell.cmd("magisk --denylist ls").exec().out
@ -84,6 +85,6 @@ class DenyListViewModel : BaseViewModel() {
(it.isChecked || (filterSystem() && filterOS())) && filterQuery()
}
state = State.LOADED
loading = false
}
}

View File

@ -35,7 +35,7 @@ class FlashFragment : BaseFragment<FragmentFlashMd2Binding>() {
setHasOptionsMenu(true)
activity?.setTitle(R.string.flash_screen_title)
viewModel.flashState.observe(this) {
viewModel.state.observe(this) {
activity?.supportActionBar?.setSubtitle(
when (it) {
FlashViewModel.State.FLASHING -> R.string.flashing

View File

@ -32,9 +32,9 @@ class FlashViewModel : BaseViewModel() {
FLASHING, SUCCESS, FAILED
}
private val _flashState = MutableLiveData(State.FLASHING)
val flashState: LiveData<State> get() = _flashState
val flashing = Transformations.map(flashState) { it == State.FLASHING }
private val _state = MutableLiveData(State.FLASHING)
val state: LiveData<State> get() = _state
val flashing = Transformations.map(state) { it == State.FLASHING }
@get:Bindable
var showReboot = Info.isRooted
@ -89,7 +89,7 @@ class FlashViewModel : BaseViewModel() {
}
private fun onResult(success: Boolean) {
_flashState.value = if (success) State.SUCCESS else State.FAILED
_state.value = if (success) State.SUCCESS else State.FAILED
}
fun onMenuItemClicked(item: MenuItem): Boolean {

View File

@ -26,14 +26,14 @@ import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
enum class MagiskState {
NOT_INSTALLED, UP_TO_DATE, OBSOLETE, LOADING
}
class HomeViewModel(
private val svc: NetworkService
) : BaseViewModel() {
enum class State {
LOADING, INVALID, OUTDATED, UP_TO_DATE
}
val magiskTitleBarrierIds =
intArrayOf(R.id.home_magisk_icon, R.id.home_magisk_title, R.id.home_magisk_button)
val appTitleBarrierIds =
@ -43,16 +43,16 @@ class HomeViewModel(
var isNoticeVisible = Config.safetyNotice
set(value) = set(value, field, { field = it }, BR.noticeVisible)
val stateMagisk
val magiskState
get() = when {
!Info.env.isActive -> MagiskState.NOT_INSTALLED
Info.env.versionCode < BuildConfig.VERSION_CODE -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE
!Info.env.isActive -> State.INVALID
Info.env.versionCode < BuildConfig.VERSION_CODE -> State.OUTDATED
else -> State.UP_TO_DATE
}
@get:Bindable
var stateManager = MagiskState.LOADING
set(value) = set(value, field, { field = it }, BR.stateManager)
var appState = State.LOADING
set(value) = set(value, field, { field = it }, BR.appState)
val magiskInstalledVersion
get() = Info.env.run {
@ -84,13 +84,11 @@ class HomeViewModel(
}
override fun refresh() = viewModelScope.launch {
state = State.LOADING
appState = State.LOADING
Info.getRemote(svc)?.apply {
state = State.LOADED
stateManager = when {
BuildConfig.VERSION_CODE < magisk.versionCode -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE
appState = when {
BuildConfig.VERSION_CODE < magisk.versionCode -> State.OUTDATED
else -> State.UP_TO_DATE
}
val isDebug = Config.updateChannel == Config.Value.DEBUG_CHANNEL
@ -98,7 +96,6 @@ class HomeViewModel(
("${magisk.version} (${magisk.versionCode}) (${stub.versionCode})" +
if (isDebug) " (D)" else "").asText()
} ?: run {
state = State.LOADING_FAILED
managerRemoteVersion = R.string.not_available.asText()
}
ensureEnv()
@ -119,14 +116,14 @@ class HomeViewModel(
fun onDeletePressed() = UninstallDialog().publish()
fun onManagerPressed() = when (state) {
State.LOADED -> withExternalRW {
fun onManagerPressed() = when (magiskState) {
State.LOADING -> SnackbarEvent(R.string.loading).publish()
State.INVALID -> SnackbarEvent(R.string.no_connection).publish()
else -> withExternalRW {
withInstallPermission {
ManagerInstallDialog().publish()
}
}
State.LOADING -> SnackbarEvent(R.string.loading).publish()
else -> SnackbarEvent(R.string.no_connection).publish()
}
fun onMagiskPressed() = withExternalRW {
@ -139,7 +136,7 @@ class HomeViewModel(
}
private suspend fun ensureEnv() {
if (MagiskState.NOT_INSTALLED == stateMagisk || checkedEnv) return
if (magiskState == State.INVALID || checkedEnv) return
val cmd = "env_check ${Info.env.versionString} ${Info.env.versionCode}"
if (!Shell.cmd(cmd).await().isSuccess) {
EnvFixDialog(this).publish()

View File

@ -95,7 +95,6 @@ class InstallViewModel(
R.id.method_inactive_slot -> FlashFragment.flash(true).navigate(true)
else -> error("Unknown value")
}
state = State.LOADING
}
override fun onSaveState(state: Bundle) {

View File

@ -1,6 +1,7 @@
package com.topjohnwu.magisk.ui.module
import android.net.Uri
import androidx.databinding.Bindable
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
@ -10,10 +11,7 @@ import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.base.ContentResultCallback
import com.topjohnwu.magisk.core.model.module.LocalModule
import com.topjohnwu.magisk.core.model.module.OnlineModule
import com.topjohnwu.magisk.databinding.MergeObservableList
import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.diffListOf
import com.topjohnwu.magisk.databinding.*
import com.topjohnwu.magisk.events.GetContentEvent
import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.events.dialog.ModuleInstallDialog
@ -36,6 +34,10 @@ class ModuleViewModel : BaseViewModel() {
val data get() = uri
@get:Bindable
var loading = true
private set(value) = set(value, field, { field = it }, BR.loading)
init {
if (Info.env.isActive && LocalModule.loaded()) {
items.insertItem(InstallModule)
@ -45,9 +47,9 @@ class ModuleViewModel : BaseViewModel() {
override fun refresh(): Job {
return viewModelScope.launch {
state = State.LOADING
loading = true
loadInstalled()
state = State.LOADED
loading = false
loadUpdateInfo()
}
}

View File

@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
import android.os.Process
import androidx.databinding.Bindable
import androidx.databinding.ObservableArrayList
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
@ -14,10 +15,7 @@ import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.core.utils.BiometricHelper
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.databinding.AnyDiffRvItem
import com.topjohnwu.magisk.databinding.MergeObservableList
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.diffListOf
import com.topjohnwu.magisk.databinding.*
import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.events.dialog.BiometricEvent
import com.topjohnwu.magisk.events.dialog.SuperuserRevokeDialog
@ -45,15 +43,17 @@ class SuperuserViewModel(
it.put(BR.listener, this)
}
// ---
@get:Bindable
var loading = true
private set(value) = set(value, field, { field = it }, BR.loading)
@SuppressLint("InlinedApi")
override fun refresh() = viewModelScope.launch {
if (!Utils.showSuperUser()) {
state = State.LOADING_FAILED
loading = false
return@launch
}
state = State.LOADING
loading = true
val (policies, diff) = withContext(Dispatchers.IO) {
db.deleteOutdated()
db.delete(AppContext.applicationInfo.uid)
@ -98,7 +98,7 @@ class SuperuserViewModel(
itemsHelpers.clear()
else if (itemsHelpers.isEmpty())
itemsHelpers.add(itemNoData)
state = State.LOADED
loading = false
}
// ---

View File

@ -24,7 +24,7 @@
android:paddingTop="@dimen/internal_action_bar_size"
app:fitsSystemWindowsInsets="top|bottom"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:invisibleUnless="@{viewModel.loaded}"
app:invisible="@{viewModel.loading}"
app:items="@{viewModel.items}"
app:extraBindings="@{viewModel.extraBindings}"
tools:listitem="@layout/item_hide_md2"
@ -52,24 +52,6 @@
</LinearLayout>
<LinearLayout
goneUnless="@{viewModel.loadFailed}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
tools:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/not_available"
android:textAppearance="@style/AppearanceFoundation.Title"
android:textStyle="bold" />
</LinearLayout>
</FrameLayout>
</layout>

View File

@ -7,8 +7,6 @@
<import type="com.topjohnwu.magisk.core.Info" />
<import type="com.topjohnwu.magisk.ui.home.MagiskState" />
<import type="com.topjohnwu.magisk.ui.home.DeveloperItem" />
<import type="com.topjohnwu.magisk.ui.home.IconLink" />

View File

@ -19,7 +19,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/superuser_list"
goneUnless="@{viewModel.loaded || !viewModel.items.empty}"
gone="@{viewModel.loading}"
app:items="@{viewModel.items}"
app:extraBindings="@{viewModel.extraBindings}"
android:layout_width="match_parent"
@ -33,7 +33,7 @@
tools:listitem="@layout/item_policy_md2" />
<LinearLayout
goneUnless="@{viewModel.loading &amp;&amp; viewModel.items.empty}"
goneUnless="@{viewModel.loading}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
@ -54,24 +54,6 @@
</LinearLayout>
<LinearLayout
goneUnless="@{viewModel.loadFailed}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
tools:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/not_available"
android:textAppearance="@style/AppearanceFoundation.Title"
android:textStyle="bold" />
</LinearLayout>
</FrameLayout>
</layout>

View File

@ -7,7 +7,7 @@
<import type="com.topjohnwu.magisk.core.Info" />
<import type="com.topjohnwu.magisk.ui.home.MagiskState" />
<import type="com.topjohnwu.magisk.ui.home.HomeViewModel.State" />
<variable
name="viewModel"
@ -63,7 +63,7 @@
<Button
style="@style/WidgetFoundation.Button"
gone="@{viewModel.stateMagisk != MagiskState.OBSOLETE}"
gone="@{viewModel.magiskState != State.OUTDATED}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.onMagiskPressed()}"
@ -74,7 +74,7 @@
<Button
style="@style/WidgetFoundation.Button.Text"
gone="@{viewModel.stateMagisk == MagiskState.OBSOLETE}"
gone="@{viewModel.magiskState == State.OUTDATED}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"

View File

@ -7,7 +7,7 @@
<import type="com.topjohnwu.magisk.core.Info" />
<import type="com.topjohnwu.magisk.ui.home.MagiskState" />
<import type="com.topjohnwu.magisk.ui.home.HomeViewModel.State" />
<variable
name="viewModel"
@ -64,7 +64,7 @@
<Button
style="@style/WidgetFoundation.Button"
gone="@{viewModel.stateManager != MagiskState.OBSOLETE}"
gone="@{viewModel.appState != State.OUTDATED}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.onManagerPressed()}"
@ -75,7 +75,7 @@
<Button
style="@style/WidgetFoundation.Button.Text"
gone="@{viewModel.stateManager != MagiskState.UP_TO_DATE}"
gone="@{viewModel.appState != State.UP_TO_DATE}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"