import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/material.dart'; import '../../Services/navigation_service.dart'; import '../../Services/theme_service.dart'; import '../Misc/InputFields/dynamic_date_time_field.dart'; import 'dialog_action.dart'; import 'dialog_input_field.dart'; import 'dialog_input_field_select_item.dart'; import 'dialog_input_field_type_enum.dart'; import 'dialog_type_enum.dart'; /// Erstellt einen neuen dynamischen Dialog class DynamicDialog { /// Erstellt eine neue Instanz dieser Klasse DynamicDialog({ this.title, this.icon, this.content, final List? inputFields, final List? actions, this.backgroundColor, this.borderRadius = 16, this.barrierDismissible = true, this.dialogType = DialogTypeEnum.info, this.hiddenValues, }) : inputFields = inputFields ?? const [], actions = actions ?? [DialogAction(label: 'Schließen')], _values = hiddenValues ?? {}; /// Der Titel des Dialogs final String? title; /// Der Icon des Dialogs final IconData? icon; /// Der Inhalt des Dialogs final Widget? content; /// Die Hintergrundfarbe des Dialogs, Standard wenn nicht gesetzt final Color? backgroundColor; /// Der BorderRadius des Dialogs final double borderRadius; /// Ob der Dialog bei einem Klick auf die Barriere geschlossen werden kann final bool barrierDismissible; /// Die InputFelder des Dialogs final List inputFields; /// Die Aktionen des Dialogs final List actions; /// Der Typ des Dialogs final DialogTypeEnum dialogType; /// Versteckte Werte, die beim Abschicken mit zurückgegeben werden final Map? hiddenValues; final Map _values; final Map _controllers = {}; BuildContext? _dialogContext; /// Zeigt den vorher zusammengebauten Dialog an Future show() async { final BuildContext? context = NavigationService.getCurrentBuildContext(); if (context != null) { final ThemeData theme = Theme.of(context); await showDialog( context: context, barrierDismissible: barrierDismissible, builder: (final BuildContext ctx) { _dialogContext = ctx; final DialogAction primaryAction = actions.firstWhere( (final a) => a.isPrimary, orElse: () => actions.first, ); Color backgroundColor = this.backgroundColor ?? theme.colorScheme.surface; if (dialogType == DialogTypeEnum.error) { backgroundColor = theme.colorScheme.errorContainer; } else if (dialogType == DialogTypeEnum.success) { backgroundColor = ThemeService.getSuccessColor( brightness: theme.brightness, ); } return SingleChildScrollView( child: AlertDialog( backgroundColor: backgroundColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(borderRadius), ), title: Column( mainAxisSize: MainAxisSize.min, children: [ if (icon != null) Icon(icon, size: 48, color: theme.colorScheme.primary), if (title != null) Padding( padding: const EdgeInsets.only(top: 8), child: Text( title!, style: theme.textTheme.titleLarge, textAlign: TextAlign.center, ), ), ], ), content: ConstrainedBox( constraints: BoxConstraints( maxHeight: MediaQuery.of(ctx).size.height * 0.7, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ if (content != null) content!, ...inputFields.map( (final DialogInputField field) => Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: _getInputField(field, primaryAction), ), ), ], ), ), actions: actions.map(_getAction).toList(), ), ); }, ); } } /// Schließt den dynamischen Dialog void close() { if (_dialogContext != null) { Navigator.of(_dialogContext!).pop(); } _values.clear(); _controllers.clear(); } void _submit(final DialogAction action) { final Map values = {} ..addEntries(_values.entries); _controllers.forEach(( final String id, final TextEditingController controller, ) { values[id] = controller.text; }); close(); action.onPressed?.call(values); } Widget _getInputField( final DialogInputField inputField, final DialogAction primaryAction, ) { if (_values[inputField.id] == null) { _values[inputField.id] = inputField.initialValue; } if (inputField.inputType == DialogInputFieldTypeEnum.date) { return DynamicDateTimeField( initialValue: _values[inputField.id], autofocus: inputField.autoFocus, onChanged: (final value) { inputField.onChanged?.call(value); _values[inputField.id] = value; }, decoration: InputDecoration( labelText: inputField.label, border: const OutlineInputBorder(), isDense: true, ), ); } else if (inputField.inputType == DialogInputFieldTypeEnum.select) { if (_values[inputField.id] is Enum) { final Enum inputFieldInitialValue = _values[inputField.id]; for (final DialogInputFieldSelectItem value in inputField.selectItems) { if (value.id == inputFieldInitialValue.index) { _values[inputField.id] = value; } } } return DropdownSearch( items: (final f, final cs) => inputField.selectItems, itemAsString: (final DialogInputFieldSelectItem value) => value.value, selectedItem: _values[inputField.id], onChanged: (final DialogInputFieldSelectItem? value) { inputField.onChanged?.call(value); _values[inputField.id] = value; }, decoratorProps: DropDownDecoratorProps( decoration: InputDecoration( labelText: inputField.label, border: const OutlineInputBorder(), isDense: true, ), ), compareFn: ( final DialogInputFieldSelectItem v1, final DialogInputFieldSelectItem v2, ) => v1.id == v2.id, ); } else { if (_controllers[inputField.id] == null) { _controllers[inputField.id] = TextEditingController( text: (_values[inputField.id] ?? '').toString() ); } return TextField( controller: _controllers[inputField.id], autofocus: inputField.autoFocus, keyboardType: inputField.keyboardType, obscureText: inputField.obscureText, onChanged: (final value) { inputField.onChanged?.call(value); _values[inputField.id] = value; }, decoration: InputDecoration( labelText: inputField.label, border: const OutlineInputBorder(), isDense: true, ), onSubmitted: (_) { _submit(primaryAction); }, ); } } Widget _getAction(final DialogAction action) { if (action.isPrimary) { return ElevatedButton( onPressed: () { _submit(action); }, child: Text(action.label), ); } else { return TextButton( onPressed: () { _submit(action); }, child: Text(action.label), ); } } }