feat(setup): choix d'arme obligatoire, redirection armurerie et max balles à 50
This commit is contained in:
@@ -6,6 +6,7 @@ import '../../data/models/weapon.dart';
|
||||
import '../../data/repositories/session_repository.dart';
|
||||
import 'session_provider.dart';
|
||||
import '../capture/capture_screen.dart';
|
||||
import '../garage/weapon_list_screen.dart';
|
||||
|
||||
class SessionSetupScreen extends StatefulWidget {
|
||||
const SessionSetupScreen({super.key});
|
||||
@@ -16,7 +17,8 @@ class SessionSetupScreen extends StatefulWidget {
|
||||
|
||||
class _SessionSetupScreenState extends State<SessionSetupScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _weaponController = TextEditingController();
|
||||
|
||||
// Plus besoin du _weaponController, on force la sélection
|
||||
int _shotsPerTarget = 5;
|
||||
int _distance = 25;
|
||||
List<Weapon> _availableWeapons = [];
|
||||
@@ -30,33 +32,41 @@ class _SessionSetupScreenState extends State<SessionSetupScreen> {
|
||||
}
|
||||
|
||||
Future<void> _loadWeapons() async {
|
||||
setState(() => _isLoadingWeapons = true);
|
||||
final repository = context.read<SessionRepository>();
|
||||
final weapons = await repository.getWeapons();
|
||||
|
||||
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!);
|
||||
}
|
||||
_isLoadingWeapons = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_weaponController.dispose();
|
||||
super.dispose();
|
||||
// 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;
|
||||
}
|
||||
|
||||
void _startSession() {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
// 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();
|
||||
sessionProvider.startSession(
|
||||
_selectedWeapon != null ? _selectedWeapon!.displayName : _weaponController.text,
|
||||
_selectedWeapon!.displayName,
|
||||
_shotsPerTarget,
|
||||
sessionId,
|
||||
weaponId: _selectedWeapon?.id,
|
||||
weaponId: _selectedWeapon!.id,
|
||||
distance: _distance,
|
||||
);
|
||||
|
||||
@@ -73,7 +83,7 @@ class _SessionSetupScreenState extends State<SessionSetupScreen> {
|
||||
appBar: AppBar(
|
||||
title: const Text('Configuration de la session'),
|
||||
),
|
||||
body: _isLoadingWeapons
|
||||
body: _isLoadingWeapons
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(AppConstants.defaultPadding),
|
||||
@@ -87,65 +97,72 @@ class _SessionSetupScreenState extends State<SessionSetupScreen> {
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
if (_availableWeapons.isNotEmpty) ...[
|
||||
DropdownButtonFormField<Weapon?>(
|
||||
|
||||
// AFFICHAGE CONDITIONNEL : Armurerie vide vs Armurerie remplie
|
||||
if (_availableWeapons.isEmpty)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.errorColor.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
|
||||
border: Border.all(color: AppTheme.errorColor.withValues(alpha: 0.5)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const Icon(Icons.warning_amber_rounded, color: AppTheme.errorColor, size: 48),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'Ton armurerie est vide.',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text(
|
||||
'Tu dois d\'abord ajouter une arme pour pouvoir démarrer une session de tir.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const WeaponListScreen()),
|
||||
).then((_) {
|
||||
// Quand on revient de l'armurerie, on recharge la liste !
|
||||
_loadWeapons();
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('ALLER À MON ARMURERIE'),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
DropdownButtonFormField<Weapon>(
|
||||
value: _selectedWeapon,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Sélectionner une arme de l\'armurerie',
|
||||
labelText: 'Sélectionner une arme',
|
||||
prefixIcon: const Icon(Icons.shield),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
|
||||
),
|
||||
),
|
||||
items: [
|
||||
const DropdownMenuItem<Weapon?>(
|
||||
value: null,
|
||||
child: Text('Nouvelle arme / Autre'),
|
||||
),
|
||||
..._availableWeapons.map((w) => DropdownMenuItem(
|
||||
value: w,
|
||||
child: Text(w.displayName),
|
||||
)),
|
||||
],
|
||||
// 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) {
|
||||
setState(() {
|
||||
_selectedWeapon = value;
|
||||
if (value != null) {
|
||||
_weaponController.text = value.displayName;
|
||||
_shotsPerTarget = value.magazineCapacity;
|
||||
// Auto-set distance based on weapon type if needed
|
||||
_distance = (value.type == WeaponType.handgun) ? 25 : 50;
|
||||
} else {
|
||||
_weaponController.clear();
|
||||
}
|
||||
});
|
||||
if (value != null) {
|
||||
setState(() {
|
||||
_selectedWeapon = value;
|
||||
_updateSettingsForWeapon(value);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Center(child: Text('OU', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold))),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// Weapon field (manual entry if no weapon selected)
|
||||
TextFormField(
|
||||
controller: _weaponController,
|
||||
enabled: _selectedWeapon == null,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Nom de l\'arme',
|
||||
hintText: 'ex: Glock 17, CZ Shadow 2...',
|
||||
prefixIcon: const Icon(Icons.edit),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Veuillez renseigner votre arme';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Distance selector
|
||||
@@ -161,9 +178,9 @@ class _SessionSetupScreenState extends State<SessionSetupScreen> {
|
||||
value: _distance.toDouble(),
|
||||
min: 5,
|
||||
max: 300,
|
||||
divisions: 59, // (300-5)/5 = 59 divisions for 5m steps
|
||||
divisions: 59,
|
||||
label: '${_distance}m',
|
||||
onChanged: (value) {
|
||||
onChanged: _availableWeapons.isEmpty ? null : (value) {
|
||||
setState(() {
|
||||
_distance = value.round();
|
||||
});
|
||||
@@ -190,7 +207,7 @@ class _SessionSetupScreenState extends State<SessionSetupScreen> {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
|
||||
// Shots per target
|
||||
const Text(
|
||||
'Nombre de balles pour la cible actuelle',
|
||||
@@ -203,10 +220,10 @@ class _SessionSetupScreenState extends State<SessionSetupScreen> {
|
||||
child: Slider(
|
||||
value: _shotsPerTarget.toDouble(),
|
||||
min: 1,
|
||||
max: 30,
|
||||
divisions: 29,
|
||||
max: 50,
|
||||
divisions: 49,
|
||||
label: '$_shotsPerTarget',
|
||||
onChanged: (value) {
|
||||
onChanged: _availableWeapons.isEmpty ? null : (value) {
|
||||
setState(() {
|
||||
_shotsPerTarget = value.round();
|
||||
});
|
||||
@@ -233,9 +250,12 @@ class _SessionSetupScreenState extends State<SessionSetupScreen> {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
|
||||
|
||||
ElevatedButton.icon(
|
||||
onPressed: _startSession,
|
||||
// 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,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
@@ -256,4 +276,4 @@ class _SessionSetupScreenState extends State<SessionSetupScreen> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user