diff --git a/android/app/src/main/kotlin/com/edde746/plezy/MainActivity.kt b/android/app/src/main/kotlin/com/edde746/plezy/MainActivity.kt index c23b5ff7..4ef3e3b2 100644 --- a/android/app/src/main/kotlin/com/edde746/plezy/MainActivity.kt +++ b/android/app/src/main/kotlin/com/edde746/plezy/MainActivity.kt @@ -26,12 +26,36 @@ class MainActivity : FlutterActivity() { private val PIP_CHANNEL = "app.plezy/pip" private val EXTERNAL_PLAYER_CHANNEL = "app.plezy/external_player" + private val THEME_CHANNEL = "app.plezy/theme" private var watchNextPlugin: WatchNextPlugin? = null private var cachedFlutterView: android.view.View? = null override fun onCreate(savedInstanceState: Bundle?) { + // Apply persisted theme color to the window background before anything + // else renders. This prevents a white flash between the native splash + // screen and Flutter's first frame for non-default themes (e.g. OLED). + val prefs = getSharedPreferences("plezy_prefs", Context.MODE_PRIVATE) + val savedTheme = prefs.getString("splash_theme", null) + if (savedTheme != null) { + val color = when (savedTheme) { + "oled" -> android.graphics.Color.BLACK + "dark" -> android.graphics.Color.parseColor("#0E0F12") + "light" -> android.graphics.Color.parseColor("#F7F7F8") + else -> null + } + if (color != null) { + window.decorView.setBackgroundColor(color) + } + } + super.onCreate(savedInstanceState) + // Disable the Android splash screen fade-out animation to avoid + // a flicker before Flutter draws its first frame. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + splashScreen.setOnExitAnimationListener { splashScreenView -> splashScreenView.remove() } + } + // Disable Android's default focus highlight ring that appears when using // D-pad navigation so the Flutter UI can render its own focus state. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -153,6 +177,41 @@ class MainActivity : FlutterActivity() { } } + // Splash screen theme: persist user's chosen theme for next launch (API 31+) + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, THEME_CHANNEL).setMethodCallHandler { call, result -> + when (call.method) { + "setSplashTheme" -> { + val mode = call.argument("mode") + + // Persist for next cold start & update window background now + getSharedPreferences("plezy_prefs", Context.MODE_PRIVATE) + .edit().putString("splash_theme", mode).apply() + val color = when (mode) { + "oled" -> android.graphics.Color.BLACK + "dark" -> android.graphics.Color.parseColor("#0E0F12") + "light" -> android.graphics.Color.parseColor("#F7F7F8") + else -> null + } + if (color != null) { + window.decorView.setBackgroundColor(color) + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val themeId = when (mode) { + "dark" -> R.style.SplashTheme_Dark + "oled" -> R.style.SplashTheme_Oled + "light" -> R.style.SplashTheme_Light + "system" -> android.content.res.Resources.ID_NULL + else -> android.content.res.Resources.ID_NULL + } + splashScreen.setSplashScreenTheme(themeId) + } + result.success(true) + } + else -> result.notImplemented() + } + } + // Register Watch Next plugin and keep reference for deep link handling watchNextPlugin = WatchNextPlugin() flutterEngine.plugins.add(watchNextPlugin!!) diff --git a/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 2727eef3..00000000 Binary files a/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png deleted file mode 100644 index 31363756..00000000 Binary files a/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index 7d04f18d..00000000 Binary files a/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 4ccdb0b7..00000000 Binary files a/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index cee25601..00000000 Binary files a/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..319552c7 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/ic_launcher_monochrome.xml b/android/app/src/main/res/drawable/ic_launcher_monochrome.xml new file mode 100644 index 00000000..d456d3be --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_monochrome.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/splash_icon.xml b/android/app/src/main/res/drawable/splash_icon.xml new file mode 100644 index 00000000..35edc6c5 --- /dev/null +++ b/android/app/src/main/res/drawable/splash_icon.xml @@ -0,0 +1,3 @@ + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 6fee2a96..93f6386e 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,14 +1,6 @@ - - - - - - + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png deleted file mode 100644 index 823e1e79..00000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png deleted file mode 100644 index ad0470ec..00000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png deleted file mode 100644 index 6d4c1025..00000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png deleted file mode 100644 index 5e610a3c..00000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png deleted file mode 100644 index 9d7b47e6..00000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png and /dev/null differ diff --git a/android/app/src/main/res/values-night-v31/styles.xml b/android/app/src/main/res/values-night-v31/styles.xml new file mode 100644 index 00000000..2f67f54d --- /dev/null +++ b/android/app/src/main/res/values-night-v31/styles.xml @@ -0,0 +1,39 @@ + + + + + + + + + + diff --git a/android/app/src/main/res/values-night/colors.xml b/android/app/src/main/res/values-night/colors.xml index 229435b0..c2158f1d 100644 --- a/android/app/src/main/res/values-night/colors.xml +++ b/android/app/src/main/res/values-night/colors.xml @@ -1,4 +1,8 @@ #1a1a1a + #0E0F12 + #0E0F12 + #000000 + #F7F7F8 diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml index f86351da..01337e8d 100644 --- a/android/app/src/main/res/values-night/styles.xml +++ b/android/app/src/main/res/values-night/styles.xml @@ -2,6 +2,7 @@ + + + + + + diff --git a/android/app/src/main/res/values-v31/styles.xml b/android/app/src/main/res/values-v31/styles.xml new file mode 100644 index 00000000..1aa7df0a --- /dev/null +++ b/android/app/src/main/res/values-v31/styles.xml @@ -0,0 +1,39 @@ + + + + + + + + + + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index ab983282..a4535a0b 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -1,4 +1,8 @@ #ffffff + #F7F7F8 + #0E0F12 + #000000 + #F7F7F8 \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index dfada3ae..c927cb19 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -2,6 +2,7 @@ + + + + + + diff --git a/assets/plezy_adaptive_foreground.svg b/assets/plezy_adaptive_foreground.svg new file mode 100644 index 00000000..257f1063 --- /dev/null +++ b/assets/plezy_adaptive_foreground.svg @@ -0,0 +1 @@ + diff --git a/assets/plezy_android_foreground.png b/assets/plezy_android_foreground.png deleted file mode 100644 index 68bda7f9..00000000 Binary files a/assets/plezy_android_foreground.png and /dev/null differ diff --git a/assets/plezy_monochrome.png b/assets/plezy_monochrome.png deleted file mode 100644 index 5e50d5bf..00000000 Binary files a/assets/plezy_monochrome.png and /dev/null differ diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index f4fb1154..fdfbba20 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -52,7 +52,12 @@ "exitConfirmMessage": "Möchtest du die App wirklich beenden?", "dontAskAgain": "Nicht erneut fragen", "exit": "Beenden", - "viewAll": "Alle anzeigen" + "viewAll": "Alle anzeigen", + "checkingNetwork": "Netzwerk wird geprüft...", + "refreshingServers": "Server werden aktualisiert...", + "loadingServers": "Server werden geladen...", + "connectingToServers": "Verbindung zu Servern...", + "startingOfflineMode": "Offlinemodus wird gestartet..." }, "screens": { "licenses": "Lizenzen", diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index dd069605..cbc18e9e 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -52,7 +52,12 @@ "exitConfirmMessage": "Are you sure you want to exit?", "dontAskAgain": "Don't ask again", "exit": "Exit", - "viewAll": "View All" + "viewAll": "View All", + "checkingNetwork": "Checking network...", + "refreshingServers": "Refreshing servers...", + "loadingServers": "Loading servers...", + "connectingToServers": "Connecting to servers...", + "startingOfflineMode": "Starting offline mode..." }, "screens": { "licenses": "Licenses", diff --git a/lib/i18n/es.i18n.json b/lib/i18n/es.i18n.json index 3440384f..7e80021f 100644 --- a/lib/i18n/es.i18n.json +++ b/lib/i18n/es.i18n.json @@ -52,7 +52,12 @@ "exitConfirmMessage": "¿Estás seguro de que quieres salir?", "dontAskAgain": "No volver a preguntar", "exit": "Salir", - "viewAll": "Ver todo" + "viewAll": "Ver todo", + "checkingNetwork": "Comprobando red...", + "refreshingServers": "Actualizando servidores...", + "loadingServers": "Cargando servidores...", + "connectingToServers": "Conectando a servidores...", + "startingOfflineMode": "Iniciando modo sin conexión..." }, "screens": { "licenses": "Licencias", diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index 585b23e0..9a0f1315 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -52,7 +52,12 @@ "exitConfirmMessage": "Êtes-vous sûr de vouloir quitter ?", "dontAskAgain": "Ne plus demander", "exit": "Quitter", - "viewAll": "Tout afficher" + "viewAll": "Tout afficher", + "checkingNetwork": "Vérification du réseau...", + "refreshingServers": "Actualisation des serveurs...", + "loadingServers": "Chargement des serveurs...", + "connectingToServers": "Connexion aux serveurs...", + "startingOfflineMode": "Démarrage en mode hors-ligne..." }, "screens": { "licenses": "Licenses", diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index 9c787968..b1da2926 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -52,7 +52,12 @@ "exitConfirmMessage": "Sei sicuro di voler uscire?", "dontAskAgain": "Non chiedere più", "exit": "Esci", - "viewAll": "Mostra tutto" + "viewAll": "Mostra tutto", + "checkingNetwork": "Verifica rete...", + "refreshingServers": "Aggiornamento server...", + "loadingServers": "Caricamento server...", + "connectingToServers": "Connessione ai server...", + "startingOfflineMode": "Avvio modalità offline..." }, "screens": { "licenses": "Licenze", diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index fa95896b..1fcca5ef 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -52,7 +52,12 @@ "exitConfirmMessage": "정말 종료하시겠습니까?", "dontAskAgain": "다시 묻지 않기", "exit": "종료", - "viewAll": "모두 보기" + "viewAll": "모두 보기", + "checkingNetwork": "네트워크 확인 중...", + "refreshingServers": "서버 새로고침 중...", + "loadingServers": "서버 로딩 중...", + "connectingToServers": "서버 연결 중...", + "startingOfflineMode": "오프라인 모드 시작 중..." }, "screens": { "licenses": "라이선스", diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index 6269ccd2..e08757ae 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -52,7 +52,12 @@ "exitConfirmMessage": "Weet je zeker dat je wilt afsluiten?", "dontAskAgain": "Niet meer vragen", "exit": "Afsluiten", - "viewAll": "Alles weergeven" + "viewAll": "Alles weergeven", + "checkingNetwork": "Netwerk controleren...", + "refreshingServers": "Servers vernieuwen...", + "loadingServers": "Servers laden...", + "connectingToServers": "Verbinden met servers...", + "startingOfflineMode": "Offlinemodus starten..." }, "screens": { "licenses": "Licenties", diff --git a/lib/i18n/strings_de.g.dart b/lib/i18n/strings_de.g.dart index 8a362f19..e89a84e9 100644 --- a/lib/i18n/strings_de.g.dart +++ b/lib/i18n/strings_de.g.dart @@ -151,6 +151,11 @@ class _TranslationsCommonDe implements TranslationsCommonEn { @override String get dontAskAgain => 'Nicht erneut fragen'; @override String get exit => 'Beenden'; @override String get viewAll => 'Alle anzeigen'; + @override String get checkingNetwork => 'Netzwerk wird geprüft...'; + @override String get refreshingServers => 'Server werden aktualisiert...'; + @override String get loadingServers => 'Server werden geladen...'; + @override String get connectingToServers => 'Verbindung zu Servern...'; + @override String get startingOfflineMode => 'Offlinemodus wird gestartet...'; } // Path: screens @@ -1338,6 +1343,11 @@ extension on TranslationsDe { 'common.dontAskAgain' => 'Nicht erneut fragen', 'common.exit' => 'Beenden', 'common.viewAll' => 'Alle anzeigen', + 'common.checkingNetwork' => 'Netzwerk wird geprüft...', + 'common.refreshingServers' => 'Server werden aktualisiert...', + 'common.loadingServers' => 'Server werden geladen...', + 'common.connectingToServers' => 'Verbindung zu Servern...', + 'common.startingOfflineMode' => 'Offlinemodus wird gestartet...', 'screens.licenses' => 'Lizenzen', 'screens.switchProfile' => 'Profil wechseln', 'screens.subtitleStyling' => 'Untertitel-Stil', diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index 2da53df6..285c991e 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -252,6 +252,21 @@ class TranslationsCommonEn { /// en: 'View All' String get viewAll => 'View All'; + + /// en: 'Checking network...' + String get checkingNetwork => 'Checking network...'; + + /// en: 'Refreshing servers...' + String get refreshingServers => 'Refreshing servers...'; + + /// en: 'Loading servers...' + String get loadingServers => 'Loading servers...'; + + /// en: 'Connecting to servers...' + String get connectingToServers => 'Connecting to servers...'; + + /// en: 'Starting offline mode...' + String get startingOfflineMode => 'Starting offline mode...'; } // Path: screens @@ -2930,6 +2945,11 @@ extension on Translations { 'common.dontAskAgain' => 'Don\'t ask again', 'common.exit' => 'Exit', 'common.viewAll' => 'View All', + 'common.checkingNetwork' => 'Checking network...', + 'common.refreshingServers' => 'Refreshing servers...', + 'common.loadingServers' => 'Loading servers...', + 'common.connectingToServers' => 'Connecting to servers...', + 'common.startingOfflineMode' => 'Starting offline mode...', 'screens.licenses' => 'Licenses', 'screens.switchProfile' => 'Switch Profile', 'screens.subtitleStyling' => 'Subtitle Styling', diff --git a/lib/i18n/strings_es.g.dart b/lib/i18n/strings_es.g.dart index 6d5f756c..2b36a0dd 100644 --- a/lib/i18n/strings_es.g.dart +++ b/lib/i18n/strings_es.g.dart @@ -151,6 +151,11 @@ class _TranslationsCommonEs implements TranslationsCommonEn { @override String get dontAskAgain => 'No volver a preguntar'; @override String get exit => 'Salir'; @override String get viewAll => 'Ver todo'; + @override String get checkingNetwork => 'Comprobando red...'; + @override String get refreshingServers => 'Actualizando servidores...'; + @override String get loadingServers => 'Cargando servidores...'; + @override String get connectingToServers => 'Conectando a servidores...'; + @override String get startingOfflineMode => 'Iniciando modo sin conexión...'; } // Path: screens @@ -1338,6 +1343,11 @@ extension on TranslationsEs { 'common.dontAskAgain' => 'No volver a preguntar', 'common.exit' => 'Salir', 'common.viewAll' => 'Ver todo', + 'common.checkingNetwork' => 'Comprobando red...', + 'common.refreshingServers' => 'Actualizando servidores...', + 'common.loadingServers' => 'Cargando servidores...', + 'common.connectingToServers' => 'Conectando a servidores...', + 'common.startingOfflineMode' => 'Iniciando modo sin conexión...', 'screens.licenses' => 'Licencias', 'screens.switchProfile' => 'Cambiar Perfil', 'screens.subtitleStyling' => 'Estilo de Subtítulos', diff --git a/lib/i18n/strings_fr.g.dart b/lib/i18n/strings_fr.g.dart index 53dab1ac..79dac6a8 100644 --- a/lib/i18n/strings_fr.g.dart +++ b/lib/i18n/strings_fr.g.dart @@ -151,6 +151,11 @@ class _TranslationsCommonFr implements TranslationsCommonEn { @override String get dontAskAgain => 'Ne plus demander'; @override String get exit => 'Quitter'; @override String get viewAll => 'Tout afficher'; + @override String get checkingNetwork => 'Vérification du réseau...'; + @override String get refreshingServers => 'Actualisation des serveurs...'; + @override String get loadingServers => 'Chargement des serveurs...'; + @override String get connectingToServers => 'Connexion aux serveurs...'; + @override String get startingOfflineMode => 'Démarrage en mode hors-ligne...'; } // Path: screens @@ -1338,6 +1343,11 @@ extension on TranslationsFr { 'common.dontAskAgain' => 'Ne plus demander', 'common.exit' => 'Quitter', 'common.viewAll' => 'Tout afficher', + 'common.checkingNetwork' => 'Vérification du réseau...', + 'common.refreshingServers' => 'Actualisation des serveurs...', + 'common.loadingServers' => 'Chargement des serveurs...', + 'common.connectingToServers' => 'Connexion aux serveurs...', + 'common.startingOfflineMode' => 'Démarrage en mode hors-ligne...', 'screens.licenses' => 'Licenses', 'screens.switchProfile' => 'Changer de profil', 'screens.subtitleStyling' => 'Configuration des sous-titres', diff --git a/lib/i18n/strings_it.g.dart b/lib/i18n/strings_it.g.dart index 20ea62c6..96039f0c 100644 --- a/lib/i18n/strings_it.g.dart +++ b/lib/i18n/strings_it.g.dart @@ -151,6 +151,11 @@ class _TranslationsCommonIt implements TranslationsCommonEn { @override String get dontAskAgain => 'Non chiedere più'; @override String get exit => 'Esci'; @override String get viewAll => 'Mostra tutto'; + @override String get checkingNetwork => 'Verifica rete...'; + @override String get refreshingServers => 'Aggiornamento server...'; + @override String get loadingServers => 'Caricamento server...'; + @override String get connectingToServers => 'Connessione ai server...'; + @override String get startingOfflineMode => 'Avvio modalità offline...'; } // Path: screens @@ -1338,6 +1343,11 @@ extension on TranslationsIt { 'common.dontAskAgain' => 'Non chiedere più', 'common.exit' => 'Esci', 'common.viewAll' => 'Mostra tutto', + 'common.checkingNetwork' => 'Verifica rete...', + 'common.refreshingServers' => 'Aggiornamento server...', + 'common.loadingServers' => 'Caricamento server...', + 'common.connectingToServers' => 'Connessione ai server...', + 'common.startingOfflineMode' => 'Avvio modalità offline...', 'screens.licenses' => 'Licenze', 'screens.switchProfile' => 'Cambia profilo', 'screens.subtitleStyling' => 'Stile sottotitoli', diff --git a/lib/i18n/strings_ko.g.dart b/lib/i18n/strings_ko.g.dart index c34fb3f1..891b5017 100644 --- a/lib/i18n/strings_ko.g.dart +++ b/lib/i18n/strings_ko.g.dart @@ -151,6 +151,11 @@ class _TranslationsCommonKo implements TranslationsCommonEn { @override String get dontAskAgain => '다시 묻지 않기'; @override String get exit => '종료'; @override String get viewAll => '모두 보기'; + @override String get checkingNetwork => '네트워크 확인 중...'; + @override String get refreshingServers => '서버 새로고침 중...'; + @override String get loadingServers => '서버 로딩 중...'; + @override String get connectingToServers => '서버 연결 중...'; + @override String get startingOfflineMode => '오프라인 모드 시작 중...'; } // Path: screens @@ -1338,6 +1343,11 @@ extension on TranslationsKo { 'common.dontAskAgain' => '다시 묻지 않기', 'common.exit' => '종료', 'common.viewAll' => '모두 보기', + 'common.checkingNetwork' => '네트워크 확인 중...', + 'common.refreshingServers' => '서버 새로고침 중...', + 'common.loadingServers' => '서버 로딩 중...', + 'common.connectingToServers' => '서버 연결 중...', + 'common.startingOfflineMode' => '오프라인 모드 시작 중...', 'screens.licenses' => '라이선스', 'screens.switchProfile' => '프로필 전환', 'screens.subtitleStyling' => '자막 스타일 설정', diff --git a/lib/i18n/strings_nl.g.dart b/lib/i18n/strings_nl.g.dart index da12c779..fc2ca405 100644 --- a/lib/i18n/strings_nl.g.dart +++ b/lib/i18n/strings_nl.g.dart @@ -151,6 +151,11 @@ class _TranslationsCommonNl implements TranslationsCommonEn { @override String get dontAskAgain => 'Niet meer vragen'; @override String get exit => 'Afsluiten'; @override String get viewAll => 'Alles weergeven'; + @override String get checkingNetwork => 'Netwerk controleren...'; + @override String get refreshingServers => 'Servers vernieuwen...'; + @override String get loadingServers => 'Servers laden...'; + @override String get connectingToServers => 'Verbinden met servers...'; + @override String get startingOfflineMode => 'Offlinemodus starten...'; } // Path: screens @@ -1338,6 +1343,11 @@ extension on TranslationsNl { 'common.dontAskAgain' => 'Niet meer vragen', 'common.exit' => 'Afsluiten', 'common.viewAll' => 'Alles weergeven', + 'common.checkingNetwork' => 'Netwerk controleren...', + 'common.refreshingServers' => 'Servers vernieuwen...', + 'common.loadingServers' => 'Servers laden...', + 'common.connectingToServers' => 'Verbinden met servers...', + 'common.startingOfflineMode' => 'Offlinemodus starten...', 'screens.licenses' => 'Licenties', 'screens.switchProfile' => 'Wissel van profiel', 'screens.subtitleStyling' => 'Ondertitel opmaak', diff --git a/lib/i18n/strings_sv.g.dart b/lib/i18n/strings_sv.g.dart index b7d2868b..735b8ab4 100644 --- a/lib/i18n/strings_sv.g.dart +++ b/lib/i18n/strings_sv.g.dart @@ -151,6 +151,11 @@ class _TranslationsCommonSv implements TranslationsCommonEn { @override String get dontAskAgain => 'Fråga inte igen'; @override String get exit => 'Avsluta'; @override String get viewAll => 'Visa alla'; + @override String get checkingNetwork => 'Kontrollerar nätverk...'; + @override String get refreshingServers => 'Uppdaterar servrar...'; + @override String get loadingServers => 'Laddar servrar...'; + @override String get connectingToServers => 'Ansluter till servrar...'; + @override String get startingOfflineMode => 'Startar offlineläge...'; } // Path: screens @@ -1338,6 +1343,11 @@ extension on TranslationsSv { 'common.dontAskAgain' => 'Fråga inte igen', 'common.exit' => 'Avsluta', 'common.viewAll' => 'Visa alla', + 'common.checkingNetwork' => 'Kontrollerar nätverk...', + 'common.refreshingServers' => 'Uppdaterar servrar...', + 'common.loadingServers' => 'Laddar servrar...', + 'common.connectingToServers' => 'Ansluter till servrar...', + 'common.startingOfflineMode' => 'Startar offlineläge...', 'screens.licenses' => 'Licenser', 'screens.switchProfile' => 'Byt profil', 'screens.subtitleStyling' => 'Undertext-styling', diff --git a/lib/i18n/strings_zh.g.dart b/lib/i18n/strings_zh.g.dart index 37a6fff0..013cfa06 100644 --- a/lib/i18n/strings_zh.g.dart +++ b/lib/i18n/strings_zh.g.dart @@ -151,6 +151,11 @@ class _TranslationsCommonZh implements TranslationsCommonEn { @override String get dontAskAgain => '不再询问'; @override String get exit => '退出'; @override String get viewAll => '查看全部'; + @override String get checkingNetwork => '正在检查网络...'; + @override String get refreshingServers => '正在刷新服务器...'; + @override String get loadingServers => '正在加载服务器...'; + @override String get connectingToServers => '正在连接服务器...'; + @override String get startingOfflineMode => '正在启动离线模式...'; } // Path: screens @@ -1338,6 +1343,11 @@ extension on TranslationsZh { 'common.dontAskAgain' => '不再询问', 'common.exit' => '退出', 'common.viewAll' => '查看全部', + 'common.checkingNetwork' => '正在检查网络...', + 'common.refreshingServers' => '正在刷新服务器...', + 'common.loadingServers' => '正在加载服务器...', + 'common.connectingToServers' => '正在连接服务器...', + 'common.startingOfflineMode' => '正在启动离线模式...', 'screens.licenses' => '许可证', 'screens.switchProfile' => '切换用户', 'screens.subtitleStyling' => '字幕样式', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index 2f52a17e..9a0761e9 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -52,7 +52,12 @@ "exitConfirmMessage": "Är du säker på att du vill avsluta?", "dontAskAgain": "Fråga inte igen", "exit": "Avsluta", - "viewAll": "Visa alla" + "viewAll": "Visa alla", + "checkingNetwork": "Kontrollerar nätverk...", + "refreshingServers": "Uppdaterar servrar...", + "loadingServers": "Laddar servrar...", + "connectingToServers": "Ansluter till servrar...", + "startingOfflineMode": "Startar offlineläge..." }, "screens": { "licenses": "Licenser", diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index 3453e72a..5b08f62c 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -52,7 +52,12 @@ "exitConfirmMessage": "确定要退出吗?", "dontAskAgain": "不再询问", "exit": "退出", - "viewAll": "查看全部" + "viewAll": "查看全部", + "checkingNetwork": "正在检查网络...", + "refreshingServers": "正在刷新服务器...", + "loadingServers": "正在加载服务器...", + "connectingToServers": "正在连接服务器...", + "startingOfflineMode": "正在启动离线模式..." }, "screens": { "licenses": "许可证", diff --git a/lib/main.dart b/lib/main.dart index 84820f9a..7f1cebd4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'package:flutter/gestures.dart'; import 'dart:io' show Platform; import 'package:window_manager/window_manager.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'screens/main_screen.dart'; import 'screens/auth_screen.dart'; import 'services/storage_service.dart'; @@ -46,6 +47,7 @@ import 'i18n/strings.g.dart'; import 'focus/input_mode_tracker.dart'; import 'focus/key_event_utils.dart'; import 'package:intl/date_symbol_data_local.dart'; +import 'utils/navigation_transitions.dart'; // Workaround for Flutter bug #177992: iPadOS 26.1+ misinterprets fake touch events // at (0,0) as barrier taps, causing modals to dismiss immediately. @@ -368,13 +370,21 @@ class SetupScreen extends StatefulWidget { } class _SetupScreenState extends State { + String _statusMessage = ''; + @override void initState() { super.initState(); _loadSavedCredentials(); } + void _setStatus(String message) { + if (mounted) setState(() => _statusMessage = message); + } + Future _loadSavedCredentials() async { + _setStatus(t.common.checkingNetwork); + final storage = await StorageService.getInstance(); final registry = ServerRegistry(storage); @@ -386,6 +396,8 @@ class _SetupScreenState extends State { final hasNetwork = !connectivityResult.contains(ConnectivityResult.none); if (hasNetwork) { + _setStatus(t.common.refreshingServers); + // Refresh servers from API to get updated connection info (IPs may change). // If the stored token is invalid (e.g. after removing a Plex profile PIN), // redirect to AuthScreen so the user can re-authenticate. @@ -393,18 +405,20 @@ class _SetupScreenState extends State { if (refreshResult == ServerRefreshResult.authError) { await storage.clearCredentials(); if (mounted) { - Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const AuthScreen())); + Navigator.pushReplacement(context, fadeRoute(const AuthScreen())); } return; } } + _setStatus(t.common.loadingServers); + // Load all configured servers final servers = await registry.getServers(); if (servers.isEmpty) { if (mounted) { - Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const AuthScreen())); + Navigator.pushReplacement(context, fadeRoute(const AuthScreen())); } return; } @@ -413,15 +427,18 @@ class _SetupScreenState extends State { // No network — skip connection attempts and go straight to offline mode if (!hasNetwork) { + _setStatus(t.common.startingOfflineMode); await context.read().ensureInitialized(); if (!mounted) return; Navigator.pushReplacement( context, - MaterialPageRoute(builder: (context) => const MainScreen(isOfflineMode: true)), + fadeRoute(const MainScreen(isOfflineMode: true)), ); return; } + _setStatus(t.common.connectingToServers); + try { final result = await ServerConnectionOrchestrator.connectAndInitialize( servers: servers, @@ -442,25 +459,27 @@ class _SetupScreenState extends State { Navigator.pushReplacement( context, - MaterialPageRoute(builder: (context) => MainScreen(client: result.firstClient!)), + fadeRoute(MainScreen(client: result.firstClient!)), ); } else { + _setStatus(t.common.startingOfflineMode); await context.read().ensureInitialized(); if (!mounted) return; Navigator.pushReplacement( context, - MaterialPageRoute(builder: (context) => const MainScreen(isOfflineMode: true)), + fadeRoute(const MainScreen(isOfflineMode: true)), ); } } catch (e, stackTrace) { appLogger.e('Error during multi-server connection', error: e, stackTrace: stackTrace); if (mounted) { + _setStatus(t.common.startingOfflineMode); await context.read().ensureInitialized(); if (!mounted) return; Navigator.pushReplacement( context, - MaterialPageRoute(builder: (context) => const MainScreen(isOfflineMode: true)), + fadeRoute(const MainScreen(isOfflineMode: true)), ); } } @@ -468,12 +487,33 @@ class _SetupScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [const CircularProgressIndicator(), const SizedBox(height: 16), Text(t.common.loading)], - ), + return ColoredBox( + color: Theme.of(context).scaffoldBackgroundColor, + child: Stack( + children: [ + // Icon dead-center, matching Android 12+ splash position. + // 192dp accounts for the 16% inset in ic_launcher.xml. + Center( + child: SvgPicture.asset('assets/plezy_adaptive_foreground.svg', width: 288, height: 288), + ), + // Status text below center, independent of icon position. + Positioned( + left: 0, + right: 0, + bottom: MediaQuery.of(context).size.height * 0.5 - 140, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 200), + child: Text( + _statusMessage, + key: ValueKey(_statusMessage), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6), + ), + ), + ), + ), + ], ), ); } diff --git a/lib/providers/theme_provider.dart b/lib/providers/theme_provider.dart index 2962db09..98743ea8 100644 --- a/lib/providers/theme_provider.dart +++ b/lib/providers/theme_provider.dart @@ -1,4 +1,6 @@ +import 'dart:io' show Platform; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:material_symbols_icons/symbols.dart'; import '../services/settings_service.dart' as settings; import '../theme/mono_theme.dart'; @@ -24,6 +26,7 @@ class ThemeProvider extends ChangeNotifier { Future _initializeSettings() async { _settingsService = await settings.SettingsService.getInstance(); _themeMode = _settingsService.getThemeMode(); + _updateSplashTheme(_themeMode); notifyListeners(); } @@ -63,14 +66,28 @@ class ThemeProvider extends ChangeNotifier { } } + static const _themeChannel = MethodChannel('app.plezy/theme'); + Future setThemeMode(settings.ThemeMode mode) async { if (_themeMode != mode) { _themeMode = mode; await _settingsService.setThemeMode(mode); + _updateSplashTheme(mode); notifyListeners(); } } + void _updateSplashTheme(settings.ThemeMode mode) { + if (!Platform.isAndroid) return; + final name = switch (mode) { + settings.ThemeMode.dark => 'dark', + settings.ThemeMode.oled => 'oled', + settings.ThemeMode.light => 'light', + settings.ThemeMode.system => 'system', + }; + _themeChannel.invokeMethod('setSplashTheme', {'mode': name}); + } + String get themeModeDisplayName { switch (_themeMode) { case settings.ThemeMode.light: diff --git a/lib/screens/auth_screen.dart b/lib/screens/auth_screen.dart index 5dc25f79..508b0ad1 100644 --- a/lib/screens/auth_screen.dart +++ b/lib/screens/auth_screen.dart @@ -17,6 +17,7 @@ import '../theme/mono_tokens.dart'; import '../utils/app_logger.dart'; import '../utils/platform_detector.dart'; import '../focus/focusable_button.dart'; +import '../utils/navigation_transitions.dart'; import 'main_screen.dart'; class AuthScreen extends StatefulWidget { @@ -114,7 +115,7 @@ class _AuthScreenState extends State { if (!mounted) return; Navigator.pushReplacement( context, - MaterialPageRoute(builder: (context) => MainScreen(client: result.firstClient!)), + fadeRoute(MainScreen(client: result.firstClient!)), ); } catch (e) { appLogger.e('Failed to connect to servers', error: e); diff --git a/lib/utils/navigation_transitions.dart b/lib/utils/navigation_transitions.dart new file mode 100644 index 00000000..b729af77 --- /dev/null +++ b/lib/utils/navigation_transitions.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +Route fadeRoute(Widget page) { + return PageRouteBuilder( + opaque: false, + pageBuilder: (context, animation, secondaryAnimation) => page, + transitionsBuilder: (context, animation, secondaryAnimation, child) => + FadeTransition(opacity: animation, child: child), + transitionDuration: const Duration(milliseconds: 500), + reverseTransitionDuration: const Duration(milliseconds: 500), + ); +} diff --git a/pubspec.lock b/pubspec.lock index 99582c4e..d014e342 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,14 +41,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" - archive: - dependency: transitive - description: - name: archive - sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" - url: "https://pub.dev" - source: hosted - version: "4.0.7" args: dependency: transitive description: @@ -488,14 +480,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.4.1" - flutter_launcher_icons: - dependency: "direct dev" - description: - name: flutter_launcher_icons - sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" - url: "https://pub.dev" - source: hosted - version: "0.14.4" flutter_lints: dependency: "direct dev" description: @@ -591,14 +575,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" - image: - dependency: transitive - description: - name: image - sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" - url: "https://pub.dev" - source: hosted - version: "4.5.4" in_app_review: dependency: "direct main" description: @@ -896,14 +872,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.2" - posix: - dependency: transitive - description: - name: posix - sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" - url: "https://pub.dev" - source: hosted - version: "6.0.3" process: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0acf7863..8f6e0d13 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -65,7 +65,6 @@ dev_dependencies: flutter_lints: ^6.0.0 build_runner: ^2.4.7 json_serializable: ^6.7.1 - flutter_launcher_icons: ^0.14.4 slang_build_runner: ^4.12.0 dart_code_linter: ^3.2.1 drift_dev: ^2.14.0 @@ -81,22 +80,9 @@ flutter: assets: - lib/data/iso_639_codes.json - assets/plezy.png + - assets/plezy_adaptive_foreground.svg - assets/go-noto-current-regular.ttf - assets/shaders/nvscaler/ - assets/shaders/anime4k/ - assets/player_icons/ - assets/rating_icons/ - -flutter_launcher_icons: - android: true - ios: false - image_path: "assets/plezy.png" - adaptive_icon_background: "#ffffff" - adaptive_icon_foreground: "assets/plezy_android_foreground.png" - adaptive_icon_monochrome: "assets/plezy_monochrome.png" - macos: - generate: false - windows: - generate: true - linux: - generate: true