Commit ec2d7d77 authored by topjohnwu's avatar topjohnwu

Reduce usage of ObservableField

parent 6c6368fd
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) }
}
}
}
......
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)
}
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,
......
......@@ -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)
@get:Bindable
var itemsChecked = 0
set(value) = set(value, field, { field = it }, BR.itemsChecked, BR.itemsCheckedPercent)
/** [toggle] depends on this functionality */
private val isHidden get() = itemsChecked.value == items.size
@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)
}
......
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)
}
......
......@@ -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 = ""
......
......@@ -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
}
......
......@@ -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())
}
......
......@@ -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()
......
......@@ -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.
*
......
......@@ -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) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment