feat: refine presenter and language options

This commit is contained in:
Brenno de Winter 2026-06-04 08:17:12 +02:00
parent d0bd1a85bf
commit d59c6ee761
9 changed files with 752 additions and 222 deletions

View file

@ -14,11 +14,12 @@ class OciDeckApp extends ConsumerWidget {
final languageCode = ref.watch(
settingsProvider.select((s) => s.languageCode),
);
AppLocalizations.setActiveLanguageCode(languageCode);
return MaterialApp(
title: 'OciDeck',
theme: AppTheme.light,
debugShowCheckedModeBanner: false,
locale: Locale(languageCode),
locale: AppLocalizations.materialLocaleFor(languageCode),
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: const [
AppLocalizations.delegate,

View file

@ -13,6 +13,8 @@ class AppLocalizations {
Locale('de'),
Locale('fr'),
Locale('es'),
Locale('fy'),
Locale('pap'),
];
static const languageNames = {
@ -22,8 +24,31 @@ class AppLocalizations {
'de': 'Deutsch',
'fr': 'Français',
'es': 'Español',
'fy': 'Frysk',
'pap': 'Papiamento',
};
static const _materialLocaleFallbacks = {
'nl': Locale('nl'),
'en': Locale('en'),
'it': Locale('it'),
'de': Locale('de'),
'fr': Locale('fr'),
'es': Locale('es'),
'fy': Locale('en'),
'pap': Locale('en'),
};
static String _activeLanguageCode = 'nl';
static void setActiveLanguageCode(String code) {
_activeLanguageCode = languageNames.containsKey(code) ? code : 'nl';
}
static Locale materialLocaleFor(String code) {
return _materialLocaleFallbacks[code] ?? const Locale('nl');
}
static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationsDelegate();
@ -32,13 +57,19 @@ class AppLocalizations {
const AppLocalizations(Locale('nl'));
}
String get languageCode => _activeLanguageCode;
String t(String key) {
return _strings[locale.languageCode]?[key] ?? _strings['nl']![key] ?? key;
if (languageCode == 'nl') return _strings['nl']![key] ?? key;
return _strings[languageCode]?[key] ??
_strings['en']?[key] ??
_strings['nl']![key] ??
key;
}
String d(String dutchText) {
if (locale.languageCode == 'nl') return dutchText;
return _dutchSourceStrings[locale.languageCode]?[dutchText] ??
if (languageCode == 'nl') return dutchText;
return _dutchSourceStrings[languageCode]?[dutchText] ??
_dutchSourceStrings['en']?[dutchText] ??
dutchText;
}
@ -567,6 +598,168 @@ const _strings = {
'of': 'de',
'exportedTo': 'Exportado a:',
},
'fy': {
'newPresentation': 'Nije presintaasje',
'open': 'Iepenje...',
'openEllipsis': 'Iepenje…',
'recentPresentations': 'Resinte presintaasjes',
'newTab': 'Nij ljepblêd',
'undo': 'Ungedien meitsje (Ctrl/Cmd+Z)',
'redo': 'Opnij útfiere (Ctrl/Cmd+Shift+Z)',
'imageLibrary': 'Ofbyldingsbibleteek',
'presentFullscreen': 'Presintearje folslein skerm · P foar presenter view',
'visualMode': 'Fisuele modus',
'markdownMode': 'Markdown-modus',
'save': 'Bewarje',
'saveShortcut': 'Bewarje (Ctrl/Cmd+S)',
'more': 'Mear',
'export': 'Eksportearje',
'exportReady': 'Eksportearje (PDF/PPTX/HTML)',
'exportNeedsSave': 'Bewarje de presintaasje earst om te eksportearjen',
'exportNeedsClean': 'Bewarje dyn wizigingen earst om te eksportearjen',
'saved': 'Bewarre',
'unsaved': 'Net bewarre',
'unsavedChanges': 'Wizigingen bewarje (Ctrl/Cmd+S)',
'noUnsavedChanges': 'Gjin net-bewarre wizigingen',
'notSavedYet': 'Noch net bewarre',
'noFileYet': 'Dizze presintaasje hat noch gjin bestân',
'slides': 'slides',
'skipped': 'oerslein',
'allSlidesIncluded': 'Alle slides wurde presintearre en eksportearre',
'skippedSlidesExcluded': 'slide(s) wurde net presintearre of eksportearre',
'styleProfile': 'Stylprofyl',
'classification': 'Klassifikaasje',
'exportNextToDeck': 'Eksport neist deck',
'exportsNextToDeck': 'Eksporten wurde neist it deck bewarre',
'exportFolder': 'Eksport',
'newPresentationTab': 'Nije presintaasje (ljepblêd)',
'exportPackage': 'Pakket eksportearje…',
'importPackage': 'Pakket ymportearje…',
'importUrl': 'Ymportearje fan URL…',
'findReplace': 'Sykje en ferfange',
'fullDeckPreview': 'Hiel deck besjen',
'presentationProperties': 'Presintaasje-eigenskippen',
'settings': 'Ynstellingen',
'settingsGeneral': 'Algemien',
'settingsColors': 'Kleuren',
'settingsLogo': 'Logo',
'language': 'Taal',
'applicationLanguage': 'Applikaasjetaal',
'languageHelp':
'De interface wikselet daliks fan taal. Presintaasje-ynhâld bliuwt itselde.',
'presentationFolder': 'Presintaasjemap',
'exportFolderSetting': 'Eksportmap',
'notSet': 'Net ynsteld',
'nextToPresentationFile': 'Neist it presintaasjebestân',
'choose': 'Kieze',
'removeDefaultFolder': 'Standertmap fuortsmite',
'removeExportFolder': 'Eksportmap fuortsmite',
'exportFolderHelp':
'Alle eksporten (PDF/PPTX) wurde hjir bewarre. Net ynsteld? Dan komme se neist it presintaasjebestân.',
'cancel': 'Annulearje',
'close': 'Slute',
'saveSettings': 'Bewarje',
'exportDialogTitle': 'Eksportearje',
'exportAgain': 'Nochris eksportearje',
'exportIntro':
'De eksport brûkt krekt de werjefte út de editor, ynklusyf dyn stylprofyl.',
'imageQualityPdf': 'Ofbyldingskwaliteit (PDF)',
'normal': 'Normaal',
'compressed': 'Komprimearre',
'compressedHelp':
'JPEG op legere resolúsje, bedoeld as handout, mei in folle lytser bestân (apart bewarre as “-compact”).',
'losslessHelp': 'Ferliesfrije ôfbyldings op folsleine resolúsje.',
'exportAsPdf': 'Eksportearje as PDF',
'exportAsPptx': 'Eksportearje as PPTX',
'exportAsHtml': 'Eksportearje as HTML (Marp, offline)',
'renderingSlides': 'Slides renderje…',
'buildingHtml': 'HTML bouwe…',
'buildingExport': 'bouwe…',
'slideOf': 'Slide',
'of': 'fan',
'exportedTo': 'Eksportearre nei:',
},
'pap': {
'newPresentation': 'Presentashon nobo',
'open': 'Habri...',
'openEllipsis': 'Habri…',
'recentPresentations': 'Presentashonnan resien',
'newTab': 'Tab nobo',
'undo': 'Deshasí (Ctrl/Cmd+Z)',
'redo': 'Hasi atrobe (Ctrl/Cmd+Shift+Z)',
'imageLibrary': 'Biblioteka di imágen',
'presentFullscreen': 'Presentá na pantalla kompletu · P pa presenter view',
'visualMode': 'Modo visual',
'markdownMode': 'Modo Markdown',
'save': 'Warda',
'saveShortcut': 'Warda (Ctrl/Cmd+S)',
'more': 'Mas',
'export': 'Eksportá',
'exportReady': 'Eksportá (PDF/PPTX/HTML)',
'exportNeedsSave': 'Warda e presentashon promé ku eksportá',
'exportNeedsClean': 'Warda bo kambionan promé ku eksportá',
'saved': 'Wardá',
'unsaved': 'No wardá',
'unsavedChanges': 'Warda kambionan (Ctrl/Cmd+S)',
'noUnsavedChanges': 'No tin kambionan sin warda',
'notSavedYet': 'Ainda no wardá',
'noFileYet': 'E presentashon aki ainda no tin un file',
'slides': 'slides',
'skipped': 'saltá',
'allSlidesIncluded': 'Tur slides lo wordu presentá i eksportá',
'skippedSlidesExcluded': 'slide(s) no lo wordu presentá òf eksportá',
'styleProfile': 'Perfil di estilo',
'classification': 'Klasifikashon',
'exportNextToDeck': 'Eksportá banda di deck',
'exportsNextToDeck': 'Eksportnan ta wordu wardá banda di e deck',
'exportFolder': 'Eksport',
'newPresentationTab': 'Presentashon nobo (tab)',
'exportPackage': 'Eksportá pakete…',
'importPackage': 'Importá pakete…',
'importUrl': 'Importá for di URL…',
'findReplace': 'Busca i reemplasá',
'fullDeckPreview': 'Mira henter e deck',
'presentationProperties': 'Propiedatnan di presentashon',
'settings': 'Preferensianan',
'settingsGeneral': 'General',
'settingsColors': 'Kolónan',
'settingsLogo': 'Logo',
'language': 'Idioma',
'applicationLanguage': 'Idioma di aplikashon',
'languageHelp':
'E interface ta cambia idioma mesora. Kontenido di presentashon ta keda igual.',
'presentationFolder': 'Folder di presentashon',
'exportFolderSetting': 'Folder di eksport',
'notSet': 'No konfigurá',
'nextToPresentationFile': 'Banda di e file di presentashon',
'choose': 'Skohe',
'removeDefaultFolder': 'Kita folder standard',
'removeExportFolder': 'Kita folder di eksport',
'exportFolderHelp':
'Tur eksportnan (PDF/PPTX) ta wordu wardá aki. Si no konfigurá, nan ta wordu wardá banda di e file di presentashon.',
'cancel': 'Kanselá',
'close': 'Sera',
'saveSettings': 'Warda',
'exportDialogTitle': 'Eksportá',
'exportAgain': 'Eksportá atrobe',
'exportIntro':
'E eksport ta usa mesun bista ku e editor, inkluyendo bo perfil di estilo.',
'imageQualityPdf': 'Kalidat di imágen (PDF)',
'normal': 'Normal',
'compressed': 'Komprimí',
'compressedHelp':
'JPEG ku resolushon mas abou, pa handout, ku un file hopi mas chikí (wardá apart komo “-compact”).',
'losslessHelp': 'Imágennan sin pèrdida na resolushon kompletu.',
'exportAsPdf': 'Eksportá komo PDF',
'exportAsPptx': 'Eksportá komo PPTX',
'exportAsHtml': 'Eksportá komo HTML (Marp, offline)',
'renderingSlides': 'Render slides…',
'buildingHtml': 'Trahando HTML…',
'buildingExport': 'trahando…',
'slideOf': 'Slide',
'of': 'di',
'exportedTo': 'Eksportá na:',
},
};
const _dutchSourceStrings = {
@ -852,21 +1045,26 @@ const _dutchSourceStrings = {
'↑↓←→ navigeren · Enter kiezen · Dubbelklik selecteert':
'↑↓←→ navigate · Enter chooses · Double-click selects',
'Sneltoetsen': 'Keyboard shortcuts',
'Toetsenlegenda': 'Key legend',
'spatie': 'space',
'klik': 'click',
'cijfers': 'numbers',
'Klik of druk op ? / H / Esc om te sluiten':
'Click or press ? / H / Esc to close',
'Klik of druk op H / Esc om te sluiten': 'Click or press H / Esc to close',
'Naar slidenummer': 'Go to slide number',
'Eerste · laatste slide': 'First · last slide',
'Slide-overzicht': 'Slide overview',
'Slide-overzicht (pijltjes + Enter)': 'Slide overview (arrows + Enter)',
'Presenter view (notities, klok)': 'Presenter view (notes, clock)',
'Scherm wisselen (meerdere schermen)': 'Switch screen (multiple displays)',
'Zwart · wit scherm': 'Black · white screen',
'Verstreken tijd resetten': 'Reset elapsed time',
'Automatische modus aan/uit': 'Automatic mode on/off',
'Herhalen (loop) aan/uit': 'Repeat (loop) on/off',
'Na audio automatisch doorgaan': 'Advance automatically after audio',
'Dit overzicht': 'This overview',
'Deze legenda': 'This legend',
'Terug / afsluiten': 'Back / exit',
'Auto (A)': 'Auto (A)',
'Handmatig (A)': 'Manual (A)',
@ -883,8 +1081,16 @@ const _dutchSourceStrings = {
'Verstreken': 'Elapsed',
'Klok': 'Clock',
'Geen notities voor deze slide.': 'No notes for this slide.',
'Wissel scherm (S)': 'Switch screen (S)',
'Kon niet van scherm wisselen.': 'Could not switch screens.',
'P publiek · G overzicht · B/W zwart/wit · R tijd · Esc stop':
'P audience · G overview · B/W black/white · R time · Esc stop',
'P publiek · S scherm · G overzicht · B/W zwart/wit · R tijd · Esc stop':
'P audience · S screen · G overview · B/W black/white · R time · Esc stop',
'P publiek · H legenda · G overzicht · B/W zwart/wit · R tijd · Esc stop':
'P audience · H legend · G overview · B/W black/white · R time · Esc stop',
'P publiek · H legenda · S scherm · G overzicht · B/W zwart/wit · R tijd · Esc stop':
'P audience · H legend · S screen · G overview · B/W black/white · R time · Esc stop',
'pijltjes + Enter of klik om te springen':
'arrows + Enter or click to jump',
'Afsluiten (Escape)': 'Exit (Escape)',
@ -1033,12 +1239,35 @@ const _dutchSourceStrings = {
'Gekopieerd': 'Copiato',
'Afbeelding verwijderen?': 'Eliminare immagine?',
'Sneltoetsen': 'Scorciatoie da tastiera',
'Toetsenlegenda': 'Legenda tasti',
'spatie': 'spazio',
'klik': 'clic',
'cijfers': 'numeri',
'Klik of druk op H / Esc om te sluiten':
'Fai clic o premi H / Esc per chiudere',
'Naar slidenummer': 'Vai al numero slide',
'Eerste · laatste slide': 'Prima · ultima slide',
'Slide-overzicht': 'Panoramica slide',
'Slide-overzicht (pijltjes + Enter)': 'Panoramica slide (frecce + Enter)',
'Presenter view (notities, klok)': 'Vista relatore (note, orologio)',
'Scherm wisselen (meerdere schermen)': 'Cambia schermo (piu schermi)',
'Zwart · wit scherm': 'Schermo nero · bianco',
'Verstreken tijd resetten': 'Reimposta tempo trascorso',
'Automatische modus aan/uit': 'Modalita automatica on/off',
'Herhalen (loop) aan/uit': 'Ripetizione (loop) on/off',
'Na audio automatisch doorgaan': 'Avanza automaticamente dopo audio',
'Deze legenda': 'Questa legenda',
'Terug / afsluiten': 'Indietro / esci',
'HUIDIGE SLIDE': 'SLIDE ATTUALE',
'VOLGENDE': 'PROSSIMA',
'NOTITIES': 'NOTE',
'Verstreken': 'Trascorso',
'Klok': 'Orologio',
'Wissel scherm (S)': 'Cambia schermo (S)',
'P publiek · H legenda · G overzicht · B/W zwart/wit · R tijd · Esc stop':
'P pubblico · H legenda · G panoramica · B/W nero/bianco · R tempo · Esc stop',
'P publiek · H legenda · S scherm · G overzicht · B/W zwart/wit · R tijd · Esc stop':
'P pubblico · H legenda · S schermo · G panoramica · B/W nero/bianco · R tempo · Esc stop',
},
'de': {
'Geen': 'Keine',
@ -1158,12 +1387,36 @@ const _dutchSourceStrings = {
'Gekopieerd': 'Kopiert',
'Afbeelding verwijderen?': 'Bild löschen?',
'Sneltoetsen': 'Tastenkürzel',
'Toetsenlegenda': 'Tastenlegende',
'spatie': 'Leertaste',
'klik': 'Klick',
'cijfers': 'Zahlen',
'Klik of druk op H / Esc om te sluiten':
'Klicken oder H / Esc drücken zum Schließen',
'Naar slidenummer': 'Zu Foliennummer',
'Eerste · laatste slide': 'Erste · letzte Folie',
'Slide-overzicht': 'Folienübersicht',
'Slide-overzicht (pijltjes + Enter)': 'Folienübersicht (Pfeile + Enter)',
'Presenter view (notities, klok)': 'Presenter-Ansicht (Notizen, Uhr)',
'Scherm wisselen (meerdere schermen)':
'Bildschirm wechseln (mehrere Bildschirme)',
'Zwart · wit scherm': 'Schwarzer · weißer Bildschirm',
'Verstreken tijd resetten': 'Vergangene Zeit zurücksetzen',
'Automatische modus aan/uit': 'Automatikmodus ein/aus',
'Herhalen (loop) aan/uit': 'Wiederholen (Loop) ein/aus',
'Na audio automatisch doorgaan': 'Nach Audio automatisch weiter',
'Deze legenda': 'Diese Legende',
'Terug / afsluiten': 'Zurück / beenden',
'HUIDIGE SLIDE': 'AKTUELLE FOLIE',
'VOLGENDE': 'NÄCHSTE',
'NOTITIES': 'NOTIZEN',
'Verstreken': 'Vergangen',
'Klok': 'Uhr',
'Wissel scherm (S)': 'Bildschirm wechseln (S)',
'P publiek · H legenda · G overzicht · B/W zwart/wit · R tijd · Esc stop':
'P Publikum · H Legende · G Übersicht · B/W schwarz/weiß · R Zeit · Esc Stopp',
'P publiek · H legenda · S scherm · G overzicht · B/W zwart/wit · R tijd · Esc stop':
'P Publikum · H Legende · S Bildschirm · G Übersicht · B/W schwarz/weiß · R Zeit · Esc Stopp',
},
'fr': {
'Geen': 'Aucun',
@ -1283,12 +1536,35 @@ const _dutchSourceStrings = {
'Gekopieerd': 'Copié',
'Afbeelding verwijderen?': 'Supprimer limage ?',
'Sneltoetsen': 'Raccourcis clavier',
'Toetsenlegenda': 'Legende des touches',
'spatie': 'espace',
'klik': 'clic',
'cijfers': 'chiffres',
'Klik of druk op H / Esc om te sluiten':
'Cliquez ou appuyez sur H / Esc pour fermer',
'Naar slidenummer': 'Aller au numero de diapositive',
'Eerste · laatste slide': 'Premiere · derniere diapositive',
'Slide-overzicht': 'Vue densemble',
'Slide-overzicht (pijltjes + Enter)': 'Vue densemble (fleches + Entree)',
'Presenter view (notities, klok)': 'Vue presentateur (notes, horloge)',
'Scherm wisselen (meerdere schermen)': 'Changer decran (plusieurs ecrans)',
'Zwart · wit scherm': 'Ecran noir · blanc',
'Verstreken tijd resetten': 'Reinitialiser le temps ecoule',
'Automatische modus aan/uit': 'Mode automatique active/desactive',
'Herhalen (loop) aan/uit': 'Boucle activee/desactivee',
'Na audio automatisch doorgaan': 'Avancer automatiquement apres laudio',
'Deze legenda': 'Cette legende',
'Terug / afsluiten': 'Retour / quitter',
'HUIDIGE SLIDE': 'DIAPOSITIVE ACTUELLE',
'VOLGENDE': 'SUIVANTE',
'NOTITIES': 'NOTES',
'Verstreken': 'Écoulé',
'Klok': 'Horloge',
'Wissel scherm (S)': 'Changer decran (S)',
'P publiek · H legenda · G overzicht · B/W zwart/wit · R tijd · Esc stop':
'P public · H legende · G vue densemble · B/W noir/blanc · R temps · Esc arret',
'P publiek · H legenda · S scherm · G overzicht · B/W zwart/wit · R tijd · Esc stop':
'P public · H legende · S ecran · G vue densemble · B/W noir/blanc · R temps · Esc arret',
},
'es': {
'Geen': 'Ninguno',
@ -1408,11 +1684,334 @@ const _dutchSourceStrings = {
'Gekopieerd': 'Copiado',
'Afbeelding verwijderen?': '¿Eliminar imagen?',
'Sneltoetsen': 'Atajos de teclado',
'Toetsenlegenda': 'Leyenda de teclas',
'spatie': 'espacio',
'klik': 'clic',
'cijfers': 'números',
'Klik of druk op H / Esc om te sluiten':
'Haz clic o pulsa H / Esc para cerrar',
'Naar slidenummer': 'Ir al número de diapositiva',
'Eerste · laatste slide': 'Primera · última diapositiva',
'Slide-overzicht': 'Vista general',
'Slide-overzicht (pijltjes + Enter)': 'Vista general (flechas + Enter)',
'Presenter view (notities, klok)': 'Vista de presentador (notas, reloj)',
'Scherm wisselen (meerdere schermen)':
'Cambiar pantalla (varias pantallas)',
'Zwart · wit scherm': 'Pantalla negra · blanca',
'Verstreken tijd resetten': 'Restablecer tiempo transcurrido',
'Automatische modus aan/uit': 'Modo automático activar/desactivar',
'Herhalen (loop) aan/uit': 'Repetir (bucle) activar/desactivar',
'Na audio automatisch doorgaan': 'Avanzar automáticamente tras el audio',
'Deze legenda': 'Esta leyenda',
'Terug / afsluiten': 'Volver / salir',
'HUIDIGE SLIDE': 'DIAPOSITIVA ACTUAL',
'VOLGENDE': 'SIGUIENTE',
'NOTITIES': 'NOTAS',
'Verstreken': 'Transcurrido',
'Klok': 'Reloj',
'Wissel scherm (S)': 'Cambiar pantalla (S)',
'P publiek · H legenda · G overzicht · B/W zwart/wit · R tijd · Esc stop':
'P público · H leyenda · G vista general · B/W negro/blanco · R tiempo · Esc detener',
'P publiek · H legenda · S scherm · G overzicht · B/W zwart/wit · R tijd · Esc stop':
'P público · H leyenda · S pantalla · G vista general · B/W negro/blanco · R tiempo · Esc detener',
},
'fy': {
'Geen': 'Gjin',
'Nieuw': 'Nij',
'Verwijderen': 'Fuortsmite',
'Herstellen': 'Weromsette',
'Opslaan en sluiten': 'Bewarje en slute',
'Importeren via URL': 'Ymportearje fan URL',
'Ophalen': 'Ophelje',
'Titelpagina': 'Titelslide',
'Tussentitel': 'Tuskenkop',
'Alleen Bullets': 'Allinnich bullets',
'Twee Bulletkolommen': 'Twa bulletkolommen',
'Bullets + Afbeelding': 'Bullets + Ofbylding',
'Twee Afbeeldingen': 'Twa ôfbyldings',
'Grote Afbeelding': 'Grutte ôfbylding',
'Tabel': 'Tabel',
'Vrije Markdown': 'Frije Markdown',
'Overgeslagen': 'Oerslein',
'Kopiëren': 'Kopiearje',
'Kopieer als afbeelding': 'Kopiearje as ôfbylding',
'Dupliceren': 'Duplisearje',
'Niet meer overslaan': 'Net mear oerslaan',
'Overslaan': 'Oerslaan',
'Titel': 'Titel',
'Titel (optioneel)': 'Titel (opsjoneel)',
'Slide titel': 'Slidetitel',
'Ondertitel': 'Undertitel',
'Optionele subtitel': 'Opsjonele subtitel',
'Bullets': 'Bullets',
'Bullet toevoegen': 'Bullet tafoegje',
'Verwijder': 'Fuortsmite',
'Citaat': 'Sitaat',
'Auteur': 'Auteur',
'Achtergrondafbeelding': 'Eftergrûnôfbylding',
'Achtergrondafbeelding (optioneel)': 'Eftergrûnôfbylding (opsjoneel)',
'Zoom achtergrond': 'Eftergrûn zoom',
'Zoom afbeelding': 'Ofbylding zoom',
'Afbeelding (rechts)': 'Ofbylding (rjochts)',
'Bullets (links)': 'Bullets (lofts)',
'Linker afbeelding': 'Linker ôfbylding',
'Rechter afbeelding': 'Rjochter ôfbylding',
'Audio bij deze slide': 'Audio by dizze slide',
'Audio automatisch afspelen': 'Audio automatysk ôfspylje',
'Video automatisch afspelen': 'Fideo automatysk ôfspylje',
'Geen audiobestand gekozen': 'Gjin audiobestân keazen',
'Geen video gekozen': 'Gjin fideo keazen',
'Kiezen': 'Kieze',
'Uit bibliotheek…': 'Ut bibleteek…',
'Van computer…': 'Fan kompjûter…',
'Geen afbeelding gekozen': 'Gjin ôfbylding keazen',
'Caption / bronvermelding': 'Byskrift / boarne',
'Beschrijving (doorzoekbaar)': 'Beskriuwing (trochsykber)',
'Markdown inhoud': 'Markdown-ynhâld',
'Rij toevoegen': 'Rige tafoegje',
'Kolom toevoegen': 'Kolom tafoegje',
'Kolom': 'Kolom',
'Presentatie openen': 'Presintaasje iepenje',
'Opslaan als': 'Bewarje as',
'Pakket importeren': 'Pakket ymportearje',
'Pakket exporteren': 'Pakket eksportearje',
'Bladeren…': 'Blêdzje…',
'Geen map gekozen': 'Gjin map keazen',
'Map kiezen': 'Map kieze',
'Slide zoeken': 'Slide sykje',
'Slides importeren': 'Slides ymportearje',
'Importeren': 'Ymportearje',
'Klaar': 'Klear',
'Toevoegen': 'Tafoegje',
'Toegevoegd': 'Tafoege',
'Selecteer alles': 'Alles selektearje',
'Deselecteer alles': 'Alles deselektearje',
'Zoeken en vervangen': 'Sykje en ferfange',
'Zoeken naar': 'Sykje nei',
'Vervangen door': 'Ferfange troch',
'Hoofdlettergevoelig': 'Haadlettergefoelich',
'Vervang alles': 'Alles ferfange',
'Nieuwe presentatie': 'Nije presintaasje',
'Aanmaken': 'Oanmeitsje',
'Slide type kiezen': 'Slidetype kieze',
'Presentatie-eigenschappen': 'Presintaasje-eigenskippen',
'Versie': 'Ferzje',
'Organisatie': 'Organisaasje',
'Datum': 'Datum',
'Beschrijving': 'Beskriuwing',
'Trefwoorden': 'Trefwurden',
'Profielnaam': 'Profylnamme',
'Stijlprofiel': 'Stylprofyl',
'Lettertype': 'Lettertype',
'Kleuren': 'Kleuren',
'Tekst': 'Tekst',
'Logo': 'Logo',
'Geen logo ingesteld': 'Gjin logo ynsteld',
'Logo positie': 'Logoposysje',
'Linksboven': 'Loftsboppe',
'Rechtsboven': 'Rjochtsboppe',
'Linksonder': 'Loftsûnder',
'Rechtsonder': 'Rjochtsûnder',
'Footertekst': 'Footer-tekst',
'Footerpositie': 'Footer-posysje',
'Links': 'Lofts',
'Midden': 'Midden',
'Rechts': 'Rjochts',
'Voorvertoning': 'Foarbyld',
'Preview': 'Foarbyld',
'Uitzoomen': 'Utzoome',
'Inzoomen': 'Ynzoome',
'Zoom resetten': 'Zoom weromsette',
'Vorige slide': 'Foarige slide',
'Volgende slide': 'Folgjende slide',
'Thema': 'Tema',
'Afbeelding kiezen': 'Ofbylding kieze',
'Afbeeldingen laden…': 'Ofbyldings lade…',
'Sluiten (Esc)': 'Slute (Esc)',
'Raster': 'Raster',
'Geen afbeeldingen gevonden': 'Gjin ôfbyldings fûn',
'Gekopieerd': 'Kopiearre',
'Afbeelding verwijderen?': 'Ofbylding fuortsmite?',
'Sneltoetsen': 'Fluchtoetsen',
'Toetsenlegenda': 'Toetsleginda',
'spatie': 'spaasje',
'klik': 'klik',
'cijfers': 'sifers',
'Klik of druk op H / Esc om te sluiten':
'Klik of druk op H / Esc om te sluten',
'Naar slidenummer': 'Nei slidenûmer',
'Eerste · laatste slide': 'Earste · lêste slide',
'Slide-overzicht': 'Slide-oersjoch',
'Slide-overzicht (pijltjes + Enter)': 'Slide-oersjoch (pylken + Enter)',
'Presenter view (notities, klok)': 'Presenter view (notysjes, klok)',
'Scherm wisselen (meerdere schermen)': 'Skerm wikselje (mear skermen)',
'Zwart · wit scherm': 'Swart · wyt skerm',
'Verstreken tijd resetten': 'Ferrûne tiid weromsette',
'Automatische modus aan/uit': 'Automatyske modus oan/út',
'Herhalen (loop) aan/uit': 'Werhelje (loop) oan/út',
'Na audio automatisch doorgaan': 'Nei audio automatysk trochgean',
'Deze legenda': 'Dizze leginda',
'Terug / afsluiten': 'Werom / ôfslute',
'HUIDIGE SLIDE': 'AKTUELE SLIDE',
'VOLGENDE': 'FOLGJENDE',
'NOTITIES': 'NOTYSJES',
'Verstreken': 'Ferrûn',
'Klok': 'Klok',
'Geen notities voor deze slide.': 'Gjin notysjes foar dizze slide.',
'Wissel scherm (S)': 'Skerm wikselje (S)',
'Kon niet van scherm wisselen.': 'Koe net fan skerm wikselje.',
'P publiek · H legenda · G overzicht · B/W zwart/wit · R tijd · Esc stop':
'P publyk · H leginda · G oersjoch · B/W swart/wyt · R tiid · Esc stop',
'P publiek · H legenda · S scherm · G overzicht · B/W zwart/wit · R tijd · Esc stop':
'P publyk · H leginda · S skerm · G oersjoch · B/W swart/wyt · R tiid · Esc stop',
},
'pap': {
'Geen': 'Ningun',
'Nieuw': 'Nobo',
'Verwijderen': 'Kita',
'Herstellen': 'Restorá',
'Opslaan en sluiten': 'Warda i sera',
'Importeren via URL': 'Importá for di URL',
'Ophalen': 'Tuma',
'Titelpagina': 'Slide di título',
'Tussentitel': 'Título di sekshon',
'Alleen Bullets': 'Solamente bullets',
'Twee Bulletkolommen': 'Dos kolòm di bullets',
'Bullets + Afbeelding': 'Bullets + Imágen',
'Twee Afbeeldingen': 'Dos imágen',
'Grote Afbeelding': 'Imágen grandi',
'Tabel': 'Tabel',
'Vrije Markdown': 'Markdown liber',
'Overgeslagen': 'Saltá',
'Kopiëren': 'Kopia',
'Kopieer als afbeelding': 'Kopia komo imágen',
'Dupliceren': 'Dupliká',
'Niet meer overslaan': 'No salta mas',
'Overslaan': 'Salta',
'Titel': 'Título',
'Titel (optioneel)': 'Título (opshonal)',
'Slide titel': 'Título di slide',
'Ondertitel': 'Subtítulo',
'Optionele subtitel': 'Subtítulo opshonal',
'Bullets': 'Bullets',
'Bullet toevoegen': 'Añadí bullet',
'Verwijder': 'Kita',
'Citaat': 'Sitá',
'Auteur': 'Outor',
'Achtergrondafbeelding': 'Imágen di fondo',
'Achtergrondafbeelding (optioneel)': 'Imágen di fondo (opshonal)',
'Zoom achtergrond': 'Zoom di fondo',
'Zoom afbeelding': 'Zoom di imágen',
'Afbeelding (rechts)': 'Imágen (banda drechi)',
'Bullets (links)': 'Bullets (banda robes)',
'Linker afbeelding': 'Imágen robes',
'Rechter afbeelding': 'Imágen drechi',
'Audio bij deze slide': 'Audio pa e slide aki',
'Audio automatisch afspelen': 'Toka audio outomátiko',
'Video automatisch afspelen': 'Toka video outomátiko',
'Geen audiobestand gekozen': 'Ningun file di audio skohe',
'Geen video gekozen': 'Ningun video skohe',
'Kiezen': 'Skohe',
'Uit bibliotheek…': 'For di biblioteka…',
'Van computer…': 'For di kòmpiuter…',
'Geen afbeelding gekozen': 'Ningun imágen skohe',
'Caption / bronvermelding': 'Caption / fuente',
'Beschrijving (doorzoekbaar)': 'Deskripshon (buskabel)',
'Markdown inhoud': 'Kontenido Markdown',
'Rij toevoegen': 'Añadí rei',
'Kolom toevoegen': 'Añadí kolòm',
'Kolom': 'Kolòm',
'Presentatie openen': 'Habri presentashon',
'Opslaan als': 'Warda komo',
'Pakket importeren': 'Importá pakete',
'Pakket exporteren': 'Eksportá pakete',
'Bladeren…': 'Navegá…',
'Geen map gekozen': 'Ningun folder skohe',
'Map kiezen': 'Skohe folder',
'Slide zoeken': 'Busca slide',
'Slides importeren': 'Importá slides',
'Importeren': 'Importá',
'Klaar': 'Kla',
'Toevoegen': 'Añadí',
'Toegevoegd': 'Añadí',
'Selecteer alles': 'Selektá tur',
'Deselecteer alles': 'Deselektá tur',
'Zoeken en vervangen': 'Busca i reemplasá',
'Zoeken naar': 'Busca',
'Vervangen door': 'Reemplasá ku',
'Hoofdlettergevoelig': 'Sensitivo pa mayúskula',
'Vervang alles': 'Reemplasá tur',
'Nieuwe presentatie': 'Presentashon nobo',
'Aanmaken': 'Krea',
'Slide type kiezen': 'Skohe tipo di slide',
'Presentatie-eigenschappen': 'Propiedatnan di presentashon',
'Versie': 'Vershon',
'Organisatie': 'Organisashon',
'Datum': 'Fecha',
'Beschrijving': 'Deskripshon',
'Trefwoorden': 'Palabranan klave',
'Profielnaam': 'Nòmber di perfil',
'Stijlprofiel': 'Perfil di estilo',
'Lettertype': 'Font',
'Kleuren': 'Kolónan',
'Tekst': 'Teksto',
'Logo': 'Logo',
'Geen logo ingesteld': 'Ningun logo konfigurá',
'Logo positie': 'Posishon di logo',
'Linksboven': 'Ariba robes',
'Rechtsboven': 'Ariba drechi',
'Linksonder': 'Abou robes',
'Rechtsonder': 'Abou drechi',
'Footertekst': 'Teksto di footer',
'Footerpositie': 'Posishon di footer',
'Links': 'Robes',
'Midden': 'Meimei',
'Rechts': 'Drechi',
'Voorvertoning': 'Preview',
'Preview': 'Preview',
'Uitzoomen': 'Zoom out',
'Inzoomen': 'Zoom in',
'Zoom resetten': 'Reset zoom',
'Vorige slide': 'Slide anterior',
'Volgende slide': 'Siguiente slide',
'Thema': 'Tema',
'Afbeelding kiezen': 'Skohe imágen',
'Afbeeldingen laden…': 'Kargando imágennan…',
'Sluiten (Esc)': 'Sera (Esc)',
'Raster': 'Grid',
'Geen afbeeldingen gevonden': 'No a haña imágen',
'Gekopieerd': 'Kopiá',
'Afbeelding verwijderen?': 'Kita imágen?',
'Sneltoetsen': 'Atahonan di tekla',
'Toetsenlegenda': 'Legenda di tekla',
'spatie': 'spasio',
'klik': 'klik',
'cijfers': 'numbernan',
'Klik of druk op H / Esc om te sluiten': 'Klik òf primi H / Esc pa sera',
'Naar slidenummer': 'Bai na number di slide',
'Eerste · laatste slide': 'Promé · último slide',
'Slide-overzicht': 'Resumen di slides',
'Slide-overzicht (pijltjes + Enter)': 'Resumen di slides (flecha + Enter)',
'Presenter view (notities, klok)': 'Presenter view (notanan, oloshi)',
'Scherm wisselen (meerdere schermen)': 'Kambia pantalla (mas pantalla)',
'Zwart · wit scherm': 'Pantalla pretu · blanku',
'Verstreken tijd resetten': 'Reset tempu transkurí',
'Automatische modus aan/uit': 'Modo outomátiko on/off',
'Herhalen (loop) aan/uit': 'Ripití (loop) on/off',
'Na audio automatisch doorgaan': 'Sigui outomátiko despues di audio',
'Deze legenda': 'E legenda aki',
'Terug / afsluiten': 'Bèk / sali',
'HUIDIGE SLIDE': 'SLIDE AKTUAL',
'VOLGENDE': 'SIGUIENTE',
'NOTITIES': 'NOTANAN',
'Verstreken': 'Transkurí',
'Klok': 'Oloshi',
'Geen notities voor deze slide.': 'No tin nota pa e slide aki.',
'Wissel scherm (S)': 'Kambia pantalla (S)',
'Kon niet van scherm wisselen.': 'No por a kambia pantalla.',
'P publiek · H legenda · G overzicht · B/W zwart/wit · R tijd · Esc stop':
'P públiko · H legenda · G resumen · B/W pretu/blanku · R tempu · Esc stop',
'P publiek · H legenda · S scherm · G overzicht · B/W zwart/wit · R tijd · Esc stop':
'P públiko · H legenda · S pantalla · G resumen · B/W pretu/blanku · R tempu · Esc stop',
},
};

View file

@ -1184,13 +1184,6 @@ class _MainLayoutState extends ConsumerState<_MainLayout> {
onPressed: saveDeck,
),
),
Tooltip(
message: exportTooltip,
child: IconButton(
icon: const Icon(Icons.upload_file_outlined, size: 18),
onPressed: canExport ? exportDeck : null,
),
),
const _ActionsDivider(),
// Overig (minder vaak gebruikt)
PopupMenuButton<String>(

View file

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:screen_retriever/screen_retriever.dart';
import 'package:window_manager/window_manager.dart';
import '../../models/deck.dart';
import '../../models/settings.dart';
@ -107,6 +108,12 @@ class _FullscreenPresenterState extends State<FullscreenPresenter> {
/// Met M te wisselen.
bool _advanceOnAudioEnd = true;
/// Known displays for moving the fullscreen presentation window. This is not
/// a second presenter window; it keeps the current output movable between
/// screens with S or the presenter-view button.
List<Display> _displays = const [];
int _displayIndex = 0;
@override
void initState() {
super.initState();
@ -119,6 +126,7 @@ class _FullscreenPresenterState extends State<FullscreenPresenter> {
});
WidgetsBinding.instance.addPostFrameCallback((_) {
_focusNode.requestFocus();
_loadDisplays();
_scheduleAdvance();
});
}
@ -209,6 +217,55 @@ class _FullscreenPresenterState extends State<FullscreenPresenter> {
_scheduleAdvance();
}
Future<void> _loadDisplays() async {
try {
final displays = await screenRetriever.getAllDisplays();
if (!mounted || displays.isEmpty) return;
final bounds = await windowManager.getBounds();
final center = bounds.center;
final current = displays.indexWhere((d) {
final p = d.visiblePosition ?? Offset.zero;
final s = d.visibleSize ?? d.size;
return Rect.fromLTWH(p.dx, p.dy, s.width, s.height).contains(center);
});
setState(() {
_displays = displays;
_displayIndex = current < 0 ? 0 : current;
});
} catch (_) {
// Screen detection is best-effort; presenting should still work.
}
}
Future<void> _moveToDisplay(int index) async {
if (_displays.length < 2) return;
final display = _displays[index.clamp(0, _displays.length - 1)];
final position = display.visiblePosition ?? Offset.zero;
final size = display.visibleSize ?? display.size;
try {
await windowManager.setFullScreen(false);
await windowManager.setBounds(
Rect.fromLTWH(position.dx, position.dy, size.width, size.height),
);
await windowManager.setFullScreen(true);
if (mounted) setState(() => _displayIndex = index);
} catch (_) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.l10n.d('Kon niet van scherm wisselen.')),
),
);
}
}
}
Future<void> _cycleDisplay() async {
if (_displays.isEmpty) await _loadDisplays();
if (_displays.length < 2) return;
await _moveToDisplay((_displayIndex + 1) % _displays.length);
}
Future<void> _exit() async {
_advanceTimer?.cancel();
await windowManager.setFullScreen(false);
@ -452,6 +509,9 @@ class _FullscreenPresenterState extends State<FullscreenPresenter> {
case LogicalKeyboardKey.keyM:
_toggleAudioAdvance();
return KeyEventResult.handled;
case LogicalKeyboardKey.keyS:
_cycleDisplay();
return KeyEventResult.handled;
case LogicalKeyboardKey.escape:
// Gelaagd: getypt nummer wissen, dan blanco scherm, dan pas afsluiten.
if (_typed.isNotEmpty) {
@ -587,12 +647,13 @@ class _FullscreenPresenterState extends State<FullscreenPresenter> {
('Home · End', l10n.d('Eerste · laatste slide')),
('G', l10n.d('Slide-overzicht (pijltjes + Enter)')),
('P', l10n.d('Presenter view (notities, klok)')),
('S', l10n.d('Scherm wisselen (meerdere schermen)')),
('B · W', l10n.d('Zwart · wit scherm')),
('R', l10n.d('Verstreken tijd resetten')),
('A', l10n.d('Automatische modus aan/uit')),
('L', l10n.d('Herhalen (loop) aan/uit')),
('M', l10n.d('Na audio automatisch doorgaan')),
('? · H', l10n.d('Dit overzicht')),
('H', l10n.d('Deze legenda')),
('Esc', l10n.d('Terug / afsluiten')),
];
return GestureDetector(
@ -627,7 +688,7 @@ class _FullscreenPresenterState extends State<FullscreenPresenter> {
),
const SizedBox(width: 10),
Text(
l10n.d('Sneltoetsen'),
l10n.d('Toetsenlegenda'),
style: const TextStyle(
color: Colors.white,
fontSize: 18,
@ -668,7 +729,7 @@ class _FullscreenPresenterState extends State<FullscreenPresenter> {
const SizedBox(height: 16),
Center(
child: Text(
l10n.d('Klik of druk op ? / H / Esc om te sluiten'),
l10n.d('Klik of druk op H / Esc om te sluiten'),
style: const TextStyle(
color: Colors.white30,
fontSize: 12,
@ -684,44 +745,6 @@ class _FullscreenPresenterState extends State<FullscreenPresenter> {
);
}
/// Subtiele statusindicator (linksonder) voor de automatische modus. Toont
/// of auto-play, herhalen en 'na audio doorgaan' actief zijn.
Widget _autoPlayStatus() {
final l10n = context.l10n;
final items = <(IconData, String, bool)>[
(
_autoPlay ? Icons.play_circle_outline : Icons.pause_circle_outline,
_autoPlay ? l10n.d('Auto (A)') : l10n.d('Handmatig (A)'),
_autoPlay,
),
(Icons.repeat, l10n.d('Herhalen (L)'), _loop),
(Icons.graphic_eq, l10n.d('Na audio (M)'), _advanceOnAudioEnd),
];
return Row(
mainAxisSize: MainAxisSize.min,
children: [
for (final (icon, label, active) in items)
Padding(
padding: const EdgeInsets.only(right: 12),
child: Opacity(
opacity: active ? 0.7 : 0.28,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 14, color: Colors.white),
const SizedBox(width: 4),
Text(
label,
style: const TextStyle(color: Colors.white, fontSize: 11),
),
],
),
),
),
],
);
}
/// Vol-vlak zwart/wit scherm dat met een klik weer verdwijnt.
Widget _blankFill() {
return GestureDetector(
@ -783,148 +806,7 @@ class _FullscreenPresenterState extends State<FullscreenPresenter> {
return GestureDetector(
onTap: _next,
onSecondaryTap: _prev,
child: Stack(
children: [
// Slide canvas
Positioned.fill(child: _slideCanvas(slide)),
// Voortgangsbalk (auto-advance)
if (_progress > 0)
Positioned(
bottom: 0,
left: 0,
right: 0,
child: LinearProgressIndicator(
value: _progress,
backgroundColor: Colors.white12,
color: Colors.white54,
minHeight: 3,
),
),
// Slide counter
Positioned(
right: 24,
bottom: 10,
child: Text(
'${_index + 1} / $total',
style: const TextStyle(
color: Colors.white38,
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
),
// Auto-play status (linksonder)
Positioned(left: 24, bottom: 10, child: _autoPlayStatus()),
// Navigation arrows
Positioned(
left: 0,
top: 0,
bottom: 0,
child: MouseRegion(
cursor: _index > 0 ? SystemMouseCursors.click : MouseCursor.defer,
child: GestureDetector(
onTap: _prev,
child: Container(
width: 60,
color: Colors.transparent,
child: _index > 0
? const Icon(
Icons.chevron_left,
color: Colors.white24,
size: 40,
)
: null,
),
),
),
),
Positioned(
right: 0,
top: 0,
bottom: 0,
child: MouseRegion(
cursor: _index < total - 1
? SystemMouseCursors.click
: MouseCursor.defer,
child: GestureDetector(
onTap: _next,
child: Container(
width: 60,
color: Colors.transparent,
child: _index < total - 1
? const Icon(
Icons.chevron_right,
color: Colors.white24,
size: 40,
)
: null,
),
),
),
),
// Top-right controls (presenter view + afsluiten)
Positioned(
top: 16,
right: 16,
child: Row(
children: [
Tooltip(
message: context.l10n.d('Sneltoetsen (?)'),
child: IconButton(
onPressed: _toggleHelp,
icon: const Icon(Icons.help_outline),
color: Colors.white,
style: IconButton.styleFrom(
backgroundColor: Colors.black45,
),
),
),
const SizedBox(width: 8),
Tooltip(
message: context.l10n.d('Slide-overzicht (G)'),
child: IconButton(
onPressed: _toggleGrid,
icon: const Icon(Icons.grid_view_rounded),
color: Colors.white,
style: IconButton.styleFrom(
backgroundColor: Colors.black45,
),
),
),
const SizedBox(width: 8),
Tooltip(
message: context.l10n.d('Presenter view (P)'),
child: IconButton(
onPressed: _togglePresenterView,
icon: const Icon(Icons.co_present_outlined),
color: Colors.white,
style: IconButton.styleFrom(
backgroundColor: Colors.black45,
),
),
),
const SizedBox(width: 8),
Tooltip(
message: context.l10n.d('Afsluiten (Escape)'),
child: IconButton(
onPressed: _exit,
icon: const Icon(Icons.close),
color: Colors.white,
style: IconButton.styleFrom(
backgroundColor: Colors.black45,
),
),
),
],
),
),
],
),
child: SizedBox.expand(child: _slideCanvas(slide)),
);
}
@ -1151,6 +1033,16 @@ class _FullscreenPresenterState extends State<FullscreenPresenter> {
icon: Icons.chevron_right,
onTap: _index < total - 1 ? _next : null,
),
if (_displays.length > 1) ...[
const SizedBox(width: 8),
Tooltip(
message: l10n.d('Wissel scherm (S)'),
child: _NavButton(
icon: Icons.screen_share_outlined,
onTap: _cycleDisplay,
),
),
],
const SizedBox(width: 16),
Text(
'${l10n.d('Slide')} ${_index + 1} / $total',
@ -1164,7 +1056,9 @@ class _FullscreenPresenterState extends State<FullscreenPresenter> {
Expanded(
child: Text(
l10n.d(
'P publiek · G overzicht · B/W zwart/wit · R tijd · Esc stop',
_displays.length > 1
? 'P publiek · H legenda · S scherm · G overzicht · B/W zwart/wit · R tijd · Esc stop'
: 'P publiek · H legenda · G overzicht · B/W zwart/wit · R tijd · Esc stop',
),
textAlign: TextAlign.right,
maxLines: 1,

View file

@ -654,7 +654,7 @@ packages:
source: hosted
version: "3.2.1"
screen_retriever:
dependency: transitive
dependency: "direct main"
description:
name: screen_retriever
sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c"

View file

@ -17,6 +17,7 @@ dependencies:
path_provider: ^2.1.5
path: ^1.9.1
uuid: ^4.5.1
screen_retriever: ^0.2.0
window_manager: ^0.5.1
shared_preferences: ^2.3.3
pasteboard: ^0.5.0

View file

@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:ocideck/l10n/app_localizations.dart';
void main() {
tearDown(() => AppLocalizations.setActiveLanguageCode('nl'));
test('supports Frisian and Papiamento language choices', () {
expect(AppLocalizations.languageNames['fy'], 'Frysk');
expect(AppLocalizations.languageNames['pap'], 'Papiamento');
expect(AppLocalizations.supportedLocales, contains(const Locale('fy')));
expect(AppLocalizations.supportedLocales, contains(const Locale('pap')));
});
test('uses app translations while Material falls back safely', () {
AppLocalizations.setActiveLanguageCode('fy');
expect(const AppLocalizations(Locale('en')).t('settings'), 'Ynstellingen');
expect(
const AppLocalizations(Locale('en')).d('Toetsenlegenda'),
'Toetsleginda',
);
expect(AppLocalizations.materialLocaleFor('fy'), const Locale('en'));
AppLocalizations.setActiveLanguageCode('pap');
expect(
const AppLocalizations(Locale('en')).t('settings'),
'Preferensianan',
);
expect(
const AppLocalizations(Locale('en')).d('Toetsenlegenda'),
'Legenda di tekla',
);
expect(AppLocalizations.materialLocaleFor('pap'), const Locale('en'));
});
}

View file

@ -29,9 +29,9 @@ void main() {
});
testWidgets('free Markdown renders display math', (tester) async {
final slide = Slide.create(SlideType.freeMarkdown).copyWith(
customMarkdown: 'Stelling:\n\n\$\$E = mc^2\$\$\n',
);
final slide = Slide.create(
SlideType.freeMarkdown,
).copyWith(customMarkdown: 'Stelling:\n\n\$\$E = mc^2\$\$\n');
await tester.pumpWidget(_host(slide));
expect(find.byType(Math), findsOneWidget);
@ -40,9 +40,9 @@ void main() {
testWidgets('an unknown code language falls back without throwing', (
tester,
) async {
final slide = Slide.create(SlideType.freeMarkdown).copyWith(
customMarkdown: '```nonexistentlang\nsome code\n```\n',
);
final slide = Slide.create(
SlideType.freeMarkdown,
).copyWith(customMarkdown: '```nonexistentlang\nsome code\n```\n');
await tester.pumpWidget(_host(slide));
// No HighlightView (unknown language) and no exception during build.

View file

@ -30,7 +30,12 @@ void main() {
await tester.pumpWidget(_host(slides));
await tester.pump();
expect(find.text('1 / 2'), findsOneWidget); // slide counter
expect(find.text('Eerste'), findsOneWidget);
expect(find.text('1 / 2'), findsNothing); // no audience chrome
expect(find.byIcon(Icons.help_outline), findsNothing);
expect(find.byIcon(Icons.grid_view_rounded), findsNothing);
expect(find.byIcon(Icons.co_present_outlined), findsNothing);
expect(find.byIcon(Icons.close), findsNothing);
expect(find.text('NOTITIES'), findsNothing); // presenter-only
expect(find.text('VOLGENDE'), findsNothing);
@ -88,17 +93,17 @@ void main() {
testWidgets('B blanks the audience screen and toggles back', (tester) async {
await tester.pumpWidget(_host(slides));
await tester.pump();
expect(find.text('1 / 2'), findsOneWidget);
expect(find.text('Eerste'), findsOneWidget);
// Blank to black: the slide (and its counter) disappears.
// Blank to black: the slide disappears.
await tester.sendKeyEvent(LogicalKeyboardKey.keyB);
await tester.pump();
expect(find.text('1 / 2'), findsNothing);
expect(find.text('Eerste'), findsNothing);
// Same key restores the slide.
await tester.sendKeyEvent(LogicalKeyboardKey.keyB);
await tester.pump();
expect(find.text('1 / 2'), findsOneWidget);
expect(find.text('Eerste'), findsOneWidget);
await tester.pumpWidget(const SizedBox());
});
@ -111,12 +116,12 @@ void main() {
await tester.sendKeyEvent(LogicalKeyboardKey.keyB);
await tester.pump();
expect(find.text('1 / 2'), findsNothing);
expect(find.text('Eerste'), findsNothing);
// First arrow un-blanks without advancing (still on slide 1).
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pump();
expect(find.text('1 / 2'), findsOneWidget);
expect(find.text('Eerste'), findsOneWidget);
await tester.pumpWidget(const SizedBox());
});
@ -142,7 +147,7 @@ void main() {
// Grid closed and we jumped to slide 2.
expect(find.text('Slide-overzicht'), findsNothing);
expect(find.text('2 / 2'), findsOneWidget);
expect(find.text('Tweede'), findsOneWidget);
await tester.pumpWidget(const SizedBox());
});
@ -152,15 +157,15 @@ void main() {
) async {
await tester.pumpWidget(_host(slides));
await tester.pump();
expect(find.text('1 / 2'), findsOneWidget);
expect(find.text('Eerste'), findsOneWidget);
await tester.sendKeyEvent(LogicalKeyboardKey.end);
await tester.pump();
expect(find.text('2 / 2'), findsOneWidget);
expect(find.text('Tweede'), findsOneWidget);
await tester.sendKeyEvent(LogicalKeyboardKey.home);
await tester.pump();
expect(find.text('1 / 2'), findsOneWidget);
expect(find.text('Eerste'), findsOneWidget);
await tester.pumpWidget(const SizedBox());
});
@ -187,7 +192,7 @@ void main() {
await tester.pump();
expect(find.text('Slide-overzicht'), findsNothing);
expect(find.text('2 / 2'), findsOneWidget);
expect(find.text('Tweede'), findsOneWidget);
await tester.pumpWidget(const SizedBox());
});
@ -199,7 +204,7 @@ void main() {
];
await tester.pumpWidget(_host(three));
await tester.pump();
expect(find.text('1 / 3'), findsOneWidget);
expect(find.text('Eerste'), findsOneWidget);
// Type "3" a badge appears, Enter jumps to slide 3.
await tester.sendKeyEvent(LogicalKeyboardKey.digit3);
@ -208,8 +213,9 @@ void main() {
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
await tester.pump();
// Badge gone, now actually on slide 3 (counter shows it).
expect(find.text('3 / 3'), findsOneWidget); // slide counter now
// Badge gone, now actually on slide 3.
expect(find.text('Derde'), findsOneWidget);
expect(find.text('3 / 3'), findsNothing);
expect(find.byIcon(Icons.south_east), findsNothing); // badge icon gone
await tester.pumpWidget(const SizedBox());
@ -218,17 +224,18 @@ void main() {
testWidgets('? toggles the shortcut cheatsheet', (tester) async {
await tester.pumpWidget(_host(slides));
await tester.pump();
expect(find.text('Sneltoetsen'), findsNothing);
expect(find.text('Toetsenlegenda'), findsNothing);
await tester.sendKeyEvent(LogicalKeyboardKey.keyH);
await tester.pump();
expect(find.text('Sneltoetsen'), findsOneWidget);
expect(find.text('Toetsenlegenda'), findsOneWidget);
expect(find.text('Scherm wisselen (meerdere schermen)'), findsOneWidget);
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
await tester.pump();
expect(find.text('Sneltoetsen'), findsNothing);
expect(find.text('Toetsenlegenda'), findsNothing);
// Esc closed the help, not the presentation.
expect(find.text('1 / 2'), findsOneWidget);
expect(find.text('Eerste'), findsOneWidget);
await tester.pumpWidget(const SizedBox());
});
@ -250,7 +257,7 @@ void main() {
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
await tester.pump();
expect(find.text('Slide-overzicht'), findsNothing);
expect(find.text('1 / 2'), findsOneWidget);
expect(find.text('Eerste'), findsOneWidget);
await tester.pumpWidget(const SizedBox());
});