import "package:flutter/material.dart"; import "package:google_fonts/google_fonts.dart"; import "../demo_identity.dart"; import "../services/api_client.dart"; import "../services/auth_service.dart"; import "../theme/ensup_colors.dart"; import "../widgets/app_message.dart"; import "../widgets/ensup_top_bar.dart"; import "../widgets/identification_option.dart"; import "../widgets/brand_corners.dart"; import "home_screen.dart"; import "responsable_screen.dart"; /// Écran de démarrage : identification de l'étudiant (RG01/RG02). class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @override State createState() => _LoginScreenState(); } class _LoginScreenState extends State { bool _connexionEnCours = false; @override void initState() { super.initState(); if (AuthService.mode == AuthenticationMode.azure) { WidgetsBinding.instance.addPostFrameCallback((_) => _restaurerSession()); } } void _ouvrirEtudiant() { Navigator.of(context).push( MaterialPageRoute( builder: (_) => const HomeScreen(), settings: const RouteSettings(name: "home"), ), ); } void _ouvrirResponsable() { Navigator.of(context).push( MaterialPageRoute( builder: (_) => const ResponsableScreen(), settings: const RouteSettings(name: "responsable"), ), ); } void _ouvrirPourRole(String roleCode) { switch (roleCode) { case "ETUDIANT": _ouvrirEtudiant(); case "RESPONSABLE": _ouvrirResponsable(); default: throw StateError("Role utilisateur non autorise."); } } Future _restaurerSession() async { setState(() => _connexionEnCours = true); try { if (!await AuthService.hasMicrosoftAccount()) { return; } final reponse = await ApiClient.get("/auth/me"); final profile = AuthService.setProfileFromApi(reponse); if (mounted) { _ouvrirPourRole(profile.roleCode); } } catch (_) { if (!mounted) return; AppMessage.alerte( context, "La session Microsoft doit être renouvelée.", titre: "Session expirée", ); } finally { if (mounted) { setState(() => _connexionEnCours = false); } } } Future _identifierParQr() async { if (_connexionEnCours) return; if (AuthService.mode == AuthenticationMode.azure) { AppMessage.information( context, "L'identification QR sécurisée n'est pas encore configurée.", titre: "QR code indisponible", ); return; } await _connecterMicrosoft(responsableDemo: false); } Future _connecterMicrosoft({required bool responsableDemo}) async { if (_connexionEnCours) return; setState(() => _connexionEnCours = true); try { if (AuthService.mode == AuthenticationMode.demo) { if (responsableDemo) { AuthService.selectDemoResponsable(); } else { AuthService.selectDemoStudent(); } } else { await AuthService.signInWithMicrosoft(); } final reponse = await ApiClient.get("/auth/me"); final profile = AuthService.setProfileFromApi(reponse); if (!mounted) return; _ouvrirPourRole(profile.roleCode); } catch (error) { if (!mounted) return; AppMessage.erreur(context, error, titre: "Connexion impossible"); } finally { if (mounted) { setState(() => _connexionEnCours = false); } } } @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: DemoIdentity.loginCampusTag, ), 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: _connexionEnCours ? null : _identifierParQr, ), 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: _connexionEnCours ? null : () => _connecterMicrosoft( responsableDemo: false, ), ), 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: _connexionEnCours ? null : () => _connecterMicrosoft( responsableDemo: true, ), 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()), ], ), ); } }