import 'package:flutter/material.dart'; import 'package:tatlock_ui/shared/widgets/entity_page.dart'; /// A reusable dialog wrapper for entity create/edit forms. /// /// Provides consistent styling across all Control Room sections: /// - Constrained max width/height /// - AppBar with title and close button /// - Scrollable content area /// /// Usage: /// ```dart /// showEntityFormDialog( /// context: context, /// title: 'New Proxy Host', /// child: ProxyHostForm( /// onCancel: () => Navigator.of(context).pop(), /// onSaved: () { /// Navigator.of(context).pop(); /// ref.invalidate(domainsProvider); /// }, /// ), /// ); /// ``` void showEntityFormDialog({ required BuildContext context, required String title, required Widget child, double maxWidth = 600, double maxHeight = 700, }) { showDialog( context: context, builder: (context) => EntityFormDialog( title: title, maxWidth: maxWidth, maxHeight: maxHeight, child: child, ), ); } /// Dialog widget for entity forms. class EntityFormDialog extends StatelessWidget { const EntityFormDialog({ super.key, required this.title, required this.child, this.maxWidth = 600, this.maxHeight = 700, }); final String title; final Widget child; final double maxWidth; final double maxHeight; @override Widget build(BuildContext context) { return Dialog( child: ConstrainedBox( constraints: BoxConstraints( maxWidth: maxWidth, maxHeight: maxHeight, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ AppBar( title: Text(title), automaticallyImplyLeading: false, actions: [ IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.of(context).pop(), ), ], ), Flexible(child: child), ], ), ), ); } } /// A standardized form wrapper with common patterns. /// /// Handles: /// - Error display banner /// - Loading state on save button /// - Cancel/Save button row /// - Form key management /// - Create vs Edit mode awareness /// /// Use [mode] to indicate whether this is a create or edit form. /// Child widgets can use [EntityFormScope.of(context)] to check the mode /// and disable fields that should only be editable during creation. class EntityForm extends StatelessWidget { const EntityForm({ super.key, required this.formKey, required this.onCancel, required this.onSave, required this.isSaving, required this.children, this.mode = EntityPageMode.create, this.error, this.saveLabel, this.cancelLabel = 'Cancel', }); final GlobalKey formKey; final VoidCallback onCancel; final VoidCallback onSave; final bool isSaving; final List children; final EntityPageMode mode; final String? error; final String? saveLabel; final String cancelLabel; bool get isCreating => mode == EntityPageMode.create; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final effectiveSaveLabel = saveLabel ?? (isCreating ? 'Create' : 'Save'); return EntityFormScope( mode: mode, child: Form( key: formKey, child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Error banner if (error != null) Container( padding: const EdgeInsets.all(12), margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: colorScheme.errorContainer, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon(Icons.error, color: colorScheme.onErrorContainer), const SizedBox(width: 8), Expanded( child: Text( error!, style: TextStyle(color: colorScheme.onErrorContainer), ), ), ], ), ), // Form fields ...children, const SizedBox(height: 32), // Action buttons Row( mainAxisAlignment: MainAxisAlignment.end, children: [ OutlinedButton( onPressed: isSaving ? null : onCancel, child: Text(cancelLabel), ), const SizedBox(width: 12), FilledButton.icon( onPressed: isSaving ? null : onSave, icon: isSaving ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.save), label: Text(effectiveSaveLabel), ), ], ), ], ), ), ), ); } } /// InheritedWidget to provide form mode to descendants. /// /// Allows form fields to check if they're in create or edit mode /// and adjust their behavior accordingly (e.g., disable create-only fields). class EntityFormScope extends InheritedWidget { const EntityFormScope({ super.key, required this.mode, required super.child, }); final EntityPageMode mode; bool get isCreating => mode == EntityPageMode.create; bool get isEditing => mode == EntityPageMode.edit; static EntityFormScope? maybeOf(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(); } static EntityFormScope of(BuildContext context) { final scope = maybeOf(context); assert(scope != null, 'No EntityFormScope found in context'); return scope!; } @override bool updateShouldNotify(EntityFormScope oldWidget) => mode != oldWidget.mode; } /// A form field wrapper that can be marked as create-only. /// /// When [createOnly] is true, the field will be disabled in edit mode. /// Shows a lock icon and tooltip to indicate the field is immutable. /// /// Usage: /// ```dart /// CreateOnlyField( /// createOnly: true, /// child: TextFormField( /// controller: _nameController, /// decoration: InputDecoration(labelText: 'Name'), /// ), /// ) /// ``` class CreateOnlyField extends StatelessWidget { const CreateOnlyField({ super.key, required this.child, this.createOnly = true, this.disabledHint = 'This field cannot be changed after creation', }); final Widget child; final bool createOnly; final String disabledHint; @override Widget build(BuildContext context) { final scope = EntityFormScope.maybeOf(context); final isEditing = scope?.isEditing ?? false; final shouldDisable = createOnly && isEditing; if (!shouldDisable) { return child; } return Tooltip( message: disabledHint, child: AbsorbPointer( absorbing: true, child: Opacity( opacity: 0.6, child: Stack( children: [ child, Positioned( right: 8, top: 8, child: Icon( Icons.lock, size: 16, color: Theme.of(context).colorScheme.outline, ), ), ], ), ), ), ); } }