mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-11-03 14:53:28 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			549 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			549 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'dart:math';
 | 
						|
 | 
						|
import 'package:hsluv/hsluv.dart';
 | 
						|
import 'package:easy_localization/easy_localization.dart';
 | 
						|
import 'package:flutter/material.dart';
 | 
						|
import 'package:obtainium/components/generated_form_modal.dart';
 | 
						|
 | 
						|
abstract class GeneratedFormItem {
 | 
						|
  late String key;
 | 
						|
  late String label;
 | 
						|
  late List<Widget> belowWidgets;
 | 
						|
  late dynamic defaultValue;
 | 
						|
  List<dynamic> additionalValidators;
 | 
						|
  dynamic ensureType(dynamic val);
 | 
						|
 | 
						|
  GeneratedFormItem(this.key,
 | 
						|
      {this.label = 'Input',
 | 
						|
      this.belowWidgets = const [],
 | 
						|
      this.defaultValue,
 | 
						|
      this.additionalValidators = const []});
 | 
						|
}
 | 
						|
 | 
						|
class GeneratedFormTextField extends GeneratedFormItem {
 | 
						|
  late bool required;
 | 
						|
  late int max;
 | 
						|
  late String? hint;
 | 
						|
  late bool password;
 | 
						|
  late TextInputType? textInputType;
 | 
						|
 | 
						|
  GeneratedFormTextField(String key,
 | 
						|
      {String label = 'Input',
 | 
						|
      List<Widget> belowWidgets = const [],
 | 
						|
      String defaultValue = '',
 | 
						|
      List<String? Function(String? value)> additionalValidators = const [],
 | 
						|
      this.required = true,
 | 
						|
      this.max = 1,
 | 
						|
      this.hint,
 | 
						|
      this.password = false,
 | 
						|
      this.textInputType})
 | 
						|
      : super(key,
 | 
						|
            label: label,
 | 
						|
            belowWidgets: belowWidgets,
 | 
						|
            defaultValue: defaultValue,
 | 
						|
            additionalValidators: additionalValidators);
 | 
						|
 | 
						|
  @override
 | 
						|
  String ensureType(val) {
 | 
						|
    return val.toString();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class GeneratedFormDropdown extends GeneratedFormItem {
 | 
						|
  late List<MapEntry<String, String>>? opts;
 | 
						|
  List<String>? disabledOptKeys;
 | 
						|
 | 
						|
  GeneratedFormDropdown(
 | 
						|
    String key,
 | 
						|
    this.opts, {
 | 
						|
    String label = 'Input',
 | 
						|
    List<Widget> belowWidgets = const [],
 | 
						|
    String defaultValue = '',
 | 
						|
    this.disabledOptKeys,
 | 
						|
    List<String? Function(String? value)> additionalValidators = const [],
 | 
						|
  }) : super(key,
 | 
						|
            label: label,
 | 
						|
            belowWidgets: belowWidgets,
 | 
						|
            defaultValue: defaultValue,
 | 
						|
            additionalValidators: additionalValidators);
 | 
						|
 | 
						|
  @override
 | 
						|
  String ensureType(val) {
 | 
						|
    return val.toString();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class GeneratedFormSwitch extends GeneratedFormItem {
 | 
						|
  GeneratedFormSwitch(
 | 
						|
    String key, {
 | 
						|
    String label = 'Input',
 | 
						|
    List<Widget> belowWidgets = const [],
 | 
						|
    bool defaultValue = false,
 | 
						|
    List<String? Function(bool value)> additionalValidators = const [],
 | 
						|
  }) : super(key,
 | 
						|
            label: label,
 | 
						|
            belowWidgets: belowWidgets,
 | 
						|
            defaultValue: defaultValue,
 | 
						|
            additionalValidators: additionalValidators);
 | 
						|
 | 
						|
  @override
 | 
						|
  bool ensureType(val) {
 | 
						|
    return val == true || val == 'true';
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class GeneratedFormTagInput extends GeneratedFormItem {
 | 
						|
  late MapEntry<String, String>? deleteConfirmationMessage;
 | 
						|
  late bool singleSelect;
 | 
						|
  late WrapAlignment alignment;
 | 
						|
  late String emptyMessage;
 | 
						|
  late bool showLabelWhenNotEmpty;
 | 
						|
  GeneratedFormTagInput(String key,
 | 
						|
      {String label = 'Input',
 | 
						|
      List<Widget> belowWidgets = const [],
 | 
						|
      Map<String, MapEntry<int, bool>> defaultValue = const {},
 | 
						|
      List<String? Function(Map<String, MapEntry<int, bool>> value)>
 | 
						|
          additionalValidators = const [],
 | 
						|
      this.deleteConfirmationMessage,
 | 
						|
      this.singleSelect = false,
 | 
						|
      this.alignment = WrapAlignment.start,
 | 
						|
      this.emptyMessage = 'Input',
 | 
						|
      this.showLabelWhenNotEmpty = true})
 | 
						|
      : super(key,
 | 
						|
            label: label,
 | 
						|
            belowWidgets: belowWidgets,
 | 
						|
            defaultValue: defaultValue,
 | 
						|
            additionalValidators: additionalValidators);
 | 
						|
 | 
						|
  @override
 | 
						|
  Map<String, MapEntry<int, bool>> ensureType(val) {
 | 
						|
    return val is Map<String, MapEntry<int, bool>> ? val : {};
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
typedef OnValueChanges = void Function(
 | 
						|
    Map<String, dynamic> values, bool valid, bool isBuilding);
 | 
						|
 | 
						|
class GeneratedForm extends StatefulWidget {
 | 
						|
  const GeneratedForm(
 | 
						|
      {super.key, required this.items, required this.onValueChanges});
 | 
						|
 | 
						|
  final List<List<GeneratedFormItem>> items;
 | 
						|
  final OnValueChanges onValueChanges;
 | 
						|
 | 
						|
  @override
 | 
						|
  State<GeneratedForm> createState() => _GeneratedFormState();
 | 
						|
}
 | 
						|
 | 
						|
// Generates a color in the HSLuv (Pastel) color space
 | 
						|
// https://pub.dev/documentation/hsluv/latest/hsluv/Hsluv/hpluvToRgb.html
 | 
						|
Color generateRandomLightColor() {
 | 
						|
  final randomSeed = Random().nextInt(120);
 | 
						|
  // https://en.wikipedia.org/wiki/Golden_angle
 | 
						|
  final goldenAngle = 180 * (3 - sqrt(5));
 | 
						|
  // Generate next golden angle hue
 | 
						|
  final double hue = randomSeed * goldenAngle;
 | 
						|
  // Map from HPLuv color space to RGB, use constant saturation=100, lightness=70
 | 
						|
  final List<double> rgbValuesDbl = Hsluv.hpluvToRgb([hue, 100, 70]);
 | 
						|
  // Map RBG values from 0-1 to 0-255:
 | 
						|
  final List<int> rgbValues =
 | 
						|
      rgbValuesDbl.map((rgb) => (rgb * 255).toInt()).toList();
 | 
						|
  return Color.fromARGB(255, rgbValues[0], rgbValues[1], rgbValues[2]);
 | 
						|
}
 | 
						|
 | 
						|
class _GeneratedFormState extends State<GeneratedForm> {
 | 
						|
  final _formKey = GlobalKey<FormState>();
 | 
						|
  Map<String, dynamic> values = {};
 | 
						|
  late List<List<Widget>> formInputs;
 | 
						|
  List<List<Widget>> rows = [];
 | 
						|
  String? initKey;
 | 
						|
 | 
						|
  // If any value changes, call this to update the parent with value and validity
 | 
						|
  void someValueChanged({bool isBuilding = false}) {
 | 
						|
    Map<String, dynamic> returnValues = values;
 | 
						|
    var valid = true;
 | 
						|
    for (int r = 0; r < widget.items.length; r++) {
 | 
						|
      for (int i = 0; i < widget.items[r].length; i++) {
 | 
						|
        if (formInputs[r][i] is TextFormField) {
 | 
						|
          var fieldState =
 | 
						|
              (formInputs[r][i].key as GlobalKey<FormFieldState>).currentState;
 | 
						|
          if (fieldState != null) {
 | 
						|
            valid = valid && fieldState.isValid;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
    widget.onValueChanges(returnValues, valid, isBuilding);
 | 
						|
  }
 | 
						|
 | 
						|
  initForm() {
 | 
						|
    initKey = widget.key.toString();
 | 
						|
    // Initialize form values as all empty
 | 
						|
    values.clear();
 | 
						|
    for (var row in widget.items) {
 | 
						|
      for (var e in row) {
 | 
						|
        values[e.key] = e.defaultValue;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // Dynamically create form inputs
 | 
						|
    formInputs = widget.items.asMap().entries.map((row) {
 | 
						|
      return row.value.asMap().entries.map((e) {
 | 
						|
        var formItem = e.value;
 | 
						|
        if (formItem is GeneratedFormTextField) {
 | 
						|
          final formFieldKey = GlobalKey<FormFieldState>();
 | 
						|
          return TextFormField(
 | 
						|
            keyboardType: formItem.textInputType,
 | 
						|
            obscureText: formItem.password,
 | 
						|
            autocorrect: !formItem.password,
 | 
						|
            enableSuggestions: !formItem.password,
 | 
						|
            key: formFieldKey,
 | 
						|
            initialValue: values[formItem.key],
 | 
						|
            autovalidateMode: AutovalidateMode.onUserInteraction,
 | 
						|
            onChanged: (value) {
 | 
						|
              setState(() {
 | 
						|
                values[formItem.key] = value;
 | 
						|
                someValueChanged();
 | 
						|
              });
 | 
						|
            },
 | 
						|
            decoration: InputDecoration(
 | 
						|
                helperText: formItem.label + (formItem.required ? ' *' : ''),
 | 
						|
                hintText: formItem.hint),
 | 
						|
            minLines: formItem.max <= 1 ? null : formItem.max,
 | 
						|
            maxLines: formItem.max <= 1 ? 1 : formItem.max,
 | 
						|
            validator: (value) {
 | 
						|
              if (formItem.required &&
 | 
						|
                  (value == null || value.trim().isEmpty)) {
 | 
						|
                return '${formItem.label} ${tr('requiredInBrackets')}';
 | 
						|
              }
 | 
						|
              for (var validator in formItem.additionalValidators) {
 | 
						|
                String? result = validator(value);
 | 
						|
                if (result != null) {
 | 
						|
                  return result;
 | 
						|
                }
 | 
						|
              }
 | 
						|
              return null;
 | 
						|
            },
 | 
						|
          );
 | 
						|
        } else if (formItem is GeneratedFormDropdown) {
 | 
						|
          if (formItem.opts!.isEmpty) {
 | 
						|
            return Text(tr('dropdownNoOptsError'));
 | 
						|
          }
 | 
						|
          return DropdownButtonFormField(
 | 
						|
              decoration: InputDecoration(labelText: formItem.label),
 | 
						|
              value: values[formItem.key],
 | 
						|
              items: formItem.opts!.map((e2) {
 | 
						|
                var enabled =
 | 
						|
                    formItem.disabledOptKeys?.contains(e2.key) != true;
 | 
						|
                return DropdownMenuItem(
 | 
						|
                    value: e2.key,
 | 
						|
                    enabled: enabled,
 | 
						|
                    child: Opacity(
 | 
						|
                        opacity: enabled ? 1 : 0.5, child: Text(e2.value)));
 | 
						|
              }).toList(),
 | 
						|
              onChanged: (value) {
 | 
						|
                setState(() {
 | 
						|
                  values[formItem.key] = value ?? formItem.opts!.first.key;
 | 
						|
                  someValueChanged();
 | 
						|
                });
 | 
						|
              });
 | 
						|
        } else {
 | 
						|
          return Container(); // Some input types added in build
 | 
						|
        }
 | 
						|
      }).toList();
 | 
						|
    }).toList();
 | 
						|
    someValueChanged(isBuilding: true);
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  void initState() {
 | 
						|
    super.initState();
 | 
						|
    initForm();
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    if (widget.key.toString() != initKey) {
 | 
						|
      initForm();
 | 
						|
    }
 | 
						|
    for (var r = 0; r < formInputs.length; r++) {
 | 
						|
      for (var e = 0; e < formInputs[r].length; e++) {
 | 
						|
        if (widget.items[r][e] is GeneratedFormSwitch) {
 | 
						|
          formInputs[r][e] = Row(
 | 
						|
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
						|
            children: [
 | 
						|
              Flexible(child: Text(widget.items[r][e].label)),
 | 
						|
              const SizedBox(
 | 
						|
                width: 8,
 | 
						|
              ),
 | 
						|
              Switch(
 | 
						|
                  value: values[widget.items[r][e].key],
 | 
						|
                  onChanged: (value) {
 | 
						|
                    setState(() {
 | 
						|
                      values[widget.items[r][e].key] = value;
 | 
						|
                      someValueChanged();
 | 
						|
                    });
 | 
						|
                  })
 | 
						|
            ],
 | 
						|
          );
 | 
						|
        } else if (widget.items[r][e] is GeneratedFormTagInput) {
 | 
						|
          formInputs[r][e] =
 | 
						|
              Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
 | 
						|
            if ((values[widget.items[r][e].key]
 | 
						|
                            as Map<String, MapEntry<int, bool>>?)
 | 
						|
                        ?.isNotEmpty ==
 | 
						|
                    true &&
 | 
						|
                (widget.items[r][e] as GeneratedFormTagInput)
 | 
						|
                    .showLabelWhenNotEmpty)
 | 
						|
              Column(
 | 
						|
                crossAxisAlignment:
 | 
						|
                    (widget.items[r][e] as GeneratedFormTagInput).alignment ==
 | 
						|
                            WrapAlignment.center
 | 
						|
                        ? CrossAxisAlignment.center
 | 
						|
                        : CrossAxisAlignment.stretch,
 | 
						|
                children: [
 | 
						|
                  Text(widget.items[r][e].label),
 | 
						|
                  const SizedBox(
 | 
						|
                    height: 8,
 | 
						|
                  ),
 | 
						|
                ],
 | 
						|
              ),
 | 
						|
            Wrap(
 | 
						|
              alignment:
 | 
						|
                  (widget.items[r][e] as GeneratedFormTagInput).alignment,
 | 
						|
              crossAxisAlignment: WrapCrossAlignment.center,
 | 
						|
              children: [
 | 
						|
                (values[widget.items[r][e].key]
 | 
						|
                                as Map<String, MapEntry<int, bool>>?)
 | 
						|
                            ?.isEmpty ==
 | 
						|
                        true
 | 
						|
                    ? Text(
 | 
						|
                        (widget.items[r][e] as GeneratedFormTagInput)
 | 
						|
                            .emptyMessage,
 | 
						|
                      )
 | 
						|
                    : const SizedBox.shrink(),
 | 
						|
                ...(values[widget.items[r][e].key]
 | 
						|
                            as Map<String, MapEntry<int, bool>>?)
 | 
						|
                        ?.entries
 | 
						|
                        .map((e2) {
 | 
						|
                      return Padding(
 | 
						|
                          padding: const EdgeInsets.symmetric(horizontal: 4),
 | 
						|
                          child: ChoiceChip(
 | 
						|
                            label: Text(e2.key),
 | 
						|
                            backgroundColor: Color(e2.value.key).withAlpha(50),
 | 
						|
                            selectedColor: Color(e2.value.key),
 | 
						|
                            visualDensity: VisualDensity.compact,
 | 
						|
                            selected: e2.value.value,
 | 
						|
                            onSelected: (value) {
 | 
						|
                              setState(() {
 | 
						|
                                (values[widget.items[r][e].key] as Map<String,
 | 
						|
                                        MapEntry<int, bool>>)[e2.key] =
 | 
						|
                                    MapEntry(
 | 
						|
                                        (values[widget.items[r][e].key] as Map<
 | 
						|
                                                String,
 | 
						|
                                                MapEntry<int, bool>>)[e2.key]!
 | 
						|
                                            .key,
 | 
						|
                                        value);
 | 
						|
                                if ((widget.items[r][e]
 | 
						|
                                            as GeneratedFormTagInput)
 | 
						|
                                        .singleSelect &&
 | 
						|
                                    value == true) {
 | 
						|
                                  for (var key in (values[
 | 
						|
                                              widget.items[r][e].key]
 | 
						|
                                          as Map<String, MapEntry<int, bool>>)
 | 
						|
                                      .keys) {
 | 
						|
                                    if (key != e2.key) {
 | 
						|
                                      (values[widget.items[r][e].key] as Map<
 | 
						|
                                              String,
 | 
						|
                                              MapEntry<int, bool>>)[key] =
 | 
						|
                                          MapEntry(
 | 
						|
                                              (values[widget.items[r][e].key]
 | 
						|
                                                      as Map<
 | 
						|
                                                          String,
 | 
						|
                                                          MapEntry<int,
 | 
						|
                                                              bool>>)[key]!
 | 
						|
                                                  .key,
 | 
						|
                                              false);
 | 
						|
                                    }
 | 
						|
                                  }
 | 
						|
                                }
 | 
						|
                                someValueChanged();
 | 
						|
                              });
 | 
						|
                            },
 | 
						|
                          ));
 | 
						|
                    }) ??
 | 
						|
                    [const SizedBox.shrink()],
 | 
						|
                (values[widget.items[r][e].key]
 | 
						|
                                as Map<String, MapEntry<int, bool>>?)
 | 
						|
                            ?.values
 | 
						|
                            .where((e) => e.value)
 | 
						|
                            .length ==
 | 
						|
                        1
 | 
						|
                    ? Padding(
 | 
						|
                        padding: const EdgeInsets.symmetric(horizontal: 4),
 | 
						|
                        child: IconButton(
 | 
						|
                          onPressed: () {
 | 
						|
                            setState(() {
 | 
						|
                              var temp = values[widget.items[r][e].key]
 | 
						|
                                  as Map<String, MapEntry<int, bool>>;
 | 
						|
                              // get selected category str where bool is true
 | 
						|
                              final oldEntry = temp.entries
 | 
						|
                                  .firstWhere((entry) => entry.value.value);
 | 
						|
                              // generate new color, ensure it is not the same
 | 
						|
                              int newColor = oldEntry.value.key;
 | 
						|
                              while (oldEntry.value.key == newColor) {
 | 
						|
                                newColor = generateRandomLightColor().value;
 | 
						|
                              }
 | 
						|
                              // Update entry with new color, remain selected
 | 
						|
                              temp.update(oldEntry.key,
 | 
						|
                                  (old) => MapEntry(newColor, old.value));
 | 
						|
                              values[widget.items[r][e].key] = temp;
 | 
						|
                              someValueChanged();
 | 
						|
                            });
 | 
						|
                          },
 | 
						|
                          icon: const Icon(Icons.format_color_fill_rounded),
 | 
						|
                          visualDensity: VisualDensity.compact,
 | 
						|
                          tooltip: tr('colour'),
 | 
						|
                        ))
 | 
						|
                    : const SizedBox.shrink(),
 | 
						|
                (values[widget.items[r][e].key]
 | 
						|
                                as Map<String, MapEntry<int, bool>>?)
 | 
						|
                            ?.values
 | 
						|
                            .where((e) => e.value)
 | 
						|
                            .isNotEmpty ==
 | 
						|
                        true
 | 
						|
                    ? Padding(
 | 
						|
                        padding: const EdgeInsets.symmetric(horizontal: 4),
 | 
						|
                        child: IconButton(
 | 
						|
                          onPressed: () {
 | 
						|
                            fn() {
 | 
						|
                              setState(() {
 | 
						|
                                var temp = values[widget.items[r][e].key]
 | 
						|
                                    as Map<String, MapEntry<int, bool>>;
 | 
						|
                                temp.removeWhere((key, value) => value.value);
 | 
						|
                                values[widget.items[r][e].key] = temp;
 | 
						|
                                someValueChanged();
 | 
						|
                              });
 | 
						|
                            }
 | 
						|
 | 
						|
                            if ((widget.items[r][e] as GeneratedFormTagInput)
 | 
						|
                                    .deleteConfirmationMessage !=
 | 
						|
                                null) {
 | 
						|
                              var message =
 | 
						|
                                  (widget.items[r][e] as GeneratedFormTagInput)
 | 
						|
                                      .deleteConfirmationMessage!;
 | 
						|
                              showDialog<Map<String, dynamic>?>(
 | 
						|
                                  context: context,
 | 
						|
                                  builder: (BuildContext ctx) {
 | 
						|
                                    return GeneratedFormModal(
 | 
						|
                                        title: message.key,
 | 
						|
                                        message: message.value,
 | 
						|
                                        items: const []);
 | 
						|
                                  }).then((value) {
 | 
						|
                                if (value != null) {
 | 
						|
                                  fn();
 | 
						|
                                }
 | 
						|
                              });
 | 
						|
                            } else {
 | 
						|
                              fn();
 | 
						|
                            }
 | 
						|
                          },
 | 
						|
                          icon: const Icon(Icons.remove),
 | 
						|
                          visualDensity: VisualDensity.compact,
 | 
						|
                          tooltip: tr('remove'),
 | 
						|
                        ))
 | 
						|
                    : const SizedBox.shrink(),
 | 
						|
                Padding(
 | 
						|
                    padding: const EdgeInsets.symmetric(horizontal: 4),
 | 
						|
                    child: IconButton(
 | 
						|
                      onPressed: () {
 | 
						|
                        showDialog<Map<String, dynamic>?>(
 | 
						|
                            context: context,
 | 
						|
                            builder: (BuildContext ctx) {
 | 
						|
                              return GeneratedFormModal(
 | 
						|
                                  title: widget.items[r][e].label,
 | 
						|
                                  items: [
 | 
						|
                                    [
 | 
						|
                                      GeneratedFormTextField('label',
 | 
						|
                                          label: tr('label'))
 | 
						|
                                    ]
 | 
						|
                                  ]);
 | 
						|
                            }).then((value) {
 | 
						|
                          String? label = value?['label'];
 | 
						|
                          if (label != null) {
 | 
						|
                            setState(() {
 | 
						|
                              var temp = values[widget.items[r][e].key]
 | 
						|
                                  as Map<String, MapEntry<int, bool>>?;
 | 
						|
                              temp ??= {};
 | 
						|
                              if (temp[label] == null) {
 | 
						|
                                var singleSelect = (widget.items[r][e]
 | 
						|
                                        as GeneratedFormTagInput)
 | 
						|
                                    .singleSelect;
 | 
						|
                                var someSelected = temp.entries
 | 
						|
                                    .where((element) => element.value.value)
 | 
						|
                                    .isNotEmpty;
 | 
						|
                                temp[label] = MapEntry(
 | 
						|
                                    generateRandomLightColor().value,
 | 
						|
                                    !(someSelected && singleSelect));
 | 
						|
                                values[widget.items[r][e].key] = temp;
 | 
						|
                                someValueChanged();
 | 
						|
                              }
 | 
						|
                            });
 | 
						|
                          }
 | 
						|
                        });
 | 
						|
                      },
 | 
						|
                      icon: const Icon(Icons.add),
 | 
						|
                      visualDensity: VisualDensity.compact,
 | 
						|
                      tooltip: tr('add'),
 | 
						|
                    )),
 | 
						|
              ],
 | 
						|
            )
 | 
						|
          ]);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    rows.clear();
 | 
						|
    formInputs.asMap().entries.forEach((rowInputs) {
 | 
						|
      if (rowInputs.key > 0) {
 | 
						|
        rows.add([
 | 
						|
          SizedBox(
 | 
						|
            height: widget.items[rowInputs.key - 1][0] is GeneratedFormSwitch
 | 
						|
                ? 8
 | 
						|
                : 25,
 | 
						|
          )
 | 
						|
        ]);
 | 
						|
      }
 | 
						|
      List<Widget> rowItems = [];
 | 
						|
      rowInputs.value.asMap().entries.forEach((rowInput) {
 | 
						|
        if (rowInput.key > 0) {
 | 
						|
          rowItems.add(const SizedBox(
 | 
						|
            width: 20,
 | 
						|
          ));
 | 
						|
        }
 | 
						|
        rowItems.add(Expanded(
 | 
						|
            child: Column(
 | 
						|
                crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
						|
                mainAxisSize: MainAxisSize.min,
 | 
						|
                children: [
 | 
						|
              rowInput.value,
 | 
						|
              ...widget.items[rowInputs.key][rowInput.key].belowWidgets
 | 
						|
            ])));
 | 
						|
      });
 | 
						|
      rows.add(rowItems);
 | 
						|
    });
 | 
						|
 | 
						|
    return Form(
 | 
						|
        key: _formKey,
 | 
						|
        child: Column(
 | 
						|
          children: [
 | 
						|
            ...rows.map((row) => Row(
 | 
						|
                  mainAxisAlignment: MainAxisAlignment.start,
 | 
						|
                  crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
                  children: [...row.map((e) => e)],
 | 
						|
                ))
 | 
						|
          ],
 | 
						|
        ));
 | 
						|
  }
 | 
						|
}
 |