/// Écran de détail d'une session. /// /// Affiche la visualisation complète d'une session sauvegardée : /// image de la cible avec overlay des impacts, scores recalculés, /// statistiques de groupement et lien vers les statistiques détaillées. library; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:intl/intl.dart'; import '../../core/constants/app_constants.dart'; import '../../core/theme/app_theme.dart'; import '../../data/models/session.dart'; import '../../data/repositories/session_repository.dart'; import '../../services/score_calculator_service.dart'; import '../../services/grouping_analyzer_service.dart'; import '../analysis/widgets/target_overlay.dart'; import '../analysis/widgets/score_card.dart'; import '../analysis/widgets/grouping_stats.dart'; import '../statistics/statistics_screen.dart'; class SessionDetailScreen extends StatelessWidget { final Session session; const SessionDetailScreen({ super.key, required this.session, }); @override Widget build(BuildContext context) { final scoreCalculator = context.read(); final groupingAnalyzer = context.read(); final scoreResult = scoreCalculator.calculateScores( shots: session.shots, targetType: session.targetType, targetCenterX: session.targetCenterX ?? 0.5, targetCenterY: session.targetCenterY ?? 0.5, targetRadius: session.targetRadius ?? 0.4, ); final groupingResult = groupingAnalyzer.analyzeGrouping(session.shots); return Scaffold( appBar: AppBar( title: Text( DateFormat('dd/MM/yyyy HH:mm').format(session.createdAt), ), actions: [ IconButton( icon: const Icon(Icons.analytics), onPressed: () => Navigator.push( context, MaterialPageRoute( builder: (_) => StatisticsScreen(singleSession: session), ), ), tooltip: 'Statistiques', ), IconButton( icon: const Icon(Icons.delete), onPressed: () => _confirmDelete(context), tooltip: 'Supprimer', ), ], ), body: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Target image with overlay AspectRatio( aspectRatio: 1, child: Stack( fit: StackFit.expand, children: [ if (File(session.imagePath).existsSync()) Image.file( File(session.imagePath), fit: BoxFit.contain, ) else Container( color: Colors.grey[200], child: const Center( child: Icon(Icons.image_not_supported, size: 64), ), ), TargetOverlay( shots: session.shots, targetCenterX: session.targetCenterX ?? 0.5, targetCenterY: session.targetCenterY ?? 0.5, targetRadius: session.targetRadius ?? 0.4, targetType: session.targetType, groupingCenterX: session.groupingCenterX, groupingCenterY: session.groupingCenterY, groupingDiameter: session.groupingDiameter, ), ], ), ), Padding( padding: const EdgeInsets.all(AppConstants.defaultPadding), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Session info _buildSessionInfo(context), const SizedBox(height: 12), // Score card ScoreCard( totalScore: session.totalScore, shotCount: session.shotCount, scoreResult: scoreResult, targetType: session.targetType, ), const SizedBox(height: 12), // Grouping stats if (session.shotCount > 1) GroupingStats( groupingResult: groupingResult, targetCenterX: session.targetCenterX ?? 0.5, targetCenterY: session.targetCenterY ?? 0.5, ), // Notes if (session.notes != null && session.notes!.isNotEmpty) ...[ const SizedBox(height: 12), _buildNotesCard(context), ], ], ), ), ], ), ), ); } Widget _buildSessionInfo(BuildContext context) { return Card( child: Padding( padding: const EdgeInsets.all(AppConstants.defaultPadding), child: Row( children: [ Icon( session.targetType == session.targetType ? Icons.track_changes : Icons.person, color: AppTheme.primaryColor, ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( session.targetType.displayName, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), Text( DateFormat('EEEE dd MMMM yyyy, HH:mm', 'fr_FR') .format(session.createdAt), style: Theme.of(context).textTheme.bodySmall, ), ], ), ), ], ), ), ); } Widget _buildNotesCard(BuildContext context) { return Card( child: Padding( padding: const EdgeInsets.all(AppConstants.defaultPadding), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.notes, color: AppTheme.primaryColor), const SizedBox(width: 8), Text( 'Notes', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), ], ), const Divider(), Text(session.notes!), ], ), ), ); } Future _confirmDelete(BuildContext context) async { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Supprimer'), content: const Text('Voulez-vous vraiment supprimer cette session?'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Annuler'), ), TextButton( onPressed: () => Navigator.pop(context, true), child: const Text( 'Supprimer', style: TextStyle(color: AppTheme.errorColor), ), ), ], ), ); if (confirmed == true && context.mounted) { try { final repository = context.read(); await repository.deleteSession(session.id); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Session supprimee')), ); Navigator.pop(context); } } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur: $e'), backgroundColor: AppTheme.errorColor, ), ); } } } } }