feat: crash log reporting

This commit is contained in:
edde746
2026-03-02 07:29:21 +01:00
parent 9dc0654225
commit 229d6b24e1
40 changed files with 434 additions and 141 deletions
+7
View File
@@ -30,6 +30,13 @@ android {
targetSdk = flutter.targetSdkVersion targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode versionCode = flutter.versionCode
versionName = flutter.versionName versionName = flutter.versionName
if (System.getenv("AMAZON") != null) {
versionCode = (flutter.versionCode ?: 0) + 3000
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a")
}
}
} }
signingConfigs { signingConfigs {
+9 -9
View File
@@ -22,17 +22,17 @@ platform :android do
debug_info_dir = "./debug-info/#{version_name}+#{version_code}" debug_info_dir = "./debug-info/#{version_name}+#{version_code}"
# Build the Flutter app # Build the Flutter app
sh("cd #{ENV['PWD']}/.. && flutter build appbundle --dart-define=ENABLE_IN_APP_REVIEW=true --obfuscate --split-debug-info=#{debug_info_dir}/aab") # sh("cd #{ENV['PWD']}/.. && flutter build appbundle --dart-define=ENABLE_IN_APP_REVIEW=true --obfuscate --split-debug-info=#{debug_info_dir}/aab")
upload_to_play_store( # upload_to_play_store(
track: "production", # track: "production",
release_status: "completed", # release_status: "completed",
version_code: version_code, # version_code: version_code,
aab: "../build/app/outputs/bundle/release/app-release.aab" # aab: "../build/app/outputs/bundle/release/app-release.aab"
) # )
# Build universal APK for Amazon Appstore (doesn't accept AABs, doesn't auto-target by ABI) # Build universal APK for Amazon Appstore (doesn't accept AABs, excludes x86 via env var)
sh("cd #{ENV['PWD']}/.. && flutter build apk --release --target-platform android-arm,android-arm64 --obfuscate --split-debug-info=#{debug_info_dir}/apk") sh("cd #{ENV['PWD']}/.. && AMAZON=1 flutter build apk --release --obfuscate --split-debug-info=#{debug_info_dir}/apk")
upload_to_amazon_appstore( upload_to_amazon_appstore(
apk_paths: [ apk_paths: [
+11
View File
@@ -66,6 +66,11 @@ PODS:
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- PromisesObjC (2.4.0) - PromisesObjC (2.4.0)
- Sentry/HybridSDK (8.46.0)
- sentry_flutter (8.14.2):
- Flutter
- FlutterMacOS
- Sentry/HybridSDK (= 8.46.0)
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
@@ -117,6 +122,7 @@ DEPENDENCIES:
- os_media_controls (from `.symlinks/plugins/os_media_controls/ios`) - os_media_controls (from `.symlinks/plugins/os_media_controls/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`) - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
@@ -138,6 +144,7 @@ SPEC REPOS:
- MLKitVision - MLKitVision
- nanopb - nanopb
- PromisesObjC - PromisesObjC
- Sentry
- sqlite3 - sqlite3
EXTERNAL SOURCES: EXTERNAL SOURCES:
@@ -161,6 +168,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/package_info_plus/ios" :path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation: path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin" :path: ".symlinks/plugins/path_provider_foundation/darwin"
sentry_flutter:
:path: ".symlinks/plugins/sentry_flutter/ios"
shared_preferences_foundation: shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin" :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite_darwin: sqflite_darwin:
@@ -198,6 +207,8 @@ SPEC CHECKSUMS:
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854
sentry_flutter: 27892878729f42701297c628eb90e7c6529f3684
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
+2
View File
@@ -151,6 +151,8 @@
"keyboardShortcutsDescription": "Tastenkürzel anpassen", "keyboardShortcutsDescription": "Tastenkürzel anpassen",
"videoPlayerNavigation": "Videoplayer-Navigation", "videoPlayerNavigation": "Videoplayer-Navigation",
"videoPlayerNavigationDescription": "Pfeiltasten zur Navigation der Videoplayer-Steuerung verwenden", "videoPlayerNavigationDescription": "Pfeiltasten zur Navigation der Videoplayer-Steuerung verwenden",
"crashReporting": "Absturzberichte",
"crashReportingDescription": "Absturzberichte senden, um die App zu verbessern",
"debugLogging": "Debug-Protokollierung", "debugLogging": "Debug-Protokollierung",
"debugLoggingDescription": "Detaillierte Protokolle zur Fehleranalyse aktivieren", "debugLoggingDescription": "Detaillierte Protokolle zur Fehleranalyse aktivieren",
"viewLogs": "Protokolle anzeigen", "viewLogs": "Protokolle anzeigen",
+2
View File
@@ -151,6 +151,8 @@
"keyboardShortcutsDescription": "Customize keyboard shortcuts", "keyboardShortcutsDescription": "Customize keyboard shortcuts",
"videoPlayerNavigation": "Video Player Navigation", "videoPlayerNavigation": "Video Player Navigation",
"videoPlayerNavigationDescription": "Use arrow keys to navigate video player controls", "videoPlayerNavigationDescription": "Use arrow keys to navigate video player controls",
"crashReporting": "Crash Reporting",
"crashReportingDescription": "Send crash reports to help improve the app",
"debugLogging": "Debug Logging", "debugLogging": "Debug Logging",
"debugLoggingDescription": "Enable detailed logging for troubleshooting", "debugLoggingDescription": "Enable detailed logging for troubleshooting",
"viewLogs": "View Logs", "viewLogs": "View Logs",
+2
View File
@@ -151,6 +151,8 @@
"keyboardShortcutsDescription": "Personalizar los atajos de teclado", "keyboardShortcutsDescription": "Personalizar los atajos de teclado",
"videoPlayerNavigation": "Navegación del Reproductor de Video", "videoPlayerNavigation": "Navegación del Reproductor de Video",
"videoPlayerNavigationDescription": "Usar las teclas de flecha para navegar por los controles del reproductor", "videoPlayerNavigationDescription": "Usar las teclas de flecha para navegar por los controles del reproductor",
"crashReporting": "Informes de Errores",
"crashReportingDescription": "Enviar informes de errores para mejorar la aplicación",
"debugLogging": "Registro de Depuración", "debugLogging": "Registro de Depuración",
"debugLoggingDescription": "Habilitar registros detallados para resolución de problemas", "debugLoggingDescription": "Habilitar registros detallados para resolución de problemas",
"viewLogs": "Ver Logs", "viewLogs": "Ver Logs",
+2
View File
@@ -151,6 +151,8 @@
"keyboardShortcutsDescription": "Personnaliser les raccourcis clavier", "keyboardShortcutsDescription": "Personnaliser les raccourcis clavier",
"videoPlayerNavigation": "Navigation dans le lecteur vidéo", "videoPlayerNavigation": "Navigation dans le lecteur vidéo",
"videoPlayerNavigationDescription": "Utilisez les touches fléchées pour naviguer dans les commandes du lecteur vidéo.", "videoPlayerNavigationDescription": "Utilisez les touches fléchées pour naviguer dans les commandes du lecteur vidéo.",
"crashReporting": "Rapports de plantage",
"crashReportingDescription": "Envoyer des rapports de plantage pour améliorer l'application",
"debugLogging": "Journalisation de débogage", "debugLogging": "Journalisation de débogage",
"debugLoggingDescription": "Activer la journalisation détaillée pour le dépannage", "debugLoggingDescription": "Activer la journalisation détaillée pour le dépannage",
"viewLogs": "Voir les logs", "viewLogs": "Voir les logs",
+2
View File
@@ -151,6 +151,8 @@
"keyboardShortcutsDescription": "Personalizza le scorciatoie da tastiera", "keyboardShortcutsDescription": "Personalizza le scorciatoie da tastiera",
"videoPlayerNavigation": "Navigazione del lettore video", "videoPlayerNavigation": "Navigazione del lettore video",
"videoPlayerNavigationDescription": "Usa i tasti freccia per navigare nei controlli del lettore video", "videoPlayerNavigationDescription": "Usa i tasti freccia per navigare nei controlli del lettore video",
"crashReporting": "Segnalazione errori",
"crashReportingDescription": "Invia segnalazioni di errori per migliorare l'app",
"debugLogging": "Log di debug", "debugLogging": "Log di debug",
"debugLoggingDescription": "Abilita il logging dettagliato per la risoluzione dei problemi", "debugLoggingDescription": "Abilita il logging dettagliato per la risoluzione dei problemi",
"viewLogs": "Visualizza log", "viewLogs": "Visualizza log",
+2
View File
@@ -151,6 +151,8 @@
"keyboardShortcutsDescription": "사용자 정의 키보드 단축키", "keyboardShortcutsDescription": "사용자 정의 키보드 단축키",
"videoPlayerNavigation": "비디오 플레이어 탐색", "videoPlayerNavigation": "비디오 플레이어 탐색",
"videoPlayerNavigationDescription": "방향 키를 사용하여 비디오 플레이어 컨트롤 탐색", "videoPlayerNavigationDescription": "방향 키를 사용하여 비디오 플레이어 컨트롤 탐색",
"crashReporting": "충돌 보고",
"crashReportingDescription": "앱 개선을 위해 충돌 보고서 전송",
"debugLogging": "디버그 로깅", "debugLogging": "디버그 로깅",
"debugLoggingDescription": "문제 해결을 위해 상세 로깅 활성화", "debugLoggingDescription": "문제 해결을 위해 상세 로깅 활성화",
"viewLogs": "로그 보기", "viewLogs": "로그 보기",
+2
View File
@@ -151,6 +151,8 @@
"keyboardShortcutsDescription": "Pas toetsenbord sneltoetsen aan", "keyboardShortcutsDescription": "Pas toetsenbord sneltoetsen aan",
"videoPlayerNavigation": "Videospeler navigatie", "videoPlayerNavigation": "Videospeler navigatie",
"videoPlayerNavigationDescription": "Gebruik pijltjestoetsen om door de videospeler bediening te navigeren", "videoPlayerNavigationDescription": "Gebruik pijltjestoetsen om door de videospeler bediening te navigeren",
"crashReporting": "Crashrapportage",
"crashReportingDescription": "Crashrapporten verzenden om de app te verbeteren",
"debugLogging": "Debug logging", "debugLogging": "Debug logging",
"debugLoggingDescription": "Schakel gedetailleerde logging in voor probleemoplossing", "debugLoggingDescription": "Schakel gedetailleerde logging in voor probleemoplossing",
"viewLogs": "Bekijk logs", "viewLogs": "Bekijk logs",
+2 -2
View File
@@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang` /// To regenerate, run: `dart run slang`
/// ///
/// Locales: 9 /// Locales: 9
/// Strings: 7173 (797 per locale) /// Strings: 7245 (805 per locale)
/// ///
/// Built on 2026-02-24 at 21:50 UTC /// Built on 2026-03-02 at 06:26 UTC
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: type=lint, unused_import // ignore_for_file: type=lint, unused_import
+8 -2
View File
@@ -270,6 +270,8 @@ class _TranslationsSettingsDe implements TranslationsSettingsEn {
@override String get keyboardShortcutsDescription => 'Tastenkürzel anpassen'; @override String get keyboardShortcutsDescription => 'Tastenkürzel anpassen';
@override String get videoPlayerNavigation => 'Videoplayer-Navigation'; @override String get videoPlayerNavigation => 'Videoplayer-Navigation';
@override String get videoPlayerNavigationDescription => 'Pfeiltasten zur Navigation der Videoplayer-Steuerung verwenden'; @override String get videoPlayerNavigationDescription => 'Pfeiltasten zur Navigation der Videoplayer-Steuerung verwenden';
@override String get crashReporting => 'Absturzberichte';
@override String get crashReportingDescription => 'Absturzberichte senden, um die App zu verbessern';
@override String get debugLogging => 'Debug-Protokollierung'; @override String get debugLogging => 'Debug-Protokollierung';
@override String get debugLoggingDescription => 'Detaillierte Protokolle zur Fehleranalyse aktivieren'; @override String get debugLoggingDescription => 'Detaillierte Protokolle zur Fehleranalyse aktivieren';
@override String get viewLogs => 'Protokolle anzeigen'; @override String get viewLogs => 'Protokolle anzeigen';
@@ -1441,6 +1443,8 @@ extension on TranslationsDe {
'settings.keyboardShortcutsDescription' => 'Tastenkürzel anpassen', 'settings.keyboardShortcutsDescription' => 'Tastenkürzel anpassen',
'settings.videoPlayerNavigation' => 'Videoplayer-Navigation', 'settings.videoPlayerNavigation' => 'Videoplayer-Navigation',
'settings.videoPlayerNavigationDescription' => 'Pfeiltasten zur Navigation der Videoplayer-Steuerung verwenden', 'settings.videoPlayerNavigationDescription' => 'Pfeiltasten zur Navigation der Videoplayer-Steuerung verwenden',
'settings.crashReporting' => 'Absturzberichte',
'settings.crashReportingDescription' => 'Absturzberichte senden, um die App zu verbessern',
'settings.debugLogging' => 'Debug-Protokollierung', 'settings.debugLogging' => 'Debug-Protokollierung',
'settings.debugLoggingDescription' => 'Detaillierte Protokolle zur Fehleranalyse aktivieren', 'settings.debugLoggingDescription' => 'Detaillierte Protokolle zur Fehleranalyse aktivieren',
'settings.viewLogs' => 'Protokolle anzeigen', 'settings.viewLogs' => 'Protokolle anzeigen',
@@ -1487,6 +1491,8 @@ extension on TranslationsDe {
'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%', 'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%',
'settings.discordRichPresence' => 'Discord Rich Presence', 'settings.discordRichPresence' => 'Discord Rich Presence',
'settings.discordRichPresenceDescription' => 'Zeige auf Discord, was du gerade schaust', 'settings.discordRichPresenceDescription' => 'Zeige auf Discord, was du gerade schaust',
'settings.autoPip' => 'Automatisches Bild-in-Bild',
'settings.autoPipDescription' => 'Automatisch Bild-in-Bild aktivieren, wenn die App während der Wiedergabe verlassen wird',
'settings.matchContentFrameRate' => 'Inhalts-Bildrate anpassen', 'settings.matchContentFrameRate' => 'Inhalts-Bildrate anpassen',
'settings.matchContentFrameRateDescription' => 'Bildwiederholfrequenz des Displays an den Videoinhalt anpassen, reduziert Ruckeln und spart Akku', 'settings.matchContentFrameRateDescription' => 'Bildwiederholfrequenz des Displays an den Videoinhalt anpassen, reduziert Ruckeln und spart Akku',
'settings.tunneledPlayback' => 'Tunneled Playback', 'settings.tunneledPlayback' => 'Tunneled Playback',
@@ -1808,6 +1814,8 @@ extension on TranslationsDe {
'liveTv.nowPlaying' => 'Läuft gerade', 'liveTv.nowPlaying' => 'Läuft gerade',
'liveTv.noPrograms' => 'Keine Programmdaten verfügbar', 'liveTv.noPrograms' => 'Keine Programmdaten verfügbar',
'liveTv.channelNumber' => ({required Object number}) => 'Kanal ${number}', 'liveTv.channelNumber' => ({required Object number}) => 'Kanal ${number}',
_ => null,
} ?? switch (path) {
'liveTv.live' => 'LIVE', 'liveTv.live' => 'LIVE',
'liveTv.hd' => 'HD', 'liveTv.hd' => 'HD',
'liveTv.premiere' => 'NEU', 'liveTv.premiere' => 'NEU',
@@ -1822,8 +1830,6 @@ extension on TranslationsDe {
'liveTv.evening' => 'Abend', 'liveTv.evening' => 'Abend',
'liveTv.lateNight' => 'Spätnacht', 'liveTv.lateNight' => 'Spätnacht',
'liveTv.whatsOn' => 'Jetzt im TV', 'liveTv.whatsOn' => 'Jetzt im TV',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => 'Kanal ansehen', 'liveTv.watchChannel' => 'Kanal ansehen',
'downloads.title' => 'Downloads', 'downloads.title' => 'Downloads',
'downloads.manage' => 'Verwalten', 'downloads.manage' => 'Verwalten',
+11 -4
View File
@@ -472,7 +472,7 @@ class TranslationsSettingsEn {
/// en: 'Hide Spoilers for Unwatched Episodes' /// en: 'Hide Spoilers for Unwatched Episodes'
String get hideSpoilers => 'Hide Spoilers for Unwatched Episodes'; String get hideSpoilers => 'Hide Spoilers for Unwatched Episodes';
/// en: 'Blur thumbnails and hide descriptions for episodes you haven\'t watched yet' /// en: 'Blur thumbnails and hide descriptions for episodes you haven't watched yet'
String get hideSpoilersDescription => 'Blur thumbnails and hide descriptions for episodes you haven\'t watched yet'; String get hideSpoilersDescription => 'Blur thumbnails and hide descriptions for episodes you haven\'t watched yet';
/// en: 'Player Backend' /// en: 'Player Backend'
@@ -556,6 +556,12 @@ class TranslationsSettingsEn {
/// en: 'Use arrow keys to navigate video player controls' /// en: 'Use arrow keys to navigate video player controls'
String get videoPlayerNavigationDescription => 'Use arrow keys to navigate video player controls'; String get videoPlayerNavigationDescription => 'Use arrow keys to navigate video player controls';
/// en: 'Crash Reporting'
String get crashReporting => 'Crash Reporting';
/// en: 'Send crash reports to help improve the app'
String get crashReportingDescription => 'Send crash reports to help improve the app';
/// en: 'Debug Logging' /// en: 'Debug Logging'
String get debugLogging => 'Debug Logging'; String get debugLogging => 'Debug Logging';
@@ -2807,7 +2813,6 @@ class TranslationsCompanionRemotePairingEn {
/// en: 'Failed to connect: ${error}' /// en: 'Failed to connect: ${error}'
String failedToConnect({required Object error}) => 'Failed to connect: ${error}'; String failedToConnect({required Object error}) => 'Failed to connect: ${error}';
} }
// Path: companionRemote.remote // Path: companionRemote.remote
@@ -3055,6 +3060,8 @@ extension on Translations {
'settings.keyboardShortcutsDescription' => 'Customize keyboard shortcuts', 'settings.keyboardShortcutsDescription' => 'Customize keyboard shortcuts',
'settings.videoPlayerNavigation' => 'Video Player Navigation', 'settings.videoPlayerNavigation' => 'Video Player Navigation',
'settings.videoPlayerNavigationDescription' => 'Use arrow keys to navigate video player controls', 'settings.videoPlayerNavigationDescription' => 'Use arrow keys to navigate video player controls',
'settings.crashReporting' => 'Crash Reporting',
'settings.crashReportingDescription' => 'Send crash reports to help improve the app',
'settings.debugLogging' => 'Debug Logging', 'settings.debugLogging' => 'Debug Logging',
'settings.debugLoggingDescription' => 'Enable detailed logging for troubleshooting', 'settings.debugLoggingDescription' => 'Enable detailed logging for troubleshooting',
'settings.viewLogs' => 'View Logs', 'settings.viewLogs' => 'View Logs',
@@ -3424,6 +3431,8 @@ extension on Translations {
'liveTv.nowPlaying' => 'Now Playing', 'liveTv.nowPlaying' => 'Now Playing',
'liveTv.noPrograms' => 'No program data available', 'liveTv.noPrograms' => 'No program data available',
'liveTv.channelNumber' => ({required Object number}) => 'Ch. ${number}', 'liveTv.channelNumber' => ({required Object number}) => 'Ch. ${number}',
_ => null,
} ?? switch (path) {
'liveTv.live' => 'LIVE', 'liveTv.live' => 'LIVE',
'liveTv.hd' => 'HD', 'liveTv.hd' => 'HD',
'liveTv.premiere' => 'NEW', 'liveTv.premiere' => 'NEW',
@@ -3438,8 +3447,6 @@ extension on Translations {
'liveTv.evening' => 'Evening', 'liveTv.evening' => 'Evening',
'liveTv.lateNight' => 'Late Night', 'liveTv.lateNight' => 'Late Night',
'liveTv.whatsOn' => 'What\'s On', 'liveTv.whatsOn' => 'What\'s On',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => 'Watch Channel', 'liveTv.watchChannel' => 'Watch Channel',
'collections.title' => 'Collections', 'collections.title' => 'Collections',
'collections.collection' => 'Collection', 'collections.collection' => 'Collection',
+8 -2
View File
@@ -270,6 +270,8 @@ class _TranslationsSettingsEs implements TranslationsSettingsEn {
@override String get keyboardShortcutsDescription => 'Personalizar los atajos de teclado'; @override String get keyboardShortcutsDescription => 'Personalizar los atajos de teclado';
@override String get videoPlayerNavigation => 'Navegación del Reproductor de Video'; @override String get videoPlayerNavigation => 'Navegación del Reproductor de Video';
@override String get videoPlayerNavigationDescription => 'Usar las teclas de flecha para navegar por los controles del reproductor'; @override String get videoPlayerNavigationDescription => 'Usar las teclas de flecha para navegar por los controles del reproductor';
@override String get crashReporting => 'Informes de Errores';
@override String get crashReportingDescription => 'Enviar informes de errores para mejorar la aplicación';
@override String get debugLogging => 'Registro de Depuración'; @override String get debugLogging => 'Registro de Depuración';
@override String get debugLoggingDescription => 'Habilitar registros detallados para resolución de problemas'; @override String get debugLoggingDescription => 'Habilitar registros detallados para resolución de problemas';
@override String get viewLogs => 'Ver Logs'; @override String get viewLogs => 'Ver Logs';
@@ -1441,6 +1443,8 @@ extension on TranslationsEs {
'settings.keyboardShortcutsDescription' => 'Personalizar los atajos de teclado', 'settings.keyboardShortcutsDescription' => 'Personalizar los atajos de teclado',
'settings.videoPlayerNavigation' => 'Navegación del Reproductor de Video', 'settings.videoPlayerNavigation' => 'Navegación del Reproductor de Video',
'settings.videoPlayerNavigationDescription' => 'Usar las teclas de flecha para navegar por los controles del reproductor', 'settings.videoPlayerNavigationDescription' => 'Usar las teclas de flecha para navegar por los controles del reproductor',
'settings.crashReporting' => 'Informes de Errores',
'settings.crashReportingDescription' => 'Enviar informes de errores para mejorar la aplicación',
'settings.debugLogging' => 'Registro de Depuración', 'settings.debugLogging' => 'Registro de Depuración',
'settings.debugLoggingDescription' => 'Habilitar registros detallados para resolución de problemas', 'settings.debugLoggingDescription' => 'Habilitar registros detallados para resolución de problemas',
'settings.viewLogs' => 'Ver Logs', 'settings.viewLogs' => 'Ver Logs',
@@ -1487,6 +1491,8 @@ extension on TranslationsEs {
'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%', 'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%',
'settings.discordRichPresence' => 'Presencia de Discord', 'settings.discordRichPresence' => 'Presencia de Discord',
'settings.discordRichPresenceDescription' => 'Mostrar lo que estás viendo en Discord', 'settings.discordRichPresenceDescription' => 'Mostrar lo que estás viendo en Discord',
'settings.autoPip' => 'Imagen en imagen automática',
'settings.autoPipDescription' => 'Activar automáticamente imagen en imagen al salir de la app durante la reproducción',
'settings.matchContentFrameRate' => 'Ajustar frecuencia de actualización', 'settings.matchContentFrameRate' => 'Ajustar frecuencia de actualización',
'settings.matchContentFrameRateDescription' => 'Ajustar la frecuencia de actualización de la pantalla para que coincida con el video, reduciendo tirones y ahorrando batería', 'settings.matchContentFrameRateDescription' => 'Ajustar la frecuencia de actualización de la pantalla para que coincida con el video, reduciendo tirones y ahorrando batería',
'settings.tunneledPlayback' => 'Tunneled Playback', 'settings.tunneledPlayback' => 'Tunneled Playback',
@@ -1808,6 +1814,8 @@ extension on TranslationsEs {
'liveTv.nowPlaying' => 'Reproduciendo ahora', 'liveTv.nowPlaying' => 'Reproduciendo ahora',
'liveTv.noPrograms' => 'No hay datos de programación disponibles', 'liveTv.noPrograms' => 'No hay datos de programación disponibles',
'liveTv.channelNumber' => ({required Object number}) => 'Canal ${number}', 'liveTv.channelNumber' => ({required Object number}) => 'Canal ${number}',
_ => null,
} ?? switch (path) {
'liveTv.live' => 'EN VIVO', 'liveTv.live' => 'EN VIVO',
'liveTv.hd' => 'HD', 'liveTv.hd' => 'HD',
'liveTv.premiere' => 'NUEVO', 'liveTv.premiere' => 'NUEVO',
@@ -1822,8 +1830,6 @@ extension on TranslationsEs {
'liveTv.evening' => 'Noche', 'liveTv.evening' => 'Noche',
'liveTv.lateNight' => 'Trasnoche', 'liveTv.lateNight' => 'Trasnoche',
'liveTv.whatsOn' => 'En emisión', 'liveTv.whatsOn' => 'En emisión',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => 'Ver canal', 'liveTv.watchChannel' => 'Ver canal',
'collections.title' => 'Colecciones', 'collections.title' => 'Colecciones',
'collections.collection' => 'Colección', 'collections.collection' => 'Colección',
+8 -2
View File
@@ -270,6 +270,8 @@ class _TranslationsSettingsFr implements TranslationsSettingsEn {
@override String get keyboardShortcutsDescription => 'Personnaliser les raccourcis clavier'; @override String get keyboardShortcutsDescription => 'Personnaliser les raccourcis clavier';
@override String get videoPlayerNavigation => 'Navigation dans le lecteur vidéo'; @override String get videoPlayerNavigation => 'Navigation dans le lecteur vidéo';
@override String get videoPlayerNavigationDescription => 'Utilisez les touches fléchées pour naviguer dans les commandes du lecteur vidéo.'; @override String get videoPlayerNavigationDescription => 'Utilisez les touches fléchées pour naviguer dans les commandes du lecteur vidéo.';
@override String get crashReporting => 'Rapports de plantage';
@override String get crashReportingDescription => 'Envoyer des rapports de plantage pour améliorer l\'application';
@override String get debugLogging => 'Journalisation de débogage'; @override String get debugLogging => 'Journalisation de débogage';
@override String get debugLoggingDescription => 'Activer la journalisation détaillée pour le dépannage'; @override String get debugLoggingDescription => 'Activer la journalisation détaillée pour le dépannage';
@override String get viewLogs => 'Voir les logs'; @override String get viewLogs => 'Voir les logs';
@@ -1441,6 +1443,8 @@ extension on TranslationsFr {
'settings.keyboardShortcutsDescription' => 'Personnaliser les raccourcis clavier', 'settings.keyboardShortcutsDescription' => 'Personnaliser les raccourcis clavier',
'settings.videoPlayerNavigation' => 'Navigation dans le lecteur vidéo', 'settings.videoPlayerNavigation' => 'Navigation dans le lecteur vidéo',
'settings.videoPlayerNavigationDescription' => 'Utilisez les touches fléchées pour naviguer dans les commandes du lecteur vidéo.', 'settings.videoPlayerNavigationDescription' => 'Utilisez les touches fléchées pour naviguer dans les commandes du lecteur vidéo.',
'settings.crashReporting' => 'Rapports de plantage',
'settings.crashReportingDescription' => 'Envoyer des rapports de plantage pour améliorer l\'application',
'settings.debugLogging' => 'Journalisation de débogage', 'settings.debugLogging' => 'Journalisation de débogage',
'settings.debugLoggingDescription' => 'Activer la journalisation détaillée pour le dépannage', 'settings.debugLoggingDescription' => 'Activer la journalisation détaillée pour le dépannage',
'settings.viewLogs' => 'Voir les logs', 'settings.viewLogs' => 'Voir les logs',
@@ -1487,6 +1491,8 @@ extension on TranslationsFr {
'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%', 'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%',
'settings.discordRichPresence' => 'Discord Rich Presence', 'settings.discordRichPresence' => 'Discord Rich Presence',
'settings.discordRichPresenceDescription' => 'Montrez ce que vous regardez sur Discord', 'settings.discordRichPresenceDescription' => 'Montrez ce que vous regardez sur Discord',
'settings.autoPip' => 'Image dans l\'image automatique',
'settings.autoPipDescription' => 'Activer automatiquement l\'image dans l\'image en quittant l\'application pendant la lecture',
'settings.matchContentFrameRate' => 'Fréquence d\'images du contenu correspondant', 'settings.matchContentFrameRate' => 'Fréquence d\'images du contenu correspondant',
'settings.matchContentFrameRateDescription' => 'Ajustez la fréquence de rafraîchissement de l\'écran en fonction du contenu vidéo, ce qui réduit les saccades et économise la batterie', 'settings.matchContentFrameRateDescription' => 'Ajustez la fréquence de rafraîchissement de l\'écran en fonction du contenu vidéo, ce qui réduit les saccades et économise la batterie',
'settings.tunneledPlayback' => 'Tunneled Playback', 'settings.tunneledPlayback' => 'Tunneled Playback',
@@ -1808,6 +1814,8 @@ extension on TranslationsFr {
'liveTv.nowPlaying' => 'En cours de lecture', 'liveTv.nowPlaying' => 'En cours de lecture',
'liveTv.noPrograms' => 'Aucune donnée de programme disponible', 'liveTv.noPrograms' => 'Aucune donnée de programme disponible',
'liveTv.channelNumber' => ({required Object number}) => 'Ch. ${number}', 'liveTv.channelNumber' => ({required Object number}) => 'Ch. ${number}',
_ => null,
} ?? switch (path) {
'liveTv.live' => 'EN DIRECT', 'liveTv.live' => 'EN DIRECT',
'liveTv.hd' => 'HD', 'liveTv.hd' => 'HD',
'liveTv.premiere' => 'NOUVEAU', 'liveTv.premiere' => 'NOUVEAU',
@@ -1822,8 +1830,6 @@ extension on TranslationsFr {
'liveTv.evening' => 'Soirée', 'liveTv.evening' => 'Soirée',
'liveTv.lateNight' => 'Nuit tardive', 'liveTv.lateNight' => 'Nuit tardive',
'liveTv.whatsOn' => 'En ce moment', 'liveTv.whatsOn' => 'En ce moment',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => 'Regarder la chaîne', 'liveTv.watchChannel' => 'Regarder la chaîne',
'collections.title' => 'Collections', 'collections.title' => 'Collections',
'collections.collection' => 'Collection', 'collections.collection' => 'Collection',
+8 -2
View File
@@ -270,6 +270,8 @@ class _TranslationsSettingsIt implements TranslationsSettingsEn {
@override String get keyboardShortcutsDescription => 'Personalizza le scorciatoie da tastiera'; @override String get keyboardShortcutsDescription => 'Personalizza le scorciatoie da tastiera';
@override String get videoPlayerNavigation => 'Navigazione del lettore video'; @override String get videoPlayerNavigation => 'Navigazione del lettore video';
@override String get videoPlayerNavigationDescription => 'Usa i tasti freccia per navigare nei controlli del lettore video'; @override String get videoPlayerNavigationDescription => 'Usa i tasti freccia per navigare nei controlli del lettore video';
@override String get crashReporting => 'Segnalazione errori';
@override String get crashReportingDescription => 'Invia segnalazioni di errori per migliorare l\'app';
@override String get debugLogging => 'Log di debug'; @override String get debugLogging => 'Log di debug';
@override String get debugLoggingDescription => 'Abilita il logging dettagliato per la risoluzione dei problemi'; @override String get debugLoggingDescription => 'Abilita il logging dettagliato per la risoluzione dei problemi';
@override String get viewLogs => 'Visualizza log'; @override String get viewLogs => 'Visualizza log';
@@ -1441,6 +1443,8 @@ extension on TranslationsIt {
'settings.keyboardShortcutsDescription' => 'Personalizza le scorciatoie da tastiera', 'settings.keyboardShortcutsDescription' => 'Personalizza le scorciatoie da tastiera',
'settings.videoPlayerNavigation' => 'Navigazione del lettore video', 'settings.videoPlayerNavigation' => 'Navigazione del lettore video',
'settings.videoPlayerNavigationDescription' => 'Usa i tasti freccia per navigare nei controlli del lettore video', 'settings.videoPlayerNavigationDescription' => 'Usa i tasti freccia per navigare nei controlli del lettore video',
'settings.crashReporting' => 'Segnalazione errori',
'settings.crashReportingDescription' => 'Invia segnalazioni di errori per migliorare l\'app',
'settings.debugLogging' => 'Log di debug', 'settings.debugLogging' => 'Log di debug',
'settings.debugLoggingDescription' => 'Abilita il logging dettagliato per la risoluzione dei problemi', 'settings.debugLoggingDescription' => 'Abilita il logging dettagliato per la risoluzione dei problemi',
'settings.viewLogs' => 'Visualizza log', 'settings.viewLogs' => 'Visualizza log',
@@ -1487,6 +1491,8 @@ extension on TranslationsIt {
'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%', 'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%',
'settings.discordRichPresence' => 'Discord Rich Presence', 'settings.discordRichPresence' => 'Discord Rich Presence',
'settings.discordRichPresenceDescription' => 'Mostra su Discord cosa stai guardando', 'settings.discordRichPresenceDescription' => 'Mostra su Discord cosa stai guardando',
'settings.autoPip' => 'Picture-in-Picture automatico',
'settings.autoPipDescription' => 'Attiva automaticamente il picture-in-picture quando si esce dall\'app durante la riproduzione',
'settings.matchContentFrameRate' => 'Adatta frequenza fotogrammi', 'settings.matchContentFrameRate' => 'Adatta frequenza fotogrammi',
'settings.matchContentFrameRateDescription' => 'Regola la frequenza di aggiornamento del display in base al contenuto video, riducendo i tremolii e risparmiando batteria', 'settings.matchContentFrameRateDescription' => 'Regola la frequenza di aggiornamento del display in base al contenuto video, riducendo i tremolii e risparmiando batteria',
'settings.tunneledPlayback' => 'Tunneled Playback', 'settings.tunneledPlayback' => 'Tunneled Playback',
@@ -1808,6 +1814,8 @@ extension on TranslationsIt {
'liveTv.nowPlaying' => 'In riproduzione', 'liveTv.nowPlaying' => 'In riproduzione',
'liveTv.noPrograms' => 'Nessun dato di programma disponibile', 'liveTv.noPrograms' => 'Nessun dato di programma disponibile',
'liveTv.channelNumber' => ({required Object number}) => 'Canale ${number}', 'liveTv.channelNumber' => ({required Object number}) => 'Canale ${number}',
_ => null,
} ?? switch (path) {
'liveTv.live' => 'IN DIRETTA', 'liveTv.live' => 'IN DIRETTA',
'liveTv.hd' => 'HD', 'liveTv.hd' => 'HD',
'liveTv.premiere' => 'NUOVO', 'liveTv.premiere' => 'NUOVO',
@@ -1822,8 +1830,6 @@ extension on TranslationsIt {
'liveTv.evening' => 'Sera', 'liveTv.evening' => 'Sera',
'liveTv.lateNight' => 'Notte tarda', 'liveTv.lateNight' => 'Notte tarda',
'liveTv.whatsOn' => 'In onda ora', 'liveTv.whatsOn' => 'In onda ora',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => 'Guarda canale', 'liveTv.watchChannel' => 'Guarda canale',
'downloads.title' => 'Download', 'downloads.title' => 'Download',
'downloads.manage' => 'Gestisci', 'downloads.manage' => 'Gestisci',
+8 -2
View File
@@ -270,6 +270,8 @@ class _TranslationsSettingsKo implements TranslationsSettingsEn {
@override String get keyboardShortcutsDescription => '사용자 정의 키보드 단축키'; @override String get keyboardShortcutsDescription => '사용자 정의 키보드 단축키';
@override String get videoPlayerNavigation => '비디오 플레이어 탐색'; @override String get videoPlayerNavigation => '비디오 플레이어 탐색';
@override String get videoPlayerNavigationDescription => '방향 키를 사용하여 비디오 플레이어 컨트롤 탐색'; @override String get videoPlayerNavigationDescription => '방향 키를 사용하여 비디오 플레이어 컨트롤 탐색';
@override String get crashReporting => '충돌 보고';
@override String get crashReportingDescription => '앱 개선을 위해 충돌 보고서 전송';
@override String get debugLogging => '디버그 로깅'; @override String get debugLogging => '디버그 로깅';
@override String get debugLoggingDescription => '문제 해결을 위해 상세 로깅 활성화'; @override String get debugLoggingDescription => '문제 해결을 위해 상세 로깅 활성화';
@override String get viewLogs => '로그 보기'; @override String get viewLogs => '로그 보기';
@@ -1441,6 +1443,8 @@ extension on TranslationsKo {
'settings.keyboardShortcutsDescription' => '사용자 정의 키보드 단축키', 'settings.keyboardShortcutsDescription' => '사용자 정의 키보드 단축키',
'settings.videoPlayerNavigation' => '비디오 플레이어 탐색', 'settings.videoPlayerNavigation' => '비디오 플레이어 탐색',
'settings.videoPlayerNavigationDescription' => '방향 키를 사용하여 비디오 플레이어 컨트롤 탐색', 'settings.videoPlayerNavigationDescription' => '방향 키를 사용하여 비디오 플레이어 컨트롤 탐색',
'settings.crashReporting' => '충돌 보고',
'settings.crashReportingDescription' => '앱 개선을 위해 충돌 보고서 전송',
'settings.debugLogging' => '디버그 로깅', 'settings.debugLogging' => '디버그 로깅',
'settings.debugLoggingDescription' => '문제 해결을 위해 상세 로깅 활성화', 'settings.debugLoggingDescription' => '문제 해결을 위해 상세 로깅 활성화',
'settings.viewLogs' => '로그 보기', 'settings.viewLogs' => '로그 보기',
@@ -1487,6 +1491,8 @@ extension on TranslationsKo {
'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%', 'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%',
'settings.discordRichPresence' => 'Discord Rich Presence', 'settings.discordRichPresence' => 'Discord Rich Presence',
'settings.discordRichPresenceDescription' => 'Discord에서 시청 중인 콘텐츠 표시', 'settings.discordRichPresenceDescription' => 'Discord에서 시청 중인 콘텐츠 표시',
'settings.autoPip' => '자동 PIP 모드',
'settings.autoPipDescription' => '재생 중 앱을 나갈 때 자동으로 PIP 모드로 전환',
'settings.matchContentFrameRate' => '콘텐츠 프레임 레이트 맞춤', 'settings.matchContentFrameRate' => '콘텐츠 프레임 레이트 맞춤',
'settings.matchContentFrameRateDescription' => '비디오 콘텐츠에 맞게 디스플레이 주사율을 조정하여 떨림을 줄이고 배터리를 절약합니다', 'settings.matchContentFrameRateDescription' => '비디오 콘텐츠에 맞게 디스플레이 주사율을 조정하여 떨림을 줄이고 배터리를 절약합니다',
'settings.tunneledPlayback' => 'Tunneled Playback', 'settings.tunneledPlayback' => 'Tunneled Playback',
@@ -1808,6 +1814,8 @@ extension on TranslationsKo {
'liveTv.nowPlaying' => '현재 재생 중', 'liveTv.nowPlaying' => '현재 재생 중',
'liveTv.noPrograms' => '프로그램 데이터가 없습니다', 'liveTv.noPrograms' => '프로그램 데이터가 없습니다',
'liveTv.channelNumber' => ({required Object number}) => '채널 ${number}', 'liveTv.channelNumber' => ({required Object number}) => '채널 ${number}',
_ => null,
} ?? switch (path) {
'liveTv.live' => '실시간', 'liveTv.live' => '실시간',
'liveTv.hd' => 'HD', 'liveTv.hd' => 'HD',
'liveTv.premiere' => '신규', 'liveTv.premiere' => '신규',
@@ -1822,8 +1830,6 @@ extension on TranslationsKo {
'liveTv.evening' => '저녁', 'liveTv.evening' => '저녁',
'liveTv.lateNight' => '심야 방송', 'liveTv.lateNight' => '심야 방송',
'liveTv.whatsOn' => '지금 방송 중', 'liveTv.whatsOn' => '지금 방송 중',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => '채널 시청', 'liveTv.watchChannel' => '채널 시청',
'collections.title' => '컬렉션', 'collections.title' => '컬렉션',
'collections.collection' => '컬렉션', 'collections.collection' => '컬렉션',
+8 -2
View File
@@ -270,6 +270,8 @@ class _TranslationsSettingsNl implements TranslationsSettingsEn {
@override String get keyboardShortcutsDescription => 'Pas toetsenbord sneltoetsen aan'; @override String get keyboardShortcutsDescription => 'Pas toetsenbord sneltoetsen aan';
@override String get videoPlayerNavigation => 'Videospeler navigatie'; @override String get videoPlayerNavigation => 'Videospeler navigatie';
@override String get videoPlayerNavigationDescription => 'Gebruik pijltjestoetsen om door de videospeler bediening te navigeren'; @override String get videoPlayerNavigationDescription => 'Gebruik pijltjestoetsen om door de videospeler bediening te navigeren';
@override String get crashReporting => 'Crashrapportage';
@override String get crashReportingDescription => 'Crashrapporten verzenden om de app te verbeteren';
@override String get debugLogging => 'Debug logging'; @override String get debugLogging => 'Debug logging';
@override String get debugLoggingDescription => 'Schakel gedetailleerde logging in voor probleemoplossing'; @override String get debugLoggingDescription => 'Schakel gedetailleerde logging in voor probleemoplossing';
@override String get viewLogs => 'Bekijk logs'; @override String get viewLogs => 'Bekijk logs';
@@ -1441,6 +1443,8 @@ extension on TranslationsNl {
'settings.keyboardShortcutsDescription' => 'Pas toetsenbord sneltoetsen aan', 'settings.keyboardShortcutsDescription' => 'Pas toetsenbord sneltoetsen aan',
'settings.videoPlayerNavigation' => 'Videospeler navigatie', 'settings.videoPlayerNavigation' => 'Videospeler navigatie',
'settings.videoPlayerNavigationDescription' => 'Gebruik pijltjestoetsen om door de videospeler bediening te navigeren', 'settings.videoPlayerNavigationDescription' => 'Gebruik pijltjestoetsen om door de videospeler bediening te navigeren',
'settings.crashReporting' => 'Crashrapportage',
'settings.crashReportingDescription' => 'Crashrapporten verzenden om de app te verbeteren',
'settings.debugLogging' => 'Debug logging', 'settings.debugLogging' => 'Debug logging',
'settings.debugLoggingDescription' => 'Schakel gedetailleerde logging in voor probleemoplossing', 'settings.debugLoggingDescription' => 'Schakel gedetailleerde logging in voor probleemoplossing',
'settings.viewLogs' => 'Bekijk logs', 'settings.viewLogs' => 'Bekijk logs',
@@ -1487,6 +1491,8 @@ extension on TranslationsNl {
'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%', 'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%',
'settings.discordRichPresence' => 'Discord Rich Presence', 'settings.discordRichPresence' => 'Discord Rich Presence',
'settings.discordRichPresenceDescription' => 'Toon op Discord wat je aan het kijken bent', 'settings.discordRichPresenceDescription' => 'Toon op Discord wat je aan het kijken bent',
'settings.autoPip' => 'Automatische beeld-in-beeld',
'settings.autoPipDescription' => 'Automatisch beeld-in-beeld activeren bij het verlaten van de app tijdens afspelen',
'settings.matchContentFrameRate' => 'Inhoudsframesnelheid afstemmen', 'settings.matchContentFrameRate' => 'Inhoudsframesnelheid afstemmen',
'settings.matchContentFrameRateDescription' => 'Pas de schermverversingssnelheid aan op de video-inhoud, vermindert haperingen en bespaart batterij', 'settings.matchContentFrameRateDescription' => 'Pas de schermverversingssnelheid aan op de video-inhoud, vermindert haperingen en bespaart batterij',
'settings.tunneledPlayback' => 'Tunneled Playback', 'settings.tunneledPlayback' => 'Tunneled Playback',
@@ -1808,6 +1814,8 @@ extension on TranslationsNl {
'liveTv.nowPlaying' => 'Nu aan het afspelen', 'liveTv.nowPlaying' => 'Nu aan het afspelen',
'liveTv.noPrograms' => 'Geen programmagegevens beschikbaar', 'liveTv.noPrograms' => 'Geen programmagegevens beschikbaar',
'liveTv.channelNumber' => ({required Object number}) => 'Kanaal ${number}', 'liveTv.channelNumber' => ({required Object number}) => 'Kanaal ${number}',
_ => null,
} ?? switch (path) {
'liveTv.live' => 'LIVE', 'liveTv.live' => 'LIVE',
'liveTv.hd' => 'HD', 'liveTv.hd' => 'HD',
'liveTv.premiere' => 'NIEUW', 'liveTv.premiere' => 'NIEUW',
@@ -1822,8 +1830,6 @@ extension on TranslationsNl {
'liveTv.evening' => 'Avond', 'liveTv.evening' => 'Avond',
'liveTv.lateNight' => 'Late avond', 'liveTv.lateNight' => 'Late avond',
'liveTv.whatsOn' => 'Nu op TV', 'liveTv.whatsOn' => 'Nu op TV',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => 'Kanaal bekijken', 'liveTv.watchChannel' => 'Kanaal bekijken',
'downloads.title' => 'Downloads', 'downloads.title' => 'Downloads',
'downloads.manage' => 'Beheren', 'downloads.manage' => 'Beheren',
+8 -2
View File
@@ -270,6 +270,8 @@ class _TranslationsSettingsSv implements TranslationsSettingsEn {
@override String get keyboardShortcutsDescription => 'Anpassa tangentbordsgenvägar'; @override String get keyboardShortcutsDescription => 'Anpassa tangentbordsgenvägar';
@override String get videoPlayerNavigation => 'Navigering i videospelaren'; @override String get videoPlayerNavigation => 'Navigering i videospelaren';
@override String get videoPlayerNavigationDescription => 'Använd piltangenter för att navigera videospelarens kontroller'; @override String get videoPlayerNavigationDescription => 'Använd piltangenter för att navigera videospelarens kontroller';
@override String get crashReporting => 'Kraschrapportering';
@override String get crashReportingDescription => 'Skicka kraschrapporter för att förbättra appen';
@override String get debugLogging => 'Felsökningsloggning'; @override String get debugLogging => 'Felsökningsloggning';
@override String get debugLoggingDescription => 'Aktivera detaljerad loggning för felsökning'; @override String get debugLoggingDescription => 'Aktivera detaljerad loggning för felsökning';
@override String get viewLogs => 'Visa loggar'; @override String get viewLogs => 'Visa loggar';
@@ -1441,6 +1443,8 @@ extension on TranslationsSv {
'settings.keyboardShortcutsDescription' => 'Anpassa tangentbordsgenvägar', 'settings.keyboardShortcutsDescription' => 'Anpassa tangentbordsgenvägar',
'settings.videoPlayerNavigation' => 'Navigering i videospelaren', 'settings.videoPlayerNavigation' => 'Navigering i videospelaren',
'settings.videoPlayerNavigationDescription' => 'Använd piltangenter för att navigera videospelarens kontroller', 'settings.videoPlayerNavigationDescription' => 'Använd piltangenter för att navigera videospelarens kontroller',
'settings.crashReporting' => 'Kraschrapportering',
'settings.crashReportingDescription' => 'Skicka kraschrapporter för att förbättra appen',
'settings.debugLogging' => 'Felsökningsloggning', 'settings.debugLogging' => 'Felsökningsloggning',
'settings.debugLoggingDescription' => 'Aktivera detaljerad loggning för felsökning', 'settings.debugLoggingDescription' => 'Aktivera detaljerad loggning för felsökning',
'settings.viewLogs' => 'Visa loggar', 'settings.viewLogs' => 'Visa loggar',
@@ -1487,6 +1491,8 @@ extension on TranslationsSv {
'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%', 'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%',
'settings.discordRichPresence' => 'Discord Rich Presence', 'settings.discordRichPresence' => 'Discord Rich Presence',
'settings.discordRichPresenceDescription' => 'Visa vad du tittar på i Discord', 'settings.discordRichPresenceDescription' => 'Visa vad du tittar på i Discord',
'settings.autoPip' => 'Automatisk bild-i-bild',
'settings.autoPipDescription' => 'Aktivera bild-i-bild automatiskt när appen lämnas under uppspelning',
'settings.matchContentFrameRate' => 'Matcha innehållets bildfrekvens', 'settings.matchContentFrameRate' => 'Matcha innehållets bildfrekvens',
'settings.matchContentFrameRateDescription' => 'Justera skärmens uppdateringsfrekvens för att matcha videoinnehållet, minskar hackighet och sparar batteri', 'settings.matchContentFrameRateDescription' => 'Justera skärmens uppdateringsfrekvens för att matcha videoinnehållet, minskar hackighet och sparar batteri',
'settings.tunneledPlayback' => 'Tunneled Playback', 'settings.tunneledPlayback' => 'Tunneled Playback',
@@ -1808,6 +1814,8 @@ extension on TranslationsSv {
'liveTv.nowPlaying' => 'Spelas nu', 'liveTv.nowPlaying' => 'Spelas nu',
'liveTv.noPrograms' => 'Ingen programdata tillgänglig', 'liveTv.noPrograms' => 'Ingen programdata tillgänglig',
'liveTv.channelNumber' => ({required Object number}) => 'Kanal ${number}', 'liveTv.channelNumber' => ({required Object number}) => 'Kanal ${number}',
_ => null,
} ?? switch (path) {
'liveTv.live' => 'LIVE', 'liveTv.live' => 'LIVE',
'liveTv.hd' => 'HD', 'liveTv.hd' => 'HD',
'liveTv.premiere' => 'NY', 'liveTv.premiere' => 'NY',
@@ -1822,8 +1830,6 @@ extension on TranslationsSv {
'liveTv.evening' => 'Kväll', 'liveTv.evening' => 'Kväll',
'liveTv.lateNight' => 'Sen kväll', 'liveTv.lateNight' => 'Sen kväll',
'liveTv.whatsOn' => 'På TV nu', 'liveTv.whatsOn' => 'På TV nu',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => 'Titta på kanal', 'liveTv.watchChannel' => 'Titta på kanal',
'downloads.title' => 'Nedladdningar', 'downloads.title' => 'Nedladdningar',
'downloads.manage' => 'Hantera', 'downloads.manage' => 'Hantera',
+8 -2
View File
@@ -270,6 +270,8 @@ class _TranslationsSettingsZh implements TranslationsSettingsEn {
@override String get keyboardShortcutsDescription => '自定义键盘快捷键'; @override String get keyboardShortcutsDescription => '自定义键盘快捷键';
@override String get videoPlayerNavigation => '视频播放器导航'; @override String get videoPlayerNavigation => '视频播放器导航';
@override String get videoPlayerNavigationDescription => '使用方向键导航视频播放器控件'; @override String get videoPlayerNavigationDescription => '使用方向键导航视频播放器控件';
@override String get crashReporting => '崩溃报告';
@override String get crashReportingDescription => '发送崩溃报告以帮助改进应用';
@override String get debugLogging => '调试日志'; @override String get debugLogging => '调试日志';
@override String get debugLoggingDescription => '启用详细日志记录以便故障排除'; @override String get debugLoggingDescription => '启用详细日志记录以便故障排除';
@override String get viewLogs => '查看日志'; @override String get viewLogs => '查看日志';
@@ -1441,6 +1443,8 @@ extension on TranslationsZh {
'settings.keyboardShortcutsDescription' => '自定义键盘快捷键', 'settings.keyboardShortcutsDescription' => '自定义键盘快捷键',
'settings.videoPlayerNavigation' => '视频播放器导航', 'settings.videoPlayerNavigation' => '视频播放器导航',
'settings.videoPlayerNavigationDescription' => '使用方向键导航视频播放器控件', 'settings.videoPlayerNavigationDescription' => '使用方向键导航视频播放器控件',
'settings.crashReporting' => '崩溃报告',
'settings.crashReportingDescription' => '发送崩溃报告以帮助改进应用',
'settings.debugLogging' => '调试日志', 'settings.debugLogging' => '调试日志',
'settings.debugLoggingDescription' => '启用详细日志记录以便故障排除', 'settings.debugLoggingDescription' => '启用详细日志记录以便故障排除',
'settings.viewLogs' => '查看日志', 'settings.viewLogs' => '查看日志',
@@ -1487,6 +1491,8 @@ extension on TranslationsZh {
'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%', 'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%',
'settings.discordRichPresence' => 'Discord 动态状态', 'settings.discordRichPresence' => 'Discord 动态状态',
'settings.discordRichPresenceDescription' => '在 Discord 上显示您正在观看的内容', 'settings.discordRichPresenceDescription' => '在 Discord 上显示您正在观看的内容',
'settings.autoPip' => '自动画中画',
'settings.autoPipDescription' => '在播放期间离开应用时自动进入画中画模式',
'settings.matchContentFrameRate' => '匹配内容帧率', 'settings.matchContentFrameRate' => '匹配内容帧率',
'settings.matchContentFrameRateDescription' => '调整显示刷新率以匹配视频内容,减少画面抖动并节省电量', 'settings.matchContentFrameRateDescription' => '调整显示刷新率以匹配视频内容,减少画面抖动并节省电量',
'settings.tunneledPlayback' => 'Tunneled Playback', 'settings.tunneledPlayback' => 'Tunneled Playback',
@@ -1808,6 +1814,8 @@ extension on TranslationsZh {
'liveTv.nowPlaying' => '正在播放', 'liveTv.nowPlaying' => '正在播放',
'liveTv.noPrograms' => '没有可用的节目数据', 'liveTv.noPrograms' => '没有可用的节目数据',
'liveTv.channelNumber' => ({required Object number}) => '频道 ${number}', 'liveTv.channelNumber' => ({required Object number}) => '频道 ${number}',
_ => null,
} ?? switch (path) {
'liveTv.live' => '直播', 'liveTv.live' => '直播',
'liveTv.hd' => '高清', 'liveTv.hd' => '高清',
'liveTv.premiere' => '', 'liveTv.premiere' => '',
@@ -1822,8 +1830,6 @@ extension on TranslationsZh {
'liveTv.evening' => '晚上', 'liveTv.evening' => '晚上',
'liveTv.lateNight' => '深夜', 'liveTv.lateNight' => '深夜',
'liveTv.whatsOn' => '正在播出', 'liveTv.whatsOn' => '正在播出',
_ => null,
} ?? switch (path) {
'liveTv.watchChannel' => '观看频道', 'liveTv.watchChannel' => '观看频道',
'downloads.title' => '下载', 'downloads.title' => '下载',
'downloads.manage' => '管理', 'downloads.manage' => '管理',
+2
View File
@@ -151,6 +151,8 @@
"keyboardShortcutsDescription": "Anpassa tangentbordsgenvägar", "keyboardShortcutsDescription": "Anpassa tangentbordsgenvägar",
"videoPlayerNavigation": "Navigering i videospelaren", "videoPlayerNavigation": "Navigering i videospelaren",
"videoPlayerNavigationDescription": "Använd piltangenter för att navigera videospelarens kontroller", "videoPlayerNavigationDescription": "Använd piltangenter för att navigera videospelarens kontroller",
"crashReporting": "Kraschrapportering",
"crashReportingDescription": "Skicka kraschrapporter för att förbättra appen",
"debugLogging": "Felsökningsloggning", "debugLogging": "Felsökningsloggning",
"debugLoggingDescription": "Aktivera detaljerad loggning för felsökning", "debugLoggingDescription": "Aktivera detaljerad loggning för felsökning",
"viewLogs": "Visa loggar", "viewLogs": "Visa loggar",
+2
View File
@@ -151,6 +151,8 @@
"keyboardShortcutsDescription": "自定义键盘快捷键", "keyboardShortcutsDescription": "自定义键盘快捷键",
"videoPlayerNavigation": "视频播放器导航", "videoPlayerNavigation": "视频播放器导航",
"videoPlayerNavigationDescription": "使用方向键导航视频播放器控件", "videoPlayerNavigationDescription": "使用方向键导航视频播放器控件",
"crashReporting": "崩溃报告",
"crashReportingDescription": "发送崩溃报告以帮助改进应用",
"debugLogging": "调试日志", "debugLogging": "调试日志",
"debugLoggingDescription": "启用详细日志记录以便故障排除", "debugLoggingDescription": "启用详细日志记录以便故障排除",
"viewLogs": "查看日志", "viewLogs": "查看日志",
+50
View File
@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
@@ -5,6 +7,7 @@ import 'dart:io' show Platform;
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'screens/main_screen.dart'; import 'screens/main_screen.dart';
import 'screens/auth_screen.dart'; import 'screens/auth_screen.dart';
import 'services/storage_service.dart'; import 'services/storage_service.dart';
@@ -48,6 +51,7 @@ import 'focus/input_mode_tracker.dart';
import 'focus/key_event_utils.dart'; import 'focus/key_event_utils.dart';
import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/date_symbol_data_local.dart';
import 'utils/navigation_transitions.dart'; import 'utils/navigation_transitions.dart';
import 'utils/log_redaction_manager.dart';
// Workaround for Flutter bug #177992: iPadOS 26.1+ misinterprets fake touch events // Workaround for Flutter bug #177992: iPadOS 26.1+ misinterprets fake touch events
// at (0,0) as barrier taps, causing modals to dismiss immediately. // at (0,0) as barrier taps, causing modals to dismiss immediately.
@@ -70,6 +74,17 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
_installZeroOffsetPointerGuard(); // Workaround for iPadOS 26.1+ modal dismissal bug _installZeroOffsetPointerGuard(); // Workaround for iPadOS 26.1+ modal dismissal bug
await SentryFlutter.init(
(options) {
options.dsn = 'https://7aa61595518d4770a4d5ea412982dcd7@bugs.plezy.app/1';
options.sendDefaultPii = true;
options.tracesSampleRate = 0;
options.anrEnabled = true;
options.anrTimeoutInterval = const Duration(seconds: 5);
options.attachStacktrace = true;
options.beforeSend = _beforeSend;
},
appRunner: () async {
// Initialize settings first to get saved locale // Initialize settings first to get saved locale
final settings = await SettingsService.getInstance(); final settings = await SettingsService.getInstance();
final savedLocale = settings.getAppLocale(); final savedLocale = settings.getAppLocale();
@@ -133,6 +148,41 @@ void main() async {
_registerShaderLicenses(); _registerShaderLicenses();
runApp(const MainApp()); runApp(const MainApp());
},
);
}
FutureOr<SentryEvent?> _beforeSend(SentryEvent event, Hint hint) {
// Drop event if user opted out of crash reporting
final instance = SettingsService.instanceOrNull;
if (instance != null && !instance.getCrashReporting()) return null;
// Scrub Plex tokens and server URLs from exception messages
var exceptions = event.exceptions;
if (exceptions != null) {
exceptions = exceptions.map((e) {
final value = e.value;
if (value != null) {
return e.copyWith(value: LogRedactionManager.redact(value));
}
return e;
}).toList();
}
// Scrub breadcrumb messages and data
var breadcrumbs = event.breadcrumbs;
if (breadcrumbs != null) {
breadcrumbs = breadcrumbs.map((b) {
final message = b.message;
final data = b.data;
return b.copyWith(
message: message != null ? LogRedactionManager.redact(message) : null,
data: data?.map((k, v) => MapEntry(k, v is String ? LogRedactionManager.redact(v) : v)),
);
}).toList();
}
return event.copyWith(exceptions: exceptions, breadcrumbs: breadcrumbs);
} }
void _registerShaderLicenses() { void _registerShaderLicenses() {
+30 -1
View File
@@ -1,8 +1,10 @@
import 'dart:io'; import 'dart:io';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:plezy/widgets/app_icon.dart'; import 'package:plezy/widgets/app_icon.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import '../../models/hotkey_model.dart'; import '../../models/hotkey_model.dart';
@@ -97,6 +99,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
static const _kDownloadOnWifiOnly = 'download_on_wifi_only'; static const _kDownloadOnWifiOnly = 'download_on_wifi_only';
static const _kVideoPlayerControls = 'video_player_controls'; static const _kVideoPlayerControls = 'video_player_controls';
static const _kVideoPlayerNavigation = 'video_player_navigation'; static const _kVideoPlayerNavigation = 'video_player_navigation';
static const _kCrashReporting = 'crash_reporting';
static const _kDebugLogging = 'debug_logging'; static const _kDebugLogging = 'debug_logging';
static const _kViewLogs = 'view_logs'; static const _kViewLogs = 'view_logs';
static const _kClearCache = 'clear_cache'; static const _kClearCache = 'clear_cache';
@@ -107,6 +110,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
late final bool _keyboardShortcutsSupported = KeyboardShortcutsService.isPlatformSupported(); late final bool _keyboardShortcutsSupported = KeyboardShortcutsService.isPlatformSupported();
bool _isLoading = true; bool _isLoading = true;
bool _crashReporting = true;
bool _enableDebugLogging = false; bool _enableDebugLogging = false;
bool _enableHardwareDecoding = true; bool _enableHardwareDecoding = true;
int _bufferSize = 0; int _bufferSize = 0;
@@ -183,6 +187,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
_crashReporting = _settingsService.getCrashReporting();
_enableDebugLogging = _settingsService.getEnableDebugLogging(); _enableDebugLogging = _settingsService.getEnableDebugLogging();
_enableHardwareDecoding = _settingsService.getEnableHardwareDecoding(); _enableHardwareDecoding = _settingsService.getEnableHardwareDecoding();
_bufferSize = _settingsService.getBufferSize(); _bufferSize = _settingsService.getBufferSize();
@@ -490,7 +495,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
await _settingsService.setEnableHardwareDecoding(value); await _settingsService.setEnableHardwareDecoding(value);
}, },
), ),
if (Platform.isAndroid) if (Platform.isAndroid || Platform.isIOS)
SwitchListTile( SwitchListTile(
focusNode: _focusTracker.get(_kAutoPip), focusNode: _focusTracker.get(_kAutoPip),
secondary: const AppIcon(Symbols.picture_in_picture_alt_rounded, fill: 1), secondary: const AppIcon(Symbols.picture_in_picture_alt_rounded, fill: 1),
@@ -935,6 +940,19 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
), ),
), ),
SwitchListTile(
focusNode: _focusTracker.get(_kCrashReporting),
secondary: const AppIcon(Symbols.monitoring_rounded, fill: 1),
title: Text(t.settings.crashReporting),
subtitle: Text(t.settings.crashReportingDescription),
value: _crashReporting,
onChanged: (value) async {
setState(() {
_crashReporting = value;
});
await _settingsService.setCrashReporting(value);
},
),
SwitchListTile( SwitchListTile(
focusNode: _focusTracker.get(_kDebugLogging), focusNode: _focusTracker.get(_kDebugLogging),
secondary: const AppIcon(Symbols.bug_report_rounded, fill: 1), secondary: const AppIcon(Symbols.bug_report_rounded, fill: 1),
@@ -974,6 +992,17 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
onTap: () => _showResetSettingsDialog(), onTap: () => _showResetSettingsDialog(),
), ),
if (kDebugMode)
ListTile(
leading: const AppIcon(Symbols.error_rounded, fill: 1),
title: const Text('Test Sentry'),
subtitle: const Text('Send a test error to Bugsink'),
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
onTap: () {
Sentry.captureException(Exception('Sentry test from settings'));
showSnackBar(context, 'Test error sent to Sentry');
},
),
], ],
), ),
); );
+5
View File
@@ -8,6 +8,7 @@ import 'package:material_symbols_icons/symbols.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:os_media_controls/os_media_controls.dart'; import 'package:os_media_controls/os_media_controls.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
@@ -581,6 +582,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
_playbackRestartSubscription = player!.streams.playbackRestart.listen((_) async { _playbackRestartSubscription = player!.streams.playbackRestart.listen((_) async {
if (!_hasFirstFrame.value) { if (!_hasFirstFrame.value) {
_hasFirstFrame.value = true; _hasFirstFrame.value = true;
Sentry.addBreadcrumb(Breadcrumb(message: 'First frame ready', category: 'player'));
// Apply frame rate matching on Android if enabled // Apply frame rate matching on Android if enabled
if (Platform.isAndroid && settingsService.getMatchContentFrameRate()) { if (Platform.isAndroid && settingsService.getMatchContentFrameRate()) {
@@ -648,6 +650,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
// Set MPV video-sync mode for smoother playback when display is synced // Set MPV video-sync mode for smoother playback when display is synced
await player!.setProperty('video-sync', 'display-tempo'); await player!.setProperty('video-sync', 'display-tempo');
Sentry.addBreadcrumb(Breadcrumb(message: 'Frame rate matching: ${fps}fps', category: 'player'));
appLogger.d('Frame rate matching: Set display to ${fps}fps (duration: ${durationMs}ms)'); appLogger.d('Frame rate matching: Set display to ${fps}fps (duration: ${durationMs}ms)');
} catch (e) { } catch (e) {
appLogger.w('Failed to apply frame rate matching', error: e); appLogger.w('Failed to apply frame rate matching', error: e);
@@ -661,6 +664,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
try { try {
await player!.clearVideoFrameRate(); await player!.clearVideoFrameRate();
await player!.setProperty('video-sync', 'audio'); await player!.setProperty('video-sync', 'audio');
Sentry.addBreadcrumb(Breadcrumb(message: 'Frame rate matching cleared', category: 'player'));
appLogger.d('Frame rate matching: Cleared, restored default display mode'); appLogger.d('Frame rate matching: Cleared, restored default display mode');
} catch (e) { } catch (e) {
appLogger.d('Failed to clear frame rate matching', error: e); appLogger.d('Failed to clear frame rate matching', error: e);
@@ -1764,6 +1768,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
} }
} }
Sentry.addBreadcrumb(Breadcrumb(message: 'Player dispose', category: 'player'));
player?.dispose(); player?.dispose();
if (_activeRatingKey == widget.metadata.ratingKey) { if (_activeRatingKey == widget.metadata.ratingKey) {
_activeRatingKey = null; _activeRatingKey = null;
+18 -2
View File
@@ -19,6 +19,7 @@ enum EpisodePosterMode { seriesPoster, seasonPoster, episodeThumbnail }
class SettingsService extends BaseSharedPreferencesService { class SettingsService extends BaseSharedPreferencesService {
static const String _keyThemeMode = 'theme_mode'; static const String _keyThemeMode = 'theme_mode';
static const String _keyEnableDebugLogging = 'enable_debug_logging'; static const String _keyEnableDebugLogging = 'enable_debug_logging';
static const String _keyCrashReporting = 'crash_reporting';
static const String _keyBufferSize = 'buffer_size'; static const String _keyBufferSize = 'buffer_size';
static const String _keyBufferSizeMigratedToAuto = 'buffer_size_migrated_to_auto'; static const String _keyBufferSizeMigratedToAuto = 'buffer_size_migrated_to_auto';
static const String _keyKeyboardShortcuts = 'keyboard_shortcuts'; static const String _keyKeyboardShortcuts = 'keyboard_shortcuts';
@@ -85,10 +86,16 @@ class SettingsService extends BaseSharedPreferencesService {
SettingsService._(); SettingsService._();
static Future<SettingsService> getInstance() { static SettingsService? _cachedInstance;
return BaseSharedPreferencesService.initializeInstance(() => SettingsService._());
static Future<SettingsService> getInstance() async {
_cachedInstance ??= await BaseSharedPreferencesService.initializeInstance(() => SettingsService._());
return _cachedInstance!;
} }
/// Synchronous access to the singleton, or null if not yet initialized.
static SettingsService? get instanceOrNull => _cachedInstance;
/// Generic helper to get an enum value from preferences /// Generic helper to get an enum value from preferences
T _getEnumValue<T extends Enum>(String key, List<T> values, T defaultValue) { T _getEnumValue<T extends Enum>(String key, List<T> values, T defaultValue) {
final stored = prefs.getString(key); final stored = prefs.getString(key);
@@ -121,6 +128,15 @@ class SettingsService extends BaseSharedPreferencesService {
return prefs.getBool(_keyEnableDebugLogging) ?? false; return prefs.getBool(_keyEnableDebugLogging) ?? false;
} }
// Crash Reporting
Future<void> setCrashReporting(bool enabled) async {
await prefs.setBool(_keyCrashReporting, enabled);
}
bool getCrashReporting() {
return prefs.getBool(_keyCrashReporting) ?? true; // Default enabled
}
// Buffer Size (in MB) // Buffer Size (in MB)
Future<void> setBufferSize(int sizeInMB) async { Future<void> setBufferSize(int sizeInMB) async {
await prefs.setInt(_keyBufferSize, sizeInMB); await prefs.setInt(_keyBufferSize, sizeInMB);
@@ -8,6 +8,7 @@
#include <os_media_controls/os_media_controls_plugin.h> #include <os_media_controls/os_media_controls_plugin.h>
#include <screen_retriever_linux/screen_retriever_linux_plugin.h> #include <screen_retriever_linux/screen_retriever_linux_plugin.h>
#include <sentry_flutter/sentry_flutter_plugin.h>
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h> #include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
#include <universal_gamepad/gamepad_plugin.h> #include <universal_gamepad/gamepad_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
@@ -20,6 +21,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin");
screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar);
g_autoptr(FlPluginRegistrar) sentry_flutter_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin");
sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar);
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
+1
View File
@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
os_media_controls os_media_controls
screen_retriever_linux screen_retriever_linux
sentry_flutter
sqlite3_flutter_libs sqlite3_flutter_libs
universal_gamepad universal_gamepad
url_launcher_linux url_launcher_linux
@@ -15,6 +15,7 @@ import os_media_controls
import package_info_plus import package_info_plus
import path_provider_foundation import path_provider_foundation
import screen_retriever_macos import screen_retriever_macos
import sentry_flutter
import shared_preferences_foundation import shared_preferences_foundation
import sqflite_darwin import sqflite_darwin
import sqlite3_flutter_libs import sqlite3_flutter_libs
@@ -34,6 +35,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin"))
SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
+11
View File
@@ -22,6 +22,11 @@ PODS:
- FlutterMacOS - FlutterMacOS
- screen_retriever_macos (0.0.1): - screen_retriever_macos (0.0.1):
- FlutterMacOS - FlutterMacOS
- Sentry/HybridSDK (8.46.0)
- sentry_flutter (8.14.2):
- Flutter
- FlutterMacOS
- Sentry/HybridSDK (= 8.46.0)
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
@@ -75,6 +80,7 @@ DEPENDENCIES:
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`)
- sentry_flutter (from `Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`) - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`)
@@ -85,6 +91,7 @@ DEPENDENCIES:
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- Sentry
- Sparkle - Sparkle
- sqlite3 - sqlite3
@@ -111,6 +118,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
screen_retriever_macos: screen_retriever_macos:
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos
sentry_flutter:
:path: Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos
shared_preferences_foundation: shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
sqflite_darwin: sqflite_darwin:
@@ -138,6 +147,8 @@ SPEC CHECKSUMS:
package_info_plus: f0052d280d17aa382b932f399edf32507174e870 package_info_plus: f0052d280d17aa382b932f399edf32507174e870
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f
Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854
sentry_flutter: 27892878729f42701297c628eb90e7c6529f3684
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
Sparkle: f4355f9ebbe9b7d932df4980d70f13922ac97b2a Sparkle: f4355f9ebbe9b7d932df4980d70f13922ac97b2a
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
+16
View File
@@ -1008,6 +1008,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0" version: "0.2.0"
sentry:
dependency: transitive
description:
name: sentry
sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb"
url: "https://pub.dev"
source: hosted
version: "8.14.2"
sentry_flutter:
dependency: "direct main"
description:
name: sentry_flutter
sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8"
url: "https://pub.dev"
source: hosted
version: "8.14.2"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
+1
View File
@@ -56,6 +56,7 @@ dependencies:
mobile_scanner: ^6.0.2 mobile_scanner: ^6.0.2
android_intent_plus: ^5.0.2 android_intent_plus: ^5.0.2
background_downloader: ^9.5.2 background_downloader: ^9.5.2
sentry_flutter: ^8.12.0
auto_updater: auto_updater:
git: git:
url: https://github.com/edde746/auto_updater url: https://github.com/edde746/auto_updater
+9
View File
@@ -0,0 +1,9 @@
ice.plezy.app {
reverse_proxy relay:8080
}
bugs.plezy.app {
reverse_proxy bugsink:8000 {
header_up X-Real-IP {remote_host}
}
}
+13
View File
@@ -0,0 +1,13 @@
FROM golang:1.22-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /relay .
FROM scratch
COPY --from=build /relay /relay
VOLUME /data/logs
EXPOSE 8080
ENV GOMEMLIMIT=64MiB
ENTRYPOINT ["/relay"]
+36
View File
@@ -0,0 +1,36 @@
services:
relay:
build: .
restart: unless-stopped
volumes:
- logs:/data/logs
expose:
- "8080"
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
bugsink:
image: bugsink/bugsink
restart: unless-stopped
env_file: .env
environment:
SECRET_KEY: ${BUGSINK_SECRET_KEY}
CREATE_SUPERUSER: ${BUGSINK_SUPERUSER}
PORT: 8000
BEHIND_HTTPS_PROXY: "True"
expose:
- "8000"
volumes:
logs:
caddy_data:
caddy_config:
+1 -9
View File
@@ -2,12 +2,4 @@ module github.com/edde746/plezy-relay
go 1.22 go 1.22
require ( require github.com/gorilla/websocket v1.5.3
github.com/gorilla/websocket v1.5.3
golang.org/x/crypto v0.31.0
)
require (
golang.org/x/net v0.33.0 // indirect
golang.org/x/text v0.21.0 // indirect
)
-6
View File
@@ -1,8 +1,2 @@
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+56 -37
View File
@@ -9,12 +9,14 @@ import (
"math/big" "math/big"
"net" "net"
"net/http" "net/http"
"os"
"path/filepath"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"golang.org/x/crypto/acme/autocert"
) )
const ( const (
@@ -29,12 +31,15 @@ const (
pingInterval = 30 * time.Second pingInterval = 30 * time.Second
maxMessageSize = 64 * 1024 maxMessageSize = 64 * 1024
maxLogSize = 1 * 1024 * 1024 // 1MB maxLogSize = 1 * 1024 * 1024 // 1MB
logMaxAge = 7 * 24 * time.Hour logMaxAge = 3 * 24 * time.Hour
logIDLength = 5 logIDLength = 5
logRateInterval = 1 * time.Minute logRateInterval = 1 * time.Minute
maxLogEntries = 500
) )
var upgrader = websocket.Upgrader{ var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool { return true }, CheckOrigin: func(r *http.Request) bool { return true },
} }
@@ -150,21 +155,36 @@ func (r *Room) sendTo(targetID string, msg serverMsg) bool {
// --- Log store --- // --- Log store ---
type logEntry struct { type logEntry struct {
Text string Size int
ExpiresAt time.Time ExpiresAt time.Time
} }
type logStore struct { type logStore struct {
entries map[string]logEntry entries map[string]logEntry
rateLimit map[string]time.Time // IP -> last upload time rateLimit map[string]time.Time // IP -> last upload time
dir string
mu sync.RWMutex mu sync.RWMutex
} }
func newLogStore() *logStore { func newLogStore(dir string) *logStore {
return &logStore{ if err := os.MkdirAll(dir, 0755); err != nil {
log.Fatalf("failed to create log dir %s: %v", dir, err)
}
ls := &logStore{
entries: make(map[string]logEntry), entries: make(map[string]logEntry),
rateLimit: make(map[string]time.Time), rateLimit: make(map[string]time.Time),
dir: dir,
} }
// Clean orphaned files from prior runs
files, _ := os.ReadDir(dir)
for _, f := range files {
os.Remove(filepath.Join(dir, f.Name()))
}
return ls
}
func (ls *logStore) filePath(id string) string {
return filepath.Join(ls.dir, id+".log")
} }
const logIDChars = "abcdefghijklmnopqrstuvwxyz0123456789" const logIDChars = "abcdefghijklmnopqrstuvwxyz0123456789"
@@ -184,6 +204,7 @@ func (ls *logStore) cleanup() {
now := time.Now() now := time.Now()
for id, entry := range ls.entries { for id, entry := range ls.entries {
if now.After(entry.ExpiresAt) { if now.After(entry.ExpiresAt) {
os.Remove(ls.filePath(id))
delete(ls.entries, id) delete(ls.entries, id)
} }
} }
@@ -202,8 +223,8 @@ type Server struct {
mu sync.RWMutex mu sync.RWMutex
} }
func newServer() *Server { func newServer(logDir string) *Server {
s := &Server{rooms: make(map[string]*Room), logs: newLogStore()} s := &Server{rooms: make(map[string]*Room), logs: newLogStore(logDir)}
go s.cleanupLoop() go s.cleanupLoop()
return s return s
} }
@@ -271,10 +292,24 @@ func (s *Server) handlePostLogs(w http.ResponseWriter, r *http.Request) {
return return
} }
s.logs.mu.Lock()
if len(s.logs.entries) >= maxLogEntries {
s.logs.mu.Unlock()
http.Error(w, "Log store full", http.StatusServiceUnavailable)
return
}
s.logs.mu.Unlock()
id := generateLogID() id := generateLogID()
if err := os.WriteFile(s.logs.filePath(id), body, 0644); err != nil {
log.Printf("logs: failed to write %s: %v", id, err)
http.Error(w, "Failed to store log", http.StatusInternalServerError)
return
}
s.logs.mu.Lock() s.logs.mu.Lock()
s.logs.entries[id] = logEntry{ s.logs.entries[id] = logEntry{
Text: string(body), Size: len(body),
ExpiresAt: time.Now().Add(logMaxAge), ExpiresAt: time.Now().Add(logMaxAge),
} }
s.logs.mu.Unlock() s.logs.mu.Unlock()
@@ -306,8 +341,15 @@ func (s *Server) handleGetLogs(w http.ResponseWriter, r *http.Request) {
return return
} }
data, err := os.ReadFile(s.logs.filePath(id))
if err != nil {
http.Error(w, "Not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Write([]byte(entry.Text)) w.Header().Set("Content-Length", strconv.Itoa(entry.Size))
w.Write(data)
} }
func (s *Server) sendError(conn *websocket.Conn, code, message string) { func (s *Server) sendError(conn *websocket.Conn, code, message string) {
@@ -485,11 +527,11 @@ func (s *Server) handleWS(w http.ResponseWriter, r *http.Request) {
} }
func main() { func main() {
dev := flag.Bool("dev", false, "Run in development mode (plain HTTP on :8080)") addr := flag.String("addr", ":8080", "Listen address")
host := flag.String("host", "ice.plezy.app", "Hostname for TLS autocert") logDir := flag.String("log-dir", "/data/logs", "Directory for log file storage")
flag.Parse() flag.Parse()
srv := newServer() srv := newServer(*logDir)
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("/relay", srv.handleWS) mux.HandleFunc("/relay", srv.handleWS)
@@ -500,29 +542,6 @@ func main() {
mux.HandleFunc("/logs", srv.handlePostLogs) mux.HandleFunc("/logs", srv.handlePostLogs)
mux.HandleFunc("/logs/", srv.handleGetLogs) mux.HandleFunc("/logs/", srv.handleGetLogs)
if *dev { log.Printf("Starting relay server on %s", *addr)
log.Println("Starting dev server on :8080") log.Fatal(http.ListenAndServe(*addr, mux))
log.Fatal(http.ListenAndServe(":8080", mux))
} else {
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(*host),
Cache: autocert.DirCache("/var/lib/plezy-relay/certs"),
}
server := &http.Server{
Addr: ":443",
Handler: mux,
TLSConfig: certManager.TLSConfig(),
}
// HTTP challenge server for Let's Encrypt
go func() {
log.Println("Starting HTTP challenge server on :80")
log.Fatal(http.ListenAndServe(":80", certManager.HTTPHandler(nil)))
}()
log.Printf("Starting relay server on :443 (host=%s)", *host)
log.Fatal(server.ListenAndServeTLS("", ""))
}
} }
@@ -10,6 +10,7 @@
#include <connectivity_plus/connectivity_plus_windows_plugin.h> #include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <os_media_controls/os_media_controls_plugin_c_api.h> #include <os_media_controls/os_media_controls_plugin_c_api.h>
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h> #include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
#include <sentry_flutter/sentry_flutter_plugin.h>
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h> #include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
#include <universal_gamepad/gamepad_plugin_c_api.h> #include <universal_gamepad/gamepad_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
@@ -24,6 +25,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("OsMediaControlsPluginCApi")); registry->GetRegistrarForPlugin("OsMediaControlsPluginCApi"));
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
SentryFlutterPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SentryFlutterPlugin"));
Sqlite3FlutterLibsPluginRegisterWithRegistrar( Sqlite3FlutterLibsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin"));
GamepadPluginCApiRegisterWithRegistrar( GamepadPluginCApiRegisterWithRegistrar(
+1
View File
@@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus connectivity_plus
os_media_controls os_media_controls
screen_retriever_windows screen_retriever_windows
sentry_flutter
sqlite3_flutter_libs sqlite3_flutter_libs
universal_gamepad universal_gamepad
url_launcher_windows url_launcher_windows