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:
qlionbleusam
2026-08-29 11:46:02 +02:00
co-authored by Claude Opus 5
parent 32143c5bb1
commit 32582aba3d
14 changed files with 1566 additions and 1 deletions
+46
View File
@@ -0,0 +1,46 @@
/// Persistance du didacticiel : mémorise les visites guidées déjà vues.
///
/// Chaque visite guidée (« tour ») possède un identifiant stocké dans les
/// SharedPreferences sous la forme `tutorial_done_<id>`. Réinitialiser le
/// didacticiel depuis les paramètres efface simplement ces clés.
library;
import 'package:shared_preferences/shared_preferences.dart';
/// Identifiants des visites guidées de l'application.
class TutorialTours {
const TutorialTours._();
/// Visite d'accueil : présentation générale et navigation.
static const String home = 'home';
/// Visite de l'éditeur d'impacts : tap, appui long et pincement pour zoomer.
static const String impactEditor = 'impact_editor';
/// Toutes les visites connues, dans l'ordre logique de découverte.
static const List<String> all = [home, impactEditor];
}
class TutorialService {
static const String _prefix = 'tutorial_done_';
Future<Set<String>> loadCompletedTours() async {
final prefs = await SharedPreferences.getInstance();
return TutorialTours.all
.where((id) => prefs.getBool('$_prefix$id') ?? false)
.toSet();
}
Future<void> markCompleted(String tourId) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('$_prefix$tourId', true);
}
/// Efface la mémoire du didacticiel : toutes les visites seront rejouées.
Future<void> resetAll() async {
final prefs = await SharedPreferences.getInstance();
for (final id in TutorialTours.all) {
await prefs.remove('$_prefix$id');
}
}
}