Reduce usage of ObservableField

This commit is contained in:
topjohnwu 2020-07-15 02:52:15 -07:00
parent 6c6368fd81
commit ec2d7d77eb
11 changed files with 130 additions and 148 deletions

View File

@ -1,11 +1,10 @@
package com.topjohnwu.magisk.core
import androidx.databinding.ObservableField
import androidx.databinding.ObservableBoolean
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.core.model.UpdateInfo
import com.topjohnwu.magisk.core.net.NetworkObserver
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ktx.value
import com.topjohnwu.magisk.utils.CachedValue
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils.fastCmd
@ -36,9 +35,9 @@ object Info {
@JvmStatic var ramdisk = false
val isConnected by lazy {
ObservableField(false).also { field ->
ObservableBoolean(false).also { field ->
NetworkObserver.observe(get()) {
UiThreadHandler.run { field.value = it.isAvailable }
UiThreadHandler.run { field.set(it.isAvailable) }
}
}
}

View File

@ -1,65 +0,0 @@
package com.topjohnwu.magisk.ktx
import androidx.databinding.Observable
import androidx.databinding.ObservableBoolean
import androidx.databinding.ObservableField
import androidx.databinding.ObservableInt
fun <T> ObservableField<T>.addOnPropertyChangedCallback(
removeAfterChanged: Boolean = false,
callback: (T?) -> Unit
) {
addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
callback(get())
if (removeAfterChanged) removeOnPropertyChangedCallback(this)
}
})
}
fun ObservableInt.addOnPropertyChangedCallback(
removeAfterChanged: Boolean = false,
callback: (Int) -> Unit
) {
addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
callback(get())
if (removeAfterChanged) removeOnPropertyChangedCallback(this)
}
})
}
fun ObservableBoolean.addOnPropertyChangedCallback(
removeAfterChanged: Boolean = false,
callback: (Boolean) -> Unit
) {
addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
callback(get())
if (removeAfterChanged) removeOnPropertyChangedCallback(this)
}
})
}
inline fun <T> ObservableField<T>.update(block: (T?) -> Unit) {
set(get().apply(block))
}
inline fun <T> ObservableField<T>.updateNonNull(block: (T) -> Unit) {
update {
it ?: return@update
block(it)
}
}
inline fun ObservableInt.update(block: (Int) -> Unit) {
set(get().apply(block))
}
inline var <T> ObservableField<T>.value
get() = get() as T
set(value) {
// Use Kotlin comparision (Any.equals)
if (value != get())
set(value)
}

View File

@ -1,13 +1,7 @@
package com.topjohnwu.magisk.ktx
import androidx.databinding.ObservableField
import androidx.databinding.ObservableList
fun ObservableField<Boolean>.toggle() {
value = !value
}
fun <T> ObservableList<T>.addOnListChangedCallback(
onChanged: ((sender: ObservableList<T>) -> Unit)? = null,
onItemRangeRemoved: ((sender: ObservableList<T>, positionStart: Int, itemCount: Int) -> Unit)? = null,

View File

@ -2,54 +2,57 @@ package com.topjohnwu.magisk.model.entity.recycler
import android.view.View
import android.view.ViewGroup
import androidx.databinding.ObservableField
import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.ktx.addOnPropertyChangedCallback
import com.topjohnwu.magisk.databinding.ObservableItem
import com.topjohnwu.magisk.ktx.startAnimations
import com.topjohnwu.magisk.ktx.toggle
import com.topjohnwu.magisk.ktx.value
import com.topjohnwu.magisk.model.entity.ProcessHideApp
import com.topjohnwu.magisk.model.entity.StatefulProcess
import com.topjohnwu.magisk.model.observer.Observer
import com.topjohnwu.magisk.ui.hide.HideViewModel
import com.topjohnwu.magisk.utils.addOnPropertyChangedCallback
import com.topjohnwu.magisk.utils.set
import kotlin.math.roundToInt
class HideItem(val item: ProcessHideApp) : ComparableRvItem<HideItem>() {
class HideItem(val item: ProcessHideApp) : ObservableItem<HideItem>() {
override val layoutRes = R.layout.item_hide_md2
val packageName = item.info.info.packageName.orEmpty()
val items = item.processes.map { HideProcessItem(it) }
val isExpanded = ObservableField(false)
val itemsChecked = ObservableField(0)
val itemsCheckedPercent = Observer(itemsChecked) {
(itemsChecked.value.toFloat() / items.size * 100).roundToInt()
}
@get:Bindable
var isExpanded = false
set(value) = set(value, field, { field = it }, BR.expanded)
/** [toggle] depends on this functionality */
private val isHidden get() = itemsChecked.value == items.size
@get:Bindable
var itemsChecked = 0
set(value) = set(value, field, { field = it }, BR.itemsChecked, BR.itemsCheckedPercent)
@get:Bindable
val itemsCheckedPercent get() = (itemsChecked.toFloat() / items.size * 100).roundToInt()
private val isHidden get() = itemsChecked == items.size
init {
items.forEach { it.isHidden.addOnPropertyChangedCallback { recalculateChecked() } }
items.forEach { it.addOnPropertyChangedCallback(BR.hidden) { recalculateChecked() } }
recalculateChecked()
}
fun collapse(v: View) {
(v.parent.parent as? ViewGroup)?.startAnimations()
isExpanded.value = false
isExpanded = false
}
fun toggle(v: View) {
(v.parent as? ViewGroup)?.startAnimations()
isExpanded.toggle()
isExpanded = !isExpanded
}
fun toggle(viewModel: HideViewModel): Boolean {
// contract implies that isHidden == all checked
if (!isHidden) {
items.filterNot { it.isHidden.value }
items.filterNot { it.isHidden }
} else {
items
}.forEach { it.toggle(viewModel) }
@ -57,7 +60,7 @@ class HideItem(val item: ProcessHideApp) : ComparableRvItem<HideItem>() {
}
private fun recalculateChecked() {
itemsChecked.value = items.count { it.isHidden.value }
itemsChecked = items.count { it.isHidden }
}
override fun contentSameAs(other: HideItem): Boolean = item == other.item
@ -65,14 +68,17 @@ class HideItem(val item: ProcessHideApp) : ComparableRvItem<HideItem>() {
}
class HideProcessItem(val item: StatefulProcess) : ComparableRvItem<HideProcessItem>() {
class HideProcessItem(val item: StatefulProcess) : ObservableItem<HideProcessItem>() {
override val layoutRes = R.layout.item_hide_process_md2
val isHidden = ObservableField(item.isHidden)
@get:Bindable
var isHidden = item.isHidden
set(value) = set(value, field, { field = it }, BR.hidden)
fun toggle(viewModel: HideViewModel) {
isHidden.toggle()
isHidden = !isHidden
viewModel.toggleItem(this)
}

View File

@ -1,51 +1,62 @@
package com.topjohnwu.magisk.model.entity.recycler
import android.graphics.drawable.Drawable
import androidx.databinding.ObservableField
import androidx.databinding.Bindable
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.model.MagiskPolicy
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.ktx.toggle
import com.topjohnwu.magisk.ktx.value
import com.topjohnwu.magisk.databinding.ObservableItem
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
import com.topjohnwu.magisk.utils.set
class PolicyItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<PolicyItem>() {
class PolicyItem(val item: MagiskPolicy, val icon: Drawable) : ObservableItem<PolicyItem>() {
override val layoutRes = R.layout.item_policy_md2
val isExpanded = ObservableField(false)
val isEnabled = ObservableField(item.policy == MagiskPolicy.ALLOW)
val shouldNotify = ObservableField(item.notification)
val shouldLog = ObservableField(item.logging)
@get:Bindable
var isExpanded = false
set(value) = set(value, field, { field = it }, BR.expanded)
@get:Bindable
var isEnabled = item.policy == MagiskPolicy.ALLOW
set(value) = set(value, field, { field = it }, BR.enabled)
@get:Bindable
var shouldNotify = item.notification
set(value) = set(value, field, { field = it }, BR.shouldNotify)
@get:Bindable
var shouldLog = item.logging
set(value) = set(value, field, { field = it }, BR.shouldLog)
private val updatedPolicy
get() = item.copy(
policy = if (isEnabled.value) MagiskPolicy.ALLOW else MagiskPolicy.DENY,
notification = shouldNotify.value,
logging = shouldLog.value
policy = if (isEnabled) MagiskPolicy.ALLOW else MagiskPolicy.DENY,
notification = shouldNotify,
logging = shouldLog
)
fun toggle(viewModel: SuperuserViewModel) {
if (isExpanded.value) {
if (isExpanded) {
toggle()
return
}
isEnabled.toggle()
viewModel.togglePolicy(this, isEnabled.value)
isEnabled = !isEnabled
viewModel.togglePolicy(this, isEnabled)
}
fun toggle() {
isExpanded.toggle()
isExpanded = !isExpanded
}
fun toggleNotify(viewModel: SuperuserViewModel) {
shouldNotify.toggle()
shouldNotify = !shouldNotify
viewModel.updatePolicy(updatedPolicy, isLogging = false)
}
fun toggleLog(viewModel: SuperuserViewModel) {
shouldLog.toggle()
shouldLog = !shouldLog
viewModel.updatePolicy(updatedPolicy, isLogging = true)
}

View File

@ -6,7 +6,6 @@ import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.ktx.value
import com.topjohnwu.magisk.model.entity.HideAppInfo
import com.topjohnwu.magisk.model.entity.HideTarget
import com.topjohnwu.magisk.model.entity.ProcessHideApp
@ -71,7 +70,7 @@ class HideViewModel(
return ProcessHideApp(a, processes)
}
private fun List<HideItem>.sort() = compareByDescending<HideItem> { it.itemsChecked.value }
private fun List<HideItem>.sort() = compareByDescending<HideItem> { it.itemsChecked }
.thenBy { it.item.info.name.toLowerCase(currentLocale) }
.thenBy { it.item.info.info.packageName }
.let { sortedWith(it) }
@ -96,7 +95,7 @@ class HideViewModel(
// ---
fun toggleItem(item: HideProcessItem) = magiskRepo
.toggleHide(item.isHidden.value, item.item.packageName, item.item.name)
.toggleHide(item.isHidden, item.item.packageName, item.item.name)
fun resetQuery() {
query = ""

View File

@ -15,7 +15,6 @@ import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.ktx.await
import com.topjohnwu.magisk.ktx.packageName
import com.topjohnwu.magisk.ktx.res
import com.topjohnwu.magisk.ktx.value
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.Manager
import com.topjohnwu.magisk.model.entity.recycler.DeveloperItem
import com.topjohnwu.magisk.model.entity.recycler.HomeItem
@ -101,7 +100,7 @@ class HomeViewModel(
}
stateManager = when {
!app.isUpdateChannelCorrect && isConnected.value -> MagiskState.NOT_INSTALLED
!app.isUpdateChannelCorrect && isConnected.get() -> MagiskState.NOT_INSTALLED
app.isObsolete -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE
}

View File

@ -12,7 +12,6 @@ import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.utils.PatchAPK
import com.topjohnwu.magisk.core.utils.Utils
import com.topjohnwu.magisk.data.database.RepoDao
import com.topjohnwu.magisk.ktx.value
import com.topjohnwu.magisk.model.entity.internal.Configuration
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.recycler.SettingsItem
@ -56,7 +55,7 @@ class SettingsViewModel(
))
if (Info.env.isActive) {
list.add(ClearRepoCache)
if (Const.USER_ID == 0 && Info.isConnected.value)
if (Const.USER_ID == 0 && Info.isConnected.get())
list.add(HideOrRestore())
}

View File

@ -11,7 +11,6 @@ import com.topjohnwu.magisk.core.model.MagiskPolicy
import com.topjohnwu.magisk.core.utils.BiometricHelper
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.ktx.toggle
import com.topjohnwu.magisk.model.entity.recycler.PolicyItem
import com.topjohnwu.magisk.model.entity.recycler.TappableHeadlineItem
import com.topjohnwu.magisk.model.entity.recycler.TextItem
@ -140,7 +139,7 @@ class SuperuserViewModel(
if (BiometricHelper.isEnabled) {
BiometricDialog {
onSuccess { updateState() }
onFailure { item.isEnabled.toggle() }
onFailure { item.isEnabled = !item.isEnabled }
}.publish()
} else {
updateState()

View File

@ -49,6 +49,22 @@ interface ObservableHost : Observable {
}
}
fun ObservableHost.addOnPropertyChangedCallback(
fieldId: Int,
removeAfterChanged: Boolean = false,
callback: () -> Unit
) {
addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
if (fieldId == propertyId) {
callback()
if (removeAfterChanged)
removeOnPropertyChangedCallback(this)
}
}
})
}
/**
* Injects boilerplate implementation for {@literal @}[androidx.databinding.Bindable] field setters.
*

View File

@ -15,7 +15,8 @@ import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatDialog
import androidx.core.view.ViewCompat
import androidx.core.view.updatePadding
import androidx.databinding.ObservableField
import androidx.databinding.Bindable
import androidx.databinding.PropertyChangeRegistry
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -23,8 +24,9 @@ import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.databinding.DialogMagiskBaseBinding
import com.topjohnwu.magisk.ktx.value
import com.topjohnwu.magisk.ui.base.itemBindingOf
import com.topjohnwu.magisk.utils.ObservableHost
import com.topjohnwu.magisk.utils.set
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapters
import me.tatarka.bindingcollectionadapter2.ItemBinding
@ -71,11 +73,24 @@ class MagiskDialog(
binding.dialogBaseOutsideContainer.setOnClickListener(listener)
}
inner class Data {
val icon = ObservableField(0)
val iconRaw = ObservableField(null as Drawable?)
val title = ObservableField<CharSequence>("")
val message = ObservableField<CharSequence>("")
inner class Data: ObservableHost {
override var callbacks: PropertyChangeRegistry? = null
@get:Bindable
var icon = 0
set(value) = set(value, field, { field = it }, BR.icon)
@get:Bindable
var iconRaw: Drawable? = null
set(value) = set(value, field, { field = it }, BR.iconRaw)
@get:Bindable
var title: CharSequence = ""
set(value) = set(value, field, { field = it }, BR.title)
@get:Bindable
var message : CharSequence = ""
set(value) = set(value, field, { field = it }, BR.message)
val buttonPositive = Button()
val buttonNeutral = Button()
@ -87,10 +102,20 @@ class MagiskDialog(
POSITIVE, NEUTRAL, NEGATIVE, IDGAF
}
inner class Button {
val icon = ObservableField(0)
val title = ObservableField<CharSequence>("")
val isEnabled = ObservableField(true)
inner class Button: ObservableHost {
override var callbacks: PropertyChangeRegistry? = null
@get:Bindable
var icon = 0
set(value) = set(value, field, { field = it }, BR.icon)
@get:Bindable
var title: CharSequence = ""
set(value) = set(value, field, { field = it }, BR.title)
@get:Bindable
var isEnabled = true
set(value) = set(value, field, { field = it }, BR.enabled)
var onClickAction: OnDialogButtonClickListener = {}
var preventDismiss = false
@ -117,24 +142,24 @@ class MagiskDialog(
inner class ButtonBuilder(private val button: Button) {
var icon: Int
get() = button.icon.value
get() = button.icon
set(value) {
button.icon.value = value
button.icon = value
}
var title: CharSequence
get() = button.title.value
get() = button.title
set(value) {
button.title.value = value
button.title = value
}
var titleRes: Int
get() = 0
set(value) {
button.title.value = context.getString(value)
button.title = context.getString(value)
}
var isEnabled: Boolean
get() = button.isEnabled.value
get() = button.isEnabled
set(value) {
button.isEnabled.value = value
button.isEnabled = value
}
var preventDismiss: Boolean
get() = button.preventDismiss
@ -148,22 +173,22 @@ class MagiskDialog(
}
fun applyTitle(@StringRes stringRes: Int) =
apply { data.title.value = context.getString(stringRes) }
apply { data.title = context.getString(stringRes) }
fun applyTitle(title: CharSequence) =
apply { data.title.value = title }
apply { data.title = title }
fun applyMessage(@StringRes stringRes: Int, vararg args: Any) =
apply { data.message.value = context.getString(stringRes, *args) }
apply { data.message = context.getString(stringRes, *args) }
fun applyMessage(message: CharSequence) =
apply { data.message.value = message }
apply { data.message = message }
fun applyIcon(@DrawableRes drawableRes: Int) =
apply { data.icon.value = drawableRes }
apply { data.icon = drawableRes }
fun applyIcon(drawable: Drawable) =
apply { data.iconRaw.value = drawable }
apply { data.iconRaw = drawable }
fun applyButton(buttonType: ButtonType, builder: ButtonBuilder.() -> Unit) = apply {
val button = when (buttonType) {