import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:intl/intl.dart'; import '../../main_navigation_holder.dart'; import '../../core/theme/app_theme.dart'; import '../../core/widgets/glass_container.dart'; import '../../data/models/session.dart'; import '../../data/repositories/session_repository.dart'; import '../capture/capture_screen.dart'; import '../history/history_screen.dart'; import '../history/session_detail_screen.dart'; import '../session/session_setup_screen.dart'; import '../session/session_provider.dart'; import '../settings/settings_screen.dart'; import '../statistics/statistics_screen.dart'; import '../tutorial/tutorial_coach.dart'; import '../tutorial/tutorial_provider.dart'; import '../tutorial/tutorial_step.dart'; import '../../services/tutorial_service.dart'; import 'widgets/stats_card.dart'; class HomeScreen extends StatefulWidget { final int refreshTick; const HomeScreen({super.key, this.refreshTick = 0}); @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State { Map? _stats; List _recentSessions = []; bool _isLoading = true; SessionProvider? _sessionProvider; bool _wasSessionActive = false; // Clés utilisées par le didacticiel pour mettre les éléments en avant. final GlobalKey _tutoActionKey = GlobalKey(); final GlobalKey _tutoStatsKey = GlobalKey(); final GlobalKey _tutoSettingsKey = GlobalKey(); TutorialProvider? _tutorialProvider; @override void initState() { super.initState(); _loadStats(); // Première utilisation : la visite guidée démarre dès le premier rendu. WidgetsBinding.instance.addPostFrameCallback((_) => _maybeStartTutorial()); } @override void didUpdateWidget(HomeScreen oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.refreshTick != widget.refreshTick) { _loadStats(); } } @override void didChangeDependencies() { super.didChangeDependencies(); final provider = context.read(); if (provider != _sessionProvider) { _sessionProvider?.removeListener(_onSessionChanged); _sessionProvider = provider; _sessionProvider!.addListener(_onSessionChanged); _wasSessionActive = provider.isSessionActive; } final tutorial = context.read(); if (tutorial != _tutorialProvider) { _tutorialProvider?.removeListener(_onTutorialChanged); _tutorialProvider = tutorial; _tutorialProvider!.addListener(_onTutorialChanged); } } /// Les préférences du didacticiel sont lues de façon asynchrone : on /// retente le démarrage dès qu'elles sont disponibles. void _onTutorialChanged() { if (!mounted) return; WidgetsBinding.instance.addPostFrameCallback((_) => _maybeStartTutorial()); } /// Lance la visite guidée de l'accueil si elle n'a pas encore été vue /// (première utilisation, ou didacticiel relancé depuis les paramètres). void _maybeStartTutorial() { if (!mounted) return; if (!(ModalRoute.of(context)?.isCurrent ?? false)) return; TutorialCoach.maybeStart( context, tourId: TutorialTours.home, stepsBuilder: _buildHomeTutorialSteps, ); } List _buildHomeTutorialSteps() { final isSessionActive = context.read().isSessionActive; return [ const TutorialStep( title: 'Bienvenue dans Bully', description: 'Ce guide rapide vous montre comment enregistrer une séance, ' 'placer vos impacts et suivre vos progrès.\n' 'Touchez l\'écran pour passer à l\'étape suivante.', icon: Icons.waving_hand_outlined, ), TutorialStep( targetKey: _tutoActionKey, title: isSessionActive ? 'Votre session en cours' : 'Démarrez une session', description: isSessionActive ? 'Reprenez la session en cours pour photographier une nouvelle ' 'cible, ou clôturez-la avec le bouton rouge.' : 'Tout part d\'ici : créez une session, choisissez votre arme, ' 'puis photographiez votre cible.', gesture: TutorialGesture.tap, icon: Icons.add_circle_outline, spotPadding: 10, ), TutorialStep( targetKey: _tutoStatsKey, title: 'Votre télémétrie', description: 'Sessions, tirs analysés, score moyen et meilleur score se mettent ' 'à jour après chaque séance. « Détails » ouvre les statistiques ' 'complètes.', icon: Icons.insights_outlined, ), TutorialStep( targetKey: navigationDockKey, title: 'La barre de navigation', description: 'Accueil, Historique, Statistiques et Armurerie : vos quatre ' 'espaces de travail, accessibles à tout moment.', gesture: TutorialGesture.tap, icon: Icons.dashboard_customize_outlined, spotPadding: 6, ), TutorialStep( targetKey: _tutoSettingsKey, title: 'Paramètres & aide', description: 'Thème, armurerie, sauvegarde… et le bouton « Revoir le ' 'didacticiel » pour rejouer ce guide quand vous le souhaitez.', gesture: TutorialGesture.tap, shape: TutorialHighlightShape.circle, icon: Icons.settings_outlined, spotPadding: 6, ), ]; } void _onSessionChanged() { final isActive = _sessionProvider?.isSessionActive ?? false; if (_wasSessionActive && !isActive) { _loadStats(); } _wasSessionActive = isActive; } @override void dispose() { _sessionProvider?.removeListener(_onSessionChanged); _tutorialProvider?.removeListener(_onTutorialChanged); super.dispose(); } Future _loadStats() async { final repository = context.read(); final stats = await repository.getStatistics(); final sessions = await repository.getAllSessions(); if (mounted) { setState(() { _stats = stats; _recentSessions = sessions; _isLoading = false; }); } } @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; final primaryColor = Theme.of(context).colorScheme.primary; final textSecondary = isDark ? AppTheme.darkTextSecondary : AppTheme.lightTextSecondary; return Scaffold( extendBody: true, appBar: AppBar( titleSpacing: 16, leading: Padding( padding: const EdgeInsets.only(left: 16), child: Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: (isDark ? Colors.white : Colors.black).withValues(alpha: 0.06), borderRadius: BorderRadius.circular(8), border: Border.all( color: isDark ? AppTheme.darkBorder : AppTheme.lightBorder, ), ), child: Text( 'v1.0.3', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, color: textSecondary, letterSpacing: 0.5, ), ), ), ), ), leadingWidth: 72, title: Row( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: primaryColor.withValues(alpha: 0.18), borderRadius: BorderRadius.circular(8), ), child: Icon(Icons.gps_fixed, color: primaryColor, size: 18), ), const SizedBox(width: 10), const Text( 'BULLY', style: TextStyle( fontWeight: FontWeight.w900, letterSpacing: 2.0, fontSize: 20, ), ), ], ), actions: [ IconButton( key: _tutoSettingsKey, icon: const Icon(Icons.settings_outlined), onPressed: () => _navigateToSettings(context), tooltip: 'Paramètres', ), const SizedBox(width: 8), ], ), body: Stack( children: [ // Ambiance lumineuse d'arrière-plan (lueurs dégradées) if (isDark) ...[ Positioned( top: -60, right: -60, child: Container( width: 260, height: 260, decoration: BoxDecoration( shape: BoxShape.circle, gradient: RadialGradient( colors: [ primaryColor.withValues(alpha: 0.18), Colors.transparent, ], ), ), ), ), Positioned( top: 300, left: -80, child: Container( width: 220, height: 220, decoration: BoxDecoration( shape: BoxShape.circle, gradient: RadialGradient( colors: [ AppTheme.secondaryColor.withValues(alpha: 0.09), Colors.transparent, ], ), ), ), ), ], RefreshIndicator( onRefresh: _loadStats, color: primaryColor, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), // Padding bas important pour laisser respirer le dock flottant padding: const EdgeInsets.fromLTRB(16, 8, 16, 100), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildHeader(isDark, primaryColor), const SizedBox(height: 16), KeyedSubtree( key: _tutoActionKey, child: _buildMainActionSection(context, isDark, primaryColor), ), const SizedBox(height: 22), if (_isLoading) const Padding( padding: EdgeInsets.symmetric(vertical: 40), child: Center(child: CircularProgressIndicator()), ) else if (_stats != null) ...[ _buildStatsSection(isDark, primaryColor), const SizedBox(height: 22), _buildRecentSessionsSection(isDark, primaryColor), ], ], ), ), ), ], ), ); } Widget _buildHeader(bool isDark, Color primaryColor) { return GlassContainer( borderRadius: 20, blur: 16, glowColor: primaryColor, borderColor: isDark ? primaryColor.withValues(alpha: 0.25) : primaryColor.withValues(alpha: 0.15), padding: const EdgeInsets.all(18), child: Row( children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( gradient: LinearGradient( colors: [ primaryColor, HSLColor.fromColor(primaryColor).withLightness( (HSLColor.fromColor(primaryColor).lightness + 0.15).clamp(0.0, 1.0), ).toColor(), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: primaryColor.withValues(alpha: 0.35), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: const Icon( Icons.track_changes, size: 28, color: Colors.white, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: primaryColor.withValues(alpha: isDark ? 0.25 : 0.15), borderRadius: BorderRadius.circular(6), ), child: Text( 'BALISTIQUE & CIBLES', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w800, letterSpacing: 1.2, color: primaryColor, ), ), ), const SizedBox(width: 6), Container( width: 6, height: 6, decoration: const BoxDecoration( color: AppTheme.secondaryColor, shape: BoxShape.circle, ), ), ], ), const SizedBox(height: 4), Text( 'Télémétrie de Tir', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w800, color: isDark ? AppTheme.darkTextPrimary : AppTheme.lightTextPrimary, letterSpacing: -0.3, ), ), const SizedBox(height: 2), Text( 'Analysez vos groupements et scores', style: TextStyle( fontSize: 12, color: isDark ? AppTheme.darkTextSecondary : AppTheme.lightTextSecondary, ), ), ], ), ), ], ), ); } Widget _buildMainActionSection(BuildContext context, bool isDark, Color primaryColor) { final sessionProvider = context.watch(); final isSessionActive = sessionProvider.isSessionActive; if (!isSessionActive) { return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), gradient: LinearGradient( colors: [ HSLColor.fromColor(primaryColor).withLightness((HSLColor.fromColor(primaryColor).lightness - 0.1).clamp(0.0, 1.0)).toColor(), primaryColor, HSLColor.fromColor(primaryColor).withLightness((HSLColor.fromColor(primaryColor).lightness + 0.15).clamp(0.0, 1.0)).toColor(), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), boxShadow: [ BoxShadow( color: primaryColor.withValues(alpha: isDark ? 0.45 : 0.3), blurRadius: 20, spreadRadius: -2, offset: const Offset(0, 8), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(20), child: Material( color: Colors.transparent, child: InkWell( onTap: () => _navigateToSessionSetup(context), splashColor: Colors.white24, child: Padding( padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 20), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.25), shape: BoxShape.circle, ), child: const Icon(Icons.add, color: Colors.white, size: 22), ), const SizedBox(width: 14), const Text( 'NOUVELLE SESSION DE TIR', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w900, color: Colors.white, letterSpacing: 1.0, ), ), ], ), ), ), ), ), ); } // Session en cours : Glass Card dynamique avec halo vert return GlassContainer( borderRadius: 20, blur: 16, glowColor: AppTheme.secondaryColor, borderColor: AppTheme.secondaryColor.withValues(alpha: isDark ? 0.45 : 0.35), padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: AppTheme.secondaryColor.withValues(alpha: isDark ? 0.22 : 0.15), borderRadius: BorderRadius.circular(8), border: Border.all( color: AppTheme.secondaryColor.withValues(alpha: 0.4), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 8, height: 8, decoration: const BoxDecoration( color: AppTheme.secondaryColor, shape: BoxShape.circle, ), ), const SizedBox(width: 6), const Text( 'SESSION EN COURS', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w800, color: AppTheme.secondaryColor, letterSpacing: 0.8, ), ), ], ), ), const Spacer(), if (sessionProvider.currentWeapon != null) Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: (isDark ? Colors.white : Colors.black).withValues(alpha: 0.06), borderRadius: BorderRadius.circular(6), ), child: Text( sessionProvider.currentWeapon!, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: isDark ? AppTheme.darkTextPrimary : AppTheme.lightTextPrimary, ), ), ), ], ), const SizedBox(height: 14), Row( children: [ Expanded( flex: 4, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(14), gradient: const LinearGradient( colors: [Color(0xFF10B981), Color(0xFF059669)], ), boxShadow: [ BoxShadow( color: AppTheme.secondaryColor.withValues(alpha: 0.3), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: ElevatedButton.icon( onPressed: () => _navigateToSessionSetup(context), style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, foregroundColor: Colors.white, shadowColor: Colors.transparent, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), ), icon: const Icon(Icons.play_arrow_rounded, size: 24), label: const Text( 'Reprendre la session', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w800), ), ), ), ), const SizedBox(width: 10), Expanded( flex: 1, child: OutlinedButton( onPressed: () => _confirmEndSession(context), style: OutlinedButton.styleFrom( foregroundColor: AppTheme.errorColor, side: BorderSide( color: AppTheme.errorColor.withValues(alpha: 0.7), width: 1.4, ), padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), ), child: const Icon(Icons.close, size: 20), ), ), ], ), ], ), ); } void _confirmEndSession(BuildContext context) { showDialog( context: context, builder: (BuildContext dialogContext) { return AlertDialog( title: const Text('Clôturer la session ?'), content: const Text( 'Êtes-vous sûr de vouloir terminer la session en cours ? Vos tirs actuels seront enregistrés.', ), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), child: const Text('Annuler'), ), ElevatedButton( onPressed: () { Navigator.pop(dialogContext); context.read().endSession(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Session terminée avec succès'), backgroundColor: AppTheme.successColor, ), ); openMainTab(mainTabStats); }, style: ElevatedButton.styleFrom( backgroundColor: AppTheme.errorColor, foregroundColor: Colors.white, ), child: const Text('Terminer'), ), ], ); }, ); } Widget _buildStatsSection(bool isDark, Color primaryColor) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Télémétrie Globale', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w800, color: isDark ? AppTheme.darkTextPrimary : AppTheme.lightTextPrimary, letterSpacing: 0.2, ), ), InkWell( onTap: () => _navigateToStatistics(context), borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), child: Row( children: [ Text( 'Détails', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: primaryColor, ), ), Icon(Icons.chevron_right, size: 18, color: primaryColor), ], ), ), ), ], ), const SizedBox(height: 12), Row( key: _tutoStatsKey, children: [ Expanded( child: InkWell( onTap: () => _navigateToStatistics(context), borderRadius: BorderRadius.circular(18), child: StatsCard( icon: Icons.assignment_outlined, title: 'Sessions', value: '${_stats!['totalSessions']}', color: primaryColor, ), ), ), const SizedBox(width: 12), Expanded( child: StatsCard( icon: Icons.track_changes, title: 'Tirs analysés', value: '${_stats!['totalShots']}', color: AppTheme.secondaryColor, ), ), ], ), const SizedBox(height: 12), Row( children: [ Expanded( child: StatsCard( icon: Icons.pie_chart_outline, title: 'Score moyen', value: '${_stats!['averageScore'].toStringAsFixed(1)} pts', subtitle: 'sur 10 max', color: AppTheme.accentGold, ), ), const SizedBox(width: 12), Expanded( child: StatsCard( icon: Icons.star_border, title: 'Meilleur score', value: '${_stats!['bestScore']} pts', color: AppTheme.secondaryColor, ), ), ], ), const SizedBox(height: 14), GlassContainer( borderRadius: 20, blur: 16, borderColor: isDark ? Colors.white.withValues(alpha: 0.1) : Colors.black.withValues(alpha: 0.06), padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Progression récente', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: isDark ? AppTheme.darkTextPrimary : AppTheme.lightTextPrimary, ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: (isDark ? Colors.white : Colors.black).withValues(alpha: 0.05), borderRadius: BorderRadius.circular(6), ), child: Text( '7 dernières séances', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w600, color: isDark ? AppTheme.darkTextMuted : AppTheme.lightTextMuted, ), ), ), ], ), const SizedBox(height: 12), SizedBox( height: 120, child: _recentSessions.length < 2 ? Center( child: Text( '2 séances minimum pour afficher la courbe', style: TextStyle( color: isDark ? AppTheme.darkTextMuted : AppTheme.lightTextMuted, fontSize: 12, ), ), ) : _buildHomeTrendChart(isDark, primaryColor), ), ], ), ), ], ); } Widget _buildHomeTrendChart(bool isDark, Color primaryColor) { final sorted = List.from(_recentSessions) ..sort((a, b) => a.createdAt.compareTo(b.createdAt)); final display = sorted.length > 7 ? sorted.sublist(sorted.length - 7) : sorted; final gridColor = isDark ? Colors.white.withValues(alpha: 0.06) : Colors.black.withValues(alpha: 0.04); final textColor = isDark ? AppTheme.darkTextMuted : AppTheme.lightTextMuted; return LineChart( LineChartData( gridData: FlGridData( show: true, drawVerticalLine: false, getDrawingHorizontalLine: (value) => FlLine( color: gridColor, strokeWidth: 1, ), ), titlesData: FlTitlesData( show: true, rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 20, interval: 1, getTitlesWidget: (value, meta) { final index = value.toInt(); if (index < 0 || index >= display.length) return const SizedBox(); final date = display[index].createdAt; return Padding( padding: const EdgeInsets.only(top: 4.0), child: Text( '${date.day}/${date.month}', style: TextStyle(fontSize: 10, color: textColor), ), ); }, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 24, interval: 2, getTitlesWidget: (value, meta) { if (value < 0 || value > 10) return const SizedBox(); return Text( value.toInt().toString(), style: TextStyle(fontSize: 10, color: textColor), ); }, ), ), ), borderData: FlBorderData(show: false), lineBarsData: [ LineChartBarData( spots: display.asMap().entries.map((e) { return FlSpot(e.key.toDouble(), e.value.averageScore); }).toList(), isCurved: true, curveSmoothness: 0.35, color: primaryColor, barWidth: 3, isStrokeCapRound: true, dotData: FlDotData( show: true, getDotPainter: (spot, percent, barData, index) => FlDotCirclePainter( radius: 3.5, color: Colors.white, strokeWidth: 2, strokeColor: primaryColor, ), ), belowBarData: BarAreaData( show: true, gradient: LinearGradient( colors: [ primaryColor.withValues(alpha: isDark ? 0.35 : 0.2), primaryColor.withValues(alpha: 0.0), ], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), ), ], ), ); } Widget _buildRecentSessionsSection(bool isDark, Color primaryColor) { if (_recentSessions.isEmpty) return const SizedBox(); final sessionsToShow = _recentSessions.take(3).toList(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Dernières Séances', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w800, color: isDark ? AppTheme.darkTextPrimary : AppTheme.lightTextPrimary, letterSpacing: 0.2, ), ), InkWell( onTap: () => _navigateToHistory(context), borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), child: Text( 'Voir tout (${_recentSessions.length})', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: primaryColor, ), ), ), ), ], ), const SizedBox(height: 12), ...sessionsToShow.map((session) => _buildRecentSessionTile(session, isDark, primaryColor)), ], ); } Widget _buildRecentSessionTile(Session session, bool isDark, Color primaryColor) { final dateFormat = DateFormat('dd MMM yyyy • HH:mm', 'fr_FR'); final formattedDate = dateFormat.format(session.createdAt); return GlassContainer( borderRadius: 16, blur: 12, margin: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.all(14), onTap: () async { await Navigator.push( context, MaterialPageRoute( builder: (_) => SessionDetailScreen(session: session), ), ); _loadStats(); }, child: Row( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( gradient: LinearGradient( colors: [ primaryColor.withValues(alpha: 0.25), primaryColor.withValues(alpha: 0.08), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(14), border: Border.all( color: primaryColor.withValues(alpha: 0.35), ), ), child: Center( child: Text( '${session.totalScore}', style: TextStyle( fontSize: 17, fontWeight: FontWeight.w900, color: primaryColor, ), ), ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( session.weapon, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w700, color: isDark ? AppTheme.darkTextPrimary : AppTheme.lightTextPrimary, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), Text( formattedDate, style: TextStyle( fontSize: 11, color: isDark ? AppTheme.darkTextSecondary : AppTheme.lightTextSecondary, ), ), ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: (isDark ? Colors.white : Colors.black).withValues(alpha: 0.06), borderRadius: BorderRadius.circular(6), border: Border.all( color: isDark ? AppTheme.darkBorder : AppTheme.lightBorder, ), ), child: Text( '${session.distance}m', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, color: isDark ? AppTheme.darkTextPrimary : AppTheme.lightTextPrimary, ), ), ), const SizedBox(height: 4), Text( '${session.totalShots} tirs', style: TextStyle( fontSize: 11, color: isDark ? AppTheme.darkTextMuted : AppTheme.lightTextMuted, ), ), ], ), ], ), ); } void _navigateToSessionSetup(BuildContext context) async { final sessionProvider = context.read(); if (sessionProvider.isSessionActive) { await Navigator.push( context, MaterialPageRoute(builder: (_) => const CaptureScreen()), ); } else { await Navigator.push( context, MaterialPageRoute(builder: (_) => const SessionSetupScreen()), ); } _loadStats(); } void _navigateToHistory(BuildContext context) async { await Navigator.push( context, MaterialPageRoute(builder: (_) => const HistoryScreen()), ); _loadStats(); } void _navigateToStatistics(BuildContext context) async { await Navigator.push( context, MaterialPageRoute(builder: (_) => const StatisticsScreen()), ); _loadStats(); } void _navigateToSettings(BuildContext context) async { await Navigator.push( context, MaterialPageRoute(builder: (_) => const SettingsScreen()), ); if (!mounted) return; _loadStats(); // Si le didacticiel a été relancé depuis les paramètres, il redémarre // une fois la transition de retour terminée. await Future.delayed(const Duration(milliseconds: 350)); _maybeStartTutorial(); } }