import 'package:flutter/material.dart'; import '../services/rpc_schema.dart'; import '../theme/etm_tokens.dart'; /// Enveloppe un champ dont la disponibilité vient du **schéma de la box**. /// /// ## Un champ grisé sans raison se lit comme un bug /// /// C'est la règle qui justifie ce widget. « Inactif » tout court laisse l'installateur /// devant trois hypothèses également plausibles — panne, droit manquant, ou fonction /// absente — et il choisira la mauvaise. On affiche donc **toujours** la raison, et le /// nom du champ manquant quand on l'a : c'est ce nom qui rend le rapport actionnable. /// /// ## Trois états, pas deux /// /// - [FieldSupport.writable] — actif, l'enfant est rendu tel quel ; /// - [FieldSupport.readOnly] — le champ existe et se lit, mais l'écrire est une erreur : /// c'est une **mesure**, pas une fonction manquante ; /// - [FieldSupport.absent] — cette box ne l'expose pas ; /// - [FieldSupport.unknown] — le schéma n'a pas pu être lu ; on ne prétend ni l'un ni /// l'autre. class SchemaGated extends StatelessWidget { /// Ce que le schéma dit du champ. final FieldSupport support; /// Nom du champ tel qu'il apparaîtrait au schéma (`endDateTime`, `minCurrent`…). /// Affiché dans la raison — c'est lui qui permet de remonter un manque utilement. final String fieldName; /// Le champ actif. final Widget child; /// Libellé humain, pour la ligne de raison. final String label; const SchemaGated({ super.key, required this.support, required this.fieldName, required this.label, required this.child, }); @override Widget build(BuildContext context) { if (support.isWritable) return child; final muted = EtmTokens.mutedOf(context); return Opacity( opacity: 0.55, child: IgnorePointer( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ child, const SizedBox(height: 4), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(_icon, size: 13, color: muted), const SizedBox(width: 5), Expanded( child: Text( _reason, style: TextStyle(fontSize: 11.5, color: muted, height: 1.3), ), ), ], ), ], ), ), ); } IconData get _icon => switch (support) { FieldSupport.readOnly => Icons.visibility_outlined, FieldSupport.absent => Icons.cloud_off_outlined, _ => Icons.help_outline, }; String get _reason => switch (support) { FieldSupport.readOnly => 'Lecture seule — « $fieldName » est une mesure publiée par la box, ' 'elle ne se règle pas ici.', FieldSupport.absent => 'Cette box ne l\'expose pas encore — champ « $fieldName » absent de son ' 'schéma. Le réglage s\'activera seul après mise à jour du moteur.', _ => 'Disponibilité inconnue — le schéma de la box n\'a pas pu être lu. ' '« $fieldName » est laissé inactif par prudence.', }; } /// Bandeau d'en-tête annonçant sur quoi l'écran s'appuie. /// /// Dès qu'un écran se comporte différemment selon la version, il faut pouvoir dire /// **laquelle on regarde** — sans quoi un rapport de terrain (« le champ est gris chez /// moi ») n'est pas exploitable. class SchemaVersionBanner extends StatelessWidget { /// Ligne de version déjà composée (`SchemaProvider.versionLine()`). final String versionLine; /// Faux si le schéma n'a pas pu être lu — le bandeau le dit alors franchement. final bool schemaLoaded; const SchemaVersionBanner({ super.key, required this.versionLine, required this.schemaLoaded, }); @override Widget build(BuildContext context) { final muted = EtmTokens.mutedOf(context); return Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9), decoration: BoxDecoration( color: EtmTokens.surfaceOf(context).withValues(alpha: 0.6), borderRadius: BorderRadius.circular(EtmTokens.radiusCard), border: Border.all(color: EtmTokens.lineOf(context)), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(schemaLoaded ? Icons.memory : Icons.help_outline, size: 15, color: muted), const SizedBox(width: 8), Expanded( child: Text( schemaLoaded ? '$versionLine — les réglages disponibles sont déduits du schéma ' 'publié par cette box.' : '$versionLine — schéma illisible : les réglages sont laissés ' 'inactifs par prudence, pas parce qu\'ils manquent.', style: TextStyle(fontSize: 11.5, color: muted, height: 1.35), ), ), ], ), ); } }