Additional plugin settings capabilities

This commit is contained in:
Kelvin 2023-11-08 16:02:52 +01:00
parent 22146a6bdc
commit 4fa61e7f52
12 changed files with 160 additions and 33 deletions

View file

@ -144,7 +144,10 @@ class SourcePluginConfig(
val description: String,
val type: String,
val default: String? = null,
val variable: String? = null
val variable: String? = null,
val dependency: String? = null,
val warningDialog: String? = null,
val options: List<String>? = null
) {
@kotlinx.serialization.Transient
val variableOrName: String get() = variable ?: name;

View file

@ -8,6 +8,7 @@ import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.R
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.Event3
import com.futo.platformplayer.dp
import com.futo.platformplayer.views.buttons.BigButton
import java.lang.reflect.Field
@ -37,7 +38,7 @@ class ButtonField : BigButton, IField {
//private val _title : TextView;
//private val _subtitle : TextView;
override val onChanged = Event2<IField, Any>();
override val onChanged = Event3<IField, Any, Any>();
constructor(context : Context, attrs : AttributeSet? = null) : super(context, attrs){
//inflate(context, R.layout.field_button, this);
@ -59,6 +60,8 @@ class ButtonField : BigButton, IField {
}
}
override fun setValue(value: Any) {}
fun fromMethod(obj : Any, method: Method) : ButtonField {
this._method = method;
this._obj = obj;

View file

@ -6,6 +6,8 @@ import android.view.View
import android.widget.*
import com.futo.platformplayer.R
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.Event3
import com.futo.platformplayer.logging.Logger
import java.lang.reflect.Field
class DropdownField : TableRow, IField {
@ -35,7 +37,7 @@ class DropdownField : TableRow, IField {
override var reference: Any? = null;
override val onChanged = Event2<IField, Any>();
override val onChanged = Event3<IField, Any, Any>();
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs){
inflate(context, R.layout.field_dropdown, this);
@ -50,13 +52,21 @@ class DropdownField : TableRow, IField {
_isInitFire = false;
return;
}
Logger.i("DropdownField", "Changed: ${_selected} -> ${pos}");
val old = _selected;
_selected = pos;
onChanged.emit(this@DropdownField, pos);
onChanged.emit(this@DropdownField, pos, old);
}
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
};
}
override fun setValue(value: Any) {
if(value is Int) {
_spinner.setSelection(value);
}
}
fun asBoolean(name: String, description: String?, obj: Boolean) : DropdownField {
_options = resources.getStringArray(R.array.enabled_disabled_array);
_spinner.adapter = ArrayAdapter<String>(context, R.layout.spinner_item_simple, _options).also {
@ -77,6 +87,23 @@ class DropdownField : TableRow, IField {
return this;
}
fun withValue(title: String, description: String?, options: List<String>, value: Int): DropdownField {
_title.text = title;
_description.visibility = if(description.isNullOrEmpty()) View.GONE else View.VISIBLE;
_description.text = description ?: "";
_options = options.toTypedArray();
_spinner.adapter = ArrayAdapter<String>(context, R.layout.spinner_item_simple, _options).also {
it.setDropDownViewResource(R.layout.spinner_dropdownitem_simple);
};
_selected = value;
_spinner.isSelected = false;
_spinner.setSelection(_selected, true);
return this;
}
override fun fromField(obj: Any, field: Field, formField: FormField?) : DropdownField {
this._field = field;
this._obj = obj;

View file

@ -1,6 +1,7 @@
package com.futo.platformplayer.views.fields
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.Event3
import java.lang.reflect.Field
@ -13,11 +14,12 @@ interface IField {
val obj : Any?;
val field : Field?;
val onChanged : Event2<IField, Any>;
val onChanged : Event3<IField, Any, Any>;
var reference: Any?;
fun fromField(obj : Any, field : Field, formField: FormField? = null) : IField;
fun setField();
fun setValue(value: Any);
}

View file

@ -5,6 +5,7 @@ import android.util.AttributeSet
import android.view.View
import android.widget.LinearLayout
import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.logging.Logger
@ -49,7 +50,7 @@ class FieldForm : LinearLayout {
throw java.lang.IllegalStateException("Only views can be IFields");
_root.addView(field as View);
field.onChanged.subscribe { a1, a2 ->
field.onChanged.subscribe { a1, a2, oldValue ->
onChanged.emit(a1, a2);
};
}
@ -67,7 +68,7 @@ class FieldForm : LinearLayout {
throw java.lang.IllegalStateException("Only views can be IFields");
_root.addView(field as View);
field.onChanged.subscribe { a1, a2 ->
field.onChanged.subscribe { a1, a2, oldValue ->
onChanged.emit(a1, a2);
};
}
@ -82,25 +83,59 @@ class FieldForm : LinearLayout {
if(groupTitle == null) {
for(field in newFields) {
if(field !is View)
if(field.second !is View)
throw java.lang.IllegalStateException("Only views can be IFields");
field.onChanged.subscribe { field, value ->
onChanged.emit(field, value);
}
finalizePluginSettingField(field.first, field.second, newFields);
_root.addView(field as View);
}
_fields = newFields;
_fields = newFields.map { it.second };
} else {
for(field in newFields) {
field.onChanged.subscribe { field, value ->
onChanged.emit(field, value);
}
finalizePluginSettingField(field.first, field.second, newFields);
}
val group = GroupField(context, groupTitle, groupDescription)
.withFields(newFields);
.withFields(newFields.map { it.second });
_root.addView(group as View);
}
}
private fun finalizePluginSettingField(setting: SourcePluginConfig.Setting, field: IField, others: List<Pair<SourcePluginConfig.Setting, IField>>) {
field.onChanged.subscribe { field, value, oldValue ->
onChanged.emit(field, value);
setting.warningDialog?.let {
if(it.isNotBlank() && isValueTrue(value))
UIDialogs.showDialog(context, R.drawable.ic_warning_yellow, setting.warningDialog, null, null, 0,
UIDialogs.Action("Cancel", {
field.setValue(oldValue);
}, UIDialogs.ActionStyle.NONE),
UIDialogs.Action("Ok", {
}, UIDialogs.ActionStyle.PRIMARY));
}
}
if(setting.dependency != null) {
val dependentField = others.firstOrNull { it.first.variableOrName == setting.dependency };
if(dependentField == null || dependentField.second !is View)
(field as View).visibility = View.GONE;
else {
dependentField.second.onChanged.subscribe { dependentField, value, oldValue ->
val isValid = isValueTrue(value);
if(isValid)
(field as View).visibility = View.VISIBLE;
else
(field as View).visibility = View.GONE;
}
}
}
}
private fun isValueTrue(value: Any): Boolean {
return when(value) {
is Int -> value > 0;
is Boolean -> value;
is String -> value.toIntOrNull()?.let { it > 0 } ?: false || value.lowercase() == "true";
else -> false
};
}
fun setObjectValues(){
val fields = _fields;
@ -133,26 +168,42 @@ class FieldForm : LinearLayout {
private val _json = Json {};
fun getFieldsFromPluginSettings(context: Context, settings: List<SourcePluginConfig.Setting>, values: HashMap<String, String?>): List<IField> {
val fields = mutableListOf<IField>()
fun getFieldsFromPluginSettings(context: Context, settings: List<SourcePluginConfig.Setting>, values: HashMap<String, String?>): List<Pair<SourcePluginConfig.Setting, IField>> {
val fields = mutableListOf<Pair<SourcePluginConfig.Setting, IField>>()
for(setting in settings) {
val value = if(values.containsKey(setting.variableOrName)) values[setting.variableOrName] else setting.default;
val field = when(setting.type.lowercase()) {
"header" -> {
val groupField = GroupField(context, setting.name, setting.description);
groupField;
}
"boolean" -> {
val value = if(values.containsKey(setting.variableOrName)) values[setting.variableOrName] else setting.default;
val field = ToggleField(context).withValue(setting.name,
setting.description,
value == "true" || value == "1" || value == "True");
field.onChanged.subscribe { field, value ->
field.onChanged.subscribe { field, value, oldValue ->
values[setting.variableOrName] = _json.encodeToString (value == 1 || value == true);
}
field;
}
"dropdown" -> {
if(setting.options != null && !setting.options.isEmpty()) {
var selected = value?.toIntOrNull()?.coerceAtLeast(0) ?: 0;
val field = DropdownField(context).withValue(setting.name, setting.description, setting.options, selected);
field.onChanged.subscribe { field, value, oldValue ->
values[setting.variableOrName] = value.toString();
}
field;
}
else null;
}
else -> null;
}
if(field != null)
fields.add(field);
fields.add(Pair(setting, field));
}
return fields;
}

View file

@ -7,6 +7,7 @@ import android.widget.LinearLayout
import android.widget.TextView
import com.futo.platformplayer.R
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.Event3
import java.lang.reflect.Field
class GroupField : LinearLayout, IField {
@ -27,7 +28,7 @@ class GroupField : LinearLayout, IField {
return _field;
};
override val onChanged = Event2<IField, Any>();
override val onChanged = Event3<IField, Any, Any>();
private val _title : TextView;
private val _subtitle : TextView;
@ -138,4 +139,6 @@ class GroupField : LinearLayout, IField {
field.setField();
}
}
override fun setValue(value: Any) {}
}

View file

@ -5,6 +5,7 @@ import android.util.AttributeSet
import android.widget.*
import com.futo.platformplayer.R
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.Event3
import java.lang.reflect.Field
import java.lang.reflect.Method
@ -27,7 +28,7 @@ class ReadOnlyTextField : TableRow, IField {
private val _title : TextView;
private val _value : TextView;
override val onChanged = Event2<IField, Any>();
override val onChanged = Event3<IField, Any, Any>();
override var reference: Any? = null;
constructor(context : Context, attrs : AttributeSet? = null) : super(context, attrs){
@ -36,6 +37,8 @@ class ReadOnlyTextField : TableRow, IField {
_value = findViewById(R.id.field_value);
}
override fun setValue(value: Any) {}
override fun fromField(obj : Any, field : Field, formField: FormField?) : ReadOnlyTextField {
this._field = field;
this._obj = obj;

View file

@ -6,6 +6,8 @@ import android.view.View
import android.widget.*
import com.futo.platformplayer.R
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.Event3
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.views.others.Toggle
import java.lang.reflect.Field
@ -28,10 +30,11 @@ class ToggleField : TableRow, IField {
private val _title : TextView;
private val _description : TextView;
private val _toggle : Toggle;
private var _lastValue: Boolean = false;
override var reference: Any? = null;
override val onChanged = Event2<IField, Any>();
override val onChanged = Event3<IField, Any, Any>();
constructor(context : Context, attrs : AttributeSet? = null) : super(context, attrs){
inflate(context, R.layout.field_toggle, this);
@ -40,10 +43,18 @@ class ToggleField : TableRow, IField {
_description = findViewById(R.id.field_description);
_toggle.onValueChanged.subscribe {
onChanged.emit(this, it);
val lastVal = _lastValue;
Logger.i("ToggleField", "Changed: ${lastVal} -> ${it}");
_lastValue = it;
onChanged.emit(this, it, lastVal);
};
}
override fun setValue(value: Any) {
if(value is Boolean)
_toggle.setValue(value, true, true);
}
fun withValue(title: String, description: String?, value: Boolean): ToggleField {
_title.text = title;
@ -54,6 +65,7 @@ class ToggleField : TableRow, IField {
_description.visibility = View.GONE;
_toggle.setValue(value, true);
_lastValue = value;
return this;
}
@ -78,14 +90,16 @@ class ToggleField : TableRow, IField {
}
val value = field.get(obj);
if(value is Boolean)
_toggle.setValue(value, true);
val toggleValue = if(value is Boolean)
value;
else if(value is Number)
_toggle.setValue((value as Number).toInt() > 0, true);
(value as Number).toInt() > 0;
else if(value == null)
_toggle.setValue(false, true);
false;
else
_toggle.setValue(false, true);
false;
_toggle.setValue(toggleValue, true);
_lastValue = toggleValue;
return this;
}

View file

@ -29,7 +29,7 @@ class Toggle : AppCompatImageView {
scaleType = ScaleType.FIT_CENTER;
}
fun setValue(v: Boolean, animated: Boolean = true) {
fun setValue(v: Boolean, animated: Boolean = true, withEvent: Boolean = false) {
if (value == v) {
return;
}
@ -44,5 +44,8 @@ class Toggle : AppCompatImageView {
} else {
setImageResource(if (v) R.drawable.toggle_enabled else R.drawable.toggle_disabled);
}
if(withEvent)
onValueChanged.emit(value);
}
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M40,840L480,80L920,840L40,840ZM178,760L782,760L480,240L178,760ZM480,720Q497,720 508.5,708.5Q520,697 520,680Q520,663 508.5,651.5Q497,640 480,640Q463,640 451.5,651.5Q440,663 440,680Q440,697 451.5,708.5Q463,720 480,720ZM440,600L520,600L520,400L440,400L440,600ZM480,500L480,500L480,500L480,500Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#E4A72E"
android:pathData="M40,840L480,80L920,840L40,840ZM178,760L782,760L480,240L178,760ZM480,720Q497,720 508.5,708.5Q520,697 520,680Q520,663 508.5,651.5Q497,640 480,640Q463,640 451.5,651.5Q440,663 440,680Q440,697 451.5,708.5Q463,720 480,720ZM440,600L520,600L520,400L440,400L440,600ZM480,500L480,500L480,500L480,500Z"/>
</vector>

@ -1 +1 @@
Subproject commit 51c249e9f49a880b8451121d396a60ff73ce5600
Subproject commit 6f9a13c7ea1cb51a4548dbe7f25dc5113b02eb48