Ajoute un didacticiel joué à la première utilisation : un voile sombre perce un trou de lumière autour de l'élément à découvrir, une main animée mime le geste attendu (tap, appui long, glisser, pincement à deux doigts pour zoomer) et une bulle explique l'étape avec sa progression. - visite d'accueil : nouvelle session, télémétrie, barre de navigation, réglages - visite de l'éditeur d'impacts : ajouter, déplacer, zoomer au pincement, valider - chaque visite n'est jouée qu'une fois (SharedPreferences) - Paramètres > Aide & didacticiel > Revoir le didacticiel : réinitialise tout et relance la visite au retour sur l'écran concerné Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
69 lines
2.1 KiB
Dart
69 lines
2.1 KiB
Dart
/// 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<void> maybeStart(
|
|
BuildContext context, {
|
|
required String tourId,
|
|
required List<TutorialStep> Function() stepsBuilder,
|
|
}) async {
|
|
if (_isShowing) return;
|
|
if (!context.read<TutorialProvider>().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<void> start(
|
|
BuildContext context, {
|
|
required String tourId,
|
|
required List<TutorialStep> steps,
|
|
}) async {
|
|
if (_isShowing || steps.isEmpty) return;
|
|
|
|
final provider = context.read<TutorialProvider>();
|
|
final navigator = Navigator.of(context, rootNavigator: true);
|
|
_isShowing = true;
|
|
|
|
try {
|
|
await navigator.push(
|
|
PageRouteBuilder<void>(
|
|
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);
|
|
}
|
|
}
|