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 description: String,
val type: String, val type: String,
val default: String? = null, 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 @kotlinx.serialization.Transient
val variableOrName: String get() = variable ?: name; val variableOrName: String get() = variable ?: name;

View File

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

View File

@ -6,6 +6,8 @@ import android.view.View
import android.widget.* import android.widget.*
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.Event3
import com.futo.platformplayer.logging.Logger
import java.lang.reflect.Field import java.lang.reflect.Field
class DropdownField : TableRow, IField { class DropdownField : TableRow, IField {
@ -35,7 +37,7 @@ class DropdownField : TableRow, IField {
override var reference: Any? = null; 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){ constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs){
inflate(context, R.layout.field_dropdown, this); inflate(context, R.layout.field_dropdown, this);
@ -50,13 +52,21 @@ class DropdownField : TableRow, IField {
_isInitFire = false; _isInitFire = false;
return; return;
} }
Logger.i("DropdownField", "Changed: ${_selected} -> ${pos}");
val old = _selected;
_selected = pos; _selected = pos;
onChanged.emit(this@DropdownField, pos); onChanged.emit(this@DropdownField, pos, old);
} }
override fun onNothingSelected(parent: AdapterView<*>?) = Unit 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 { fun asBoolean(name: String, description: String?, obj: Boolean) : DropdownField {
_options = resources.getStringArray(R.array.enabled_disabled_array); _options = resources.getStringArray(R.array.enabled_disabled_array);
_spinner.adapter = ArrayAdapter<String>(context, R.layout.spinner_item_simple, _options).also { _spinner.adapter = ArrayAdapter<String>(context, R.layout.spinner_item_simple, _options).also {
@ -77,6 +87,23 @@ class DropdownField : TableRow, IField {
return this; 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 { override fun fromField(obj: Any, field: Field, formField: FormField?) : DropdownField {
this._field = field; this._field = field;
this._obj = obj; this._obj = obj;

View File

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

View File

@ -5,6 +5,7 @@ import android.util.AttributeSet
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
@ -49,7 +50,7 @@ class FieldForm : LinearLayout {
throw java.lang.IllegalStateException("Only views can be IFields"); throw java.lang.IllegalStateException("Only views can be IFields");
_root.addView(field as View); _root.addView(field as View);
field.onChanged.subscribe { a1, a2 -> field.onChanged.subscribe { a1, a2, oldValue ->
onChanged.emit(a1, a2); onChanged.emit(a1, a2);
}; };
} }
@ -67,7 +68,7 @@ class FieldForm : LinearLayout {
throw java.lang.IllegalStateException("Only views can be IFields"); throw java.lang.IllegalStateException("Only views can be IFields");
_root.addView(field as View); _root.addView(field as View);
field.onChanged.subscribe { a1, a2 -> field.onChanged.subscribe { a1, a2, oldValue ->
onChanged.emit(a1, a2); onChanged.emit(a1, a2);
}; };
} }
@ -82,25 +83,59 @@ class FieldForm : LinearLayout {
if(groupTitle == null) { if(groupTitle == null) {
for(field in newFields) { for(field in newFields) {
if(field !is View) if(field.second !is View)
throw java.lang.IllegalStateException("Only views can be IFields"); throw java.lang.IllegalStateException("Only views can be IFields");
field.onChanged.subscribe { field, value -> finalizePluginSettingField(field.first, field.second, newFields);
onChanged.emit(field, value);
}
_root.addView(field as View); _root.addView(field as View);
} }
_fields = newFields; _fields = newFields.map { it.second };
} else { } else {
for(field in newFields) { for(field in newFields) {
field.onChanged.subscribe { field, value -> finalizePluginSettingField(field.first, field.second, newFields);
onChanged.emit(field, value);
}
} }
val group = GroupField(context, groupTitle, groupDescription) val group = GroupField(context, groupTitle, groupDescription)
.withFields(newFields); .withFields(newFields.map { it.second });
_root.addView(group as View); _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(){ fun setObjectValues(){
val fields = _fields; val fields = _fields;
@ -133,26 +168,42 @@ class FieldForm : LinearLayout {
private val _json = Json {}; private val _json = Json {};
fun getFieldsFromPluginSettings(context: Context, settings: List<SourcePluginConfig.Setting>, values: HashMap<String, String?>): List<IField> { fun getFieldsFromPluginSettings(context: Context, settings: List<SourcePluginConfig.Setting>, values: HashMap<String, String?>): List<Pair<SourcePluginConfig.Setting, IField>> {
val fields = mutableListOf<IField>() val fields = mutableListOf<Pair<SourcePluginConfig.Setting, IField>>()
for(setting in settings) { for(setting in settings) {
val value = if(values.containsKey(setting.variableOrName)) values[setting.variableOrName] else setting.default;
val field = when(setting.type.lowercase()) { val field = when(setting.type.lowercase()) {
"header" -> {
val groupField = GroupField(context, setting.name, setting.description);
groupField;
}
"boolean" -> { "boolean" -> {
val value = if(values.containsKey(setting.variableOrName)) values[setting.variableOrName] else setting.default;
val field = ToggleField(context).withValue(setting.name, val field = ToggleField(context).withValue(setting.name,
setting.description, setting.description,
value == "true" || value == "1" || value == "True"); 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); values[setting.variableOrName] = _json.encodeToString (value == 1 || value == true);
} }
field; 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; else -> null;
} }
if(field != null) if(field != null)
fields.add(field); fields.add(Pair(setting, field));
} }
return fields; return fields;
} }

View File

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

View File

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

View File

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

View File

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