Settings search, Fix nested video events, Adding setting descriptions for metered

This commit is contained in:
Kelvin 2023-11-24 15:22:03 +01:00
parent eb3dd854d4
commit 12b2552185
14 changed files with 170 additions and 23 deletions

View file

@ -307,29 +307,29 @@ class Settings : FragmentedStorageFileJson() {
else -> 1.0f;
};
@FormField(R.string.preferred_quality, FieldForm.DROPDOWN, -1, 2)
@FormField(R.string.preferred_quality, FieldForm.DROPDOWN, R.string.preferred_quality_description, 2)
@DropdownFieldOptionsId(R.array.preferred_quality_array)
var preferredQuality: Int = 0;
@FormField(R.string.preferred_metered_quality, FieldForm.DROPDOWN, -1, 2)
@FormField(R.string.preferred_metered_quality, FieldForm.DROPDOWN, R.string.preferred_metered_quality_description, 3)
@DropdownFieldOptionsId(R.array.preferred_quality_array)
var preferredMeteredQuality: Int = 0;
fun getPreferredQualityPixelCount(): Int = preferedQualityToPixels(preferredQuality);
fun getPreferredMeteredQualityPixelCount(): Int = preferedQualityToPixels(preferredMeteredQuality);
fun getCurrentPreferredQualityPixelCount(): Int = if(!StateApp.instance.isCurrentMetered()) getPreferredQualityPixelCount() else getPreferredMeteredQualityPixelCount();
@FormField(R.string.preferred_preview_quality, FieldForm.DROPDOWN, -1, 3)
@FormField(R.string.preferred_preview_quality, FieldForm.DROPDOWN, R.string.preferred_preview_quality_description, 4)
@DropdownFieldOptionsId(R.array.preferred_quality_array)
var preferredPreviewQuality: Int = 5;
fun getPreferredPreviewQualityPixelCount(): Int = preferedQualityToPixels(preferredPreviewQuality);
@FormField(R.string.auto_rotate, FieldForm.DROPDOWN, -1, 4)
@FormField(R.string.auto_rotate, FieldForm.DROPDOWN, -1, 5)
@DropdownFieldOptionsId(R.array.system_enabled_disabled_array)
var autoRotate: Int = 2;
fun isAutoRotate() = autoRotate == 1 || (autoRotate == 2 && StateApp.instance.getCurrentSystemAutoRotate());
@FormField(R.string.auto_rotate_dead_zone, FieldForm.DROPDOWN, R.string.this_prevents_the_device_from_rotating_within_the_given_amount_of_degrees, 5)
@FormField(R.string.auto_rotate_dead_zone, FieldForm.DROPDOWN, R.string.this_prevents_the_device_from_rotating_within_the_given_amount_of_degrees, 6)
@DropdownFieldOptionsId(R.array.auto_rotate_dead_zone)
var autoRotateDeadZone: Int = 0;
@ -337,7 +337,7 @@ class Settings : FragmentedStorageFileJson() {
return autoRotateDeadZone * 5;
}
@FormField(R.string.background_behavior, FieldForm.DROPDOWN, -1, 6)
@FormField(R.string.background_behavior, FieldForm.DROPDOWN, -1, 7)
@DropdownFieldOptionsId(R.array.player_background_behavior)
var backgroundPlay: Int = 2;

View file

@ -69,9 +69,11 @@ class SettingsActivity : AppCompatActivity(), IWithResultLauncher {
}
fun reloadSettings() {
_form.setSearchVisible(false);
_loader.start();
_form.fromObject(lifecycleScope, Settings.instance) {
_loader.stop();
_form.setSearchVisible(true);
var devCounter = 0;
_form.findField("code")?.assume<ReadOnlyTextField>()?.setOnClickListener {

View file

@ -5,10 +5,12 @@ import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.api.media.models.contents.ContentType
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
import com.futo.platformplayer.api.media.models.nested.IPlatformNestedContent
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.images.GlideHelper.Companion.loadThumbnails
@ -17,6 +19,7 @@ import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.video.PlayerManager
import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.Loader
import com.futo.platformplayer.views.platform.PlatformIndicator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -25,6 +28,7 @@ class PreviewNestedVideoView : PreviewVideoView {
protected val _platformIndicatorNested: PlatformIndicator;
protected val _containerLoader: LinearLayout;
protected val _loader: Loader;
protected val _containerUnavailable: LinearLayout;
protected val _textNestedUrl: TextView;
@ -38,8 +42,39 @@ class PreviewNestedVideoView : PreviewVideoView {
constructor(context: Context, feedStyle: FeedStyle, exoPlayer: PlayerManager? = null): super(context, feedStyle, exoPlayer) {
_platformIndicatorNested = findViewById(R.id.thumbnail_platform_nested);
_containerLoader = findViewById(R.id.container_loader);
_loader = findViewById(R.id.loader);
_containerUnavailable = findViewById(R.id.container_unavailable);
_textNestedUrl = findViewById(R.id.text_nested_url);
_imageChannel?.setOnClickListener { _contentNested?.let { onChannelClicked.emit(it.author) } };
_textChannelName.setOnClickListener { _contentNested?.let { onChannelClicked.emit(it.author) } };
_textVideoMetadata.setOnClickListener { _contentNested?.let { onChannelClicked.emit(it.author) } };
_button_add_to.setOnClickListener {
if(_contentNested is IPlatformVideo)
_contentNested?.let { onAddToClicked.emit(it as IPlatformVideo) }
else _content?.let {
if(it is IPlatformNestedContent)
loadNested(it) {
if(it is IPlatformVideo)
onAddToClicked.emit(it);
else
UIDialogs.toast(context, "Content is not a video");
}
}
};
_button_add_to_queue.setOnClickListener {
if(_contentNested is IPlatformVideo)
_contentNested?.let { onAddToQueueClicked.emit(it as IPlatformVideo) }
else _content?.let {
if(it is IPlatformNestedContent)
loadNested(it) {
if(it is IPlatformVideo)
onAddToQueueClicked.emit(it);
else
UIDialogs.toast(context, "Content is not a video");
}
}
};
}
override fun inflate(feedStyle: FeedStyle) {
@ -81,6 +116,7 @@ class PreviewNestedVideoView : PreviewVideoView {
if(!_contentSupported) {
_containerUnavailable.visibility = View.VISIBLE;
_containerLoader.visibility = View.GONE;
_loader.stop();
}
else {
if(_feedStyle == FeedStyle.THUMBNAIL)
@ -96,12 +132,14 @@ class PreviewNestedVideoView : PreviewVideoView {
_contentSupported = false;
_containerUnavailable.visibility = View.VISIBLE;
_containerLoader.visibility = View.GONE;
_loader.stop();
}
}
private fun loadNested(content: IPlatformNestedContent) {
private fun loadNested(content: IPlatformNestedContent, onCompleted: ((IPlatformContentDetails)->Unit)? = null) {
Logger.i(TAG, "Loading nested content [${content.contentUrl}]");
_containerLoader.visibility = View.VISIBLE;
_loader.start();
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
val def = StatePlatform.instance.getContentDetails(content.contentUrl);
def.invokeOnCompletion {
@ -112,11 +150,13 @@ class PreviewNestedVideoView : PreviewVideoView {
if(_content == content) {
_containerUnavailable.visibility = View.VISIBLE;
_containerLoader.visibility = View.GONE;
_loader.stop();
}
//TODO: Handle exception
}
else if(_content == content) {
_containerLoader.visibility = View.GONE;
_loader.stop();
val nestedContent = def.getCompleted();
_contentNested = nestedContent;
if(nestedContent is IPlatformVideoDetails) {
@ -131,6 +171,8 @@ class PreviewNestedVideoView : PreviewVideoView {
else {
_containerUnavailable.visibility = View.VISIBLE;
}
if(onCompleted != null)
onCompleted(nestedContent);
}
}
};

View file

@ -19,6 +19,9 @@ open class BigButton : LinearLayout {
private val _textPrimary: TextView;
private val _textSecondary: TextView;
val title: String get() = _textPrimary.text.toString();
val description: String get() = _textSecondary.text.toString();
val onClick = Event0();
constructor(context : Context, text: String, subText: String, icon: Int, action: ()->Unit) : super(context) {

View file

@ -28,6 +28,10 @@ class ButtonField : BigButton, IField {
override val value: Any? = null;
override val searchContent: String?
get() = "$title $description";
override val obj : Any? get() {
if(this._obj == null)
throw java.lang.IllegalStateException("Can only be called if fromField is used");

View file

@ -41,6 +41,9 @@ class DropdownField : TableRow, IField {
override val value: Any? get() = _selected;
override val searchContent: String?
get() = "${_title.text} ${_description.text}";
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs){
inflate(context, R.layout.field_dropdown, this);
_spinner = findViewById(R.id.field_spinner);

View file

@ -23,6 +23,8 @@ interface IField {
var reference: Any?;
val searchContent: String?;
fun fromField(obj : Any, field : Field, formField: FormField? = null) : IField;
fun setField();

View file

@ -3,12 +3,14 @@ package com.futo.platformplayer.views.fields
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.core.widget.addTextChangedListener
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
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -24,11 +26,12 @@ import kotlin.reflect.full.hasAnnotation
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.javaMethod
import kotlin.streams.asStream
import kotlin.streams.toList
class FieldForm : LinearLayout {
private val _root : LinearLayout;
private val _containerSearch: FrameLayout;
private val _editSearch: EditText;
private val _fieldsContainer : LinearLayout;
val onChanged = Event2<IField, Any>();
@ -36,11 +39,45 @@ class FieldForm : LinearLayout {
constructor(context : Context, attrs : AttributeSet? = null) : super(context, attrs) {
inflate(context, R.layout.field_form, this);
_root = findViewById(R.id.field_form_root);
_containerSearch = findViewById(R.id.container_search);
_editSearch = findViewById(R.id.edit_search);
_fieldsContainer = findViewById(R.id.field_form_container);
_editSearch.addTextChangedListener {
updateSettingsVisibility();
}
}
fun updateSettingsVisibility(group: GroupField? = null) {
val settings = group?.getFields() ?: _fields;
val query = _editSearch.text.toString().lowercase();
var groupVisible = false;
val isGroupMatch = query.isEmpty() || group?.searchContent?.lowercase()?.contains(query) == true;
for(field in settings) {
if(field is GroupField)
updateSettingsVisibility(field);
else if(field is View && field.descriptor != null) {
val txt = field.searchContent?.lowercase();
if(txt != null) {
val visible = isGroupMatch || txt.contains(query);
field.visibility = if (visible) View.VISIBLE else View.GONE;
groupVisible = groupVisible || visible;
}
}
}
if(group != null)
group.visibility = if(groupVisible) View.VISIBLE else View.GONE;
}
fun setSearchVisible(visible: Boolean) {
_containerSearch.visibility = if(visible) View.VISIBLE else View.GONE;
_editSearch.setText("");
}
fun fromObject(scope: CoroutineScope, obj : Any, onLoaded: (()->Unit)? = null) {
_root.removeAllViews();
_fieldsContainer.removeAllViews();
scope.launch(Dispatchers.Default) {
val newFields = getFieldsFromObject(context, obj);
@ -50,7 +87,7 @@ class FieldForm : LinearLayout {
if (field !is View)
throw java.lang.IllegalStateException("Only views can be IFields");
_root.addView(field as View);
_fieldsContainer.addView(field as View);
field.onChanged.subscribe { a1, a2, oldValue ->
onChanged.emit(a1, a2);
};
@ -62,13 +99,13 @@ class FieldForm : LinearLayout {
}
}
fun fromObject(obj : Any) {
_root.removeAllViews();
_fieldsContainer.removeAllViews();
val newFields = getFieldsFromObject(context, obj);
for(field in newFields) {
if(field !is View)
throw java.lang.IllegalStateException("Only views can be IFields");
_root.addView(field as View);
_fieldsContainer.addView(field as View);
field.onChanged.subscribe { a1, a2, oldValue ->
onChanged.emit(a1, a2);
};
@ -76,7 +113,7 @@ class FieldForm : LinearLayout {
_fields = newFields;
}
fun fromPluginSettings(settings: List<SourcePluginConfig.Setting>, values: HashMap<String, String?>, groupTitle: String? = null, groupDescription: String? = null) {
_root.removeAllViews();
_fieldsContainer.removeAllViews();
val newFields = getFieldsFromPluginSettings(context, settings, values);
if (newFields.isEmpty()) {
return;
@ -87,7 +124,7 @@ class FieldForm : LinearLayout {
if(field.second !is View)
throw java.lang.IllegalStateException("Only views can be IFields");
finalizePluginSettingField(field.first, field.second, newFields);
_root.addView(field as View);
_fieldsContainer.addView(field as View);
}
_fields = newFields.map { it.second };
} else {
@ -96,7 +133,7 @@ class FieldForm : LinearLayout {
}
val group = GroupField(context, groupTitle, groupDescription)
.withFields(newFields.map { it.second });
_root.addView(group as View);
_fieldsContainer.addView(group as View);
}
}
private fun finalizePluginSettingField(setting: SourcePluginConfig.Setting, field: IField, others: List<Pair<SourcePluginConfig.Setting, IField>>) {
@ -210,7 +247,6 @@ class FieldForm : LinearLayout {
.asStream()
.filter { it.hasAnnotation<FormField>() && it.javaField != null }
.map { Pair<KProperty<*>, FormField>(it, it.findAnnotation()!!) }
.toList()
//TODO: Rewrite fields to properties so no map is required
val propertyMap = mutableMapOf<Field, KProperty<*>>();
@ -252,7 +288,6 @@ class FieldForm : LinearLayout {
.asStream()
.filter { it.hasAnnotation<FormField>() && it.javaField == null && it.getter.javaMethod != null}
.map { Pair<Method, FormField>(it.getter.javaMethod!!, it.findAnnotation()!!) }
.toList();
for(prop in objProps) {
prop.first.isAccessible = true;
@ -270,7 +305,6 @@ class FieldForm : LinearLayout {
.asStream()
.filter { it.getAnnotation(FormField::class.java) != null && !it.name.startsWith("get") && !it.name.startsWith("set") }
.map { Pair<Method, FormField>(it, it.getAnnotation(FormField::class.java)) }
.toList();
for(meth in objMethods) {
meth.first.isAccessible = true;

View file

@ -39,6 +39,8 @@ class GroupField : LinearLayout, IField {
override val value: Any? = null;
override val searchContent: String? get() = "${_title.text} ${_subtitle.text}";
constructor(context : Context, attrs : AttributeSet? = null) : super(context, attrs) {
inflate(context, R.layout.field_group, this);
_title = findViewById(R.id.field_group_title);
@ -142,6 +144,9 @@ class GroupField : LinearLayout, IField {
field.setField();
}
}
fun getFields(): List<IField> {
return _fields;
}
override fun setValue(value: Any) {}
}

View file

@ -34,6 +34,9 @@ class ReadOnlyTextField : TableRow, IField {
override val value: Any? = null;
override val searchContent: String?
get() = "${_title.text}";
constructor(context : Context, attrs : AttributeSet? = null) : super(context, attrs){
inflate(context, R.layout.field_readonly_text, this);
_title = findViewById(R.id.field_title);

View file

@ -38,6 +38,9 @@ class ToggleField : TableRow, IField {
override val value: Any get() = _lastValue;
override val searchContent: String?
get() = "${_title.text} ${_description.text}";
constructor(context : Context, attrs : AttributeSet? = null) : super(context, attrs){
inflate(context, R.layout.field_toggle, this);
_toggle = findViewById(R.id.field_toggle);

View file

@ -2,8 +2,44 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical"
android:id="@+id/field_form_root">
<!--Search Text-->
<FrameLayout
android:id="@+id/container_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_margin="10dp">
<EditText
android:id="@+id/edit_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:imeOptions="actionDone"
android:singleLine="true"
android:hint="Search"
android:paddingEnd="46dp" />
<ImageButton
android:id="@+id/button_clear_search"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingStart="18dp"
android:paddingEnd="18dp"
android:layout_gravity="right|center_vertical"
android:visibility="invisible"
android:src="@drawable/ic_clear_16dp" />
</FrameLayout>
<LinearLayout
android:id="@+id/field_form_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical">
</LinearLayout>
</LinearLayout>

View file

@ -125,7 +125,13 @@
android:layout_height="match_parent"
android:background="#BB000000"
android:visibility="gone"
android:orientation="vertical" />
android:gravity="center"
android:orientation="vertical">
<com.futo.platformplayer.views.Loader
android:id="@+id/loader"
android:layout_width="50dp"
android:layout_height="50dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/container_unavailable"

View file

@ -29,6 +29,7 @@
<string name="defaults">Defaults</string>
<string name="home_screen">Home Screen</string>
<string name="preferred_quality">Preferred Quality</string>
<string name="preferred_quality_description">Default quality for watching a video</string>
<string name="update">Update</string>
<string name="close">Close</string>
<string name="never">Never</string>
@ -358,8 +359,11 @@
<string name="player">Player</string>
<string name="plugins">Plugins</string>
<string name="preferred_casting_quality">Preferred Casting Quality</string>
<string name="preferred_casting_quality_description">Default quality while casting to an external device</string>
<string name="preferred_metered_quality">Preferred Metered Quality</string>
<string name="preferred_metered_quality_description">Default quality while on metered connections such as cellular</string>
<string name="preferred_preview_quality">Preferred Preview Quality</string>
<string name="preferred_preview_quality_description">Default quality while previewing a video in a feed</string>
<string name="primary_language">Primary Language</string>
<string name="default_comment_section">Default Comment Section</string>
<string name="reinstall_embedded_plugins">Reinstall Embedded Plugins</string>