Magisk/app/src/main/java/com/topjohnwu/magisk/ui/settings/BaseSettingsItem.kt

177 lines
5.6 KiB
Kotlin

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
import com.topjohnwu.magisk.databinding.ObservableItem
import com.topjohnwu.magisk.utils.TextHolder
import com.topjohnwu.magisk.utils.set
import com.topjohnwu.magisk.view.MagiskDialog
sealed class BaseSettingsItem : ObservableItem<BaseSettingsItem>() {
override val layoutRes get() = R.layout.item_settings
open val icon: Int get() = 0
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)
open fun onPressed(view: View, callback: Callback) {
callback.onItemPressed(view, this)
}
open fun refresh() {}
override fun itemSameAs(other: BaseSettingsItem) = this === other
override fun contentSameAs(other: BaseSettingsItem) = itemSameAs(other)
// ---
interface Callback {
fun onItemPressed(view: View, item: BaseSettingsItem, callback: () -> Unit = {})
fun onItemChanged(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>() {
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 onPressed(view: View) {
value = !value
}
}
abstract class Input : Value<String>() {
protected abstract val inputResult: String?
override fun onPressed(view: View) {
MagiskDialog(view.context)
.applyTitle(title.getText(view.resources))
.applyView(getView(view.context))
.applyButton(MagiskDialog.ButtonType.POSITIVE) {
titleRes = android.R.string.ok
onClick {
inputResult?.let { result ->
preventDismiss = false
value = result
it.dismiss()
return@onClick
}
preventDismiss = true
}
}
.applyButton(MagiskDialog.ButtonType.NEGATIVE) {
titleRes = android.R.string.cancel
}
.reveal()
}
abstract fun getView(context: Context): View
}
abstract class Selector : Value<Int>() {
open val entryRes get() = -1
open val descriptionRes get() = entryRes
open fun entries(res: Resources) = res.getArrayOrEmpty(entryRes)
open fun descriptions(res: Resources) = res.getArrayOrEmpty(descriptionRes)
override val description = object : TextHolder() {
override fun getText(resources: Resources): CharSequence {
return descriptions(resources).getOrElse(value) { "" }
}
}
private fun Resources.getArrayOrEmpty(id: Int): Array<String> =
runCatching { getStringArray(id) }.getOrDefault(emptyArray())
override fun onPressed(view: View) {
MagiskDialog(view.context)
.applyTitle(title.getText(view.resources))
.applyButton(MagiskDialog.ButtonType.NEGATIVE) {
titleRes = android.R.string.cancel
}
.applyAdapter(entries(view.resources)) {
value = it
}
.reveal()
}
}
abstract class Blank : BaseSettingsItem()
abstract class Section : BaseSettingsItem() {
override val layoutRes = R.layout.item_settings_section
}
}