254 lines
8.1 KiB
Dart
254 lines
8.1 KiB
Dart
/// É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<ScoreCalculatorService>();
|
|
final groupingAnalyzer = context.read<GroupingAnalyzerService>();
|
|
|
|
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<void> _confirmDelete(BuildContext context) async {
|
|
final confirmed = await showDialog<bool>(
|
|
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<SessionRepository>();
|
|
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,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|