328 lines
11 KiB
Dart
328 lines
11 KiB
Dart
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/models/target_analysis.dart';
|
|
import '../../data/models/target_type.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 StatefulWidget {
|
|
final Session session;
|
|
|
|
const SessionDetailScreen({
|
|
super.key,
|
|
required this.session,
|
|
});
|
|
|
|
@override
|
|
State<SessionDetailScreen> createState() => _SessionDetailScreenState();
|
|
}
|
|
|
|
class _SessionDetailScreenState extends State<SessionDetailScreen> {
|
|
int _currentTargetIndex = 0;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final scoreCalculator = context.read<ScoreCalculatorService>();
|
|
final groupingAnalyzer = context.read<GroupingAnalyzerService>();
|
|
|
|
final currentAnalysis = widget.session.analyses[_currentTargetIndex];
|
|
|
|
final scoreResult = scoreCalculator.calculateScores(
|
|
shots: currentAnalysis.shots,
|
|
targetType: currentAnalysis.targetType,
|
|
targetCenterX: currentAnalysis.targetCenterX ?? 0.5,
|
|
targetCenterY: currentAnalysis.targetCenterY ?? 0.5,
|
|
targetRadius: currentAnalysis.targetRadius ?? 0.4,
|
|
);
|
|
|
|
final groupingResult = groupingAnalyzer.analyzeGrouping(currentAnalysis.shots);
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(widget.session.weapon),
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.analytics),
|
|
onPressed: () => Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => StatisticsScreen(singleSession: widget.session),
|
|
),
|
|
),
|
|
tooltip: 'Statistiques',
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.delete),
|
|
onPressed: () => _confirmDelete(context),
|
|
tooltip: 'Supprimer',
|
|
),
|
|
],
|
|
),
|
|
body: SingleChildScrollView(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
// Weapon and session overview
|
|
_buildSessionHeader(context),
|
|
|
|
// Target selector if multiple targets
|
|
if (widget.session.targetCount > 1)
|
|
_buildTargetSelector(),
|
|
|
|
// Target image with overlay
|
|
AspectRatio(
|
|
aspectRatio: 1,
|
|
child: Stack(
|
|
fit: StackFit.expand,
|
|
children: [
|
|
if (File(currentAnalysis.imagePath).existsSync())
|
|
Image.file(
|
|
File(currentAnalysis.imagePath),
|
|
fit: BoxFit.contain,
|
|
)
|
|
else
|
|
Container(
|
|
color: Colors.grey[200],
|
|
child: const Center(
|
|
child: Icon(Icons.image_not_supported, size: 64),
|
|
),
|
|
),
|
|
TargetOverlay(
|
|
shots: currentAnalysis.shots,
|
|
targetCenterX: currentAnalysis.targetCenterX ?? 0.5,
|
|
targetCenterY: currentAnalysis.targetCenterY ?? 0.5,
|
|
targetRadius: currentAnalysis.targetRadius ?? 0.4,
|
|
targetType: currentAnalysis.targetType,
|
|
groupingCenterX: currentAnalysis.groupingCenterX,
|
|
groupingCenterY: currentAnalysis.groupingCenterY,
|
|
groupingDiameter: currentAnalysis.groupingDiameter,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
Padding(
|
|
padding: const EdgeInsets.all(AppConstants.defaultPadding),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Analysis Info
|
|
_buildAnalysisInfo(context, currentAnalysis),
|
|
const SizedBox(height: 12),
|
|
|
|
// Score card for current target
|
|
ScoreCard(
|
|
totalScore: currentAnalysis.totalScore,
|
|
shotCount: currentAnalysis.shotCount,
|
|
scoreResult: scoreResult,
|
|
targetType: currentAnalysis.targetType,
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
// Grouping stats for current target
|
|
if (currentAnalysis.shotCount > 1)
|
|
GroupingStats(
|
|
groupingResult: groupingResult,
|
|
targetCenterX: currentAnalysis.targetCenterX ?? 0.5,
|
|
targetCenterY: currentAnalysis.targetCenterY ?? 0.5,
|
|
),
|
|
|
|
// Notes for current target
|
|
if (currentAnalysis.notes != null && currentAnalysis.notes!.isNotEmpty) ...[
|
|
const SizedBox(height: 12),
|
|
_buildNotesCard(context, currentAnalysis.notes!),
|
|
],
|
|
|
|
// Session notes
|
|
if (widget.session.notes != null && widget.session.notes!.isNotEmpty) ...[
|
|
const SizedBox(height: 12),
|
|
_buildNotesCard(context, widget.session.notes!, title: 'Notes de session'),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSessionHeader(BuildContext context) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(16),
|
|
color: AppTheme.primaryColor.withValues(alpha: 0.1),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
DateFormat('EEEE dd MMMM yyyy', 'fr_FR').format(widget.session.createdAt),
|
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
Text(
|
|
DateFormat('HH:mm').format(widget.session.createdAt),
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
),
|
|
],
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Text(
|
|
'Score Total: ${widget.session.totalScore}',
|
|
style: const TextStyle(fontWeight: FontWeight.bold, color: AppTheme.primaryColor),
|
|
),
|
|
Text(
|
|
'${widget.session.totalShots} tirs au total',
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTargetSelector() {
|
|
return Container(
|
|
height: 50,
|
|
margin: const EdgeInsets.symmetric(vertical: 8),
|
|
child: ListView.builder(
|
|
scrollDirection: Axis.horizontal,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
itemCount: widget.session.targetCount,
|
|
itemBuilder: (context, index) {
|
|
final isSelected = _currentTargetIndex == index;
|
|
return Padding(
|
|
padding: const EdgeInsets.only(right: 8),
|
|
child: ChoiceChip(
|
|
label: Text('Cible ${index + 1}'),
|
|
selected: isSelected,
|
|
onSelected: (selected) {
|
|
if (selected) {
|
|
setState(() => _currentTargetIndex = index);
|
|
}
|
|
},
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildAnalysisInfo(BuildContext context, TargetAnalysis analysis) {
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(12),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
analysis.targetType == TargetType.concentric ? Icons.track_changes : Icons.person,
|
|
color: AppTheme.primaryColor,
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
analysis.targetType.displayName,
|
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
Text(
|
|
'Cible ${_currentTargetIndex + 1} sur ${widget.session.targetCount}',
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildNotesCard(BuildContext context, String notes, {String title = 'Notes'}) {
|
|
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, size: 20),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
title,
|
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
],
|
|
),
|
|
const Divider(),
|
|
Text(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 entière ?'),
|
|
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(widget.session.id);
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Session supprimée')),
|
|
);
|
|
Navigator.pop(context);
|
|
}
|
|
} catch (e) {
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Erreur: $e'), backgroundColor: AppTheme.errorColor),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|