Overhaul SettingsItem

Close #5021
This commit is contained in:
topjohnwu 2022-01-22 05:25:36 -08:00
parent 761a8dde65
commit 5313a46aa2
6 changed files with 163 additions and 218 deletions

View File

@ -3,7 +3,6 @@ package com.topjohnwu.magisk.ui.settings
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.view.View import android.view.View
import androidx.annotation.CallSuper
import androidx.databinding.Bindable import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
@ -20,76 +19,33 @@ sealed class BaseSettingsItem : ObservableRvItem() {
open val title: TextHolder get() = TextHolder.EMPTY open val title: TextHolder get() = TextHolder.EMPTY
@get:Bindable @get:Bindable
open val description: TextHolder get() = TextHolder.EMPTY open val description: TextHolder get() = TextHolder.EMPTY
// ---
open val showSwitch get() = false open val showSwitch get() = false
@get:Bindable @get:Bindable
open val isChecked get() = false open val isChecked get() = false
open fun onToggle(view: View, callback: Callback, checked: Boolean) {}
// ---
@get:Bindable @get:Bindable
var isEnabled = true var isEnabled = true
set(value) = set(value, field, { field = it }, BR.enabled, BR.description) set(value) = set(value, field, { field = it }, BR.enabled, BR.description)
open fun onPressed(view: View, callback: Callback) { open fun onToggle(view: View, handler: Handler, checked: Boolean) {}
callback.onItemPressed(view, this) open fun onPressed(view: View, handler: Handler) {
handler.onItemPressed(view, this)
} }
open fun refresh() {} open fun refresh() {}
// --- interface Handler {
fun onItemPressed(view: View, item: BaseSettingsItem, andThen: () -> Unit = {})
interface Callback { fun onItemAction(view: View, item: BaseSettingsItem)
fun onItemPressed(view: View, item: BaseSettingsItem, callback: () -> Unit = {})
fun onItemChanged(view: View, item: BaseSettingsItem)
} }
// ---
abstract class Value<T> : BaseSettingsItem() { abstract class Value<T> : BaseSettingsItem() {
/** /**
* Represents last agreed-upon value by the validation process and the user for current * Represents last agreed-upon value by the validation process and the user for current
* child. Be very aware that this shouldn't be **set** unless both sides agreed that _that_ * child. Be very aware that this shouldn't be **set** unless both sides agreed that _that_
* is the new value. * is the new value.
*
* Annotating [value] as [Bindable] property should raise red flags immediately. If you
* need a [Bindable] property create another one. Seriously.
* */ * */
abstract var value: T abstract var value: T
/**
* We don't want this to be accessible to be set from outside the instances. It will
* introduce unwanted bugs!
* */
protected set protected set
protected var callbackVars: Pair<View, Callback>? = null
@CallSuper
override fun onPressed(view: View, callback: Callback) {
callbackVars = view to callback
callback.onItemPressed(view, this) {
onPressed(view)
}
}
abstract fun onPressed(view: View)
protected inline fun <reified T> setV(
new: T, old: T, setter: (T) -> Unit, afterChanged: (T) -> Unit = {}) {
set(new, old, setter, BR.description, BR.checked) {
afterChanged(it)
callbackVars?.let { (view, callback) ->
callbackVars = null
callback.onItemChanged(view, this)
}
}
}
} }
abstract class Toggle : Value<Boolean>() { abstract class Toggle : Value<Boolean>() {
@ -97,19 +53,25 @@ sealed class BaseSettingsItem : ObservableRvItem() {
override val showSwitch get() = true override val showSwitch get() = true
override val isChecked get() = value override val isChecked get() = value
override fun onToggle(view: View, callback: Callback, checked: Boolean) = override fun onToggle(view: View, handler: Handler, checked: Boolean) =
set(checked, value, { onPressed(view, callback) }, BR.checked) set(checked, value, { onPressed(view, handler) })
override fun onPressed(view: View) { override fun onPressed(view: View, handler: Handler) {
handler.onItemPressed(view, this) {
value = !value value = !value
notifyPropertyChanged(BR.checked)
handler.onItemAction(view, this)
}
} }
} }
abstract class Input : Value<String>() { abstract class Input : Value<String>() {
protected abstract val inputResult: String? @get:Bindable
abstract val inputResult: String?
override fun onPressed(view: View) { override fun onPressed(view: View, handler: Handler) {
handler.onItemPressed(view, this) {
MagiskDialog(view.context).apply { MagiskDialog(view.context).apply {
setTitle(title.getText(view.resources)) setTitle(title.getText(view.resources))
setView(getView(view.context)) setView(getView(view.context))
@ -119,7 +81,7 @@ sealed class BaseSettingsItem : ObservableRvItem() {
inputResult?.let { result -> inputResult?.let { result ->
doNotDismiss = false doNotDismiss = false
value = result value = result
it.dismiss() handler.onItemAction(view, this@Input)
return@onClick return@onClick
} }
doNotDismiss = true doNotDismiss = true
@ -130,6 +92,7 @@ sealed class BaseSettingsItem : ObservableRvItem() {
} }
}.show() }.show()
} }
}
abstract fun getView(context: Context): View abstract fun getView(context: Context): View
} }
@ -150,18 +113,23 @@ sealed class BaseSettingsItem : ObservableRvItem() {
private fun Resources.getArrayOrEmpty(id: Int): Array<String> = private fun Resources.getArrayOrEmpty(id: Int): Array<String> =
runCatching { getStringArray(id) }.getOrDefault(emptyArray()) runCatching { getStringArray(id) }.getOrDefault(emptyArray())
override fun onPressed(view: View) { override fun onPressed(view: View, handler: Handler) {
handler.onItemPressed(view, this) {
MagiskDialog(view.context).apply { MagiskDialog(view.context).apply {
setTitle(title.getText(view.resources)) setTitle(title.getText(view.resources))
setButton(MagiskDialog.ButtonType.NEGATIVE) { setButton(MagiskDialog.ButtonType.NEGATIVE) {
text = android.R.string.cancel text = android.R.string.cancel
} }
setListItems(entries(view.resources)) { setListItems(entries(view.resources)) {
if (value != it) {
value = it value = it
notifyPropertyChanged(BR.description)
handler.onItemAction(view, this@Selector)
}
} }
}.show() }.show()
} }
}
} }
abstract class Blank : BaseSettingsItem() abstract class Blank : BaseSettingsItem()

View File

@ -37,8 +37,9 @@ object Customization : BaseSettingsItem.Section() {
object Language : BaseSettingsItem.Selector() { object Language : BaseSettingsItem.Selector() {
override var value = -1 override var value = -1
set(value) = setV(value, field, { field = it }) { set(value) {
Config.locale = entryValues[it] field = value
Config.locale = entryValues[value]
} }
override val title = R.string.language.asText() override val title = R.string.language.asText()
@ -49,9 +50,9 @@ object Language : BaseSettingsItem.Selector() {
override fun entries(res: Resources) = entries override fun entries(res: Resources) = entries
override fun descriptions(res: Resources) = entries override fun descriptions(res: Resources) = entries
override fun onPressed(view: View, callback: Callback) { override fun onPressed(view: View, handler: Handler) {
if (entries.isEmpty()) return if (entries.isNotEmpty())
super.onPressed(view, callback) super.onPressed(view, handler)
} }
suspend fun loadLanguages(scope: CoroutineScope) { suspend fun loadLanguages(scope: CoroutineScope) {
@ -80,9 +81,7 @@ object AppSettings : BaseSettingsItem.Section() {
object Hide : BaseSettingsItem.Input() { object Hide : BaseSettingsItem.Input() {
override val title = R.string.settings_hide_app_title.asText() override val title = R.string.settings_hide_app_title.asText()
override val description = R.string.settings_hide_app_summary.asText() override val description = R.string.settings_hide_app_summary.asText()
override var value = "" override var value = ""
set(value) = setV(value, field, { field = it })
override val inputResult override val inputResult
get() = if (isError) null else result get() = if (isError) null else result
@ -113,30 +112,31 @@ object AddShortcut : BaseSettingsItem.Blank() {
} }
object DownloadPath : BaseSettingsItem.Input() { object DownloadPath : BaseSettingsItem.Input() {
override var value = Config.downloadDir override var value
set(value) = setV(value, field, { field = it }) { Config.downloadDir = it } get() = Config.downloadDir
set(value) {
Config.downloadDir = value
notifyPropertyChanged(BR.description)
}
override val title = R.string.settings_download_path_title.asText() override val title = R.string.settings_download_path_title.asText()
override val description get() = path.asText() override val description get() = MediaStoreUtils.fullPath(value).asText()
override val inputResult: String get() = result override var inputResult: String = value
set(value) = set(value, field, { field = it }, BR.inputResult, BR.path)
@get:Bindable @get:Bindable
var result = value val path get() = MediaStoreUtils.fullPath(inputResult)
set(value) = set(value, field, { field = it }, BR.result, BR.path)
@get:Bindable
val path
get() = MediaStoreUtils.fullPath(result)
override fun getView(context: Context) = DialogSettingsDownloadPathBinding override fun getView(context: Context) = DialogSettingsDownloadPathBinding
.inflate(LayoutInflater.from(context)).also { it.data = this }.root .inflate(LayoutInflater.from(context)).also { it.data = this }.root
} }
object UpdateChannel : BaseSettingsItem.Selector() { object UpdateChannel : BaseSettingsItem.Selector() {
override var value = Config.updateChannel.let { if (it < 0) 0 else it } override var value
set(value) = setV(value, field, { field = it }) { get() = Config.updateChannel
Config.updateChannel = it set(value) {
Config.updateChannel = value
Info.remote = Info.EMPTY_REMOTE Info.remote = Info.EMPTY_REMOTE
} }
@ -154,18 +154,17 @@ object UpdateChannel : BaseSettingsItem.Selector() {
object UpdateChannelUrl : BaseSettingsItem.Input() { object UpdateChannelUrl : BaseSettingsItem.Input() {
override val title = R.string.settings_update_custom.asText() override val title = R.string.settings_update_custom.asText()
override var value = Config.customChannelUrl
set(value) = setV(value, field, { field = it }) {
Config.customChannelUrl = it
Info.remote = Info.EMPTY_REMOTE
}
override val description get() = value.asText() override val description get() = value.asText()
override var value
get() = Config.customChannelUrl
set(value) {
Config.customChannelUrl = value
Info.remote = Info.EMPTY_REMOTE
notifyPropertyChanged(BR.description)
}
override val inputResult get() = result override var inputResult: String = value
set(value) = set(value, field, { field = it }, BR.inputResult)
@get:Bindable
var result = value
set(value) = set(value, field, { field = it }, BR.result)
override fun refresh() { override fun refresh() {
isEnabled = UpdateChannel.value == Config.Value.CUSTOM_CHANNEL isEnabled = UpdateChannel.value == Config.Value.CUSTOM_CHANNEL
@ -178,9 +177,10 @@ object UpdateChannelUrl : BaseSettingsItem.Input() {
object UpdateChecker : BaseSettingsItem.Toggle() { object UpdateChecker : BaseSettingsItem.Toggle() {
override val title = R.string.settings_check_update_title.asText() override val title = R.string.settings_check_update_title.asText()
override val description = R.string.settings_check_update_summary.asText() override val description = R.string.settings_check_update_summary.asText()
override var value = Config.checkUpdate override var value
set(value) = setV(value, field, { field = it }) { get() = Config.checkUpdate
Config.checkUpdate = it set(value) {
Config.checkUpdate = value
JobService.schedule(AppContext) JobService.schedule(AppContext)
} }
} }
@ -188,51 +188,14 @@ object UpdateChecker : BaseSettingsItem.Toggle() {
object DoHToggle : BaseSettingsItem.Toggle() { object DoHToggle : BaseSettingsItem.Toggle() {
override val title = R.string.settings_doh_title.asText() override val title = R.string.settings_doh_title.asText()
override val description = R.string.settings_doh_description.asText() override val description = R.string.settings_doh_description.asText()
override var value = Config.doh override var value by Config::doh
set(value) = setV(value, field, { field = it }) {
Config.doh = it
}
} }
// check whether is module already installed beforehand?
object SystemlessHosts : BaseSettingsItem.Blank() { object SystemlessHosts : BaseSettingsItem.Blank() {
override val title = R.string.settings_hosts_title.asText() override val title = R.string.settings_hosts_title.asText()
override val description = R.string.settings_hosts_summary.asText() override val description = R.string.settings_hosts_summary.asText()
} }
object Tapjack : BaseSettingsItem.Toggle() {
override val title = R.string.settings_su_tapjack_title.asText()
override var description = R.string.settings_su_tapjack_summary.asText()
override var value = Config.suTapjack
set(value) = setV(value, field, { field = it }) { Config.suTapjack = it }
}
object Biometrics : BaseSettingsItem.Toggle() {
override val title = R.string.settings_su_biometric_title.asText()
override var value = Config.suBiometric
set(value) = setV(value, field, { field = it }) { Config.suBiometric = it }
override var description = R.string.settings_su_biometric_summary.asText()
override fun refresh() {
isEnabled = BiometricHelper.isSupported
if (!isEnabled) {
value = false
description = R.string.no_biometric.asText()
}
}
}
object Reauthenticate : BaseSettingsItem.Toggle() {
override val title = R.string.settings_su_reauth_title.asText()
override val description = R.string.settings_su_reauth_summary.asText()
override var value = Config.suReAuth
set(value) = setV(value, field, { field = it }) { Config.suReAuth = it }
override fun refresh() {
isEnabled = Build.VERSION.SDK_INT < Build.VERSION_CODES.O && Utils.showSuperUser()
}
}
// --- Magisk // --- Magisk
object Magisk : BaseSettingsItem.Section() { object Magisk : BaseSettingsItem.Section() {
@ -244,11 +207,13 @@ object Zygisk : BaseSettingsItem.Toggle() {
override val description get() = override val description get() =
if (mismatch) R.string.reboot_apply_change.asText() if (mismatch) R.string.reboot_apply_change.asText()
else R.string.settings_zygisk_summary.asText() else R.string.settings_zygisk_summary.asText()
override var value = Config.zygisk override var value
set(value) = setV(value, field, { field = it }) { get() = Config.zygisk
Config.zygisk = it set(value) {
DenyList.isEnabled = it Config.zygisk = value
DenyListConfig.isEnabled = it DenyList.isEnabled = value
DenyListConfig.isEnabled = value
notifyPropertyChanged(BR.description)
DenyList.notifyPropertyChanged(BR.description) DenyList.notifyPropertyChanged(BR.description)
} }
val mismatch get() = value != Info.isZygiskEnabled val mismatch get() = value != Info.isZygiskEnabled
@ -267,11 +232,16 @@ object DenyList : BaseSettingsItem.Toggle() {
} }
override var value = Config.denyList override var value = Config.denyList
set(value) = setV(value, field, { field = it }) { set(value) {
val cmd = if (it) "enable" else "disable" field = value
val cmd = if (value) "enable" else "disable"
Shell.su("magisk --denylist $cmd").submit { result -> Shell.su("magisk --denylist $cmd").submit { result ->
if (result.isSuccess) Config.denyList = it if (result.isSuccess) {
else field = !it Config.denyList = value
} else {
field = !value
notifyPropertyChanged(BR.checked)
}
} }
} }
@ -290,6 +260,27 @@ object DenyListConfig : BaseSettingsItem.Blank() {
// --- Superuser // --- Superuser
object Tapjack : BaseSettingsItem.Toggle() {
override val title = R.string.settings_su_tapjack_title.asText()
override val description = R.string.settings_su_tapjack_summary.asText()
override var value by Config::suTapjack
}
object Biometrics : BaseSettingsItem.Toggle() {
override val title = R.string.settings_su_biometric_title.asText()
override var description = R.string.settings_su_biometric_summary.asText()
override var value by Config::suBiometric
override fun refresh() {
isEnabled = BiometricHelper.isSupported
if (!isEnabled) {
value = false
description = R.string.no_biometric.asText()
notifyPropertyChanged(BR.checked)
}
}
}
object Superuser : BaseSettingsItem.Section() { object Superuser : BaseSettingsItem.Section() {
override val title = R.string.superuser.asText() override val title = R.string.superuser.asText()
} }
@ -297,22 +288,14 @@ object Superuser : BaseSettingsItem.Section() {
object AccessMode : BaseSettingsItem.Selector() { object AccessMode : BaseSettingsItem.Selector() {
override val title = R.string.superuser_access.asText() override val title = R.string.superuser_access.asText()
override val entryRes = R.array.su_access override val entryRes = R.array.su_access
override var value by Config::rootMode
override var value = Config.rootMode
set(value) = setV(value, field, { field = it }) {
Config.rootMode = it
}
} }
object MultiuserMode : BaseSettingsItem.Selector() { object MultiuserMode : BaseSettingsItem.Selector() {
override val title = R.string.multiuser_mode.asText() override val title = R.string.multiuser_mode.asText()
override val entryRes = R.array.multiuser_mode override val entryRes = R.array.multiuser_mode
override val descriptionRes = R.array.multiuser_summary override val descriptionRes = R.array.multiuser_summary
override var value by Config::suMultiuserMode
override var value = Config.suMultiuserMode
set(value) = setV(value, field, { field = it }) {
Config.suMultiuserMode = it
}
override fun refresh() { override fun refresh() {
isEnabled = Const.USER_ID == 0 isEnabled = Const.USER_ID == 0
@ -323,21 +306,13 @@ object MountNamespaceMode : BaseSettingsItem.Selector() {
override val title = R.string.mount_namespace_mode.asText() override val title = R.string.mount_namespace_mode.asText()
override val entryRes = R.array.namespace override val entryRes = R.array.namespace
override val descriptionRes = R.array.namespace_summary override val descriptionRes = R.array.namespace_summary
override var value by Config::suMntNamespaceMode
override var value = Config.suMntNamespaceMode
set(value) = setV(value, field, { field = it }) {
Config.suMntNamespaceMode = it
}
} }
object AutomaticResponse : BaseSettingsItem.Selector() { object AutomaticResponse : BaseSettingsItem.Selector() {
override val title = R.string.auto_response.asText() override val title = R.string.auto_response.asText()
override val entryRes = R.array.auto_response override val entryRes = R.array.auto_response
override var value by Config::suAutoResponse
override var value = Config.suAutoResponse
set(value) = setV(value, field, { field = it }) {
Config.suAutoResponse = it
}
} }
object RequestTimeout : BaseSettingsItem.Selector() { object RequestTimeout : BaseSettingsItem.Selector() {
@ -345,21 +320,25 @@ object RequestTimeout : BaseSettingsItem.Selector() {
override val entryRes = R.array.request_timeout override val entryRes = R.array.request_timeout
private val entryValues = listOf(10, 15, 20, 30, 45, 60) private val entryValues = listOf(10, 15, 20, 30, 45, 60)
override var value = selected override var value = entryValues.indexOfFirst { it == Config.suDefaultTimeout }
set(value) = setV(value, field, { field = it }) { set(value) {
Config.suDefaultTimeout = entryValues[it] field = value
Config.suDefaultTimeout = entryValues[value]
} }
private val selected: Int
get() = entryValues.indexOfFirst { it == Config.suDefaultTimeout }
} }
object SUNotification : BaseSettingsItem.Selector() { object SUNotification : BaseSettingsItem.Selector() {
override val title = R.string.superuser_notification.asText() override val title = R.string.superuser_notification.asText()
override val entryRes = R.array.su_notification override val entryRes = R.array.su_notification
override var value by Config::suNotification
}
override var value = Config.suNotification object Reauthenticate : BaseSettingsItem.Toggle() {
set(value) = setV(value, field, { field = it }) { override val title = R.string.settings_su_reauth_title.asText()
Config.suNotification = it override val description = R.string.settings_su_reauth_summary.asText()
override var value by Config::suReAuth
override fun refresh() {
isEnabled = Build.VERSION.SDK_INT < Build.VERSION_CODES.O && Utils.showSuperUser()
} }
} }

View File

@ -26,10 +26,10 @@ import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Callback { class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
val adapter = adapterOf<BaseSettingsItem>() val adapter = adapterOf<BaseSettingsItem>()
val itemBinding = itemBindingOf<BaseSettingsItem> { it.bindExtra(BR.callback, this) } val itemBinding = itemBindingOf<BaseSettingsItem> { it.bindExtra(BR.handler, this) }
val items = createItems() val items = createItems()
init { init {
@ -96,27 +96,25 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Callback {
return list return list
} }
override fun onItemPressed(view: View, item: BaseSettingsItem, callback: () -> Unit) { override fun onItemPressed(view: View, item: BaseSettingsItem, andThen: () -> Unit) {
when (item) { when (item) {
is DownloadPath -> withExternalRW(callback) DownloadPath -> withExternalRW(andThen)
is Biometrics -> authenticate(callback) Biometrics -> authenticate(andThen)
is Theme -> Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate()
SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate() DenyListConfig -> SettingsFragmentDirections.actionSettingsFragmentToDenyFragment().navigate()
is DenyListConfig -> SystemlessHosts -> createHosts()
SettingsFragmentDirections.actionSettingsFragmentToDenyFragment().navigate() Restore -> RestoreAppDialog().publish()
is SystemlessHosts -> createHosts() AddShortcut -> AddHomeIconEvent().publish()
is Restore -> RestoreAppDialog().publish() else -> andThen()
is AddShortcut -> AddHomeIconEvent().publish()
else -> callback()
} }
} }
override fun onItemChanged(view: View, item: BaseSettingsItem) { override fun onItemAction(view: View, item: BaseSettingsItem) {
when (item) { when (item) {
is Language -> RecreateEvent().publish() Language -> RecreateEvent().publish()
is UpdateChannel -> openUrlIfNecessary(view) UpdateChannel -> openUrlIfNecessary(view)
is Hide -> viewModelScope.launch { HideAPK.hide(view.activity, item.value) } is Hide -> viewModelScope.launch { HideAPK.hide(view.activity, item.value) }
is Zygisk -> if (Zygisk.mismatch) SnackbarEvent(R.string.reboot_apply_change).publish() Zygisk -> if (Zygisk.mismatch) SnackbarEvent(R.string.reboot_apply_change).publish()
else -> Unit else -> Unit
} }
} }

View File

@ -42,7 +42,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="textUri" android:inputType="textUri"
android:text="@={data.result}" android:text="@={data.inputResult}"
android:textAppearance="@style/AppearanceFoundation.Body" android:textAppearance="@style/AppearanceFoundation.Body"
android:textColor="?colorOnSurface" android:textColor="?colorOnSurface"
tools:text="@tools:sample/lorem" /> tools:text="@tools:sample/lorem" />

View File

@ -34,7 +34,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="textUri" android:inputType="textUri"
android:text="@={data.result}" android:text="@={data.inputResult}"
android:textAppearance="@style/AppearanceFoundation.Body" android:textAppearance="@style/AppearanceFoundation.Body"
android:textColor="?colorOnSurface" android:textColor="?colorOnSurface"
tools:text="@tools:sample/lorem" /> tools:text="@tools:sample/lorem" />

View File

@ -10,8 +10,8 @@
type="com.topjohnwu.magisk.ui.settings.BaseSettingsItem" /> type="com.topjohnwu.magisk.ui.settings.BaseSettingsItem" />
<variable <variable
name="callback" name="handler"
type="com.topjohnwu.magisk.ui.settings.BaseSettingsItem.Callback" /> type="com.topjohnwu.magisk.ui.settings.BaseSettingsItem.Handler" />
</data> </data>
@ -23,7 +23,7 @@
android:alpha="@{item.enabled ? 1f : .5f}" android:alpha="@{item.enabled ? 1f : .5f}"
android:clickable="@{item.enabled}" android:clickable="@{item.enabled}"
android:focusable="@{item.enabled}" android:focusable="@{item.enabled}"
android:onClick="@{(view) -> item.onPressed(view, callback)}" android:onClick="@{(view) -> item.onPressed(view, handler)}"
tools:layout_gravity="center"> tools:layout_gravity="center">
<LinearLayout <LinearLayout
@ -81,7 +81,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:checked="@{item.checked}" android:checked="@{item.checked}"
android:focusable="@{item.enabled}" android:focusable="@{item.enabled}"
android:onCheckedChanged="@{(v, c) -> item.onToggle(v, callback, c)}" /> android:onCheckedChanged="@{(v, c) -> item.onToggle(v, handler, c)}" />
</LinearLayout> </LinearLayout>