From 97faf3e9eeaf34fdbcb0f1ca539db492a70940ac Mon Sep 17 00:00:00 2001 From: SaidSoighiri94 Date: Mon, 6 Jul 2026 11:25:40 +0200 Subject: [PATCH] feat(frontend): add student identification screen as entry point --- eme-frontend/lib/main.dart | 4 +- eme-frontend/lib/screens/login_screen.dart | 175 ++++++++++++++++++ eme-frontend/lib/widgets/ensup_top_bar.dart | 42 +++-- .../lib/widgets/identification_option.dart | 76 ++++++++ eme-frontend/test/widget_test.dart | 6 +- review.md | 7 +- 6 files changed, 287 insertions(+), 23 deletions(-) create mode 100644 eme-frontend/lib/screens/login_screen.dart create mode 100644 eme-frontend/lib/widgets/identification_option.dart diff --git a/eme-frontend/lib/main.dart b/eme-frontend/lib/main.dart index 898997e..2e32f41 100644 --- a/eme-frontend/lib/main.dart +++ b/eme-frontend/lib/main.dart @@ -1,6 +1,6 @@ import "package:flutter/material.dart"; import "theme/ensup_theme.dart"; -import "screens/home_screen.dart"; +import "screens/login_screen.dart"; void main() { runApp(const EmeApp()); @@ -15,7 +15,7 @@ class EmeApp extends StatelessWidget { title: "EME — Emprunt Matériel ENSUP", debugShowCheckedModeBanner: false, theme: buildEnsupTheme(), - home: const HomeScreen(), + home: const LoginScreen(), ); } } diff --git a/eme-frontend/lib/screens/login_screen.dart b/eme-frontend/lib/screens/login_screen.dart new file mode 100644 index 0000000..328ae84 --- /dev/null +++ b/eme-frontend/lib/screens/login_screen.dart @@ -0,0 +1,175 @@ +import "package:flutter/material.dart"; +import "package:google_fonts/google_fonts.dart"; +import "../theme/ensup_colors.dart"; +import "../widgets/ensup_top_bar.dart"; +import "../widgets/identification_option.dart"; +import "../widgets/brand_corners.dart"; +import "home_screen.dart"; + +/// Écran de démarrage : identification de l'étudiant (RG01/RG02). +class LoginScreen extends StatelessWidget { + const LoginScreen({super.key}); + + void _identifier(BuildContext context) { + Navigator.of(context).push( + MaterialPageRoute(builder: (_) => const HomeScreen()), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: EnsupColors.soft, + body: Stack( + children: [ + Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 1280, maxHeight: 820), + child: Container( + margin: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: EnsupColors.text.withValues(alpha: 0.14), + blurRadius: 60, + offset: const Offset(0, 18), + ), + ], + ), + clipBehavior: Clip.antiAlias, + child: Column( + children: [ + const EnsupTopBar( + brandText: "EME — Emprunt Matériel ENSUP", + campusTag: "Ensitech · Paris", + ), + Expanded( + child: DecoratedBox( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFFF0F6FF), Color(0xFFF8FAFC)], + ), + ), + child: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 40), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 560), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 72, + height: 72, + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [EnsupColors.blue3, EnsupColors.cyan], + ), + borderRadius: BorderRadius.circular(22), + boxShadow: [ + BoxShadow( + color: EnsupColors.blue3.withValues(alpha: 0.35), + blurRadius: 24, + offset: const Offset(0, 8), + ), + ], + ), + child: const Icon(Icons.badge_outlined, color: Colors.white, size: 34), + ), + const SizedBox(height: 20), + Text( + "Bienvenue sur EME", + style: GoogleFonts.darkerGrotesque( + fontSize: 34, + fontWeight: FontWeight.w900, + color: EnsupColors.text, + ), + ), + const SizedBox(height: 8), + Text( + "Emprunt Matériel ENSUP · Accès réservé aux membres du groupe ENSUP", + textAlign: TextAlign.center, + style: GoogleFonts.titilliumWeb( + fontSize: 14, + color: EnsupColors.muted, + ), + ), + const SizedBox(height: 28), + IdentificationOption( + icon: Icons.qr_code_2, + iconColor: EnsupColors.cyan, + label: "Scanner ma carte étudiante", + description: + "Présentez votre carte étudiante dématérialisée à la douchette ou à la tablette", + onTap: () => _identifier(context), + ), + const SizedBox(height: 12), + IdentificationOption( + icon: Icons.mail_outline, + iconColor: EnsupColors.teal, + label: "Connexion compte professionnel ENSUP", + description: "Utiliser mon adresse e-mail Microsoft 365", + onTap: () => _identifier(context), + ), + const SizedBox(height: 20), + Row( + children: [ + const Expanded(child: Divider(color: EnsupColors.line)), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + "ou", + style: GoogleFonts.titilliumWeb( + fontSize: 12, + color: EnsupColors.muted, + ), + ), + ), + const Expanded(child: Divider(color: EnsupColors.line)), + ], + ), + const SizedBox(height: 20), + SizedBox( + width: double.infinity, + child: OutlinedButton.icon( + onPressed: () {}, + icon: const Icon(Icons.person_outline, size: 16), + label: Text( + "Accès responsable matériel", + style: GoogleFonts.darkerGrotesque( + fontWeight: FontWeight.w700, + fontSize: 15, + ), + ), + style: OutlinedButton.styleFrom( + foregroundColor: EnsupColors.text2, + side: const BorderSide(color: EnsupColors.line, width: 1.5), + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + const Positioned.fill(child: BrandCorners()), + ], + ), + ); + } +} diff --git a/eme-frontend/lib/widgets/ensup_top_bar.dart b/eme-frontend/lib/widgets/ensup_top_bar.dart index 26e2483..9b6b74c 100644 --- a/eme-frontend/lib/widgets/ensup_top_bar.dart +++ b/eme-frontend/lib/widgets/ensup_top_bar.dart @@ -2,21 +2,27 @@ import "package:flutter/material.dart"; import "package:google_fonts/google_fonts.dart"; import "../theme/ensup_colors.dart"; -/// Barre supérieure ENSUP : marque EME, campus et utilisateur connecté. +/// Barre supérieure ENSUP. L'utilisateur est optionnel (absent sur l'écran +/// d'identification, présent une fois l'étudiant connecté). class EnsupTopBar extends StatelessWidget { const EnsupTopBar({ super.key, + this.brandText = "EME", required this.campusTag, - required this.userName, - required this.userInitials, + this.userName, + this.userInitials, }); + final String brandText; final String campusTag; - final String userName; - final String userInitials; + final String? userName; + final String? userInitials; @override Widget build(BuildContext context) { + final name = userName; + final initials = userInitials; + return Container( height: 56, padding: const EdgeInsets.symmetric(horizontal: 28), @@ -26,10 +32,10 @@ class EnsupTopBar extends StatelessWidget { children: [ Row( children: [ - const Icon(Icons.devices_other, color: Colors.white, size: 18), + const Icon(Icons.badge_outlined, color: Colors.white, size: 18), const SizedBox(width: 10), Text( - "EME", + brandText, style: GoogleFonts.darkerGrotesque( color: Colors.white, fontWeight: FontWeight.w900, @@ -57,17 +63,19 @@ class EnsupTopBar extends StatelessWidget { ), ), ), - const SizedBox(width: 12), - _Avatar(initials: userInitials), - const SizedBox(width: 8), - Text( - userName, - style: GoogleFonts.titilliumWeb( - color: Colors.white, - fontSize: 13, - fontWeight: FontWeight.w600, + if (name != null && initials != null) ...[ + const SizedBox(width: 12), + _Avatar(initials: initials), + const SizedBox(width: 8), + Text( + name, + style: GoogleFonts.titilliumWeb( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.w600, + ), ), - ), + ], ], ), ], diff --git a/eme-frontend/lib/widgets/identification_option.dart b/eme-frontend/lib/widgets/identification_option.dart new file mode 100644 index 0000000..2b273d9 --- /dev/null +++ b/eme-frontend/lib/widgets/identification_option.dart @@ -0,0 +1,76 @@ +import "package:flutter/material.dart"; +import "package:google_fonts/google_fonts.dart"; +import "../theme/ensup_colors.dart"; + +/// Option d'identification de l'écran de connexion (QR ou mail ENSUP). +class IdentificationOption extends StatelessWidget { + const IdentificationOption({ + super.key, + required this.icon, + required this.iconColor, + required this.label, + required this.description, + this.onTap, + }); + + final IconData icon; + final Color iconColor; + final String label; + final String description; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(12), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: EnsupColors.line, width: 2), + ), + child: Row( + children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: iconColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(icon, color: iconColor, size: 24), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: GoogleFonts.darkerGrotesque( + fontWeight: FontWeight.w700, + fontSize: 18, + color: EnsupColors.text, + ), + ), + const SizedBox(height: 3), + Text( + description, + style: GoogleFonts.titilliumWeb( + fontSize: 12, + color: EnsupColors.muted, + height: 1.4, + ), + ), + ], + ), + ), + const Icon(Icons.chevron_right, color: EnsupColors.muted, size: 20), + ], + ), + ), + ); + } +} diff --git a/eme-frontend/test/widget_test.dart b/eme-frontend/test/widget_test.dart index 94b47da..7593bd9 100644 --- a/eme-frontend/test/widget_test.dart +++ b/eme-frontend/test/widget_test.dart @@ -2,10 +2,10 @@ import "package:flutter_test/flutter_test.dart"; import "package:eme_frontend/main.dart"; void main() { - testWidgets("L'accueil affiche les actions Emprunter et Restituer", (tester) async { + testWidgets("L'écran de démarrage affiche l'identification", (tester) async { await tester.pumpWidget(const EmeApp()); - expect(find.text("EMPRUNTER"), findsOneWidget); - expect(find.text("RESTITUER"), findsOneWidget); + expect(find.text("Bienvenue sur EME"), findsOneWidget); + expect(find.text("Scanner ma carte étudiante"), findsOneWidget); }); } diff --git a/review.md b/review.md index 7e1f436..3097b24 100644 --- a/review.md +++ b/review.md @@ -177,6 +177,11 @@ En terminal interactif (VS Code), `migrate dev` fonctionne normalement (taper `y - Références mises à jour dans `CLAUDE.md` et `CONTEXT.md` pour pointer vers la maquette. - Le PDF reste récupérable dans l'historique Git si besoin (logo officiel source). +### Étape 23 — Frontend : écran d'identification (écran de démarrage) +- Correction : l'écran de démarrage est l'identification (`s-login` de la maquette), pas l'accueil étudiant. Logo, "Bienvenue sur EME", deux options (scanner carte / compte ENSUP Microsoft 365), bouton accès responsable. +- `EnsupTopBar` rendue réutilisable (utilisateur optionnel, marque paramétrable) ; nouveau widget `IdentificationOption` ; coins ENSUP aussi sur cet écran. +- Navigation identification -> accueil étudiant (`HomeScreen`). + --- ## Dette technique en attente @@ -192,4 +197,4 @@ En terminal interactif (VS Code), `migrate dev` fonctionne normalement (taper `y --- -*Dernière mise à jour : 2026-07-06 — Frontend Flutter (accueil ENSUP) ; PDF de charte retiré au profit de la maquette.* +*Dernière mise à jour : 2026-07-06 — Frontend : écran d'identification (démarrage) + accueil étudiant.*