import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import '../views/login_page.dart'; import '../utils/colors.dart'; /// Gate de démarrage qui attend la restauration Firebase Auth avant /// d'afficher soit le contenu connecté, soit la page de connexion. class AppStartGate extends StatelessWidget { const AppStartGate({super.key}); @override Widget build(BuildContext context) { // Sur le web, certaines erreurs natives (ex: cookies tiers bloqués) // peuvent faire remonter une FirebaseException sur le stream d'auth. // Pour éviter que StreamBuilder reçoive une erreur qui casse le build // (TypeError JS interop), on "handleError" et on transforme l'erreur // en une valeur nulle (pas d'utilisateur) afin de garder l'app stable. // Accès protégé à `FirebaseAuth.instance` — sur le web certaines erreurs // d'interop JS peuvent produire des TypeError non compatibles. Nous // attrapons toute exception lors de l'accès et fournissons un stream // neutre (pas d'utilisateur) afin de garder l'UI stable. late final Stream safeAuthStream; try { safeAuthStream = FirebaseAuth.instance .authStateChanges() .handleError((error, stack) { // Log pour debug ; ne rethrow pas debugPrint('[AppStartGate] authStateChanges error: $error'); }); } catch (e, st) { // Sur certaines configurations web l'accès à FirebaseAuth.instance // peut échouer au niveau JS interop. On log puis on fournit un stream // qui émet une seule valeur nulle pour indiquer "pas d'utilisateur". debugPrint('[AppStartGate] FirebaseAuth.instance access error: $e\n$st'); safeAuthStream = Stream.value(null); } return StreamBuilder( stream: safeAuthStream, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const _StartupSplashScreen(); } if (snapshot.hasError) { // En théorie handleError évite d'arriver ici, mais on garde // une protection supplémentaire. debugPrint('[AppStartGate] snapshot error: ${snapshot.error}'); return const _StartupSplashScreen(message: 'Erreur de connexion'); } if (snapshot.data != null) { return const _AuthenticatedBootstrap(); } return const LoginPage(); }, ); } } class _AuthenticatedBootstrap extends StatefulWidget { const _AuthenticatedBootstrap(); @override State<_AuthenticatedBootstrap> createState() => _AuthenticatedBootstrapState(); } class _AuthenticatedBootstrapState extends State<_AuthenticatedBootstrap> { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _redirectAfterAuth(); }); } Future _redirectAfterAuth() async { final fragment = Uri.base.fragment; if (!mounted) return; if (fragment.isNotEmpty && fragment != '/' && fragment != '/calendar') { Navigator.of(context).pushReplacementNamed(fragment); } else { Navigator.of(context).pushReplacementNamed('/calendar'); } } @override Widget build(BuildContext context) { return const _StartupSplashScreen(); } } class _StartupSplashScreen extends StatelessWidget { final String message; const _StartupSplashScreen({this.message = 'Démarrage...'}); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( 'assets/logos/RectangleLogoBlack.png', width: 160, height: 160, fit: BoxFit.contain, errorBuilder: (context, error, stackTrace) { return const Icon( Icons.event_available, size: 72, color: AppColors.rouge, ); }, ), const SizedBox(height: 24), const CircularProgressIndicator(), const SizedBox(height: 16), Text(message), ], ), ), ); } }