Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay

This commit is contained in:
Koen 2023-11-07 16:03:05 +01:00
commit 63137b4c4d
19 changed files with 144 additions and 31 deletions

View file

@ -4,9 +4,7 @@ import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.webkit.CookieManager import android.webkit.CookieManager
import androidx.core.content.ContextCompat.startActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.activities.* import com.futo.platformplayer.activities.*
import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.http.ManagedHttpClient
@ -30,6 +28,7 @@ import kotlinx.serialization.*
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import java.io.File import java.io.File
import java.time.OffsetDateTime import java.time.OffsetDateTime
import java.util.Locale
@Serializable @Serializable
data class MenuBottomBarSetting(val id: Int, var enabled: Boolean); data class MenuBottomBarSetting(val id: Int, var enabled: Boolean);
@ -46,7 +45,7 @@ class Settings : FragmentedStorageFileJson() {
@FormField( @FormField(
R.string.manage_polycentric_identity, FieldForm.BUTTON, R.string.manage_polycentric_identity, FieldForm.BUTTON,
R.string.manage_your_polycentric_identity, -4 R.string.manage_your_polycentric_identity, -5
) )
@FormFieldButton(R.drawable.ic_person) @FormFieldButton(R.drawable.ic_person)
fun managePolycentricIdentity() { fun managePolycentricIdentity() {
@ -61,7 +60,7 @@ class Settings : FragmentedStorageFileJson() {
@FormField( @FormField(
R.string.show_faq, FieldForm.BUTTON, R.string.show_faq, FieldForm.BUTTON,
R.string.get_answers_to_common_questions, -3 R.string.get_answers_to_common_questions, -4
) )
@FormFieldButton(R.drawable.ic_quiz) @FormFieldButton(R.drawable.ic_quiz)
fun openFAQ() { fun openFAQ() {
@ -74,7 +73,7 @@ class Settings : FragmentedStorageFileJson() {
} }
@FormField( @FormField(
R.string.show_issues, FieldForm.BUTTON, R.string.show_issues, FieldForm.BUTTON,
R.string.a_list_of_user_reported_and_self_reported_issues, -2 R.string.a_list_of_user_reported_and_self_reported_issues, -3
) )
@FormFieldButton(R.drawable.ic_data_alert) @FormFieldButton(R.drawable.ic_data_alert)
fun openIssues() { fun openIssues() {
@ -109,7 +108,7 @@ class Settings : FragmentedStorageFileJson() {
@FormField( @FormField(
R.string.manage_tabs, FieldForm.BUTTON, R.string.manage_tabs, FieldForm.BUTTON,
R.string.change_tabs_visible_on_the_home_screen, -1 R.string.change_tabs_visible_on_the_home_screen, -2
) )
@FormFieldButton(R.drawable.ic_tabs) @FormFieldButton(R.drawable.ic_tabs)
fun manageTabs() { fun manageTabs() {
@ -122,11 +121,39 @@ class Settings : FragmentedStorageFileJson() {
} }
} }
@FormField(R.string.home, "group", R.string.configure_how_your_home_tab_works_and_feels, 0)
var language = LanguageSettings();
@Serializable
class LanguageSettings {
@FormField(R.string.app_language, FieldForm.DROPDOWN, R.string.may_require_restart, 5, "app_language")
@DropdownFieldOptionsId(R.array.app_languages)
var appLanguage: Int = 0;
fun getAppLanguageLocaleString(): String? {
return when(appLanguage) {
0 -> null
1 -> "en";
2 -> "de";
3 -> "es";
4 -> "pt";
5 -> "fr"
6 -> "ja";
7 -> "ko";
8 -> "zh";
9 -> "ru";
10 -> "ar";
else -> null
}
}
}
@FormField(R.string.home, "group", R.string.configure_how_your_home_tab_works_and_feels, 1) @FormField(R.string.home, "group", R.string.configure_how_your_home_tab_works_and_feels, 1)
var home = HomeSettings(); var home = HomeSettings();
@Serializable @Serializable
class HomeSettings { class HomeSettings {
@FormField(R.string.feed_style, FieldForm.DROPDOWN, -1, 5) @FormField(R.string.feed_style, FieldForm.DROPDOWN, R.string.may_require_restart, 5)
@DropdownFieldOptionsId(R.array.feed_style) @DropdownFieldOptionsId(R.array.feed_style)
var homeFeedStyle: Int = 1; var homeFeedStyle: Int = 1;
@ -136,21 +163,28 @@ class Settings : FragmentedStorageFileJson() {
else else
return FeedStyle.THUMBNAIL; return FeedStyle.THUMBNAIL;
} }
@FormField(R.string.preview_feed_items, FieldForm.TOGGLE, R.string.preview_feed_items_description, 6)
var previewFeedItems: Boolean = true;
} }
@FormField(R.string.search, "group", -1, 2) @FormField(R.string.search, "group", -1, 2)
var search = SearchSettings(); var search = SearchSettings();
@Serializable @Serializable
class SearchSettings { class SearchSettings {
@FormField(R.string.search_history, FieldForm.TOGGLE, -1, 4) @FormField(R.string.search_history, FieldForm.TOGGLE, R.string.may_require_restart, 3)
@Serializable(with = FlexibleBooleanSerializer::class) @Serializable(with = FlexibleBooleanSerializer::class)
var searchHistory: Boolean = true; var searchHistory: Boolean = true;
@FormField(R.string.feed_style, FieldForm.DROPDOWN, -1, 5) @FormField(R.string.feed_style, FieldForm.DROPDOWN, -1, 4)
@DropdownFieldOptionsId(R.array.feed_style) @DropdownFieldOptionsId(R.array.feed_style)
var searchFeedStyle: Int = 1; var searchFeedStyle: Int = 1;
@FormField(R.string.preview_feed_items, FieldForm.TOGGLE, R.string.preview_feed_items_description, 5)
var previewFeedItems: Boolean = true;
fun getSearchFeedStyle(): FeedStyle { fun getSearchFeedStyle(): FeedStyle {
if(searchFeedStyle == 0) if(searchFeedStyle == 0)
@ -164,7 +198,7 @@ class Settings : FragmentedStorageFileJson() {
var subscriptions = SubscriptionsSettings(); var subscriptions = SubscriptionsSettings();
@Serializable @Serializable
class SubscriptionsSettings { class SubscriptionsSettings {
@FormField(R.string.feed_style, FieldForm.DROPDOWN, -1, 5) @FormField(R.string.feed_style, FieldForm.DROPDOWN, R.string.may_require_restart, 4)
@DropdownFieldOptionsId(R.array.feed_style) @DropdownFieldOptionsId(R.array.feed_style)
var subscriptionsFeedStyle: Int = 1; var subscriptionsFeedStyle: Int = 1;
@ -175,6 +209,9 @@ class Settings : FragmentedStorageFileJson() {
return FeedStyle.THUMBNAIL; return FeedStyle.THUMBNAIL;
} }
@FormField(R.string.preview_feed_items, FieldForm.TOGGLE, R.string.preview_feed_items_description, 5)
var previewFeedItems: Boolean = true;
@FormField(R.string.fetch_on_app_boot, FieldForm.TOGGLE, R.string.shortly_after_opening_the_app_start_fetching_subscriptions, 6) @FormField(R.string.fetch_on_app_boot, FieldForm.TOGGLE, R.string.shortly_after_opening_the_app_start_fetching_subscriptions, 6)
@Serializable(with = FlexibleBooleanSerializer::class) @Serializable(with = FlexibleBooleanSerializer::class)
var fetchOnAppBoot: Boolean = true; var fetchOnAppBoot: Boolean = true;
@ -208,6 +245,7 @@ class Settings : FragmentedStorageFileJson() {
@FormField(R.string.track_playtime_locally, FieldForm.TOGGLE, R.string.track_playtime_locally_description, 10) @FormField(R.string.track_playtime_locally, FieldForm.TOGGLE, R.string.track_playtime_locally_description, 10)
var allowPlaytimeTracking: Boolean = true; var allowPlaytimeTracking: Boolean = true;
} }
@FormField(R.string.player, "group", R.string.change_behavior_of_the_player, 4) @FormField(R.string.player, "group", R.string.change_behavior_of_the_player, 4)
@ -215,10 +253,10 @@ class Settings : FragmentedStorageFileJson() {
@Serializable @Serializable
class PlaybackSettings { class PlaybackSettings {
@FormField(R.string.primary_language, FieldForm.DROPDOWN, -1, 0) @FormField(R.string.primary_language, FieldForm.DROPDOWN, -1, 0)
@DropdownFieldOptionsId(R.array.languages) @DropdownFieldOptionsId(R.array.audio_languages)
var primaryLanguage: Int = 0; var primaryLanguage: Int = 0;
fun getPrimaryLanguage(context: Context) = context.resources.getStringArray(R.array.languages)[primaryLanguage]; fun getPrimaryLanguage(context: Context) = context.resources.getStringArray(R.array.audio_languages)[primaryLanguage];
@FormField(R.string.default_playback_speed, FieldForm.DROPDOWN, -1, 1) @FormField(R.string.default_playback_speed, FieldForm.DROPDOWN, -1, 1)
@DropdownFieldOptionsId(R.array.playback_speeds) @DropdownFieldOptionsId(R.array.playback_speeds)
@ -277,10 +315,6 @@ class Settings : FragmentedStorageFileJson() {
@DropdownFieldOptionsId(R.array.resume_after_preview) @DropdownFieldOptionsId(R.array.resume_after_preview)
var resumeAfterPreview: Int = 1; var resumeAfterPreview: Int = 1;
@FormField(R.string.live_chat_webview, FieldForm.TOGGLE, R.string.use_the_live_chat_web_window_when_available_over_native_implementation, 8)
var useLiveChatWindow: Boolean = true;
fun shouldResumePreview(previewedPosition: Long): Boolean{ fun shouldResumePreview(previewedPosition: Long): Boolean{
if(resumeAfterPreview == 2) if(resumeAfterPreview == 2)
return true; return true;
@ -288,6 +322,14 @@ class Settings : FragmentedStorageFileJson() {
return true; return true;
return false; return false;
} }
@FormField(R.string.live_chat_webview, FieldForm.TOGGLE, R.string.use_the_live_chat_web_window_when_available_over_native_implementation, 8)
var useLiveChatWindow: Boolean = true;
@FormField(R.string.background_switch_audio, FieldForm.TOGGLE, R.string.background_switch_audio_description, 8)
var backgroundSwitchToAudio: Boolean = true;
} }
@FormField(R.string.downloads, "group", R.string.configure_downloading_of_videos, 5) @FormField(R.string.downloads, "group", R.string.configure_downloading_of_videos, 5)

View file

@ -7,6 +7,7 @@ import android.content.pm.ActivityInfo
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
import android.widget.FrameLayout import android.widget.FrameLayout
@ -154,6 +155,11 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
} }
} }
override fun attachBaseContext(newBase: Context?) {
Logger.i(TAG, "MainActivity.attachBaseContext")
super.attachBaseContext(StateApp.instance.getLocaleContext(newBase))
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
StateApp.instance.setGlobalContext(this, lifecycleScope); StateApp.instance.setGlobalContext(this, lifecycleScope);
StateApp.instance.mainAppStarting(this); StateApp.instance.mainAppStarting(this);

View file

@ -1,6 +1,7 @@
package com.futo.platformplayer.activities package com.futo.platformplayer.activities
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
@ -13,6 +14,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.* import com.futo.platformplayer.*
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.views.Loader import com.futo.platformplayer.views.Loader
import com.futo.platformplayer.views.fields.FieldForm import com.futo.platformplayer.views.fields.FieldForm
import com.futo.platformplayer.views.fields.ReadOnlyTextField import com.futo.platformplayer.views.fields.ReadOnlyTextField
@ -28,6 +30,10 @@ class SettingsActivity : AppCompatActivity(), IWithResultLauncher {
private var _isFinished = false; private var _isFinished = false;
override fun attachBaseContext(newBase: Context?) {
Logger.i("SettingsActivity", "SettingsActivity.attachBaseContext")
super.attachBaseContext(StateApp.instance.getLocaleContext(newBase))
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings); setContentView(R.layout.activity_settings);
@ -43,6 +49,11 @@ class SettingsActivity : AppCompatActivity(), IWithResultLauncher {
Logger.i("SettingsActivity", "Setting [${field.field?.name}] changed, saving"); Logger.i("SettingsActivity", "Setting [${field.field?.name}] changed, saving");
_form.setObjectValues(); _form.setObjectValues();
Settings.instance.save(); Settings.instance.save();
if(field.descriptor?.id == "app_language") {
Logger.i("SettingsActivity", "App language change detected, propogating to shared preferences");
StateApp.instance.setLocaleSetting(this, Settings.instance.language.getAppLanguageLocaleString());
}
}; };
_buttonBack.setOnClickListener { _buttonBack.setOnClickListener {
finish(); finish();

View file

@ -63,7 +63,7 @@ class ContentSearchResultsFragment : MainFragment() {
} }
fun setPreviewsEnabled(previewsEnabled: Boolean) { fun setPreviewsEnabled(previewsEnabled: Boolean) {
_view?.setPreviewsEnabled(previewsEnabled); _view?.setPreviewsEnabled(previewsEnabled && Settings.instance.search.previewFeedItems);
} }
@SuppressLint("ViewConstructor") @SuppressLint("ViewConstructor")
@ -93,6 +93,8 @@ class ContentSearchResultsFragment : MainFragment() {
Logger.w(TAG, "Failed to load results.", it); Logger.w(TAG, "Failed to load results.", it);
UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() }); UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() });
} }
setPreviewsEnabled(Settings.instance.search.previewFeedItems);
} }
override fun cleanup() { override fun cleanup() {

View file

@ -78,7 +78,7 @@ class HomeFragment : MainFragment() {
} }
fun setPreviewsEnabled(previewsEnabled: Boolean) { fun setPreviewsEnabled(previewsEnabled: Boolean) {
_view?.setPreviewsEnabled(previewsEnabled); _view?.setPreviewsEnabled(previewsEnabled && Settings.instance.home.previewFeedItems);
} }
@SuppressLint("ViewConstructor") @SuppressLint("ViewConstructor")
@ -122,6 +122,8 @@ class HomeFragment : MainFragment() {
setLoading(false); setLoading(false);
}; };
}; };
setPreviewsEnabled(Settings.instance.home.previewFeedItems);
} }
fun onShown() { fun onShown() {

View file

@ -81,7 +81,7 @@ class SubscriptionsFeedFragment : MainFragment() {
} }
fun setPreviewsEnabled(previewsEnabled: Boolean) { fun setPreviewsEnabled(previewsEnabled: Boolean) {
_view?.setPreviewsEnabled(previewsEnabled); _view?.setPreviewsEnabled(previewsEnabled && Settings.instance.subscriptions.previewFeedItems);
} }
@SuppressLint("ViewConstructor") @SuppressLint("ViewConstructor")
@ -108,6 +108,8 @@ class SubscriptionsFeedFragment : MainFragment() {
}; };
initializeToolbarContent(); initializeToolbarContent();
setPreviewsEnabled(Settings.instance.subscriptions.previewFeedItems);
} }
fun onShown() { fun onShown() {

View file

@ -814,7 +814,7 @@ class VideoDetailView : ConstraintLayout {
when (Settings.instance.playback.backgroundPlay) { when (Settings.instance.playback.backgroundPlay) {
0 -> handlePause(); 0 -> handlePause();
1 -> { 1 -> {
if(!(video?.isLive ?: false)) if(!(video?.isLive ?: false) && Settings.instance.playback.backgroundSwitchToAudio)
_player.switchToAudioMode(); _player.switchToAudioMode();
StatePlayer.instance.startOrUpdateMediaSession(context, video); StatePlayer.instance.startOrUpdateMediaSession(context, video);
} }

View file

@ -731,6 +731,34 @@ class StateApp {
} }
} }
fun getLocaleContext(baseContext: Context?): Context? {
val locale = getLocaleSetting(baseContext);
try {
if (baseContext != null && locale != null) {
val config = baseContext.resources.configuration;
config.setLocale(locale);
return baseContext.createConfigurationContext(config);
}
return baseContext;
}
catch (ex: Throwable) {
Logger.e(TAG, "Failed to load locale", ex);
return baseContext;
}
}
fun getLocaleSetting(context: Context?): Locale? {
return context?.getSharedPreferences("language", Context.MODE_PRIVATE)
?.getString("language", null)
?.let { Locale(it) };
}
fun setLocaleSetting(context: Context?, locale: String?) {
context?.getSharedPreferences("language", Context.MODE_PRIVATE)
?.edit()
?.putString("language", locale)
?.apply();
}
companion object { companion object {
private val TAG = "StateApp"; private val TAG = "StateApp";
@SuppressLint("StaticFieldLeak") //This is only alive while MainActivity is alive @SuppressLint("StaticFieldLeak") //This is only alive while MainActivity is alive

View file

@ -34,7 +34,8 @@ class CommentsList : ConstraintLayout {
} }
.exception<Throwable> { .exception<Throwable> {
Logger.e(TAG, "Failed to load comments.", it); Logger.e(TAG, "Failed to load comments.", it);
UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_comments) + (it.message ?: ""), it, ::fetchComments); UIDialogs.toast(context, context.getString(R.string.failed_to_load_comments) + "\n" + (it.message ?: ""));
//UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_comments) + (it.message ?: ""), it, ::fetchComments);
setLoading(false); setLoading(false);
} else TaskHandler(IPlatformVideoDetails::class.java, StateApp.instance.scopeGetter); } else TaskHandler(IPlatformVideoDetails::class.java, StateApp.instance.scopeGetter);

View file

@ -701,7 +701,7 @@
<item>استئناف بعد 10 ثوان</item> <item>استئناف بعد 10 ثوان</item>
<item>استئناف دائم</item> <item>استئناف دائم</item>
</string-array> </string-array>
<string-array name="languages"> <string-array name="audio_languages">
<item>الإنجليزية</item> <item>الإنجليزية</item>
<item>الإسبانية</item> <item>الإسبانية</item>
<item>الفرنسية</item> <item>الفرنسية</item>

View file

@ -701,7 +701,7 @@
<item>Nach 10 Sekunden fortsetzen</item> <item>Nach 10 Sekunden fortsetzen</item>
<item>Immer fortsetzen</item> <item>Immer fortsetzen</item>
</string-array> </string-array>
<string-array name="languages"> <string-array name="audio_languages">
<item>Englisch</item> <item>Englisch</item>
<item>Spanisch</item> <item>Spanisch</item>
<item>Französisch</item> <item>Französisch</item>

View file

@ -717,7 +717,7 @@
<item>Reanudar Después de 10s</item> <item>Reanudar Después de 10s</item>
<item>Siempre Reanudar</item> <item>Siempre Reanudar</item>
</string-array> </string-array>
<string-array name="languages"> <string-array name="audio_languages">
<item>Inglés</item> <item>Inglés</item>
<item>Español</item> <item>Español</item>
<item>Francés</item> <item>Francés</item>

View file

@ -701,7 +701,7 @@
<item>Reprendre après 10s</item> <item>Reprendre après 10s</item>
<item>Toujours reprendre</item> <item>Toujours reprendre</item>
</string-array> </string-array>
<string-array name="languages"> <string-array name="audio_languages">
<item>Anglais</item> <item>Anglais</item>
<item>Espagnol</item> <item>Espagnol</item>
<item>Français</item> <item>Français</item>

View file

@ -701,7 +701,7 @@
<item>10秒後から再開</item> <item>10秒後から再開</item>
<item>常に再開</item> <item>常に再開</item>
</string-array> </string-array>
<string-array name="languages"> <string-array name="audio_languages">
<item>英語</item> <item>英語</item>
<item>スペイン語</item> <item>スペイン語</item>
<item>フランス語</item> <item>フランス語</item>

View file

@ -701,7 +701,7 @@
<item>10초 후에 이어서</item> <item>10초 후에 이어서</item>
<item>항상 이어서</item> <item>항상 이어서</item>
</string-array> </string-array>
<string-array name="languages"> <string-array name="audio_languages">
<item>영어</item> <item>영어</item>
<item>스페인어</item> <item>스페인어</item>
<item>프랑스어</item> <item>프랑스어</item>

View file

@ -701,7 +701,7 @@
<item>Continuar Após 10s</item> <item>Continuar Após 10s</item>
<item>Sempre Continuar</item> <item>Sempre Continuar</item>
</string-array> </string-array>
<string-array name="languages"> <string-array name="audio_languages">
<item>Inglês</item> <item>Inglês</item>
<item>Espanhol</item> <item>Espanhol</item>
<item>Francês</item> <item>Francês</item>

View file

@ -701,7 +701,7 @@
<item>Продолжить после 10 секунд</item> <item>Продолжить после 10 секунд</item>
<item>Всегда продолжать</item> <item>Всегда продолжать</item>
</string-array> </string-array>
<string-array name="languages"> <string-array name="audio_languages">
<item>Английский</item> <item>Английский</item>
<item>Испанский</item> <item>Испанский</item>
<item>Французский</item> <item>Французский</item>

View file

@ -701,7 +701,7 @@
<item>预览后10秒继续</item> <item>预览后10秒继续</item>
<item>始终继续</item> <item>始终继续</item>
</string-array> </string-array>
<string-array name="languages"> <string-array name="audio_languages">
<item>英语</item> <item>英语</item>
<item>西班牙语</item> <item>西班牙语</item>
<item>法语</item> <item>法语</item>

View file

@ -312,11 +312,17 @@
<string name="import_data_description">Select a file to import, support various files (alternative to opening directly)</string> <string name="import_data_description">Select a file to import, support various files (alternative to opening directly)</string>
<string name="external_storage">External Storage</string> <string name="external_storage">External Storage</string>
<string name="feed_style">Feed Style</string> <string name="feed_style">Feed Style</string>
<string name="app_language">App Language</string>
<string name="may_require_restart">May require restart</string>
<string name="fetch_on_app_boot">Fetch on app boot</string> <string name="fetch_on_app_boot">Fetch on app boot</string>
<string name="get_answers_to_common_questions">Get answers to common questions</string> <string name="get_answers_to_common_questions">Get answers to common questions</string>
<string name="give_feedback_on_the_application">Give feedback on the application</string> <string name="give_feedback_on_the_application">Give feedback on the application</string>
<string name="info">Info</string> <string name="info">Info</string>
<string name="live_chat_webview">Live Chat Webview</string> <string name="live_chat_webview">Live Chat Webview</string>
<string name="background_switch_audio">Switch to Audio in Background</string>
<string name="background_switch_audio_description">Optimize bandwidth usage by switching to audio-only stream in background if available, may cause stutter</string>
<string name="preview_feed_items">Preview Feed Items</string>
<string name="preview_feed_items_description">When the preview feedstyle is used, if items should auto-preview when scrolling over them</string>
<string name="log_level">Log Level</string> <string name="log_level">Log Level</string>
<string name="logging">Logging</string> <string name="logging">Logging</string>
<string name="manage_polycentric_identity">Manage Polycentric identity</string> <string name="manage_polycentric_identity">Manage Polycentric identity</string>
@ -730,6 +736,19 @@
<item>Preview</item> <item>Preview</item>
<item>List</item> <item>List</item>
</string-array> </string-array>
<string-array name="app_languages">
<item>System</item>
<item>English (EN)</item>
<item>German (DE)</item>
<item>Spanish (ES)</item>
<item>Portuguese (PT)</item>
<item>French (FR)</item>
<item>Japanese (JA)</item>
<item>Korean (KO)</item>
<item>Chinese (ZH)</item>
<item>Russian (RU)</item>
<item>Arabic (AR)</item>
</string-array>
<string-array name="player_background_behavior"> <string-array name="player_background_behavior">
<item>None</item> <item>None</item>
<item>Keep Playing</item> <item>Keep Playing</item>
@ -740,7 +759,7 @@
<item>Resume After 10s</item> <item>Resume After 10s</item>
<item>Always Resume</item> <item>Always Resume</item>
</string-array> </string-array>
<string-array name="languages"> <string-array name="audio_languages">
<item>English</item> <item>English</item>
<item>Spanish</item> <item>Spanish</item>
<item>French</item> <item>French</item>