diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index 470f8601..f074d7c4 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -4,9 +4,7 @@ import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.net.Uri -import android.os.Build import android.webkit.CookieManager -import androidx.core.content.ContextCompat.startActivity import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.activities.* import com.futo.platformplayer.api.http.ManagedHttpClient @@ -30,6 +28,7 @@ import kotlinx.serialization.* import kotlinx.serialization.json.* import java.io.File import java.time.OffsetDateTime +import java.util.Locale @Serializable data class MenuBottomBarSetting(val id: Int, var enabled: Boolean); @@ -46,7 +45,7 @@ class Settings : FragmentedStorageFileJson() { @FormField( 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) fun managePolycentricIdentity() { @@ -61,7 +60,7 @@ class Settings : FragmentedStorageFileJson() { @FormField( 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) fun openFAQ() { @@ -74,7 +73,7 @@ class Settings : FragmentedStorageFileJson() { } @FormField( 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) fun openIssues() { @@ -109,7 +108,7 @@ class Settings : FragmentedStorageFileJson() { @FormField( 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) 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) var home = HomeSettings(); @Serializable 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) var homeFeedStyle: Int = 1; @@ -136,21 +163,28 @@ class Settings : FragmentedStorageFileJson() { else 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) var search = SearchSettings(); @Serializable 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) 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) 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 { if(searchFeedStyle == 0) @@ -164,7 +198,7 @@ class Settings : FragmentedStorageFileJson() { var subscriptions = SubscriptionsSettings(); @Serializable 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) var subscriptionsFeedStyle: Int = 1; @@ -175,6 +209,9 @@ class Settings : FragmentedStorageFileJson() { 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) @Serializable(with = FlexibleBooleanSerializer::class) 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) var allowPlaytimeTracking: Boolean = true; + } @FormField(R.string.player, "group", R.string.change_behavior_of_the_player, 4) @@ -215,10 +253,10 @@ class Settings : FragmentedStorageFileJson() { @Serializable class PlaybackSettings { @FormField(R.string.primary_language, FieldForm.DROPDOWN, -1, 0) - @DropdownFieldOptionsId(R.array.languages) + @DropdownFieldOptionsId(R.array.audio_languages) 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) @DropdownFieldOptionsId(R.array.playback_speeds) @@ -277,10 +315,6 @@ class Settings : FragmentedStorageFileJson() { @DropdownFieldOptionsId(R.array.resume_after_preview) 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{ if(resumeAfterPreview == 2) return true; @@ -288,6 +322,14 @@ class Settings : FragmentedStorageFileJson() { return true; 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) diff --git a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt index 877ded06..d0fa3628 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt @@ -7,6 +7,7 @@ import android.content.pm.ActivityInfo import android.content.res.Configuration import android.net.Uri import android.os.Bundle +import android.preference.PreferenceManager import android.util.TypedValue import android.view.View 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?) { StateApp.instance.setGlobalContext(this, lifecycleScope); StateApp.instance.mainAppStarting(this); 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 ce9b12e1..1925a1c9 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt @@ -1,6 +1,7 @@ package com.futo.platformplayer.activities import android.annotation.SuppressLint +import android.content.Context import android.content.Intent import android.os.Bundle import android.view.View @@ -13,6 +14,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.* import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.views.Loader import com.futo.platformplayer.views.fields.FieldForm import com.futo.platformplayer.views.fields.ReadOnlyTextField @@ -28,6 +30,10 @@ class SettingsActivity : AppCompatActivity(), IWithResultLauncher { 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?) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_settings); @@ -43,6 +49,11 @@ class SettingsActivity : AppCompatActivity(), IWithResultLauncher { Logger.i("SettingsActivity", "Setting [${field.field?.name}] changed, saving"); _form.setObjectValues(); 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 { finish(); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt index 6ca87a28..523eb110 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt @@ -63,7 +63,7 @@ class ContentSearchResultsFragment : MainFragment() { } fun setPreviewsEnabled(previewsEnabled: Boolean) { - _view?.setPreviewsEnabled(previewsEnabled); + _view?.setPreviewsEnabled(previewsEnabled && Settings.instance.search.previewFeedItems); } @SuppressLint("ViewConstructor") @@ -93,6 +93,8 @@ class ContentSearchResultsFragment : MainFragment() { Logger.w(TAG, "Failed to load results.", it); UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() }); } + + setPreviewsEnabled(Settings.instance.search.previewFeedItems); } override fun cleanup() { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt index 12e6550c..fea259af 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt @@ -78,7 +78,7 @@ class HomeFragment : MainFragment() { } fun setPreviewsEnabled(previewsEnabled: Boolean) { - _view?.setPreviewsEnabled(previewsEnabled); + _view?.setPreviewsEnabled(previewsEnabled && Settings.instance.home.previewFeedItems); } @SuppressLint("ViewConstructor") @@ -122,6 +122,8 @@ class HomeFragment : MainFragment() { setLoading(false); }; }; + + setPreviewsEnabled(Settings.instance.home.previewFeedItems); } fun onShown() { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt index c418b0e9..87b12759 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt @@ -81,7 +81,7 @@ class SubscriptionsFeedFragment : MainFragment() { } fun setPreviewsEnabled(previewsEnabled: Boolean) { - _view?.setPreviewsEnabled(previewsEnabled); + _view?.setPreviewsEnabled(previewsEnabled && Settings.instance.subscriptions.previewFeedItems); } @SuppressLint("ViewConstructor") @@ -108,6 +108,8 @@ class SubscriptionsFeedFragment : MainFragment() { }; initializeToolbarContent(); + + setPreviewsEnabled(Settings.instance.subscriptions.previewFeedItems); } fun onShown() { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index 308cd63c..3b22c358 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -812,7 +812,7 @@ class VideoDetailView : ConstraintLayout { when (Settings.instance.playback.backgroundPlay) { 0 -> handlePause(); 1 -> { - if(!(video?.isLive ?: false)) + if(!(video?.isLive ?: false) && Settings.instance.playback.backgroundSwitchToAudio) _player.switchToAudioMode(); StatePlayer.instance.startOrUpdateMediaSession(context, video); } diff --git a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt index 586ebf20..8181adb1 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt @@ -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 { private val TAG = "StateApp"; @SuppressLint("StaticFieldLeak") //This is only alive while MainActivity is alive diff --git a/app/src/main/java/com/futo/platformplayer/views/segments/CommentsList.kt b/app/src/main/java/com/futo/platformplayer/views/segments/CommentsList.kt index 9841bfd9..0677aa99 100644 --- a/app/src/main/java/com/futo/platformplayer/views/segments/CommentsList.kt +++ b/app/src/main/java/com/futo/platformplayer/views/segments/CommentsList.kt @@ -34,7 +34,8 @@ class CommentsList : ConstraintLayout { } .exception { 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); } else TaskHandler(IPlatformVideoDetails::class.java, StateApp.instance.scopeGetter); diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 3beb1c91..fd1562c7 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -697,7 +697,7 @@ استئناف بعد 10 ثوان استئناف دائم - + الإنجليزية الإسبانية الفرنسية diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 59c449eb..115d99eb 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -697,7 +697,7 @@ Nach 10 Sekunden fortsetzen Immer fortsetzen - + Englisch Spanisch Französisch diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e1fa17e4..7b5e5782 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -713,7 +713,7 @@ Reanudar Después de 10s Siempre Reanudar - + Inglés Español Francés diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 0a68d5e6..d1d01748 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -697,7 +697,7 @@ Reprendre après 10s Toujours reprendre - + Anglais Espagnol Français diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index a8c94763..414de250 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -697,7 +697,7 @@ 10秒後から再開 常に再開 - + 英語 スペイン語 フランス語 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 1e0e78fe..07b884e4 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -697,7 +697,7 @@ 10초 후에 이어서 항상 이어서 - + 영어 스페인어 프랑스어 diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 3c1e45f4..32904cb2 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -697,7 +697,7 @@ Continuar Após 10s Sempre Continuar - + Inglês Espanhol Francês diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 172c29be..10b5522c 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -697,7 +697,7 @@ Продолжить после 10 секунд Всегда продолжать - + Английский Испанский Французский diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 398874b8..569db7e6 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -697,7 +697,7 @@ 预览后10秒继续 始终继续 - + 英语 西班牙语 法语 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dee4fc7d..a7367b0e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -307,11 +307,17 @@ Select a file to import, support various files (alternative to opening directly) External Storage Feed Style + App Language + May require restart Fetch on app boot Get answers to common questions Give feedback on the application Info Live Chat Webview + Switch to Audio in Background + Optimize bandwidth usage by switching to audio-only stream in background if available, may cause stutter + Preview Feed Items + When the preview feedstyle is used, if items should auto-preview when scrolling over them Log Level Logging Manage Polycentric identity @@ -724,6 +730,19 @@ Preview List + + System + English (EN) + German (DE) + Spanish (ES) + Portuguese (PT) + French (FR) + Japanese (JA) + Korean (KO) + Chinese (ZH) + Russian (RU) + Arabic (AR) + None Keep Playing @@ -734,7 +753,7 @@ Resume After 10s Always Resume - + English Spanish French