From 8165d3bab3aa04ea46cc13ce7765fa5b00820e37 Mon Sep 17 00:00:00 2001 From: qguillaume Date: Fri, 19 Jun 2026 14:09:46 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=C3=A9dition=20rapide=20des=20accessoir?= =?UTF-8?q?es=20cr=C3=A9ant=20une=20nouvelle=20arme/config=20pour=20diff?= =?UTF-8?q?=C3=A9rencier=20les=20stats?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/features/garage/weapon_detail_screen.dart | 125 +++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/lib/features/garage/weapon_detail_screen.dart b/lib/features/garage/weapon_detail_screen.dart index bfc42430..979e6d4f 100644 --- a/lib/features/garage/weapon_detail_screen.dart +++ b/lib/features/garage/weapon_detail_screen.dart @@ -152,7 +152,24 @@ class _WeaponDetailScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Options & Personnalisation', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Expanded( + child: Text('Options & Personnalisation', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), + ), + IconButton( + tooltip: 'Modifier les accessoires (crée une nouvelle configuration)', + icon: const Icon(Icons.edit, color: Colors.white, size: 18), + style: IconButton.styleFrom( + backgroundColor: AppTheme.primaryColor, + padding: const EdgeInsets.all(8), + minimumSize: const Size(36, 36), + ), + onPressed: () => _showEditAccessoriesDialog(context), + ), + ], + ), const Divider(), _buildInfoRow('Optique / Lunette', _weapon.optic ?? 'Mire fer'), _buildInfoRow('Modérateur / Silencieux', _weapon.silencer ?? 'Aucun'), @@ -374,6 +391,112 @@ class _WeaponDetailScreenState extends State { } } + // Édition rapide des accessoires uniquement. Comme la qualité de tir dépend + // fortement des accessoires, on ne modifie PAS l'arme existante : on crée une + // nouvelle arme (même modèle, accessoires différents) dans l'armurerie afin de + // pouvoir comparer les scores selon la configuration utilisée. + void _showEditAccessoriesDialog(BuildContext context) async { + final opticController = TextEditingController(text: _weapon.optic); + final silencerController = TextEditingController(text: _weapon.silencer); + final triggerController = TextEditingController(text: _weapon.trigger); + final customNameController = TextEditingController(text: _weapon.customName); + + final result = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Modifier les accessoires'), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Une nouvelle arme sera créée dans l\'armurerie avec ces accessoires, ' + 'pour pouvoir comparer les scores selon la configuration.', + style: TextStyle(fontSize: 12, color: Colors.grey), + ), + const SizedBox(height: 12), + _autoScrollOnFocus(TextField( + controller: opticController, + decoration: const InputDecoration(labelText: 'Optique / Lunette', hintText: 'ex: Holosun 507C'), + )), + _autoScrollOnFocus(TextField( + controller: silencerController, + decoration: const InputDecoration(labelText: 'Modérateur / Silencieux'), + )), + _autoScrollOnFocus(TextField( + controller: triggerController, + decoration: const InputDecoration(labelText: 'Détente / Trigger'), + )), + _autoScrollOnFocus(TextField( + controller: customNameController, + decoration: const InputDecoration( + labelText: 'Surnom de la configuration', + hintText: 'ex: Echelon + Holosun', + ), + )), + ], + ), + ), + actions: [ + TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Annuler')), + ElevatedButton(onPressed: () => Navigator.pop(context, true), child: const Text('Créer la configuration')), + ], + ), + ); + + if (result != true) return; + + final newOptic = opticController.text.isEmpty ? null : opticController.text; + final newSilencer = silencerController.text.isEmpty ? null : silencerController.text; + final newTrigger = triggerController.text.isEmpty ? null : triggerController.text; + var newCustomName = customNameController.text.isEmpty ? null : customNameController.text; + + // Rien à faire si aucun accessoire n'a changé. + final unchanged = newOptic == _weapon.optic && + newSilencer == _weapon.silencer && + newTrigger == _weapon.trigger; + if (unchanged) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Aucun accessoire modifié, aucune configuration créée.')), + ); + } + return; + } + + // Génère un surnom par défaut décrivant la config si l'utilisateur n'en a pas saisi. + if (newCustomName == null) { + final accessories = [newOptic, newSilencer, newTrigger] + .where((a) => a != null && a.isNotEmpty) + .join(' / '); + newCustomName = accessories.isEmpty ? null : '${_weapon.name} ($accessories)'; + } + + final repository = context.read(); + final newWeapon = await repository.addWeapon( + name: _weapon.name, + type: _weapon.type, + caliber: _weapon.caliber, + magazineCount: _weapon.magazineCount, + magazineCapacity: _weapon.magazineCapacity, + notes: _weapon.notes, + optic: newOptic, + silencer: newSilencer, + trigger: newTrigger, + customName: newCustomName, + ); + + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Nouvelle configuration créée : ${newWeapon.displayName}')), + ); + // On bascule sur la nouvelle arme pour que les sessions suivantes y soient rattachées. + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (_) => WeaponDetailScreen(weapon: newWeapon)), + ); + } + void _showAddMaintenanceDialog(BuildContext context) async { final descController = TextEditingController(); MaintenanceType selectedType = MaintenanceType.cleaning;