Deux retours d'usage, tous deux sur la même carte. 1. « Placer en dernier » poussait « Configurer » hors de sa place. Il rejoint « Renommer » sur une ligne à part : ce sont les deux gestes qui ne touchent pas au mécanisme, et un bouton d'action principale ne se fait pas déplacer par un geste secondaire. 2. Quitter l'écran avec des modifications non enregistrées ne demandait rien — et ce silence cachait pire qu'une perte : le brouillon vit dans LoadConfigProvider, pas dans l'écran, donc il SURVIT à la navigation. On repartait en laissant une configuration modifiée invisible, qui réapparaissait au retour sans contexte, ou disparaissait à la première relecture de la box (LoadConfigChanged, reconnexion) sans que personne ait choisi. Les deux issues sont mauvaises pour la même raison : l'utilisateur ne sait pas ce qu'il laisse derrière lui. GardeBrouillon (PopScope) s'interpose sur les DEUX écrans qui partagent ce brouillon — la liste des charges et l'écran de mécanisme. L'alerte dit l'état de la BOX, pas seulement celui de l'écran : « vos changements n'ont pas été envoyés, elle porte toujours la configuration d'avant ». Deux issues, toutes deux explicites : rester, ou quitter en abandonnant — un brouillon fantôme qui ressort trois écrans plus loin est pire qu'une modification perdue qu'on a accepté de perdre. Aucune écriture n'est proposée depuis la boîte de dialogue : enregistrer depuis une sortie ferait partir une configuration que personne n'a relue. Quatre tests, dont le contrôle négatif — sans brouillon, on sort sans être interrogé. Une alerte qui se déclenche pour rien apprend à passer outre sans lire. 235 tests (+4), flutter analyze inchangé à 27 remarques. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQbZKrWqsMFP1Lh2jjjd9f
74 lines
2.8 KiB
Dart
74 lines
2.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import '../providers/load_config_provider.dart';
|
|
import '../theme/etm_tokens.dart';
|
|
|
|
/// **Quitter un écran avec un brouillon en attente** — demander, jamais décider seul.
|
|
///
|
|
/// ## Pourquoi une alerte, et pas un abandon silencieux
|
|
///
|
|
/// Le brouillon vit dans `LoadConfigProvider`, pas dans l'écran : il **survit** à la
|
|
/// navigation. Quitter sans rien dire laissait donc une configuration modifiée en attente,
|
|
/// invisible, qui réapparaissait au retour — ou disparaissait à la première relecture de la
|
|
/// box (`LoadConfigChanged`, reconnexion), sans que personne ait choisi.
|
|
///
|
|
/// Les deux issues sont mauvaises pour la même raison : **l'utilisateur ne sait pas ce
|
|
/// qu'il laisse derrière lui.** L'alerte rend le choix explicite, et le fait porter à qui
|
|
/// de droit.
|
|
///
|
|
/// ## Ce que chaque bouton fait vraiment
|
|
///
|
|
/// - **Rester** : rien ne bouge, le brouillon reste, l'écran non plus.
|
|
/// - **Quitter sans enregistrer** : le brouillon est **abandonné** (`discardDraft`), et
|
|
/// l'écran se ferme. C'est délibérément destructeur — un brouillon fantôme qui ressort
|
|
/// trois écrans plus loin est pire qu'une modification perdue qu'on a acceptée de perdre.
|
|
///
|
|
/// Aucune écriture n'est proposée ici : enregistrer depuis une boîte de dialogue de sortie
|
|
/// ferait partir une configuration que l'utilisateur n'a pas relue.
|
|
class GardeBrouillon extends StatelessWidget {
|
|
final Widget child;
|
|
|
|
const GardeBrouillon({super.key, required this.child});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final provider = context.watch<LoadConfigProvider>();
|
|
|
|
return PopScope(
|
|
canPop: !provider.hasPendingChanges,
|
|
onPopInvokedWithResult: (didPop, _) async {
|
|
if (didPop) return;
|
|
final partir = await _demander(context);
|
|
if (partir != true || !context.mounted) return;
|
|
provider.discardDraft();
|
|
Navigator.of(context).pop();
|
|
},
|
|
child: child,
|
|
);
|
|
}
|
|
|
|
Future<bool?> _demander(BuildContext context) => showDialog<bool>(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
title: const Text('Modifications non enregistrées'),
|
|
content: const Text(
|
|
'Vos changements n\'ont pas été envoyés à la box : elle porte toujours la '
|
|
'configuration d\'avant.\n\n'
|
|
'Quitter maintenant les abandonne.',
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(ctx, false),
|
|
child: const Text('Rester'),
|
|
),
|
|
FilledButton(
|
|
style: FilledButton.styleFrom(backgroundColor: EtmTokens.danger),
|
|
onPressed: () => Navigator.pop(ctx, true),
|
|
child: const Text('Quitter sans enregistrer'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|