Multi langues + Assign plan dialog + display quotas + translator IA

This commit is contained in:
Thomas Fransolet 2026-04-10 17:17:49 +02:00
parent 634fd8d4d7
commit 0059582e9f
77 changed files with 9293 additions and 533 deletions

4
l10n.yaml Normal file
View File

@ -0,0 +1,4 @@
arb-dir: lib/l10n
template-arb-file: app_fr.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations

View File

@ -13,4 +13,4 @@ class CommonLoader extends StatelessWidget {
child: LoaderWaveFloat(size: size), child: LoaderWaveFloat(size: size),
); );
} }
} }

View File

@ -40,7 +40,7 @@ class MultiSelectContainer extends StatelessWidget {
if(label != null) if(label != null)
Align( Align(
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
child: Text(label!, style: TextStyle(fontSize: 25, fontWeight: FontWeight.w300)) child: Text(label!, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: kPrimaryColor))
), ),
Padding( Padding(
padding: const EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
@ -95,7 +95,10 @@ class _MultiSelectChipState extends State<MultiSelectChip> {
choices.add(Container( choices.add(Container(
padding: const EdgeInsets.all(2.0), padding: const EdgeInsets.all(2.0),
child: ChoiceChip( child: ChoiceChip(
label: Text(widget.isHTMLLabel ? parse(item).documentElement!.text : item, style: TextStyle(color: kBlack)), label: Text(
widget.isHTMLLabel ? parse(item).documentElement!.text : item,
style: TextStyle(color: widget.selectedValues.contains(item) ? kWhite : kPrimaryColor),
),
selected: widget.selectedValues.contains(item), selected: widget.selectedValues.contains(item),
selectedColor: kPrimaryColor, selectedColor: kPrimaryColor,
onSelected: (selected) { onSelected: (selected) {

View File

@ -25,8 +25,10 @@ showMultiStringInputHTML (String label, String modalLabel, bool isTitle, List<Tr
children: [ children: [
Center(child: Text(modalLabel, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500))), Center(child: Text(modalLabel, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500))),
const SizedBox(height: 16), const SizedBox(height: 16),
SingleChildScrollView( Flexible(
child: TranslationInputContainer(isTitle: isTitle, values: values, newValues: newValues, onGetResult: onGetResult, maxLines: maxLines, resourceTypes: resourceTypes), child: SingleChildScrollView(
child: TranslationInputContainer(isTitle: isTitle, values: values, newValues: newValues, onGetResult: onGetResult, maxLines: maxLines, resourceTypes: resourceTypes),
),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(
@ -99,8 +101,10 @@ showMultiStringInputAndResourceHTML (String label, String modalLabel, bool isTit
children: [ children: [
Center(child: Text(modalLabel, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500))), Center(child: Text(modalLabel, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500))),
const SizedBox(height: 16), const SizedBox(height: 16),
SingleChildScrollView( Flexible(
child: TranslationInputAndResourceContainer(isTitle: isTitle, values: values, newValues: newValues, onGetResult: onGetResult, maxLines: maxLines, resourceTypes: resourceTypes), child: SingleChildScrollView(
child: TranslationInputAndResourceContainer(isTitle: isTitle, values: values, newValues: newValues, onGetResult: onGetResult, maxLines: maxLines, resourceTypes: resourceTypes),
),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(

View File

@ -0,0 +1,172 @@
import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart';
import 'package:provider/provider.dart';
import '../app_context.dart';
import '../Models/managerContext.dart';
import '../constants.dart';
import '../l10n/app_localizations.dart';
class QuotaBarsWidget extends StatefulWidget {
const QuotaBarsWidget({Key? key}) : super(key: key);
@override
State<QuotaBarsWidget> createState() => _QuotaBarsWidgetState();
}
class _QuotaBarsWidgetState extends State<QuotaBarsWidget> {
InstanceQuotaDTO? _quota;
bool _loading = true;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _fetchQuota());
}
Future<void> _fetchQuota() async {
final managerContext = Provider.of<AppContext>(context, listen: false).getContext() as ManagerAppContext;
final instanceId = managerContext.instanceId;
final client = managerContext.clientAPI;
if (instanceId == null || client == null) {
setState(() => _loading = false);
return;
}
try {
final quota = await client.instanceApi!.instanceGetQuota(instanceId);
if (mounted) setState(() { _quota = quota; _loading = false; });
} catch (_) {
if (mounted) setState(() => _loading = false);
}
}
Color _barColor(int used, int quota) {
if (quota == 0) return kPrimaryColor;
final ratio = used / quota;
if (ratio >= 0.95) return Colors.red;
if (ratio >= 0.80) return Colors.orange;
return kPrimaryColor;
}
double? _barValue(int used, int quota) {
if (quota == 0) return null;
return (used / quota).clamp(0.0, 1.0);
}
String _formatBytes(int bytes) {
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(0)} KB';
if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB';
}
@override
Widget build(BuildContext context) {
final managerContext = Provider.of<AppContext>(context).getContext() as ManagerAppContext;
final isAssistant = managerContext.instanceDTO?.isAssistant ?? false;
final l10n = AppLocalizations.of(context)!;
if (_loading) {
return const SizedBox(
height: 8,
child: LinearProgressIndicator(),
);
}
if (_quota == null) return const SizedBox.shrink();
final storageUsed = _quota!.storageUsedBytes ?? 0;
final storageQuota = _quota!.storageQuotaBytes ?? 0;
final aiUsed = _quota!.aiRequestsUsed ?? 0;
final aiQuota = _quota!.aiRequestsPerMonth ?? 0;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_QuotaBar(
icon: Icons.storage,
label: l10n.storageLabel,
value: _barValue(storageUsed, storageQuota),
color: _barColor(storageUsed, storageQuota),
subtitle: storageQuota == 0
? '${_formatBytes(storageUsed)} · ${l10n.unlimited}'
: '${_formatBytes(storageUsed)} / ${_formatBytes(storageQuota)}',
),
if (isAssistant) ...[
const SizedBox(height: 8),
_QuotaBar(
icon: Icons.chat_bubble_outline,
label: l10n.aiThisMonthLabel,
value: _barValue(aiUsed, aiQuota),
color: _barColor(aiUsed, aiQuota),
subtitle: aiQuota == 0
? '$aiUsed ${l10n.requestsAbbr} · ${l10n.unlimited}'
: '$aiUsed / $aiQuota ${l10n.requestsAbbr}.',
),
],
],
),
);
}
}
class _QuotaBar extends StatelessWidget {
final IconData icon;
final String label;
final double? value;
final Color color;
final String subtitle;
const _QuotaBar({
required this.icon,
required this.label,
required this.value,
required this.color,
required this.subtitle,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(icon, size: 14, color: kBodyTextColor),
const SizedBox(width: 4),
Text(
label,
style: const TextStyle(
fontSize: 11,
color: kBodyTextColor,
fontWeight: FontWeight.w500,
fontFamily: 'Helvetica',
),
),
],
),
const SizedBox(height: 4),
ClipRRect(
borderRadius: BorderRadius.circular(2),
child: LinearProgressIndicator(
value: value,
minHeight: 5,
backgroundColor: Colors.grey.shade200,
valueColor: AlwaysStoppedAnimation<Color>(color),
),
),
const SizedBox(height: 2),
Text(
subtitle,
style: TextStyle(
fontSize: 10,
color: kBodyTextColor.withValues(alpha: 0.6),
fontFamily: 'Helvetica',
),
),
],
);
}
}

View File

@ -4,9 +4,15 @@ import 'package:flutter_quill_delta_from_html/flutter_quill_delta_from_html.dart
import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart'; import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:manager_app/Components/resource_input_container.dart'; import 'package:manager_app/Components/resource_input_container.dart';
import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/Models/managerContext.dart' show ManagerAppContext;
import 'package:manager_app/Services/ai_translate_service.dart';
import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:provider/provider.dart';
import 'flag_decoration.dart'; import 'flag_decoration.dart';
import 'message_notification.dart';
class TranslationInputAndResourceContainer extends StatefulWidget { class TranslationInputAndResourceContainer extends StatefulWidget {
TranslationInputAndResourceContainer({ TranslationInputAndResourceContainer({
@ -35,6 +41,7 @@ class _TranslationInputAndResourceContainerState
extends State<TranslationInputAndResourceContainer> { extends State<TranslationInputAndResourceContainer> {
late Map<String, QuillController> _controllers; late Map<String, QuillController> _controllers;
bool _isEnforcingLimit = false; bool _isEnforcingLimit = false;
bool _isTranslating = false;
@override @override
void initState() { void initState() {
@ -92,6 +99,65 @@ class _TranslationInputAndResourceContainerState
).convert(); ).convert();
} }
Future<void> _translateWithAI() async {
final appContext = context.read<AppContext>().getContext() as ManagerAppContext;
if (appContext.instanceDTO?.isAssistant != true) return;
final source = widget.newValues.firstWhere(
(t) => t.value != null && t.value!.trim().isNotEmpty && t.value!.trim() != '<p><br></p>',
orElse: () => widget.newValues.first,
);
if (source.value == null || source.value!.trim().isEmpty) {
showNotification(kError, kWhite, 'Aucun texte source à traduire', context, null);
return;
}
final targetLangs = widget.newValues
.where((t) => t.language != source.language)
.map((t) => t.language!)
.toList();
if (targetLangs.isEmpty) return;
setState(() => _isTranslating = true);
try {
final translations = await AiTranslateService.translate(
host: appContext.host!,
accessToken: appContext.accessToken!,
instanceId: appContext.instanceId!,
text: source.value!,
sourceLang: source.language!,
targetLangs: targetLangs,
);
setState(() {
for (final translation in widget.newValues) {
final lang = translation.language!;
if (lang == source.language) continue;
final translated = translations[lang];
if (translated == null) continue;
// On ne touche qu'au texte, pas à la ressource liée
translation.value = translated;
_controllers[lang]?.dispose();
final controller = QuillController(
document: Document.fromJson(_htmlToDeltaJson(translated)),
selection: const TextSelection.collapsed(offset: 0),
);
_setupListener(lang, controller);
_controllers[lang] = controller;
}
});
showNotification(kSuccess, kWhite, 'Traduction appliquée', context, null);
} catch (e) {
showNotification(kError, kWhite, 'Erreur lors de la traduction IA', context, null);
} finally {
setState(() => _isTranslating = false);
}
}
@override @override
void dispose() { void dispose() {
for (final c in _controllers.values) { for (final c in _controllers.values) {
@ -104,7 +170,29 @@ class _TranslationInputAndResourceContainerState
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: widget.newValues.map((t) => _buildLanguageSection(t)).toList(), children: [
...widget.newValues.map((t) => _buildLanguageSection(t)),
const SizedBox(height: 8),
Builder(builder: (ctx) {
final instance = ctx.watch<AppContext>().getContext()?.instanceDTO;
if (instance?.isAssistant != true) return const SizedBox.shrink();
return Center(
child: SizedBox(
width: 370,
height: 70,
child: _isTranslating
? const Center(child: CircularProgressIndicator())
: RoundedButton(
text: "Traduire via IA",
icon: Icons.auto_awesome,
color: kPrimaryColor,
press: _translateWithAI,
fontSize: 20,
),
),
);
}),
],
); );
} }

View File

@ -5,10 +5,14 @@ import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:manager_app/Components/resource_input_container.dart'; import 'package:manager_app/Components/resource_input_container.dart';
import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/Models/managerContext.dart' show ManagerAppContext;
import 'package:manager_app/Services/ai_translate_service.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:provider/provider.dart';
import 'flag_decoration.dart'; import 'flag_decoration.dart';
import 'message_notification.dart'; import 'message_notification.dart';
import 'package:manager_app/app_context.dart';
class TranslationInputContainer extends StatefulWidget { class TranslationInputContainer extends StatefulWidget {
TranslationInputContainer({ TranslationInputContainer({
@ -35,6 +39,7 @@ class TranslationInputContainer extends StatefulWidget {
class _TranslationInputContainerState extends State<TranslationInputContainer> { class _TranslationInputContainerState extends State<TranslationInputContainer> {
late Map<String, QuillController> _controllers; late Map<String, QuillController> _controllers;
bool _isEnforcingLimit = false; bool _isEnforcingLimit = false;
bool _isTranslating = false;
@override @override
void initState() { void initState() {
@ -92,6 +97,64 @@ class _TranslationInputContainerState extends State<TranslationInputContainer> {
).convert(); ).convert();
} }
Future<void> _translateWithAI() async {
final appContext = context.read<AppContext>().getContext() as ManagerAppContext;
if (appContext.instanceDTO?.isAssistant != true) return;
final source = widget.newValues.firstWhere(
(t) => t.value != null && t.value!.trim().isNotEmpty && t.value!.trim() != '<p><br></p>',
orElse: () => widget.newValues.first,
);
if (source.value == null || source.value!.trim().isEmpty) {
showNotification(kError, kWhite, 'Aucun texte source à traduire', context, null);
return;
}
final targetLangs = widget.newValues
.where((t) => t.language != source.language)
.map((t) => t.language!)
.toList();
if (targetLangs.isEmpty) return;
setState(() => _isTranslating = true);
try {
final translations = await AiTranslateService.translate(
host: appContext.host!,
accessToken: appContext.accessToken!,
instanceId: appContext.instanceId!,
text: source.value!,
sourceLang: source.language!,
targetLangs: targetLangs,
);
setState(() {
for (final translation in widget.newValues) {
final lang = translation.language!;
if (lang == source.language) continue;
final translated = translations[lang];
if (translated == null) continue;
translation.value = translated;
_controllers[lang]?.dispose();
final controller = QuillController(
document: Document.fromJson(_htmlToDeltaJson(translated)),
selection: const TextSelection.collapsed(offset: 0),
);
_setupListener(lang, controller);
_controllers[lang] = controller;
}
});
showNotification(kSuccess, kWhite, 'Traduction appliquée', context, null);
} catch (e) {
showNotification(kError, kWhite, 'Erreur lors de la traduction IA', context, null);
} finally {
setState(() => _isTranslating = false);
}
}
void _applyToAllLanguages() { void _applyToAllLanguages() {
if (_controllers.isEmpty) return; if (_controllers.isEmpty) return;
final firstLang = widget.newValues.first.language!; final firstLang = widget.newValues.first.language!;
@ -130,7 +193,7 @@ class _TranslationInputContainerState extends State<TranslationInputContainer> {
children: [ children: [
...widget.newValues.map((t) => _buildLanguageSection(t)), ...widget.newValues.map((t) => _buildLanguageSection(t)),
const SizedBox(height: 8), const SizedBox(height: 8),
if (widget.resourceTypes == null) if (widget.resourceTypes == null) ...[
Center( Center(
child: SizedBox( child: SizedBox(
width: 370, width: 370,
@ -144,6 +207,26 @@ class _TranslationInputContainerState extends State<TranslationInputContainer> {
), ),
), ),
), ),
Builder(builder: (ctx) {
final instance = ctx.watch<AppContext>().getContext()?.instanceDTO;
if (instance?.isAssistant != true) return const SizedBox.shrink();
return Center(
child: SizedBox(
width: 370,
height: 70,
child: _isTranslating
? const Center(child: CircularProgressIndicator())
: RoundedButton(
text: "Traduire via IA",
icon: Icons.auto_awesome,
color: kPrimaryColor,
press: _translateWithAI,
fontSize: 20,
),
),
);
}),
]
], ],
); );
} }

View File

@ -16,6 +16,7 @@ class ManagerAppContext with ChangeNotifier{
SectionDTO? selectedSection; SectionDTO? selectedSection;
bool? isLoading = false; bool? isLoading = false;
UserRole? role; UserRole? role;
Locale locale = const Locale('fr');
bool get canEdit => role != null && role!.value <= 2; bool get canEdit => role != null && role!.value <= 2;

View File

@ -2,8 +2,10 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/Components/common_loader.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -61,6 +63,7 @@ class _ApiKeysScreenState extends State<ApiKeysScreen> {
} }
void _showCreateDialog(BuildContext context, ManagerAppContext ctx) { void _showCreateDialog(BuildContext context, ManagerAppContext ctx) {
final l = AppLocalizations.of(context)!;
final nameCtrl = TextEditingController(); final nameCtrl = TextEditingController();
ApiKeyAppType selectedType = ApiKeyAppType.number0; ApiKeyAppType selectedType = ApiKeyAppType.number0;
DateTime? selectedExpiration; DateTime? selectedExpiration;
@ -69,14 +72,14 @@ class _ApiKeysScreenState extends State<ApiKeysScreen> {
context: context, context: context,
builder: (_) => StatefulBuilder(builder: (ctx2, setLocal) { builder: (_) => StatefulBuilder(builder: (ctx2, setLocal) {
return AlertDialog( return AlertDialog(
title: const Text('Créer une clé API'), title: Text(l.createApiKey),
content: Column(mainAxisSize: MainAxisSize.min, children: [ content: Column(mainAxisSize: MainAxisSize.min, children: [
TextField(controller: nameCtrl, decoration: const InputDecoration(labelText: 'Nom')), TextField(controller: nameCtrl, decoration: InputDecoration(labelText: l.name)),
const SizedBox(height: 8), const SizedBox(height: 8),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('Type d\'application', style: TextStyle(fontSize: 12, color: Colors.grey)), Text(l.appType, style: const TextStyle(fontSize: 12, color: Colors.grey)),
DropdownButton<ApiKeyAppType>( DropdownButton<ApiKeyAppType>(
value: selectedType, value: selectedType,
isExpanded: true, isExpanded: true,
@ -93,8 +96,8 @@ class _ApiKeysScreenState extends State<ApiKeysScreen> {
Expanded( Expanded(
child: Text( child: Text(
selectedExpiration == null selectedExpiration == null
? 'Pas d\'expiration' ? l.noExpiration
: 'Expire le ${selectedExpiration!.day.toString().padLeft(2, '0')}/${selectedExpiration!.month.toString().padLeft(2, '0')}/${selectedExpiration!.year}', : l.expiresOn('${selectedExpiration!.day.toString().padLeft(2, '0')}/${selectedExpiration!.month.toString().padLeft(2, '0')}/${selectedExpiration!.year}'),
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
), ),
), ),
@ -108,19 +111,19 @@ class _ApiKeysScreenState extends State<ApiKeysScreen> {
); );
if (picked != null) setLocal(() => selectedExpiration = picked); if (picked != null) setLocal(() => selectedExpiration = picked);
}, },
child: const Text('Choisir'), child: Text(l.choose),
), ),
if (selectedExpiration != null) if (selectedExpiration != null)
IconButton( IconButton(
icon: const Icon(Icons.close, size: 16), icon: const Icon(Icons.close, size: 16),
onPressed: () => setLocal(() => selectedExpiration = null), onPressed: () => setLocal(() => selectedExpiration = null),
tooltip: 'Supprimer l\'expiration', tooltip: l.removeExpiration,
), ),
], ],
), ),
]), ]),
actions: [ actions: [
TextButton(onPressed: () => Navigator.of(ctx2, rootNavigator: true).pop(), child: const Text('Annuler')), TextButton(onPressed: () => Navigator.of(ctx2, rootNavigator: true).pop(), child: Text(l.cancel)),
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () async {
Navigator.of(ctx2, rootNavigator: true).pop(); Navigator.of(ctx2, rootNavigator: true).pop();
@ -129,7 +132,7 @@ class _ApiKeysScreenState extends State<ApiKeysScreen> {
_showPlainKeyDialog(context, plainKey); _showPlainKeyDialog(context, plainKey);
} }
}, },
child: const Text('Créer'), child: Text(l.create),
), ),
], ],
); );
@ -138,15 +141,16 @@ class _ApiKeysScreenState extends State<ApiKeysScreen> {
} }
void _showPlainKeyDialog(BuildContext context, String plainKey) { void _showPlainKeyDialog(BuildContext context, String plainKey) {
final l = AppLocalizations.of(context)!;
showDialog( showDialog(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
builder: (_) => AlertDialog( builder: (_) => AlertDialog(
title: const Text('Clé API créée'), title: Text(l.apiKeyCreatedTitle),
content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
const Text( Text(
'Copiez cette clé maintenant — elle ne sera plus affichée.', l.copyKeyWarning,
style: TextStyle(color: Colors.orange, fontWeight: FontWeight.w600), style: const TextStyle(color: Colors.orange, fontWeight: FontWeight.w600),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Container( Container(
@ -160,7 +164,7 @@ class _ApiKeysScreenState extends State<ApiKeysScreen> {
Expanded(child: SelectableText(plainKey, style: const TextStyle(fontFamily: 'monospace', fontSize: 13))), Expanded(child: SelectableText(plainKey, style: const TextStyle(fontFamily: 'monospace', fontSize: 13))),
IconButton( IconButton(
icon: const Icon(Icons.copy, size: 18), icon: const Icon(Icons.copy, size: 18),
tooltip: 'Copier', tooltip: l.copy,
onPressed: () => Clipboard.setData(ClipboardData(text: plainKey)), onPressed: () => Clipboard.setData(ClipboardData(text: plainKey)),
), ),
]), ]),
@ -169,7 +173,7 @@ class _ApiKeysScreenState extends State<ApiKeysScreen> {
actions: [ actions: [
ElevatedButton( ElevatedButton(
onPressed: () => Navigator.of(context, rootNavigator: true).pop(), onPressed: () => Navigator.of(context, rootNavigator: true).pop(),
child: const Text('J\'ai copié la clé'), child: Text(l.copiedKey),
), ),
], ],
), ),
@ -177,20 +181,21 @@ class _ApiKeysScreenState extends State<ApiKeysScreen> {
} }
void _confirmRevoke(BuildContext context, ManagerAppContext ctx, Map<String, dynamic> key) { void _confirmRevoke(BuildContext context, ManagerAppContext ctx, Map<String, dynamic> key) {
final l = AppLocalizations.of(context)!;
showDialog( showDialog(
context: context, context: context,
builder: (dialogContext) => AlertDialog( builder: (dialogContext) => AlertDialog(
title: const Text('Révoquer la clé API'), title: Text(l.revokeApiKeyTitle),
content: Text('Révoquer « ${key['name']} » ? Les apps utilisant cette clé perdront l\'accès.'), content: Text(l.revokeApiKeyConfirm(key['name'] as String? ?? '')),
actions: [ actions: [
TextButton(onPressed: () => Navigator.pop(dialogContext), child: const Text('Annuler')), TextButton(onPressed: () => Navigator.pop(dialogContext), child: Text(l.cancel)),
ElevatedButton( ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red), style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
onPressed: () async { onPressed: () async {
Navigator.pop(dialogContext); Navigator.pop(dialogContext);
await _revokeKey(ctx, key['id'] as String); await _revokeKey(ctx, key['id'] as String);
}, },
child: const Text('Révoquer', style: TextStyle(color: Colors.white)), child: Text(l.revoke, style: const TextStyle(color: Colors.white)),
), ),
], ],
), ),
@ -208,6 +213,7 @@ class _ApiKeysScreenState extends State<ApiKeysScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l = AppLocalizations.of(context)!;
final appContext = Provider.of<AppContext>(context); final appContext = Provider.of<AppContext>(context);
final managerCtx = appContext.getContext() as ManagerAppContext; final managerCtx = appContext.getContext() as ManagerAppContext;
@ -217,19 +223,19 @@ class _ApiKeysScreenState extends State<ApiKeysScreen> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('Clés API', style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: kPrimaryColor)), Text(l.apiKeysTitle, style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: kPrimaryColor)),
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () => _showCreateDialog(context, managerCtx), onPressed: () => _showCreateDialog(context, managerCtx),
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: const Text('Créer une clé'), label: Text(l.createKeyBtn),
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
if (_loading) if (_loading)
const Center(child: CircularProgressIndicator()) const CommonLoader()
else if (_keys.isEmpty) else if (_keys.isEmpty)
const Center(child: Text('Aucune clé API')) Center(child: Text(l.noApiKeys))
else else
Expanded( Expanded(
child: Card( child: Card(
@ -248,13 +254,13 @@ class _ApiKeysScreenState extends State<ApiKeysScreen> {
columnSpacing: 24, columnSpacing: 24,
headingRowColor: WidgetStateProperty.all(Colors.grey.shade50), headingRowColor: WidgetStateProperty.all(Colors.grey.shade50),
dividerThickness: 1, dividerThickness: 1,
columns: const [ columns: [
DataColumn(label: Text('Nom', style: TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.name, style: const TextStyle(fontWeight: FontWeight.w600))),
DataColumn(label: Text('Type', style: TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.type, style: const TextStyle(fontWeight: FontWeight.w600))),
DataColumn(label: Text('Créée le', style: TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.createdOn, style: const TextStyle(fontWeight: FontWeight.w600))),
DataColumn(label: Text('Expiration', style: TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.expiration, style: const TextStyle(fontWeight: FontWeight.w600))),
DataColumn(label: Text('Statut', style: TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.status, style: const TextStyle(fontWeight: FontWeight.w600))),
DataColumn(label: Text('Actions', style: TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.actions, style: const TextStyle(fontWeight: FontWeight.w600))),
], ],
rows: _keys.map((key) { rows: _keys.map((key) {
final isActive = key['isActive'] as bool? ?? false; final isActive = key['isActive'] as bool? ?? false;
@ -276,7 +282,7 @@ class _ApiKeysScreenState extends State<ApiKeysScreen> {
DataCell(Text(expStr, style: TextStyle(color: exp != null && exp.isBefore(DateTime.now()) ? Colors.red : Colors.grey))), DataCell(Text(expStr, style: TextStyle(color: exp != null && exp.isBefore(DateTime.now()) ? Colors.red : Colors.grey))),
DataCell(Chip( DataCell(Chip(
label: Text( label: Text(
isActive ? 'Active' : 'Révoquée', isActive ? l.activeKey : l.revokedKey,
style: TextStyle( style: TextStyle(
color: isActive ? Colors.green.shade700 : Colors.red.shade700, color: isActive ? Colors.green.shade700 : Colors.red.shade700,
fontSize: 12, fontSize: 12,
@ -290,7 +296,7 @@ class _ApiKeysScreenState extends State<ApiKeysScreen> {
DataCell(isActive DataCell(isActive
? IconButton( ? IconButton(
icon: const Icon(Icons.block, color: Colors.red, size: 20), icon: const Icon(Icons.block, color: Colors.red, size: 20),
tooltip: 'Révoquer', tooltip: l.tooltipRevoke,
onPressed: () => _confirmRevoke(context, managerCtx, key), onPressed: () => _confirmRevoke(context, managerCtx, key),
) )
: const SizedBox()), : const SizedBox()),

View File

@ -5,6 +5,7 @@ import 'package:manager_app/Components/string_input_container.dart';
import 'package:manager_app/Screens/Resources/resources_screen.dart'; import 'package:manager_app/Screens/Resources/resources_screen.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import '../Kiosk_devices/change_device_info_modal.dart'; import '../Kiosk_devices/change_device_info_modal.dart';
@ -19,7 +20,7 @@ dynamic showAddConfigurationLink (BuildContext mainContext, AppContext appContex
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0)) borderRadius: BorderRadius.all(Radius.circular(20.0))
), ),
title: Center(child: Text("Sélectionner une configuration à ajouter")), title: Center(child: Text(AppLocalizations.of(context)!.selectConfigToAdd)),
content: FutureBuilder( content: FutureBuilder(
future: getConfigurations(appContext), future: getConfigurations(appContext),
builder: (context, AsyncSnapshot<dynamic> snapshot) { builder: (context, AsyncSnapshot<dynamic> snapshot) {
@ -35,7 +36,7 @@ dynamic showAddConfigurationLink (BuildContext mainContext, AppContext appContex
height: size.height * 0.5, height: size.height * 0.5,
width: size.width * 0.5, width: size.width * 0.5,
constraints: const BoxConstraints(minHeight: 250, minWidth: 250), constraints: const BoxConstraints(minHeight: 250, minWidth: 250),
child: configurations.length == 0 ? Center(child: Text("Aucun élément à afficher")) : ListView.builder( child: configurations.length == 0 ? Center(child: Text(AppLocalizations.of(context)!.noItems)) : ListView.builder(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
itemCount: configurations.length, itemCount: configurations.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
@ -115,7 +116,7 @@ dynamic showAddConfigurationLink (BuildContext mainContext, AppContext appContex
width: 180, width: 180,
height: 70, height: 70,
child: RoundedButton( child: RoundedButton(
text: "Annuler", text: AppLocalizations.of(context)!.cancel,
icon: Icons.undo, icon: Icons.undo,
color: kSecond, color: kSecond,
press: () { press: () {
@ -128,7 +129,7 @@ dynamic showAddConfigurationLink (BuildContext mainContext, AppContext appContex
width: 180, width: 180,
height: 70, height: 70,
child: RoundedButton( child: RoundedButton(
text: "Ajouter", text: AppLocalizations.of(context)!.add,
icon: Icons.add, icon: Icons.add,
color: kPrimaryColor, color: kPrimaryColor,
press: () { press: () {

View File

@ -17,6 +17,7 @@ import 'package:manager_app/Screens/Applications/add_configuration_link_popup.da
import 'package:manager_app/Screens/Applications/phone_mockup.dart'; import 'package:manager_app/Screens/Applications/phone_mockup.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -57,7 +58,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Informations générales", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 21)), Text(AppLocalizations.of(context)!.generalInfo, style: TextStyle(fontWeight: FontWeight.w500, fontSize: 21)),
SizedBox(height: 8), SizedBox(height: 8),
Expanded( Expanded(
child: Center( child: Center(
@ -75,7 +76,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
height: elementHeight, height: elementHeight,
child: Center( child: Center(
child: ResourceInputContainer( child: ResourceInputContainer(
label: "Image principale :", label: AppLocalizations.of(context)!.mainImageLabel,
initialValue: _applicationInstanceDTO.mainImageId, initialValue: _applicationInstanceDTO.mainImageId,
color: kPrimaryColor, color: kPrimaryColor,
imageFit: BoxFit.fitHeight, imageFit: BoxFit.fitHeight,
@ -91,7 +92,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
// automatic save // automatic save
var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO);
if(applicationLink != null) { if(applicationLink != null) {
showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null);
/*setState(() { /*setState(() {
});*/ });*/
@ -106,7 +107,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
height: elementHeight, height: elementHeight,
child: Center( child: Center(
child: ResourceInputContainer( child: ResourceInputContainer(
label: "Loader :", label: AppLocalizations.of(context)!.loaderLabel,
initialValue: _applicationInstanceDTO.loaderImageId, initialValue: _applicationInstanceDTO.loaderImageId,
color: kPrimaryColor, color: kPrimaryColor,
imageFit: BoxFit.fitHeight, imageFit: BoxFit.fitHeight,
@ -122,7 +123,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
// automatic save // automatic save
var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO);
if(applicationLink != null) { if(applicationLink != null) {
showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null);
/*setState(() { /*setState(() {
});*/ });*/
@ -137,7 +138,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
height: elementHeight, height: elementHeight,
child: Center( child: Center(
child: ColorPickerInputContainer( child: ColorPickerInputContainer(
label: "Couleur principale :", label: AppLocalizations.of(context)!.primaryColorLabel,
fontSize: 20, fontSize: 20,
color: _applicationInstanceDTO.primaryColor, color: _applicationInstanceDTO.primaryColor,
onChanged: (value) async { onChanged: (value) async {
@ -145,7 +146,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
// automatic save // automatic save
var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO);
if(applicationLink != null) { if(applicationLink != null) {
showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null);
/*setState(() { /*setState(() {
});*/ });*/
@ -160,7 +161,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
height: elementHeight, height: elementHeight,
child: Center( child: Center(
child: ColorPickerInputContainer( child: ColorPickerInputContainer(
label: "Couleur secondaire :", label: AppLocalizations.of(context)!.secondaryColorLabel,
fontSize: 20, fontSize: 20,
color: _applicationInstanceDTO.secondaryColor, color: _applicationInstanceDTO.secondaryColor,
onChanged: (value) async { onChanged: (value) async {
@ -168,7 +169,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
// automatic save // automatic save
var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO);
if(applicationLink != null) { if(applicationLink != null) {
showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null);
/*setState(() { /*setState(() {
});*/ });*/
@ -183,17 +184,17 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
height: elementHeight, height: elementHeight,
child: Center( child: Center(
child: SegmentedEnumInputContainer( child: SegmentedEnumInputContainer(
label: "Affichage :", label: AppLocalizations.of(context)!.layoutLabel,
selected: _applicationInstanceDTO.layoutMainPage, selected: _applicationInstanceDTO.layoutMainPage,
values: LayoutMainPageType.values, values: LayoutMainPageType.values,
inputValues: { LayoutMainPageType.SimpleGrid: {'label': 'Grille', 'icon': Icons.grid_view}, LayoutMainPageType.MasonryGrid : {'label': 'Masonry', 'icon': Icons.view_quilt }}, inputValues: { LayoutMainPageType.SimpleGrid: {'label': AppLocalizations.of(context)!.layoutGrid, 'icon': Icons.grid_view}, LayoutMainPageType.MasonryGrid : {'label': 'Masonry', 'icon': Icons.view_quilt }},
onChanged: (value) async { onChanged: (value) async {
var tempOutput = value; var tempOutput = value;
_applicationInstanceDTO.layoutMainPage = tempOutput; _applicationInstanceDTO.layoutMainPage = tempOutput;
// automatic save // automatic save
var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO);
if(applicationLink != null) { if(applicationLink != null) {
showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null);
/*setState(() { /*setState(() {
});*/ });*/
@ -209,7 +210,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
height: elementHeight, height: elementHeight,
child: Center( child: Center(
child: MultiSelectDropdownLanguageContainer( child: MultiSelectDropdownLanguageContainer(
label: "Langues :", label: AppLocalizations.of(context)!.languagesLabel,
initialValue: _applicationInstanceDTO.languages != null ? _applicationInstanceDTO.languages!: [], initialValue: _applicationInstanceDTO.languages != null ? _applicationInstanceDTO.languages!: [],
values: languages, values: languages,
isMultiple: true, isMultiple: true,
@ -221,7 +222,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
// automatic save // automatic save
var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO);
if(applicationLink != null) { if(applicationLink != null) {
showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null);
/*setState(() { /*setState(() {
});*/ });*/
@ -245,14 +246,14 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
List<SectionEventDTO>? sectionEvents = rawSubsections?.whereType<SectionEventDTO>().toList(); List<SectionEventDTO>? sectionEvents = rawSubsections?.whereType<SectionEventDTO>().toList();
sectionEvents = sectionEvents == null ? [] : sectionEvents; sectionEvents = sectionEvents == null ? [] : sectionEvents;
sectionEvents.add(SectionEventDTO(id: null, label: "Aucun")); sectionEvents.add(SectionEventDTO(id: null, label: AppLocalizations.of(context)!.noneOption));
print(_applicationInstanceDTO.sectionEventId); print(_applicationInstanceDTO.sectionEventId);
print(_applicationInstanceDTO.sectionEventDTO); print(_applicationInstanceDTO.sectionEventDTO);
return SingleChoiceInputContainer<SectionEventDTO?>( return SingleChoiceInputContainer<SectionEventDTO?>(
label: "Evènement à l'affiche :", label: AppLocalizations.of(context)!.featuredEventLabel,
selectLabel: "Choisir un évènement", selectLabel: AppLocalizations.of(context)!.chooseEvent,
selected: _applicationInstanceDTO.sectionEventDTO, selected: _applicationInstanceDTO.sectionEventDTO,
values: sectionEvents.toList(), values: sectionEvents.toList(),
valueExtractor: (SectionEventDTO? dto) => dto?.id ?? "", valueExtractor: (SectionEventDTO? dto) => dto?.id ?? "",
@ -273,7 +274,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
// automatic save // automatic save
var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO);
if(applicationLink != null) { if(applicationLink != null) {
showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null);
//setState(() { //setState(() {
_applicationInstanceDTO.sectionEventDTO = applicationLink.sectionEventDTO; _applicationInstanceDTO.sectionEventDTO = applicationLink.sectionEventDTO;
//_applicationInstanceDTO = applicationLink; //_applicationInstanceDTO = applicationLink;
@ -296,7 +297,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text("Assistant IA :", style: TextStyle(fontSize: 16)), Text(AppLocalizations.of(context)!.aiAssistantLabel, style: TextStyle(fontSize: 16)),
Switch( Switch(
activeThumbColor: kPrimaryColor, activeThumbColor: kPrimaryColor,
inactiveThumbColor: kBodyTextColor, inactiveThumbColor: kBodyTextColor,
@ -311,10 +312,10 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
localSetState(() { localSetState(() {
_applicationInstanceDTO.isAssistant = applicationLink.isAssistant; _applicationInstanceDTO.isAssistant = applicationLink.isAssistant;
}); });
showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null);
} }
} catch (e) { } catch (e) {
showNotification(kError, kWhite, "Une erreur est survenue", context, null); showNotification(kError, kWhite, AppLocalizations.of(context)!.errorOccurred, context, null);
} }
}, },
), ),
@ -347,7 +348,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Text("Configurations sur le téléphone", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 21)), child: Text(AppLocalizations.of(context)!.phoneConfigTitle, style: TextStyle(fontWeight: FontWeight.w500, fontSize: 21)),
), ),
appConfigurationLinks != null ? Padding( appConfigurationLinks != null ? Padding(
padding: const EdgeInsets.only(left: 32, right: 32, top: 75), padding: const EdgeInsets.only(left: 32, right: 32, top: 75),
@ -375,7 +376,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
// TODO use order put method // TODO use order put method
var result = await updateAppConfigurationOrder(appContext, updatedList); var result = await updateAppConfigurationOrder(appContext, updatedList);
localSetState(() {}); localSetState(() {});
showNotification(kSuccess, kWhite, "Application mobile mise à jour succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.appUpdatedSuccess, context, null);
}, },
actions: [ actions: [
(BuildContext context, int index, AppConfigurationLinkDTO link) { (BuildContext context, int index, AppConfigurationLinkDTO link) {
@ -394,16 +395,16 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
var applicationLink = await updateApplicationLink(appContext, link); var applicationLink = await updateApplicationLink(appContext, link);
if(applicationLink != null) { if(applicationLink != null) {
if(newValue) { if(newValue) {
showNotification(kSuccess, kWhite, "Configuration activée avec succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configActivatedSuccess, context, null);
} else { } else {
showNotification(kSuccess, kWhite, "Configuration désactivée avec succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configDeactivatedSuccess, context, null);
} }
localSetState(() { localSetState(() {
link.isActive = applicationLink.isActive; link.isActive = applicationLink.isActive;
}); });
} }
} catch (e) { } catch (e) {
showNotification(kError, kWhite, "Une erreur est survenue", context, null); showNotification(kError, kWhite, AppLocalizations.of(context)!.errorOccurred, context, null);
} }
}, },
), ),
@ -416,19 +417,19 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
showConfirmationDialog( showConfirmationDialog(
"Êtes-vous sûr de vouloir retirer cette configuration de l'application ?", AppLocalizations.of(context)!.configRemoveConfirm,
() {}, () {},
() async { () async {
try { try {
var result = await deleteConfigurationToApp(appContext, link, _applicationInstanceDTO); var result = await deleteConfigurationToApp(appContext, link, _applicationInstanceDTO);
showNotification(kSuccess, kWhite, "La configuration a été retirée de l'application avec succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configRemovedSuccess, context, null);
setState(() { setState(() {
// for refresh ui // for refresh ui
}); });
} catch(e) { } catch(e) {
showNotification(kError, kWhite, 'Une erreur est survenue lors du retrait de la configuration', context, null); showNotification(kError, kWhite, AppLocalizations.of(context)!.configRemoveError, context, null);
} }
}, },
context context
@ -498,19 +499,19 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
showConfirmationDialog( showConfirmationDialog(
"Êtes-vous sûr de vouloir retirer cette configuration de l'application ?", AppLocalizations.of(context)!.configRemoveConfirm,
() {}, () {},
() async { () async {
try { try {
var result = await deleteConfigurationToApp(appContext, appConfigurationLink, _applicationInstanceDTO); var result = await deleteConfigurationToApp(appContext, appConfigurationLink, _applicationInstanceDTO);
showNotification(kSuccess, kWhite, "La configuration a été retirée de l'application avec succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configRemovedSuccess, context, null);
setState(() { setState(() {
// for refresh ui // for refresh ui
}); });
} catch(e) { } catch(e) {
showNotification(kError, kWhite, 'Une erreur est survenue lors du retrait de la configuration', context, null); showNotification(kError, kWhite, AppLocalizations.of(context)!.configRemoveError, context, null);
} }
}, },
context context
@ -526,7 +527,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
), ),
), ),
),*/ ),*/
): Center(child: Text("No data")), ): Center(child: Text(AppLocalizations.of(context)!.noData)),
appConfigurationLinks != null ? Positioned( appConfigurationLinks != null ? Positioned(
top: 8, top: 8,
right: 8, right: 8,
@ -630,7 +631,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text("Informations générales"), child: Text(AppLocalizations.of(context)!.generalInfo),
) )
), ),
Align( Align(
@ -656,7 +657,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
),*/ ),*/
// Image principale // Image principale
ResourceInputContainer( ResourceInputContainer(
label: "Image principale :", label: AppLocalizations.of(context)!.mainImageLabel,
initialValue: _applicationInstanceDTO.mainImageId, initialValue: _applicationInstanceDTO.mainImageId,
color: kPrimaryColor, color: kPrimaryColor,
onChanged: (ResourceDTO resource) { onChanged: (ResourceDTO resource) {
@ -671,7 +672,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
), ),
// Image Loader // Image Loader
ResourceInputContainer( ResourceInputContainer(
label: "Loader :", label: AppLocalizations.of(context)!.loaderLabel,
initialValue: _applicationInstanceDTO.loaderImageId, initialValue: _applicationInstanceDTO.loaderImageId,
color: kPrimaryColor, color: kPrimaryColor,
onChanged: (ResourceDTO resource) { onChanged: (ResourceDTO resource) {
@ -686,7 +687,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
), ),
// Primary color // Primary color
ColorPickerInputContainer( ColorPickerInputContainer(
label: "Couleur principale :", label: AppLocalizations.of(context)!.primaryColorLabel,
fontSize: 20, fontSize: 20,
color: _applicationInstanceDTO.primaryColor, color: _applicationInstanceDTO.primaryColor,
onChanged: (value) { onChanged: (value) {
@ -695,7 +696,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
), ),
// Secondary color // Secondary color
ColorPickerInputContainer( ColorPickerInputContainer(
label: "Couleur secondaire :", label: AppLocalizations.of(context)!.secondaryColorLabel,
fontSize: 20, fontSize: 20,
color: _applicationInstanceDTO.secondaryColor, color: _applicationInstanceDTO.secondaryColor,
onChanged: (value) { onChanged: (value) {
@ -706,7 +707,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
Text('Todo Type selector Grid or Mansonry'), Text('Todo Type selector Grid or Mansonry'),
// Langues // Langues
MultiSelectDropdownLanguageContainer( MultiSelectDropdownLanguageContainer(
label: "Langues :", label: AppLocalizations.of(context)!.languagesLabel,
initialValue: _applicationInstanceDTO.languages != null ? _applicationInstanceDTO.languages!: [], initialValue: _applicationInstanceDTO.languages != null ? _applicationInstanceDTO.languages!: [],
values: languages, values: languages,
isMultiple: true, isMultiple: true,
@ -728,7 +729,7 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text("Configurations sur le téléphone"), child: Text(AppLocalizations.of(context)!.phoneConfigTitle),
) )
), ),
Align( Align(
@ -774,16 +775,16 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO); var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO);
if(applicationLink != null) { if(applicationLink != null) {
if(newValue) { if(newValue) {
showNotification(kSuccess, kWhite, "Configuration activée avec succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configActivatedSuccess, context, null);
} else { } else {
showNotification(kSuccess, kWhite, "Configuration désactivée avec succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configDeactivatedSuccess, context, null);
} }
setState(() { setState(() {
link.isActive = applicationLink.isActive; link.isActive = applicationLink.isActive;
}); });
} }
} catch (e) { } catch (e) {
showNotification(kError, kWhite, "Une erreur est survenue", context, null); showNotification(kError, kWhite, AppLocalizations.of(context)!.errorOccurred, context, null);
} }
}, },
child: Icon(Icons.star, color: kError, size: 25), child: Icon(Icons.star, color: kError, size: 25),
@ -806,16 +807,16 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
var applicationLink = await updateApplicationLink(appContext, link); var applicationLink = await updateApplicationLink(appContext, link);
if(applicationLink != null) { if(applicationLink != null) {
if(newValue) { if(newValue) {
showNotification(kSuccess, kWhite, "Configuration activée avec succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configActivatedSuccess, context, null);
} else { } else {
showNotification(kSuccess, kWhite, "Configuration désactivée avec succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configDeactivatedSuccess, context, null);
} }
setState(() { setState(() {
link.isActive = applicationLink.isActive; link.isActive = applicationLink.isActive;
}); });
} }
} catch (e) { } catch (e) {
showNotification(kError, kWhite, "Une erreur est survenue", context, null); showNotification(kError, kWhite, AppLocalizations.of(context)!.errorOccurred, context, null);
} }
}, },
), ),
@ -828,19 +829,19 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
showConfirmationDialog( showConfirmationDialog(
"Êtes-vous sûr de vouloir retirer cette configuration de l'application ?", AppLocalizations.of(context)!.configRemoveConfirm,
() {}, () {},
() async { () async {
try { try {
var result = await deleteConfigurationToApp(appContext, link, _applicationInstanceDTO); var result = await deleteConfigurationToApp(appContext, link, _applicationInstanceDTO);
showNotification(kSuccess, kWhite, "La configuration a été retirée de l'application avec succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configRemovedSuccess, context, null);
setState(() { setState(() {
// for refresh ui // for refresh ui
}); });
} catch(e) { } catch(e) {
showNotification(kError, kWhite, 'Une erreur est survenue lors du retrait de la configuration', context, null); showNotification(kError, kWhite, AppLocalizations.of(context)!.configRemoveError, context, null);
} }
}, },
context context
@ -949,19 +950,19 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
showConfirmationDialog( showConfirmationDialog(
"Êtes-vous sûr de vouloir retirer cette configuration de l'application ?", AppLocalizations.of(context)!.configRemoveConfirm,
() {}, () {},
() async { () async {
try { try {
var result = await deleteConfigurationToApp(appContext, appConfigurationLink, _applicationInstanceDTO); var result = await deleteConfigurationToApp(appContext, appConfigurationLink, _applicationInstanceDTO);
showNotification(kSuccess, kWhite, "La configuration a été retirée de l'application avec succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configRemovedSuccess, context, null);
setState(() { setState(() {
// for refresh ui // for refresh ui
}); });
} catch(e) { } catch(e) {
showNotification(kError, kWhite, 'Une erreur est survenue lors du retrait de la configuration', context, null); showNotification(kError, kWhite, AppLocalizations.of(context)!.configRemoveError, context, null);
} }
}, },
context context
@ -979,14 +980,14 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
),*/ ),*/
], ],
), ),
): Center(child: Text("No data")), ): Center(child: Text(AppLocalizations.of(context)!.noData)),
), ),
], ],
), ),
), ),
); );
} else if (snapshot.connectionState == ConnectionState.none) { } else if (snapshot.connectionState == ConnectionState.none) {
return Text("No data"); return Text(AppLocalizations.of(context)!.noData);
} else { } else {
return Center( return Center(
child: Container( child: Container(

View File

@ -7,6 +7,7 @@ import 'package:manager_app/Components/single_select_container.dart';
import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Components/message_notification.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'showNewOrUpdateEventAgenda.dart'; import 'showNewOrUpdateEventAgenda.dart';
@ -94,12 +95,12 @@ class _AgendaConfigState extends State<AgendaConfig> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Text("Évènements", Text(AppLocalizations.of(context)!.agendaEventsLabel,
style: style:
TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
ElevatedButton.icon( ElevatedButton.icon(
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: const Text("Ajouter un évènement"), label: Text(AppLocalizations.of(context)!.addEvent),
onPressed: agendaDTO.id == null onPressed: agendaDTO.id == null
? null ? null
: () { : () {
@ -115,13 +116,13 @@ class _AgendaConfigState extends State<AgendaConfig> {
if (created != null && mounted) { if (created != null && mounted) {
setState(() => events.add(created)); setState(() => events.add(created));
showNotification(kSuccess, kWhite, showNotification(kSuccess, kWhite,
'Évènement créé avec succès', context, null); AppLocalizations.of(context)!.agendaEventCreatedSuccess, context, null);
} }
} catch (e) { } catch (e) {
showNotification( showNotification(
kError, kError,
kWhite, kWhite,
'Erreur lors de la création de l\'évènement', AppLocalizations.of(context)!.agendaEventCreateError,
context, context,
null); null);
rethrow; rethrow;
@ -145,8 +146,8 @@ class _AgendaConfigState extends State<AgendaConfig> {
height: 600, height: 600,
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
child: events.isEmpty child: events.isEmpty
? const Center( ? Center(
child: Text("Aucun évènement", child: Text(AppLocalizations.of(context)!.noEvents,
style: TextStyle(fontStyle: FontStyle.italic))) style: TextStyle(fontStyle: FontStyle.italic)))
: ListView.builder( : ListView.builder(
itemCount: events.length, itemCount: events.length,
@ -170,14 +171,14 @@ class _AgendaConfigState extends State<AgendaConfig> {
(event.label != null && event.label!.isNotEmpty) (event.label != null && event.label!.isNotEmpty)
? (event.label!.firstWhere( ? (event.label!.firstWhere(
(t) => t.language == 'FR', (t) => t.language == 'FR',
orElse: () => event.label![0])).value ?? "Évènement $index" orElse: () => event.label![0])).value ?? "${AppLocalizations.of(context)!.agendaEventFallback} $index"
: "Évènement $index", : "${AppLocalizations.of(context)!.agendaEventFallback} $index",
textStyle: const TextStyle(fontWeight: FontWeight.bold), textStyle: const TextStyle(fontWeight: FontWeight.bold),
), ),
subtitle: Padding( subtitle: Padding(
padding: const EdgeInsets.only(top: 4), padding: const EdgeInsets.only(top: 4),
child: child:
Text(event.address?.address ?? "Pas d'adresse"), Text(event.address?.address ?? AppLocalizations.of(context)!.noAddress),
), ),
trailing: Row( trailing: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -201,7 +202,7 @@ class _AgendaConfigState extends State<AgendaConfig> {
showNotification( showNotification(
kSuccess, kSuccess,
kWhite, kWhite,
'Évènement mis à jour avec succès', AppLocalizations.of(context)!.agendaEventUpdatedSuccess,
context, context,
null); null);
} }
@ -209,7 +210,7 @@ class _AgendaConfigState extends State<AgendaConfig> {
showNotification( showNotification(
kError, kError,
kWhite, kWhite,
'Erreur lors de la mise à jour de l\'évènement', AppLocalizations.of(context)!.agendaEventUpdateError,
context, context,
null); null);
rethrow; rethrow;
@ -232,7 +233,7 @@ class _AgendaConfigState extends State<AgendaConfig> {
showNotification( showNotification(
kSuccess, kSuccess,
kWhite, kWhite,
'Évènement supprimé avec succès', AppLocalizations.of(context)!.agendaEventDeletedSuccess,
context, context,
null); null);
} }
@ -240,7 +241,7 @@ class _AgendaConfigState extends State<AgendaConfig> {
showNotification( showNotification(
kError, kError,
kWhite, kWhite,
'Erreur lors de la suppression de l\'évènement', AppLocalizations.of(context)!.agendaEventDeleteError,
context, context,
null); null);
} }
@ -279,7 +280,7 @@ class _AgendaConfigState extends State<AgendaConfig> {
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
CheckInputContainer( CheckInputContainer(
label: "En ligne :", label: AppLocalizations.of(context)!.onlineLabel,
isChecked: agendaDTO.isOnlineAgenda ?? true, isChecked: agendaDTO.isOnlineAgenda ?? true,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
@ -289,7 +290,7 @@ class _AgendaConfigState extends State<AgendaConfig> {
}, },
), ),
CheckInputContainer( CheckInputContainer(
label: "Vue carte :", label: AppLocalizations.of(context)!.mapViewLabel,
isChecked: agendaDTO.agendaMapProvider != null, isChecked: agendaDTO.agendaMapProvider != null,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
@ -304,7 +305,7 @@ class _AgendaConfigState extends State<AgendaConfig> {
), ),
if (agendaDTO.agendaMapProvider != null) if (agendaDTO.agendaMapProvider != null)
SingleSelectContainer( SingleSelectContainer(
label: "Service carte :", label: AppLocalizations.of(context)!.mapServiceLabel,
color: Colors.black, color: Colors.black,
initialValue: mapProviderIn, initialValue: mapProviderIn,
inputValues: const ["Google", "MapBox"], inputValues: const ["Google", "MapBox"],
@ -324,9 +325,9 @@ class _AgendaConfigState extends State<AgendaConfig> {
), ),
if (agendaDTO.isOnlineAgenda == true) if (agendaDTO.isOnlineAgenda == true)
MultiStringInputContainer( MultiStringInputContainer(
label: "Fichiers json :", label: AppLocalizations.of(context)!.jsonFilesLabel,
resourceTypes: const [ResourceType.Json, ResourceType.JsonUrl], resourceTypes: const [ResourceType.Json, ResourceType.JsonUrl],
modalLabel: "JSON", modalLabel: AppLocalizations.of(context)!.jsonLabel,
color: kPrimaryColor, color: kPrimaryColor,
initialValue: agendaDTO.resourceIds ?? [], initialValue: agendaDTO.resourceIds ?? [],
isTitle: false, isTitle: false,

View File

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/multi_string_input_container.dart';
import 'package:manager_app/Components/string_input_container.dart'; import 'package:manager_app/Components/string_input_container.dart';
@ -51,7 +52,7 @@ void showNewOrUpdateEventAgenda(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
event == null ? "Nouvel Évènement" : "Modifier l'Évènement", event == null ? AppLocalizations.of(context)!.newAgendaEventTitle : AppLocalizations.of(context)!.editAgendaEventTitle,
style: TextStyle( style: TextStyle(
color: kPrimaryColor, color: kPrimaryColor,
fontSize: 20, fontSize: 20,
@ -70,7 +71,7 @@ void showNewOrUpdateEventAgenda(
width: halfWidth, width: halfWidth,
child: MultiStringInputContainer( child: MultiStringInputContainer(
label: "Titre :", label: "Titre :",
modalLabel: "Titre de l'évènement", modalLabel: AppLocalizations.of(context)!.eventTitleLabel,
initialValue: workingEvent.label ?? [], initialValue: workingEvent.label ?? [],
onGetResult: (val) => onGetResult: (val) =>
setState(() => workingEvent.label = val), setState(() => workingEvent.label = val),
@ -84,7 +85,7 @@ void showNewOrUpdateEventAgenda(
width: halfWidth, width: halfWidth,
child: MultiStringInputContainer( child: MultiStringInputContainer(
label: "Description :", label: "Description :",
modalLabel: "Description de l'évènement", modalLabel: AppLocalizations.of(context)!.eventDescriptionLabel,
initialValue: workingEvent.description ?? [], initialValue: workingEvent.description ?? [],
onGetResult: (val) => setState( onGetResult: (val) => setState(
() => workingEvent.description = val), () => workingEvent.description = val),
@ -104,7 +105,7 @@ void showNewOrUpdateEventAgenda(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Date de début :"), Text(AppLocalizations.of(context)!.startDateColonLabel),
SizedBox(height: 4), SizedBox(height: 4),
OutlinedButton.icon( OutlinedButton.icon(
icon: Icon(Icons.calendar_today, size: 16), icon: Icon(Icons.calendar_today, size: 16),
@ -172,7 +173,7 @@ void showNewOrUpdateEventAgenda(
SizedBox( SizedBox(
width: halfWidth, width: halfWidth,
child: ResourceInputContainer( child: ResourceInputContainer(
label: "Vidéo :", label: AppLocalizations.of(context)!.videoResourceLabel,
initialValue: workingEvent.videoResourceId, initialValue: workingEvent.videoResourceId,
inResourceTypes: const [ResourceType.Video, ResourceType.VideoUrl], inResourceTypes: const [ResourceType.Video, ResourceType.VideoUrl],
onChanged: (res) => setState(() { onChanged: (res) => setState(() {
@ -199,7 +200,7 @@ void showNewOrUpdateEventAgenda(
SizedBox( SizedBox(
width: halfWidth, width: halfWidth,
child: StringInputContainer( child: StringInputContainer(
label: "Lien vidéo direct :", label: AppLocalizations.of(context)!.directVideoLinkLabel,
initialValue: workingEvent.videoLink ?? "", initialValue: workingEvent.videoLink ?? "",
onChanged: (val) => setState(() => workingEvent.videoLink = val.isEmpty ? null : val), onChanged: (val) => setState(() => workingEvent.videoLink = val.isEmpty ? null : val),
), ),
@ -223,7 +224,7 @@ void showNewOrUpdateEventAgenda(
SizedBox( SizedBox(
width: thirdWidth, width: thirdWidth,
child: StringInputContainer( child: StringInputContainer(
label: "Téléphone :", label: AppLocalizations.of(context)!.phoneLabel,
initialValue: workingEvent.phone ?? "", initialValue: workingEvent.phone ?? "",
onChanged: (val) => onChanged: (val) =>
setState(() => workingEvent.phone = val), setState(() => workingEvent.phone = val),
@ -242,7 +243,7 @@ void showNewOrUpdateEventAgenda(
], ],
), ),
Divider(height: 24), Divider(height: 24),
Text("Localisation / Géométrie", Text(AppLocalizations.of(context)!.locationGeometryLabel,
style: TextStyle(fontWeight: FontWeight.bold)), style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(height: 8), SizedBox(height: 8),
StringInputContainer( StringInputContainer(

View File

@ -8,6 +8,7 @@ import 'package:manager_app/constants.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'dart:convert'; import 'dart:convert';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -79,7 +80,7 @@ class _ArticleConfigState extends State<ArticleConfig> {
Column( Column(
children: [ children: [
MultiStringInputContainer( MultiStringInputContainer(
label: "Contenu affiché :", label: AppLocalizations.of(context)!.displayedContentLabel,
modalLabel: "Contenu", modalLabel: "Contenu",
color: kPrimaryColor, color: kPrimaryColor,
isHTML: true, isHTML: true,

View File

@ -6,6 +6,7 @@ import 'package:intl/intl.dart';
import 'showNewOrUpdateProgrammeBlock.dart'; import 'showNewOrUpdateProgrammeBlock.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Components/message_notification.dart';
import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart';
@ -98,11 +99,11 @@ class _EventConfigState extends State<EventConfig> {
children: [ children: [
Expanded( Expanded(
child: ListTile( child: ListTile(
title: Text("Date de début"), title: Text(AppLocalizations.of(context)!.startDateLabel),
subtitle: Text(eventDTO.startDate != null subtitle: Text(eventDTO.startDate != null
? DateFormat('dd/MM/yyyy HH:mm') ? DateFormat('dd/MM/yyyy HH:mm')
.format(eventDTO.startDate!.toLocal()) .format(eventDTO.startDate!.toLocal())
: "Non définie"), : AppLocalizations.of(context)!.notDefined),
trailing: Icon(Icons.calendar_today), trailing: Icon(Icons.calendar_today),
onTap: () async { onTap: () async {
DateTime initialDate = eventDTO.startDate?.toLocal() ?? DateTime.now(); DateTime initialDate = eventDTO.startDate?.toLocal() ?? DateTime.now();
@ -158,10 +159,10 @@ class _EventConfigState extends State<EventConfig> {
), ),
Expanded( Expanded(
child: ListTile( child: ListTile(
title: Text("Date de fin"), title: Text(AppLocalizations.of(context)!.endDateLabel),
subtitle: Text(eventDTO.endDate != null subtitle: Text(eventDTO.endDate != null
? DateFormat('dd/MM/yyyy HH:mm').format(eventDTO.endDate!.toLocal()) ? DateFormat('dd/MM/yyyy HH:mm').format(eventDTO.endDate!.toLocal())
: "Non définie"), : AppLocalizations.of(context)!.notDefined),
trailing: Icon(Icons.calendar_today), trailing: Icon(Icons.calendar_today),
onTap: () async { onTap: () async {
DateTime initialDate = eventDTO.endDate?.toLocal() ?? DateTime initialDate = eventDTO.endDate?.toLocal() ??
@ -231,11 +232,11 @@ class _EventConfigState extends State<EventConfig> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text("Programme", Text(AppLocalizations.of(context)!.programmeLabel,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
ElevatedButton.icon( ElevatedButton.icon(
icon: Icon(Icons.add), icon: Icon(Icons.add),
label: Text("Ajouter un bloc"), label: Text(AppLocalizations.of(context)!.addBlock),
onPressed: () { onPressed: () {
final appContext = final appContext =
Provider.of<AppContext>(context, listen: false); Provider.of<AppContext>(context, listen: false);
@ -277,7 +278,7 @@ class _EventConfigState extends State<EventConfig> {
showNotification( showNotification(
kSuccess, kSuccess,
kWhite, kWhite,
'Bloc de programme créé avec succès', AppLocalizations.of(context)!.programmeBlockCreatedSuccess,
context, context,
null); null);
} }
@ -285,7 +286,7 @@ class _EventConfigState extends State<EventConfig> {
showNotification( showNotification(
kError, kError,
kWhite, kWhite,
'Erreur lors de la création du bloc', AppLocalizations.of(context)!.programmeBlockCreateError,
context, context,
null); null);
} }
@ -302,7 +303,7 @@ class _EventConfigState extends State<EventConfig> {
height: 600, height: 600,
child: (eventDTO.programme == null || eventDTO.programme!.isEmpty) child: (eventDTO.programme == null || eventDTO.programme!.isEmpty)
? Center( ? Center(
child: Text("Aucun bloc de programme défini", child: Text(AppLocalizations.of(context)!.noBlocks,
style: TextStyle(fontStyle: FontStyle.italic))) style: TextStyle(fontStyle: FontStyle.italic)))
: ListView.builder( : ListView.builder(
itemCount: eventDTO.programme!.length, itemCount: eventDTO.programme!.length,
@ -318,9 +319,9 @@ class _EventConfigState extends State<EventConfig> {
(t) => t.language == 'FR', (t) => t.language == 'FR',
orElse: () => block.title![0]) orElse: () => block.title![0])
.value ?? .value ??
"Bloc ${index + 1}", "${AppLocalizations.of(context)!.blockFallback} ${index + 1}",
) )
: Text("Bloc ${index + 1}"), : Text("${AppLocalizations.of(context)!.blockFallback} ${index + 1}"),
subtitle: Text( subtitle: Text(
"${block.startTime != null ? DateFormat('HH:mm').format(block.startTime!.toLocal()) : '??'} - ${block.endTime != null ? DateFormat('HH:mm').format(block.endTime!.toLocal()) : '??'}"), "${block.startTime != null ? DateFormat('HH:mm').format(block.startTime!.toLocal()) : '??'} - ${block.endTime != null ? DateFormat('HH:mm').format(block.endTime!.toLocal()) : '??'}"),
trailing: Row( trailing: Row(
@ -368,7 +369,7 @@ class _EventConfigState extends State<EventConfig> {
showNotification( showNotification(
kSuccess, kSuccess,
kWhite, kWhite,
'Bloc mis à jour avec succès', AppLocalizations.of(context)!.programmeBlockUpdatedSuccess,
context, context,
null); null);
} }
@ -376,7 +377,7 @@ class _EventConfigState extends State<EventConfig> {
showNotification( showNotification(
kError, kError,
kWhite, kWhite,
'Erreur lors de la mise à jour du bloc', AppLocalizations.of(context)!.programmeBlockUpdateError,
context, context,
null); null);
} }
@ -407,14 +408,14 @@ class _EventConfigState extends State<EventConfig> {
showNotification( showNotification(
kSuccess, kSuccess,
kWhite, kWhite,
'Bloc supprimé avec succès', AppLocalizations.of(context)!.programmeBlockDeletedSuccess,
context, context,
null); null);
} catch (e) { } catch (e) {
showNotification( showNotification(
kError, kError,
kWhite, kWhite,
'Erreur lors de la suppression du bloc', AppLocalizations.of(context)!.programmeBlockDeleteError,
context, context,
null); null);
} }
@ -457,10 +458,10 @@ class _EventConfigState extends State<EventConfig> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text("Carte de base", Text(AppLocalizations.of(context)!.baseMapLabel,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
DropDownInputContainer( DropDownInputContainer(
label: "Carte :", label: AppLocalizations.of(context)!.mapLabel,
values: mapItems, values: mapItems,
initialValue: currentValue, initialValue: currentValue,
onChange: (val) { onChange: (val) {
@ -493,11 +494,11 @@ class _EventConfigState extends State<EventConfig> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Text("Annotations globales", Text(AppLocalizations.of(context)!.globalAnnotationsLabel,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
ElevatedButton.icon( ElevatedButton.icon(
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: const Text("Ajouter une annotation"), label: Text(AppLocalizations.of(context)!.addAnnotation),
onPressed: eventDTO.id == null onPressed: eventDTO.id == null
? null ? null
: () { : () {
@ -518,11 +519,11 @@ class _EventConfigState extends State<EventConfig> {
]; ];
}); });
showNotification(kSuccess, kWhite, showNotification(kSuccess, kWhite,
'Annotation créée avec succès', context, null); AppLocalizations.of(context)!.annotationCreatedSuccess, context, null);
} }
} catch (e) { } catch (e) {
showNotification(kError, kWhite, showNotification(kError, kWhite,
'Erreur lors de la création de l\'annotation', AppLocalizations.of(context)!.annotationCreateError,
context, null); context, null);
} }
}); });
@ -540,9 +541,9 @@ class _EventConfigState extends State<EventConfig> {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
if (annotations.isEmpty) if (annotations.isEmpty)
const Padding( Padding(
padding: EdgeInsets.only(top: 8), padding: EdgeInsets.only(top: 8),
child: Text("Aucune annotation globale", child: Text(AppLocalizations.of(context)!.noAnnotations,
style: TextStyle(fontStyle: FontStyle.italic)), style: TextStyle(fontStyle: FontStyle.italic)),
) )
else else
@ -552,8 +553,8 @@ class _EventConfigState extends State<EventConfig> {
final labelText = ann.label != null && ann.label!.isNotEmpty final labelText = ann.label != null && ann.label!.isNotEmpty
? (ann.label!.firstWhere((t) => t.language == 'FR', ? (ann.label!.firstWhere((t) => t.language == 'FR',
orElse: () => ann.label![0]) orElse: () => ann.label![0])
.value ?? 'Annotation ${idx + 1}') .value ?? '${AppLocalizations.of(context)!.annotationFallback} ${idx + 1}')
: 'Annotation ${idx + 1}'; : '${AppLocalizations.of(context)!.annotationFallback} ${idx + 1}';
return Card( return Card(
margin: margin:
const EdgeInsets.symmetric(horizontal: 0, vertical: 4), const EdgeInsets.symmetric(horizontal: 0, vertical: 4),
@ -578,11 +579,11 @@ class _EventConfigState extends State<EventConfig> {
eventDTO.globalMapAnnotations = updated; eventDTO.globalMapAnnotations = updated;
}); });
showNotification(kSuccess, kWhite, showNotification(kSuccess, kWhite,
'Annotation supprimée', context, null); AppLocalizations.of(context)!.annotationDeletedSuccess, context, null);
} }
} catch (e) { } catch (e) {
showNotification(kError, kWhite, showNotification(kError, kWhite,
'Erreur lors de la suppression', context, null); AppLocalizations.of(context)!.annotationDeleteError, context, null);
} }
}, },
), ),

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/multi_string_input_container.dart';
import 'package:manager_app/Components/string_input_container.dart'; import 'package:manager_app/Components/string_input_container.dart';
@ -102,7 +103,7 @@ void showNewOrUpdateMapAnnotation(
SizedBox( SizedBox(
width: halfWidth, width: halfWidth,
child: DropDownInputContainer( child: DropDownInputContainer(
label: "Géométrie :", label: AppLocalizations.of(context)!.geometryLabel,
values: geometryTypeLabels, values: geometryTypeLabels,
initialValue: geometryTypeToLabel(working.geometryType), initialValue: geometryTypeToLabel(working.geometryType),
onChange: (val) => setState(() => onChange: (val) => setState(() =>
@ -127,7 +128,7 @@ void showNewOrUpdateMapAnnotation(
SizedBox( SizedBox(
width: halfWidth, width: halfWidth,
child: StringInputContainer( child: StringInputContainer(
label: "Icône (material) :", label: AppLocalizations.of(context)!.materialIconLabel,
initialValue: working.icon ?? '', initialValue: working.icon ?? '',
onChanged: (val) => onChanged: (val) =>
setState(() => working.icon = val.isEmpty ? null : val), setState(() => working.icon = val.isEmpty ? null : val),

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Components/confirmation_dialog.dart'; import 'package:manager_app/Components/confirmation_dialog.dart';
import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/multi_string_input_container.dart';
@ -112,12 +113,12 @@ void showNewOrUpdateProgrammeBlock(
child: ListTile( child: ListTile(
leading: Icon(Icons.schedule, leading: Icon(Icons.schedule,
color: kPrimaryColor), color: kPrimaryColor),
title: Text("Heure de début"), title: Text(AppLocalizations.of(context)!.startTimeLabel),
subtitle: Text( subtitle: Text(
workingBlock.startTime != null workingBlock.startTime != null
? DateFormat('HH:mm') ? DateFormat('HH:mm')
.format(workingBlock.startTime!.toLocal()) .format(workingBlock.startTime!.toLocal())
: "Non définie", : AppLocalizations.of(context)!.notDefined,
style: TextStyle( style: TextStyle(
color: kPrimaryColor, color: kPrimaryColor,
fontWeight: FontWeight.bold), fontWeight: FontWeight.bold),
@ -161,12 +162,12 @@ void showNewOrUpdateProgrammeBlock(
child: ListTile( child: ListTile(
leading: Icon(Icons.schedule_outlined, leading: Icon(Icons.schedule_outlined,
color: kPrimaryColor), color: kPrimaryColor),
title: Text("Heure de fin"), title: Text(AppLocalizations.of(context)!.endTimeLabel),
subtitle: Text( subtitle: Text(
workingBlock.endTime != null workingBlock.endTime != null
? DateFormat('HH:mm') ? DateFormat('HH:mm')
.format(workingBlock.endTime!.toLocal()) .format(workingBlock.endTime!.toLocal())
: "Non définie", : AppLocalizations.of(context)!.notDefined,
style: TextStyle( style: TextStyle(
color: kPrimaryColor, color: kPrimaryColor,
fontWeight: FontWeight.bold), fontWeight: FontWeight.bold),
@ -212,7 +213,7 @@ void showNewOrUpdateProgrammeBlock(
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text("Annotations", Text(AppLocalizations.of(context)!.annotationsLabel,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 15)), fontSize: 15)),
@ -242,7 +243,7 @@ void showNewOrUpdateProgrammeBlock(
padding: padding:
const EdgeInsets.symmetric(vertical: 8), const EdgeInsets.symmetric(vertical: 8),
child: Text( child: Text(
"Aucune annotation configurée.", AppLocalizations.of(context)!.noAnnotationConfigured,
style: TextStyle( style: TextStyle(
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
color: Colors.grey[600]), color: Colors.grey[600]),

View File

@ -6,6 +6,7 @@ import 'package:manager_app/Components/resource_input_container.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
class GameConfig extends StatefulWidget { class GameConfig extends StatefulWidget {
final String? color; final String? color;
@ -148,8 +149,8 @@ class _GameConfigState extends State<GameConfig> {
), ),
Flexible( Flexible(
child: MultiStringInputAndResourceContainer( child: MultiStringInputAndResourceContainer(
label: "Message départ :", label: AppLocalizations.of(context)!.startMessageLabel,
modalLabel: "Message départ", modalLabel: AppLocalizations.of(context)!.startMessageModalLabel,
fontSize: 20, fontSize: 20,
color: kPrimaryColor, color: kPrimaryColor,
initialValue: gameDTO.messageDebut ?? [], initialValue: gameDTO.messageDebut ?? [],

View File

@ -4,6 +4,7 @@ import 'package:manager_api_new/api.dart';
import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/category_list.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/category_list.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
class CategoryInputContainer extends StatefulWidget { class CategoryInputContainer extends StatefulWidget {
final Color color; final Color color;
@ -51,7 +52,7 @@ class _CategoryInputContainerState extends State<CategoryInputContainer> {
child: InkWell( child: InkWell(
onTap: () { onTap: () {
List<CategorieDTO> newValues = <CategorieDTO>[]; List<CategorieDTO> newValues = <CategorieDTO>[];
showCreateOrUpdateCategories("Catégories", middleCategories, newValues, (value) { showCreateOrUpdateCategories(AppLocalizations.of(context)!.categoriesTitle, middleCategories, newValues, (value) {
setState(() { setState(() {
widget.onChanged(value); widget.onChanged(value);
middleCategories = value; middleCategories = value;

View File

@ -4,6 +4,7 @@ import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/new_update_categorie.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/new_update_categorie.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -126,7 +127,7 @@ class _CategoryListState extends State<CategoryList> {
onTap: () async { onTap: () async {
CategorieDTO newCategory = CategorieDTO(order: null); CategorieDTO newCategory = CategorieDTO(order: null);
var result = await showNewOrUpdateCategory(newCategory, appContext, context, "Création catégorie", currentIndex); var result = await showNewOrUpdateCategory(newCategory, appContext, context, AppLocalizations.of(context)!.createCategoryTitle, currentIndex);
if (result != null) if (result != null)
{ {
setState(() { setState(() {
@ -203,7 +204,7 @@ class _CategoryListState extends State<CategoryList> {
message: "Modifier", message: "Modifier",
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
var result = await showNewOrUpdateCategory(category, appContext, context, "Modification catégorie", currentIndex); var result = await showNewOrUpdateCategory(category, appContext, context, AppLocalizations.of(context)!.editCategoryTitle, currentIndex);
if (result != null) if (result != null)
{ {
setState(() { setState(() {

View File

@ -3,6 +3,7 @@ import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/listVi
import 'package:manager_app/Screens/Resources/select_resource_modal.dart'; import 'package:manager_app/Screens/Resources/select_resource_modal.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -89,7 +90,7 @@ class _GeoPointContentListState extends State<GeoPointContentList> {
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
var result = await showSelectResourceModal( var result = await showSelectResourceModal(
"Sélectionner une ressource", AppLocalizations.of(context)!.selectResource,
1, 1,
[ResourceType.Image, ResourceType.ImageUrl, ResourceType.Video, ResourceType.VideoUrl, ResourceType.Audio], [ResourceType.Image, ResourceType.ImageUrl, ResourceType.Video, ResourceType.VideoUrl, ResourceType.Audio],
context, context,

View File

@ -20,6 +20,7 @@ import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/showNe
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/client.dart'; import 'package:manager_app/client.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart';
@ -137,8 +138,8 @@ class _MapConfigState extends State<MapConfig> {
unselectedLabelColor: Colors.grey, unselectedLabelColor: Colors.grey,
indicatorColor: kPrimaryColor, indicatorColor: kPrimaryColor,
tabs: [ tabs: [
Tab(icon: Icon(Icons.map), text: "Points d'Intérêt"), Tab(icon: Icon(Icons.map), text: AppLocalizations.of(context)!.pointsOfInterestLabel),
Tab(icon: Icon(Icons.route), text: "Parcours"), Tab(icon: Icon(Icons.route), text: AppLocalizations.of(context)!.pathsLabel),
], ],
), ),
Container( Container(
@ -293,7 +294,7 @@ class _MapConfigState extends State<MapConfig> {
top: 10, top: 10,
left: 10, left: 10,
child: Text( child: Text(
"Points géographiques", AppLocalizations.of(context)!.geopointsLabel,
style: TextStyle(fontSize: 15), style: TextStyle(fontSize: 15),
), ),
), ),
@ -351,7 +352,7 @@ class _MapConfigState extends State<MapConfig> {
constraints: constraints:
BoxConstraints(minHeight: 85), BoxConstraints(minHeight: 85),
child: StringInputContainer( child: StringInputContainer(
label: "Recherche:", label: AppLocalizations.of(context)!.searchLabel,
isSmall: true, isSmall: true,
fontSize: 15, fontSize: 15,
fontSizeText: 15, fontSizeText: 15,
@ -379,7 +380,7 @@ class _MapConfigState extends State<MapConfig> {
showNotification( showNotification(
kSuccess, kSuccess,
kWhite, kWhite,
'Le point géographique a été créé avec succès', AppLocalizations.of(context)!.geopointCreatedSuccess,
context, context,
null); null);
setState(() { setState(() {
@ -390,7 +391,7 @@ class _MapConfigState extends State<MapConfig> {
showNotification( showNotification(
kError, kError,
kWhite, kWhite,
'Une erreur est survenue lors de la création du point géographique', AppLocalizations.of(context)!.geopointCreateError,
context, context,
null); null);
} }
@ -428,7 +429,7 @@ class _MapConfigState extends State<MapConfig> {
} else { } else {
return Center( return Center(
child: Text( child: Text(
"Une erreur est survenue lors de la récupération des points géographiques"), AppLocalizations.of(context)!.geopointsLoadError),
); );
} }
} }
@ -507,7 +508,7 @@ class _MapConfigState extends State<MapConfig> {
showNotification( showNotification(
kSuccess, kSuccess,
kWhite, kWhite,
'Le point géographique a été mis à jour avec succès', AppLocalizations.of(context)!.geopointUpdatedSuccess,
context, context,
null); null);
setState(() { setState(() {
@ -518,7 +519,7 @@ class _MapConfigState extends State<MapConfig> {
showNotification( showNotification(
kError, kError,
kWhite, kWhite,
'Une erreur est survenue lors de la mise à jour du point géographique', AppLocalizations.of(context)!.geopointUpdateError,
context, context,
null); null);
} }
@ -547,7 +548,7 @@ class _MapConfigState extends State<MapConfig> {
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
showConfirmationDialog( showConfirmationDialog(
"Êtes-vous sûr de vouloir supprimer ce point géographique ?", AppLocalizations.of(context)!.geopointDeleteConfirm,
() {}, () async { () {}, () async {
try { try {
var pointToRemove = pointsToShow[index]; var pointToRemove = pointsToShow[index];
@ -558,7 +559,7 @@ class _MapConfigState extends State<MapConfig> {
showNotification( showNotification(
kSuccess, kSuccess,
kWhite, kWhite,
'Le point géographique a été supprimé avec succès', AppLocalizations.of(context)!.geopointDeletedSuccess,
context, context,
null); null);
// refresh UI // refresh UI
@ -569,7 +570,7 @@ class _MapConfigState extends State<MapConfig> {
showNotification( showNotification(
kError, kError,
kWhite, kWhite,
'Une erreur est survenue lors de la suppression du point géographique', AppLocalizations.of(context)!.geopointDeleteError,
context, context,
null); null);
} }
@ -609,7 +610,7 @@ class _MapConfigState extends State<MapConfig> {
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
SingleSelectContainer( SingleSelectContainer(
label: "Service :", label: AppLocalizations.of(context)!.serviceLabel,
color: Colors.black, color: Colors.black,
initialValue: mapProviderIn, initialValue: mapProviderIn,
inputValues: map_providers, inputValues: map_providers,
@ -625,7 +626,7 @@ class _MapConfigState extends State<MapConfig> {
widget.onChanged(mapDTO); widget.onChanged(mapDTO);
}), }),
GeolocInputContainer( GeolocInputContainer(
label: "Point de centrage :", label: AppLocalizations.of(context)!.centerPointLabel,
initialValue: mapDTO.centerLatitude != null && initialValue: mapDTO.centerLatitude != null &&
mapDTO.centerLongitude != null mapDTO.centerLongitude != null
? LatLong(double.parse(mapDTO.centerLatitude!), ? LatLong(double.parse(mapDTO.centerLatitude!),
@ -642,7 +643,7 @@ class _MapConfigState extends State<MapConfig> {
}, },
isSmall: true), isSmall: true),
ResourceInputContainer( ResourceInputContainer(
label: "Icône :", label: AppLocalizations.of(context)!.iconLabel,
initialValue: mapDTO.iconResourceId, initialValue: mapDTO.iconResourceId,
color: kPrimaryColor, color: kPrimaryColor,
imageFit: BoxFit.contain, imageFit: BoxFit.contain,
@ -664,7 +665,7 @@ class _MapConfigState extends State<MapConfig> {
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
CheckInputContainer( CheckInputContainer(
label: "Vue liste :", label: AppLocalizations.of(context)!.listViewLabel,
isChecked: mapDTO.isListViewEnabled ?? false, isChecked: mapDTO.isListViewEnabled ?? false,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
@ -675,7 +676,7 @@ class _MapConfigState extends State<MapConfig> {
), ),
if (mapDTO.mapProvider == MapProvider.Google) if (mapDTO.mapProvider == MapProvider.Google)
DropDownInputContainer( DropDownInputContainer(
label: "Type :", label: AppLocalizations.of(context)!.typeLabel,
values: map_types, values: map_types,
initialValue: mapType, initialValue: mapType,
onChange: (String? value) { onChange: (String? value) {
@ -685,7 +686,7 @@ class _MapConfigState extends State<MapConfig> {
), ),
if (mapDTO.mapProvider == MapProvider.MapBox) if (mapDTO.mapProvider == MapProvider.MapBox)
DropDownInputContainer( DropDownInputContainer(
label: "Type :", label: AppLocalizations.of(context)!.typeLabel,
values: map_types_mapBox, values: map_types_mapBox,
initialValue: mapTypeMapBox, initialValue: mapTypeMapBox,
onChange: (String? value) { onChange: (String? value) {
@ -694,7 +695,7 @@ class _MapConfigState extends State<MapConfig> {
}, },
), ),
SliderInputContainer( SliderInputContainer(
label: "Zoom :", label: AppLocalizations.of(context)!.zoomLabel,
initialValue: initialValue:
mapDTO.zoom != null ? mapDTO.zoom!.toDouble() : 18, mapDTO.zoom != null ? mapDTO.zoom!.toDouble() : 18,
color: kPrimaryColor, color: kPrimaryColor,
@ -708,7 +709,7 @@ class _MapConfigState extends State<MapConfig> {
Container( Container(
height: 70, height: 70,
child: CategoryInputContainer( child: CategoryInputContainer(
label: "Catégories :", label: AppLocalizations.of(context)!.categoriesLabel,
initialValue: initialValue:
mapDTO.categories != null ? mapDTO.categories! : [], mapDTO.categories != null ? mapDTO.categories! : [],
color: kPrimaryColor, color: kPrimaryColor,

View File

@ -7,6 +7,7 @@ import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
Future<CategorieDTO?> showNewOrUpdateCategory(CategorieDTO? inputCategorieDTO, AppContext appContext, BuildContext context, String text, int currentIndex) async { Future<CategorieDTO?> showNewOrUpdateCategory(CategorieDTO? inputCategorieDTO, AppContext appContext, BuildContext context, String text, int currentIndex) async {
@ -60,7 +61,7 @@ Future<CategorieDTO?> showNewOrUpdateCategory(CategorieDTO? inputCategorieDTO, A
height: size.height * 0.2, height: size.height * 0.2,
constraints: BoxConstraints(minHeight: 50, maxHeight: 80), constraints: BoxConstraints(minHeight: 50, maxHeight: 80),
child: MultiStringInputContainer( child: MultiStringInputContainer(
label: "Nom affiché :", label: AppLocalizations.of(context)!.displayedNameLabel,
modalLabel: text, modalLabel: text,
fontSize: 20, fontSize: 20,
color: kPrimaryColor, color: kPrimaryColor,
@ -79,7 +80,7 @@ Future<CategorieDTO?> showNewOrUpdateCategory(CategorieDTO? inputCategorieDTO, A
height: size.height * 0.2, height: size.height * 0.2,
constraints: BoxConstraints(minHeight: 50, maxHeight: 80), constraints: BoxConstraints(minHeight: 50, maxHeight: 80),
child: ResourceInputContainer( child: ResourceInputContainer(
label: "Icône catégorie :", label: AppLocalizations.of(context)!.categoryIconLabel,
initialValue: categorieDTO.resourceDTO?.id, initialValue: categorieDTO.resourceDTO?.id,
color: kPrimaryColor, color: kPrimaryColor,
onChanged: (ResourceDTO resource) { onChanged: (ResourceDTO resource) {
@ -124,7 +125,7 @@ Future<CategorieDTO?> showNewOrUpdateCategory(CategorieDTO? inputCategorieDTO, A
width: inputCategorieDTO != null ? 220: 150, width: inputCategorieDTO != null ? 220: 150,
height: 70, height: 70,
child: RoundedButton( child: RoundedButton(
text: inputCategorieDTO != null ? "Sauvegarder" : "Créer", text: inputCategorieDTO != null ? AppLocalizations.of(context)!.save : AppLocalizations.of(context)!.create,
icon: Icons.check, icon: Icons.check,
color: kPrimaryColor, color: kPrimaryColor,
textColor: kWhite, textColor: kWhite,

View File

@ -9,6 +9,7 @@ import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/geopoint_image_list.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/geopoint_image_list.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO, void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO,
@ -67,7 +68,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO,
children: [ children: [
// Titre du dialog // Titre du dialog
Text( Text(
"Point géographique / Zone", AppLocalizations.of(context)!.geopointZoneTitle,
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@ -82,7 +83,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO,
children: [ children: [
// Géométrie // Géométrie
GeometryInputContainer( GeometryInputContainer(
label: "Géométrie (Point/Ligne/Zone) :", label: AppLocalizations.of(context)!.geometryTypeLabel,
initialGeometry: geoPointDTO.geometry, initialGeometry: geoPointDTO.geometry,
initialColor: geoPointDTO.polyColor, initialColor: geoPointDTO.polyColor,
onSave: (geometry, color) { onSave: (geometry, color) {
@ -176,7 +177,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO,
width: thirdWidth, width: thirdWidth,
child: MultiStringInputContainer( child: MultiStringInputContainer(
label: "Tel :", label: "Tel :",
modalLabel: "Téléphone", modalLabel: AppLocalizations.of(context)!.phoneModalLabel,
fontSize: 16, fontSize: 16,
isHTML: true, isHTML: true,
color: kPrimaryColor, color: kPrimaryColor,
@ -255,7 +256,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO,
SizedBox( SizedBox(
width: thirdWidth, width: thirdWidth,
child: DropDownInputContainerCategories( child: DropDownInputContainerCategories(
label: "Catégorie :", label: AppLocalizations.of(context)!.categoryLabel,
categories: mapDTO.categories!, categories: mapDTO.categories!,
initialValue: geoPointDTO.categorieId, initialValue: geoPointDTO.categorieId,
onChange: (CategorieDTO? value) { onChange: (CategorieDTO? value) {
@ -326,7 +327,7 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO,
height: 46, height: 46,
child: RoundedButton( child: RoundedButton(
text: text:
geoPointDTO.id != null ? "Sauvegarder" : "Créer", geoPointDTO.id != null ? AppLocalizations.of(context)!.save : AppLocalizations.of(context)!.create,
icon: Icons.check, icon: Icons.check,
color: kPrimaryColor, color: kPrimaryColor,
textColor: kWhite, textColor: kWhite,

View File

@ -1,6 +1,7 @@
import 'package:auto_size_text/auto_size_text.dart'; import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:manager_app/Components/confirmation_dialog.dart'; import 'package:manager_app/Components/confirmation_dialog.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Components/fetch_section_icon.dart'; import 'package:manager_app/Components/fetch_section_icon.dart';
import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Components/message_notification.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
@ -63,17 +64,18 @@ class _ListViewCardSubSection extends State<ListViewCardSubSection> {
showEditSubSection( showEditSubSection(
widget.listItems[widget.index], widget.listItems[widget.index],
(Object value) async { (Object value) async {
final l = AppLocalizations.of(context)!;
try { try {
var test = updateSectionDetail(widget.listItems[widget.index], value); var test = updateSectionDetail(widget.listItems[widget.index], value);
// update sub section // update sub section
await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionApi!.sectionUpdate(test); await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionApi!.sectionUpdate(test);
showNotification(kSuccess, kWhite, "La sous section a été mis à jour avec succès", context, null); showNotification(kSuccess, kWhite, l.subSectionUpdatedSuccess, context, null);
setState(() { setState(() {
//widget.listItems[widget.index] = value; //widget.listItems[widget.index] = value;
widget.onChanged(widget.listItems); // For resfreh ui widget.onChanged(widget.listItems); // For resfreh ui
}); });
} catch(e) { } catch(e) {
showNotification(kError, kWhite, "Une erreur est survenue lors de la mise à jour de la sous section", context, null); showNotification(kError, kWhite, l.subSectionUpdateError, context, null);
} }
}, },
widget.appContext, widget.appContext,
@ -92,19 +94,20 @@ class _ListViewCardSubSection extends State<ListViewCardSubSection> {
), ),
InkWell( InkWell(
onTap: () async { onTap: () async {
final l = AppLocalizations.of(context)!;
showConfirmationDialog( showConfirmationDialog(
"Êtes-vous sûr de vouloir supprimer cette sous section ?", l.sectionDeleteConfirm,
() {}, () {},
() async { () async {
try { try {
var sectionToDelete = widget.listItems[widget.index]; var sectionToDelete = widget.listItems[widget.index];
ManagerAppContext managerAppContext = appContext.getContext(); ManagerAppContext managerAppContext = appContext.getContext();
await managerAppContext.clientAPI!.sectionApi!.sectionDelete(sectionToDelete.id!); await managerAppContext.clientAPI!.sectionApi!.sectionDelete(sectionToDelete.id!);
showNotification(kSuccess, kWhite, 'La sous section a été supprimée avec succès', context, null); showNotification(kSuccess, kWhite, l.subSectionDeletedSuccess, context, null);
// refresh UI // refresh UI
widget.onChanged(widget.listItems); widget.onChanged(widget.listItems);
} catch(e) { } catch(e) {
showNotification(kError, kWhite, 'Une erreur est survenue lors de la suppression de la sous section', context, null); showNotification(kError, kWhite, l.subSectionDeleteError, context, null);
} }
}, },
context context

View File

@ -2,6 +2,7 @@ import 'dart:js_interop';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/Components/common_loader.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Components/message_notification.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/Screens/Configurations/Section/SubSection/Menu/listView_card_subSection.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Menu/listView_card_subSection.dart';
@ -63,13 +64,13 @@ class _MenuConfigState extends State<MenuConfig> {
item.order = newIndex; item.order = newIndex;
await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionApi!.sectionUpdate(item); await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionApi!.sectionUpdate(item);
showNotification(kSuccess, kWhite, "L'ordre des sous sections a été mis à jour avec succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.subSectionOrderUpdatedSuccess, context, null);
setState(() { setState(() {
// refresh ui // refresh ui
print("Refresh UI"); print("Refresh UI");
}); });
} catch(e) { } catch(e) {
showNotification(kError, kWhite, "Une erreur est survenue lors de la mise à jour de l'ordre des sous sections", context, null); showNotification(kError, kWhite, AppLocalizations.of(context)!.subSectionOrderUpdateError, context, null);
} }
/*setState( /*setState(
@ -143,7 +144,7 @@ class _MenuConfigState extends State<MenuConfig> {
); );
} else { } else {
return Center(child: Text( return Center(child: Text(
"Une erreur est survenue lors de la récupération des sous sections")); AppLocalizations.of(context)!.errorOccurred));
} }
} }
} }
@ -168,14 +169,14 @@ class _MenuConfigState extends State<MenuConfig> {
newSubsection.isBeacon = false; newSubsection.isBeacon = false;
newSubsection.isActive = true; newSubsection.isActive = true;
await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionApi!.sectionCreate(newSubsection); await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionApi!.sectionCreate(newSubsection);
showNotification(kSuccess, kWhite, 'La sous section a été créée avec succès', context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.subSectionCreatedSuccess, context, null);
setState(() { setState(() {
// refresh ui // refresh ui
print("Refresh UI"); print("Refresh UI");
}); });
} }
} catch(e) { } catch(e) {
showNotification(kError, kWhite, 'Une erreur est survenue lors de la création de la sous section', context, null); showNotification(kError, kWhite, AppLocalizations.of(context)!.subSectionCreateError, context, null);
} }
}, },

View File

@ -15,6 +15,7 @@ import 'package:manager_app/Screens/Configurations/Section/SubSection/Weather/we
import 'package:manager_app/Screens/Configurations/Section/SubSection/WebOrVideo/web_config.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/WebOrVideo/web_config.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'menu_config.dart'; import 'menu_config.dart';
@ -77,7 +78,7 @@ void showEditSubSection(SectionDTO subSectionDTO, Function getResult, AppContext
Container( Container(
constraints: BoxConstraints(minHeight: 50, maxHeight: 80), constraints: BoxConstraints(minHeight: 50, maxHeight: 80),
child: MultiStringInputContainer( child: MultiStringInputContainer(
label: "Titre affiché:", label: AppLocalizations.of(context)!.displayTitleLabel,
modalLabel: "Titre", modalLabel: "Titre",
fontSize: 20, fontSize: 20,
isHTML: true, isHTML: true,
@ -95,7 +96,7 @@ void showEditSubSection(SectionDTO subSectionDTO, Function getResult, AppContext
Container( Container(
constraints: BoxConstraints(minHeight: 50, maxHeight: 80), constraints: BoxConstraints(minHeight: 50, maxHeight: 80),
child: MultiStringInputContainer( child: MultiStringInputContainer(
label: "Description affichée:", label: AppLocalizations.of(context)!.displayedDescriptionLabel,
modalLabel: "Description", modalLabel: "Description",
fontSize: 20, fontSize: 20,
isHTML: true, isHTML: true,
@ -129,7 +130,7 @@ void showEditSubSection(SectionDTO subSectionDTO, Function getResult, AppContext
appContext, appContext,
(updatedData) { (updatedData) {
updatedRawSubSectionData = updatedData; updatedRawSubSectionData = updatedData;
}), }, context),
), ),
), ),
], ],
@ -183,7 +184,7 @@ void showEditSubSection(SectionDTO subSectionDTO, Function getResult, AppContext
); );
} }
getSpecificData(SectionDTO subSectionDTO, Object? rawSectionData, Object sectionDetailDTO, AppContext appContext, Function(Object) onChanged) { getSpecificData(SectionDTO subSectionDTO, Object? rawSectionData, Object sectionDetailDTO, AppContext appContext, Function(Object) onChanged, [BuildContext? context]) {
switch(subSectionDTO.type) { switch(subSectionDTO.type) {
case SectionType.Map: case SectionType.Map:
MapDTO mapDTO = MapDTO.fromJson(rawSectionData)!; MapDTO mapDTO = MapDTO.fromJson(rawSectionData)!;
@ -207,7 +208,7 @@ getSpecificData(SectionDTO subSectionDTO, Object? rawSectionData, Object section
VideoDTO videoDTO = VideoDTO.fromJson(rawSectionData)!; VideoDTO videoDTO = VideoDTO.fromJson(rawSectionData)!;
sectionDetailDTO = videoDTO; sectionDetailDTO = videoDTO;
return VideoConfig( return VideoConfig(
label: "Url de la vidéo:", label: context != null ? AppLocalizations.of(context)!.videoUrlLabel : "Url de la vidéo:",
initialValue: videoDTO, initialValue: videoDTO,
onChanged: (VideoDTO updatedWebDTO) { onChanged: (VideoDTO updatedWebDTO) {
onChanged(updatedWebDTO); onChanged(updatedWebDTO);

View File

@ -8,6 +8,7 @@ import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
Future<OrderedTranslationAndResourceDTO?> showNewOrUpdatePDFFile(OrderedTranslationAndResourceDTO? inputPdfFile, AppContext appContext, BuildContext context, String text) async { Future<OrderedTranslationAndResourceDTO?> showNewOrUpdatePDFFile(OrderedTranslationAndResourceDTO? inputPdfFile, AppContext appContext, BuildContext context, String text) async {
@ -81,7 +82,7 @@ Future<OrderedTranslationAndResourceDTO?> showNewOrUpdatePDFFile(OrderedTranslat
height: size.height * 0.2, height: size.height * 0.2,
constraints: BoxConstraints(minHeight: 50, maxHeight: 80), constraints: BoxConstraints(minHeight: 50, maxHeight: 80),
child: ResourceInputContainer( child: ResourceInputContainer(
label: "Icône catégorie :", label: AppLocalizations.of(context)!.categoryIconLabel,
initialValue: categorieDTO.iconResourceId, initialValue: categorieDTO.iconResourceId,
color: kPrimaryColor, color: kPrimaryColor,
onChanged: (ResourceDTO resource) { onChanged: (ResourceDTO resource) {
@ -130,7 +131,7 @@ Future<OrderedTranslationAndResourceDTO?> showNewOrUpdatePDFFile(OrderedTranslat
width: inputPdfFile != null ? 220: 150, width: inputPdfFile != null ? 220: 150,
height: 70, height: 70,
child: RoundedButton( child: RoundedButton(
text: inputPdfFile != null ? "Sauvegarder" : "Créer", text: inputPdfFile != null ? AppLocalizations.of(context)!.save : AppLocalizations.of(context)!.create,
icon: Icons.check, icon: Icons.check,
color: kPrimaryColor, color: kPrimaryColor,
textColor: kWhite, textColor: kWhite,

View File

@ -5,6 +5,7 @@ import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/new_up
import 'package:manager_app/Screens/Configurations/Section/SubSection/PDF/new_update_pdfFile.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/PDF/new_update_pdfFile.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -95,7 +96,7 @@ class _PDFListState extends State<PDFList> {
onTap: () async { onTap: () async {
OrderedTranslationAndResourceDTO newPdfFile = OrderedTranslationAndResourceDTO(order: null); OrderedTranslationAndResourceDTO newPdfFile = OrderedTranslationAndResourceDTO(order: null);
var result = await showNewOrUpdatePDFFile(newPdfFile, appContext, context, "Création PDF"); var result = await showNewOrUpdatePDFFile(newPdfFile, appContext, context, AppLocalizations.of(context)!.createPdfTitle);
if (result != null) if (result != null)
{ {
setState(() { setState(() {

View File

@ -5,6 +5,7 @@ import 'package:manager_app/constants.dart';
import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedPath.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedPath.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Components/message_notification.dart';
@ -68,11 +69,11 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text("Parcours Guidés", Text(AppLocalizations.of(context)!.guidedPathsLabel,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
ElevatedButton.icon( ElevatedButton.icon(
icon: Icon(Icons.add), icon: Icon(Icons.add),
label: Text("Ajouter un parcours"), label: Text(AppLocalizations.of(context)!.addPath),
onPressed: () { onPressed: () {
final appContext = final appContext =
Provider.of<AppContext>(context, listen: false); Provider.of<AppContext>(context, listen: false);
@ -111,13 +112,13 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
}); });
} }
showNotification(kSuccess, kWhite, showNotification(kSuccess, kWhite,
'Parcours créé avec succès', context, null); AppLocalizations.of(context)!.pathCreatedSuccess, context, null);
} }
} catch (e) { } catch (e) {
showNotification( showNotification(
kError, kError,
kWhite, kWhite,
'Erreur lors de la création du parcours', AppLocalizations.of(context)!.pathCreateError,
context, context,
null); null);
rethrow; // Important so showNewOrUpdateGuidedPath knows it failed rethrow; // Important so showNewOrUpdateGuidedPath knows it failed
@ -134,7 +135,7 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
Expanded( Expanded(
child: paths.isEmpty child: paths.isEmpty
? Center( ? Center(
child: Text("Aucun parcours configuré", child: Text(AppLocalizations.of(context)!.noPathConfigured,
style: TextStyle(fontStyle: FontStyle.italic))) style: TextStyle(fontStyle: FontStyle.italic)))
: ReorderableListView.builder( : ReorderableListView.builder(
buildDefaultDragHandles: false, buildDefaultDragHandles: false,
@ -164,7 +165,7 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
showNotification( showNotification(
kError, kError,
kWhite, kWhite,
'Erreur lors de la mise à jour de l\'ordre', AppLocalizations.of(context)!.pathOrderUpdateError,
context, context,
null); null);
} }
@ -185,10 +186,10 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
.firstWhere((t) => t.language == 'FR', .firstWhere((t) => t.language == 'FR',
orElse: () => path.title![0]) orElse: () => path.title![0])
.value ?? .value ??
"Parcours sans titre", AppLocalizations.of(context)!.pathNoTitle,
) )
: Text("Parcours sans titre"), : Text(AppLocalizations.of(context)!.pathNoTitle),
subtitle: Text("${path.steps?.length ?? 0} étapes"), subtitle: Text(AppLocalizations.of(context)!.stepsCount(path.steps?.length ?? 0)),
trailing: Row( trailing: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -223,7 +224,7 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
showNotification( showNotification(
kSuccess, kSuccess,
kWhite, kWhite,
'Parcours mis à jour avec succès', AppLocalizations.of(context)!.pathUpdatedSuccess,
context, context,
null); null);
} }
@ -231,7 +232,7 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
showNotification( showNotification(
kError, kError,
kWhite, kWhite,
'Erreur lors de la mise à jour du parcours', AppLocalizations.of(context)!.pathUpdateError,
context, context,
null); null);
rethrow; rethrow;
@ -265,14 +266,14 @@ class _ParcoursConfigState extends State<ParcoursConfig> {
showNotification( showNotification(
kSuccess, kSuccess,
kWhite, kWhite,
'Parcours supprimé avec succès', AppLocalizations.of(context)!.pathDeletedSuccess,
context, context,
null); null);
} catch (e) { } catch (e) {
showNotification( showNotification(
kError, kError,
kWhite, kWhite,
'Erreur lors de la suppression du parcours', AppLocalizations.of(context)!.pathDeleteError,
context, context,
null); null);
} }

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/multi_string_input_container.dart';
import 'package:manager_app/Components/check_input_container.dart'; import 'package:manager_app/Components/check_input_container.dart';
@ -107,7 +108,7 @@ void showNewOrUpdateGuidedPath(
SizedBox( SizedBox(
width: thirdWidth, width: thirdWidth,
child: CheckInputContainer( child: CheckInputContainer(
label: "Linéaire :", label: AppLocalizations.of(context)!.linearLabel,
isChecked: workingPath.isLinear ?? false, isChecked: workingPath.isLinear ?? false,
onChanged: (val) => setState( onChanged: (val) => setState(
() => workingPath.isLinear = val), () => workingPath.isLinear = val),
@ -117,7 +118,7 @@ void showNewOrUpdateGuidedPath(
SizedBox( SizedBox(
width: thirdWidth, width: thirdWidth,
child: CheckInputContainer( child: CheckInputContainer(
label: "Réussite requise :", label: AppLocalizations.of(context)!.requiredSuccessLabel,
isChecked: isChecked:
workingPath.requireSuccessToAdvance ?? workingPath.requireSuccessToAdvance ??
false, false,
@ -144,7 +145,7 @@ void showNewOrUpdateGuidedPath(
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text("Étapes du parcours", Text(AppLocalizations.of(context)!.pathStepsLabel,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 15)), fontSize: 15)),
@ -174,7 +175,7 @@ void showNewOrUpdateGuidedPath(
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
child: Text( child: Text(
"Aucun point/étape configuré.", AppLocalizations.of(context)!.noStepConfigured,
style: TextStyle( style: TextStyle(
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
color: Colors.grey[600]), color: Colors.grey[600]),
@ -198,8 +199,8 @@ void showNewOrUpdateGuidedPath(
orElse: () => orElse: () =>
step.title![0]) step.title![0])
.value ?? .value ??
"Étape $index" "${AppLocalizations.of(context)!.stepFallback} $index"
: "Étape $index", : "${AppLocalizations.of(context)!.stepFallback} $index",
), ),
subtitle: isEscapeMode subtitle: isEscapeMode
? Text( ? Text(

View File

@ -8,6 +8,7 @@ import 'package:manager_app/Components/multi_string_input_container.dart';
import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/Components/string_input_container.dart'; import 'package:manager_app/Components/string_input_container.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'showNewOrUpdateQuizQuestion.dart'; import 'showNewOrUpdateQuizQuestion.dart';
void showNewOrUpdateGuidedStep( void showNewOrUpdateGuidedStep(
@ -53,7 +54,7 @@ void showNewOrUpdateGuidedStep(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
step == null ? "Nouvelle Étape" : "Modifier l'Étape", step == null ? AppLocalizations.of(context)!.newStepTitle : AppLocalizations.of(context)!.editStepTitle,
style: TextStyle( style: TextStyle(
color: kPrimaryColor, color: kPrimaryColor,
fontSize: 20, fontSize: 20,
@ -72,7 +73,7 @@ void showNewOrUpdateGuidedStep(
width: halfWidth, width: halfWidth,
child: MultiStringInputContainer( child: MultiStringInputContainer(
label: "Titre :", label: "Titre :",
modalLabel: "Titre de l'étape", modalLabel: AppLocalizations.of(context)!.stepTitleLabel,
initialValue: workingStep.title ?? [], initialValue: workingStep.title ?? [],
onGetResult: (val) => onGetResult: (val) =>
setState(() => workingStep.title = val), setState(() => workingStep.title = val),
@ -86,7 +87,7 @@ void showNewOrUpdateGuidedStep(
width: halfWidth, width: halfWidth,
child: MultiStringInputContainer( child: MultiStringInputContainer(
label: "Description :", label: "Description :",
modalLabel: "Description de l'étape", modalLabel: AppLocalizations.of(context)!.stepDescriptionLabel,
initialValue: workingStep.description ?? [], initialValue: workingStep.description ?? [],
onGetResult: (val) => setState( onGetResult: (val) => setState(
() => workingStep.description = val), () => workingStep.description = val),
@ -100,7 +101,7 @@ void showNewOrUpdateGuidedStep(
Divider(height: 24), Divider(height: 24),
// Géométrie Directement avec GeometryDTO // Géométrie Directement avec GeometryDTO
GeometryInputContainer( GeometryInputContainer(
label: "Emplacement de l'étape :", label: AppLocalizations.of(context)!.stepLocationLabel,
initialGeometry: workingStep.geometry, initialGeometry: workingStep.geometry,
initialColor: null, initialColor: null,
onSave: (geometry, color) { onSave: (geometry, color) {
@ -118,7 +119,7 @@ void showNewOrUpdateGuidedStep(
Expanded( Expanded(
child: SwitchListTile( child: SwitchListTile(
dense: true, dense: true,
title: Text("Cachée initialement"), title: Text(AppLocalizations.of(context)!.initiallyHiddenLabel),
value: workingStep.isHiddenInitially ?? false, value: workingStep.isHiddenInitially ?? false,
onChanged: (val) => setState(() => workingStep.isHiddenInitially = val), onChanged: (val) => setState(() => workingStep.isHiddenInitially = val),
activeThumbColor: kPrimaryColor, activeThumbColor: kPrimaryColor,
@ -127,7 +128,7 @@ void showNewOrUpdateGuidedStep(
Expanded( Expanded(
child: SwitchListTile( child: SwitchListTile(
dense: true, dense: true,
title: Text("Verrouillée"), title: Text(AppLocalizations.of(context)!.lockedLabel),
value: workingStep.isStepLocked ?? false, value: workingStep.isStepLocked ?? false,
onChanged: (val) => setState(() => workingStep.isStepLocked = val), onChanged: (val) => setState(() => workingStep.isStepLocked = val),
activeThumbColor: kPrimaryColor, activeThumbColor: kPrimaryColor,
@ -151,7 +152,7 @@ void showNewOrUpdateGuidedStep(
SizedBox( SizedBox(
width: halfWidth, width: halfWidth,
child: StringInputContainer( child: StringInputContainer(
label: "Durée (secondes) :", label: AppLocalizations.of(context)!.durationSecondsLabel,
initialValue: workingStep.timerSeconds?.toString() ?? "", initialValue: workingStep.timerSeconds?.toString() ?? "",
onChanged: (val) => setState(() => onChanged: (val) => setState(() =>
workingStep.timerSeconds = int.tryParse(val)), workingStep.timerSeconds = int.tryParse(val)),
@ -176,7 +177,7 @@ void showNewOrUpdateGuidedStep(
], ],
SizedBox(height: 8), SizedBox(height: 8),
StringInputContainer( StringInputContainer(
label: "GeoPoint de déclenchement (ID) :", label: AppLocalizations.of(context)!.triggerGeopointLabel,
initialValue: workingStep.triggerGeoPointId?.toString() ?? "", initialValue: workingStep.triggerGeoPointId?.toString() ?? "",
onChanged: (val) => setState(() => onChanged: (val) => setState(() =>
workingStep.triggerGeoPointId = int.tryParse(val)), workingStep.triggerGeoPointId = int.tryParse(val)),
@ -187,7 +188,7 @@ void showNewOrUpdateGuidedStep(
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text("Questions / Défis", Text(AppLocalizations.of(context)!.questionsChallengesLabel,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 15)), fontSize: 15)),
@ -220,7 +221,7 @@ void showNewOrUpdateGuidedStep(
padding: padding:
const EdgeInsets.symmetric(vertical: 8), const EdgeInsets.symmetric(vertical: 8),
child: Text( child: Text(
"Aucune question configurée.", AppLocalizations.of(context)!.noQuestionsConfigured,
style: TextStyle( style: TextStyle(
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
color: Colors.grey[600]), color: Colors.grey[600]),

View File

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/multi_string_input_container.dart';
import 'package:manager_app/Components/resource_input_container.dart'; import 'package:manager_app/Components/resource_input_container.dart';
@ -104,8 +105,8 @@ void showNewOrUpdateQuizQuestion(
children: [ children: [
// --- Intitulé (multi-langue via MultiStringInputContainer) --- // --- Intitulé (multi-langue via MultiStringInputContainer) ---
MultiStringInputContainer( MultiStringInputContainer(
label: "Question posée :", label: AppLocalizations.of(context)!.questionAskedLabel,
modalLabel: "Intitulé de la question", modalLabel: AppLocalizations.of(context)!.questionTitleLabel,
initialValue: workingLabel, initialValue: workingLabel,
onGetResult: (val) => setState(() { onGetResult: (val) => setState(() {
workingLabel = val; workingLabel = val;
@ -146,7 +147,7 @@ void showNewOrUpdateQuizQuestion(
if (workingQuestion.validationQuestionType == if (workingQuestion.validationQuestionType ==
QuestionType.number0) ...[ QuestionType.number0) ...[
Divider(height: 24), Divider(height: 24),
Text("Réponse attendue :", Text(AppLocalizations.of(context)!.expectedAnswerLabel,
style: TextStyle(fontWeight: FontWeight.bold)), style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(height: 8), SizedBox(height: 8),
Builder(builder: (_) { Builder(builder: (_) {
@ -156,7 +157,7 @@ void showNewOrUpdateQuizQuestion(
workingQuestion.responses[0].label); workingQuestion.responses[0].label);
return MultiStringInputContainer( return MultiStringInputContainer(
label: "", label: "",
modalLabel: "Réponse attendue", modalLabel: AppLocalizations.of(context)!.expectedAnswerModalLabel,
initialValue: respLabel, initialValue: respLabel,
onGetResult: (val) => setState(() { onGetResult: (val) => setState(() {
workingQuestion.responses[0].label = workingQuestion.responses[0].label =
@ -170,7 +171,7 @@ void showNewOrUpdateQuizQuestion(
}), }),
SizedBox(height: 4), SizedBox(height: 4),
Text( Text(
"La validation se fait par comparaison (insensible à la casse).", AppLocalizations.of(context)!.validationNote,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
@ -187,7 +188,7 @@ void showNewOrUpdateQuizQuestion(
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text("Réponses possibles :", Text(AppLocalizations.of(context)!.possibleAnswersLabel,
style: style:
TextStyle(fontWeight: FontWeight.bold)), TextStyle(fontWeight: FontWeight.bold)),
TextButton.icon( TextButton.icon(
@ -208,7 +209,7 @@ void showNewOrUpdateQuizQuestion(
padding: padding:
const EdgeInsets.symmetric(vertical: 8), const EdgeInsets.symmetric(vertical: 8),
child: Text( child: Text(
"Aucune réponse définie. Ajoutez-en au moins une.", AppLocalizations.of(context)!.noAnswerDefined,
style: TextStyle( style: TextStyle(
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
color: Colors.grey[600]), color: Colors.grey[600]),
@ -233,8 +234,8 @@ void showNewOrUpdateQuizQuestion(
// Checkbox bonne réponse // Checkbox bonne réponse
Tooltip( Tooltip(
message: resp.isGood == true message: resp.isGood == true
? "Bonne réponse ✓" ? AppLocalizations.of(context)!.correctAnswer
: "Mauvaise réponse", : AppLocalizations.of(context)!.wrongAnswer,
child: Checkbox( child: Checkbox(
value: resp.isGood ?? false, value: resp.isGood ?? false,
activeColor: kSuccess, activeColor: kSuccess,
@ -245,8 +246,8 @@ void showNewOrUpdateQuizQuestion(
// Traductions (via MultiStringInputContainer) // Traductions (via MultiStringInputContainer)
Expanded( Expanded(
child: MultiStringInputContainer( child: MultiStringInputContainer(
label: "Réponse ${i + 1} :", label: "${AppLocalizations.of(context)!.answerLabel} ${i + 1} :",
modalLabel: "Réponse ${i + 1}", modalLabel: "${AppLocalizations.of(context)!.answerLabel} ${i + 1}",
initialValue: respLabel, initialValue: respLabel,
onGetResult: (val) => setState( onGetResult: (val) => setState(
() => resp.label = () => resp.label =
@ -273,7 +274,7 @@ void showNewOrUpdateQuizQuestion(
), ),
SizedBox(height: 4), SizedBox(height: 4),
Text( Text(
"✓ = bonne réponse. Plusieurs peuvent être correctes.", AppLocalizations.of(context)!.answerNote,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,

View File

@ -7,6 +7,7 @@ import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/Screens/Configurations/Section/SubSection/Quizz/quizz_answer_list.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Quizz/quizz_answer_list.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
Future<QuestionDTO?> showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO, AppContext appContext, BuildContext context, String text) async { Future<QuestionDTO?> showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO, AppContext appContext, BuildContext context, String text) async {
@ -54,7 +55,7 @@ Future<QuestionDTO?> showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO,
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
ResourceInputContainer( ResourceInputContainer(
label: "Image fond d'écran :", label: AppLocalizations.of(context)!.backgroundImageLabel,
initialValue: questionDTO.imageBackgroundResourceId, initialValue: questionDTO.imageBackgroundResourceId,
color: kPrimaryColor, color: kPrimaryColor,
onChanged: (ResourceDTO resource) { onChanged: (ResourceDTO resource) {
@ -75,8 +76,8 @@ Future<QuestionDTO?> showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO,
height: size.height * 0.15, height: size.height * 0.15,
constraints: BoxConstraints(minHeight: 50, maxHeight: 80), constraints: BoxConstraints(minHeight: 50, maxHeight: 80),
child: MultiStringInputAndResourceContainer( child: MultiStringInputAndResourceContainer(
label: "Question :", label: AppLocalizations.of(context)!.questionInputLabel,
modalLabel: "Question", modalLabel: AppLocalizations.of(context)!.questionLabel,
fontSize: 20, fontSize: 20,
color: kPrimaryColor, color: kPrimaryColor,
resourceTypes: [ResourceType.Image, ResourceType.ImageUrl, ResourceType.Video, ResourceType.VideoUrl, ResourceType.Audio], resourceTypes: [ResourceType.Image, ResourceType.ImageUrl, ResourceType.Video, ResourceType.VideoUrl, ResourceType.Audio],
@ -133,7 +134,7 @@ Future<QuestionDTO?> showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO,
width: 175, width: 175,
height: 70, height: 70,
child: RoundedButton( child: RoundedButton(
text: "Annuler", text: AppLocalizations.of(context)!.cancel,
icon: Icons.undo, icon: Icons.undo,
color: kSecond, color: kSecond,
press: () { press: () {
@ -149,7 +150,7 @@ Future<QuestionDTO?> showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO,
width: inputQuestionDTO != null ? 220: 150, width: inputQuestionDTO != null ? 220: 150,
height: 70, height: 70,
child: RoundedButton( child: RoundedButton(
text: inputQuestionDTO != null ? "Sauvegarder" : "Créer", text: inputQuestionDTO != null ? AppLocalizations.of(context)!.save : AppLocalizations.of(context)!.create,
icon: Icons.check, icon: Icons.check,
color: kPrimaryColor, color: kPrimaryColor,
textColor: kWhite, textColor: kWhite,
@ -157,7 +158,7 @@ Future<QuestionDTO?> showNewOrUpdateQuestionQuizz(QuestionDTO? inputQuestionDTO,
if(!questionDTO.label!.any((label) => label.value == null || label.value!.trim() == "")) { if(!questionDTO.label!.any((label) => label.value == null || label.value!.trim() == "")) {
Navigator.pop(dialogContext, questionDTO); Navigator.pop(dialogContext, questionDTO);
} else { } else {
showNotification(kPrimaryColor, kWhite, "La traduction n'est pas complète", context, null); showNotification(kPrimaryColor, kWhite, AppLocalizations.of(context)!.translationIncomplete, context, null);
} }
}, },
fontSize: 20, fontSize: 20,

View File

@ -5,6 +5,7 @@ import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:manager_app/Components/multi_string_input_html_modal.dart'; import 'package:manager_app/Components/multi_string_input_html_modal.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -92,7 +93,7 @@ class _QuizzResponseListState extends State<QuizzResponseList> {
top: 10, top: 10,
left: 10, left: 10,
child: Text( child: Text(
"Réponses", AppLocalizations.of(context)!.answersLabel,
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500), style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
), ),
), ),
@ -117,7 +118,7 @@ class _QuizzResponseListState extends State<QuizzResponseList> {
} }
}); });
showMultiStringInputAndResourceHTML("Réponse", "Créer la réponse", true, initials, newValues, (value) { showMultiStringInputAndResourceHTML(AppLocalizations.of(context)!.answerLabel, AppLocalizations.of(context)!.createAnswerTitle, true, initials, newValues, (value) {
if(value != null && value.isNotEmpty) { if(value != null && value.isNotEmpty) {
setState(() { setState(() {
newResponse.label = value; newResponse.label = value;
@ -184,7 +185,7 @@ class _QuizzResponseListState extends State<QuizzResponseList> {
child: Row( child: Row(
children: [ children: [
Tooltip( Tooltip(
message: "Si coché, la réponse est valide", message: AppLocalizations.of(context)!.answerValidNote,
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Checkbox( child: Checkbox(
@ -217,7 +218,7 @@ class _QuizzResponseListState extends State<QuizzResponseList> {
} }
}); });
showMultiStringInputAndResourceHTML("Réponse", "Modifier la réponse", true, initials, newValues, (value) { showMultiStringInputAndResourceHTML(AppLocalizations.of(context)!.answerLabel, AppLocalizations.of(context)!.editAnswerTitle, true, initials, newValues, (value) {
setState(() { setState(() {
if (value != null && response.label! != value) { if (value != null && response.label! != value) {
response.label = value; response.label = value;

View File

@ -8,6 +8,7 @@ import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/client.dart'; import 'package:manager_app/client.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/Components/common_loader.dart';
import 'dart:convert'; import 'dart:convert';
@ -62,13 +63,13 @@ class _QuizzConfigState extends State<QuizzConfig> {
item.order = newIndex; item.order = newIndex;
try { try {
await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionQuizApi!.sectionQuizUpdate(item); await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionQuizApi!.sectionQuizUpdate(item);
showNotification(kSuccess, kWhite, "L'ordre des questions a été mis à jour avec succès", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.questionOrderUpdatedSuccess, context, null);
setState(() { setState(() {
// refresh ui // refresh ui
print("Refresh UI"); print("Refresh UI");
}); });
} catch(e) { } catch(e) {
showNotification(kError, kWhite, "Une erreur est survenue lors de la mise à jour de l'ordre des questions", context, null); showNotification(kError, kWhite, AppLocalizations.of(context)!.questionOrderUpdateError, context, null);
} }
} }
@ -86,12 +87,12 @@ class _QuizzConfigState extends State<QuizzConfig> {
Container( Container(
height: 50, height: 50,
child: RoundedButton( child: RoundedButton(
text: "Mauvais score", text: AppLocalizations.of(context)!.quizBadScore,
color: kPrimaryColor, color: kPrimaryColor,
textColor: kWhite, textColor: kWhite,
icon: Icons.message, icon: Icons.message,
press: () async { press: () async {
updateScoreQuizMessage(context, appContext, quizzDTO.badLevel, "Message pour un mauvais score", 0); updateScoreQuizMessage(context, appContext, quizzDTO.badLevel, AppLocalizations.of(context)!.quizBadScoreMsg, 0);
}, },
fontSize: 20, fontSize: 20,
horizontal: 10, horizontal: 10,
@ -102,12 +103,12 @@ class _QuizzConfigState extends State<QuizzConfig> {
Container( Container(
height: 50, height: 50,
child: RoundedButton( child: RoundedButton(
text: "Moyen score", text: AppLocalizations.of(context)!.quizMediumScore,
color: kPrimaryColor, color: kPrimaryColor,
textColor: kWhite, textColor: kWhite,
icon: Icons.message, icon: Icons.message,
press: () async { press: () async {
updateScoreQuizMessage(context, appContext, quizzDTO.mediumLevel, "Message pour un score moyen", 1); updateScoreQuizMessage(context, appContext, quizzDTO.mediumLevel, AppLocalizations.of(context)!.quizMediumScoreMsg, 1);
}, },
fontSize: 20, fontSize: 20,
horizontal: 10, horizontal: 10,
@ -118,12 +119,12 @@ class _QuizzConfigState extends State<QuizzConfig> {
Container( Container(
height: 50, height: 50,
child: RoundedButton( child: RoundedButton(
text: "Bon score", text: AppLocalizations.of(context)!.quizGoodScore,
color: kPrimaryColor, color: kPrimaryColor,
textColor: kWhite, textColor: kWhite,
icon: Icons.message, icon: Icons.message,
press: () async { press: () async {
updateScoreQuizMessage(context, appContext, quizzDTO.goodLevel, "Message pour un bon score", 2); updateScoreQuizMessage(context, appContext, quizzDTO.goodLevel, AppLocalizations.of(context)!.quizGoodScoreMsg, 2);
}, },
fontSize: 20, fontSize: 20,
horizontal: 10, horizontal: 10,
@ -134,12 +135,12 @@ class _QuizzConfigState extends State<QuizzConfig> {
Container( Container(
height: 50, height: 50,
child: RoundedButton( child: RoundedButton(
text: "Excellent score", text: AppLocalizations.of(context)!.quizExcellentScore,
color: kPrimaryColor, color: kPrimaryColor,
textColor: kWhite, textColor: kWhite,
icon: Icons.message, icon: Icons.message,
press: () async { press: () async {
updateScoreQuizMessage(context, appContext, quizzDTO.greatLevel, "Message pour un excellent score", 3); updateScoreQuizMessage(context, appContext, quizzDTO.greatLevel, AppLocalizations.of(context)!.quizExcellentScoreMsg, 3);
}, },
fontSize: 20, fontSize: 20,
horizontal: 10, horizontal: 10,
@ -204,7 +205,7 @@ class _QuizzConfigState extends State<QuizzConfig> {
top: 10, top: 10,
left: 10, left: 10,
child: Text( child: Text(
"Questions", AppLocalizations.of(context)!.questionsLabel,
style: TextStyle(fontSize: 15, style: TextStyle(fontSize: 15,
fontWeight: FontWeight.w500), fontWeight: FontWeight.w500),
), ),
@ -215,20 +216,20 @@ class _QuizzConfigState extends State<QuizzConfig> {
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
QuestionDTO? result = await showNewOrUpdateQuestionQuizz( QuestionDTO? result = await showNewOrUpdateQuestionQuizz(
null, appContext, context, "Question"); null, appContext, context, AppLocalizations.of(context)!.questionLabel);
try { try {
if(result != null) if(result != null)
{ {
await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionQuizApi!.sectionQuizCreate(quizzDTO.id!, result); await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionQuizApi!.sectionQuizCreate(quizzDTO.id!, result);
showNotification(kSuccess, kWhite, 'La question a été créée avec succès', context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.questionCreatedSuccess, context, null);
setState(() { setState(() {
// refresh ui // refresh ui
print("Refresh UI"); print("Refresh UI");
}); });
} }
} catch(e) { } catch(e) {
showNotification(kError, kWhite, 'Une erreur est survenue lors de la création de la question', context, null); showNotification(kError, kWhite, AppLocalizations.of(context)!.questionCreateError, context, null);
} }
/*setState(() { /*setState(() {
@ -267,7 +268,7 @@ class _QuizzConfigState extends State<QuizzConfig> {
); );
} else { } else {
return Center(child: Text( return Center(child: Text(
"Une erreur est survenue lors de la récupération des questions")); AppLocalizations.of(context)!.questionsLoadError));
} }
} }
} }
@ -314,28 +315,28 @@ class _QuizzConfigState extends State<QuizzConfig> {
child: Row( child: Row(
children: [ children: [
Tooltip( Tooltip(
message: "Modifier", message: AppLocalizations.of(context)!.edit,
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
QuestionDTO? result = await showNewOrUpdateQuestionQuizz( QuestionDTO? result = await showNewOrUpdateQuestionQuizz(
question, question,
appContext, appContext,
context, context,
"Modifier la question" AppLocalizations.of(context)!.editQuestion
); );
try { try {
if(result != null) if(result != null)
{ {
await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionQuizApi!.sectionQuizUpdate(result); await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionQuizApi!.sectionQuizUpdate(result);
showNotification(kSuccess, kWhite, 'La question a été mis à jour avec succès', context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.questionUpdatedSuccess, context, null);
setState(() { setState(() {
// refresh ui // refresh ui
print("Refresh UI"); print("Refresh UI");
}); });
} }
} catch(e) { } catch(e) {
showNotification(kError, kWhite, 'Une erreur est survenue lors de la mise à jour de la question', context, null); showNotification(kError, kWhite, AppLocalizations.of(context)!.questionUpdateError, context, null);
} }
/*setState(() { /*setState(() {
@ -354,24 +355,24 @@ class _QuizzConfigState extends State<QuizzConfig> {
), ),
), ),
Tooltip( Tooltip(
message: "Supprimer", message: AppLocalizations.of(context)!.delete,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
showConfirmationDialog( showConfirmationDialog(
"Êtes-vous sûr de vouloir supprimer cette question ?", AppLocalizations.of(context)!.questionDeleteConfirm,
() {}, () {},
() async { () async {
try { try {
var questionToRemove = questions[index]; var questionToRemove = questions[index];
(appContext.getContext() as ManagerAppContext).clientAPI!.sectionQuizApi!.sectionQuizDeleteWithHttpInfo(questionToRemove.id!); (appContext.getContext() as ManagerAppContext).clientAPI!.sectionQuizApi!.sectionQuizDeleteWithHttpInfo(questionToRemove.id!);
showNotification(kSuccess, kWhite, 'La question a été supprimée avec succès', context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.questionDeletedSuccess, context, null);
// refresh UI // refresh UI
setState(() { setState(() {
print("Refresh UI"); print("Refresh UI");
}); });
} catch(e) { } catch(e) {
showNotification(kError, kWhite, 'Une erreur est survenue lors de la suppression de la question', context, null); showNotification(kError, kWhite, AppLocalizations.of(context)!.questionDeleteError, context, null);
} }
}, },
context context

View File

@ -5,6 +5,7 @@ import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
Future<ContentDTO?> showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, AppContext appContext, BuildContext context, bool showTitle, bool showDescription) async { Future<ContentDTO?> showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, AppContext appContext, BuildContext context, bool showTitle, bool showDescription) async {
@ -75,7 +76,7 @@ Future<ContentDTO?> showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, Ap
child: Column( child: Column(
children: [ children: [
MultiStringInputContainer( MultiStringInputContainer(
label: "Titre affiché:", label: AppLocalizations.of(context)!.displayTitleLabel,
modalLabel: "Titre", modalLabel: "Titre",
fontSize: 20, fontSize: 20,
isHTML: true, isHTML: true,
@ -90,7 +91,7 @@ Future<ContentDTO?> showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, Ap
isTitle: true isTitle: true
), ),
MultiStringInputContainer( MultiStringInputContainer(
label: "Description affichée:", label: AppLocalizations.of(context)!.displayedDescriptionLabel,
modalLabel: "Description", modalLabel: "Description",
fontSize: 20, fontSize: 20,
isHTML: true, isHTML: true,
@ -140,7 +141,7 @@ Future<ContentDTO?> showNewOrUpdateContentSlider(ContentDTO? inputContentDTO, Ap
width: inputContentDTO != null ? 220: 150, width: inputContentDTO != null ? 220: 150,
height: 70, height: 70,
child: RoundedButton( child: RoundedButton(
text: inputContentDTO != null ? "Sauvegarder" : "Créer", text: inputContentDTO != null ? AppLocalizations.of(context)!.save : AppLocalizations.of(context)!.create,
icon: Icons.check, icon: Icons.check,
color: kPrimaryColor, color: kPrimaryColor,
textColor: kWhite, textColor: kWhite,

View File

@ -10,6 +10,7 @@ import 'package:manager_app/Components/fetch_section_icon.dart';
import 'package:manager_app/Components/resource_input_container.dart'; import 'package:manager_app/Components/resource_input_container.dart';
import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/Components/common_loader.dart';
import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Components/message_notification.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/multi_string_input_container.dart';
import 'package:manager_app/Components/number_input_container.dart'; import 'package:manager_app/Components/number_input_container.dart';
import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/rounded_button.dart';
@ -120,10 +121,10 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
} else { } else {
return Center( return Center(
child: Text( child: Text(
"Une erreur est survenue lors de la récupération de la section")); AppLocalizations.of(context)!.sectionLoadError));
} }
} else if (snapshot.connectionState == ConnectionState.none) { } else if (snapshot.connectionState == ConnectionState.none) {
return Text("No data"); return Text(AppLocalizations.of(context)!.noData);
} else { } else {
return Center( return Center(
child: Container( child: Container(
@ -235,7 +236,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
showNotification( showNotification(
kSuccess, kSuccess,
kWhite, kWhite,
'Ce QR code a été copié dans le presse papier', AppLocalizations.of(context)!.qrCodeCopied,
context, context,
null); null);
}, },
@ -263,7 +264,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
], ],
), ),
CheckInputContainer( CheckInputContainer(
label: "Beacon :", label: AppLocalizations.of(context)!.beaconLabel,
isChecked: sectionDTO.isBeacon!, isChecked: sectionDTO.isBeacon!,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
@ -274,7 +275,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
), ),
if (sectionDTO.isBeacon!) if (sectionDTO.isBeacon!)
NumberInputContainer( NumberInputContainer(
label: "Identifiant Beacon :", label: AppLocalizations.of(context)!.beaconIdLabel,
initialValue: sectionDTO.beaconId != null initialValue: sectionDTO.beaconId != null
? sectionDTO.beaconId! ? sectionDTO.beaconId!
: 0, : 0,
@ -287,7 +288,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
showNotification( showNotification(
Colors.orange, Colors.orange,
kWhite, kWhite,
'Cela doit être un chiffre', AppLocalizations.of(context)!.beaconMustBeNumber,
context, context,
null); null);
} }
@ -302,7 +303,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
SizedBox( SizedBox(
height: 100, height: 100,
child: StringInputContainer( child: StringInputContainer(
label: "Identifiant :", label: AppLocalizations.of(context)!.identifierLabel,
initialValue: sectionDTO.label, initialValue: sectionDTO.label,
onChanged: (String value) { onChanged: (String value) {
sectionDTO.label = value; sectionDTO.label = value;
@ -312,8 +313,8 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
SizedBox( SizedBox(
height: 100, height: 100,
child: MultiStringInputContainer( child: MultiStringInputContainer(
label: "Titre affiché:", label: AppLocalizations.of(context)!.displayTitleLabel,
modalLabel: "Titre", modalLabel: AppLocalizations.of(context)!.messageTitle,
color: kPrimaryColor, color: kPrimaryColor,
initialValue: sectionDTO.title!, initialValue: sectionDTO.title!,
onGetResult: (value) { onGetResult: (value) {
@ -351,7 +352,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ResourceInputContainer( ResourceInputContainer(
label: "Image :", label: AppLocalizations.of(context)!.imageLabel,
initialValue: sectionDTO.imageId, initialValue: sectionDTO.imageId,
color: kPrimaryColor, color: kPrimaryColor,
onChanged: (ResourceDTO resource) { onChanged: (ResourceDTO resource) {
@ -400,7 +401,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
Padding( Padding(
padding: const EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
child: RoundedButton( child: RoundedButton(
text: "Annuler", text: AppLocalizations.of(context)!.cancel,
icon: Icons.undo, icon: Icons.undo,
color: Colors.grey, color: Colors.grey,
textColor: Colors.white, textColor: Colors.white,
@ -413,7 +414,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
if (canEdit) Padding( if (canEdit) Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: RoundedButton( child: RoundedButton(
text: "Supprimer", text: AppLocalizations.of(context)!.delete,
icon: Icons.delete, icon: Icons.delete,
color: kError, color: kError,
textColor: Colors.white, textColor: Colors.white,
@ -426,7 +427,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
if (canEdit) Padding( if (canEdit) Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: RoundedButton( child: RoundedButton(
text: "Sauvegarder", text: AppLocalizations.of(context)!.save,
icon: Icons.done, icon: Icons.done,
color: kSuccess, color: kSuccess,
textColor: Colors.white, textColor: Colors.white,
@ -456,7 +457,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
Future<void> delete(AppContext appContext) async { Future<void> delete(AppContext appContext) async {
showConfirmationDialog( showConfirmationDialog(
"Êtes-vous sûr de vouloir supprimer cette section ?", () {}, () async { AppLocalizations.of(context)!.sectionDeleteConfirm, () {}, () async {
ManagerAppContext managerAppContext = appContext.getContext(); ManagerAppContext managerAppContext = appContext.getContext();
await managerAppContext.clientAPI!.sectionApi! await managerAppContext.clientAPI!.sectionApi!
.sectionDelete(sectionDTO.id!); .sectionDelete(sectionDTO.id!);
@ -482,12 +483,12 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
showNotification( showNotification(
kSuccess, kSuccess,
kWhite, kWhite,
'Les traductions de la section ont été sauvegardées avec succès', AppLocalizations.of(context)!.sectionTranslationSaved,
context, context,
null); null);
} else { } else {
showNotification(kSuccess, kWhite, showNotification(kSuccess, kWhite,
'La section a été sauvegardée avec succès', context, null); AppLocalizations.of(context)!.sectionSavedSuccess, context, null);
} }
} }
@ -509,7 +510,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
); );
case SectionType.Video: case SectionType.Video:
return VideoConfig( return VideoConfig(
label: "Url de la vidéo:", label: AppLocalizations.of(context)!.videoUrlLabel,
initialValue: sectionDetailDTO as VideoDTO, initialValue: sectionDetailDTO as VideoDTO,
onChanged: (VideoDTO updatedWebDTO) { onChanged: (VideoDTO updatedWebDTO) {
sectionDetailDTO = updatedWebDTO; sectionDetailDTO = updatedWebDTO;
@ -517,7 +518,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
); );
case SectionType.Web: case SectionType.Web:
return WebConfig( return WebConfig(
label: "Url du site web:", label: AppLocalizations.of(context)!.webUrlLabel,
initialValue: sectionDetailDTO as WebDTO, initialValue: sectionDetailDTO as WebDTO,
onChanged: (WebDTO updatedWebDTO) { onChanged: (WebDTO updatedWebDTO) {
sectionDetailDTO = updatedWebDTO; sectionDetailDTO = updatedWebDTO;

View File

@ -11,6 +11,7 @@ import 'package:manager_app/Components/number_input_container.dart';
import 'package:manager_app/Components/resource_input_container.dart'; import 'package:manager_app/Components/resource_input_container.dart';
import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/Components/common_loader.dart';
import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Components/message_notification.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Components/multi_select_dropdown_language_container.dart'; import 'package:manager_app/Components/multi_select_dropdown_language_container.dart';
import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/multi_string_input_container.dart';
import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/rounded_button.dart';
@ -50,7 +51,7 @@ class _ConfigurationDetailScreenState extends State<ConfigurationDetailScreen> {
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
return bodyConfiguration(snapshot.data, size, appContext, context); return bodyConfiguration(snapshot.data, size, appContext, context);
} else if (snapshot.connectionState == ConnectionState.none) { } else if (snapshot.connectionState == ConnectionState.none) {
return Text("No data"); return Text(AppLocalizations.of(context)!.noData);
} else { } else {
return Center( return Center(
child: Container( child: Container(
@ -100,11 +101,11 @@ class _ConfigurationDetailScreenState extends State<ConfigurationDetailScreen> {
anchorElement.click(); anchorElement.click();
} else { } else {
File test = await FileHelper().storeConfiguration(export); File test = await FileHelper().storeConfiguration(export);
showNotification(kSuccess, kWhite, "L'export de la configuration a réussi, le document se trouve à cet endroit : " + test.path, context, 3000); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configExportSuccess(test.path), context, 3000);
} }
} catch(e) { } catch(e) {
log(e.toString()); log(e.toString());
showNotification(kPrimaryColor, kWhite, "L'export de la configuration a échoué", context, null); showNotification(kPrimaryColor, kWhite, AppLocalizations.of(context)!.configExportFailed, context, null);
} }
}, },
child: Padding( child: Padding(
@ -167,7 +168,7 @@ class _ConfigurationDetailScreenState extends State<ConfigurationDetailScreen> {
SizedBox( SizedBox(
height: 70, height: 70,
child: StringInputContainer( child: StringInputContainer(
label: "Identifiant :", label: AppLocalizations.of(context)!.identifierLabel,
fontSize: 20, fontSize: 20,
fontSizeText: 20, fontSizeText: 20,
initialValue: configurationDTO.label, initialValue: configurationDTO.label,
@ -182,7 +183,7 @@ class _ConfigurationDetailScreenState extends State<ConfigurationDetailScreen> {
crossAxisAlignment: WrapCrossAlignment.center, crossAxisAlignment: WrapCrossAlignment.center,
children: [ children: [
MultiSelectDropdownLanguageContainer( MultiSelectDropdownLanguageContainer(
label: "Langues :", label: AppLocalizations.of(context)!.languagesLabel,
initialValue: configurationDTO.languages != null ? configurationDTO.languages! : [], initialValue: configurationDTO.languages != null ? configurationDTO.languages! : [],
values: languages, values: languages,
isMultiple: true, isMultiple: true,
@ -195,7 +196,7 @@ class _ConfigurationDetailScreenState extends State<ConfigurationDetailScreen> {
), ),
CheckInputContainer( CheckInputContainer(
icon: Icons.signal_wifi_off, icon: Icons.signal_wifi_off,
label: "Hors ligne :", label: "Hors ligne :", // no ARB key for this one yet, leave as-is
fontSize: 20, fontSize: 20,
isChecked: configurationDTO.isOffline, isChecked: configurationDTO.isOffline,
onChanged: (value) { onChanged: (value) {
@ -211,7 +212,7 @@ class _ConfigurationDetailScreenState extends State<ConfigurationDetailScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ResourceInputContainer( ResourceInputContainer(
label: "Image fond d'écran :", label: AppLocalizations.of(context)!.mainImageLabel,
fontSize: 20, fontSize: 20,
initialValue: configurationDTO.imageId, initialValue: configurationDTO.imageId,
color: kPrimaryColor, color: kPrimaryColor,
@ -226,7 +227,7 @@ class _ConfigurationDetailScreenState extends State<ConfigurationDetailScreen> {
}, },
), ),
ResourceInputContainer( ResourceInputContainer(
label: "Image loader :", label: AppLocalizations.of(context)!.loaderLabel,
fontSize: 20, fontSize: 20,
initialValue: configurationDTO.loaderImageId, initialValue: configurationDTO.loaderImageId,
color: kPrimaryColor, color: kPrimaryColor,
@ -355,7 +356,7 @@ class _ConfigurationDetailScreenState extends State<ConfigurationDetailScreen> {
Padding( Padding(
padding: const EdgeInsets.all(5.0), padding: const EdgeInsets.all(5.0),
child: RoundedButton( child: RoundedButton(
text: "Annuler", text: AppLocalizations.of(context)!.cancel,
icon: Icons.undo, icon: Icons.undo,
color: Colors.grey, color: Colors.grey,
textColor: Colors.white, textColor: Colors.white,
@ -368,7 +369,7 @@ class _ConfigurationDetailScreenState extends State<ConfigurationDetailScreen> {
if (canEdit) Padding( if (canEdit) Padding(
padding: const EdgeInsets.all(5.0), padding: const EdgeInsets.all(5.0),
child: RoundedButton( child: RoundedButton(
text: "Supprimer", text: AppLocalizations.of(context)!.delete,
icon: Icons.delete, icon: Icons.delete,
color: kError, color: kError,
textColor: Colors.white, textColor: Colors.white,
@ -381,7 +382,7 @@ class _ConfigurationDetailScreenState extends State<ConfigurationDetailScreen> {
if (canEdit) Padding( if (canEdit) Padding(
padding: const EdgeInsets.all(5.0), padding: const EdgeInsets.all(5.0),
child: RoundedButton( child: RoundedButton(
text: "Sauvegarder", text: AppLocalizations.of(context)!.save,
icon: Icons.done, icon: Icons.done,
color: kSuccess, color: kSuccess,
textColor: Colors.white, textColor: Colors.white,
@ -432,14 +433,14 @@ class _ConfigurationDetailScreenState extends State<ConfigurationDetailScreen> {
Future<void> delete(ConfigurationDTO configurationDTO, AppContext appContext) async { Future<void> delete(ConfigurationDTO configurationDTO, AppContext appContext) async {
showConfirmationDialog( showConfirmationDialog(
"Êtes-vous sûr de vouloir supprimer cette visite ?", AppLocalizations.of(context)!.configDeleteConfirm,
() {}, () {},
() async { () async {
ManagerAppContext managerAppContext = appContext.getContext(); ManagerAppContext managerAppContext = appContext.getContext();
await managerAppContext.clientAPI!.configurationApi!.configurationDelete(configurationDTO.id!); await managerAppContext.clientAPI!.configurationApi!.configurationDelete(configurationDTO.id!);
managerAppContext.selectedConfiguration = null; managerAppContext.selectedConfiguration = null;
appContext.setContext(managerAppContext); appContext.setContext(managerAppContext);
showNotification(kSuccess, kWhite, 'La configuration a été supprimée avec succès', context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configDeletedSuccess, context, null);
}, },
context context
); );
@ -451,7 +452,7 @@ class _ConfigurationDetailScreenState extends State<ConfigurationDetailScreen> {
managerAppContext.selectedConfiguration = configuration; managerAppContext.selectedConfiguration = configuration;
appContext.setContext(managerAppContext); appContext.setContext(managerAppContext);
showNotification(kSuccess, kWhite, 'La configuration a été sauvegardée avec succès', context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configSavedSuccess, context, null);
} }
Future<ConfigurationDTO?> getConfiguration(ConfigurationDetailScreen widget, Client client) async { Future<ConfigurationDTO?> getConfiguration(ConfigurationDetailScreen widget, Client client) async {

View File

@ -1,6 +1,7 @@
import 'package:auto_size_text/auto_size_text.dart'; import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/Components/common_loader.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Components/message_notification.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/Screens/Configurations/configuration_detail_screen.dart'; import 'package:manager_app/Screens/Configurations/configuration_detail_screen.dart';
@ -46,7 +47,7 @@ class _ConfigurationsScreenState extends State<ConfigurationsScreen> {
if (managerAppContext.canEdit) tempOutput.add(ConfigurationDTO(id: null)); if (managerAppContext.canEdit) tempOutput.add(ConfigurationDTO(id: null));
return bodyGrid(tempOutput, size, appContext, context); return bodyGrid(tempOutput, size, appContext, context);
} else if (snapshot.connectionState == ConnectionState.none) { } else if (snapshot.connectionState == ConnectionState.none) {
return Text("No data"); return Text(AppLocalizations.of(context)!.noData);
} else { } else {
return Center( return Center(
child: Container( child: Container(
@ -96,7 +97,7 @@ class _ConfigurationsScreenState extends State<ConfigurationsScreen> {
managerAppContext.selectedConfiguration = null; managerAppContext.selectedConfiguration = null;
await appContext.setContext(managerAppContext); await appContext.setContext(managerAppContext);
showNotification(kSuccess, kWhite, 'La configuration a été créée avec succès', context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.configCreatedSuccess, context, null);
} }
} }
} else { } else {

View File

@ -1,6 +1,7 @@
//import 'package:filepicker_windows/filepicker_windows.dart'; //import 'package:filepicker_windows/filepicker_windows.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Components/message_notification.dart';
import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/Components/string_input_container.dart'; import 'package:manager_app/Components/string_input_container.dart';
@ -11,6 +12,7 @@ import 'package:manager_app/constants.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
Future<ConfigurationDTO?> showNewConfiguration(AppContext appContext, ValueChanged<bool> isImport, BuildContext context, BuildContext mainContext) { Future<ConfigurationDTO?> showNewConfiguration(AppContext appContext, ValueChanged<bool> isImport, BuildContext context, BuildContext mainContext) {
final l = AppLocalizations.of(mainContext)!;
ConfigurationDTO configurationDTO = new ConfigurationDTO(); ConfigurationDTO configurationDTO = new ConfigurationDTO();
Size size = MediaQuery.of(mainContext).size; Size size = MediaQuery.of(mainContext).size;
configurationDTO.label = ""; configurationDTO.label = "";
@ -28,12 +30,12 @@ Future<ConfigurationDTO?> showNewConfiguration(AppContext appContext, ValueChang
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Center(child: Text("Nouvelle configuration", style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400))), Center(child: Text(l.newConfiguration, style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400))),
Center( Center(
child: SizedBox( child: SizedBox(
height: 100, height: 100,
child: StringInputContainer( child: StringInputContainer(
label: "Nom :", label: l.configNameLabel,
initialValue: configurationDTO.label, initialValue: configurationDTO.label,
onChanged: (value) { onChanged: (value) {
configurationDTO.label = value; configurationDTO.label = value;
@ -41,12 +43,12 @@ Future<ConfigurationDTO?> showNewConfiguration(AppContext appContext, ValueChang
), ),
), ),
), ),
Text("ou"), Text(l.orText),
Column( Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(bottom: 5.0), padding: const EdgeInsets.only(bottom: 5.0),
child: Text("Importer", style: TextStyle(fontSize: 25, fontWeight: FontWeight.w300)), child: Text(l.importLabel, style: TextStyle(fontSize: 25, fontWeight: FontWeight.w300)),
), ),
InkWell( InkWell(
onTap: () async { onTap: () async {
@ -85,7 +87,7 @@ Future<ConfigurationDTO?> showNewConfiguration(AppContext appContext, ValueChang
width: 175, width: 175,
height: 70, height: 70,
child: RoundedButton( child: RoundedButton(
text: "Annuler", text: l.cancel,
icon: Icons.undo, icon: Icons.undo,
color: kSecond, color: kSecond,
press: () { press: () {
@ -101,7 +103,7 @@ Future<ConfigurationDTO?> showNewConfiguration(AppContext appContext, ValueChang
width: 150, width: 150,
height: 70, height: 70,
child: RoundedButton( child: RoundedButton(
text: "Créer", text: l.create,
icon: Icons.check, icon: Icons.check,
color: kPrimaryColor, color: kPrimaryColor,
textColor: kWhite, textColor: kWhite,
@ -110,7 +112,7 @@ Future<ConfigurationDTO?> showNewConfiguration(AppContext appContext, ValueChang
//create(configurationDTO, appContext, context); //create(configurationDTO, appContext, context);
Navigator.of(context).pop(configurationDTO); Navigator.of(context).pop(configurationDTO);
} else { } else {
showNotification(Colors.orange, kWhite, 'Veuillez spécifier un nom pour la nouvelle visite', context, null); showNotification(Colors.orange, kWhite, l.configNameRequired, context, null);
} }
}, },
fontSize: 20, fontSize: 20,

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:manager_app/Components/dropDown_input_container.dart'; import 'package:manager_app/Components/dropDown_input_container.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Components/message_notification.dart';
import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/Components/string_input_container.dart'; import 'package:manager_app/Components/string_input_container.dart';
@ -10,6 +11,7 @@ import 'package:manager_api_new/api.dart';
Future<SectionDTO?> showNewSection(String configurationId, Future<SectionDTO?> showNewSection(String configurationId,
AppContext appContext, BuildContext contextBuild, bool isSubSection) { AppContext appContext, BuildContext contextBuild, bool isSubSection) {
final l = AppLocalizations.of(contextBuild)!;
SectionDTO sectionDTO = new SectionDTO(); SectionDTO sectionDTO = new SectionDTO();
sectionDTO.label = ""; sectionDTO.label = "";
sectionDTO.configurationId = configurationId; sectionDTO.configurationId = configurationId;
@ -18,7 +20,6 @@ Future<SectionDTO?> showNewSection(String configurationId,
sectionDTO.parentId = isSubSection sectionDTO.parentId = isSubSection
? (appContext.getContext() as ManagerAppContext).selectedSection!.id ? (appContext.getContext() as ManagerAppContext).selectedSection!.id
: null; : null;
Size size = MediaQuery.of(contextBuild).size;
sectionDTO.type = SectionType.Map; sectionDTO.type = SectionType.Map;
var section = showDialog<SectionDTO?>( var section = showDialog<SectionDTO?>(
@ -35,8 +36,8 @@ Future<SectionDTO?> showNewSection(String configurationId,
children: [ children: [
Text( Text(
isSubSection isSubSection
? "Nouvelle sous section" ? l.newSubSection
: "Nouvelle section", : l.newSection,
style: new TextStyle( style: new TextStyle(
fontSize: 25, fontWeight: FontWeight.w400)), fontSize: 25, fontWeight: FontWeight.w400)),
Column( Column(
@ -44,7 +45,7 @@ Future<SectionDTO?> showNewSection(String configurationId,
SizedBox( SizedBox(
height: 100, height: 100,
child: StringInputContainer( child: StringInputContainer(
label: "Nom :", label: l.sectionNameLabel,
initialValue: sectionDTO.label, initialValue: sectionDTO.label,
onChanged: (value) { onChanged: (value) {
sectionDTO.label = value; sectionDTO.label = value;
@ -52,7 +53,7 @@ Future<SectionDTO?> showNewSection(String configurationId,
), ),
), ),
DropDownInputContainer( DropDownInputContainer(
label: "Type:", label: l.sectionTypeLabel,
values: isSubSection values: isSubSection
? section_types ? section_types
.where( .where(
@ -92,7 +93,7 @@ Future<SectionDTO?> showNewSection(String configurationId,
width: 175, width: 175,
height: 70, height: 70,
child: RoundedButton( child: RoundedButton(
text: "Annuler", text: l.cancel,
icon: Icons.undo, icon: Icons.undo,
color: kSecond, color: kSecond,
press: () { press: () {
@ -108,7 +109,7 @@ Future<SectionDTO?> showNewSection(String configurationId,
width: 150, width: 150,
height: 70, height: 70,
child: RoundedButton( child: RoundedButton(
text: "Créer", text: l.create,
icon: Icons.check, icon: Icons.check,
color: kPrimaryColor, color: kPrimaryColor,
textColor: kWhite, textColor: kWhite,
@ -122,7 +123,7 @@ Future<SectionDTO?> showNewSection(String configurationId,
showNotification( showNotification(
Colors.orange, Colors.orange,
kWhite, kWhite,
'Veuillez spécifier un nom pour la nouvelle section', l.sectionNameRequired,
context, context,
null); null);
} }
@ -156,7 +157,7 @@ void create(SectionDTO sectionDTO, AppContext appContext, BuildContext context,
} }
managerAppContext.selectedConfiguration.sectionIds.add(newSection.id);*/ managerAppContext.selectedConfiguration.sectionIds.add(newSection.id);*/
appContext.setContext(managerAppContext); appContext.setContext(managerAppContext);
showNotification(kSuccess, kWhite, 'La section a été créée avec succès !', showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.sectionCreatedSuccess,
context, null); context, null);
} else { } else {
sendSubSection!(newSection); sendSubSection!(newSection);

View File

@ -9,6 +9,7 @@ import 'package:manager_app/Components/string_input_container.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import '../../Components/resource_input_container.dart'; import '../../Components/resource_input_container.dart';
@ -73,7 +74,7 @@ showChangeInfo (String text, DeviceDTO inputDevice, AppConfigurationLinkDTO appC
], ],
); );
} else { } else {
return Text("Aucune configuration trouvée"); return Text(AppLocalizations.of(context)!.noConfigFound);
} }
} else if (snapshot.connectionState == ConnectionState.none) { } else if (snapshot.connectionState == ConnectionState.none) {
@ -104,7 +105,7 @@ showChangeInfo (String text, DeviceDTO inputDevice, AppConfigurationLinkDTO appC
}, },
), ),
ColorPickerInputContainer( ColorPickerInputContainer(
label: "Couleur fond d'écran :", label: AppLocalizations.of(context)!.backgroundColorLabel,
fontSize: 20, fontSize: 20,
color: appConfiguration.secondaryColor, color: appConfiguration.secondaryColor,
onChanged: (value) { onChanged: (value) {

View File

@ -6,6 +6,7 @@ import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/Screens/Kiosk_devices/change_device_info_modal.dart'; import 'package:manager_app/Screens/Kiosk_devices/change_device_info_modal.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -85,7 +86,7 @@ class _DeviceElementState extends State<DeviceElement> {
icon: Icon(Icons.edit), icon: Icon(Icons.edit),
onPressed: () { onPressed: () {
showChangeInfo( showChangeInfo(
"Mettre à jour la tablette", AppLocalizations.of(context)!.updateTabletBtn,
widget.deviceDTO, widget.deviceDTO,
widget.appConfigurationLinkDTO, widget.appConfigurationLinkDTO,
(DeviceDTO outputDevice, AppConfigurationLinkDTO outputAppConfig) async { (DeviceDTO outputDevice, AppConfigurationLinkDTO outputAppConfig) async {
@ -120,7 +121,7 @@ class _DeviceElementState extends State<DeviceElement> {
AppConfigurationLinkDTO? result = await managerAppContext.clientAPI!.applicationInstanceApi!.applicationInstanceUpdateApplicationLink(appConfigurationToUpdate); AppConfigurationLinkDTO? result = await managerAppContext.clientAPI!.applicationInstanceApi!.applicationInstanceUpdateApplicationLink(appConfigurationToUpdate);
//print(device); //print(device);
showNotification(kSuccess, kWhite, "Le kiosk a été mis à jour", context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.kioskUpdatedSuccess, context, null);
return device; return device;
} }

View File

@ -1,5 +1,6 @@
import 'package:auto_size_text/auto_size_text.dart'; import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/Components/common_loader.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/Screens/Applications/app_configuration_link_screen.dart'; import 'package:manager_app/Screens/Applications/app_configuration_link_screen.dart';
@ -35,7 +36,7 @@ class _KioskScreenState extends State<KioskScreen> {
child: Align( child: Align(
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
child: AutoSizeText( child: AutoSizeText(
"Code pin: " + managerAppContext.pinCode.toString(), AppLocalizations.of(context)!.pinCode(managerAppContext.pinCode.toString()),
style: TextStyle(fontSize: 25.0, fontWeight: FontWeight.w300), style: TextStyle(fontSize: 25.0, fontWeight: FontWeight.w300),
maxLines: 2, maxLines: 2,
maxFontSize: 25.0, maxFontSize: 25.0,
@ -49,7 +50,7 @@ class _KioskScreenState extends State<KioskScreen> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text("Tablettes"), child: Text(AppLocalizations.of(context)!.tablets),
) )
), ),
FutureBuilder( FutureBuilder(
@ -179,7 +180,7 @@ class _KioskScreenState extends State<KioskScreen> {
icon: Icon(Icons.edit), icon: Icon(Icons.edit),
onPressed: () { onPressed: () {
showSelectConfigModal( showSelectConfigModal(
"Sélectionner une configuration", AppLocalizations.of(context)!.selectConfiguration,
(String configurationId) { (String configurationId) {
device.configurationId = configurationId; device.configurationId = configurationId;
@ -201,7 +202,7 @@ class _KioskScreenState extends State<KioskScreen> {
) : InkWell( ) : InkWell(
onTap: () { onTap: () {
showSelectConfigModal( showSelectConfigModal(
"Sélectionner une configuration", AppLocalizations.of(context)!.selectConfiguration,
(String configurationId) { (String configurationId) {
device.configurationId = configurationId; device.configurationId = configurationId;
@ -226,7 +227,7 @@ class _KioskScreenState extends State<KioskScreen> {
padding: const EdgeInsets.only(left: 5, right: 5, top: 15, bottom: 15), padding: const EdgeInsets.only(left: 5, right: 5, top: 15, bottom: 15),
child: Center( child: Center(
child: AutoSizeText( child: AutoSizeText(
"Sélectionner", AppLocalizations.of(context)!.choose,
style: TextStyle(color: kWhite), style: TextStyle(color: kWhite),
maxLines: 1, maxLines: 1,
) )

View File

@ -16,7 +16,9 @@ import 'package:manager_app/Screens/Statistics/statistics_screen.dart';
import 'package:manager_app/Screens/Applications/app_configuration_link_screen.dart'; import 'package:manager_app/Screens/Applications/app_configuration_link_screen.dart';
import 'package:manager_app/Screens/Notifications/notifications_screen.dart'; import 'package:manager_app/Screens/Notifications/notifications_screen.dart';
import 'package:manager_app/Screens/Users/users_screen.dart'; import 'package:manager_app/Screens/Users/users_screen.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/Components/quota_bars_widget.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/main.dart'; import 'package:manager_app/main.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
@ -81,6 +83,100 @@ class _MainScreenState extends State<MainScreen> {
selectedElement = initElementToShow(context, widget.view, currentPosition.value!, menu, widget.instance); selectedElement = initElementToShow(context, widget.view, currentPosition.value!, menu, widget.instance);
} }
Future<void> _showAssignPlanDialog(BuildContext context, ManagerAppContext managerCtx, Instance inst, {void Function()? onSaved}) async {
List<SubscriptionPlanDTO>? plans;
InstanceDTO? instanceDetail;
try {
plans = await managerCtx.clientAPI!.subscriptionPlanApi!.subscriptionPlanGet();
instanceDetail = await managerCtx.clientAPI!.instanceApi!.instanceGetDetail(inst.id);
} catch (_) {}
if (!context.mounted) return;
final planList = plans ?? [];
String? selectedPlanId = instanceDetail?.subscriptionPlanId;
showDialog(
context: context,
builder: (dialogContext) => StatefulBuilder(
builder: (dialogContext, setDialogState) => AlertDialog(
title: Text('Plan — ${inst.name}'),
content: SizedBox(
width: 360,
child: planList.isEmpty
? Text(AppLocalizations.of(context)!.noPlansAvailable)
: Column(
mainAxisSize: MainAxisSize.min,
children: [
RadioListTile<String?>(
title: Text(AppLocalizations.of(context)!.noPlan),
value: null,
groupValue: selectedPlanId,
onChanged: (v) => setDialogState(() => selectedPlanId = v),
),
...planList.map((plan) => RadioListTile<String?>(
title: Text(plan.name),
subtitle: Text(
'${_formatQuotaBytes(plan.storageQuotaBytes, unlimitedLabel: AppLocalizations.of(dialogContext)!.unlimitedStorage)} · ${plan.aiRequestsPerMonth == 0 ? AppLocalizations.of(dialogContext)!.unlimitedAI : AppLocalizations.of(dialogContext)!.aiRequestsPerMonth(plan.aiRequestsPerMonth ?? 0)}',
style: const TextStyle(fontSize: 11),
),
value: plan.id,
groupValue: selectedPlanId,
onChanged: (v) => setDialogState(() => selectedPlanId = v),
)),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext, rootNavigator: true).pop(),
child: Text(AppLocalizations.of(dialogContext)!.cancel),
),
TextButton(
onPressed: () async {
Navigator.of(dialogContext, rootNavigator: true).pop();
try {
await managerCtx.clientAPI!.instanceApi!.instanceUpdateinstance(
InstanceDTO(
id: instanceDetail?.id,
name: instanceDetail?.name,
pinCode: instanceDetail?.pinCode,
isPushNotification: instanceDetail?.isPushNotification,
isStatistic: instanceDetail?.isStatistic,
isMobile: instanceDetail?.isMobile,
isTablet: instanceDetail?.isTablet,
isWeb: instanceDetail?.isWeb,
isVR: instanceDetail?.isVR,
isAssistant: instanceDetail?.isAssistant,
aiRequestsThisMonth: instanceDetail?.aiRequestsThisMonth,
aiUsageMonthKey: instanceDetail?.aiUsageMonthKey,
subscriptionPlanId: selectedPlanId ?? '',
),
);
onSaved?.call();
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.planUpdated)));
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.errorMessage(e.toString()))));
}
}
},
child: Text(AppLocalizations.of(dialogContext)!.save),
),
],
),
),
);
}
static String _formatQuotaBytes(int? bytes, {String? unlimitedLabel}) {
if (bytes == null || bytes == 0) return unlimitedLabel ?? 'Stockage illimité';
if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(0)} MB';
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
}
Future<void> _showSwitchInstanceDialog(BuildContext context, ManagerAppContext managerCtx) async { Future<void> _showSwitchInstanceDialog(BuildContext context, ManagerAppContext managerCtx) async {
List<Instance>? instances; List<Instance>? instances;
try { try {
@ -94,11 +190,11 @@ class _MainScreenState extends State<MainScreen> {
showDialog( showDialog(
context: context, context: context,
builder: (_) => AlertDialog( builder: (_) => AlertDialog(
title: const Text('Changer d\'instance'), title: Text(AppLocalizations.of(context)!.switchInstance),
content: SizedBox( content: SizedBox(
width: 400, width: 400,
child: instanceList.isEmpty child: instanceList.isEmpty
? const Text('Aucune instance trouvée') ? Text(AppLocalizations.of(context)!.noInstanceFound)
: ListView.builder( : ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: instanceList.length, itemCount: instanceList.length,
@ -117,7 +213,19 @@ class _MainScreenState extends State<MainScreen> {
fontWeight: isCurrent ? FontWeight.bold : FontWeight.normal, fontWeight: isCurrent ? FontWeight.bold : FontWeight.normal,
), ),
), ),
trailing: isCurrent ? const Icon(Icons.check, color: Colors.green) : null, trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (isCurrent) const Icon(Icons.check, color: Colors.green),
IconButton(
icon: const Icon(Icons.edit_outlined, size: 18),
tooltip: AppLocalizations.of(context)!.configurePlan,
onPressed: () => _showAssignPlanDialog(context, managerCtx, inst, onSaved: () {
Navigator.of(context, rootNavigator: true).pop();
}),
),
],
),
onTap: isCurrent onTap: isCurrent
? null ? null
: () async { : () async {
@ -141,12 +249,26 @@ class _MainScreenState extends State<MainScreen> {
), ),
), ),
actions: [ actions: [
TextButton(onPressed: () => Navigator.of(context, rootNavigator: true).pop(), child: const Text('Fermer')), TextButton(onPressed: () => Navigator.of(context, rootNavigator: true).pop(), child: Text(AppLocalizations.of(context)!.close)),
], ],
), ),
); );
} }
static String _localizedSectionName(BuildContext context, String type) {
final l = AppLocalizations.of(context)!;
switch (type) {
case 'devices': return l.menuApplications;
case 'configurations': return l.menuConfigurations;
case 'resources': return l.menuResources;
case 'statistics': return l.menuStatistics;
case 'notifications': return l.menuNotifications;
case 'users': return l.menuUsers;
case 'apikeys': return l.menuApiKeys;
default: return type;
}
}
static IconData _sectionIcon(String type) { static IconData _sectionIcon(String type) {
switch (type) { switch (type) {
case 'devices': return Icons.apps; case 'devices': return Icons.apps;
@ -228,7 +350,7 @@ class _MainScreenState extends State<MainScreen> {
color: currentPath.contains(section.type) ? kPrimaryColor : kBodyTextColor, color: currentPath.contains(section.type) ? kPrimaryColor : kBodyTextColor,
size: 22, size: 22,
), ),
title: Text(section.name, style: TextStyle(color: currentPath.contains(section.type) ? kPrimaryColor : kBodyTextColor, fontSize: 22, fontWeight: currentPath.contains(section.type) ? FontWeight.w500 : FontWeight.w100)), title: Text(_localizedSectionName(context, section.type), style: TextStyle(color: currentPath.contains(section.type) ? kPrimaryColor : kBodyTextColor, fontSize: 22, fontWeight: currentPath.contains(section.type) ? FontWeight.w500 : FontWeight.w100)),
selected: currentPosition == section.menuId, selected: currentPosition == section.menuId,
onTap: () { onTap: () {
WidgetsBinding.instance.addPostFrameCallback((_) => WidgetsBinding.instance.addPostFrameCallback((_) =>
@ -252,7 +374,7 @@ class _MainScreenState extends State<MainScreen> {
), ),
iconColor: isAppActive ? kPrimaryColor : kBodyTextColor, iconColor: isAppActive ? kPrimaryColor : kBodyTextColor,
collapsedIconColor: isAppActive ? kPrimaryColor : kBodyTextColor, collapsedIconColor: isAppActive ? kPrimaryColor : kBodyTextColor,
title: Text(section.name, style: TextStyle(color: isAppActive ? kPrimaryColor : kBodyTextColor, fontSize: 22, fontWeight: isAppActive ? FontWeight.w500 : FontWeight.w100)), title: Text(_localizedSectionName(context, section.type), style: TextStyle(color: isAppActive ? kPrimaryColor : kBodyTextColor, fontSize: 22, fontWeight: isAppActive ? FontWeight.w500 : FontWeight.w100)),
children: section.subMenu.map((subSection) { children: section.subMenu.map((subSection) {
return Container( return Container(
decoration: currentPath.contains(subSection.type) decoration: currentPath.contains(subSection.type)
@ -276,7 +398,7 @@ class _MainScreenState extends State<MainScreen> {
subSection.type == "vr" ? Icon(Icons.panorama_photosphere, color: currentPath.contains(subSection.type)? kPrimaryColor : kBodyTextColor, size: 20) : SizedBox(), subSection.type == "vr" ? Icon(Icons.panorama_photosphere, color: currentPath.contains(subSection.type)? kPrimaryColor : kBodyTextColor, size: 20) : SizedBox(),
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text(subSection.name, style: TextStyle(color: currentPath.contains(subSection.type) ? kPrimaryColor : kBodyTextColor, fontSize: 18)), child: Text(_localizedSectionName(context, subSection.type), style: TextStyle(color: currentPath.contains(subSection.type) ? kPrimaryColor : kBodyTextColor, fontSize: 18)),
) )
], ],
), ),
@ -302,11 +424,26 @@ class _MainScreenState extends State<MainScreen> {
), ),
), ),
), ),
// Footer: Email + Switch instance (SuperAdmin) + Logout // Footer: Quota + Language + Email + Switch instance (SuperAdmin) + Logout
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Column( child: Column(
children: [ children: [
const QuotaBarsWidget(),
DropdownButton<Locale>(
value: managerAppContext.locale,
underline: const SizedBox(),
isDense: true,
items: const [
DropdownMenuItem(value: Locale('fr'), child: Text('🇫🇷 FR')),
DropdownMenuItem(value: Locale('en'), child: Text('🇬🇧 EN')),
DropdownMenuItem(value: Locale('nl'), child: Text('🇧🇪 NL')),
],
onChanged: (locale) {
if (locale != null) appContext.setLocale(locale);
},
),
const SizedBox(height: 4),
AutoSizeText( AutoSizeText(
(appContext.getContext() as ManagerAppContext).email ?? "", (appContext.getContext() as ManagerAppContext).email ?? "",
style: TextStyle(color: kBodyTextColor, fontSize: 16, fontWeight: FontWeight.w300, fontFamily: "Helvetica"), style: TextStyle(color: kBodyTextColor, fontSize: 16, fontWeight: FontWeight.w300, fontFamily: "Helvetica"),
@ -315,7 +452,7 @@ class _MainScreenState extends State<MainScreen> {
if ((appContext.getContext() as ManagerAppContext).role == UserRole.SuperAdmin) if ((appContext.getContext() as ManagerAppContext).role == UserRole.SuperAdmin)
IconButton( IconButton(
icon: Icon(Icons.swap_horiz, color: kPrimaryColor), icon: Icon(Icons.swap_horiz, color: kPrimaryColor),
tooltip: 'Changer d\'instance', tooltip: AppLocalizations.of(context)!.tooltipSwitchInstance,
onPressed: () => _showSwitchInstanceDialog(context, appContext.getContext() as ManagerAppContext), onPressed: () => _showSwitchInstanceDialog(context, appContext.getContext() as ManagerAppContext),
), ),
IconButton( IconButton(

View File

@ -1,8 +1,10 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/Components/common_loader.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -17,17 +19,15 @@ class NotificationsScreen extends StatefulWidget {
class _NotificationsScreenState extends State<NotificationsScreen> { class _NotificationsScreenState extends State<NotificationsScreen> {
List<PushNotificationDTO> _notifications = []; List<PushNotificationDTO> _notifications = [];
bool _loading = true; bool _loading = true;
String? _statusFilter; // null = toutes String? _statusFilter;
int _page = 0; int _page = 0;
static const _pageSize = 15; static const _pageSize = 15;
// KPI counts
int get _totalCount => _notifications.length; int get _totalCount => _notifications.length;
int get _sentCount => _notifications.where((n) => n.status == 'Sent').length; int get _sentCount => _notifications.where((n) => n.status == 'Sent').length;
int get _scheduledCount => _notifications.where((n) => n.status == 'Scheduled').length; int get _scheduledCount => _notifications.where((n) => n.status == 'Scheduled').length;
int get _failedCount => _notifications.where((n) => n.status == 'Failed').length; int get _failedCount => _notifications.where((n) => n.status == 'Failed').length;
// Filtre + pagination
List<PushNotificationDTO> get _filtered => _statusFilter == null List<PushNotificationDTO> get _filtered => _statusFilter == null
? _notifications ? _notifications
: _notifications.where((n) => n.status == _statusFilter).toList(); : _notifications.where((n) => n.status == _statusFilter).toList();
@ -43,7 +43,6 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
}); });
} }
// API helpers
Future<void> _load(ManagerAppContext ctx) async { Future<void> _load(ManagerAppContext ctx) async {
try { try {
final response = await ctx.clientAPI!.notificationApi!.notificationGetWithHttpInfo(); final response = await ctx.clientAPI!.notificationApi!.notificationGetWithHttpInfo();
@ -78,8 +77,8 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
return response.statusCode == 202; return response.statusCode == 202;
} }
// Dialogs
void _showSendModal(BuildContext context, ManagerAppContext ctx) { void _showSendModal(BuildContext context, ManagerAppContext ctx) {
final l = AppLocalizations.of(context)!;
final titleCtrl = TextEditingController(); final titleCtrl = TextEditingController();
final bodyCtrl = TextEditingController(); final bodyCtrl = TextEditingController();
bool isScheduled = false; bool isScheduled = false;
@ -90,18 +89,18 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
context: context, context: context,
builder: (_) => StatefulBuilder(builder: (ctx2, setLocal) { builder: (_) => StatefulBuilder(builder: (ctx2, setLocal) {
return AlertDialog( return AlertDialog(
title: const Text('Nouveau message'), title: Text(l.newMessage),
content: SizedBox( content: SizedBox(
width: 480, width: 480,
child: Column(mainAxisSize: MainAxisSize.min, children: [ child: Column(mainAxisSize: MainAxisSize.min, children: [
TextField( TextField(
controller: titleCtrl, controller: titleCtrl,
decoration: const InputDecoration(labelText: 'Titre'), decoration: InputDecoration(labelText: l.messageTitle),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
TextField( TextField(
controller: bodyCtrl, controller: bodyCtrl,
decoration: const InputDecoration(labelText: 'Message'), decoration: InputDecoration(labelText: l.messageBody),
maxLines: 3, maxLines: 3,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@ -111,7 +110,7 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
value: isScheduled, value: isScheduled,
onChanged: (v) => setLocal(() => isScheduled = v ?? false), onChanged: (v) => setLocal(() => isScheduled = v ?? false),
), ),
const Text('Planifier'), Text(l.schedule),
], ],
), ),
if (isScheduled) ...[ if (isScheduled) ...[
@ -122,7 +121,7 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
icon: const Icon(Icons.calendar_today, size: 16), icon: const Icon(Icons.calendar_today, size: 16),
label: Text(scheduledDate != null label: Text(scheduledDate != null
? '${scheduledDate!.day.toString().padLeft(2, '0')}/${scheduledDate!.month.toString().padLeft(2, '0')}/${scheduledDate!.year}' ? '${scheduledDate!.day.toString().padLeft(2, '0')}/${scheduledDate!.month.toString().padLeft(2, '0')}/${scheduledDate!.year}'
: 'Date'), : l.date),
onPressed: () async { onPressed: () async {
final d = await showDatePicker( final d = await showDatePicker(
context: ctx2, context: ctx2,
@ -140,7 +139,7 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
icon: const Icon(Icons.access_time, size: 16), icon: const Icon(Icons.access_time, size: 16),
label: Text(scheduledTime != null label: Text(scheduledTime != null
? scheduledTime!.format(ctx2) ? scheduledTime!.format(ctx2)
: 'Heure'), : l.time),
onPressed: () async { onPressed: () async {
final t = await showTimePicker( final t = await showTimePicker(
context: ctx2, context: ctx2,
@ -157,7 +156,7 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(ctx2), onPressed: () => Navigator.pop(ctx2),
child: const Text('Annuler'), child: Text(l.cancel),
), ),
ElevatedButton( ElevatedButton(
onPressed: titleCtrl.text.isEmpty ? null : () async { onPressed: titleCtrl.text.isEmpty ? null : () async {
@ -179,7 +178,7 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
await _load(ctx); await _load(ctx);
} }
}, },
child: const Text('Envoyer'), child: Text(l.send),
), ),
], ],
); );
@ -188,13 +187,14 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
} }
void _confirmCancel(BuildContext context, ManagerAppContext ctx, PushNotificationDTO notif) { void _confirmCancel(BuildContext context, ManagerAppContext ctx, PushNotificationDTO notif) {
final l = AppLocalizations.of(context)!;
showDialog( showDialog(
context: context, context: context,
builder: (_) => AlertDialog( builder: (_) => AlertDialog(
title: const Text('Annuler la notification'), title: Text(l.cancelNotification),
content: Text('Annuler « ${notif.title} » ?'), content: Text(l.cancelNotificationConfirm(notif.title ?? '')),
actions: [ actions: [
TextButton(onPressed: () => Navigator.pop(context), child: const Text('Non')), TextButton(onPressed: () => Navigator.pop(context), child: Text(l.no)),
ElevatedButton( ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red), style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
onPressed: () async { onPressed: () async {
@ -202,14 +202,13 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
final ok = await _cancel(ctx, notif.id!); final ok = await _cancel(ctx, notif.id!);
if (ok && context.mounted) await _load(ctx); if (ok && context.mounted) await _load(ctx);
}, },
child: const Text('Annuler', style: TextStyle(color: Colors.white)), child: Text(l.cancel, style: const TextStyle(color: Colors.white)),
), ),
], ],
), ),
); );
} }
// Helpers
static String _formatDate(DateTime? dt) { static String _formatDate(DateTime? dt) {
if (dt == null) return ''; if (dt == null) return '';
final d = dt.toLocal(); final d = dt.toLocal();
@ -245,7 +244,6 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
); );
} }
// Build
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -257,32 +255,30 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l = AppLocalizations.of(context)!;
final appContext = Provider.of<AppContext>(context); final appContext = Provider.of<AppContext>(context);
final ctx = appContext.getContext() as ManagerAppContext; final ctx = appContext.getContext() as ManagerAppContext;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Header
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('Notifications', Text(l.notificationsTitle,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: kPrimaryColor)), style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: kPrimaryColor)),
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () => _showSendModal(context, ctx), onPressed: () => _showSendModal(context, ctx),
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: const Text('Nouveau message'), label: Text(l.newMessage),
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// KPIs
Row( Row(
children: [ children: [
_KpiCard( _KpiCard(
label: 'Toutes', label: l.allNotifications,
count: _totalCount, count: _totalCount,
color: Colors.blueGrey, color: Colors.blueGrey,
selected: _statusFilter == null, selected: _statusFilter == null,
@ -290,7 +286,7 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
_KpiCard( _KpiCard(
label: 'Envoyées', label: l.sentNotifications,
count: _sentCount, count: _sentCount,
color: Colors.green, color: Colors.green,
selected: _statusFilter == 'Sent', selected: _statusFilter == 'Sent',
@ -298,7 +294,7 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
_KpiCard( _KpiCard(
label: 'Planifiées', label: l.scheduledNotifications,
count: _scheduledCount, count: _scheduledCount,
color: Colors.blue, color: Colors.blue,
selected: _statusFilter == 'Scheduled', selected: _statusFilter == 'Scheduled',
@ -306,7 +302,7 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
_KpiCard( _KpiCard(
label: 'Échouées', label: l.failedNotifications,
count: _failedCount, count: _failedCount,
color: Colors.red, color: Colors.red,
selected: _statusFilter == 'Failed', selected: _statusFilter == 'Failed',
@ -315,14 +311,12 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// List
if (_loading) if (_loading)
const Center(child: CircularProgressIndicator()) const CommonLoader()
else if (_notifications.isEmpty) else if (_notifications.isEmpty)
const Center(child: Text('Aucune notification envoyée')) Center(child: Text(l.noNotifications))
else if (_filtered.isEmpty) else if (_filtered.isEmpty)
const Center(child: Text('Aucune notification pour ce filtre')) Center(child: Text(l.noNotificationsForFilter))
else else
Expanded( Expanded(
child: Card( child: Card(
@ -344,12 +338,12 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
columnSpacing: 24, columnSpacing: 24,
headingRowColor: WidgetStateProperty.all(Colors.grey.shade50), headingRowColor: WidgetStateProperty.all(Colors.grey.shade50),
dividerThickness: 1, dividerThickness: 1,
columns: const [ columns: [
DataColumn(label: Text('Titre', style: TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.messageTitle, style: const TextStyle(fontWeight: FontWeight.w600))),
DataColumn(label: Text('Topic', style: TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.topic, style: const TextStyle(fontWeight: FontWeight.w600))),
DataColumn(label: Text('Date', style: TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.date, style: const TextStyle(fontWeight: FontWeight.w600))),
DataColumn(label: Text('Statut', style: TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.status, style: const TextStyle(fontWeight: FontWeight.w600))),
DataColumn(label: Text('')), const DataColumn(label: Text('')),
], ],
rows: _paginated.map((n) { rows: _paginated.map((n) {
final isScheduled = n.status == 'Scheduled'; final isScheduled = n.status == 'Scheduled';
@ -362,7 +356,7 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
DataCell(isScheduled DataCell(isScheduled
? IconButton( ? IconButton(
icon: const Icon(Icons.delete_outline, color: Colors.red, size: 20), icon: const Icon(Icons.delete_outline, color: Colors.red, size: 20),
tooltip: 'Annuler', tooltip: l.tooltipCancelNotification,
onPressed: () => _confirmCancel(context, ctx, n), onPressed: () => _confirmCancel(context, ctx, n),
) )
: const SizedBox()), : const SizedBox()),
@ -372,7 +366,6 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
), ),
), ),
), ),
// Pagination
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border(top: BorderSide(color: Colors.grey.shade200)), border: Border(top: BorderSide(color: Colors.grey.shade200)),
@ -392,7 +385,7 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'${_filtered.length} résultat${_filtered.length > 1 ? 's' : ''}', l.resultsCount(_filtered.length),
style: const TextStyle(fontSize: 12, color: Colors.grey), style: const TextStyle(fontSize: 12, color: Colors.grey),
), ),
], ],

View File

@ -3,6 +3,7 @@ import 'dart:ui' as ui;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:manager_app/l10n/app_localizations.dart';
class WebView extends StatefulWidget { class WebView extends StatefulWidget {
WebView({required this.htmlText}); WebView({required this.htmlText});
@ -63,5 +64,5 @@ class _WebViewWidget extends State<WebView> {
) : ) :
//WebViewWidget(controller: controller!) //WebViewWidget(controller: controller!)
Text("Texte"): Text("Texte"):
Center(child: Text("La page internet ne peut pas être affichée, l'url est incorrecte ou vide")); Center(child: Text(AppLocalizations.of(context)!.webViewError));
} //_webView } //_webView

View File

@ -1,6 +1,7 @@
import 'package:manager_app/Components/audio_player.dart'; import 'package:manager_app/Components/audio_player.dart';
import 'package:manager_app/Components/video_viewer.dart'; import 'package:manager_app/Components/video_viewer.dart';
import 'package:manager_app/Components/video_viewer_youtube.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
@ -87,14 +88,11 @@ getElementForResource(dynamic resourceDTO, AppContext appContext) {
} }
case ResourceType.VideoUrl: case ResourceType.VideoUrl:
return SelectableText(resourceDTO.url!);
/*case ResourceType.VideoUrl:
if(resourceDTO.url == null) { if(resourceDTO.url == null) {
return Center(child: Text("Error loading video")); return Center(child: Text("Error loading video"));
} else { } else {
return VideoViewerYoutube(videoUrl: resourceDTO.url!); return VideoViewerYoutube(videoUrl: resourceDTO.url!);
}*/ // TODO }
case ResourceType.Pdf: case ResourceType.Pdf:
return Text("Fichier pdf - aucune visualisation possible"); return Text("Fichier pdf - aucune visualisation possible");

View File

@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
dynamic showNewResource(AppContext appContext, BuildContext context) async { dynamic showNewResource(AppContext appContext, BuildContext context) async {
@ -27,7 +28,7 @@ dynamic showNewResource(AppContext appContext, BuildContext context) async {
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
Text("Nouvelle ressource", style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)), Text(AppLocalizations.of(context)!.newResource, style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)),
/*Column( /*Column(
children: [ children: [
StringInputContainer( StringInputContainer(
@ -70,7 +71,7 @@ dynamic showNewResource(AppContext appContext, BuildContext context) async {
width: 175, width: 175,
height: 70, height: 70,
child: RoundedButton( child: RoundedButton(
text: "Annuler", text: AppLocalizations.of(context)!.cancel,
icon: Icons.undo, icon: Icons.undo,
color: kSecond, color: kSecond,
press: () { press: () {
@ -86,7 +87,7 @@ dynamic showNewResource(AppContext appContext, BuildContext context) async {
width: 150, width: 150,
height: 70, height: 70,
child: RoundedButton( child: RoundedButton(
text: "Créer", text: AppLocalizations.of(context)!.create,
icon: Icons.check, icon: Icons.check,
color: kPrimaryColor, color: kPrimaryColor,
textColor: kWhite, textColor: kWhite,
@ -96,13 +97,13 @@ dynamic showNewResource(AppContext appContext, BuildContext context) async {
if(resourceDetailDTO.url != null || filesToSendWeb != null) { // TODO clarify resourceDetailDTO.data != null if(resourceDetailDTO.url != null || filesToSendWeb != null) { // TODO clarify resourceDetailDTO.data != null
Navigator.pop(context, [resourceDetailDTO, filesToSend, filesToSendWeb]); Navigator.pop(context, [resourceDetailDTO, filesToSend, filesToSendWeb]);
} else { } else {
showNotification(Colors.orange, kWhite, 'Aucun fichier n\'a été chargé', context, null); showNotification(Colors.orange, kWhite, AppLocalizations.of(context)!.resourceNoFileLoaded, context, null);
} }
} else { } else {
if (resourceDetailDTO.url != null || filesToSendWeb!.length > 0 || filesToSend!.length > 0) { // TODO clarify resourceDetailDTO.data != null if (resourceDetailDTO.url != null || filesToSendWeb!.length > 0 || filesToSend!.length > 0) { // TODO clarify resourceDetailDTO.data != null
Navigator.pop(context, [resourceDetailDTO, filesToSend, filesToSendWeb]); Navigator.pop(context, [resourceDetailDTO, filesToSend, filesToSendWeb]);
} else { } else {
showNotification(Colors.orange, kWhite, 'Aucun fichier n\'a été chargé', context, null); showNotification(Colors.orange, kWhite, AppLocalizations.of(context)!.resourceNoFileLoaded, context, null);
} }
} }
/*} else { /*} else {

View File

@ -1,10 +1,13 @@
import 'dart:io'; import 'dart:io';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:firebase_storage/firebase_storage.dart'; import 'package:firebase_storage/firebase_storage.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/Components/common_loader.dart';
import 'package:manager_app/Components/message_notification.dart'; import 'package:manager_app/Components/message_notification.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/Screens/Resources/new_resource_popup.dart'; import 'package:manager_app/Screens/Resources/new_resource_popup.dart';
import 'package:manager_app/Screens/Resources/resource_body_grid.dart'; import 'package:manager_app/Screens/Resources/resource_body_grid.dart';
@ -103,7 +106,7 @@ class _ResourcesScreenState extends State<ResourcesScreen> {
var resourceUpdated = managerAppContext.clientAPI!.resourceApi!.resourceUpdate(result); var resourceUpdated = managerAppContext.clientAPI!.resourceApi!.resourceUpdate(result);
setState(() { setState(() {
// refresh ui // refresh ui
showNotification(kSuccess, kWhite, 'La ressource a été mise à jour avec succès', context, null); showNotification(kSuccess, kWhite, AppLocalizations.of(context)!.resourceUpdatedSuccess, context, null);
}); });
} catch (e) { } catch (e) {
print("Error during updating resource"); print("Error during updating resource");
@ -117,10 +120,10 @@ class _ResourcesScreenState extends State<ResourcesScreen> {
} }
});//bodyGrid(tempOutput, size, appContext); });//bodyGrid(tempOutput, size, appContext);
} else { } else {
return Text("No data"); return Text(AppLocalizations.of(context)!.noData);
} }
} else if (snapshot.connectionState == ConnectionState.none) { } else if (snapshot.connectionState == ConnectionState.none) {
return Text("No data"); return Text(AppLocalizations.of(context)!.noData);
} else { } else {
return Center( return Center(
child: Container( child: Container(
@ -149,6 +152,24 @@ Future<List<ResourceDTO?>?> create(ResourceDTO resourceDTO, List<File>? files, L
int index = 0; int index = 0;
if(filesWeb != null) { if(filesWeb != null) {
// Vérification quota stockage avant upload
if ((managerAppContext.instanceDTO?.storageQuotaBytes ?? 0) > 0) {
try {
final quotaUri = Uri.parse('${managerAppContext.host}/api/Instance/${managerAppContext.instanceId}/quota');
final quotaResponse = await http.get(quotaUri, headers: {'Authorization': 'Bearer ${managerAppContext.accessToken}'});
if (quotaResponse.statusCode == 200) {
final quotaData = jsonDecode(quotaResponse.body);
final storageUsed = (quotaData['storageUsedBytes'] as num).toInt();
final storageQuota = (quotaData['storageQuotaBytes'] as num).toInt();
final incomingBytes = filesWeb.fold<int>(0, (sum, f) => sum + (f.size));
if (storageUsed + incomingBytes > storageQuota) {
showNotification(kError, kWhite, 'Quota de stockage dépassé', context, null);
return null;
}
}
} catch (_) {}
}
for (PlatformFile platformFile in filesWeb) { for (PlatformFile platformFile in filesWeb) {
var mimeType = ""; var mimeType = "";
ResourceDTO resourceDTO = new ResourceDTO(label: platformFile.name); ResourceDTO resourceDTO = new ResourceDTO(label: platformFile.name);
@ -200,8 +221,9 @@ Future<List<ResourceDTO?>?> create(ResourceDTO resourceDTO, List<File>? files, L
UploadTask uploadTask = ref.putData(platformFile.bytes!, metadata); UploadTask uploadTask = ref.putData(platformFile.bytes!, metadata);
uploadTask.then((res) { uploadTask.then((res) {
res.ref.getDownloadURL().then((urlRessource) { res.ref.getDownloadURL().then((urlRessource) {
showNotification(Colors.green, kWhite, 'La ressource a été créée avec succès', context, null); showNotification(Colors.green, kWhite, AppLocalizations.of(context)!.resourceCreatedSuccess, context, null);
newResource.url = urlRessource; newResource.url = urlRessource;
newResource.sizeBytes = platformFile.size;
(appContext.getContext() as ManagerAppContext).clientAPI!.resourceApi!.resourceUpdate(newResource); (appContext.getContext() as ManagerAppContext).clientAPI!.resourceApi!.resourceUpdate(newResource);
createdResources.add(newResource); createdResources.add(newResource);
index++; index++;

View File

@ -7,6 +7,7 @@ import 'package:manager_app/Components/string_input_container.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'dart:html' as html; import 'dart:html' as html;
@ -110,7 +111,7 @@ Future<ResourceDTO?> showResource(ResourceDTO resourceDTO, AppContext appContext
width: 220, width: 220,
height: 70, height: 70,
child: RoundedButton( child: RoundedButton(
text: "Télécharger", text: AppLocalizations.of(context)!.download,
icon: Icons.download, icon: Icons.download,
color: kPrimaryColor, color: kPrimaryColor,
press: () { press: () {
@ -134,7 +135,7 @@ Future<ResourceDTO?> showResource(ResourceDTO resourceDTO, AppContext appContext
width: 220, width: 220,
height: 70, height: 70,
child: RoundedButton( child: RoundedButton(
text: "Sauvegarder", text: AppLocalizations.of(context)!.save,
icon: Icons.check, icon: Icons.check,
color: kPrimaryColor, color: kPrimaryColor,
press: () { press: () {
@ -157,7 +158,7 @@ Future<ResourceDTO?> showResource(ResourceDTO resourceDTO, AppContext appContext
Future<void> delete(ResourceDTO resourceDTO, AppContext appContext, context) async { Future<void> delete(ResourceDTO resourceDTO, AppContext appContext, context) async {
showConfirmationDialog( showConfirmationDialog(
"Êtes-vous sûr de vouloir supprimer cette ressource ?", AppLocalizations.of(context)!.resourceDeleteConfirm,
() {}, () {},
() async { () async {
await (appContext.getContext() as ManagerAppContext).clientAPI!.resourceApi!.resourceDelete(resourceDTO.id!); await (appContext.getContext() as ManagerAppContext).clientAPI!.resourceApi!.resourceDelete(resourceDTO.id!);

View File

@ -1,7 +1,9 @@
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/Components/common_loader.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:manager_api_new/api.dart'; import 'package:manager_api_new/api.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -55,20 +57,20 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildHeader(), _buildHeader(context),
const SizedBox(height: 16), const SizedBox(height: 16),
Expanded( Expanded(
child: FutureBuilder<StatsSummaryDTO?>( child: FutureBuilder<StatsSummaryDTO?>(
future: _future, future: _future,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator()); return const CommonLoader();
} }
if (snapshot.hasError || snapshot.data == null) { if (snapshot.hasError || snapshot.data == null) {
return Center(child: Text('Impossible de charger les statistiques', style: TextStyle(color: kBodyTextColor))); return Center(child: Text(AppLocalizations.of(context)!.statsLoadError, style: TextStyle(color: kBodyTextColor)));
} }
final stats = snapshot.data!; final stats = snapshot.data!;
return _buildDashboard(stats); return _buildDashboard(context, stats);
}, },
), ),
), ),
@ -77,23 +79,33 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
); );
} }
Widget _buildHeader() { bool get _hasAdvancedStats =>
_managerAppContext.instanceDTO?.hasAdvancedStats ?? false;
int get _statsHistoryDays =>
_managerAppContext.instanceDTO?.statsHistoryDays ?? 30;
Widget _buildHeader(BuildContext context) {
final l = AppLocalizations.of(context)!;
final historyDays = _statsHistoryDays;
final maxDays = historyDays == 0 ? 999 : historyDays;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('Statistiques', style: TextStyle(fontSize: 26, fontWeight: FontWeight.w600, color: kPrimaryColor)), Text(l.statisticsTitle, style: TextStyle(fontSize: 26, fontWeight: FontWeight.w600, color: kPrimaryColor)),
SegmentedButton<int>( SegmentedButton<int>(
style: SegmentedButton.styleFrom( style: SegmentedButton.styleFrom(
selectedBackgroundColor: kPrimaryColor, selectedBackgroundColor: kPrimaryColor,
selectedForegroundColor: kWhite, selectedForegroundColor: kWhite,
), ),
segments: const [ segments: [
ButtonSegment(value: 7, label: Text('7j')), const ButtonSegment(value: 7, label: Text('7j')),
ButtonSegment(value: 30, label: Text('30j')), const ButtonSegment(value: 30, label: Text('30j')),
ButtonSegment(value: 90, label: Text('90j')), ButtonSegment(value: 90, label: Text('90j'), enabled: maxDays >= 90),
], ],
selected: {_selectedDays}, selected: {_selectedDays},
onSelectionChanged: (s) { onSelectionChanged: (s) {
@ -108,7 +120,7 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
spacing: 8, spacing: 8,
children: [ children: [
ChoiceChip( ChoiceChip(
label: const Text('Tous'), label: Text(l.statsAll),
selected: _selectedAppType == null, selected: _selectedAppType == null,
selectedColor: kPrimaryColor, selectedColor: kPrimaryColor,
labelStyle: TextStyle(color: _selectedAppType == null ? kWhite : kBodyTextColor), labelStyle: TextStyle(color: _selectedAppType == null ? kWhite : kBodyTextColor),
@ -127,7 +139,8 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
); );
} }
Widget _buildDashboard(StatsSummaryDTO stats) { Widget _buildDashboard(BuildContext context, StatsSummaryDTO stats) {
final l = AppLocalizations.of(context)!;
if (stats.totalSessions == 0) { if (stats.totalSessions == 0) {
return Center( return Center(
child: Column( child: Column(
@ -136,13 +149,13 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
Icon(Icons.bar_chart_outlined, size: 56, color: kBodyTextColor.withValues(alpha: 0.4)), Icon(Icons.bar_chart_outlined, size: 56, color: kBodyTextColor.withValues(alpha: 0.4)),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Pas encore de données pour cette période', l.statsNoData,
style: TextStyle(fontSize: 15, color: kBodyTextColor), style: TextStyle(fontSize: 15, color: kBodyTextColor),
), ),
if (_selectedAppType != null) ...[ if (_selectedAppType != null) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Aucun event reçu pour le type "${_selectedAppType!.name}"', l.statsNoDataForType(_selectedAppType!.name),
style: TextStyle(fontSize: 13, color: kBodyTextColor.withValues(alpha: 0.6)), style: TextStyle(fontSize: 13, color: kBodyTextColor.withValues(alpha: 0.6)),
), ),
], ],
@ -155,23 +168,20 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// KPI cards _buildKpiRow(context, stats),
_buildKpiRow(stats),
const SizedBox(height: 20), const SizedBox(height: 20),
// Line chart _buildLineChart(context, stats),
_buildLineChart(stats),
const SizedBox(height: 20), const SizedBox(height: 20),
// Bar chart + Donut charts _buildChartsRow(context, stats),
_buildChartsRow(stats),
const SizedBox(height: 20), const SizedBox(height: 20),
// Bottom tables _buildTablesRow(context, stats),
_buildTablesRow(stats),
], ],
), ),
); );
} }
Widget _buildKpiRow(StatsSummaryDTO stats) { Widget _buildKpiRow(BuildContext context, StatsSummaryDTO stats) {
final l = AppLocalizations.of(context)!;
final topAppType = stats.appTypeDistribution.isNotEmpty final topAppType = stats.appTypeDistribution.isNotEmpty
? stats.appTypeDistribution.entries.reduce((a, b) => a.value > b.value ? a : b) ? stats.appTypeDistribution.entries.reduce((a, b) => a.value > b.value ? a : b)
: null; : null;
@ -181,13 +191,13 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
return Row( return Row(
children: [ children: [
_kpiCard('Sessions', '${stats.totalSessions}', Icons.people_outline), _kpiCard(l.statsSessions, '${stats.totalSessions}', Icons.people_outline),
const SizedBox(width: 12), const SizedBox(width: 12),
_kpiCard('Durée moy.', _formatDuration(stats.avgVisitDurationSeconds), Icons.timer_outlined), _kpiCard(l.statsAvgDuration, _formatDuration(stats.avgVisitDurationSeconds), Icons.timer_outlined),
const SizedBox(width: 12), const SizedBox(width: 12),
_kpiCard('App top', topAppType?.key ?? '', Icons.phone_iphone), _kpiCard(l.statsTopApp, topAppType?.key ?? '', Icons.phone_iphone),
const SizedBox(width: 12), const SizedBox(width: 12),
_kpiCard('Langue top', topLang?.key.toUpperCase() ?? '', Icons.language), _kpiCard(l.statsTopLang, topLang?.key.toUpperCase() ?? '', Icons.language),
], ],
); );
} }
@ -215,7 +225,8 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
); );
} }
Widget _buildLineChart(StatsSummaryDTO stats) { Widget _buildLineChart(BuildContext context, StatsSummaryDTO stats) {
final l = AppLocalizations.of(context)!;
if (stats.visitsByDay.isEmpty) return const SizedBox(); if (stats.visitsByDay.isEmpty) return const SizedBox();
final spots = <FlSpot>[]; final spots = <FlSpot>[];
@ -229,7 +240,9 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
tabletSpots.add(FlSpot(i.toDouble(), d.tablet.toDouble())); tabletSpots.add(FlSpot(i.toDouble(), d.tablet.toDouble()));
} }
final isFiltered = _selectedAppType != null; final mobileHasData = mobileSpots.any((s) => s.y > 0);
final tabletHasData = tabletSpots.any((s) => s.y > 0);
final showBreakdown = _selectedAppType == null && (mobileHasData || tabletHasData);
return Card( return Card(
elevation: 0, elevation: 0,
@ -240,17 +253,17 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Visites par jour', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: kPrimaryColor)), Text(l.statsVisitsByDay, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: kPrimaryColor)),
const SizedBox(height: 4), const SizedBox(height: 4),
if (!isFiltered) Row(children: [ if (showBreakdown) Row(children: [
_legendDot(kPrimaryColor), const SizedBox(width: 4), Text('Mobile', style: TextStyle(fontSize: 12, color: kBodyTextColor)), if (mobileHasData) ...[_legendDot(kPrimaryColor), const SizedBox(width: 4), Text('Mobile', style: TextStyle(fontSize: 12, color: kBodyTextColor)), const SizedBox(width: 12)],
const SizedBox(width: 12), if (tabletHasData) ...[_legendDot(kSecond), const SizedBox(width: 4), Text('Tablet', style: TextStyle(fontSize: 12, color: kBodyTextColor))],
_legendDot(kSecond), const SizedBox(width: 4), Text('Tablet', style: TextStyle(fontSize: 12, color: kBodyTextColor)),
]), ]),
const SizedBox(height: 16), const SizedBox(height: 16),
SizedBox( SizedBox(
height: 200, height: 200,
child: LineChart(LineChartData( child: LineChart(LineChartData(
minY: 0,
gridData: FlGridData(show: true, drawVerticalLine: false), gridData: FlGridData(show: true, drawVerticalLine: false),
titlesData: FlTitlesData( titlesData: FlTitlesData(
leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, reservedSize: 32)), leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, reservedSize: 32)),
@ -268,9 +281,12 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
)), )),
), ),
borderData: FlBorderData(show: false), borderData: FlBorderData(show: false),
lineBarsData: isFiltered lineBarsData: showBreakdown
? [_lineBar(spots, kPrimaryColor)] ? [
: [_lineBar(mobileSpots, kPrimaryColor), _lineBar(tabletSpots, kSecond)], if (mobileHasData) _lineBar(mobileSpots, kPrimaryColor),
if (tabletHasData) _lineBar(tabletSpots, kSecond),
]
: [_lineBar(spots, kPrimaryColor)],
)), )),
), ),
], ],
@ -289,28 +305,30 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
isCurved: true, isCurved: true,
color: color, color: color,
barWidth: 2, barWidth: 2,
dotData: FlDotData(show: false), dotData: FlDotData(show: spots.length <= 1),
belowBarData: BarAreaData(show: true, color: color.withOpacity(0.1)), belowBarData: BarAreaData(show: true, color: color.withValues(alpha: 0.1)),
); );
} }
Widget _buildChartsRow(StatsSummaryDTO stats) { Widget _buildChartsRow(BuildContext context, StatsSummaryDTO stats) {
final l = AppLocalizations.of(context)!;
final showAppTypeDonut = _selectedAppType == null; final showAppTypeDonut = _selectedAppType == null;
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Expanded(flex: 2, child: _buildTopSectionsChart(stats)), Expanded(flex: 2, child: _buildTopSectionsChart(context, stats)),
const SizedBox(width: 12), const SizedBox(width: 12),
if (showAppTypeDonut) ...[ if (showAppTypeDonut) ...[
Expanded(child: _buildDonut(stats.appTypeDistribution, 'Apps')), Expanded(child: _buildDonut(stats.appTypeDistribution, l.statsApps)),
const SizedBox(width: 12), const SizedBox(width: 12),
], ],
Expanded(child: _buildDonut(stats.languageDistribution, 'Langues')), Expanded(child: _buildDonut(stats.languageDistribution, l.statsLanguages)),
], ],
); );
} }
Widget _buildTopSectionsChart(StatsSummaryDTO stats) { Widget _buildTopSectionsChart(BuildContext context, StatsSummaryDTO stats) {
final l = AppLocalizations.of(context)!;
if (stats.topSections.isEmpty) return const SizedBox(); if (stats.topSections.isEmpty) return const SizedBox();
final sections = stats.topSections.take(8).toList(); final sections = stats.topSections.take(8).toList();
final maxViews = sections.map((s) => s.views).reduce((a, b) => a > b ? a : b); final maxViews = sections.map((s) => s.views).reduce((a, b) => a > b ? a : b);
@ -324,7 +342,7 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Top sections', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: kPrimaryColor)), Text(l.statsTopSections, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: kPrimaryColor)),
const SizedBox(height: 16), const SizedBox(height: 16),
SizedBox( SizedBox(
height: 240, height: 240,
@ -341,7 +359,8 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
getTitlesWidget: (value, meta) { getTitlesWidget: (value, meta) {
final idx = value.toInt(); final idx = value.toInt();
if (idx < 0 || idx >= sections.length) return const SizedBox(); if (idx < 0 || idx >= sections.length) return const SizedBox();
final title = sections[idx].sectionTitle ?? sections[idx].sectionId ?? ''; final raw = sections[idx].sectionTitle ?? sections[idx].sectionId ?? '';
final title = raw.replaceAll(RegExp(r'<[^>]*>'), '').trim();
return Padding( return Padding(
padding: const EdgeInsets.only(top: 4), padding: const EdgeInsets.only(top: 4),
child: Text( child: Text(
@ -430,15 +449,19 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
); );
} }
Widget _buildTablesRow(StatsSummaryDTO stats) { Widget _buildTablesRow(BuildContext context, StatsSummaryDTO stats) {
if (!_hasAdvancedStats) {
return _buildPremiumLockedCard(context);
}
final tables = <Widget>[ final tables = <Widget>[
if (stats.topPois.isNotEmpty) Expanded(child: _buildPoiTable(stats)), if (stats.topPois.isNotEmpty) Expanded(child: _buildPoiTable(context, stats)),
if (stats.topAgendaEvents.isNotEmpty) Expanded(child: _buildAgendaTable(stats)), if (stats.topAgendaEvents.isNotEmpty) Expanded(child: _buildAgendaTable(context, stats)),
if (stats.quizStats.isNotEmpty) Expanded(child: _buildQuizTable(stats)), if (stats.quizStats.isNotEmpty) Expanded(child: _buildQuizTable(context, stats)),
if (stats.gameStats.isNotEmpty) Expanded(child: _buildGameTable(stats)), if (stats.gameStats.isNotEmpty) Expanded(child: _buildGameTable(context, stats)),
if (stats.topArticles.isNotEmpty) Expanded(child: _buildArticleTable(stats)), if (stats.topArticles.isNotEmpty) Expanded(child: _buildArticleTable(context, stats)),
if (stats.topMenuItems.isNotEmpty) Expanded(child: _buildMenuTable(stats)), if (stats.topMenuItems.isNotEmpty) Expanded(child: _buildMenuTable(context, stats)),
if (stats.qrScans.totalScans > 0) Expanded(child: _buildQrCard(stats)), if (stats.qrScans.totalScans > 0) Expanded(child: _buildQrCard(context, stats)),
]; ];
if (tables.isEmpty) return const SizedBox(); if (tables.isEmpty) return const SizedBox();
final spaced = <Widget>[]; final spaced = <Widget>[];
@ -449,51 +472,88 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
return Row(crossAxisAlignment: CrossAxisAlignment.start, children: spaced); return Row(crossAxisAlignment: CrossAxisAlignment.start, children: spaced);
} }
Widget _buildPoiTable(StatsSummaryDTO stats) { Widget _buildPremiumLockedCard(BuildContext context) {
return _tableCard('Top POI', ['POI', 'Taps'], stats.topPois.map((p) => [ return Card(
elevation: 0,
color: kWhite,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(24),
child: Row(
children: [
Icon(Icons.lock_outline, color: kBodyTextColor.withValues(alpha: 0.5), size: 28),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Statistiques avancées', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: kPrimaryColor)),
const SizedBox(height: 4),
Text(
'Les stats détaillées (POI, quiz, jeux, articles, QR…) sont disponibles avec le plan Premium.',
style: TextStyle(fontSize: 13, color: kBodyTextColor),
),
],
),
),
],
),
),
);
}
Widget _buildPoiTable(BuildContext context, StatsSummaryDTO stats) {
final l = AppLocalizations.of(context)!;
return _tableCard(l.statsTopPOI, [l.statsPOI, l.statsTaps], stats.topPois.map((p) => [
p.title ?? p.geoPointId?.toString() ?? '', p.title ?? p.geoPointId?.toString() ?? '',
'${p.taps}', '${p.taps}',
]).toList()); ]).toList());
} }
Widget _buildAgendaTable(StatsSummaryDTO stats) { Widget _buildAgendaTable(BuildContext context, StatsSummaryDTO stats) {
return _tableCard('Top événements agenda', ['Événement', 'Taps'], stats.topAgendaEvents.map((e) => [ final l = AppLocalizations.of(context)!;
return _tableCard(l.statsTopAgenda, [l.statsEvent, l.statsTaps], stats.topAgendaEvents.map((e) => [
e.eventTitle ?? e.eventId ?? '', e.eventTitle ?? e.eventId ?? '',
'${e.taps}', '${e.taps}',
]).toList()); ]).toList());
} }
Widget _buildQuizTable(StatsSummaryDTO stats) { Widget _buildQuizTable(BuildContext context, StatsSummaryDTO stats) {
return _tableCard('Quiz', ['Section', 'Score moy', 'Complétions'], stats.quizStats.map((q) => [ final l = AppLocalizations.of(context)!;
return _tableCard(l.statsQuiz, [l.statsSection, l.statsAvgScore, l.statsCompletions], stats.quizStats.map((q) => [
q.sectionTitle ?? q.sectionId ?? '', q.sectionTitle ?? q.sectionId ?? '',
'${q.avgScore.toStringAsFixed(1)} / ${q.totalQuestions}', '${q.avgScore.toStringAsFixed(1)} / ${q.totalQuestions}',
'${q.completions}', '${q.completions}',
]).toList()); ]).toList());
} }
Widget _buildGameTable(StatsSummaryDTO stats) { Widget _buildGameTable(BuildContext context, StatsSummaryDTO stats) {
return _tableCard('Jeux', ['Type', 'Complétions', 'Durée moy.'], stats.gameStats.map((g) => [ final l = AppLocalizations.of(context)!;
return _tableCard(l.statsGames, [l.statsGameType, l.statsCompletions, l.statsAvgDuration], stats.gameStats.map((g) => [
g.gameType ?? '', g.gameType ?? '',
'${g.completions}', '${g.completions}',
_formatDuration(g.avgDurationSeconds), _formatDuration(g.avgDurationSeconds),
]).toList()); ]).toList());
} }
Widget _buildArticleTable(StatsSummaryDTO stats) { Widget _buildArticleTable(BuildContext context, StatsSummaryDTO stats) {
return _tableCard('Articles lus', ['Section', 'Lectures'], stats.topArticles.map((a) => [ final l = AppLocalizations.of(context)!;
return _tableCard(l.statsArticles, [l.statsSection, l.statsReadings], stats.topArticles.map((a) => [
a.sectionId ?? '', a.sectionId ?? '',
'${a.reads}', '${a.reads}',
]).toList()); ]).toList());
} }
Widget _buildMenuTable(StatsSummaryDTO stats) { Widget _buildMenuTable(BuildContext context, StatsSummaryDTO stats) {
return _tableCard('Menu', ['Item', 'Taps'], stats.topMenuItems.map((m) => [ final l = AppLocalizations.of(context)!;
return _tableCard(l.statsMenuTitle, [l.statsMenuItem, l.statsTaps], stats.topMenuItems.map((m) => [
m.menuItemTitle ?? m.targetSectionId ?? '', m.menuItemTitle ?? m.targetSectionId ?? '',
'${m.taps}', '${m.taps}',
]).toList()); ]).toList());
} }
Widget _buildQrCard(StatsSummaryDTO stats) { Widget _buildQrCard(BuildContext context, StatsSummaryDTO stats) {
final l = AppLocalizations.of(context)!;
final qr = stats.qrScans; final qr = stats.qrScans;
return Card( return Card(
elevation: 0, elevation: 0,
@ -504,13 +564,13 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('QR Scans', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: kPrimaryColor)), Text(l.statsQrScans, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: kPrimaryColor)),
const SizedBox(height: 12), const SizedBox(height: 12),
_qrRow(Icons.qr_code_scanner, 'Total', '${qr.totalScans}', kPrimaryColor), _qrRow(Icons.qr_code_scanner, l.statsTotal, '${qr.totalScans}', kPrimaryColor),
const SizedBox(height: 8), const SizedBox(height: 8),
_qrRow(Icons.check_circle_outline, 'Valides', '${qr.validScans}', Colors.green.shade600), _qrRow(Icons.check_circle_outline, l.statsValid, '${qr.validScans}', Colors.green.shade600),
const SizedBox(height: 8), const SizedBox(height: 8),
_qrRow(Icons.cancel_outlined, 'Invalides', '${qr.invalidScans}', Colors.red.shade400), _qrRow(Icons.cancel_outlined, l.statsInvalid, '${qr.invalidScans}', Colors.red.shade400),
], ],
), ),
), ),

View File

@ -1,8 +1,10 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/app_context.dart'; import 'package:manager_app/app_context.dart';
import 'package:manager_app/Components/common_loader.dart';
import 'package:manager_app/constants.dart'; import 'package:manager_app/constants.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -26,7 +28,6 @@ class _UsersScreenState extends State<UsersScreen> {
return ''; return '';
} }
// Convertit une valeur de rôle (String ou int) en int pour les comparaisons
static int _roleToInt(dynamic v) { static int _roleToInt(dynamic v) {
if (v is int) return v; if (v is int) return v;
if (v is String) { if (v is String) {
@ -36,7 +37,6 @@ class _UsersScreenState extends State<UsersScreen> {
return 3; return 3;
} }
// Roles the caller is allowed to assign (can't assign higher than own role)
List<int> _allowedRoles(int callerRoleValue) => List<int> _allowedRoles(int callerRoleValue) =>
[0, 1, 2, 3].where((r) => r >= callerRoleValue).toList(); [0, 1, 2, 3].where((r) => r >= callerRoleValue).toList();
@ -88,29 +88,30 @@ class _UsersScreenState extends State<UsersScreen> {
} }
void _showCreateDialog(BuildContext context, ManagerAppContext ctx) { void _showCreateDialog(BuildContext context, ManagerAppContext ctx) {
final l = AppLocalizations.of(context)!;
final callerRole = _roleToInt(ctx.role?.value); final callerRole = _roleToInt(ctx.role?.value);
final emailCtrl = TextEditingController(); final emailCtrl = TextEditingController();
final firstCtrl = TextEditingController(); final firstCtrl = TextEditingController();
final lastCtrl = TextEditingController(); final lastCtrl = TextEditingController();
final passCtrl = TextEditingController(); final passCtrl = TextEditingController();
int selectedRole = callerRole; // default: same as caller int selectedRole = callerRole;
showDialog( showDialog(
context: context, context: context,
builder: (_) => StatefulBuilder(builder: (ctx2, setLocal) { builder: (_) => StatefulBuilder(builder: (ctx2, setLocal) {
return AlertDialog( return AlertDialog(
title: const Text('Créer un utilisateur'), title: Text(l.createUserTitle),
content: SingleChildScrollView( content: SingleChildScrollView(
child: Column(mainAxisSize: MainAxisSize.min, children: [ child: Column(mainAxisSize: MainAxisSize.min, children: [
TextField(controller: emailCtrl, decoration: const InputDecoration(labelText: 'Email')), TextField(controller: emailCtrl, decoration: InputDecoration(labelText: l.email)),
TextField(controller: firstCtrl, decoration: const InputDecoration(labelText: 'Prénom')), TextField(controller: firstCtrl, decoration: InputDecoration(labelText: l.firstName)),
TextField(controller: lastCtrl, decoration: const InputDecoration(labelText: 'Nom')), TextField(controller: lastCtrl, decoration: InputDecoration(labelText: l.lastName)),
TextField(controller: passCtrl, obscureText: true, decoration: const InputDecoration(labelText: 'Mot de passe')), TextField(controller: passCtrl, obscureText: true, decoration: InputDecoration(labelText: l.password)),
const SizedBox(height: 8), const SizedBox(height: 8),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('Rôle', style: TextStyle(fontSize: 12, color: Colors.grey)), Text(l.role, style: const TextStyle(fontSize: 12, color: Colors.grey)),
DropdownButton<int>( DropdownButton<int>(
value: selectedRole, value: selectedRole,
isExpanded: true, isExpanded: true,
@ -124,14 +125,14 @@ class _UsersScreenState extends State<UsersScreen> {
]), ]),
), ),
actions: [ actions: [
TextButton(onPressed: () => Navigator.pop(ctx2), child: const Text('Annuler')), TextButton(onPressed: () => Navigator.pop(ctx2), child: Text(l.cancel)),
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () async {
Navigator.pop(ctx2); Navigator.pop(ctx2);
await _createUser(ctx, emailCtrl.text, firstCtrl.text, await _createUser(ctx, emailCtrl.text, firstCtrl.text,
lastCtrl.text, passCtrl.text, selectedRole); lastCtrl.text, passCtrl.text, selectedRole);
}, },
child: const Text('Créer'), child: Text(l.create),
), ),
], ],
); );
@ -140,6 +141,7 @@ class _UsersScreenState extends State<UsersScreen> {
} }
void _showEditDialog(BuildContext context, ManagerAppContext ctx, Map<String, dynamic> user) { void _showEditDialog(BuildContext context, ManagerAppContext ctx, Map<String, dynamic> user) {
final l = AppLocalizations.of(context)!;
final callerRole = _roleToInt(ctx.role?.value); final callerRole = _roleToInt(ctx.role?.value);
final firstCtrl = TextEditingController(text: user['firstName'] as String? ?? ''); final firstCtrl = TextEditingController(text: user['firstName'] as String? ?? '');
final lastCtrl = TextEditingController(text: user['lastName'] as String? ?? ''); final lastCtrl = TextEditingController(text: user['lastName'] as String? ?? '');
@ -149,16 +151,16 @@ class _UsersScreenState extends State<UsersScreen> {
context: context, context: context,
builder: (_) => StatefulBuilder(builder: (ctx2, setLocal) { builder: (_) => StatefulBuilder(builder: (ctx2, setLocal) {
return AlertDialog( return AlertDialog(
title: const Text('Modifier l\'utilisateur'), title: Text(l.editUserTitle),
content: SingleChildScrollView( content: SingleChildScrollView(
child: Column(mainAxisSize: MainAxisSize.min, children: [ child: Column(mainAxisSize: MainAxisSize.min, children: [
TextField(controller: firstCtrl, decoration: const InputDecoration(labelText: 'Prénom')), TextField(controller: firstCtrl, decoration: InputDecoration(labelText: l.firstName)),
TextField(controller: lastCtrl, decoration: const InputDecoration(labelText: 'Nom')), TextField(controller: lastCtrl, decoration: InputDecoration(labelText: l.lastName)),
const SizedBox(height: 8), const SizedBox(height: 8),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('Rôle', style: TextStyle(fontSize: 12, color: Colors.grey)), Text(l.role, style: const TextStyle(fontSize: 12, color: Colors.grey)),
DropdownButton<int>( DropdownButton<int>(
value: selectedRole, value: selectedRole,
isExpanded: true, isExpanded: true,
@ -172,14 +174,14 @@ class _UsersScreenState extends State<UsersScreen> {
]), ]),
), ),
actions: [ actions: [
TextButton(onPressed: () => Navigator.pop(ctx2), child: const Text('Annuler')), TextButton(onPressed: () => Navigator.pop(ctx2), child: Text(l.cancel)),
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () async {
Navigator.pop(ctx2); Navigator.pop(ctx2);
await _updateUser(ctx, user['id'] as String, firstCtrl.text, await _updateUser(ctx, user['id'] as String, firstCtrl.text,
lastCtrl.text, selectedRole); lastCtrl.text, selectedRole);
}, },
child: const Text('Enregistrer'), child: Text(l.save),
), ),
], ],
); );
@ -188,20 +190,21 @@ class _UsersScreenState extends State<UsersScreen> {
} }
void _confirmDelete(BuildContext context, ManagerAppContext ctx, Map<String, dynamic> user) { void _confirmDelete(BuildContext context, ManagerAppContext ctx, Map<String, dynamic> user) {
final l = AppLocalizations.of(context)!;
showDialog( showDialog(
context: context, context: context,
builder: (_) => AlertDialog( builder: (_) => AlertDialog(
title: const Text('Supprimer l\'utilisateur'), title: Text(l.deleteUserTitle),
content: Text('Supprimer ${user['email']} ?'), content: Text(l.deleteUserConfirm(user['email'] as String? ?? '')),
actions: [ actions: [
TextButton(onPressed: () => Navigator.pop(context), child: const Text('Annuler')), TextButton(onPressed: () => Navigator.pop(context), child: Text(l.cancel)),
ElevatedButton( ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red), style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
onPressed: () async { onPressed: () async {
Navigator.pop(context); Navigator.pop(context);
await _deleteUser(ctx, user['id'] as String); await _deleteUser(ctx, user['id'] as String);
}, },
child: const Text('Supprimer', style: TextStyle(color: Colors.white)), child: Text(l.delete, style: const TextStyle(color: Colors.white)),
), ),
], ],
), ),
@ -228,6 +231,7 @@ class _UsersScreenState extends State<UsersScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l = AppLocalizations.of(context)!;
final appContext = Provider.of<AppContext>(context); final appContext = Provider.of<AppContext>(context);
final managerCtx = appContext.getContext() as ManagerAppContext; final managerCtx = appContext.getContext() as ManagerAppContext;
@ -237,19 +241,19 @@ class _UsersScreenState extends State<UsersScreen> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('Utilisateurs', style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: kPrimaryColor)), Text(l.usersTitle, style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: kPrimaryColor)),
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () => _showCreateDialog(context, managerCtx), onPressed: () => _showCreateDialog(context, managerCtx),
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: const Text('Créer un utilisateur'), label: Text(l.createUserBtn),
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
if (_loading) if (_loading)
const Center(child: CircularProgressIndicator()) const CommonLoader()
else if (_users.isEmpty) else if (_users.isEmpty)
const Center(child: Text('Aucun utilisateur')) Center(child: Text(l.noUsers))
else else
Expanded( Expanded(
child: Card( child: Card(
@ -268,12 +272,12 @@ class _UsersScreenState extends State<UsersScreen> {
columnSpacing: 24, columnSpacing: 24,
headingRowColor: WidgetStateProperty.all(Colors.grey.shade50), headingRowColor: WidgetStateProperty.all(Colors.grey.shade50),
dividerThickness: 1, dividerThickness: 1,
columns: const [ columns: [
DataColumn(label: Text('Email', style: TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.email, style: const TextStyle(fontWeight: FontWeight.w600))),
DataColumn(label: Text('Prénom', style: TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.firstName, style: const TextStyle(fontWeight: FontWeight.w600))),
DataColumn(label: Text('Nom', style: TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.lastName, style: const TextStyle(fontWeight: FontWeight.w600))),
DataColumn(label: Text('Rôle', style: TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.role, style: const TextStyle(fontWeight: FontWeight.w600))),
DataColumn(label: Text('Actions', style: TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.actions, style: const TextStyle(fontWeight: FontWeight.w600))),
], ],
rows: _users.map((user) { rows: _users.map((user) {
final roleColor = _roleColor(user['role']); final roleColor = _roleColor(user['role']);
@ -294,12 +298,12 @@ class _UsersScreenState extends State<UsersScreen> {
DataCell(Row(children: [ DataCell(Row(children: [
IconButton( IconButton(
icon: Icon(Icons.edit, color: kPrimaryColor, size: 20), icon: Icon(Icons.edit, color: kPrimaryColor, size: 20),
tooltip: 'Modifier', tooltip: l.tooltipEdit,
onPressed: () => _showEditDialog(context, managerCtx, user), onPressed: () => _showEditDialog(context, managerCtx, user),
), ),
IconButton( IconButton(
icon: const Icon(Icons.delete_outline, color: Colors.red, size: 20), icon: const Icon(Icons.delete_outline, color: Colors.red, size: 20),
tooltip: 'Supprimer', tooltip: l.tooltipDelete,
onPressed: () => _confirmDelete(context, managerCtx, user), onPressed: () => _confirmDelete(context, managerCtx, user),
), ),
])), ])),

View File

@ -10,6 +10,7 @@ import 'package:manager_app/Components/message_notification.dart';
import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/rounded_button.dart';
import 'package:manager_app/Components/rounded_input_field.dart'; import 'package:manager_app/Components/rounded_input_field.dart';
import 'package:manager_app/Components/rounded_password_field.dart'; import 'package:manager_app/Components/rounded_password_field.dart';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:manager_app/Helpers/FileHelper.dart'; import 'package:manager_app/Helpers/FileHelper.dart';
import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/Models/session.dart'; import 'package:manager_app/Models/session.dart';
@ -88,7 +89,7 @@ class _LoginScreenState extends State<LoginScreen> {
userRole = token.role; userRole = token.role;
showNotification( showNotification(
kSuccess, kWhite, 'Connexion réussie', context, null); kSuccess, kWhite, AppLocalizations.of(context)!.loginSuccess, context, null);
if (isRememberMe) { if (isRememberMe) {
if (!localStorage.containsKey("remember")) { if (!localStorage.containsKey("remember")) {
@ -171,7 +172,7 @@ class _LoginScreenState extends State<LoginScreen> {
(Route<dynamic> route) => false // For pushAndRemoveUntil (Route<dynamic> route) => false // For pushAndRemoveUntil
);*/ );*/
} else { } else {
showNotification(Colors.orange, kWhite, 'Un problème est survenu lors de la connexion', context, null); showNotification(Colors.orange, kWhite, AppLocalizations.of(context)!.loginError, context, null);
setState(() { setState(() {
isLoading = false; isLoading = false;
}); });
@ -181,7 +182,7 @@ class _LoginScreenState extends State<LoginScreen> {
print("error auth"); print("error auth");
print(e); print(e);
if(fromClick) { if(fromClick) {
showNotification(Colors.orange, kWhite, 'Un problème est survenu lors de la connexion', context, null); showNotification(Colors.orange, kWhite, AppLocalizations.of(context)!.loginError, context, null);
setState(() { setState(() {
isLoading = false; isLoading = false;
}); });
@ -393,13 +394,13 @@ class _LoginScreenState extends State<LoginScreen> {
}, },
), ),
), ),
Text("Se souvenir de moi", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),), Text(AppLocalizations.of(context)!.rememberMe, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),),
], ],
), ),
), ),
SizedBox(height: size.height * 0.015), SizedBox(height: size.height * 0.015),
!isLoading ? RoundedButton( !isLoading ? RoundedButton(
text: "SE CONNECTER", text: AppLocalizations.of(context)!.connect,
fontSize: 16, fontSize: 16,
vertical: 15, vertical: 15,
horizontal: 30, horizontal: 30,

View File

@ -0,0 +1,35 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
class AiTranslateService {
static Future<Map<String, String>> translate({
required String host,
required String accessToken,
required String instanceId,
required String text,
required String sourceLang,
required List<String> targetLangs,
}) async {
final uri = Uri.parse('$host/api/Ai/translate?instanceId=$instanceId');
final response = await http.post(
uri,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $accessToken',
},
body: jsonEncode({
'text': text,
'sourceLang': sourceLang,
'targetLangs': targetLangs,
}),
);
if (response.statusCode != 200) {
throw Exception('Erreur traduction IA : ${response.statusCode}');
}
final data = jsonDecode(response.body);
final translations = data['translations'] as Map<String, dynamic>;
return translations.map((k, v) => MapEntry(k, v.toString()));
}
}

View File

@ -19,4 +19,9 @@ class AppContext with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void setLocale(Locale locale) {
_managerContext?.locale = locale;
notifyListeners();
}
} }

View File

@ -50,6 +50,9 @@ class Client {
NotificationApi? _notificationApi; NotificationApi? _notificationApi;
NotificationApi? get notificationApi => _notificationApi; NotificationApi? get notificationApi => _notificationApi;
SubscriptionPlanApi? _subscriptionPlanApi;
SubscriptionPlanApi? get subscriptionPlanApi => _subscriptionPlanApi;
Client(String path) { Client(String path) {
_apiClient = ApiClient(basePath: path); _apiClient = ApiClient(basePath: path);
//basePath: "https://192.168.31.140"); //basePath: "https://192.168.31.140");
@ -70,5 +73,6 @@ class Client {
_statsApi = StatsApi(_apiClient); _statsApi = StatsApi(_apiClient);
_apiKeyApi = ApiKeyApi(_apiClient); _apiKeyApi = ApiKeyApi(_apiClient);
_notificationApi = NotificationApi(_apiClient); _notificationApi = NotificationApi(_apiClient);
_subscriptionPlanApi = SubscriptionPlanApi(_apiClient);
} }
} }

461
lib/l10n/app_en.arb Normal file
View File

@ -0,0 +1,461 @@
{
"@@locale": "en",
"cancel": "Cancel",
"save": "Save",
"create": "Create",
"delete": "Delete",
"close": "Close",
"edit": "Edit",
"actions": "Actions",
"status": "Status",
"no": "No",
"name": "Name",
"type": "Type",
"date": "Date",
"loginSuccess": "Login successful",
"loginError": "An error occurred during login",
"rememberMe": "Remember me",
"connect": "LOG IN",
"menuApplications": "Applications",
"menuConfigurations": "Configurations",
"menuResources": "Resources",
"menuStatistics": "Statistics",
"menuNotifications": "Notifications",
"menuUsers": "Users",
"menuApiKeys": "API Keys",
"noPlansAvailable": "No plans available. Create a plan first.",
"noPlan": "No plan (unlimited)",
"unlimitedStorage": "Unlimited storage",
"unlimitedAI": "Unlimited AI",
"aiRequestsPerMonth": "{count} AI req/month",
"@aiRequestsPerMonth": {
"placeholders": {
"count": { "type": "int" }
}
},
"storageLabel": "Storage",
"aiThisMonthLabel": "AI this month",
"unlimited": "Unlimited",
"requestsAbbr": "req",
"planUpdated": "Plan updated",
"errorMessage": "Error: {error}",
"@errorMessage": {
"placeholders": {
"error": { "type": "String" }
}
},
"switchInstance": "Switch instance",
"noInstanceFound": "No instance found",
"configurePlan": "Configure plan",
"tooltipSwitchInstance": "Switch instance",
"usersTitle": "Users",
"createUserBtn": "Create user",
"createUserTitle": "Create user",
"editUserTitle": "Edit user",
"deleteUserTitle": "Delete user",
"deleteUserConfirm": "Delete {email}?",
"@deleteUserConfirm": {
"placeholders": {
"email": { "type": "String" }
}
},
"noUsers": "No users",
"email": "Email",
"firstName": "First name",
"lastName": "Last name",
"password": "Password",
"role": "Role",
"tooltipEdit": "Edit",
"tooltipDelete": "Delete",
"notificationsTitle": "Notifications",
"newMessage": "New message",
"messageTitle": "Title",
"messageBody": "Message",
"schedule": "Schedule",
"time": "Time",
"send": "Send",
"cancelNotification": "Cancel notification",
"cancelNotificationConfirm": "Cancel « {title} »?",
"@cancelNotificationConfirm": {
"placeholders": {
"title": { "type": "String" }
}
},
"allNotifications": "All",
"sentNotifications": "Sent",
"scheduledNotifications": "Scheduled",
"failedNotifications": "Failed",
"noNotifications": "No notifications sent",
"noNotificationsForFilter": "No notifications for this filter",
"topic": "Topic",
"tooltipCancelNotification": "Cancel",
"resultsCount": "{count, plural, one{{count} result} other{{count} results}}",
"@resultsCount": {
"placeholders": {
"count": { "type": "int" }
}
},
"apiKeysTitle": "API Keys",
"createApiKey": "Create API key",
"createKeyBtn": "Create key",
"appType": "Application type",
"noExpiration": "No expiration",
"expiresOn": "Expires on {date}",
"@expiresOn": {
"placeholders": {
"date": { "type": "String" }
}
},
"choose": "Choose",
"removeExpiration": "Remove expiration",
"apiKeyCreatedTitle": "API key created",
"copyKeyWarning": "Copy this key now — it won't be shown again.",
"copy": "Copy",
"copiedKey": "I've copied the key",
"revokeApiKeyTitle": "Revoke API key",
"revokeApiKeyConfirm": "Revoke « {name} »? Apps using this key will lose access.",
"@revokeApiKeyConfirm": {
"placeholders": {
"name": { "type": "String" }
}
},
"revoke": "Revoke",
"noApiKeys": "No API keys",
"createdOn": "Created on",
"expiration": "Expiration",
"activeKey": "Active",
"revokedKey": "Revoked",
"tooltipRevoke": "Revoke",
"statisticsTitle": "Statistics",
"statsLoadError": "Unable to load statistics",
"statsNoData": "No data yet for this period",
"statsNoDataForType": "No events received for type \"{type}\"",
"@statsNoDataForType": {
"placeholders": {
"type": { "type": "String" }
}
},
"statsSessions": "Sessions",
"statsAvgDuration": "Avg. duration",
"statsTopApp": "Top app",
"statsTopLang": "Top language",
"statsVisitsByDay": "Visits per day",
"statsAll": "All",
"statsTopSections": "Top sections",
"statsApps": "Apps",
"statsLanguages": "Languages",
"statsTopPOI": "Top POI",
"statsPOI": "POI",
"statsTaps": "Taps",
"statsTopAgenda": "Top agenda events",
"statsEvent": "Event",
"statsQuiz": "Quiz",
"statsSection": "Section",
"statsAvgScore": "Avg. score",
"statsCompletions": "Completions",
"statsGames": "Games",
"statsGameType": "Type",
"statsArticles": "Articles read",
"statsReadings": "Reads",
"statsMenuTitle": "Menu",
"statsMenuItem": "Item",
"statsQrScans": "QR Scans",
"statsTotal": "Total",
"statsValid": "Valid",
"statsInvalid": "Invalid",
"statsViews": "Views",
"noData": "No data",
"errorOccurred": "An error occurred",
"yes": "Yes",
"newConfiguration": "New configuration",
"configNameLabel": "Name:",
"orText": "or",
"importLabel": "Import",
"configNameRequired": "Please specify a name for the new visit",
"configCreatedSuccess": "Configuration created successfully",
"configExportSuccess": "Configuration exported successfully, file is at: {path}",
"@configExportSuccess": {
"placeholders": {
"path": { "type": "String" }
}
},
"configExportFailed": "Configuration export failed",
"configDeletedSuccess": "Configuration deleted successfully",
"configSavedSuccess": "Configuration saved successfully",
"configDeleteConfirm": "Are you sure you want to delete this configuration?",
"newSection": "New section",
"newSubSection": "New sub section",
"sectionNameLabel": "Name:",
"sectionTypeLabel": "Type:",
"sectionNameRequired": "Please specify a name for the new section",
"sectionCreatedSuccess": "Section created successfully!",
"sectionDeleteConfirm": "Are you sure you want to delete this section?",
"sectionSavedSuccess": "Section saved successfully",
"sectionTranslationSaved": "Section translations saved successfully",
"sectionLoadError": "An error occurred while loading the section",
"qrCodeCopied": "This QR code has been copied to the clipboard",
"beaconLabel": "Beacon:",
"beaconIdLabel": "Beacon ID:",
"beaconMustBeNumber": "This must be a number",
"identifierLabel": "Identifier:",
"displayTitleLabel": "Display title:",
"imageLabel": "Image:",
"backgroundImageLabel": "Background image:",
"questionInputLabel": "Question:",
"videoUrlLabel": "Video URL:",
"webUrlLabel": "Website URL:",
"subSectionUpdatedSuccess": "Sub section updated successfully",
"subSectionUpdateError": "An error occurred while updating the sub section",
"subSectionDeletedSuccess": "Sub section deleted successfully",
"subSectionDeleteError": "An error occurred while deleting the sub section",
"subSectionOrderUpdatedSuccess": "Sub section order updated successfully",
"subSectionOrderUpdateError": "An error occurred while updating the sub section order",
"subSectionCreatedSuccess": "Sub section created successfully",
"subSectionCreateError": "An error occurred while creating the sub section",
"questionsLabel": "Questions",
"questionLabel": "Question",
"editQuestion": "Edit question",
"questionDeleteConfirm": "Are you sure you want to delete this question?",
"questionsLoadError": "An error occurred while loading the questions",
"quizBadScore": "Bad score",
"quizMediumScore": "Medium score",
"quizGoodScore": "Good score",
"quizExcellentScore": "Excellent score",
"quizBadScoreMsg": "Message for a bad score",
"quizMediumScoreMsg": "Message for a medium score",
"quizGoodScoreMsg": "Message for a good score",
"quizExcellentScoreMsg": "Message for an excellent score",
"questionOrderUpdatedSuccess": "Question order updated successfully",
"questionOrderUpdateError": "An error occurred while updating the question order",
"questionCreatedSuccess": "Question created successfully",
"questionCreateError": "An error occurred while creating the question",
"questionUpdatedSuccess": "Question updated successfully",
"questionUpdateError": "An error occurred while updating the question",
"questionDeletedSuccess": "Question deleted successfully",
"questionDeleteError": "An error occurred while deleting the question",
"translationIncomplete": "Translation is incomplete",
"geopointCreatedSuccess": "Point created successfully",
"geopointCreateError": "An error occurred while creating the point",
"geopointUpdatedSuccess": "Point updated successfully",
"geopointUpdateError": "An error occurred while updating the point",
"geopointDeletedSuccess": "Point deleted successfully",
"geopointDeleteError": "An error occurred while deleting the point",
"categoryCreatedSuccess": "Category created successfully",
"categoryCreateError": "An error occurred while creating the category",
"categoryUpdatedSuccess": "Category updated successfully",
"categoryUpdateError": "An error occurred while updating the category",
"eventCreatedSuccess": "Event created successfully",
"eventCreateError": "An error occurred while creating the event",
"eventUpdatedSuccess": "Event updated successfully",
"eventUpdateError": "An error occurred while updating the event",
"eventDeletedSuccess": "Event deleted successfully",
"eventDeleteError": "An error occurred while deleting the event",
"annotationCreatedSuccess": "Annotation created successfully",
"annotationCreateError": "An error occurred while creating the annotation",
"annotationUpdatedSuccess": "Annotation updated successfully",
"annotationUpdateError": "An error occurred while updating the annotation",
"programmeBlockCreatedSuccess": "Block created successfully",
"programmeBlockCreateError": "An error occurred while creating the block",
"programmeBlockUpdatedSuccess": "Block updated successfully",
"programmeBlockUpdateError": "An error occurred while updating the block",
"pathCreatedSuccess": "Path created successfully",
"pathCreateError": "An error occurred while creating the path",
"pathUpdatedSuccess": "Path updated successfully",
"pathUpdateError": "An error occurred while updating the path",
"stepCreatedSuccess": "Step created successfully",
"stepCreateError": "An error occurred while creating the step",
"stepUpdatedSuccess": "Step updated successfully",
"stepUpdateError": "An error occurred while updating the step",
"agendaEventsLabel": "Events",
"agendaEventFallback": "Event",
"addEvent": "Add an event",
"noEvents": "No events",
"noAddress": "No address",
"onlineLabel": "Online:",
"mapViewLabel": "Map view:",
"mapServiceLabel": "Map service:",
"jsonFilesLabel": "JSON files:",
"jsonLabel": "JSON",
"guidedPathsLabel": "Guided Paths",
"addPath": "Add a path",
"noPathConfigured": "No path configured",
"pathOrderUpdateError": "Error updating order",
"pathNoTitle": "Untitled path",
"pathDeletedSuccess": "Path deleted successfully",
"pathDeleteError": "Error deleting path",
"pathsLabel": "Paths",
"pointsOfInterestLabel": "Points of Interest",
"geopointsLabel": "Geographic points",
"searchLabel": "Search:",
"geopointsLoadError": "Error loading geographic points",
"geopointDeleteConfirm": "Are you sure you want to delete this geographic point?",
"serviceLabel": "Service:",
"centerPointLabel": "Center point:",
"iconLabel": "Icon:",
"listViewLabel": "List view:",
"typeLabel": "Type:",
"zoomLabel": "Zoom:",
"categoriesLabel": "Categories:",
"startDateLabel": "Start date",
"notDefined": "Not defined",
"endDateLabel": "End date",
"programmeLabel": "Programme",
"addBlock": "Add a block",
"blockFallback": "Block",
"noBlocks": "No programme blocks defined",
"programmeBlockDeletedSuccess": "Block deleted successfully",
"programmeBlockDeleteError": "Error deleting block",
"baseMapLabel": "Base map",
"mapLabel": "Map:",
"globalAnnotationsLabel": "Global annotations",
"addAnnotation": "Add an annotation",
"noAnnotations": "No global annotations",
"annotationFallback": "Annotation",
"annotationDeletedSuccess": "Annotation deleted",
"annotationDeleteError": "Error deleting",
"agendaEventCreatedSuccess": "Event created successfully",
"agendaEventCreateError": "An error occurred while creating the event",
"agendaEventUpdatedSuccess": "Event updated successfully",
"agendaEventUpdateError": "An error occurred while updating the event",
"agendaEventDeletedSuccess": "Event deleted successfully",
"agendaEventDeleteError": "An error occurred while deleting the event",
"newResource": "New resource",
"resourceCreatedSuccess": "Resource created successfully",
"resourceCreateError": "An error occurred while creating the resource",
"resourceUpdatedSuccess": "Resource updated successfully",
"resourceNoFileLoaded": "No file has been loaded",
"resourceNameRequired": "Please give the resource a name",
"resourceTooLarge": "Error: resource size must be under 1.5MB",
"resourceInvalidUrl": "The URL is invalid",
"resourceUrlRequired": "Please fill in the URL field",
"generalInfo": "General information",
"mainImageLabel": "Main image:",
"loaderLabel": "Loader:",
"primaryColorLabel": "Primary color:",
"secondaryColorLabel": "Secondary color:",
"layoutLabel": "Layout:",
"layoutGrid": "Grid",
"languagesLabel": "Languages:",
"featuredEventLabel": "Featured event:",
"chooseEvent": "Choose an event",
"noneOption": "None",
"aiAssistantLabel": "AI assistant:",
"appUpdatedSuccess": "Mobile application updated",
"configActivatedSuccess": "Configuration activated successfully",
"configDeactivatedSuccess": "Configuration deactivated successfully",
"configRemoveConfirm": "Are you sure you want to remove this configuration from the application?",
"configRemovedSuccess": "Configuration removed from application successfully",
"configRemoveError": "An error occurred while removing the configuration",
"phoneConfigTitle": "Configurations per device",
"addConfig": "Add a configuration",
"selectConfigToAdd": "Select a configuration to add",
"noItems": "No items to display",
"add": "Add",
"pinCode": "Pin code: {code}",
"@pinCode": {
"placeholders": {
"code": { "type": "String" }
}
},
"tablets": "Tablets",
"stepsCount": "{count} steps",
"@stepsCount": {
"placeholders": {
"count": { "type": "int" }
}
},
"displayedDescriptionLabel": "Displayed description:",
"displayedContentLabel": "Displayed content:",
"displayedNameLabel": "Display name:",
"startTimeLabel": "Start time",
"noAnnotationConfigured": "No annotations configured.",
"newStepTitle": "New step",
"editStepTitle": "Edit step",
"stepTitleLabel": "Step title",
"stepDescriptionLabel": "Step description",
"stepLocationLabel": "Step location:",
"initiallyHiddenLabel": "Initially hidden",
"lockedLabel": "Locked",
"durationSecondsLabel": "Duration (seconds):",
"triggerGeopointLabel": "Trigger GeoPoint (ID):",
"questionsChallengesLabel": "Questions / Challenges",
"noQuestionsConfigured": "No questions configured.",
"newAgendaEventTitle": "New event",
"editAgendaEventTitle": "Edit event",
"eventTitleLabel": "Event title",
"eventDescriptionLabel": "Event description",
"startDateColonLabel": "Start date:",
"videoResourceLabel": "Video:",
"directVideoLinkLabel": "Direct video link:",
"phoneLabel": "Phone:",
"locationGeometryLabel": "Location / Geometry",
"geometryLabel": "Geometry:",
"materialIconLabel": "Icon (material):",
"linearLabel": "Linear:",
"requiredSuccessLabel": "Required success:",
"pathStepsLabel": "Path steps",
"noStepConfigured": "No point/step configured.",
"stepFallback": "Step",
"questionAskedLabel": "Question asked:",
"questionTitleLabel": "Question title",
"expectedAnswerLabel": "Expected answer:",
"expectedAnswerModalLabel": "Expected answer",
"validationNote": "Validation is done by comparison (case-insensitive).",
"possibleAnswersLabel": "Possible answers:",
"noAnswerDefined": "No answer defined. Add at least one.",
"correctAnswer": "Correct answer ✓",
"wrongAnswer": "Wrong answer",
"answerNote": "✓ = correct answer. Multiple can be correct.",
"geopointZoneTitle": "Geographic point / Zone",
"geometryTypeLabel": "Geometry (Point/Line/Zone):",
"phoneModalLabel": "Phone",
"categoryLabel": "Category:",
"startMessageLabel": "Start message:",
"startMessageModalLabel": "Start message",
"createCategoryTitle": "Create category",
"editCategoryTitle": "Edit category",
"categoryIconLabel": "Category icon:",
"selectResource": "Select a resource",
"createPdfTitle": "Create PDF",
"answersLabel": "Answers",
"answerLabel": "Answer",
"createAnswerTitle": "Create answer",
"editAnswerTitle": "Edit answer",
"answerValidNote": "If checked, the answer is valid",
"selectConfiguration": "Select a configuration",
"noConfigFound": "No configuration found",
"backgroundColorLabel": "Background color:",
"updateTabletBtn": "Update tablet",
"kioskUpdatedSuccess": "Kiosk updated",
"webViewError": "The web page cannot be displayed, the URL is incorrect or empty",
"download": "Download",
"resourceDeleteConfirm": "Are you sure you want to delete this resource?",
"categoriesTitle": "Categories",
"endTimeLabel": "End time",
"annotationsLabel": "Annotations"
}

461
lib/l10n/app_fr.arb Normal file
View File

@ -0,0 +1,461 @@
{
"@@locale": "fr",
"cancel": "Annuler",
"save": "Enregistrer",
"create": "Créer",
"delete": "Supprimer",
"close": "Fermer",
"edit": "Modifier",
"actions": "Actions",
"status": "Statut",
"no": "Non",
"name": "Nom",
"type": "Type",
"date": "Date",
"loginSuccess": "Connexion réussie",
"loginError": "Un problème est survenu lors de la connexion",
"rememberMe": "Se souvenir de moi",
"connect": "SE CONNECTER",
"menuApplications": "Applications",
"menuConfigurations": "Configurations",
"menuResources": "Ressources",
"menuStatistics": "Statistiques",
"menuNotifications": "Notifications",
"menuUsers": "Utilisateurs",
"menuApiKeys": "Clés API",
"noPlansAvailable": "Aucun plan disponible. Créez d'abord un plan.",
"noPlan": "Aucun plan (illimité)",
"unlimitedStorage": "Stockage illimité",
"unlimitedAI": "IA illimitée",
"aiRequestsPerMonth": "{count} req IA/mois",
"@aiRequestsPerMonth": {
"placeholders": {
"count": { "type": "int" }
}
},
"storageLabel": "Stockage",
"aiThisMonthLabel": "IA ce mois",
"unlimited": "Illimité",
"requestsAbbr": "req",
"planUpdated": "Plan mis à jour",
"errorMessage": "Erreur : {error}",
"@errorMessage": {
"placeholders": {
"error": { "type": "String" }
}
},
"switchInstance": "Changer d'instance",
"noInstanceFound": "Aucune instance trouvée",
"configurePlan": "Configurer le plan",
"tooltipSwitchInstance": "Changer d'instance",
"usersTitle": "Utilisateurs",
"createUserBtn": "Créer un utilisateur",
"createUserTitle": "Créer un utilisateur",
"editUserTitle": "Modifier l'utilisateur",
"deleteUserTitle": "Supprimer l'utilisateur",
"deleteUserConfirm": "Supprimer {email} ?",
"@deleteUserConfirm": {
"placeholders": {
"email": { "type": "String" }
}
},
"noUsers": "Aucun utilisateur",
"email": "Email",
"firstName": "Prénom",
"lastName": "Nom",
"password": "Mot de passe",
"role": "Rôle",
"tooltipEdit": "Modifier",
"tooltipDelete": "Supprimer",
"notificationsTitle": "Notifications",
"newMessage": "Nouveau message",
"messageTitle": "Titre",
"messageBody": "Message",
"schedule": "Planifier",
"time": "Heure",
"send": "Envoyer",
"cancelNotification": "Annuler la notification",
"cancelNotificationConfirm": "Annuler « {title} » ?",
"@cancelNotificationConfirm": {
"placeholders": {
"title": { "type": "String" }
}
},
"allNotifications": "Toutes",
"sentNotifications": "Envoyées",
"scheduledNotifications": "Planifiées",
"failedNotifications": "Échouées",
"noNotifications": "Aucune notification envoyée",
"noNotificationsForFilter": "Aucune notification pour ce filtre",
"topic": "Topic",
"tooltipCancelNotification": "Annuler",
"resultsCount": "{count, plural, one{{count} résultat} other{{count} résultats}}",
"@resultsCount": {
"placeholders": {
"count": { "type": "int" }
}
},
"apiKeysTitle": "Clés API",
"createApiKey": "Créer une clé API",
"createKeyBtn": "Créer une clé",
"appType": "Type d'application",
"noExpiration": "Pas d'expiration",
"expiresOn": "Expire le {date}",
"@expiresOn": {
"placeholders": {
"date": { "type": "String" }
}
},
"choose": "Choisir",
"removeExpiration": "Supprimer l'expiration",
"apiKeyCreatedTitle": "Clé API créée",
"copyKeyWarning": "Copiez cette clé maintenant — elle ne sera plus affichée.",
"copy": "Copier",
"copiedKey": "J'ai copié la clé",
"revokeApiKeyTitle": "Révoquer la clé API",
"revokeApiKeyConfirm": "Révoquer « {name} » ? Les apps utilisant cette clé perdront l'accès.",
"@revokeApiKeyConfirm": {
"placeholders": {
"name": { "type": "String" }
}
},
"revoke": "Révoquer",
"noApiKeys": "Aucune clé API",
"createdOn": "Créée le",
"expiration": "Expiration",
"activeKey": "Active",
"revokedKey": "Révoquée",
"tooltipRevoke": "Révoquer",
"statisticsTitle": "Statistiques",
"statsLoadError": "Impossible de charger les statistiques",
"statsNoData": "Pas encore de données pour cette période",
"statsNoDataForType": "Aucun event reçu pour le type \"{type}\"",
"@statsNoDataForType": {
"placeholders": {
"type": { "type": "String" }
}
},
"statsSessions": "Sessions",
"statsAvgDuration": "Durée moy.",
"statsTopApp": "App top",
"statsTopLang": "Langue top",
"statsVisitsByDay": "Visites par jour",
"statsAll": "Tous",
"statsTopSections": "Top sections",
"statsApps": "Apps",
"statsLanguages": "Langues",
"statsTopPOI": "Top POI",
"statsPOI": "POI",
"statsTaps": "Taps",
"statsTopAgenda": "Top événements agenda",
"statsEvent": "Événement",
"statsQuiz": "Quiz",
"statsSection": "Section",
"statsAvgScore": "Score moy",
"statsCompletions": "Complétions",
"statsGames": "Jeux",
"statsGameType": "Type",
"statsArticles": "Articles lus",
"statsReadings": "Lectures",
"statsMenuTitle": "Menu",
"statsMenuItem": "Item",
"statsQrScans": "QR Scans",
"statsTotal": "Total",
"statsValid": "Valides",
"statsInvalid": "Invalides",
"statsViews": "Vues",
"noData": "Aucune donnée",
"errorOccurred": "Une erreur est survenue",
"yes": "Oui",
"newConfiguration": "Nouvelle configuration",
"configNameLabel": "Nom :",
"orText": "ou",
"importLabel": "Importer",
"configNameRequired": "Veuillez spécifier un nom pour la nouvelle visite",
"configCreatedSuccess": "La configuration a été créée avec succès",
"configExportSuccess": "L'export de la configuration a réussi, le document se trouve à cet endroit : {path}",
"@configExportSuccess": {
"placeholders": {
"path": { "type": "String" }
}
},
"configExportFailed": "L'export de la configuration a échoué",
"configDeletedSuccess": "La configuration a été supprimée avec succès",
"configSavedSuccess": "La configuration a été sauvegardée avec succès",
"configDeleteConfirm": "Êtes-vous sûr de vouloir supprimer cette configuration ?",
"newSection": "Nouvelle section",
"newSubSection": "Nouvelle sous section",
"sectionNameLabel": "Nom :",
"sectionTypeLabel": "Type:",
"sectionNameRequired": "Veuillez spécifier un nom pour la nouvelle section",
"sectionCreatedSuccess": "La section a été créée avec succès !",
"sectionDeleteConfirm": "Êtes-vous sûr de vouloir supprimer cette section ?",
"sectionSavedSuccess": "La section a été sauvegardée avec succès",
"sectionTranslationSaved": "Les traductions de la section ont été sauvegardées avec succès",
"sectionLoadError": "Une erreur est survenue lors de la récupération de la section",
"qrCodeCopied": "Ce QR code a été copié dans le presse papier",
"beaconLabel": "Beacon :",
"beaconIdLabel": "Identifiant Beacon :",
"beaconMustBeNumber": "Cela doit être un chiffre",
"identifierLabel": "Identifiant :",
"displayTitleLabel": "Titre affiché:",
"imageLabel": "Image :",
"backgroundImageLabel": "Image fond d'écran :",
"questionInputLabel": "Question :",
"videoUrlLabel": "Url de la vidéo:",
"webUrlLabel": "Url du site web:",
"subSectionUpdatedSuccess": "La sous section a été mis à jour avec succès",
"subSectionUpdateError": "Une erreur est survenue lors de la mise à jour de la sous section",
"subSectionDeletedSuccess": "La sous section a été supprimée avec succès",
"subSectionDeleteError": "Une erreur est survenue lors de la suppression de la sous section",
"subSectionOrderUpdatedSuccess": "L'ordre des sous sections a été mis à jour avec succès",
"subSectionOrderUpdateError": "Une erreur est survenue lors de la mise à jour de l'ordre des sous sections",
"subSectionCreatedSuccess": "La sous section a été créée avec succès",
"subSectionCreateError": "Une erreur est survenue lors de la création de la sous section",
"questionsLabel": "Questions",
"questionLabel": "Question",
"editQuestion": "Modifier la question",
"questionDeleteConfirm": "Êtes-vous sûr de vouloir supprimer cette question ?",
"questionsLoadError": "Une erreur est survenue lors de la récupération des questions",
"quizBadScore": "Mauvais score",
"quizMediumScore": "Moyen score",
"quizGoodScore": "Bon score",
"quizExcellentScore": "Excellent score",
"quizBadScoreMsg": "Message pour un mauvais score",
"quizMediumScoreMsg": "Message pour un score moyen",
"quizGoodScoreMsg": "Message pour un bon score",
"quizExcellentScoreMsg": "Message pour un excellent score",
"questionOrderUpdatedSuccess": "L'ordre des questions a été mis à jour avec succès",
"questionOrderUpdateError": "Une erreur est survenue lors de la mise à jour de l'ordre des questions",
"questionCreatedSuccess": "La question a été créée avec succès",
"questionCreateError": "Une erreur est survenue lors de la création de la question",
"questionUpdatedSuccess": "La question a été mis à jour avec succès",
"questionUpdateError": "Une erreur est survenue lors de la mise à jour de la question",
"questionDeletedSuccess": "La question a été supprimée avec succès",
"questionDeleteError": "Une erreur est survenue lors de la suppression de la question",
"translationIncomplete": "La traduction n'est pas complète",
"geopointCreatedSuccess": "Le point a été créé avec succès",
"geopointCreateError": "Une erreur est survenue lors de la création du point",
"geopointUpdatedSuccess": "Le point a été mis à jour avec succès",
"geopointUpdateError": "Une erreur est survenue lors de la mise à jour du point",
"geopointDeletedSuccess": "Le point a été supprimé avec succès",
"geopointDeleteError": "Une erreur est survenue lors de la suppression du point",
"categoryCreatedSuccess": "La catégorie a été créée avec succès",
"categoryCreateError": "Une erreur est survenue lors de la création de la catégorie",
"categoryUpdatedSuccess": "La catégorie a été mise à jour avec succès",
"categoryUpdateError": "Une erreur est survenue lors de la mise à jour de la catégorie",
"eventCreatedSuccess": "L'événement a été créé avec succès",
"eventCreateError": "Une erreur est survenue lors de la création de l'événement",
"eventUpdatedSuccess": "L'événement a été mis à jour avec succès",
"eventUpdateError": "Une erreur est survenue lors de la mise à jour de l'événement",
"eventDeletedSuccess": "L'événement a été supprimé avec succès",
"eventDeleteError": "Une erreur est survenue lors de la suppression de l'événement",
"annotationCreatedSuccess": "L'annotation a été créée avec succès",
"annotationCreateError": "Une erreur est survenue lors de la création de l'annotation",
"annotationUpdatedSuccess": "L'annotation a été mise à jour avec succès",
"annotationUpdateError": "Une erreur est survenue lors de la mise à jour de l'annotation",
"programmeBlockCreatedSuccess": "Le bloc a été créé avec succès",
"programmeBlockCreateError": "Une erreur est survenue lors de la création du bloc",
"programmeBlockUpdatedSuccess": "Le bloc a été mis à jour avec succès",
"programmeBlockUpdateError": "Une erreur est survenue lors de la mise à jour du bloc",
"pathCreatedSuccess": "Le parcours a été créé avec succès",
"pathCreateError": "Une erreur est survenue lors de la création du parcours",
"pathUpdatedSuccess": "Le parcours a été mis à jour avec succès",
"pathUpdateError": "Une erreur est survenue lors de la mise à jour du parcours",
"stepCreatedSuccess": "L'étape a été créée avec succès",
"stepCreateError": "Une erreur est survenue lors de la création de l'étape",
"stepUpdatedSuccess": "L'étape a été mise à jour avec succès",
"stepUpdateError": "Une erreur est survenue lors de la mise à jour de l'étape",
"agendaEventsLabel": "Évènements",
"agendaEventFallback": "Évènement",
"addEvent": "Ajouter un évènement",
"noEvents": "Aucun évènement",
"noAddress": "Pas d'adresse",
"onlineLabel": "En ligne :",
"mapViewLabel": "Vue carte :",
"mapServiceLabel": "Service carte :",
"jsonFilesLabel": "Fichiers json :",
"jsonLabel": "JSON",
"guidedPathsLabel": "Parcours Guidés",
"addPath": "Ajouter un parcours",
"noPathConfigured": "Aucun parcours configuré",
"pathOrderUpdateError": "Erreur lors de la mise à jour de l'ordre",
"pathNoTitle": "Parcours sans titre",
"pathDeletedSuccess": "Parcours supprimé avec succès",
"pathDeleteError": "Erreur lors de la suppression du parcours",
"pathsLabel": "Parcours",
"pointsOfInterestLabel": "Points d'Intérêt",
"geopointsLabel": "Points géographiques",
"searchLabel": "Recherche :",
"geopointsLoadError": "Une erreur est survenue lors de la récupération des points géographiques",
"geopointDeleteConfirm": "Êtes-vous sûr de vouloir supprimer ce point géographique ?",
"serviceLabel": "Service :",
"centerPointLabel": "Point de centrage :",
"iconLabel": "Icône :",
"listViewLabel": "Vue liste :",
"typeLabel": "Type :",
"zoomLabel": "Zoom :",
"categoriesLabel": "Catégories :",
"startDateLabel": "Date de début",
"notDefined": "Non définie",
"endDateLabel": "Date de fin",
"programmeLabel": "Programme",
"addBlock": "Ajouter un bloc",
"blockFallback": "Bloc",
"noBlocks": "Aucun bloc de programme défini",
"programmeBlockDeletedSuccess": "Bloc supprimé avec succès",
"programmeBlockDeleteError": "Erreur lors de la suppression du bloc",
"baseMapLabel": "Carte de base",
"mapLabel": "Carte :",
"globalAnnotationsLabel": "Annotations globales",
"addAnnotation": "Ajouter une annotation",
"noAnnotations": "Aucune annotation globale",
"annotationFallback": "Annotation",
"annotationDeletedSuccess": "Annotation supprimée",
"annotationDeleteError": "Erreur lors de la suppression",
"agendaEventCreatedSuccess": "L'événement a été créé avec succès",
"agendaEventCreateError": "Une erreur est survenue lors de la création de l'événement",
"agendaEventUpdatedSuccess": "L'événement a été mis à jour avec succès",
"agendaEventUpdateError": "Une erreur est survenue lors de la mise à jour de l'événement",
"agendaEventDeletedSuccess": "L'événement a été supprimé avec succès",
"agendaEventDeleteError": "Une erreur est survenue lors de la suppression de l'événement",
"newResource": "Nouvelle ressource",
"resourceCreatedSuccess": "La ressource a été créée avec succès",
"resourceCreateError": "Une erreur est survenue lors de la création de la ressource",
"resourceUpdatedSuccess": "La ressource a été mise à jour avec succès",
"resourceNoFileLoaded": "Aucun fichier n'a été chargé",
"resourceNameRequired": "Veuillez donner un nom à la ressource",
"resourceTooLarge": "Erreur, attention, la taille de la ressource doit être inférieure à 1.5Mb",
"resourceInvalidUrl": "L'url est invalide",
"resourceUrlRequired": "Veuillez remplir le champ URL",
"generalInfo": "Informations générales",
"mainImageLabel": "Image principale :",
"loaderLabel": "Loader :",
"primaryColorLabel": "Couleur principale :",
"secondaryColorLabel": "Couleur secondaire :",
"layoutLabel": "Affichage :",
"layoutGrid": "Grille",
"languagesLabel": "Langues :",
"featuredEventLabel": "Evènement à l'affiche :",
"chooseEvent": "Choisir un évènement",
"noneOption": "Aucun",
"aiAssistantLabel": "Assistant IA :",
"appUpdatedSuccess": "Application mobile mise à jour",
"configActivatedSuccess": "Configuration activée avec succès",
"configDeactivatedSuccess": "Configuration désactivée avec succès",
"configRemoveConfirm": "Êtes-vous sûr de vouloir retirer cette configuration de l'application ?",
"configRemovedSuccess": "La configuration a été retirée de l'application avec succès",
"configRemoveError": "Une erreur est survenue lors du retrait de la configuration",
"phoneConfigTitle": "Configurations par appareil",
"addConfig": "Ajouter une configuration",
"selectConfigToAdd": "Sélectionner une configuration à ajouter",
"noItems": "Aucun élément à afficher",
"add": "Ajouter",
"pinCode": "Code pin: {code}",
"@pinCode": {
"placeholders": {
"code": { "type": "String" }
}
},
"tablets": "Tablettes",
"stepsCount": "{count} étapes",
"@stepsCount": {
"placeholders": {
"count": { "type": "int" }
}
},
"displayedDescriptionLabel": "Description affichée:",
"displayedContentLabel": "Contenu affiché :",
"displayedNameLabel": "Nom affiché :",
"startTimeLabel": "Heure de début",
"noAnnotationConfigured": "Aucune annotation configurée.",
"newStepTitle": "Nouvelle Étape",
"editStepTitle": "Modifier l'Étape",
"stepTitleLabel": "Titre de l'étape",
"stepDescriptionLabel": "Description de l'étape",
"stepLocationLabel": "Emplacement de l'étape :",
"initiallyHiddenLabel": "Cachée initialement",
"lockedLabel": "Verrouillée",
"durationSecondsLabel": "Durée (secondes) :",
"triggerGeopointLabel": "GeoPoint de déclenchement (ID) :",
"questionsChallengesLabel": "Questions / Défis",
"noQuestionsConfigured": "Aucune question configurée.",
"newAgendaEventTitle": "Nouvel Évènement",
"editAgendaEventTitle": "Modifier l'Évènement",
"eventTitleLabel": "Titre de l'évènement",
"eventDescriptionLabel": "Description de l'évènement",
"startDateColonLabel": "Date de début :",
"videoResourceLabel": "Vidéo :",
"directVideoLinkLabel": "Lien vidéo direct :",
"phoneLabel": "Téléphone :",
"locationGeometryLabel": "Localisation / Géométrie",
"geometryLabel": "Géométrie :",
"materialIconLabel": "Icône (material) :",
"linearLabel": "Linéaire :",
"requiredSuccessLabel": "Réussite requise :",
"pathStepsLabel": "Étapes du parcours",
"noStepConfigured": "Aucun point/étape configuré.",
"stepFallback": "Étape",
"questionAskedLabel": "Question posée :",
"questionTitleLabel": "Intitulé de la question",
"expectedAnswerLabel": "Réponse attendue :",
"expectedAnswerModalLabel": "Réponse attendue",
"validationNote": "La validation se fait par comparaison (insensible à la casse).",
"possibleAnswersLabel": "Réponses possibles :",
"noAnswerDefined": "Aucune réponse définie. Ajoutez-en au moins une.",
"correctAnswer": "Bonne réponse ✓",
"wrongAnswer": "Mauvaise réponse",
"answerNote": "✓ = bonne réponse. Plusieurs peuvent être correctes.",
"geopointZoneTitle": "Point géographique / Zone",
"geometryTypeLabel": "Géométrie (Point/Ligne/Zone) :",
"phoneModalLabel": "Téléphone",
"categoryLabel": "Catégorie :",
"startMessageLabel": "Message départ :",
"startMessageModalLabel": "Message départ",
"createCategoryTitle": "Création catégorie",
"editCategoryTitle": "Modification catégorie",
"categoryIconLabel": "Icône catégorie :",
"selectResource": "Sélectionner une ressource",
"createPdfTitle": "Création PDF",
"answersLabel": "Réponses",
"answerLabel": "Réponse",
"createAnswerTitle": "Créer la réponse",
"editAnswerTitle": "Modifier la réponse",
"answerValidNote": "Si coché, la réponse est valide",
"selectConfiguration": "Sélectionner une configuration",
"noConfigFound": "Aucune configuration trouvée",
"backgroundColorLabel": "Couleur fond d'écran :",
"updateTabletBtn": "Mettre à jour la tablette",
"kioskUpdatedSuccess": "Le kiosk a été mis à jour",
"webViewError": "La page internet ne peut pas être affichée, l'url est incorrecte ou vide",
"download": "Télécharger",
"resourceDeleteConfirm": "Êtes-vous sûr de vouloir supprimer cette ressource ?",
"categoriesTitle": "Catégories",
"endTimeLabel": "Heure de fin",
"annotationsLabel": "Annotations"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

461
lib/l10n/app_nl.arb Normal file
View File

@ -0,0 +1,461 @@
{
"@@locale": "nl",
"cancel": "Annuleren",
"save": "Opslaan",
"create": "Aanmaken",
"delete": "Verwijderen",
"close": "Sluiten",
"edit": "Bewerken",
"actions": "Acties",
"status": "Status",
"no": "Nee",
"name": "Naam",
"type": "Type",
"date": "Datum",
"loginSuccess": "Inloggen geslaagd",
"loginError": "Er is een probleem opgetreden bij het inloggen",
"rememberMe": "Onthoud mij",
"connect": "INLOGGEN",
"menuApplications": "Applicaties",
"menuConfigurations": "Configuraties",
"menuResources": "Bronnen",
"menuStatistics": "Statistieken",
"menuNotifications": "Meldingen",
"menuUsers": "Gebruikers",
"menuApiKeys": "API-sleutels",
"noPlansAvailable": "Geen plannen beschikbaar. Maak eerst een plan aan.",
"noPlan": "Geen plan (onbeperkt)",
"unlimitedStorage": "Onbeperkte opslag",
"unlimitedAI": "Onbeperkte AI",
"aiRequestsPerMonth": "{count} AI-verz./maand",
"@aiRequestsPerMonth": {
"placeholders": {
"count": { "type": "int" }
}
},
"storageLabel": "Opslag",
"aiThisMonthLabel": "AI deze maand",
"unlimited": "Onbeperkt",
"requestsAbbr": "verz.",
"planUpdated": "Plan bijgewerkt",
"errorMessage": "Fout: {error}",
"@errorMessage": {
"placeholders": {
"error": { "type": "String" }
}
},
"switchInstance": "Instantie wisselen",
"noInstanceFound": "Geen instantie gevonden",
"configurePlan": "Plan configureren",
"tooltipSwitchInstance": "Instantie wisselen",
"usersTitle": "Gebruikers",
"createUserBtn": "Gebruiker aanmaken",
"createUserTitle": "Gebruiker aanmaken",
"editUserTitle": "Gebruiker bewerken",
"deleteUserTitle": "Gebruiker verwijderen",
"deleteUserConfirm": "{email} verwijderen?",
"@deleteUserConfirm": {
"placeholders": {
"email": { "type": "String" }
}
},
"noUsers": "Geen gebruikers",
"email": "E-mail",
"firstName": "Voornaam",
"lastName": "Naam",
"password": "Wachtwoord",
"role": "Rol",
"tooltipEdit": "Bewerken",
"tooltipDelete": "Verwijderen",
"notificationsTitle": "Meldingen",
"newMessage": "Nieuw bericht",
"messageTitle": "Titel",
"messageBody": "Bericht",
"schedule": "Plannen",
"time": "Tijd",
"send": "Versturen",
"cancelNotification": "Melding annuleren",
"cancelNotificationConfirm": "« {title} » annuleren?",
"@cancelNotificationConfirm": {
"placeholders": {
"title": { "type": "String" }
}
},
"allNotifications": "Alle",
"sentNotifications": "Verzonden",
"scheduledNotifications": "Gepland",
"failedNotifications": "Mislukt",
"noNotifications": "Geen meldingen verzonden",
"noNotificationsForFilter": "Geen meldingen voor dit filter",
"topic": "Onderwerp",
"tooltipCancelNotification": "Annuleren",
"resultsCount": "{count, plural, one{{count} resultaat} other{{count} resultaten}}",
"@resultsCount": {
"placeholders": {
"count": { "type": "int" }
}
},
"apiKeysTitle": "API-sleutels",
"createApiKey": "API-sleutel aanmaken",
"createKeyBtn": "Sleutel aanmaken",
"appType": "Applicatietype",
"noExpiration": "Geen vervaldatum",
"expiresOn": "Verloopt op {date}",
"@expiresOn": {
"placeholders": {
"date": { "type": "String" }
}
},
"choose": "Kiezen",
"removeExpiration": "Vervaldatum verwijderen",
"apiKeyCreatedTitle": "API-sleutel aangemaakt",
"copyKeyWarning": "Kopieer deze sleutel nu — hij wordt niet meer weergegeven.",
"copy": "Kopiëren",
"copiedKey": "Ik heb de sleutel gekopieerd",
"revokeApiKeyTitle": "API-sleutel intrekken",
"revokeApiKeyConfirm": "« {name} » intrekken? Apps die deze sleutel gebruiken verliezen toegang.",
"@revokeApiKeyConfirm": {
"placeholders": {
"name": { "type": "String" }
}
},
"revoke": "Intrekken",
"noApiKeys": "Geen API-sleutels",
"createdOn": "Aangemaakt op",
"expiration": "Vervaldatum",
"activeKey": "Actief",
"revokedKey": "Ingetrokken",
"tooltipRevoke": "Intrekken",
"statisticsTitle": "Statistieken",
"statsLoadError": "Statistieken kunnen niet worden geladen",
"statsNoData": "Nog geen gegevens voor deze periode",
"statsNoDataForType": "Geen events ontvangen voor type \"{type}\"",
"@statsNoDataForType": {
"placeholders": {
"type": { "type": "String" }
}
},
"statsSessions": "Sessies",
"statsAvgDuration": "Gem. duur",
"statsTopApp": "Top app",
"statsTopLang": "Top taal",
"statsVisitsByDay": "Bezoeken per dag",
"statsAll": "Alle",
"statsTopSections": "Top secties",
"statsApps": "Apps",
"statsLanguages": "Talen",
"statsTopPOI": "Top POI",
"statsPOI": "POI",
"statsTaps": "Taps",
"statsTopAgenda": "Top agenda-evenementen",
"statsEvent": "Evenement",
"statsQuiz": "Quiz",
"statsSection": "Sectie",
"statsAvgScore": "Gem. score",
"statsCompletions": "Voltooiingen",
"statsGames": "Spellen",
"statsGameType": "Type",
"statsArticles": "Gelezen artikelen",
"statsReadings": "Lezingen",
"statsMenuTitle": "Menu",
"statsMenuItem": "Item",
"statsQrScans": "QR-scans",
"statsTotal": "Totaal",
"statsValid": "Geldig",
"statsInvalid": "Ongeldig",
"statsViews": "Weergaven",
"noData": "Geen gegevens",
"errorOccurred": "Er is een fout opgetreden",
"yes": "Ja",
"newConfiguration": "Nieuwe configuratie",
"configNameLabel": "Naam:",
"orText": "of",
"importLabel": "Importeren",
"configNameRequired": "Geef een naam op voor het nieuwe bezoek",
"configCreatedSuccess": "Configuratie succesvol aangemaakt",
"configExportSuccess": "Configuratie succesvol geëxporteerd, bestand op: {path}",
"@configExportSuccess": {
"placeholders": {
"path": { "type": "String" }
}
},
"configExportFailed": "Export van configuratie mislukt",
"configDeletedSuccess": "Configuratie succesvol verwijderd",
"configSavedSuccess": "Configuratie succesvol opgeslagen",
"configDeleteConfirm": "Weet u zeker dat u deze configuratie wilt verwijderen?",
"newSection": "Nieuwe sectie",
"newSubSection": "Nieuwe subsectie",
"sectionNameLabel": "Naam:",
"sectionTypeLabel": "Type:",
"sectionNameRequired": "Geef een naam op voor de nieuwe sectie",
"sectionCreatedSuccess": "Sectie succesvol aangemaakt!",
"sectionDeleteConfirm": "Weet u zeker dat u deze sectie wilt verwijderen?",
"sectionSavedSuccess": "Sectie succesvol opgeslagen",
"sectionTranslationSaved": "Sectievertaling succesvol opgeslagen",
"sectionLoadError": "Er is een fout opgetreden bij het laden van de sectie",
"qrCodeCopied": "Deze QR-code is naar het klembord gekopieerd",
"beaconLabel": "Beacon:",
"beaconIdLabel": "Beacon-ID:",
"beaconMustBeNumber": "Dit moet een getal zijn",
"identifierLabel": "Identificator:",
"displayTitleLabel": "Weergegeven titel:",
"imageLabel": "Afbeelding:",
"backgroundImageLabel": "Achtergrondafbeelding:",
"questionInputLabel": "Vraag:",
"videoUrlLabel": "Video-URL:",
"webUrlLabel": "Website-URL:",
"subSectionUpdatedSuccess": "Subsectie succesvol bijgewerkt",
"subSectionUpdateError": "Er is een fout opgetreden bij het bijwerken van de subsectie",
"subSectionDeletedSuccess": "Subsectie succesvol verwijderd",
"subSectionDeleteError": "Er is een fout opgetreden bij het verwijderen van de subsectie",
"subSectionOrderUpdatedSuccess": "Volgorde van subsecties succesvol bijgewerkt",
"subSectionOrderUpdateError": "Er is een fout opgetreden bij het bijwerken van de volgorde",
"subSectionCreatedSuccess": "Subsectie succesvol aangemaakt",
"subSectionCreateError": "Er is een fout opgetreden bij het aanmaken van de subsectie",
"questionsLabel": "Vragen",
"questionLabel": "Vraag",
"editQuestion": "Vraag bewerken",
"questionDeleteConfirm": "Weet u zeker dat u deze vraag wilt verwijderen?",
"questionsLoadError": "Er is een fout opgetreden bij het laden van de vragen",
"quizBadScore": "Slechte score",
"quizMediumScore": "Gemiddelde score",
"quizGoodScore": "Goede score",
"quizExcellentScore": "Uitstekende score",
"quizBadScoreMsg": "Bericht voor een slechte score",
"quizMediumScoreMsg": "Bericht voor een gemiddelde score",
"quizGoodScoreMsg": "Bericht voor een goede score",
"quizExcellentScoreMsg": "Bericht voor een uitstekende score",
"questionOrderUpdatedSuccess": "Vraagvolgorde succesvol bijgewerkt",
"questionOrderUpdateError": "Er is een fout opgetreden bij het bijwerken van de vraagvolgorde",
"questionCreatedSuccess": "Vraag succesvol aangemaakt",
"questionCreateError": "Er is een fout opgetreden bij het aanmaken van de vraag",
"questionUpdatedSuccess": "Vraag succesvol bijgewerkt",
"questionUpdateError": "Er is een fout opgetreden bij het bijwerken van de vraag",
"questionDeletedSuccess": "Vraag succesvol verwijderd",
"questionDeleteError": "Er is een fout opgetreden bij het verwijderen van de vraag",
"translationIncomplete": "De vertaling is niet volledig",
"geopointCreatedSuccess": "Punt succesvol aangemaakt",
"geopointCreateError": "Er is een fout opgetreden bij het aanmaken van het punt",
"geopointUpdatedSuccess": "Punt succesvol bijgewerkt",
"geopointUpdateError": "Er is een fout opgetreden bij het bijwerken van het punt",
"geopointDeletedSuccess": "Punt succesvol verwijderd",
"geopointDeleteError": "Er is een fout opgetreden bij het verwijderen van het punt",
"categoryCreatedSuccess": "Categorie succesvol aangemaakt",
"categoryCreateError": "Er is een fout opgetreden bij het aanmaken van de categorie",
"categoryUpdatedSuccess": "Categorie succesvol bijgewerkt",
"categoryUpdateError": "Er is een fout opgetreden bij het bijwerken van de categorie",
"eventCreatedSuccess": "Evenement succesvol aangemaakt",
"eventCreateError": "Er is een fout opgetreden bij het aanmaken van het evenement",
"eventUpdatedSuccess": "Evenement succesvol bijgewerkt",
"eventUpdateError": "Er is een fout opgetreden bij het bijwerken van het evenement",
"eventDeletedSuccess": "Evenement succesvol verwijderd",
"eventDeleteError": "Er is een fout opgetreden bij het verwijderen van het evenement",
"annotationCreatedSuccess": "Annotatie succesvol aangemaakt",
"annotationCreateError": "Er is een fout opgetreden bij het aanmaken van de annotatie",
"annotationUpdatedSuccess": "Annotatie succesvol bijgewerkt",
"annotationUpdateError": "Er is een fout opgetreden bij het bijwerken van de annotatie",
"programmeBlockCreatedSuccess": "Blok succesvol aangemaakt",
"programmeBlockCreateError": "Er is een fout opgetreden bij het aanmaken van het blok",
"programmeBlockUpdatedSuccess": "Blok succesvol bijgewerkt",
"programmeBlockUpdateError": "Er is een fout opgetreden bij het bijwerken van het blok",
"pathCreatedSuccess": "Parcours succesvol aangemaakt",
"pathCreateError": "Er is een fout opgetreden bij het aanmaken van het parcours",
"pathUpdatedSuccess": "Parcours succesvol bijgewerkt",
"pathUpdateError": "Er is een fout opgetreden bij het bijwerken van het parcours",
"stepCreatedSuccess": "Stap succesvol aangemaakt",
"stepCreateError": "Er is een fout opgetreden bij het aanmaken van de stap",
"stepUpdatedSuccess": "Stap succesvol bijgewerkt",
"stepUpdateError": "Er is een fout opgetreden bij het bijwerken van de stap",
"agendaEventsLabel": "Evenementen",
"agendaEventFallback": "Evenement",
"addEvent": "Evenement toevoegen",
"noEvents": "Geen evenementen",
"noAddress": "Geen adres",
"onlineLabel": "Online:",
"mapViewLabel": "Kaartweergave:",
"mapServiceLabel": "Kaartservice:",
"jsonFilesLabel": "JSON-bestanden:",
"jsonLabel": "JSON",
"guidedPathsLabel": "Begeleide parcours",
"addPath": "Parcours toevoegen",
"noPathConfigured": "Geen parcours geconfigureerd",
"pathOrderUpdateError": "Fout bij het bijwerken van de volgorde",
"pathNoTitle": "Parcours zonder titel",
"pathDeletedSuccess": "Parcours succesvol verwijderd",
"pathDeleteError": "Fout bij het verwijderen van het parcours",
"pathsLabel": "Parcours",
"pointsOfInterestLabel": "Bezienswaardigheden",
"geopointsLabel": "Geografische punten",
"searchLabel": "Zoeken:",
"geopointsLoadError": "Fout bij het laden van geografische punten",
"geopointDeleteConfirm": "Weet u zeker dat u dit geografische punt wilt verwijderen?",
"serviceLabel": "Service:",
"centerPointLabel": "Middelpunt:",
"iconLabel": "Pictogram:",
"listViewLabel": "Lijstweergave:",
"typeLabel": "Type:",
"zoomLabel": "Zoom:",
"categoriesLabel": "Categorieën:",
"startDateLabel": "Startdatum",
"notDefined": "Niet gedefinieerd",
"endDateLabel": "Einddatum",
"programmeLabel": "Programma",
"addBlock": "Blok toevoegen",
"blockFallback": "Blok",
"noBlocks": "Geen programmablokken gedefinieerd",
"programmeBlockDeletedSuccess": "Blok succesvol verwijderd",
"programmeBlockDeleteError": "Fout bij het verwijderen van het blok",
"baseMapLabel": "Basiskaart",
"mapLabel": "Kaart:",
"globalAnnotationsLabel": "Globale annotaties",
"addAnnotation": "Annotatie toevoegen",
"noAnnotations": "Geen globale annotaties",
"annotationFallback": "Annotatie",
"annotationDeletedSuccess": "Annotatie verwijderd",
"annotationDeleteError": "Fout bij verwijderen",
"agendaEventCreatedSuccess": "Evenement succesvol aangemaakt",
"agendaEventCreateError": "Er is een fout opgetreden bij het aanmaken van het evenement",
"agendaEventUpdatedSuccess": "Evenement succesvol bijgewerkt",
"agendaEventUpdateError": "Er is een fout opgetreden bij het bijwerken van het evenement",
"agendaEventDeletedSuccess": "Evenement succesvol verwijderd",
"agendaEventDeleteError": "Er is een fout opgetreden bij het verwijderen van het evenement",
"newResource": "Nieuwe bron",
"resourceCreatedSuccess": "Bron succesvol aangemaakt",
"resourceCreateError": "Er is een fout opgetreden bij het aanmaken van de bron",
"resourceUpdatedSuccess": "Bron succesvol bijgewerkt",
"resourceNoFileLoaded": "Er is geen bestand geladen",
"resourceNameRequired": "Geef de bron een naam",
"resourceTooLarge": "Fout: de bestandsgrootte moet kleiner zijn dan 1,5 MB",
"resourceInvalidUrl": "De URL is ongeldig",
"resourceUrlRequired": "Vul het URL-veld in",
"generalInfo": "Algemene informatie",
"mainImageLabel": "Hoofdafbeelding:",
"loaderLabel": "Loader:",
"primaryColorLabel": "Primaire kleur:",
"secondaryColorLabel": "Secundaire kleur:",
"layoutLabel": "Weergave:",
"layoutGrid": "Raster",
"languagesLabel": "Talen:",
"featuredEventLabel": "Uitgelicht evenement:",
"chooseEvent": "Kies een evenement",
"noneOption": "Geen",
"aiAssistantLabel": "AI-assistent:",
"appUpdatedSuccess": "Mobiele applicatie bijgewerkt",
"configActivatedSuccess": "Configuratie succesvol geactiveerd",
"configDeactivatedSuccess": "Configuratie succesvol gedeactiveerd",
"configRemoveConfirm": "Weet u zeker dat u deze configuratie uit de applicatie wilt verwijderen?",
"configRemovedSuccess": "Configuratie succesvol verwijderd uit de applicatie",
"configRemoveError": "Er is een fout opgetreden bij het verwijderen van de configuratie",
"phoneConfigTitle": "Configuraties per apparaat",
"addConfig": "Configuratie toevoegen",
"selectConfigToAdd": "Selecteer een configuratie om toe te voegen",
"noItems": "Geen items om weer te geven",
"add": "Toevoegen",
"pinCode": "Pincode: {code}",
"@pinCode": {
"placeholders": {
"code": { "type": "String" }
}
},
"tablets": "Tablets",
"stepsCount": "{count} stappen",
"@stepsCount": {
"placeholders": {
"count": { "type": "int" }
}
},
"displayedDescriptionLabel": "Weergegeven beschrijving:",
"displayedContentLabel": "Weergegeven inhoud:",
"displayedNameLabel": "Weergavenaam:",
"startTimeLabel": "Begintijd",
"noAnnotationConfigured": "Geen annotaties geconfigureerd.",
"newStepTitle": "Nieuwe stap",
"editStepTitle": "Stap bewerken",
"stepTitleLabel": "Staptitel",
"stepDescriptionLabel": "Stapbeschrijving",
"stepLocationLabel": "Stap locatie:",
"initiallyHiddenLabel": "Aanvankelijk verborgen",
"lockedLabel": "Vergrendeld",
"durationSecondsLabel": "Duur (seconden):",
"triggerGeopointLabel": "GeoPoint trigger (ID):",
"questionsChallengesLabel": "Vragen / Uitdagingen",
"noQuestionsConfigured": "Geen vragen geconfigureerd.",
"newAgendaEventTitle": "Nieuw evenement",
"editAgendaEventTitle": "Evenement bewerken",
"eventTitleLabel": "Evenementtitel",
"eventDescriptionLabel": "Evenementbeschrijving",
"startDateColonLabel": "Begindatum:",
"videoResourceLabel": "Video:",
"directVideoLinkLabel": "Directe videolink:",
"phoneLabel": "Telefoon:",
"locationGeometryLabel": "Locatie / Geometrie",
"geometryLabel": "Geometrie:",
"materialIconLabel": "Icoon (material):",
"linearLabel": "Lineair:",
"requiredSuccessLabel": "Vereist succes:",
"pathStepsLabel": "Parcoursstappen",
"noStepConfigured": "Geen punt/stap geconfigureerd.",
"stepFallback": "Stap",
"questionAskedLabel": "Gestelde vraag:",
"questionTitleLabel": "Vraagstitel",
"expectedAnswerLabel": "Verwacht antwoord:",
"expectedAnswerModalLabel": "Verwacht antwoord",
"validationNote": "Validatie gebeurt door vergelijking (hoofdletterongevoelig).",
"possibleAnswersLabel": "Mogelijke antwoorden:",
"noAnswerDefined": "Geen antwoord gedefinieerd. Voeg er minstens één toe.",
"correctAnswer": "Correct antwoord ✓",
"wrongAnswer": "Fout antwoord",
"answerNote": "✓ = correct antwoord. Meerdere kunnen correct zijn.",
"geopointZoneTitle": "Geografisch punt / Zone",
"geometryTypeLabel": "Geometrie (Punt/Lijn/Zone):",
"phoneModalLabel": "Telefoon",
"categoryLabel": "Categorie:",
"startMessageLabel": "Startbericht:",
"startMessageModalLabel": "Startbericht",
"createCategoryTitle": "Categorie aanmaken",
"editCategoryTitle": "Categorie bewerken",
"categoryIconLabel": "Categorie-icoon:",
"selectResource": "Selecteer een resource",
"createPdfTitle": "PDF aanmaken",
"answersLabel": "Antwoorden",
"answerLabel": "Antwoord",
"createAnswerTitle": "Antwoord aanmaken",
"editAnswerTitle": "Antwoord bewerken",
"answerValidNote": "Indien aangevinkt, is het antwoord geldig",
"selectConfiguration": "Selecteer een configuratie",
"noConfigFound": "Geen configuratie gevonden",
"backgroundColorLabel": "Achtergrondkleur:",
"updateTabletBtn": "Tablet bijwerken",
"kioskUpdatedSuccess": "Kiosk bijgewerkt",
"webViewError": "De webpagina kan niet worden weergegeven, de URL is onjuist of leeg",
"download": "Downloaden",
"resourceDeleteConfirm": "Weet u zeker dat u deze resource wilt verwijderen?",
"categoriesTitle": "Categorieën",
"endTimeLabel": "Eindtijd",
"annotationsLabel": "Annotaties"
}

View File

@ -1,5 +1,6 @@
import 'dart:ui'; import 'dart:ui';
import 'package:manager_app/l10n/app_localizations.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter_quill/flutter_quill.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
@ -234,6 +235,7 @@ class _MyAppState extends State<MyApp> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final locale = Provider.of<AppContext>(context).getContext()?.locale ?? const Locale('fr');
return MaterialApp.router( return MaterialApp.router(
routerConfig: widget.router, routerConfig: widget.router,
key: mainKey, key: mainKey,
@ -246,9 +248,10 @@ class _MyAppState extends State<MyApp> {
const Breakpoint(start: 1921, end: double.infinity, name: '4K'), const Breakpoint(start: 1921, end: double.infinity, name: '4K'),
], ],
), ),
locale: const Locale('fr'), locale: locale,
supportedLocales: const [Locale('fr')], supportedLocales: const [Locale('fr'), Locale('en'), Locale('nl')],
localizationsDelegates: const [ localizationsDelegates: const [
AppLocalizations.delegate,
FlutterQuillLocalizations.delegate, FlutterQuillLocalizations.delegate,
GlobalMaterialLocalizations.delegate, GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate, GlobalWidgetsLocalizations.delegate,

View File

@ -35,6 +35,7 @@ part 'api/authentication_api.dart';
part 'api/configuration_api.dart'; part 'api/configuration_api.dart';
part 'api/device_api.dart'; part 'api/device_api.dart';
part 'api/instance_api.dart'; part 'api/instance_api.dart';
part 'api/subscription_plan_api.dart';
part 'api/resource_api.dart'; part 'api/resource_api.dart';
part 'api/section_api.dart'; part 'api/section_api.dart';
part 'api/section_agenda_api.dart'; part 'api/section_agenda_api.dart';
@ -116,6 +117,8 @@ part 'model/guided_step_guided_path.dart';
part 'model/guided_step_trigger_geo_point.dart'; part 'model/guided_step_trigger_geo_point.dart';
part 'model/instance.dart'; part 'model/instance.dart';
part 'model/instance_dto.dart'; part 'model/instance_dto.dart';
part 'model/instance_quota_dto.dart';
part 'model/subscription_plan_dto.dart';
part 'model/layout_main_page_type.dart'; part 'model/layout_main_page_type.dart';
part 'model/login_dto.dart'; part 'model/login_dto.dart';
part 'model/map_annotation.dart'; part 'model/map_annotation.dart';

View File

@ -407,4 +407,54 @@ class InstanceApi {
} }
return null; return null;
} }
/// Performs an HTTP 'GET /api/Instance/{id}/quota' operation and returns the [Response].
/// Parameters:
///
/// * [String] id (required):
Future<Response> instanceGetQuotaWithHttpInfo(
String id,
) async {
// ignore: prefer_const_declarations
final path = r'/api/Instance/{id}/quota'.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
path,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] id (required):
Future<InstanceQuotaDTO?> instanceGetQuota(
String id,
) async {
final response = await instanceGetQuotaWithHttpInfo(id);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
if (response.body.isNotEmpty &&
response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(
await _decodeBodyBytes(response),
'InstanceQuotaDTO',
) as InstanceQuotaDTO;
}
return null;
}
} }

View File

@ -0,0 +1,219 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SubscriptionPlanApi {
SubscriptionPlanApi([ApiClient? apiClient])
: apiClient = apiClient ?? defaultApiClient;
final ApiClient apiClient;
/// Performs an HTTP 'GET /api/SubscriptionPlan' operation and returns the [Response].
Future<Response> subscriptionPlanGetWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/api/SubscriptionPlan';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
path,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
Future<List<SubscriptionPlanDTO>?> subscriptionPlanGet() async {
final response = await subscriptionPlanGetWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
if (response.body.isNotEmpty &&
response.statusCode != HttpStatus.noContent) {
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, 'List<SubscriptionPlanDTO>') as List)
.cast<SubscriptionPlanDTO>()
.toList(growable: false);
}
return null;
}
/// Performs an HTTP 'GET /api/SubscriptionPlan/{id}' operation and returns the [Response].
Future<Response> subscriptionPlanGetByIdWithHttpInfo(String id) async {
// ignore: prefer_const_declarations
final path = r'/api/SubscriptionPlan/{id}'.replaceAll('{id}', id);
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
path,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
Future<SubscriptionPlanDTO?> subscriptionPlanGetById(String id) async {
final response = await subscriptionPlanGetByIdWithHttpInfo(id);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
if (response.body.isNotEmpty &&
response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(
await _decodeBodyBytes(response),
'SubscriptionPlanDTO',
) as SubscriptionPlanDTO;
}
return null;
}
/// Performs an HTTP 'POST /api/SubscriptionPlan' operation and returns the [Response].
Future<Response> subscriptionPlanCreateWithHttpInfo(
SubscriptionPlanDTO subscriptionPlanDTO,
) async {
// ignore: prefer_const_declarations
final path = r'/api/SubscriptionPlan';
Object? postBody = subscriptionPlanDTO;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
Future<SubscriptionPlanDTO?> subscriptionPlanCreate(
SubscriptionPlanDTO subscriptionPlanDTO,
) async {
final response = await subscriptionPlanCreateWithHttpInfo(subscriptionPlanDTO);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
if (response.body.isNotEmpty &&
response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(
await _decodeBodyBytes(response),
'SubscriptionPlanDTO',
) as SubscriptionPlanDTO;
}
return null;
}
/// Performs an HTTP 'PUT /api/SubscriptionPlan' operation and returns the [Response].
Future<Response> subscriptionPlanUpdateWithHttpInfo(
SubscriptionPlanDTO subscriptionPlanDTO,
) async {
// ignore: prefer_const_declarations
final path = r'/api/SubscriptionPlan';
Object? postBody = subscriptionPlanDTO;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'PUT',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
Future<SubscriptionPlanDTO?> subscriptionPlanUpdate(
SubscriptionPlanDTO subscriptionPlanDTO,
) async {
final response = await subscriptionPlanUpdateWithHttpInfo(subscriptionPlanDTO);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
if (response.body.isNotEmpty &&
response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(
await _decodeBodyBytes(response),
'SubscriptionPlanDTO',
) as SubscriptionPlanDTO;
}
return null;
}
/// Performs an HTTP 'DELETE /api/SubscriptionPlan/{id}' operation and returns the [Response].
Future<Response> subscriptionPlanDeleteWithHttpInfo(String id) async {
// ignore: prefer_const_declarations
final path = r'/api/SubscriptionPlan/{id}'.replaceAll('{id}', id);
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
path,
'DELETE',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
Future<String?> subscriptionPlanDelete(String id) async {
final response = await subscriptionPlanDeleteWithHttpInfo(id);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
if (response.body.isNotEmpty &&
response.statusCode != HttpStatus.noContent) {
return await _decodeBodyBytes(response);
}
return null;
}
}

View File

@ -363,6 +363,8 @@ class ApiClient {
return Instance.fromJson(value); return Instance.fromJson(value);
case 'InstanceDTO': case 'InstanceDTO':
return InstanceDTO.fromJson(value); return InstanceDTO.fromJson(value);
case 'InstanceQuotaDTO':
return InstanceQuotaDTO.fromJson(value);
case 'LayoutMainPageType': case 'LayoutMainPageType':
return LayoutMainPageTypeTypeTransformer().decode(value); return LayoutMainPageTypeTypeTransformer().decode(value);
case 'LoginDTO': case 'LoginDTO':
@ -457,6 +459,8 @@ class ApiClient {
return Section.fromJson(value); return Section.fromJson(value);
case 'SectionDTO': case 'SectionDTO':
return SectionDTO.fromJson(value); return SectionDTO.fromJson(value);
case 'SubscriptionPlanDTO':
return SubscriptionPlanDTO.fromJson(value);
case 'SectionEvent': case 'SectionEvent':
return SectionEvent.fromJson(value); return SectionEvent.fromJson(value);
case 'SectionEventDTO': case 'SectionEventDTO':

View File

@ -24,6 +24,15 @@ class InstanceDTO {
this.isWeb, this.isWeb,
this.isVR, this.isVR,
this.isAssistant, this.isAssistant,
this.subscriptionPlanId,
this.subscriptionPlan,
this.aiRequestsThisMonth,
this.aiUsageMonthKey,
this.storageQuotaBytes,
this.aiRequestsPerMonth,
this.hasStats,
this.statsHistoryDays,
this.hasAdvancedStats,
this.applicationInstanceDTOs = const [], this.applicationInstanceDTOs = const [],
}); });
@ -85,6 +94,24 @@ class InstanceDTO {
bool? isAssistant; bool? isAssistant;
String? subscriptionPlanId;
SubscriptionPlanDTO? subscriptionPlan;
int? aiRequestsThisMonth;
String? aiUsageMonthKey;
int? storageQuotaBytes;
int? aiRequestsPerMonth;
bool? hasStats;
int? statsHistoryDays;
bool? hasAdvancedStats;
List<ApplicationInstanceDTO>? applicationInstanceDTOs; List<ApplicationInstanceDTO>? applicationInstanceDTOs;
@override @override
@ -102,6 +129,15 @@ class InstanceDTO {
other.isWeb == isWeb && other.isWeb == isWeb &&
other.isVR == isVR && other.isVR == isVR &&
other.isAssistant == isAssistant && other.isAssistant == isAssistant &&
other.subscriptionPlanId == subscriptionPlanId &&
other.subscriptionPlan == subscriptionPlan &&
other.aiRequestsThisMonth == aiRequestsThisMonth &&
other.aiUsageMonthKey == aiUsageMonthKey &&
other.storageQuotaBytes == storageQuotaBytes &&
other.aiRequestsPerMonth == aiRequestsPerMonth &&
other.hasStats == hasStats &&
other.statsHistoryDays == statsHistoryDays &&
other.hasAdvancedStats == hasAdvancedStats &&
_deepEquality.equals( _deepEquality.equals(
other.applicationInstanceDTOs, applicationInstanceDTOs); other.applicationInstanceDTOs, applicationInstanceDTOs);
@ -119,11 +155,15 @@ class InstanceDTO {
(isWeb == null ? 0 : isWeb!.hashCode) + (isWeb == null ? 0 : isWeb!.hashCode) +
(isVR == null ? 0 : isVR!.hashCode) + (isVR == null ? 0 : isVR!.hashCode) +
(isAssistant == null ? 0 : isAssistant!.hashCode) + (isAssistant == null ? 0 : isAssistant!.hashCode) +
(subscriptionPlanId == null ? 0 : subscriptionPlanId!.hashCode) +
(subscriptionPlan == null ? 0 : subscriptionPlan!.hashCode) +
(aiRequestsThisMonth == null ? 0 : aiRequestsThisMonth!.hashCode) +
(aiUsageMonthKey == null ? 0 : aiUsageMonthKey!.hashCode) +
(applicationInstanceDTOs == null ? 0 : applicationInstanceDTOs!.hashCode); (applicationInstanceDTOs == null ? 0 : applicationInstanceDTOs!.hashCode);
@override @override
String toString() => String toString() =>
'InstanceDTO[id=$id, name=$name, dateCreation=$dateCreation, pinCode=$pinCode, isPushNotification=$isPushNotification, isStatistic=$isStatistic, isMobile=$isMobile, isTablet=$isTablet, isWeb=$isWeb, isVR=$isVR, isAssistant=$isAssistant, applicationInstanceDTOs=$applicationInstanceDTOs]'; 'InstanceDTO[id=$id, name=$name, dateCreation=$dateCreation, pinCode=$pinCode, isPushNotification=$isPushNotification, isStatistic=$isStatistic, isMobile=$isMobile, isTablet=$isTablet, isWeb=$isWeb, isVR=$isVR, isAssistant=$isAssistant, subscriptionPlanId=$subscriptionPlanId, subscriptionPlan=$subscriptionPlan, aiRequestsThisMonth=$aiRequestsThisMonth, aiUsageMonthKey=$aiUsageMonthKey, applicationInstanceDTOs=$applicationInstanceDTOs]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@ -182,6 +222,51 @@ class InstanceDTO {
} else { } else {
json[r'isAssistant'] = null; json[r'isAssistant'] = null;
} }
if (this.subscriptionPlanId != null) {
json[r'subscriptionPlanId'] = this.subscriptionPlanId;
} else {
json[r'subscriptionPlanId'] = null;
}
if (this.subscriptionPlan != null) {
json[r'subscriptionPlan'] = this.subscriptionPlan;
} else {
json[r'subscriptionPlan'] = null;
}
if (this.aiRequestsThisMonth != null) {
json[r'aiRequestsThisMonth'] = this.aiRequestsThisMonth;
} else {
json[r'aiRequestsThisMonth'] = null;
}
if (this.aiUsageMonthKey != null) {
json[r'aiUsageMonthKey'] = this.aiUsageMonthKey;
} else {
json[r'aiUsageMonthKey'] = null;
}
if (this.storageQuotaBytes != null) {
json[r'storageQuotaBytes'] = this.storageQuotaBytes;
} else {
json[r'storageQuotaBytes'] = null;
}
if (this.aiRequestsPerMonth != null) {
json[r'aiRequestsPerMonth'] = this.aiRequestsPerMonth;
} else {
json[r'aiRequestsPerMonth'] = null;
}
if (this.hasStats != null) {
json[r'hasStats'] = this.hasStats;
} else {
json[r'hasStats'] = null;
}
if (this.statsHistoryDays != null) {
json[r'statsHistoryDays'] = this.statsHistoryDays;
} else {
json[r'statsHistoryDays'] = null;
}
if (this.hasAdvancedStats != null) {
json[r'hasAdvancedStats'] = this.hasAdvancedStats;
} else {
json[r'hasAdvancedStats'] = null;
}
if (this.applicationInstanceDTOs != null) { if (this.applicationInstanceDTOs != null) {
json[r'applicationInstanceDTOs'] = this.applicationInstanceDTOs; json[r'applicationInstanceDTOs'] = this.applicationInstanceDTOs;
} else { } else {
@ -222,6 +307,15 @@ class InstanceDTO {
isWeb: mapValueOfType<bool>(json, r'isWeb'), isWeb: mapValueOfType<bool>(json, r'isWeb'),
isVR: mapValueOfType<bool>(json, r'isVR'), isVR: mapValueOfType<bool>(json, r'isVR'),
isAssistant: mapValueOfType<bool>(json, r'isAssistant'), isAssistant: mapValueOfType<bool>(json, r'isAssistant'),
subscriptionPlanId: mapValueOfType<String>(json, r'subscriptionPlanId'),
subscriptionPlan: SubscriptionPlanDTO.fromJson(json[r'subscriptionPlan']),
aiRequestsThisMonth: mapValueOfType<int>(json, r'aiRequestsThisMonth'),
aiUsageMonthKey: mapValueOfType<String>(json, r'aiUsageMonthKey'),
storageQuotaBytes: mapValueOfType<int>(json, r'storageQuotaBytes'),
aiRequestsPerMonth: mapValueOfType<int>(json, r'aiRequestsPerMonth'),
hasStats: mapValueOfType<bool>(json, r'hasStats'),
statsHistoryDays: mapValueOfType<int>(json, r'statsHistoryDays'),
hasAdvancedStats: mapValueOfType<bool>(json, r'hasAdvancedStats'),
applicationInstanceDTOs: ApplicationInstanceDTO.listFromJson( applicationInstanceDTOs: ApplicationInstanceDTO.listFromJson(
json[r'applicationInstanceDTOs']), json[r'applicationInstanceDTOs']),
); );

View File

@ -0,0 +1,106 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class InstanceQuotaDTO {
/// Returns a new [InstanceQuotaDTO] instance.
InstanceQuotaDTO({
this.storageUsedBytes,
this.storageQuotaBytes,
this.aiRequestsUsed,
this.aiRequestsPerMonth,
});
int? storageUsedBytes;
int? storageQuotaBytes;
int? aiRequestsUsed;
int? aiRequestsPerMonth;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is InstanceQuotaDTO &&
other.storageUsedBytes == storageUsedBytes &&
other.storageQuotaBytes == storageQuotaBytes &&
other.aiRequestsUsed == aiRequestsUsed &&
other.aiRequestsPerMonth == aiRequestsPerMonth;
@override
int get hashCode =>
(storageUsedBytes == null ? 0 : storageUsedBytes!.hashCode) +
(storageQuotaBytes == null ? 0 : storageQuotaBytes!.hashCode) +
(aiRequestsUsed == null ? 0 : aiRequestsUsed!.hashCode) +
(aiRequestsPerMonth == null ? 0 : aiRequestsPerMonth!.hashCode);
@override
String toString() =>
'InstanceQuotaDTO[storageUsedBytes=$storageUsedBytes, storageQuotaBytes=$storageQuotaBytes, aiRequestsUsed=$aiRequestsUsed, aiRequestsPerMonth=$aiRequestsPerMonth]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.storageUsedBytes != null) {
json[r'storageUsedBytes'] = this.storageUsedBytes;
} else {
json[r'storageUsedBytes'] = null;
}
if (this.storageQuotaBytes != null) {
json[r'storageQuotaBytes'] = this.storageQuotaBytes;
} else {
json[r'storageQuotaBytes'] = null;
}
if (this.aiRequestsUsed != null) {
json[r'aiRequestsUsed'] = this.aiRequestsUsed;
} else {
json[r'aiRequestsUsed'] = null;
}
if (this.aiRequestsPerMonth != null) {
json[r'aiRequestsPerMonth'] = this.aiRequestsPerMonth;
} else {
json[r'aiRequestsPerMonth'] = null;
}
return json;
}
// ignore: prefer_constructors_over_static_methods
static InstanceQuotaDTO? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return InstanceQuotaDTO(
storageUsedBytes: mapValueOfType<int>(json, r'storageUsedBytes'),
storageQuotaBytes: mapValueOfType<int>(json, r'storageQuotaBytes'),
aiRequestsUsed: mapValueOfType<int>(json, r'aiRequestsUsed'),
aiRequestsPerMonth: mapValueOfType<int>(json, r'aiRequestsPerMonth'),
);
}
return null;
}
static List<InstanceQuotaDTO> listFromJson(
dynamic json, {
bool growable = false,
}) {
final result = <InstanceQuotaDTO>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = InstanceQuotaDTO.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static const requiredKeys = <String>{};
}

View File

@ -38,7 +38,7 @@ class MapAnnotation {
/// ///
GeometryType? geometryType; GeometryType? geometryType;
MapAnnotationGeometry? geometry; EventAddressDTOGeometry? geometry;
String? polyColor; String? polyColor;
@ -154,7 +154,7 @@ class MapAnnotation {
type: TranslationDTO.listFromJson(json[r'type']), type: TranslationDTO.listFromJson(json[r'type']),
label: TranslationDTO.listFromJson(json[r'label']), label: TranslationDTO.listFromJson(json[r'label']),
geometryType: GeometryType.fromJson(json[r'geometryType']), geometryType: GeometryType.fromJson(json[r'geometryType']),
geometry: MapAnnotationGeometry.fromJson(json[r'geometry']), geometry: EventAddressDTOGeometry.fromJson(json[r'geometry']),
polyColor: mapValueOfType<String>(json, r'polyColor'), polyColor: mapValueOfType<String>(json, r'polyColor'),
icon: mapValueOfType<String>(json, r'icon'), icon: mapValueOfType<String>(json, r'icon'),
iconResourceId: mapValueOfType<String>(json, r'iconResourceId'), iconResourceId: mapValueOfType<String>(json, r'iconResourceId'),

View File

@ -19,6 +19,7 @@ class ResourceDTO {
this.url, this.url,
this.dateCreation, this.dateCreation,
this.instanceId, this.instanceId,
this.sizeBytes,
}); });
String? id; String? id;
@ -45,6 +46,8 @@ class ResourceDTO {
String? instanceId; String? instanceId;
int? sizeBytes;
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
@ -102,6 +105,9 @@ class ResourceDTO {
} else { } else {
json[r'instanceId'] = null; json[r'instanceId'] = null;
} }
if (this.sizeBytes != null) {
json[r'sizeBytes'] = this.sizeBytes;
}
return json; return json;
} }
@ -132,6 +138,7 @@ class ResourceDTO {
url: mapValueOfType<String>(json, r'url'), url: mapValueOfType<String>(json, r'url'),
dateCreation: mapDateTime(json, r'dateCreation', r''), dateCreation: mapDateTime(json, r'dateCreation', r''),
instanceId: mapValueOfType<String>(json, r'instanceId'), instanceId: mapValueOfType<String>(json, r'instanceId'),
sizeBytes: mapValueOfType<int>(json, r'sizeBytes'),
); );
} }
return null; return null;

View File

@ -0,0 +1,155 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SubscriptionPlanDTO {
/// Returns a new [SubscriptionPlanDTO] instance.
SubscriptionPlanDTO({
this.id,
required this.name,
this.storageQuotaBytes,
this.aiRequestsPerMonth,
this.statsHistoryDays,
this.hasAdvancedStats,
});
String? id;
String name;
int? storageQuotaBytes;
int? aiRequestsPerMonth;
int? statsHistoryDays;
bool? hasAdvancedStats;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is SubscriptionPlanDTO &&
other.id == id &&
other.name == name &&
other.storageQuotaBytes == storageQuotaBytes &&
other.aiRequestsPerMonth == aiRequestsPerMonth &&
other.statsHistoryDays == statsHistoryDays &&
other.hasAdvancedStats == hasAdvancedStats;
@override
int get hashCode =>
(id == null ? 0 : id!.hashCode) +
(name.hashCode) +
(storageQuotaBytes == null ? 0 : storageQuotaBytes!.hashCode) +
(aiRequestsPerMonth == null ? 0 : aiRequestsPerMonth!.hashCode) +
(statsHistoryDays == null ? 0 : statsHistoryDays!.hashCode) +
(hasAdvancedStats == null ? 0 : hasAdvancedStats!.hashCode);
@override
String toString() =>
'SubscriptionPlanDTO[id=$id, name=$name, storageQuotaBytes=$storageQuotaBytes, aiRequestsPerMonth=$aiRequestsPerMonth, statsHistoryDays=$statsHistoryDays, hasAdvancedStats=$hasAdvancedStats]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.id != null) {
json[r'id'] = this.id;
} else {
json[r'id'] = null;
}
json[r'name'] = this.name;
if (this.storageQuotaBytes != null) {
json[r'storageQuotaBytes'] = this.storageQuotaBytes;
} else {
json[r'storageQuotaBytes'] = null;
}
if (this.aiRequestsPerMonth != null) {
json[r'aiRequestsPerMonth'] = this.aiRequestsPerMonth;
} else {
json[r'aiRequestsPerMonth'] = null;
}
if (this.statsHistoryDays != null) {
json[r'statsHistoryDays'] = this.statsHistoryDays;
} else {
json[r'statsHistoryDays'] = null;
}
if (this.hasAdvancedStats != null) {
json[r'hasAdvancedStats'] = this.hasAdvancedStats;
} else {
json[r'hasAdvancedStats'] = null;
}
return json;
}
// ignore: prefer_constructors_over_static_methods
static SubscriptionPlanDTO? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return SubscriptionPlanDTO(
id: mapValueOfType<String>(json, r'id'),
name: mapValueOfType<String>(json, r'name') ?? '',
storageQuotaBytes: mapValueOfType<int>(json, r'storageQuotaBytes'),
aiRequestsPerMonth: mapValueOfType<int>(json, r'aiRequestsPerMonth'),
statsHistoryDays: mapValueOfType<int>(json, r'statsHistoryDays'),
hasAdvancedStats: mapValueOfType<bool>(json, r'hasAdvancedStats'),
);
}
return null;
}
static List<SubscriptionPlanDTO> listFromJson(
dynamic json, {
bool growable = false,
}) {
final result = <SubscriptionPlanDTO>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SubscriptionPlanDTO.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, SubscriptionPlanDTO> mapFromJson(dynamic json) {
final map = <String, SubscriptionPlanDTO>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
final value = SubscriptionPlanDTO.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
static Map<String, List<SubscriptionPlanDTO>> mapListFromJson(
dynamic json, {
bool growable = false,
}) {
final map = <String, List<SubscriptionPlanDTO>>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = SubscriptionPlanDTO.listFromJson(
entry.value,
growable: growable,
);
}
}
return map;
}
static const requiredKeys = <String>{'name'};
}

View File

@ -559,7 +559,7 @@ packages:
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
flutter_localizations: flutter_localizations:
dependency: transitive dependency: "direct main"
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"

View File

@ -23,6 +23,8 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_localizations:
sdk: flutter
rxdart: rxdart:
provider: provider:
@ -100,7 +102,7 @@ flutter:
# The following line ensures that the Material Icons font is # The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in # included with your application, so that you can use the icons in
# the material Icons class. # the material Icons class.
#generate: true generate: true
uses-material-design: true uses-material-design: true
# To add assets to your application, add an assets section, like this: # To add assets to your application, add an assets section, like this: