/// Point d'entrée du didacticiel : lance une visite guidée par-dessus l'écran /// courant (route transparente) et mémorise qu'elle a été vue. library; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'tutorial_provider.dart'; import 'tutorial_step.dart'; import 'widgets/tutorial_overlay.dart'; class TutorialCoach { const TutorialCoach._(); static bool _isShowing = false; /// Une visite est-elle déjà affichée ? (évite les doubles déclenchements) static bool get isShowing => _isShowing; /// Joue la visite [tourId] uniquement si elle n'a jamais été vue. /// /// [stepsBuilder] n'est évalué que si la visite doit réellement démarrer : /// les clés des widgets sont ainsi lues au dernier moment. static Future maybeStart( BuildContext context, { required String tourId, required List Function() stepsBuilder, }) async { if (_isShowing) return; if (!context.read().shouldRun(tourId)) return; await start(context, tourId: tourId, steps: stepsBuilder()); } /// Joue la visite [tourId], même si elle a déjà été vue. static Future start( BuildContext context, { required String tourId, required List steps, }) async { if (_isShowing || steps.isEmpty) return; final provider = context.read(); final navigator = Navigator.of(context, rootNavigator: true); _isShowing = true; try { await navigator.push( PageRouteBuilder( opaque: false, barrierDismissible: false, barrierColor: Colors.transparent, transitionDuration: const Duration(milliseconds: 240), reverseTransitionDuration: const Duration(milliseconds: 180), pageBuilder: (routeContext, animation, _) => FadeTransition( opacity: animation, child: TutorialOverlay( steps: steps, onFinished: () => Navigator.of(routeContext).pop(), ), ), ), ); } finally { _isShowing = false; } await provider.complete(tourId); } }