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; 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) @DropdownFieldOptionsId(R.array.preferred_quality_array)
var preferredQuality: Int = 0; 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) @DropdownFieldOptionsId(R.array.preferred_quality_array)
var preferredMeteredQuality: Int = 0; var preferredMeteredQuality: Int = 0;
fun getPreferredQualityPixelCount(): Int = preferedQualityToPixels(preferredQuality); fun getPreferredQualityPixelCount(): Int = preferedQualityToPixels(preferredQuality);
fun getPreferredMeteredQualityPixelCount(): Int = preferedQualityToPixels(preferredMeteredQuality); fun getPreferredMeteredQualityPixelCount(): Int = preferedQualityToPixels(preferredMeteredQuality);
fun getCurrentPreferredQualityPixelCount(): Int = if(!StateApp.instance.isCurrentMetered()) getPreferredQualityPixelCount() else getPreferredMeteredQualityPixelCount(); 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) @DropdownFieldOptionsId(R.array.preferred_quality_array)
var preferredPreviewQuality: Int = 5; var preferredPreviewQuality: Int = 5;
fun getPreferredPreviewQualityPixelCount(): Int = preferedQualityToPixels(preferredPreviewQuality); 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) @DropdownFieldOptionsId(R.array.system_enabled_disabled_array)
var autoRotate: Int = 2; var autoRotate: Int = 2;
fun isAutoRotate() = autoRotate == 1 || (autoRotate == 2 && StateApp.instance.getCurrentSystemAutoRotate()); 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) @DropdownFieldOptionsId(R.array.auto_rotate_dead_zone)
var autoRotateDeadZone: Int = 0; var autoRotateDeadZone: Int = 0;
@ -337,7 +337,7 @@ class Settings : FragmentedStorageFileJson() {
return autoRotateDeadZone * 5; 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) @DropdownFieldOptionsId(R.array.player_background_behavior)
var backgroundPlay: Int = 2; var backgroundPlay: Int = 2;

View File

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

View File

@ -5,10 +5,12 @@ import android.view.View
import android.widget.LinearLayout 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.UIDialogs
import com.futo.platformplayer.api.media.models.contents.ContentType 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.IPlatformContent
import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails 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.nested.IPlatformNestedContent
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.images.GlideHelper.Companion.loadThumbnails 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.states.StatePlatform
import com.futo.platformplayer.video.PlayerManager import com.futo.platformplayer.video.PlayerManager
import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.Loader
import com.futo.platformplayer.views.platform.PlatformIndicator import com.futo.platformplayer.views.platform.PlatformIndicator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -25,6 +28,7 @@ class PreviewNestedVideoView : PreviewVideoView {
protected val _platformIndicatorNested: PlatformIndicator; protected val _platformIndicatorNested: PlatformIndicator;
protected val _containerLoader: LinearLayout; protected val _containerLoader: LinearLayout;
protected val _loader: Loader;
protected val _containerUnavailable: LinearLayout; protected val _containerUnavailable: LinearLayout;
protected val _textNestedUrl: TextView; protected val _textNestedUrl: TextView;
@ -38,8 +42,39 @@ class PreviewNestedVideoView : PreviewVideoView {
constructor(context: Context, feedStyle: FeedStyle, exoPlayer: PlayerManager? = null): super(context, feedStyle, exoPlayer) { constructor(context: Context, feedStyle: FeedStyle, exoPlayer: PlayerManager? = null): super(context, feedStyle, exoPlayer) {
_platformIndicatorNested = findViewById(R.id.thumbnail_platform_nested); _platformIndicatorNested = findViewById(R.id.thumbnail_platform_nested);
_containerLoader = findViewById(R.id.container_loader); _containerLoader = findViewById(R.id.container_loader);
_loader = findViewById(R.id.loader);
_containerUnavailable = findViewById(R.id.container_unavailable); _containerUnavailable = findViewById(R.id.container_unavailable);
_textNestedUrl = findViewById(R.id.text_nested_url); _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) { override fun inflate(feedStyle: FeedStyle) {
@ -81,6 +116,7 @@ class PreviewNestedVideoView : PreviewVideoView {
if(!_contentSupported) { if(!_contentSupported) {
_containerUnavailable.visibility = View.VISIBLE; _containerUnavailable.visibility = View.VISIBLE;
_containerLoader.visibility = View.GONE; _containerLoader.visibility = View.GONE;
_loader.stop();
} }
else { else {
if(_feedStyle == FeedStyle.THUMBNAIL) if(_feedStyle == FeedStyle.THUMBNAIL)
@ -96,12 +132,14 @@ class PreviewNestedVideoView : PreviewVideoView {
_contentSupported = false; _contentSupported = false;
_containerUnavailable.visibility = View.VISIBLE; _containerUnavailable.visibility = View.VISIBLE;
_containerLoader.visibility = View.GONE; _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}]"); Logger.i(TAG, "Loading nested content [${content.contentUrl}]");
_containerLoader.visibility = View.VISIBLE; _containerLoader.visibility = View.VISIBLE;
_loader.start();
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
val def = StatePlatform.instance.getContentDetails(content.contentUrl); val def = StatePlatform.instance.getContentDetails(content.contentUrl);
def.invokeOnCompletion { def.invokeOnCompletion {
@ -112,11 +150,13 @@ class PreviewNestedVideoView : PreviewVideoView {
if(_content == content) { if(_content == content) {
_containerUnavailable.visibility = View.VISIBLE; _containerUnavailable.visibility = View.VISIBLE;
_containerLoader.visibility = View.GONE; _containerLoader.visibility = View.GONE;
_loader.stop();
} }
//TODO: Handle exception //TODO: Handle exception
} }
else if(_content == content) { else if(_content == content) {
_containerLoader.visibility = View.GONE; _containerLoader.visibility = View.GONE;
_loader.stop();
val nestedContent = def.getCompleted(); val nestedContent = def.getCompleted();
_contentNested = nestedContent; _contentNested = nestedContent;
if(nestedContent is IPlatformVideoDetails) { if(nestedContent is IPlatformVideoDetails) {
@ -131,6 +171,8 @@ class PreviewNestedVideoView : PreviewVideoView {
else { else {
_containerUnavailable.visibility = View.VISIBLE; _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 _textPrimary: TextView;
private val _textSecondary: TextView; private val _textSecondary: TextView;
val title: String get() = _textPrimary.text.toString();
val description: String get() = _textSecondary.text.toString();
val onClick = Event0(); val onClick = Event0();
constructor(context : Context, text: String, subText: String, icon: Int, action: ()->Unit) : super(context) { 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 value: Any? = null;
override val searchContent: String?
get() = "$title $description";
override val obj : Any? get() { override val obj : Any? get() {
if(this._obj == null) if(this._obj == null)
throw java.lang.IllegalStateException("Can only be called if fromField is used"); 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 value: Any? get() = _selected;
override val searchContent: String?
get() = "${_title.text} ${_description.text}";
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);
_spinner = findViewById(R.id.field_spinner); _spinner = findViewById(R.id.field_spinner);

View File

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

View File

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

View File

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

View File

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

View File

@ -38,6 +38,9 @@ class ToggleField : TableRow, IField {
override val value: Any get() = _lastValue; override val value: Any get() = _lastValue;
override val searchContent: String?
get() = "${_title.text} ${_description.text}";
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);
_toggle = findViewById(R.id.field_toggle); _toggle = findViewById(R.id.field_toggle);

View File

@ -2,8 +2,44 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical" android:orientation="vertical"
android:id="@+id/field_form_root"> 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> </LinearLayout>

View File

@ -125,7 +125,13 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#BB000000" android:background="#BB000000"
android:visibility="gone" 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 <LinearLayout
android:id="@+id/container_unavailable" android:id="@+id/container_unavailable"

View File

@ -29,6 +29,7 @@
<string name="defaults">Defaults</string> <string name="defaults">Defaults</string>
<string name="home_screen">Home Screen</string> <string name="home_screen">Home Screen</string>
<string name="preferred_quality">Preferred Quality</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="update">Update</string>
<string name="close">Close</string> <string name="close">Close</string>
<string name="never">Never</string> <string name="never">Never</string>
@ -358,8 +359,11 @@
<string name="player">Player</string> <string name="player">Player</string>
<string name="plugins">Plugins</string> <string name="plugins">Plugins</string>
<string name="preferred_casting_quality">Preferred Casting Quality</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">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">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="primary_language">Primary Language</string>
<string name="default_comment_section">Default Comment Section</string> <string name="default_comment_section">Default Comment Section</string>
<string name="reinstall_embedded_plugins">Reinstall Embedded Plugins</string> <string name="reinstall_embedded_plugins">Reinstall Embedded Plugins</string>