bonpoint/mobile/lib/features/onboarding/onboarding_flow.dart
Julien Martin aac3a729a2 Initialise l'app Flutter mobile : BDD Drift, onboarding sans compte et tableau de bord.
Phase 1 du MVP : schéma SQLite porté depuis le prototype web, seed des règles/récompenses, flux d'onboarding en 4 étapes et écran d'accueil minimal.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 12:12:01 +02:00

130 lines
3.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../providers/database_provider.dart';
import '../dashboard/dashboard_screen.dart';
import 'onboarding_state.dart';
import 'steps/child_step.dart';
import 'steps/pin_step.dart';
import 'steps/rules_step.dart';
import 'steps/welcome_step.dart';
class OnboardingFlow extends ConsumerStatefulWidget {
const OnboardingFlow({super.key});
@override
ConsumerState<OnboardingFlow> createState() => _OnboardingFlowState();
}
class _OnboardingFlowState extends ConsumerState<OnboardingFlow> {
final _pageController = PageController();
bool _isSaving = false;
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
void _goTo(int page) {
_pageController.animateToPage(
page,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
Future<void> _finish() async {
final draft = ref.read(onboardingDraftProvider);
if (draft.childName.trim().isEmpty) {
_showError('Indiquez le prénom de l\'enfant.');
_goTo(1);
return;
}
if (draft.pin.length < 4) {
_showError('Le PIN doit contenir au moins 4 chiffres.');
_goTo(2);
return;
}
if (draft.pin != draft.pinConfirm) {
_showError('Les deux PIN ne correspondent pas.');
_goTo(2);
return;
}
setState(() => _isSaving = true);
try {
final repo = ref.read(onboardingRepositoryProvider);
await repo.finish(
childName: draft.childName,
color: draft.color,
pin: draft.pin,
importDefaultRules: draft.importDefaultRules,
);
ref.invalidate(onboardingCompleteProvider);
ref.invalidate(childrenProvider);
if (!mounted) return;
Navigator.of(context).pushReplacement(
MaterialPageRoute<void>(builder: (_) => const DashboardScreen()),
);
} catch (e) {
_showError('Erreur lors de l\'enregistrement. Réessayez.');
} finally {
if (mounted) setState(() => _isSaving = false);
}
}
void _showError(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message), backgroundColor: Colors.red.shade700),
);
}
@override
Widget build(BuildContext context) {
return PageView(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
onPageChanged: (_) {},
children: [
WelcomeStep(onNext: () => _goTo(1)),
ChildStep(
step: 2,
onBack: () => _goTo(0),
onNext: () {
final name = ref.read(onboardingDraftProvider).childName.trim();
if (name.isEmpty) {
_showError('Indiquez le prénom de l\'enfant.');
return;
}
_goTo(2);
},
),
PinStep(
step: 3,
onBack: () => _goTo(1),
onNext: () {
final draft = ref.read(onboardingDraftProvider);
if (draft.pin.length < 4) {
_showError('Le PIN doit contenir au moins 4 chiffres.');
return;
}
if (draft.pin != draft.pinConfirm) {
_showError('Les deux PIN ne correspondent pas.');
return;
}
_goTo(3);
},
),
RulesStep(
step: 4,
isSaving: _isSaving,
onBack: () => _goTo(2),
onFinish: _finish,
),
],
);
}
}