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
@@ -25,6 +25,9 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../data/models/shot.dart';
|
||||
import '../../services/tutorial_service.dart';
|
||||
import '../tutorial/tutorial_coach.dart';
|
||||
import '../tutorial/tutorial_step.dart';
|
||||
import 'analysis_provider.dart';
|
||||
import 'widgets/target_overlay.dart';
|
||||
|
||||
@@ -43,12 +46,71 @@ class _ImpactEditorScreenState extends State<ImpactEditorScreen> {
|
||||
double _currentZoomScale = 1.0;
|
||||
String? _movingShotId;
|
||||
|
||||
// Clés du didacticiel : zone de travail et bouton de validation.
|
||||
final GlobalKey _tutoCanvasKey = GlobalKey();
|
||||
final GlobalKey _tutoValidateKey = GlobalKey();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_transformationController.addListener(_onTransformChanged);
|
||||
// Première ouverture de l'éditeur : on montre les gestes disponibles.
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
TutorialCoach.maybeStart(
|
||||
context,
|
||||
tourId: TutorialTours.impactEditor,
|
||||
stepsBuilder: _buildEditorTutorialSteps,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
List<TutorialStep> _buildEditorTutorialSteps() => [
|
||||
TutorialStep(
|
||||
targetKey: _tutoCanvasKey,
|
||||
title: 'Ajouter un impact',
|
||||
description:
|
||||
'Touchez la cible à l\'endroit de l\'impact : il est ajouté '
|
||||
'immédiatement, même collé à un impact déjà placé. Le score est '
|
||||
'calculé automatiquement selon la zone touchée.',
|
||||
gesture: TutorialGesture.tap,
|
||||
icon: Icons.add_location_alt_outlined,
|
||||
spotPadding: 0,
|
||||
),
|
||||
TutorialStep(
|
||||
targetKey: _tutoCanvasKey,
|
||||
title: 'Déplacer un impact',
|
||||
description:
|
||||
'Appui long sur un impact, puis glissez le doigt pour l\'ajuster '
|
||||
'au millimètre. L\'impact reste visible au-dessus du doigt.',
|
||||
gesture: TutorialGesture.drag,
|
||||
icon: Icons.open_with,
|
||||
spotPadding: 0,
|
||||
),
|
||||
TutorialStep(
|
||||
targetKey: _tutoCanvasKey,
|
||||
title: 'Zoomer sur la cible',
|
||||
description:
|
||||
'Écartez deux doigts pour zoomer (jusqu\'à 12×) et placer vos '
|
||||
'impacts avec précision ; rapprochez-les pour dézoomer. À un '
|
||||
'doigt, vous faites glisser l\'image.',
|
||||
gesture: TutorialGesture.pinch,
|
||||
icon: Icons.zoom_in,
|
||||
spotPadding: 0,
|
||||
),
|
||||
TutorialStep(
|
||||
targetKey: _tutoValidateKey,
|
||||
title: 'Valider vos impacts',
|
||||
description:
|
||||
'VALIDER renvoie vers la synthèse avec les scores et le '
|
||||
'groupement. La corbeille, à gauche, efface tous les impacts '
|
||||
'sans toucher à la calibration.',
|
||||
gesture: TutorialGesture.tap,
|
||||
icon: Icons.check_circle_outline,
|
||||
spotPadding: 8,
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_transformationController.removeListener(_onTransformChanged);
|
||||
@@ -123,6 +185,7 @@ class _ImpactEditorScreenState extends State<ImpactEditorScreen> {
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
FloatingActionButton.extended(
|
||||
key: _tutoValidateKey,
|
||||
heroTag: 'validate_impacts',
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
@@ -147,6 +210,7 @@ class _ImpactEditorScreenState extends State<ImpactEditorScreen> {
|
||||
|
||||
// Zone image plein écran : InteractiveViewer dans un body nu.
|
||||
Expanded(
|
||||
key: _tutoCanvasKey,
|
||||
child: InteractiveViewer(
|
||||
transformationController: _transformationController,
|
||||
minScale: 1.0,
|
||||
|
||||
Reference in New Issue
Block a user