diff --git a/android/app/src/main/kotlin/com/example/flutter_application_1/MainActivity.kt b/android/app/src/main/kotlin/com/example/flutter_application_1/MainActivity.kt index 81c64d54..3f36a9c0 100644 --- a/android/app/src/main/kotlin/com/example/flutter_application_1/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/flutter_application_1/MainActivity.kt @@ -2,7 +2,9 @@ package com.edde746.plezy import android.os.Build import android.os.Bundle +import android.app.AppOpsManager import android.app.PictureInPictureParams +import android.content.Context import android.content.res.Configuration import android.util.Rational import io.flutter.embedding.android.FlutterActivity @@ -34,13 +36,41 @@ class MainActivity : FlutterActivity() { result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) } "enter" -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + result.success(mapOf("success" to false, "errorCode" to "android_version")) + return@setMethodCallHandler + } + + // Check if PiP permission is granted via AppOpsManager + val appOpsManager = getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager + val pipAllowed = appOpsManager.checkOpNoThrow( + AppOpsManager.OPSTR_PICTURE_IN_PICTURE, + applicationInfo.uid, + packageName + ) == AppOpsManager.MODE_ALLOWED + + if (!pipAllowed) { + result.success(mapOf("success" to false, "errorCode" to "permission_disabled")) + return@setMethodCallHandler + } + + try { val width = call.argument("width") ?: 16 val height = call.argument("height") ?: 9 - val params = PictureInPictureParams.Builder().setAspectRatio(Rational(width, height)).build() - enterPictureInPictureMode(params) + val params = PictureInPictureParams.Builder() + .setAspectRatio(Rational(width, height)) + .build() + val success = enterPictureInPictureMode(params) + if (success) { + result.success(mapOf("success" to true)) + } else { + result.success(mapOf("success" to false, "errorCode" to "failed")) + } + } catch (e: IllegalStateException) { + result.success(mapOf("success" to false, "errorCode" to "not_supported")) + } catch (e: Exception) { + result.success(mapOf("success" to false, "errorCode" to "unknown", "errorMessage" to (e.message ?: "Unknown error"))) } - result.success(null) } else -> result.notImplemented() } } diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index 14f9253f..a8bd35e8 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -290,7 +290,15 @@ "rotationLockButton": "Dreh­sperre", "timelineSlider": "Video-Zeitleiste", "volumeSlider": "Lautstärkepegel", - "backButton": "Zurück" + "backButton": "Zurück", + "pipFailed": "Bild-in-Bild konnte nicht gestartet werden", + "pipErrors": { + "androidVersion": "Erfordert Android 8.0 oder neuer", + "permissionDisabled": "Bild-in-Bild-Berechtigung ist deaktiviert. Aktiviere sie unter Einstellungen > Apps > Plezy > Bild-in-Bild", + "notSupported": "Dieses Gerät unterstützt den Bild-in-Bild-Modus nicht", + "failed": "Bild-in-Bild konnte nicht gestartet werden", + "unknown": "Ein Fehler ist aufgetreten: ${error}" + } }, "userStatus": { "admin": "Eigentümer", diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index 5d512ac2..238c1ee1 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -289,7 +289,15 @@ "rotationLockButton": "Rotation lock", "timelineSlider": "Video timeline", "volumeSlider": "Volume level", - "backButton": "Back" + "backButton": "Back", + "pipFailed": "Picture-in-picture failed to start", + "pipErrors": { + "androidVersion": "Requires Android 8.0 or newer", + "permissionDisabled": "Picture-in-picture permission is disabled. Enable it in Settings > Apps > Plezy > Picture-in-picture", + "notSupported": "Device doesn't support picture-in-picture mode", + "failed": "Picture-in-picture failed to start", + "unknown": "An error occurred: ${error}" + } }, "userStatus": { "admin": "Admin", diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index 2770d3dd..4d299a81 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -289,7 +289,15 @@ "rotationLockButton": "Verrouillage de rotation", "timelineSlider": "Timeline vidéo", "volumeSlider": "Niveau sonore", - "backButton": "Retour" + "backButton": "Retour", + "pipFailed": "Échec du démarrage du mode image dans l'image", + "pipErrors": { + "androidVersion": "Nécessite Android 8.0 ou plus récent", + "permissionDisabled": "L'autorisation Image dans l'image est désactivée. Activez-la dans Paramètres > Applications > Plezy > Image dans l'image", + "notSupported": "Cet appareil ne prend pas en charge le mode image dans l'image", + "failed": "Échec du démarrage du mode image dans l'image", + "unknown": "Une erreur s'est produite : ${error}" + } }, "userStatus": { "admin": "Admin", diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index e58f778f..111ed41e 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -290,7 +290,15 @@ "rotationLockButton": "Blocco rotazione", "timelineSlider": "Timeline video", "volumeSlider": "Livello volume", - "backButton": "Indietro" + "backButton": "Indietro", + "pipFailed": "Impossibile avviare la modalità Picture-in-Picture", + "pipErrors": { + "androidVersion": "Richiede Android 8.0 o versioni successive", + "permissionDisabled": "L'autorizzazione Picture-in-Picture è disabilitata. Abilitala in Impostazioni > App > Plezy > Picture-in-Picture", + "notSupported": "Questo dispositivo non supporta la modalità Picture-in-Picture", + "failed": "Impossibile avviare la modalità Picture-in-Picture", + "unknown": "Si è verificato un errore: ${error}" + } }, "userStatus": { "admin": "Admin", diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index 9fd9d9f7..fa6115ef 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -290,7 +290,15 @@ "rotationLockButton": "회전 잠금", "timelineSlider": "타임라인", "volumeSlider": "볼륨 조절", - "backButton": "뒤로 가기" + "backButton": "뒤로 가기", + "pipFailed": "화면 속 화면 모드를 시작할 수 없습니다", + "pipErrors": { + "androidVersion": "Android 8.0 이상이 필요합니다", + "permissionDisabled": "화면 속 화면 권한이 비활성화되어 있습니다. 설정 > 앱 > Plezy > 화면 속 화면에서 활성화하세요", + "notSupported": "이 기기는 화면 속 화면 모드를 지원하지 않습니다", + "failed": "화면 속 화면 모드를 시작할 수 없습니다", + "unknown": "오류가 발생했습니다: ${error}" + } }, "userStatus": { "admin": "관리자", diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index 32e96b4c..4baf043c 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -290,7 +290,15 @@ "rotationLockButton": "Rotatievergrendeling", "timelineSlider": "Videotijdlijn", "volumeSlider": "Volumeniveau", - "backButton": "Terug" + "backButton": "Terug", + "pipFailed": "Beeld-in-beeld kon niet worden gestart", + "pipErrors": { + "androidVersion": "Vereist Android 8.0 of nieuwer", + "permissionDisabled": "Beeld-in-beeld toestemming is uitgeschakeld. Schakel deze in via Instellingen > Apps > Plezy > Beeld-in-beeld", + "notSupported": "Dit apparaat ondersteunt geen beeld-in-beeld modus", + "failed": "Beeld-in-beeld kon niet worden gestart", + "unknown": "Er is een fout opgetreden: ${error}" + } }, "userStatus": { "admin": "Beheerder", diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 45efdcff..a4600cee 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 8 -/// Strings: 4324 (540 per locale) +/// Strings: 4372 (546 per locale) /// -/// Built on 2026-01-20 at 15:18 UTC +/// Built on 2026-01-21 at 22:57 UTC // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/lib/i18n/strings_de.g.dart b/lib/i18n/strings_de.g.dart index 14597486..1e5ffa1a 100644 --- a/lib/i18n/strings_de.g.dart +++ b/lib/i18n/strings_de.g.dart @@ -442,6 +442,8 @@ class _TranslationsVideoControlsDe implements TranslationsVideoControlsEn { @override String get timelineSlider => 'Video-Zeitleiste'; @override String get volumeSlider => 'Lautstärkepegel'; @override String get backButton => 'Zurück'; + @override String get pipFailed => 'Bild-in-Bild konnte nicht gestartet werden'; + @override late final _TranslationsVideoControlsPipErrorsDe pipErrors = _TranslationsVideoControlsPipErrorsDe._(_root); } // Path: userStatus @@ -902,6 +904,20 @@ class _TranslationsHotkeysActionsDe implements TranslationsHotkeysActionsEn { @override String get subSeekPrev => 'Zum vorherigen Untertitel springen'; } +// Path: videoControls.pipErrors +class _TranslationsVideoControlsPipErrorsDe implements TranslationsVideoControlsPipErrorsEn { + _TranslationsVideoControlsPipErrorsDe._(this._root); + + final TranslationsDe _root; // ignore: unused_field + + // Translations + @override String get androidVersion => 'Erfordert Android 8.0 oder neuer'; + @override String get permissionDisabled => 'Bild-in-Bild-Berechtigung ist deaktiviert. Aktiviere sie unter Einstellungen > Apps > Plezy > Bild-in-Bild'; + @override String get notSupported => 'Dieses Gerät unterstützt den Bild-in-Bild-Modus nicht'; + @override String get failed => 'Bild-in-Bild konnte nicht gestartet werden'; + @override String unknown({required Object error}) => 'Ein Fehler ist aufgetreten: ${error}'; +} + // Path: libraries.tabs class _TranslationsLibrariesTabsDe implements TranslationsLibrariesTabsEn { _TranslationsLibrariesTabsDe._(this._root); @@ -1201,6 +1217,12 @@ extension on TranslationsDe { 'videoControls.timelineSlider' => 'Video-Zeitleiste', 'videoControls.volumeSlider' => 'Lautstärkepegel', 'videoControls.backButton' => 'Zurück', + 'videoControls.pipFailed' => 'Bild-in-Bild konnte nicht gestartet werden', + 'videoControls.pipErrors.androidVersion' => 'Erfordert Android 8.0 oder neuer', + 'videoControls.pipErrors.permissionDisabled' => 'Bild-in-Bild-Berechtigung ist deaktiviert. Aktiviere sie unter Einstellungen > Apps > Plezy > Bild-in-Bild', + 'videoControls.pipErrors.notSupported' => 'Dieses Gerät unterstützt den Bild-in-Bild-Modus nicht', + 'videoControls.pipErrors.failed' => 'Bild-in-Bild konnte nicht gestartet werden', + 'videoControls.pipErrors.unknown' => ({required Object error}) => 'Ein Fehler ist aufgetreten: ${error}', 'userStatus.admin' => 'Eigentümer', 'userStatus.restricted' => 'Eingeschränkt', 'userStatus.protected' => 'Geschützt', @@ -1444,14 +1466,14 @@ extension on TranslationsDe { 'watchTogether.joining' => 'Beitreten...', 'watchTogether.controlMode' => 'Steuerungsmodus', 'watchTogether.controlModeQuestion' => 'Wer kann die Wiedergabe steuern?', + _ => null, + } ?? switch (path) { 'watchTogether.hostOnly' => 'Nur Host', 'watchTogether.anyone' => 'Alle', 'watchTogether.hostingSession' => 'Sitzung Hosten', 'watchTogether.inSession' => 'In Sitzung', 'watchTogether.sessionCode' => 'Sitzungscode', 'watchTogether.hostControlsPlayback' => 'Host steuert die Wiedergabe', - _ => null, - } ?? switch (path) { 'watchTogether.anyoneCanControl' => 'Alle können die Wiedergabe steuern', 'watchTogether.hostControls' => 'Host steuert', 'watchTogether.anyoneControls' => 'Alle steuern', diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index 49b51ecd..1bec1e85 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -935,6 +935,11 @@ class TranslationsVideoControlsEn { /// en: 'Back' String get backButton => 'Back'; + + /// en: 'Picture-in-picture failed to start' + String get pipFailed => 'Picture-in-picture failed to start'; + + late final TranslationsVideoControlsPipErrorsEn pipErrors = TranslationsVideoControlsPipErrorsEn._(_root); } // Path: userStatus @@ -1963,6 +1968,30 @@ class TranslationsHotkeysActionsEn { String get subSeekPrev => 'Seek to Previous Subtitle'; } +// Path: videoControls.pipErrors +class TranslationsVideoControlsPipErrorsEn { + TranslationsVideoControlsPipErrorsEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Requires Android 8.0 or newer' + String get androidVersion => 'Requires Android 8.0 or newer'; + + /// en: 'Picture-in-picture permission is disabled. Enable it in Settings > Apps > Plezy > Picture-in-picture' + String get permissionDisabled => 'Picture-in-picture permission is disabled. Enable it in Settings > Apps > Plezy > Picture-in-picture'; + + /// en: 'Device doesn't support picture-in-picture mode' + String get notSupported => 'Device doesn\'t support picture-in-picture mode'; + + /// en: 'Picture-in-picture failed to start' + String get failed => 'Picture-in-picture failed to start'; + + /// en: 'An error occurred: ${error}' + String unknown({required Object error}) => 'An error occurred: ${error}'; +} + // Path: libraries.tabs class TranslationsLibrariesTabsEn { TranslationsLibrariesTabsEn._(this._root); @@ -2281,6 +2310,12 @@ extension on Translations { 'videoControls.timelineSlider' => 'Video timeline', 'videoControls.volumeSlider' => 'Volume level', 'videoControls.backButton' => 'Back', + 'videoControls.pipFailed' => 'Picture-in-picture failed to start', + 'videoControls.pipErrors.androidVersion' => 'Requires Android 8.0 or newer', + 'videoControls.pipErrors.permissionDisabled' => 'Picture-in-picture permission is disabled. Enable it in Settings > Apps > Plezy > Picture-in-picture', + 'videoControls.pipErrors.notSupported' => 'Device doesn\'t support picture-in-picture mode', + 'videoControls.pipErrors.failed' => 'Picture-in-picture failed to start', + 'videoControls.pipErrors.unknown' => ({required Object error}) => 'An error occurred: ${error}', 'userStatus.admin' => 'Admin', 'userStatus.restricted' => 'Restricted', 'userStatus.protected' => 'Protected', @@ -2525,14 +2560,14 @@ extension on Translations { 'watchTogether.watchingWithOthers' => 'Watching with others', 'watchTogether.endSession' => 'End Session', 'watchTogether.leaveSession' => 'Leave Session', + _ => null, + } ?? switch (path) { 'watchTogether.endSessionQuestion' => 'End Session?', 'watchTogether.leaveSessionQuestion' => 'Leave Session?', 'watchTogether.endSessionConfirm' => 'This will end the session for all participants.', 'watchTogether.leaveSessionConfirm' => 'You will be removed from the session.', 'watchTogether.endSessionConfirmOverlay' => 'This will end the watch session for all participants.', 'watchTogether.leaveSessionConfirmOverlay' => 'You will be disconnected from the watch session.', - _ => null, - } ?? switch (path) { 'watchTogether.end' => 'End', 'watchTogether.leave' => 'Leave', 'watchTogether.syncing' => 'Syncing...', diff --git a/lib/i18n/strings_fr.g.dart b/lib/i18n/strings_fr.g.dart index 91c2630d..324f9eed 100644 --- a/lib/i18n/strings_fr.g.dart +++ b/lib/i18n/strings_fr.g.dart @@ -441,6 +441,8 @@ class _TranslationsVideoControlsFr implements TranslationsVideoControlsEn { @override String get timelineSlider => 'Timeline vidéo'; @override String get volumeSlider => 'Niveau sonore'; @override String get backButton => 'Retour'; + @override String get pipFailed => 'Échec du démarrage du mode image dans l\'image'; + @override late final _TranslationsVideoControlsPipErrorsFr pipErrors = _TranslationsVideoControlsPipErrorsFr._(_root); } // Path: userStatus @@ -900,6 +902,20 @@ class _TranslationsHotkeysActionsFr implements TranslationsHotkeysActionsEn { @override String get subSeekPrev => 'Rechercher le sous-titre précédent'; } +// Path: videoControls.pipErrors +class _TranslationsVideoControlsPipErrorsFr implements TranslationsVideoControlsPipErrorsEn { + _TranslationsVideoControlsPipErrorsFr._(this._root); + + final TranslationsFr _root; // ignore: unused_field + + // Translations + @override String get androidVersion => 'Nécessite Android 8.0 ou plus récent'; + @override String get permissionDisabled => 'L\'autorisation Image dans l\'image est désactivée. Activez-la dans Paramètres > Applications > Plezy > Image dans l\'image'; + @override String get notSupported => 'Cet appareil ne prend pas en charge le mode image dans l\'image'; + @override String get failed => 'Échec du démarrage du mode image dans l\'image'; + @override String unknown({required Object error}) => 'Une erreur s\'est produite : ${error}'; +} + // Path: libraries.tabs class _TranslationsLibrariesTabsFr implements TranslationsLibrariesTabsEn { _TranslationsLibrariesTabsFr._(this._root); @@ -1198,6 +1214,12 @@ extension on TranslationsFr { 'videoControls.timelineSlider' => 'Timeline vidéo', 'videoControls.volumeSlider' => 'Niveau sonore', 'videoControls.backButton' => 'Retour', + 'videoControls.pipFailed' => 'Échec du démarrage du mode image dans l\'image', + 'videoControls.pipErrors.androidVersion' => 'Nécessite Android 8.0 ou plus récent', + 'videoControls.pipErrors.permissionDisabled' => 'L\'autorisation Image dans l\'image est désactivée. Activez-la dans Paramètres > Applications > Plezy > Image dans l\'image', + 'videoControls.pipErrors.notSupported' => 'Cet appareil ne prend pas en charge le mode image dans l\'image', + 'videoControls.pipErrors.failed' => 'Échec du démarrage du mode image dans l\'image', + 'videoControls.pipErrors.unknown' => ({required Object error}) => 'Une erreur s\'est produite : ${error}', 'userStatus.admin' => 'Admin', 'userStatus.restricted' => 'Restreint', 'userStatus.protected' => 'Protégé', @@ -1442,14 +1464,14 @@ extension on TranslationsFr { 'watchTogether.watchingWithOthers' => 'Regarder avec d\'autres personnes', 'watchTogether.endSession' => 'Fin de session', 'watchTogether.leaveSession' => 'Quitter la session', + _ => null, + } ?? switch (path) { 'watchTogether.endSessionQuestion' => 'Terminer la session ?', 'watchTogether.leaveSessionQuestion' => 'Quitter la session ?', 'watchTogether.endSessionConfirm' => 'Cela mettra fin à la session pour tous les participants.', 'watchTogether.leaveSessionConfirm' => 'Vous allez être déconnecté de la session.', 'watchTogether.endSessionConfirmOverlay' => 'Cela mettra fin à la session de visionnage pour tous les participants.', 'watchTogether.leaveSessionConfirmOverlay' => 'Vous serez déconnecté de la session de visionnage.', - _ => null, - } ?? switch (path) { 'watchTogether.end' => 'Terminer', 'watchTogether.leave' => 'Fin', 'watchTogether.syncing' => 'Synchronisation...', diff --git a/lib/i18n/strings_it.g.dart b/lib/i18n/strings_it.g.dart index f462b71e..34a367a2 100644 --- a/lib/i18n/strings_it.g.dart +++ b/lib/i18n/strings_it.g.dart @@ -442,6 +442,8 @@ class _TranslationsVideoControlsIt implements TranslationsVideoControlsEn { @override String get timelineSlider => 'Timeline video'; @override String get volumeSlider => 'Livello volume'; @override String get backButton => 'Indietro'; + @override String get pipFailed => 'Impossibile avviare la modalità Picture-in-Picture'; + @override late final _TranslationsVideoControlsPipErrorsIt pipErrors = _TranslationsVideoControlsPipErrorsIt._(_root); } // Path: userStatus @@ -902,6 +904,20 @@ class _TranslationsHotkeysActionsIt implements TranslationsHotkeysActionsEn { @override String get subSeekPrev => 'Vai al sottotitolo precedente'; } +// Path: videoControls.pipErrors +class _TranslationsVideoControlsPipErrorsIt implements TranslationsVideoControlsPipErrorsEn { + _TranslationsVideoControlsPipErrorsIt._(this._root); + + final TranslationsIt _root; // ignore: unused_field + + // Translations + @override String get androidVersion => 'Richiede Android 8.0 o versioni successive'; + @override String get permissionDisabled => 'L\'autorizzazione Picture-in-Picture è disabilitata. Abilitala in Impostazioni > App > Plezy > Picture-in-Picture'; + @override String get notSupported => 'Questo dispositivo non supporta la modalità Picture-in-Picture'; + @override String get failed => 'Impossibile avviare la modalità Picture-in-Picture'; + @override String unknown({required Object error}) => 'Si è verificato un errore: ${error}'; +} + // Path: libraries.tabs class _TranslationsLibrariesTabsIt implements TranslationsLibrariesTabsEn { _TranslationsLibrariesTabsIt._(this._root); @@ -1201,6 +1217,12 @@ extension on TranslationsIt { 'videoControls.timelineSlider' => 'Timeline video', 'videoControls.volumeSlider' => 'Livello volume', 'videoControls.backButton' => 'Indietro', + 'videoControls.pipFailed' => 'Impossibile avviare la modalità Picture-in-Picture', + 'videoControls.pipErrors.androidVersion' => 'Richiede Android 8.0 o versioni successive', + 'videoControls.pipErrors.permissionDisabled' => 'L\'autorizzazione Picture-in-Picture è disabilitata. Abilitala in Impostazioni > App > Plezy > Picture-in-Picture', + 'videoControls.pipErrors.notSupported' => 'Questo dispositivo non supporta la modalità Picture-in-Picture', + 'videoControls.pipErrors.failed' => 'Impossibile avviare la modalità Picture-in-Picture', + 'videoControls.pipErrors.unknown' => ({required Object error}) => 'Si è verificato un errore: ${error}', 'userStatus.admin' => 'Admin', 'userStatus.restricted' => 'Limitato', 'userStatus.protected' => 'Protetto', @@ -1444,14 +1466,14 @@ extension on TranslationsIt { 'watchTogether.joining' => 'Connessione...', 'watchTogether.controlMode' => 'Modalità di Controllo', 'watchTogether.controlModeQuestion' => 'Chi può controllare la riproduzione?', + _ => null, + } ?? switch (path) { 'watchTogether.hostOnly' => 'Solo Host', 'watchTogether.anyone' => 'Tutti', 'watchTogether.hostingSession' => 'Hosting Sessione', 'watchTogether.inSession' => 'In Sessione', 'watchTogether.sessionCode' => 'Codice Sessione', 'watchTogether.hostControlsPlayback' => 'L\'host controlla la riproduzione', - _ => null, - } ?? switch (path) { 'watchTogether.anyoneCanControl' => 'Tutti possono controllare la riproduzione', 'watchTogether.hostControls' => 'Controllo host', 'watchTogether.anyoneControls' => 'Controllo di tutti', diff --git a/lib/i18n/strings_ko.g.dart b/lib/i18n/strings_ko.g.dart index 9036f0d7..a778c89a 100644 --- a/lib/i18n/strings_ko.g.dart +++ b/lib/i18n/strings_ko.g.dart @@ -442,6 +442,8 @@ class _TranslationsVideoControlsKo implements TranslationsVideoControlsEn { @override String get timelineSlider => '타임라인'; @override String get volumeSlider => '볼륨 조절'; @override String get backButton => '뒤로 가기'; + @override String get pipFailed => '화면 속 화면 모드를 시작할 수 없습니다'; + @override late final _TranslationsVideoControlsPipErrorsKo pipErrors = _TranslationsVideoControlsPipErrorsKo._(_root); } // Path: userStatus @@ -902,6 +904,20 @@ class _TranslationsHotkeysActionsKo implements TranslationsHotkeysActionsEn { @override String get subSeekPrev => '이전 자막으로 이동'; } +// Path: videoControls.pipErrors +class _TranslationsVideoControlsPipErrorsKo implements TranslationsVideoControlsPipErrorsEn { + _TranslationsVideoControlsPipErrorsKo._(this._root); + + final TranslationsKo _root; // ignore: unused_field + + // Translations + @override String get androidVersion => 'Android 8.0 이상이 필요합니다'; + @override String get permissionDisabled => '화면 속 화면 권한이 비활성화되어 있습니다. 설정 > 앱 > Plezy > 화면 속 화면에서 활성화하세요'; + @override String get notSupported => '이 기기는 화면 속 화면 모드를 지원하지 않습니다'; + @override String get failed => '화면 속 화면 모드를 시작할 수 없습니다'; + @override String unknown({required Object error}) => '오류가 발생했습니다: ${error}'; +} + // Path: libraries.tabs class _TranslationsLibrariesTabsKo implements TranslationsLibrariesTabsEn { _TranslationsLibrariesTabsKo._(this._root); @@ -1201,6 +1217,12 @@ extension on TranslationsKo { 'videoControls.timelineSlider' => '타임라인', 'videoControls.volumeSlider' => '볼륨 조절', 'videoControls.backButton' => '뒤로 가기', + 'videoControls.pipFailed' => '화면 속 화면 모드를 시작할 수 없습니다', + 'videoControls.pipErrors.androidVersion' => 'Android 8.0 이상이 필요합니다', + 'videoControls.pipErrors.permissionDisabled' => '화면 속 화면 권한이 비활성화되어 있습니다. 설정 > 앱 > Plezy > 화면 속 화면에서 활성화하세요', + 'videoControls.pipErrors.notSupported' => '이 기기는 화면 속 화면 모드를 지원하지 않습니다', + 'videoControls.pipErrors.failed' => '화면 속 화면 모드를 시작할 수 없습니다', + 'videoControls.pipErrors.unknown' => ({required Object error}) => '오류가 발생했습니다: ${error}', 'userStatus.admin' => '관리자', 'userStatus.restricted' => '제한됨', 'userStatus.protected' => '보호됨', @@ -1444,14 +1466,14 @@ extension on TranslationsKo { 'watchTogether.hostBadge' => '호스트', 'watchTogether.youAreHost' => '당신은 호스트 입니다', 'watchTogether.watchingWithOthers' => '다른 사람과 함께 시청 중', + _ => null, + } ?? switch (path) { 'watchTogether.endSession' => '세션 종료', 'watchTogether.leaveSession' => '세션 탈퇴', 'watchTogether.endSessionQuestion' => '세션을 종료 하시겠습니까?', 'watchTogether.leaveSessionQuestion' => '세션을 탈퇴 하시겠습니까?', 'watchTogether.endSessionConfirm' => '이 작업은 모든 참가자의 세션을 종료합니다.', 'watchTogether.leaveSessionConfirm' => '당신은 세션에서 제거됩니다.', - _ => null, - } ?? switch (path) { 'watchTogether.endSessionConfirmOverlay' => '이것은 모든 참가자의 시청 세션을 종료합니다.', 'watchTogether.leaveSessionConfirmOverlay' => '시청 세션 연결이 끊어집니다.', 'watchTogether.end' => '종료', diff --git a/lib/i18n/strings_nl.g.dart b/lib/i18n/strings_nl.g.dart index fa8f6870..4b17fe06 100644 --- a/lib/i18n/strings_nl.g.dart +++ b/lib/i18n/strings_nl.g.dart @@ -442,6 +442,8 @@ class _TranslationsVideoControlsNl implements TranslationsVideoControlsEn { @override String get timelineSlider => 'Videotijdlijn'; @override String get volumeSlider => 'Volumeniveau'; @override String get backButton => 'Terug'; + @override String get pipFailed => 'Beeld-in-beeld kon niet worden gestart'; + @override late final _TranslationsVideoControlsPipErrorsNl pipErrors = _TranslationsVideoControlsPipErrorsNl._(_root); } // Path: userStatus @@ -902,6 +904,20 @@ class _TranslationsHotkeysActionsNl implements TranslationsHotkeysActionsEn { @override String get subSeekPrev => 'Naar vorige ondertitel'; } +// Path: videoControls.pipErrors +class _TranslationsVideoControlsPipErrorsNl implements TranslationsVideoControlsPipErrorsEn { + _TranslationsVideoControlsPipErrorsNl._(this._root); + + final TranslationsNl _root; // ignore: unused_field + + // Translations + @override String get androidVersion => 'Vereist Android 8.0 of nieuwer'; + @override String get permissionDisabled => 'Beeld-in-beeld toestemming is uitgeschakeld. Schakel deze in via Instellingen > Apps > Plezy > Beeld-in-beeld'; + @override String get notSupported => 'Dit apparaat ondersteunt geen beeld-in-beeld modus'; + @override String get failed => 'Beeld-in-beeld kon niet worden gestart'; + @override String unknown({required Object error}) => 'Er is een fout opgetreden: ${error}'; +} + // Path: libraries.tabs class _TranslationsLibrariesTabsNl implements TranslationsLibrariesTabsEn { _TranslationsLibrariesTabsNl._(this._root); @@ -1201,6 +1217,12 @@ extension on TranslationsNl { 'videoControls.timelineSlider' => 'Videotijdlijn', 'videoControls.volumeSlider' => 'Volumeniveau', 'videoControls.backButton' => 'Terug', + 'videoControls.pipFailed' => 'Beeld-in-beeld kon niet worden gestart', + 'videoControls.pipErrors.androidVersion' => 'Vereist Android 8.0 of nieuwer', + 'videoControls.pipErrors.permissionDisabled' => 'Beeld-in-beeld toestemming is uitgeschakeld. Schakel deze in via Instellingen > Apps > Plezy > Beeld-in-beeld', + 'videoControls.pipErrors.notSupported' => 'Dit apparaat ondersteunt geen beeld-in-beeld modus', + 'videoControls.pipErrors.failed' => 'Beeld-in-beeld kon niet worden gestart', + 'videoControls.pipErrors.unknown' => ({required Object error}) => 'Er is een fout opgetreden: ${error}', 'userStatus.admin' => 'Beheerder', 'userStatus.restricted' => 'Beperkt', 'userStatus.protected' => 'Beschermd', @@ -1444,14 +1466,14 @@ extension on TranslationsNl { 'watchTogether.joining' => 'Deelnemen...', 'watchTogether.controlMode' => 'Controlemodus', 'watchTogether.controlModeQuestion' => 'Wie kan het afspelen bedienen?', + _ => null, + } ?? switch (path) { 'watchTogether.hostOnly' => 'Alleen Host', 'watchTogether.anyone' => 'Iedereen', 'watchTogether.hostingSession' => 'Sessie Hosten', 'watchTogether.inSession' => 'In Sessie', 'watchTogether.sessionCode' => 'Sessiecode', 'watchTogether.hostControlsPlayback' => 'Host bedient het afspelen', - _ => null, - } ?? switch (path) { 'watchTogether.anyoneCanControl' => 'Iedereen kan het afspelen bedienen', 'watchTogether.hostControls' => 'Host bedient', 'watchTogether.anyoneControls' => 'Iedereen bedient', diff --git a/lib/i18n/strings_sv.g.dart b/lib/i18n/strings_sv.g.dart index 90ec44a6..53fa5a23 100644 --- a/lib/i18n/strings_sv.g.dart +++ b/lib/i18n/strings_sv.g.dart @@ -442,6 +442,8 @@ class _TranslationsVideoControlsSv implements TranslationsVideoControlsEn { @override String get timelineSlider => 'Videotidslinje'; @override String get volumeSlider => 'Volymnivå'; @override String get backButton => 'Tillbaka'; + @override String get pipFailed => 'Bild-i-bild kunde inte starta'; + @override late final _TranslationsVideoControlsPipErrorsSv pipErrors = _TranslationsVideoControlsPipErrorsSv._(_root); } // Path: userStatus @@ -902,6 +904,20 @@ class _TranslationsHotkeysActionsSv implements TranslationsHotkeysActionsEn { @override String get subSeekPrev => 'Hoppa till föregående undertext'; } +// Path: videoControls.pipErrors +class _TranslationsVideoControlsPipErrorsSv implements TranslationsVideoControlsPipErrorsEn { + _TranslationsVideoControlsPipErrorsSv._(this._root); + + final TranslationsSv _root; // ignore: unused_field + + // Translations + @override String get androidVersion => 'Kräver Android 8.0 eller nyare'; + @override String get permissionDisabled => 'Bild-i-bild-behörighet är inaktiverad. Aktivera den i Inställningar > Appar > Plezy > Bild-i-bild'; + @override String get notSupported => 'Denna enhet stöder inte bild-i-bild-läge'; + @override String get failed => 'Bild-i-bild kunde inte starta'; + @override String unknown({required Object error}) => 'Ett fel uppstod: ${error}'; +} + // Path: libraries.tabs class _TranslationsLibrariesTabsSv implements TranslationsLibrariesTabsEn { _TranslationsLibrariesTabsSv._(this._root); @@ -1201,6 +1217,12 @@ extension on TranslationsSv { 'videoControls.timelineSlider' => 'Videotidslinje', 'videoControls.volumeSlider' => 'Volymnivå', 'videoControls.backButton' => 'Tillbaka', + 'videoControls.pipFailed' => 'Bild-i-bild kunde inte starta', + 'videoControls.pipErrors.androidVersion' => 'Kräver Android 8.0 eller nyare', + 'videoControls.pipErrors.permissionDisabled' => 'Bild-i-bild-behörighet är inaktiverad. Aktivera den i Inställningar > Appar > Plezy > Bild-i-bild', + 'videoControls.pipErrors.notSupported' => 'Denna enhet stöder inte bild-i-bild-läge', + 'videoControls.pipErrors.failed' => 'Bild-i-bild kunde inte starta', + 'videoControls.pipErrors.unknown' => ({required Object error}) => 'Ett fel uppstod: ${error}', 'userStatus.admin' => 'Admin', 'userStatus.restricted' => 'Begränsad', 'userStatus.protected' => 'Skyddad', @@ -1444,14 +1466,14 @@ extension on TranslationsSv { 'watchTogether.joining' => 'Ansluter...', 'watchTogether.controlMode' => 'Kontrollläge', 'watchTogether.controlModeQuestion' => 'Vem kan styra uppspelningen?', + _ => null, + } ?? switch (path) { 'watchTogether.hostOnly' => 'Endast Värd', 'watchTogether.anyone' => 'Alla', 'watchTogether.hostingSession' => 'Värd för Session', 'watchTogether.inSession' => 'I Session', 'watchTogether.sessionCode' => 'Sessionskod', 'watchTogether.hostControlsPlayback' => 'Värden styr uppspelningen', - _ => null, - } ?? switch (path) { 'watchTogether.anyoneCanControl' => 'Alla kan styra uppspelningen', 'watchTogether.hostControls' => 'Värd styr', 'watchTogether.anyoneControls' => 'Alla styr', diff --git a/lib/i18n/strings_zh.g.dart b/lib/i18n/strings_zh.g.dart index 8a46db5f..f9184bcc 100644 --- a/lib/i18n/strings_zh.g.dart +++ b/lib/i18n/strings_zh.g.dart @@ -442,6 +442,8 @@ class _TranslationsVideoControlsZh implements TranslationsVideoControlsEn { @override String get timelineSlider => '视频时间轴'; @override String get volumeSlider => '音量调节'; @override String get backButton => '返回'; + @override String get pipFailed => '画中画启动失败'; + @override late final _TranslationsVideoControlsPipErrorsZh pipErrors = _TranslationsVideoControlsPipErrorsZh._(_root); } // Path: userStatus @@ -902,6 +904,20 @@ class _TranslationsHotkeysActionsZh implements TranslationsHotkeysActionsEn { @override String get subSeekPrev => '跳转到上一字幕'; } +// Path: videoControls.pipErrors +class _TranslationsVideoControlsPipErrorsZh implements TranslationsVideoControlsPipErrorsEn { + _TranslationsVideoControlsPipErrorsZh._(this._root); + + final TranslationsZh _root; // ignore: unused_field + + // Translations + @override String get androidVersion => '需要 Android 8.0 或更高版本'; + @override String get permissionDisabled => '画中画权限已禁用。请在设置 > 应用 > Plezy > 画中画中启用'; + @override String get notSupported => '此设备不支持画中画模式'; + @override String get failed => '画中画启动失败'; + @override String unknown({required Object error}) => '发生错误:${error}'; +} + // Path: libraries.tabs class _TranslationsLibrariesTabsZh implements TranslationsLibrariesTabsEn { _TranslationsLibrariesTabsZh._(this._root); @@ -1201,6 +1217,12 @@ extension on TranslationsZh { 'videoControls.timelineSlider' => '视频时间轴', 'videoControls.volumeSlider' => '音量调节', 'videoControls.backButton' => '返回', + 'videoControls.pipFailed' => '画中画启动失败', + 'videoControls.pipErrors.androidVersion' => '需要 Android 8.0 或更高版本', + 'videoControls.pipErrors.permissionDisabled' => '画中画权限已禁用。请在设置 > 应用 > Plezy > 画中画中启用', + 'videoControls.pipErrors.notSupported' => '此设备不支持画中画模式', + 'videoControls.pipErrors.failed' => '画中画启动失败', + 'videoControls.pipErrors.unknown' => ({required Object error}) => '发生错误:${error}', 'userStatus.admin' => '管理员', 'userStatus.restricted' => '受限', 'userStatus.protected' => '受保护', @@ -1444,14 +1466,14 @@ extension on TranslationsZh { 'watchTogether.joining' => '加入中...', 'watchTogether.controlMode' => '控制模式', 'watchTogether.controlModeQuestion' => '谁可以控制播放?', + _ => null, + } ?? switch (path) { 'watchTogether.hostOnly' => '仅主持人', 'watchTogether.anyone' => '任何人', 'watchTogether.hostingSession' => '主持会话', 'watchTogether.inSession' => '在会话中', 'watchTogether.sessionCode' => '会话代码', 'watchTogether.hostControlsPlayback' => '主持人控制播放', - _ => null, - } ?? switch (path) { 'watchTogether.anyoneCanControl' => '任何人都可以控制播放', 'watchTogether.hostControls' => '主持人控制', 'watchTogether.anyoneControls' => '任何人控制', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index c1cac4cf..407b3655 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -290,7 +290,15 @@ "rotationLockButton": "Rotationslås", "timelineSlider": "Videotidslinje", "volumeSlider": "Volymnivå", - "backButton": "Tillbaka" + "backButton": "Tillbaka", + "pipFailed": "Bild-i-bild kunde inte starta", + "pipErrors": { + "androidVersion": "Kräver Android 8.0 eller nyare", + "permissionDisabled": "Bild-i-bild-behörighet är inaktiverad. Aktivera den i Inställningar > Appar > Plezy > Bild-i-bild", + "notSupported": "Denna enhet stöder inte bild-i-bild-läge", + "failed": "Bild-i-bild kunde inte starta", + "unknown": "Ett fel uppstod: ${error}" + } }, "userStatus": { "admin": "Admin", diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index b7311b9b..59bc3a22 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -290,7 +290,15 @@ "rotationLockButton": "旋转锁定", "timelineSlider": "视频时间轴", "volumeSlider": "音量调节", - "backButton": "返回" + "backButton": "返回", + "pipFailed": "画中画启动失败", + "pipErrors": { + "androidVersion": "需要 Android 8.0 或更高版本", + "permissionDisabled": "画中画权限已禁用。请在设置 > 应用 > Plezy > 画中画中启用", + "notSupported": "此设备不支持画中画模式", + "failed": "画中画启动失败", + "unknown": "发生错误:${error}" + } }, "userStatus": { "admin": "管理员", diff --git a/lib/screens/video_player_screen.dart b/lib/screens/video_player_screen.dart index 264f59f7..f3245e85 100644 --- a/lib/screens/video_player_screen.dart +++ b/lib/screens/video_player_screen.dart @@ -858,10 +858,11 @@ class VideoPlayerScreenState extends State with WidgetsBindin ); } - void _togglePIPMode() { - setState(() { - _videoPIPManager?.togglePIP(); - }); + Future _togglePIPMode() async { + final result = await _videoPIPManager?.togglePIP(); + if (result != null && !result.$1 && mounted) { + showErrorSnackBar(context, result.$2 ?? t.videoControls.pipFailed); + } } /// Cycle through BoxFit modes: contain → cover → fill → contain (for button) diff --git a/lib/services/pip_service.dart b/lib/services/pip_service.dart index bb02ed35..4680d4b1 100644 --- a/lib/services/pip_service.dart +++ b/lib/services/pip_service.dart @@ -1,5 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; +import 'package:plezy/i18n/strings.g.dart'; class PipService { static const MethodChannel _channel = MethodChannel('app.plezy/pip'); @@ -29,7 +30,25 @@ class PipService { return await _channel.invokeMethod('isSupported') ?? false; } - static Future enter({int? width, int? height}) async { - await _channel.invokeMethod('enter', {'width': width, 'height': height}); + static Future<(bool success, String? error)> enter({int? width, int? height}) async { + final result = await _channel.invokeMethod('enter', {'width': width, 'height': height}); + if (result == null) { + return (false, t.videoControls.pipErrors.unknown(error: 'No response')); + } + final success = result['success'] as bool? ?? false; + final errorCode = result['errorCode'] as String?; + final errorMessage = result['errorMessage'] as String?; + final error = errorCode != null ? _getLocalizedError(errorCode, errorMessage) : null; + return (success, error); + } + + static String _getLocalizedError(String errorCode, String? errorMessage) { + return switch (errorCode) { + 'android_version' => t.videoControls.pipErrors.androidVersion, + 'permission_disabled' => t.videoControls.pipErrors.permissionDisabled, + 'not_supported' => t.videoControls.pipErrors.notSupported, + 'failed' => t.videoControls.pipErrors.failed, + _ => t.videoControls.pipErrors.unknown(error: errorMessage ?? 'Unknown error'), + }; } } diff --git a/lib/services/video_pip_manager.dart b/lib/services/video_pip_manager.dart index f462f79e..8aa75782 100644 --- a/lib/services/video_pip_manager.dart +++ b/lib/services/video_pip_manager.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../mpv/mpv.dart'; import '../services/pip_service.dart'; @@ -16,9 +15,10 @@ class VideoPIPManager { ValueNotifier get isPipActive => PipService().isPipActive; /// Toggle native PiP - Future togglePIP() async { + /// Returns a tuple of (success, error message) for error handling + Future<(bool success, String? error)> togglePIP() async { final supported = await PipService.isSupported(); - if (!supported) return; + if (!supported) return (false, 'PiP not supported on this device'); // Try to get actual video dimensions from MPV int? width; @@ -39,6 +39,6 @@ class VideoPIPManager { width ??= _playerSize?.width.toInt(); height ??= _playerSize?.height.toInt(); - await PipService.enter(width: width, height: height); + return await PipService.enter(width: width, height: height); } }