Updated settings to level functionality with the legacy

This commit is contained in:
Viktor De Pasquale 2019-12-02 18:35:48 +01:00
parent 89da45f9ac
commit 3efea47ca8
7 changed files with 254 additions and 26 deletions

View File

@ -23,7 +23,7 @@ val redesignModule = module {
viewModel { ModuleViewModel(get(), get(), get()) } viewModel { ModuleViewModel(get(), get(), get()) }
viewModel { RequestViewModel() } viewModel { RequestViewModel() }
viewModel { SafetynetViewModel(get()) } viewModel { SafetynetViewModel(get()) }
viewModel { SettingsViewModel() } viewModel { SettingsViewModel(get()) }
viewModel { SuperuserViewModel(get(), get(), get()) } viewModel { SuperuserViewModel(get(), get(), get()) }
viewModel { ThemeViewModel() } viewModel { ThemeViewModel() }
viewModel { InstallViewModel() } viewModel { InstallViewModel() }

View File

@ -19,4 +19,9 @@ class SettingsFragment : CompatFragment<SettingsViewModel, FragmentSettingsMd2Bi
activity.title = resources.getString(R.string.section_settings) activity.title = resources.getString(R.string.section_settings)
} }
override fun onResume() {
super.onResume()
viewModel.items.forEach { it.refresh() }
}
} }

View File

@ -1,21 +1,19 @@
package com.topjohnwu.magisk.redesign.settings package com.topjohnwu.magisk.redesign.settings
import android.content.Context import android.content.Context
import android.os.Build
import android.os.Environment import android.os.Environment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.Toast
import androidx.databinding.Bindable import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.databinding.DialogSettingsAppNameBinding
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding import com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding
import com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding import com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding
import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.*
import com.topjohnwu.magisk.utils.asTransitive import com.topjohnwu.superuser.Shell
import com.topjohnwu.magisk.utils.availableLocales
import com.topjohnwu.magisk.utils.currentLocale
import java.io.File import java.io.File
// --- Customization // --- Customization
@ -27,6 +25,7 @@ object Customization : SettingsItem.Section() {
object Language : SettingsItem.Selector() { object Language : SettingsItem.Selector() {
override var value by dataObservable(0) { override var value by dataObservable(0) {
Config.locale = entryValues.getOrNull(it)?.toString() ?: return@dataObservable Config.locale = entryValues.getOrNull(it)?.toString() ?: return@dataObservable
refreshLocale()
} }
override val title = R.string.language.asTransitive() override val title = R.string.language.asTransitive()
@ -61,11 +60,34 @@ object Manager : SettingsItem.Section() {
object ClearRepoCache : SettingsItem.Blank() { object ClearRepoCache : SettingsItem.Blank() {
override val title = R.string.settings_clear_cache_title.asTransitive() override val title = R.string.settings_clear_cache_title.asTransitive()
override val description = R.string.settings_clear_cache_summary.asTransitive() override val description = R.string.settings_clear_cache_summary.asTransitive()
override fun refresh() {
isEnabled = Info.env.isActive
}
} }
object Hide : SettingsItem.Blank() { object Hide : SettingsItem.Input() {
override val title = R.string.settings_hide_manager_title.asTransitive() override val title = R.string.settings_hide_manager_title.asTransitive()
override val description = R.string.settings_hide_manager_summary.asTransitive() override val description = R.string.settings_hide_manager_summary.asTransitive()
override val showStrip = false
override var value: String = resources.getString(R.string.re_app_name)
set(value) {
field = value
notifyChange(BR.value)
notifyChange(BR.error)
}
val isError
@Bindable get() = value.length > 14 || value.isBlank()
override val intermediate: String?
get() = if (isError) null else value
override fun getView(context: Context) = DialogSettingsAppNameBinding
.inflate(LayoutInflater.from(context)).also { it.data = this }.root
override fun refresh() {
isEnabled = Info.env.isActive
}
} }
object Restore : SettingsItem.Blank() { object Restore : SettingsItem.Blank() {
@ -129,11 +151,7 @@ object UpdateChannelUrl : SettingsItem.Input() {
notifyChange(BR.result) notifyChange(BR.result)
} }
init { override fun refresh() {
updateState()
}
fun updateState() {
isEnabled = UpdateChannel.value == Config.Value.CUSTOM_CHANNEL isEnabled = UpdateChannel.value == Config.Value.CUSTOM_CHANNEL
} }
@ -144,19 +162,43 @@ object UpdateChannelUrl : SettingsItem.Input() {
object UpdateChecker : SettingsItem.Toggle() { object UpdateChecker : SettingsItem.Toggle() {
override val title = R.string.settings_check_update_title.asTransitive() override val title = R.string.settings_check_update_title.asTransitive()
override val description = R.string.settings_check_update_summary.asTransitive() override val description = R.string.settings_check_update_summary.asTransitive()
override var value by dataObservable(Config.checkUpdate) { Config.checkUpdate = it } override var value by dataObservable(Config.checkUpdate) {
Config.checkUpdate = it
Utils.scheduleUpdateCheck(get())
}
} }
// check whether is module already installed beforehand? // check whether is module already installed beforehand?
object SystemlessHosts : SettingsItem.Blank() { object SystemlessHosts : SettingsItem.Blank() {
override val title = R.string.settings_hosts_title.asTransitive() override val title = R.string.settings_hosts_title.asTransitive()
override val description = R.string.settings_hosts_summary.asTransitive() override val description = R.string.settings_hosts_summary.asTransitive()
override fun refresh() {
isEnabled = Info.env.isActive
}
} }
object Biometrics : SettingsItem.Toggle() { object Biometrics : SettingsItem.Toggle() {
override val title = R.string.settings_su_biometric_title.asTransitive() override val title = R.string.settings_su_biometric_title.asTransitive()
override val description = R.string.settings_su_biometric_summary.asTransitive() override val description = R.string.settings_su_biometric_summary.asTransitive()
override var value by dataObservable(Config.suBiometric) { Config.suBiometric = it } override var value by dataObservable(Config.suBiometric) { Config.suBiometric = it }
override fun refresh() {
isEnabled = BiometricHelper.isSupported && Utils.showSuperUser()
if (!isEnabled) {
value = false
}
}
}
object Reauthenticate : SettingsItem.Toggle() {
override val title = R.string.settings_su_reauth_title.asTransitive()
override val description = R.string.settings_su_reauth_summary.asTransitive()
override var value by dataObservable(Config.suReAuth) { Config.suReAuth = it }
override fun refresh() {
isEnabled = Build.VERSION.SDK_INT < Build.VERSION_CODES.O && Utils.showSuperUser()
}
} }
// --- Magisk // --- Magisk
@ -168,13 +210,35 @@ object Magisk : SettingsItem.Section() {
object SafeMode : SettingsItem.Toggle() { object SafeMode : SettingsItem.Toggle() {
override val title = R.string.settings_core_only_title.asTransitive() override val title = R.string.settings_core_only_title.asTransitive()
override val description = R.string.settings_core_only_summary.asTransitive() override val description = R.string.settings_core_only_summary.asTransitive()
override var value by dataObservable(Config.coreOnly) { Config.coreOnly = it } override var value by dataObservable(Config.coreOnly) {
if (Config.coreOnly == it) return@dataObservable
Config.coreOnly = it
when {
it -> runCatching { Const.MAGISK_DISABLE_FILE.createNewFile() }
else -> Const.MAGISK_DISABLE_FILE.delete()
}
Utils.toast(R.string.settings_reboot_toast, Toast.LENGTH_LONG)
}
override fun refresh() {
isEnabled = Info.env.isActive
}
} }
object MagiskHide : SettingsItem.Toggle() { object MagiskHide : SettingsItem.Toggle() {
override val title = R.string.magiskhide.asTransitive() override val title = R.string.magiskhide.asTransitive()
override val description = R.string.settings_magiskhide_summary.asTransitive() override val description = R.string.settings_magiskhide_summary.asTransitive()
override var value by dataObservable(Config.magiskHide) { Config.magiskHide = it } override var value by dataObservable(Config.magiskHide) {
Config.magiskHide = it
when {
it -> Shell.su("magiskhide --enable").submit()
else -> Shell.su("magiskhide --disable").submit()
}
}
override fun refresh() {
isEnabled = Info.env.isActive
}
} }
// --- Superuser // --- Superuser
@ -197,6 +261,10 @@ object AccessMode : SettingsItem.Selector() {
resources.getStringArray(R.array.value_array) resources.getStringArray(R.array.value_array)
) )
} }
override fun refresh() {
isEnabled = Utils.showSuperUser()
}
} }
object MultiuserMode : SettingsItem.Selector() { object MultiuserMode : SettingsItem.Selector() {
@ -213,6 +281,10 @@ object MultiuserMode : SettingsItem.Selector() {
resources.getStringArray(R.array.value_array) resources.getStringArray(R.array.value_array)
) )
} }
override fun refresh() {
isEnabled = Const.USER_ID <= 0 && Utils.showSuperUser()
}
} }
object MountNamespaceMode : SettingsItem.Selector() { object MountNamespaceMode : SettingsItem.Selector() {
@ -229,6 +301,10 @@ object MountNamespaceMode : SettingsItem.Selector() {
resources.getStringArray(R.array.value_array) resources.getStringArray(R.array.value_array)
) )
} }
override fun refresh() {
isEnabled = Utils.showSuperUser()
}
} }
object AutomaticResponse : SettingsItem.Selector() { object AutomaticResponse : SettingsItem.Selector() {
@ -245,6 +321,10 @@ object AutomaticResponse : SettingsItem.Selector() {
resources.getStringArray(R.array.value_array) resources.getStringArray(R.array.value_array)
) )
} }
override fun refresh() {
isEnabled = Utils.showSuperUser()
}
} }
object RequestTimeout : SettingsItem.Selector() { object RequestTimeout : SettingsItem.Selector() {
@ -263,6 +343,10 @@ object RequestTimeout : SettingsItem.Selector() {
val currentValue = Config.suDefaultTimeout.toString() val currentValue = Config.suDefaultTimeout.toString()
value = entryValues.indexOfFirst { it == currentValue } value = entryValues.indexOfFirst { it == currentValue }
} }
override fun refresh() {
isEnabled = Utils.showSuperUser()
}
} }
object SUNotification : SettingsItem.Selector() { object SUNotification : SettingsItem.Selector() {
@ -279,4 +363,8 @@ object SUNotification : SettingsItem.Selector() {
resources.getStringArray(R.array.value_array) resources.getStringArray(R.array.value_array)
) )
} }
override fun refresh() {
isEnabled = Utils.showSuperUser()
}
} }

View File

@ -1,30 +1,47 @@
package com.topjohnwu.magisk.redesign.settings package com.topjohnwu.magisk.redesign.settings
import android.Manifest
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.widget.Toast
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.databinding.Bindable import androidx.databinding.Bindable
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.StaggeredGridLayoutManager import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.database.RepoDao
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.model.entity.internal.Configuration
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.recycler.ObservableItem import com.topjohnwu.magisk.model.entity.recycler.ObservableItem
import com.topjohnwu.magisk.model.events.DieEvent import com.topjohnwu.magisk.model.events.DieEvent
import com.topjohnwu.magisk.model.events.PermissionEvent
import com.topjohnwu.magisk.model.events.RecreateEvent
import com.topjohnwu.magisk.model.events.dialog.BiometricDialog
import com.topjohnwu.magisk.model.navigation.Navigation import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.redesign.compat.CompatViewModel import com.topjohnwu.magisk.redesign.compat.CompatViewModel
import com.topjohnwu.magisk.redesign.home.itemBindingOf import com.topjohnwu.magisk.redesign.home.itemBindingOf
import com.topjohnwu.magisk.redesign.module.adapterOf import com.topjohnwu.magisk.redesign.module.adapterOf
import com.topjohnwu.magisk.redesign.superuser.diffListOf import com.topjohnwu.magisk.redesign.superuser.diffListOf
import com.topjohnwu.magisk.utils.PatchAPK
import com.topjohnwu.magisk.utils.TransitiveText import com.topjohnwu.magisk.utils.TransitiveText
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.superuser.Shell
import io.reactivex.Completable
import io.reactivex.subjects.PublishSubject
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
import org.koin.core.get import org.koin.core.get
import kotlin.properties.ObservableProperty import kotlin.properties.ObservableProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
class SettingsViewModel : CompatViewModel(), SettingsItem.Callback { class SettingsViewModel(
private val repositoryDao: RepoDao
) : CompatViewModel(), SettingsItem.Callback {
val adapter = adapterOf<SettingsItem>() val adapter = adapterOf<SettingsItem>()
val itemBinding = itemBindingOf<SettingsItem> { it.bindExtra(BR.callback, this) } val itemBinding = itemBindingOf<SettingsItem> { it.bindExtra(BR.callback, this) }
@ -34,10 +51,10 @@ class SettingsViewModel : CompatViewModel(), SettingsItem.Callback {
Manager, Manager,
UpdateChannel, UpdateChannelUrl, ClearRepoCache, HideOrRestore(), UpdateChecker, UpdateChannel, UpdateChannelUrl, ClearRepoCache, HideOrRestore(), UpdateChecker,
SystemlessHosts, Biometrics, Biometrics, Reauthenticate,
Magisk, Magisk,
SafeMode, MagiskHide, SafeMode, MagiskHide, SystemlessHosts,
Superuser, Superuser,
AccessMode, MultiuserMode, MountNamespaceMode, AutomaticResponse, RequestTimeout, AccessMode, MultiuserMode, MountNamespaceMode, AutomaticResponse, RequestTimeout,
@ -45,20 +62,74 @@ class SettingsViewModel : CompatViewModel(), SettingsItem.Callback {
) )
override fun onItemPressed(view: View, item: SettingsItem) = when (item) { override fun onItemPressed(view: View, item: SettingsItem) = when (item) {
is DownloadPath -> requireRWPermission()
else -> Unit
}
override fun onItemChanged(view: View, item: SettingsItem) = when (item) {
// use only instances you want, don't declare everything // use only instances you want, don't declare everything
is Theme -> Navigation.theme().publish() is Theme -> Navigation.theme().publish()
is Redesign -> DieEvent().publish() is Redesign -> DieEvent().publish()
is Language -> RecreateEvent().publish()
is UpdateChannel -> openUrlIfNecessary(view) is UpdateChannel -> openUrlIfNecessary(view)
is Biometrics -> authenticateOrRevert()
is ClearRepoCache -> clearRepoCache()
is SystemlessHosts -> createHosts()
is Hide -> updateManager(hide = true)
is Restore -> updateManager(hide = false)
else -> Unit else -> Unit
} }
private fun openUrlIfNecessary(view: View) { private fun openUrlIfNecessary(view: View) {
UpdateChannelUrl.updateState() UpdateChannelUrl.refresh()
if (UpdateChannelUrl.value.isBlank()) { if (UpdateChannelUrl.value.isBlank()) {
UpdateChannelUrl.onPressed(view, this@SettingsViewModel) UpdateChannelUrl.onPressed(view, this@SettingsViewModel)
} }
} }
private fun authenticateOrRevert() {
// immediately revert the preference
Biometrics.value = !Biometrics.value
BiometricDialog {
// allow the change on success
onSuccess { Biometrics.value = !Biometrics.value }
}.publish()
}
private fun clearRepoCache() {
Completable.fromAction { repositoryDao.clear() }
.subscribeK { Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT) }
}
private fun createHosts() {
Shell.su("add_hosts_module").submit {
Utils.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)
}
}
private fun requireRWPermission() {
val callback = PublishSubject.create<Boolean>()
callback.subscribeK { if (!it) requireRWPermission() }
PermissionEvent(
listOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
), callback
).publish()
}
private fun updateManager(hide: Boolean) {
if (hide) {
PatchAPK.hideManager(get(), Hide.value)
} else {
DownloadService(get()) {
subject = DownloadSubject.Manager(Configuration.APK.Restore)
}
}
}
} }
sealed class SettingsItem : ObservableItem<SettingsItem>() { sealed class SettingsItem : ObservableItem<SettingsItem>() {
@ -81,7 +152,7 @@ sealed class SettingsItem : ObservableItem<SettingsItem>() {
@CallSuper @CallSuper
open fun onPressed(view: View, callback: Callback) { open fun onPressed(view: View, callback: Callback) {
callback.onItemPressed(view, this) callback.onItemChanged(view, this)
// notify only after the callback invocation; callback can invalidate the backing data, // notify only after the callback invocation; callback can invalidate the backing data,
// which wouldn't be recognized with reverse approach // which wouldn't be recognized with reverse approach
@ -90,6 +161,8 @@ sealed class SettingsItem : ObservableItem<SettingsItem>() {
notifyChange(BR.description) notifyChange(BR.description)
} }
open fun refresh() {}
override fun onBindingBound(binding: ViewDataBinding) { override fun onBindingBound(binding: ViewDataBinding) {
super.onBindingBound(binding) super.onBindingBound(binding)
if (isFullSpan) { if (isFullSpan) {
@ -105,6 +178,7 @@ sealed class SettingsItem : ObservableItem<SettingsItem>() {
interface Callback { interface Callback {
fun onItemPressed(view: View, item: SettingsItem) fun onItemPressed(view: View, item: SettingsItem)
fun onItemChanged(view: View, item: SettingsItem)
} }
// --- // ---
@ -131,6 +205,7 @@ sealed class SettingsItem : ObservableItem<SettingsItem>() {
override val layoutRes = R.layout.item_settings_toggle override val layoutRes = R.layout.item_settings_toggle
override fun onPressed(view: View, callback: Callback) { override fun onPressed(view: View, callback: Callback) {
callback.onItemPressed(view, this)
value = !value value = !value
super.onPressed(view, callback) super.onPressed(view, callback)
} }
@ -147,11 +222,13 @@ sealed class SettingsItem : ObservableItem<SettingsItem>() {
abstract class Input : Value<String>(), KoinComponent { abstract class Input : Value<String>(), KoinComponent {
override val layoutRes = R.layout.item_settings_input override val layoutRes = R.layout.item_settings_input
open val showStrip = true
protected val resources get() = get<Resources>() protected val resources get() = get<Resources>()
protected abstract val intermediate: String? protected abstract val intermediate: String?
override fun onPressed(view: View, callback: Callback) { override fun onPressed(view: View, callback: Callback) {
callback.onItemPressed(view, this)
MagiskDialog(view.context) MagiskDialog(view.context)
.applyTitle(title.getText(resources)) .applyTitle(title.getText(resources))
.applyView(getView(view.context)) .applyView(getView(view.context))
@ -210,6 +287,7 @@ sealed class SettingsItem : ObservableItem<SettingsItem>() {
override fun onPressed(view: View, callback: Callback) { override fun onPressed(view: View, callback: Callback) {
if (entries.isEmpty() || entryValues.isEmpty()) return if (entries.isEmpty() || entryValues.isEmpty()) return
callback.onItemPressed(view, this)
MagiskDialog(view.context) MagiskDialog(view.context)
.applyTitle(title.getText(resources)) .applyTitle(title.getText(resources))
.applyButton(MagiskDialog.ButtonType.NEGATIVE) { .applyButton(MagiskDialog.ButtonType.NEGATIVE) {
@ -229,6 +307,11 @@ sealed class SettingsItem : ObservableItem<SettingsItem>() {
override val layoutRes = R.layout.item_settings_blank override val layoutRes = R.layout.item_settings_blank
override fun onPressed(view: View, callback: Callback) {
callback.onItemPressed(view, this)
super.onPressed(view, callback)
}
} }
abstract class Section : SettingsItem() { abstract class Section : SettingsItem() {

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="data"
type="com.topjohnwu.magisk.redesign.settings.Hide" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="@dimen/margin_generic">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_generic"
android:hint="@string/settings_app_name_hint"
app:boxStrokeColor="?colorOnSurfaceVariant"
app:counterEnabled="true"
app:counterMaxLength="14"
app:counterOverflowTextColor="@color/colorError"
app:error="@{data.error ? @string/settings_app_name_error : @string/empty}"
app:errorEnabled="true"
app:errorTextColor="?colorError"
app:helperText="@string/settings_app_name_helper"
app:hintEnabled="true"
app:hintTextAppearance="@style/AppearanceFoundation.Tiny"
app:hintTextColor="?colorOnSurfaceVariant">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/dialog_custom_download_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords"
android:text="@={data.value}"
android:textAppearance="@style/AppearanceFoundation.Body"
android:textColor="?colorOnSurface"
tools:text="@tools:sample/lorem" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</layout>

View File

@ -21,7 +21,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:alpha="@{item.enabled ? 1f : .5f}" android:alpha="@{item.enabled ? 1f : .5f}"
android:onClick="@{(view) -> callback.onItemPressed(view, item)}" android:onClick="@{(view) -> callback.onItemChanged(view, item)}"
tools:layout_gravity="center"> tools:layout_gravity="center">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout

View File

@ -92,7 +92,7 @@
<View <View
android:id="@+id/input_divider" android:id="@+id/input_divider"
gone="@{item.value.empty}" gone="@{item.value.empty || !item.showStrip}"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:alpha=".5" android:alpha=".5"
@ -102,7 +102,7 @@
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/input_selection" android:id="@+id/input_selection"
gone="@{item.value.empty}" gone="@{item.value.empty || !item.showStrip}"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="@dimen/l1" android:paddingStart="@dimen/l1"