feat(frontend): improve application feedback messages
This commit is contained in:
@@ -6,6 +6,7 @@ import "../models/materiel_item.dart";
|
|||||||
import "../services/materiel_service.dart";
|
import "../services/materiel_service.dart";
|
||||||
import "../widgets/ensup_top_bar.dart";
|
import "../widgets/ensup_top_bar.dart";
|
||||||
import "../widgets/brand_corners.dart";
|
import "../widgets/brand_corners.dart";
|
||||||
|
import "../widgets/app_message.dart";
|
||||||
import "../widgets/materiel_card.dart";
|
import "../widgets/materiel_card.dart";
|
||||||
import "detail_screen.dart";
|
import "detail_screen.dart";
|
||||||
|
|
||||||
@@ -65,9 +66,7 @@ class _CatalogueScreenState extends State<CatalogueScreen> {
|
|||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ScaffoldMessenger.of(
|
AppMessage.erreur(context, error);
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text(error.toString())));
|
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _selectionEnCoursId = null);
|
setState(() => _selectionEnCoursId = null);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import "../services/emprunt_service.dart";
|
|||||||
import "../widgets/ensup_top_bar.dart";
|
import "../widgets/ensup_top_bar.dart";
|
||||||
import "../widgets/brand_corners.dart";
|
import "../widgets/brand_corners.dart";
|
||||||
import "../widgets/etat_checklist.dart";
|
import "../widgets/etat_checklist.dart";
|
||||||
|
import "../widgets/app_message.dart";
|
||||||
import "confirmation_screen.dart";
|
import "confirmation_screen.dart";
|
||||||
|
|
||||||
class ChecklistDepartScreen extends StatefulWidget {
|
class ChecklistDepartScreen extends StatefulWidget {
|
||||||
@@ -77,9 +78,7 @@ class _ChecklistDepartScreenState extends State<ChecklistDepartScreen> {
|
|||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ScaffoldMessenger.of(
|
AppMessage.erreur(context, error);
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text(error.toString())));
|
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _envoiEnCours = false);
|
setState(() => _envoiEnCours = false);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import "../services/emprunt_service.dart";
|
|||||||
import "../widgets/ensup_top_bar.dart";
|
import "../widgets/ensup_top_bar.dart";
|
||||||
import "../widgets/brand_corners.dart";
|
import "../widgets/brand_corners.dart";
|
||||||
import "../widgets/etat_checklist.dart";
|
import "../widgets/etat_checklist.dart";
|
||||||
|
import "../widgets/app_message.dart";
|
||||||
import "resultat_retour_screen.dart";
|
import "resultat_retour_screen.dart";
|
||||||
|
|
||||||
class ChecklistRetourScreen extends StatefulWidget {
|
class ChecklistRetourScreen extends StatefulWidget {
|
||||||
@@ -114,9 +115,7 @@ class _ChecklistRetourScreenState extends State<ChecklistRetourScreen> {
|
|||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ScaffoldMessenger.of(
|
AppMessage.erreur(context, error);
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text(error.toString())));
|
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _envoiEnCours = false);
|
setState(() => _envoiEnCours = false);
|
||||||
@@ -142,19 +141,15 @@ class _ChecklistRetourScreenState extends State<ChecklistRetourScreen> {
|
|||||||
etatDemande: _etatApi(etat),
|
etatDemande: _etatApi(etat),
|
||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
AppMessage.alerte(
|
||||||
const SnackBar(
|
context,
|
||||||
content: Text(
|
"Le responsable a été alerté de cette tentative de modification.",
|
||||||
"Le responsable a été alerté de cette tentative de modification.",
|
titre: "Changement signalé",
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() => _alerteEnvoyee = false);
|
setState(() => _alerteEnvoyee = false);
|
||||||
ScaffoldMessenger.of(
|
AppMessage.erreur(context, error);
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text(error.toString())));
|
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _alerteEnCours = false);
|
setState(() => _alerteEnCours = false);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import "package:google_fonts/google_fonts.dart";
|
|||||||
import "../demo_identity.dart";
|
import "../demo_identity.dart";
|
||||||
import "../services/auth_service.dart";
|
import "../services/auth_service.dart";
|
||||||
import "../theme/ensup_colors.dart";
|
import "../theme/ensup_colors.dart";
|
||||||
|
import "../widgets/app_message.dart";
|
||||||
import "../widgets/ensup_top_bar.dart";
|
import "../widgets/ensup_top_bar.dart";
|
||||||
import "../widgets/profile_card.dart";
|
import "../widgets/profile_card.dart";
|
||||||
import "../widgets/action_card.dart";
|
import "../widgets/action_card.dart";
|
||||||
@@ -22,9 +23,7 @@ class HomeScreen extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(
|
AppMessage.erreur(context, error);
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text(error.toString())));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import "../demo_identity.dart";
|
|||||||
import "../services/api_client.dart";
|
import "../services/api_client.dart";
|
||||||
import "../services/auth_service.dart";
|
import "../services/auth_service.dart";
|
||||||
import "../theme/ensup_colors.dart";
|
import "../theme/ensup_colors.dart";
|
||||||
|
import "../widgets/app_message.dart";
|
||||||
import "../widgets/ensup_top_bar.dart";
|
import "../widgets/ensup_top_bar.dart";
|
||||||
import "../widgets/identification_option.dart";
|
import "../widgets/identification_option.dart";
|
||||||
import "../widgets/brand_corners.dart";
|
import "../widgets/brand_corners.dart";
|
||||||
@@ -72,10 +73,10 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
AppMessage.alerte(
|
||||||
const SnackBar(
|
context,
|
||||||
content: Text("La session Microsoft doit etre renouvelee."),
|
"La session Microsoft doit être renouvelée.",
|
||||||
),
|
titre: "Session expirée",
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@@ -87,12 +88,10 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
Future<void> _identifierParQr() async {
|
Future<void> _identifierParQr() async {
|
||||||
if (_connexionEnCours) return;
|
if (_connexionEnCours) return;
|
||||||
if (AuthService.mode == AuthenticationMode.azure) {
|
if (AuthService.mode == AuthenticationMode.azure) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
AppMessage.information(
|
||||||
const SnackBar(
|
context,
|
||||||
content: Text(
|
"L'identification QR sécurisée n'est pas encore configurée.",
|
||||||
"L'identification QR securisee n'est pas encore configuree.",
|
titre: "QR code indisponible",
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -121,9 +120,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
_ouvrirPourRole(profile.roleCode);
|
_ouvrirPourRole(profile.roleCode);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
AppMessage.erreur(context, error, titre: "Connexion impossible");
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text(error.toString())));
|
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _connexionEnCours = false);
|
setState(() => _connexionEnCours = false);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import "../services/auth_service.dart";
|
|||||||
import "../services/responsable_service.dart";
|
import "../services/responsable_service.dart";
|
||||||
import "../theme/ensup_colors.dart";
|
import "../theme/ensup_colors.dart";
|
||||||
import "../utils/file_download.dart";
|
import "../utils/file_download.dart";
|
||||||
|
import "../widgets/app_message.dart";
|
||||||
|
|
||||||
const _workspaceBackground = Color(0xFFF3F6F9);
|
const _workspaceBackground = Color(0xFFF3F6F9);
|
||||||
const _orange = Color(0xFFD97706);
|
const _orange = Color(0xFFD97706);
|
||||||
@@ -55,9 +56,7 @@ class _ResponsableScreenState extends State<ResponsableScreen> {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
AppMessage.erreur(context, error);
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text(error.toString())));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,12 +72,16 @@ class _ResponsableScreenState extends State<ResponsableScreen> {
|
|||||||
anomalie["id"] as int,
|
anomalie["id"] as int,
|
||||||
prochain,
|
prochain,
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
AppMessage.succes(
|
||||||
|
context,
|
||||||
|
"L'anomalie est maintenant au statut ${_statusLabel(prochain)}.",
|
||||||
|
titre: "Statut mis à jour",
|
||||||
|
);
|
||||||
_rafraichir();
|
_rafraichir();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
AppMessage.erreur(context, error);
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text(error.toString())));
|
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _transitionEnCours = false);
|
setState(() => _transitionEnCours = false);
|
||||||
@@ -106,21 +109,17 @@ class _ResponsableScreenState extends State<ResponsableScreen> {
|
|||||||
statutMateriel: decision.statutMateriel,
|
statutMateriel: decision.statutMateriel,
|
||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
AppMessage.succes(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
decision.decision == "CONFIRMER"
|
||||||
decision.decision == "CONFIRMER"
|
? "Le changement a été confirmé et l'anomalie a été créée."
|
||||||
? "Le changement a été confirmé et l'anomalie a été créée."
|
: "Le changement a été refusé.",
|
||||||
: "Le changement a été refusé.",
|
titre: "Décision enregistrée",
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
_rafraichir();
|
_rafraichir();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
AppMessage.erreur(context, error);
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text(error.toString())));
|
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _decisionEnCours = false);
|
setState(() => _decisionEnCours = false);
|
||||||
@@ -134,9 +133,7 @@ class _ResponsableScreenState extends State<ResponsableScreen> {
|
|||||||
_rafraichir();
|
_rafraichir();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
AppMessage.erreur(context, error);
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text(error.toString())));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,14 +149,14 @@ class _ResponsableScreenState extends State<ResponsableScreen> {
|
|||||||
contentType: file.contentType,
|
contentType: file.contentType,
|
||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
AppMessage.succes(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(const SnackBar(content: Text("Export CSV téléchargé.")));
|
"Le fichier d'historique est disponible dans vos téléchargements.",
|
||||||
|
titre: "Export CSV téléchargé",
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
AppMessage.erreur(context, error, titre: "Export impossible");
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text(error.toString())));
|
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _exportEnCours = false);
|
setState(() => _exportEnCours = false);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import "../services/emprunt_service.dart";
|
|||||||
import "../services/materiel_service.dart";
|
import "../services/materiel_service.dart";
|
||||||
import "../widgets/ensup_top_bar.dart";
|
import "../widgets/ensup_top_bar.dart";
|
||||||
import "../widgets/brand_corners.dart";
|
import "../widgets/brand_corners.dart";
|
||||||
|
import "../widgets/app_message.dart";
|
||||||
import "checklist_retour_screen.dart";
|
import "checklist_retour_screen.dart";
|
||||||
|
|
||||||
/// Sélection de l'emprunt en cours à restituer (RG15 : ses propres emprunts).
|
/// Sélection de l'emprunt en cours à restituer (RG15 : ses propres emprunts).
|
||||||
@@ -50,9 +51,7 @@ class _RestitutionSelectScreenState extends State<RestitutionSelectScreen> {
|
|||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ScaffoldMessenger.of(
|
AppMessage.erreur(context, error);
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text(error.toString())));
|
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _chargementId = null);
|
setState(() => _chargementId = null);
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:google_fonts/google_fonts.dart";
|
||||||
|
|
||||||
|
import "../theme/ensup_colors.dart";
|
||||||
|
|
||||||
|
enum AppMessageType { information, succes, alerte, erreur }
|
||||||
|
|
||||||
|
class AppMessage {
|
||||||
|
AppMessage._();
|
||||||
|
|
||||||
|
static int _messageActif = 0;
|
||||||
|
|
||||||
|
static void information(
|
||||||
|
BuildContext context,
|
||||||
|
String message, {
|
||||||
|
String titre = "Information",
|
||||||
|
}) {
|
||||||
|
_afficher(
|
||||||
|
context,
|
||||||
|
type: AppMessageType.information,
|
||||||
|
titre: titre,
|
||||||
|
message: message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void succes(
|
||||||
|
BuildContext context,
|
||||||
|
String message, {
|
||||||
|
String titre = "Action réussie",
|
||||||
|
}) {
|
||||||
|
_afficher(
|
||||||
|
context,
|
||||||
|
type: AppMessageType.succes,
|
||||||
|
titre: titre,
|
||||||
|
message: message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void alerte(
|
||||||
|
BuildContext context,
|
||||||
|
String message, {
|
||||||
|
String titre = "Attention",
|
||||||
|
}) {
|
||||||
|
_afficher(
|
||||||
|
context,
|
||||||
|
type: AppMessageType.alerte,
|
||||||
|
titre: titre,
|
||||||
|
message: message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void erreur(
|
||||||
|
BuildContext context,
|
||||||
|
Object erreur, {
|
||||||
|
String titre = "Une erreur est survenue",
|
||||||
|
}) {
|
||||||
|
_afficher(
|
||||||
|
context,
|
||||||
|
type: AppMessageType.erreur,
|
||||||
|
titre: titre,
|
||||||
|
message: _nettoyerErreur(erreur),
|
||||||
|
duree: const Duration(seconds: 8),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _afficher(
|
||||||
|
BuildContext context, {
|
||||||
|
required AppMessageType type,
|
||||||
|
required String titre,
|
||||||
|
required String message,
|
||||||
|
Duration duree = const Duration(seconds: 6),
|
||||||
|
}) {
|
||||||
|
final messenger = ScaffoldMessenger.of(context);
|
||||||
|
final identifiant = ++_messageActif;
|
||||||
|
final style = _style(type);
|
||||||
|
|
||||||
|
messenger
|
||||||
|
..clearSnackBars()
|
||||||
|
..hideCurrentMaterialBanner()
|
||||||
|
..showMaterialBanner(
|
||||||
|
MaterialBanner(
|
||||||
|
backgroundColor: style.fond,
|
||||||
|
surfaceTintColor: Colors.transparent,
|
||||||
|
elevation: 6,
|
||||||
|
shadowColor: EnsupColors.text.withValues(alpha: 0.18),
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 14, 8, 14),
|
||||||
|
leadingPadding: const EdgeInsets.only(right: 14),
|
||||||
|
leading: Container(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: style.couleur,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Icon(style.icone, color: Colors.white, size: 22),
|
||||||
|
),
|
||||||
|
content: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
titre,
|
||||||
|
style: GoogleFonts.darkerGrotesque(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
color: style.texte,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
message,
|
||||||
|
style: GoogleFonts.titilliumWeb(
|
||||||
|
fontSize: 13,
|
||||||
|
color: EnsupColors.text2,
|
||||||
|
height: 1.35,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
tooltip: "Fermer le message",
|
||||||
|
onPressed: () {
|
||||||
|
_messageActif++;
|
||||||
|
messenger.hideCurrentMaterialBanner();
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.close, color: style.texte),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void>.delayed(duree, () {
|
||||||
|
if (_messageActif == identifiant && messenger.mounted) {
|
||||||
|
messenger.hideCurrentMaterialBanner();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _nettoyerErreur(Object erreur) {
|
||||||
|
return erreur
|
||||||
|
.toString()
|
||||||
|
.replaceFirst(RegExp(r"^(Exception|Error):\s*"), "")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
static _AppMessageStyle _style(AppMessageType type) {
|
||||||
|
return switch (type) {
|
||||||
|
AppMessageType.information => const _AppMessageStyle(
|
||||||
|
fond: Color(0xFFEFF8FC),
|
||||||
|
couleur: EnsupColors.cyan,
|
||||||
|
texte: EnsupColors.blue3,
|
||||||
|
icone: Icons.info_outline,
|
||||||
|
),
|
||||||
|
AppMessageType.succes => const _AppMessageStyle(
|
||||||
|
fond: Color(0xFFF0FDF4),
|
||||||
|
couleur: EnsupColors.green,
|
||||||
|
texte: Color(0xFF166534),
|
||||||
|
icone: Icons.check_circle_outline,
|
||||||
|
),
|
||||||
|
AppMessageType.alerte => const _AppMessageStyle(
|
||||||
|
fond: Color(0xFFFFF7ED),
|
||||||
|
couleur: Color(0xFFD97706),
|
||||||
|
texte: Color(0xFF9A3412),
|
||||||
|
icone: Icons.warning_amber_rounded,
|
||||||
|
),
|
||||||
|
AppMessageType.erreur => const _AppMessageStyle(
|
||||||
|
fond: Color(0xFFFFF1F2),
|
||||||
|
couleur: EnsupColors.red,
|
||||||
|
texte: Color(0xFF9F1239),
|
||||||
|
icone: Icons.error_outline,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppMessageStyle {
|
||||||
|
const _AppMessageStyle({
|
||||||
|
required this.fond,
|
||||||
|
required this.couleur,
|
||||||
|
required this.texte,
|
||||||
|
required this.icone,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Color fond;
|
||||||
|
final Color couleur;
|
||||||
|
final Color texte;
|
||||||
|
final IconData icone;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user