mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-04-20 03:24:50 +00:00
Settings search, Fix nested video events, Adding setting descriptions for metered
This commit is contained in:
parent
eb3dd854d4
commit
12b2552185
14 changed files with 170 additions and 23 deletions
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -23,6 +23,8 @@ interface IField {
|
|||
|
||||
var reference: Any?;
|
||||
|
||||
val searchContent: String?;
|
||||
|
||||
fun fromField(obj : Any, field : Field, formField: FormField? = null) : IField;
|
||||
fun setField();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue