diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index 56aff38d..8daa4736 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -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; diff --git a/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt index 1925a1c9..3e5259a9 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt @@ -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()?.setOnClickListener { diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewNestedVideoView.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewNestedVideoView.kt index 8d8845e0..6644d7eb 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewNestedVideoView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewNestedVideoView.kt @@ -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); } } }; diff --git a/app/src/main/java/com/futo/platformplayer/views/buttons/BigButton.kt b/app/src/main/java/com/futo/platformplayer/views/buttons/BigButton.kt index 66d0b3bb..6d5e30e8 100644 --- a/app/src/main/java/com/futo/platformplayer/views/buttons/BigButton.kt +++ b/app/src/main/java/com/futo/platformplayer/views/buttons/BigButton.kt @@ -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) { diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/ButtonField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/ButtonField.kt index e3342948..6996005c 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/ButtonField.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/ButtonField.kt @@ -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"); diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/DropdownField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/DropdownField.kt index 5fc34884..f2f95d1a 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/DropdownField.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/DropdownField.kt @@ -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); diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/Field.kt b/app/src/main/java/com/futo/platformplayer/views/fields/Field.kt index 0f950bea..1b5a9282 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/Field.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/Field.kt @@ -23,6 +23,8 @@ interface IField { var reference: Any?; + val searchContent: String?; + fun fromField(obj : Any, field : Field, formField: FormField? = null) : IField; fun setField(); diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/FieldForm.kt b/app/src/main/java/com/futo/platformplayer/views/fields/FieldForm.kt index 5a112c37..24ebf38e 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/FieldForm.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/FieldForm.kt @@ -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(); @@ -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, values: HashMap, 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>) { @@ -210,7 +247,6 @@ class FieldForm : LinearLayout { .asStream() .filter { it.hasAnnotation() && it.javaField != null } .map { Pair, FormField>(it, it.findAnnotation()!!) } - .toList() //TODO: Rewrite fields to properties so no map is required val propertyMap = mutableMapOf>(); @@ -252,7 +288,6 @@ class FieldForm : LinearLayout { .asStream() .filter { it.hasAnnotation() && it.javaField == null && it.getter.javaMethod != null} .map { Pair(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(it, it.getAnnotation(FormField::class.java)) } - .toList(); for(meth in objMethods) { meth.first.isAccessible = true; diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/GroupField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/GroupField.kt index 5f657d88..6ea48ee7 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/GroupField.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/GroupField.kt @@ -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 { + return _fields; + } override fun setValue(value: Any) {} } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/ReadOnlyTextField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/ReadOnlyTextField.kt index c54239ae..7b0244dc 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/ReadOnlyTextField.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/ReadOnlyTextField.kt @@ -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); diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt index 01ff8f1c..2bbbbb21 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt @@ -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); diff --git a/app/src/main/res/layout/field_form.xml b/app/src/main/res/layout/field_form.xml index 74aee5fa..1b1d9f94 100644 --- a/app/src/main/res/layout/field_form.xml +++ b/app/src/main/res/layout/field_form.xml @@ -2,8 +2,44 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_video_thumbnail_nested.xml b/app/src/main/res/layout/list_video_thumbnail_nested.xml index 6b699eb4..a54e6276 100644 --- a/app/src/main/res/layout/list_video_thumbnail_nested.xml +++ b/app/src/main/res/layout/list_video_thumbnail_nested.xml @@ -125,7 +125,13 @@ android:layout_height="match_parent" android:background="#BB000000" android:visibility="gone" - android:orientation="vertical" /> + android:gravity="center" + android:orientation="vertical"> + + Defaults Home Screen Preferred Quality + Default quality for watching a video Update Close Never @@ -358,8 +359,11 @@ Player Plugins Preferred Casting Quality + Default quality while casting to an external device Preferred Metered Quality + Default quality while on metered connections such as cellular Preferred Preview Quality + Default quality while previewing a video in a feed Primary Language Default Comment Section Reinstall Embedded Plugins