diff --git a/app/src/main/java/com/topjohnwu/magisk/core/Info.kt b/app/src/main/java/com/topjohnwu/magisk/core/Info.kt index 57aa98b3c..04761b936 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/Info.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/Info.kt @@ -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) } } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ktx/DataBinding.kt b/app/src/main/java/com/topjohnwu/magisk/ktx/DataBinding.kt deleted file mode 100644 index 812918e43..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/ktx/DataBinding.kt +++ /dev/null @@ -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 ObservableField.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 ObservableField.update(block: (T?) -> Unit) { - set(get().apply(block)) -} - -inline fun ObservableField.updateNonNull(block: (T) -> Unit) { - update { - it ?: return@update - block(it) - } -} - -inline fun ObservableInt.update(block: (Int) -> Unit) { - set(get().apply(block)) -} - -inline var ObservableField.value - get() = get() as T - set(value) { - // Use Kotlin comparision (Any.equals) - if (value != get()) - set(value) - } diff --git a/app/src/main/java/com/topjohnwu/magisk/ktx/XBinding.kt b/app/src/main/java/com/topjohnwu/magisk/ktx/XBinding.kt index 59bbf6128..49467cad1 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ktx/XBinding.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ktx/XBinding.kt @@ -1,13 +1,7 @@ package com.topjohnwu.magisk.ktx -import androidx.databinding.ObservableField import androidx.databinding.ObservableList - -fun ObservableField.toggle() { - value = !value -} - fun ObservableList.addOnListChangedCallback( onChanged: ((sender: ObservableList) -> Unit)? = null, onItemRangeRemoved: ((sender: ObservableList, positionStart: Int, itemCount: Int) -> Unit)? = null, diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/HideRvItem.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/HideRvItem.kt index 24c13030f..cc5189fe1 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/HideRvItem.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/HideRvItem.kt @@ -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() { +class HideItem(val item: ProcessHideApp) : ObservableItem() { 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() { } 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() { } -class HideProcessItem(val item: StatefulProcess) : ComparableRvItem() { +class HideProcessItem(val item: StatefulProcess) : ObservableItem() { 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) } diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/PolicyRvItem.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/PolicyRvItem.kt index dd0594bd1..dbe007880 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/PolicyRvItem.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/PolicyRvItem.kt @@ -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() { +class PolicyItem(val item: MagiskPolicy, val icon: Drawable) : ObservableItem() { 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) } 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 ba9d23f40..4db35a6ac 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 @@ -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.sort() = compareByDescending { it.itemsChecked.value } + private fun List.sort() = compareByDescending { 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 = "" diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt index afd6f90ff..def26b3fb 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt @@ -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 } 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 9ca99eaf0..9d5e781c0 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 @@ -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()) } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt index e8759f05f..1b2b6b993 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt @@ -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() diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/ObservableHost.kt b/app/src/main/java/com/topjohnwu/magisk/utils/ObservableHost.kt index d0da419fe..1e1349ce8 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/ObservableHost.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/ObservableHost.kt @@ -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. * diff --git a/app/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt b/app/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt index d8963b603..5bc3fd3e0 100644 --- a/app/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt +++ b/app/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt @@ -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("") - val message = ObservableField("") + 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("") - 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) {