Commit 5313a46a authored by topjohnwu's avatar topjohnwu

Overhaul SettingsItem

Close #5021
parent 761a8dde
......@@ -3,7 +3,6 @@ package com.topjohnwu.magisk.ui.settings
import android.content.Context
import android.content.res.Resources
import android.view.View
import androidx.annotation.CallSuper
import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
......@@ -20,76 +19,33 @@ sealed class BaseSettingsItem : ObservableRvItem() {
open val title: TextHolder get() = TextHolder.EMPTY
@get:Bindable
open val description: TextHolder get() = TextHolder.EMPTY
// ---
open val showSwitch get() = false
@get:Bindable
open val isChecked get() = false
open fun onToggle(view: View, callback: Callback, checked: Boolean) {}
// ---
@get:Bindable
var isEnabled = true
set(value) = set(value, field, { field = it }, BR.enabled, BR.description)
open fun onPressed(view: View, callback: Callback) {
callback.onItemPressed(view, this)
open fun onToggle(view: View, handler: Handler, checked: Boolean) {}
open fun onPressed(view: View, handler: Handler) {
handler.onItemPressed(view, this)
}
open fun refresh() {}
// ---
interface Callback {
fun onItemPressed(view: View, item: BaseSettingsItem, callback: () -> Unit = {})
fun onItemChanged(view: View, item: BaseSettingsItem)
interface Handler {
fun onItemPressed(view: View, item: BaseSettingsItem, andThen: () -> Unit = {})
fun onItemAction(view: View, item: BaseSettingsItem)
}
// ---
abstract class Value<T> : BaseSettingsItem() {
/**
* 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_
* 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
/**
* We don't want this to be accessible to be set from outside the instances. It will
* introduce unwanted bugs!
* */
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>() {
......@@ -97,19 +53,25 @@ sealed class BaseSettingsItem : ObservableRvItem() {
override val showSwitch get() = true
override val isChecked get() = value
override fun onToggle(view: View, callback: Callback, checked: Boolean) =
set(checked, value, { onPressed(view, callback) }, BR.checked)
override fun onToggle(view: View, handler: Handler, checked: Boolean) =
set(checked, value, { onPressed(view, handler) })
override fun onPressed(view: View) {
override fun onPressed(view: View, handler: Handler) {
handler.onItemPressed(view, this) {
value = !value
notifyPropertyChanged(BR.checked)
handler.onItemAction(view, this)
}
}
}
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 {
setTitle(title.getText(view.resources))
setView(getView(view.context))
......@@ -119,7 +81,7 @@ sealed class BaseSettingsItem : ObservableRvItem() {
inputResult?.let { result ->
doNotDismiss = false
value = result
it.dismiss()
handler.onItemAction(view, this@Input)
return@onClick
}
doNotDismiss = true
......@@ -130,6 +92,7 @@ sealed class BaseSettingsItem : ObservableRvItem() {
}
}.show()
}
}
abstract fun getView(context: Context): View
}
......@@ -150,18 +113,23 @@ sealed class BaseSettingsItem : ObservableRvItem() {
private fun Resources.getArrayOrEmpty(id: Int): Array<String> =
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 {
setTitle(title.getText(view.resources))
setButton(MagiskDialog.ButtonType.NEGATIVE) {
text = android.R.string.cancel
}
setListItems(entries(view.resources)) {
if (value != it) {
value = it
notifyPropertyChanged(BR.description)
handler.onItemAction(view, this@Selector)
}
}
}.show()
}
}
}
abstract class Blank : BaseSettingsItem()
......
......@@ -26,10 +26,10 @@ import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch
class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Callback {
class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
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()
init {
......@@ -96,27 +96,25 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Callback {
return list
}
override fun onItemPressed(view: View, item: BaseSettingsItem, callback: () -> Unit) {
override fun onItemPressed(view: View, item: BaseSettingsItem, andThen: () -> Unit) {
when (item) {
is DownloadPath -> withExternalRW(callback)
is Biometrics -> authenticate(callback)
is Theme ->
SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate()
is DenyListConfig ->
SettingsFragmentDirections.actionSettingsFragmentToDenyFragment().navigate()
is SystemlessHosts -> createHosts()
is Restore -> RestoreAppDialog().publish()
is AddShortcut -> AddHomeIconEvent().publish()
else -> callback()
DownloadPath -> withExternalRW(andThen)
Biometrics -> authenticate(andThen)
Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate()
DenyListConfig -> SettingsFragmentDirections.actionSettingsFragmentToDenyFragment().navigate()
SystemlessHosts -> createHosts()
Restore -> RestoreAppDialog().publish()
AddShortcut -> AddHomeIconEvent().publish()
else -> andThen()
}
}
override fun onItemChanged(view: View, item: BaseSettingsItem) {
override fun onItemAction(view: View, item: BaseSettingsItem) {
when (item) {
is Language -> RecreateEvent().publish()
is UpdateChannel -> openUrlIfNecessary(view)
Language -> RecreateEvent().publish()
UpdateChannel -> openUrlIfNecessary(view)
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
}
}
......
......@@ -42,7 +42,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textUri"
android:text="@={data.result}"
android:text="@={data.inputResult}"
android:textAppearance="@style/AppearanceFoundation.Body"
android:textColor="?colorOnSurface"
tools:text="@tools:sample/lorem" />
......
......@@ -34,7 +34,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textUri"
android:text="@={data.result}"
android:text="@={data.inputResult}"
android:textAppearance="@style/AppearanceFoundation.Body"
android:textColor="?colorOnSurface"
tools:text="@tools:sample/lorem" />
......
......@@ -10,8 +10,8 @@
type="com.topjohnwu.magisk.ui.settings.BaseSettingsItem" />
<variable
name="callback"
type="com.topjohnwu.magisk.ui.settings.BaseSettingsItem.Callback" />
name="handler"
type="com.topjohnwu.magisk.ui.settings.BaseSettingsItem.Handler" />
</data>
......@@ -23,7 +23,7 @@
android:alpha="@{item.enabled ? 1f : .5f}"
android:clickable="@{item.enabled}"
android:focusable="@{item.enabled}"
android:onClick="@{(view) -> item.onPressed(view, callback)}"
android:onClick="@{(view) -> item.onPressed(view, handler)}"
tools:layout_gravity="center">
<LinearLayout
......@@ -81,7 +81,7 @@
android:layout_height="wrap_content"
android:checked="@{item.checked}"
android:focusable="@{item.enabled}"
android:onCheckedChanged="@{(v, c) -> item.onToggle(v, callback, c)}" />
android:onCheckedChanged="@{(v, c) -> item.onToggle(v, handler, c)}" />
</LinearLayout>
......
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