feat(tutorial): guide the first launch with spotlighted steps and gesture hands
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
32143c5bb1
commit
32582aba3d
@@ -0,0 +1,68 @@
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user