feat(session): ajout du sélecteur de date et persistance dans l'historique de tir

This commit is contained in:
qguillaume
2026-05-27 00:57:26 +02:00
parent e570bd4296
commit 78c9fc96c9
6 changed files with 111 additions and 54 deletions

View File

@@ -4,6 +4,8 @@ import '../../data/models/target_analysis.dart';
import '../../data/models/weapon.dart';
class SessionProvider extends ChangeNotifier {
DateTime? _sessionDate;
DateTime? get sessionDate => _sessionDate;
String? _currentWeaponName;
String? _currentWeaponId;
int _shotsPerTarget = 5;
@@ -23,11 +25,12 @@ class SessionProvider extends ChangeNotifier {
int get totalSessionScore => _currentAnalyses.fold(0, (sum, a) => sum + a.totalScore);
int get targetCount => _currentAnalyses.length;
void startSession(String weaponName, int shots, String sessionId, {String? weaponId, int distance = 25}) {
void startSession(String weaponName, int shots, String sessionId, {String? weaponId, int distance = 25, DateTime? date}) {
_currentWeaponName = weaponName;
_currentWeaponId = weaponId;
_shotsPerTarget = shots;
_distance = distance;
_sessionDate = date ?? DateTime.now();
_activeSessionId = sessionId;
_currentAnalyses.clear();
_isSessionActive = true;

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart'; // Utile pour formater proprement la date en français
import '../../core/constants/app_constants.dart';
import '../../core/theme/app_theme.dart';
import '../../data/models/weapon.dart';
@@ -18,13 +19,15 @@ class SessionSetupScreen extends StatefulWidget {
class _SessionSetupScreenState extends State<SessionSetupScreen> {
final _formKey = GlobalKey<FormState>();
// Plus besoin du _weaponController, on force la sélection
int _shotsPerTarget = 5;
int _distance = 25;
List<Weapon> _availableWeapons = [];
Weapon? _selectedWeapon;
bool _isLoadingWeapons = true;
// AJOUT DE LA VARIABLE DATE : Initialisée par défaut à maintenant
DateTime _selectedDate = DateTime.now();
@override
void initState() {
super.initState();
@@ -39,7 +42,6 @@ class _SessionSetupScreenState extends State<SessionSetupScreen> {
if (mounted) {
setState(() {
_availableWeapons = weapons;
// Si la liste n'est pas vide et qu'aucune arme n'est sélectionnée, on sélectionne la première par défaut
if (_availableWeapons.isNotEmpty && _selectedWeapon == null) {
_selectedWeapon = _availableWeapons.first;
_updateSettingsForWeapon(_selectedWeapon!);
@@ -49,25 +51,47 @@ class _SessionSetupScreenState extends State<SessionSetupScreen> {
}
}
// Petite méthode pour mettre à jour les curseurs selon l'arme sélectionnée
void _updateSettingsForWeapon(Weapon weapon) {
_shotsPerTarget = weapon.magazineCapacity;
_distance = (weapon.type == WeaponType.handgun) ? 25 : 50;
}
// Fonction pour ouvrir le calendrier si l'utilisateur clique sur le champ
Future<void> _pickDate() async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: _selectedDate,
firstDate: DateTime(2020),
lastDate: DateTime(2100),
locale: const Locale('fr', 'FR'), // Force le calendrier en français
);
if (picked != null && picked != _selectedDate) {
setState(() {
// On garde aussi l'heure actuelle lors du changement de date
final now = DateTime.now();
_selectedDate = DateTime(picked.year, picked.month, picked.day, now.hour, now.minute);
});
}
}
void _startSession() {
// Sécurité supplémentaire : impossible de démarrer sans arme
if (_formKey.currentState!.validate() && _selectedWeapon != null) {
final repository = context.read<SessionRepository>();
final sessionProvider = context.read<SessionProvider>();
final sessionId = repository.generateId();
// PASSE-PARTOUT : On envoie la session au provider.
// Note : Si ton `sessionProvider.startSession` ne prend pas encore la date en paramètre,
// pas de panique, la compilation passera car Dart tolère les arguments nommés optionnels
// s'ils sont déjà présents, ou tu pourras l'ajouter à ta méthode startSession.
sessionProvider.startSession(
_selectedWeapon!.displayName,
_shotsPerTarget,
sessionId,
weaponId: _selectedWeapon!.id,
distance: _distance,
date: _selectedDate,
);
Navigator.pushReplacement(
@@ -79,6 +103,9 @@ class _SessionSetupScreenState extends State<SessionSetupScreen> {
@override
Widget build(BuildContext context) {
// Petit formatage sympa en français (ex: "27 mai 2026")
final String formattedDate = DateFormat('dd MMMM yyyy', 'fr_FR').format(_selectedDate);
return Scaffold(
appBar: AppBar(
title: const Text('Configuration de la session'),
@@ -93,7 +120,7 @@ class _SessionSetupScreenState extends State<SessionSetupScreen> {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'Choix de l\'arme',
'Informations générales',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
@@ -128,7 +155,6 @@ class _SessionSetupScreenState extends State<SessionSetupScreen> {
context,
MaterialPageRoute(builder: (_) => const WeaponListScreen()),
).then((_) {
// Quand on revient de l'armurerie, on recharge la liste !
_loadWeapons();
});
},
@@ -139,28 +165,50 @@ class _SessionSetupScreenState extends State<SessionSetupScreen> {
),
)
else
DropdownButtonFormField<Weapon>(
value: _selectedWeapon,
decoration: InputDecoration(
labelText: 'Sélectionner une arme',
prefixIcon: const Icon(Icons.shield),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
Column(
children: [
DropdownButtonFormField<Weapon>(
value: _selectedWeapon,
decoration: InputDecoration(
labelText: 'Sélectionner une arme',
prefixIcon: const Icon(Icons.shield),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
),
),
items: _availableWeapons.map((w) => DropdownMenuItem(
value: w,
child: Text(w.displayName),
)).toList(),
onChanged: (value) {
if (value != null) {
setState(() {
_selectedWeapon = value;
_updateSettingsForWeapon(value);
});
}
},
),
),
// On a retiré le choix "null / Autre", l'utilisateur DOIT choisir une arme existante
items: _availableWeapons.map((w) => DropdownMenuItem(
value: w,
child: Text(w.displayName),
)).toList(),
onChanged: (value) {
if (value != null) {
setState(() {
_selectedWeapon = value;
_updateSettingsForWeapon(value);
});
}
},
const SizedBox(height: 16),
// NOUVEAU CHAMP : Sélecteur de date interactif
InkWell(
onTap: _availableWeapons.isEmpty ? null : _pickDate,
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
child: IgnorePointer(
child: TextFormField(
decoration: InputDecoration(
labelText: 'Date de la session',
prefixIcon: const Icon(Icons.calendar_today),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
),
),
controller: TextEditingController(text: formattedDate),
),
),
),
],
),
const SizedBox(height: 24),
@@ -252,7 +300,6 @@ class _SessionSetupScreenState extends State<SessionSetupScreen> {
const SizedBox(height: 48),
ElevatedButton.icon(
// On désactive le bouton si la liste est vide ou si aucune arme n'est sélectionnée
onPressed: (_availableWeapons.isEmpty || _selectedWeapon == null)
? null
: _startSession,