From 9bd62ae5df0f5ffa8442a378d78de3157de0daa1 Mon Sep 17 00:00:00 2001 From: Michael Aziz Date: Tue, 8 Apr 2025 15:04:59 +0200 Subject: [PATCH] [SYNC] --- lib/src/form/src/controller.dart | 885 +++++++++++------- lib/src/form/src/form_group_wrapper.dart | 8 +- .../models/form_group_structure.model.dart | 23 +- 3 files changed, 551 insertions(+), 365 deletions(-) diff --git a/lib/src/form/src/controller.dart b/lib/src/form/src/controller.dart index e082766..84a8fb2 100644 --- a/lib/src/form/src/controller.dart +++ b/lib/src/form/src/controller.dart @@ -19,21 +19,6 @@ import 'models/form_group_value.model.dart'; /// A specialized form controller to handle form states, class AstromicFormController extends FormController { - // State Variables - final Map fieldStates = {}; - final Map fieldMessages = {}; - final Map _hostedValues = {}; - final Stream>? errorStream; - final List _formGroups = []; - - // State Stream Variables - static final StreamController<(String, AstromicFieldState)> _stateStreamController = StreamController<(String id, AstromicFieldState)>.broadcast(); - final Stream<(String id, AstromicFieldState)> stateStream = _stateStreamController.stream; - - // Hosted Value Validation Stream Variables - static final StreamController<(String, bool)> _hostedValueValidationStreamController = StreamController<(String id, bool)>.broadcast(); - final Stream<(String id, bool isValidationErrored)> hostedValueValidationStream = _hostedValueValidationStreamController.stream; - AstromicFormController({ Map? initialValues, this.errorStream, @@ -42,338 +27,7 @@ class AstromicFormController extends FormController { _addInitialControllers(initialValues); } - get i => null; - - /// Does the controller has a field with this ID. - bool hasKey(String id) => controllers.containsKey(id); - - /* some-group: { - some-group-0 : null, - some-group-1 : null, - some-group-2: null, - some-group-3: { - some-group-3-a: null, - some-group-3-b: null, - some-group-3-c: null, - some-group-3-d: null, - }, - } - - */ - - // At the start of a screen? - void initializeFormGroup(FormGroupStructure groupStructure, {int initialCount = 1}) { - if (groupStructure.fields.isEmpty) { - throw Exception('Group Fields should NOT be empty.'); - } - _formGroups.add(groupStructure); - - // Add the controllers and values. - for (int i = 0; i < initialCount; i++) { - for (String fieldID in groupStructure.fields) { - String finalFieldID = '${groupStructure.id}-#$i-$fieldID'; - controller(finalFieldID); - } - if (groupStructure.values != null && groupStructure.values!.isNotEmpty) { - for (String valueID in groupStructure.values!) { - String finalFieldID = '${groupStructure.id}-#$i-$valueID'; - controller(finalFieldID); - } - } - } - } - -/*{ -{ -0: { - field1: something, - field2: something else - field3: somethin another, - sub-group: { - 0: - { - sub-field1: someValue, - sub-field2: someOtherValue, - } - 1: - 2: - } - } -1: -2: -} -} -*/ - - bool hasGroup(String formGroupID, {bool isSubGroup = false}) { - FormGroupStructure? groupStructure; - if (isSubGroup) { - FormGroupStructure? parentGroup = _formGroups.where((FormGroupStructure f) => (f.subGroups?.map((FormGroupStructure ss) => ss.id).contains(formGroupID) ?? false)).nonNulls.toList().firstOrNull; - if (parentGroup != null && parentGroup.subGroups != null && parentGroup.subGroups!.map((FormGroupStructure i) => i.id).contains(formGroupID)) { - groupStructure = parentGroup.subGroups!.where((FormGroupStructure f) => f.id == formGroupID).nonNulls.toList().firstOrNull; - } - } else { - groupStructure = _formGroups.where((FormGroupStructure f) => f.id == formGroupID).nonNulls.toList().firstOrNull; - } - // - return groupStructure != null; - } - - FormGroupStructure? getGroupStructure(String formGroupID, {bool isSubGroup = false}) { - // Get the group structure with the same ID - FormGroupStructure? groupStructure; - if (isSubGroup) { - FormGroupStructure? parentGroup = _formGroups.where((FormGroupStructure f) => (f.subGroups?.map((FormGroupStructure ss) => ss.id).contains(formGroupID) ?? false)).nonNulls.toList().firstOrNull; - if (parentGroup != null && parentGroup.subGroups != null && parentGroup.subGroups!.map((FormGroupStructure i) => i.id).contains(formGroupID)) { - groupStructure = parentGroup.subGroups!.where((FormGroupStructure f) => f.id == formGroupID).nonNulls.toList().firstOrNull; - } - } else { - groupStructure = _formGroups.where((FormGroupStructure f) => f.id == formGroupID).nonNulls.toList().firstOrNull; - } - // - return groupStructure; - } - - FormGroupValue? getFormGroupValue(String formGroupID, {bool isSubGroup = false}) { - // Get the group structure with the same ID - FormGroupStructure? groupStructure; - if (isSubGroup) { - FormGroupStructure? parentGroup = _formGroups.where((FormGroupStructure f) => (f.subGroups?.map((FormGroupStructure ss) => ss.id).contains(formGroupID) ?? false)).nonNulls.toList().firstOrNull; - if (parentGroup != null && parentGroup.subGroups != null && parentGroup.subGroups!.map((FormGroupStructure i) => i.id).contains(formGroupID)) { - groupStructure = parentGroup.subGroups!.where((FormGroupStructure f) => f.id == formGroupID).nonNulls.toList().firstOrNull; - } else { - throw Exception('The group ID $formGroupID doesn\'t have any elements. Did you initialize the group?'); - } - } else { - groupStructure = _formGroups.where((FormGroupStructure f) => f.id == formGroupID).nonNulls.toList().firstOrNull; - if (groupStructure == null) { - throw Exception('The group ID $formGroupID doesn\'t have any elements. Did you initialize the group?'); - } - } - - print('Got the structure: $groupStructure'); - - // Get the current fields with this ID - Map firstSetOfFieldsWithID = Map.fromEntries( - controllers.entries.where((MapEntry c) => RegExp(formGroupID + r'-#[\d+]-' + groupStructure!.fields.first).hasMatch(c.key)).nonNulls.toList(), - ); - - print('First set of fields: $firstSetOfFieldsWithID'); - // get the fields IDs - List fieldsIDs = groupStructure!.fields.nonNulls.toList(); - print('fieldIDs: $fieldsIDs'); - // get the values IDs - List valuesIDs = groupStructure.values?.nonNulls.toList() ?? []; - print('valueIDs: $valuesIDs'); - - // get the subGroups - List subValues = []; - if (groupStructure.subGroups != null && groupStructure.subGroups!.isNotEmpty) { - subValues = groupStructure.subGroups!.map((FormGroupStructure s) => getFormGroupValue(s.id, isSubGroup: true)).nonNulls.toList(); - } - print('subValues: $subValues'); - - List instances = []; - for (int i = 0; i < firstSetOfFieldsWithID.length; i++) { - instances.add( - FormGroupInstance( - composedID: '$formGroupID-#$i-', - fields: Map.fromEntries(fieldsIDs.map((String id) => MapEntry('$formGroupID-#$i-$id', value('$formGroupID-#$i-$id'))).toList()), - values: valuesIDs.isNotEmpty - ? Map.fromEntries(valuesIDs.map((String a) => MapEntry('$formGroupID-#$i-$a', getValue('$formGroupID-#$i-$a'))).toList()) - : {}, - subGroups: subValues, - ), - ); - } - - FormGroupValue groupValue = FormGroupValue(groupID: formGroupID, instancesCount: firstSetOfFieldsWithID.length, instances: instances); - return groupValue; - } - - String addInstanceToFormGroup(String formGroupID, {bool isSubGroup = false}) { - // Get the group structure with the same ID - FormGroupStructure? groupStructure; - if (isSubGroup) { - FormGroupStructure? parentGroup = _formGroups.where((FormGroupStructure f) => (f.subGroups?.map((FormGroupStructure ss) => ss.id).contains(formGroupID) ?? false)).nonNulls.toList().firstOrNull; - if (parentGroup != null && parentGroup.subGroups != null && parentGroup.subGroups!.map((FormGroupStructure i) => i.id).contains(formGroupID)) { - groupStructure = parentGroup.subGroups!.where((FormGroupStructure f) => f.id == formGroupID).nonNulls.toList().firstOrNull; - } else { - throw Exception('The group ID $formGroupID doesn\'t have any elements. Did you initialize the group?'); - } - } else { - groupStructure = _formGroups.where((FormGroupStructure f) => f.id == formGroupID).nonNulls.toList().firstOrNull; - if (groupStructure == null) { - throw Exception('The group ID $formGroupID doesn\'t have any elements. Did you initialize the group?'); - } - } - - // Get the current fields with this ID - Map firstSetOfFieldsWithID = Map.fromEntries( - controllers.entries.where((MapEntry c) => RegExp(formGroupID + r'-#[\d+]-' + groupStructure!.fields.first).hasMatch(c.key)).nonNulls.toList(), - ); - - // get the fields IDs - List fieldsIDs = groupStructure!.fields.nonNulls.toList(); - print('fieldIDs: $fieldsIDs'); - // get the values IDs - List valuesIDs = groupStructure.values?.nonNulls.toList() ?? []; - print('valueIDs: $valuesIDs'); - - // Add the controllers and values. - - for (String fieldID in groupStructure.fields) { - String finalFieldID = '${groupStructure.id}-#${firstSetOfFieldsWithID.length}-$fieldID'; - controller(finalFieldID); - } - if (groupStructure.values != null && groupStructure.values!.isNotEmpty) { - for (String valueID in groupStructure.values!) { - String finalFieldID = '${groupStructure.id}-#${firstSetOfFieldsWithID.length}-$valueID'; - controller(finalFieldID); - } - } - return '${groupStructure.id}-#${firstSetOfFieldsWithID.length}-'; - } - - void removeInstanceFromFormGroup(String formGroupID, int indexToRemove, {bool isSubGroup = false}) { - // Get the group structure with the same ID - FormGroupStructure? groupStructure; - if (isSubGroup) { - FormGroupStructure? parentGroup = _formGroups.where((FormGroupStructure f) => (f.subGroups?.map((FormGroupStructure ss) => ss.id).contains(formGroupID) ?? false)).nonNulls.toList().firstOrNull; - if (parentGroup != null && parentGroup.subGroups != null && parentGroup.subGroups!.map((FormGroupStructure i) => i.id).contains(formGroupID)) { - groupStructure = parentGroup.subGroups!.where((FormGroupStructure f) => f.id == formGroupID).nonNulls.toList().firstOrNull; - } else { - throw Exception('The group ID $formGroupID doesn\'t have any elements. Did you initialize the group?'); - } - } else { - groupStructure = _formGroups.where((FormGroupStructure f) => f.id == formGroupID).nonNulls.toList().firstOrNull; - if (groupStructure == null) { - throw Exception('The group ID $formGroupID doesn\'t have any elements. Did you initialize the group?'); - } - } - - // Get the current fields with this ID - Map firstSetOfFieldsWithID = Map.fromEntries( - controllers.entries.where((MapEntry c) => RegExp(formGroupID + r'-#[\d+]-' + groupStructure!.fields.first).hasMatch(c.key)).nonNulls.toList(), - ); - - if (indexToRemove >= firstSetOfFieldsWithID.length) { - throw Exception('The index to remove is larger than the whole instances count.'); - } else { - // get the fields IDs - List fieldsIDs = groupStructure!.fields.nonNulls.toList(); - print('fieldIDs: $fieldsIDs'); - // get the values IDs - List valuesIDs = groupStructure.values?.nonNulls.toList() ?? []; - print('valueIDs: $valuesIDs'); - - if (indexToRemove == (firstSetOfFieldsWithID.length - 1)) { - // Remove the last item - for (String fieldID in groupStructure.fields) { - removeController('${groupStructure.id}-#$indexToRemove-$fieldID'); - } - if (groupStructure.values != null && groupStructure.values!.isNotEmpty) { - for (String valueID in groupStructure.values!) { - removeController('${groupStructure.id}-#$indexToRemove-$valueID'); - _hostedValues.remove('${groupStructure.id}-#$indexToRemove-$valueID'); - } - } - } else { - // Switch and remove - int nextIndex = indexToRemove + 1; - for (String fieldID in groupStructure.fields) { - set('${groupStructure.id}-#$indexToRemove-$fieldID', value('${groupStructure.id}-#$nextIndex-$fieldID')); - removeController('${groupStructure.id}-#$nextIndex-$fieldID'); - } - if (groupStructure.values != null && groupStructure.values!.isNotEmpty) { - for (String valueID in groupStructure.values!) { - _hostedValues.remove('${groupStructure.id}-#$indexToRemove-$valueID'); - set('${groupStructure.id}-#$indexToRemove-$valueID', value('${groupStructure.id}-#$nextIndex-$valueID')); - setValue('${groupStructure.id}-#$indexToRemove-$valueID', getValue('${groupStructure.id}-#$nextIndex-$valueID')); - removeController('${groupStructure.id}-#$nextIndex-$valueID'); - _hostedValues.remove('${groupStructure.id}-#$nextIndex-$valueID'); - } - } - } - // Remove last instance - - } - - } - - // void removeFromFormGroup(String formGroupID, int index) { - // // Get all the groups with the same ID - // List groupsWithSameID = _formGroups.where((FormGroupStructure f) => f.id == formGroupID).nonNulls.toList(); - // if (groupsWithSameID.isEmpty) { - // throw Exception('The group ID $formGroupID doesn\'t have any elements. Did you initialize the group?'); - // } - // _formGroups.removeAt(groupsWithSameID.first); - // for (MapEntry fieldEntry in groupsWithSameID.first.fields.entries) { - // String fieldID = '$formGroupID-${groupsWithSameID.length}-${fieldEntry.key}'; - // controller(fieldID); - // if (fieldEntry.value) { - // // Is a value field - // } else { - // // Is a text field - // } - // } - // } - - // Map> getFormGroup(String groupID){} - - /// Get the field state and message of a specific field using it's ID. - (AstromicFieldState, String? message)? getState(String fieldId) => (fieldStates.containsKey(fieldId)) ? (fieldStates[fieldId]!, fieldMessages[fieldId]) : null; - - /// Set the field state and message of a specific field using it's ID. - void setState(String fieldId, AstromicFieldState state, {String? message}) { - if (!fieldStates.containsKey(fieldId)) { - throw Exception('The state of the field ID $fieldId does not exist.'); - } - fieldStates[fieldId] = state; - fieldMessages[fieldId] = message; - _stateStreamController.add((fieldId, state)); - } - - /// Reset the state of a specific field using it's ID. - void resetState(String fieldId) => setState(fieldId, AstromicFieldState.idle); - - /// Validate hosted values. - bool validateValues(List valueIDs) { - for (String hostedValueID in valueIDs) { - if (_hostedValues.containsKey(hostedValueID) && _hostedValues[hostedValueID]!.$2 && _hostedValues[hostedValueID]!.$1 == null) { - // Validation Error! - _hostedValueValidationStreamController.add((hostedValueID, true)); - return false; - } - } - return true; - } - - /// Prepare a hosted value. - void prepareValue(String id, bool isRequired) { - if (!_hostedValues.keys.toList().contains(id)) { - return _hostedValues.addEntries(>[MapEntry(id, (null, isRequired))]); - } - } - - /// Get the value of a hosted state variable using it's ID. - T? getValue(String id) { - prepareValue(id, false); - - if (_hostedValues[id]?.$1 is T?) { - return _hostedValues[id]?.$1; - } else { - throw Exception('Value found but is not of the type $T, it\'s of type ${_hostedValues[id]?.$1.runtimeType}'); - } - } - - /// Set the value of a hosted state variable using it's ID. - void setValue(String id, T data, {bool isRequired = false}) { - prepareValue(id, isRequired); - _hostedValues[id] = (data, isRequired); - _hostedValueValidationStreamController.add((id, false)); - } - + //SECTION - Overrides @override TextEditingController controller(String id, {String? initialText, bool isObscure = false}) { TextEditingController ret = super.controller(id, initialText: initialText, isObscure: isObscure); @@ -400,8 +54,545 @@ class AstromicFormController extends FormController { fieldMessages.remove(id); } } + //!SECTION + + //SECTION - Field State + //S1 - State Variables + final Map fieldStates = {}; + final Map fieldMessages = {}; + //S1 - Streams + static final StreamController<(String, AstromicFieldState)> _stateStreamController = StreamController<(String id, AstromicFieldState)>.broadcast(); + final Stream<(String id, AstromicFieldState)> stateStream = _stateStreamController.stream; + final Stream>? errorStream; + //S1 - Methods + + /// Does the controller has a field with this ID. + bool hasKey(String id) => controllers.containsKey(id); + + /// Get the field state and message of a specific field using it's ID. + (AstromicFieldState, String? message)? getState(String fieldId) => (fieldStates.containsKey(fieldId)) ? (fieldStates[fieldId]!, fieldMessages[fieldId]) : null; + + /// Set the field state and message of a specific field using it's ID. + void setState(String fieldId, AstromicFieldState state, {String? message}) { + if (!fieldStates.containsKey(fieldId)) { + throw Exception('The state of the field ID $fieldId does not exist.'); + } + fieldStates[fieldId] = state; + fieldMessages[fieldId] = message; + _stateStreamController.add((fieldId, state)); + } + + /// Reset the state of a specific field using it's ID. + void resetState(String fieldId) => setState(fieldId, AstromicFieldState.idle); + //!SECTION + + //SECTION - Hosted Values + //S1 - State Variables + final Map _hostedValues = {}; + + //S1 - Streams + static final StreamController<(String, bool)> _hostedValueValidationStreamController = StreamController<(String id, bool)>.broadcast(); + final Stream<(String id, bool isValidationErrored)> hostedValueValidationStream = _hostedValueValidationStreamController.stream; + + //S1 - Methods + + /// Prepare a hosted value. + void prepareValue(String id, bool isRequired) { + if (!_hostedValues.keys.toList().contains(id)) { + return _hostedValues.addEntries(>[MapEntry(id, (null, isRequired))]); + } + } + + /// Get the value of a hosted state variable using it's ID. + T? getValue(String id) { + prepareValue(id, false); + + if (_hostedValues[id]?.$1 is T?) { + return _hostedValues[id]?.$1; + } else { + throw Exception('Value found but is not of the type $T, it\'s of type ${_hostedValues[id]?.$1.runtimeType}'); + } + } + + /// Set the value of a hosted state variable using it's ID. + void setValue(String id, T data, {bool isRequired = false}) { + prepareValue(id, isRequired); + _hostedValues[id] = (data, isRequired); + _hostedValueValidationStreamController.add((id, false)); + } + + /// Validate hosted values. + bool validateValues(List valueIDs) { + for (String hostedValueID in valueIDs) { + if (_hostedValues.containsKey(hostedValueID) && _hostedValues[hostedValueID]!.$2 && _hostedValues[hostedValueID]!.$1 == null) { + // Validation Error! + _hostedValueValidationStreamController.add((hostedValueID, true)); + return false; + } + } + return true; + } + //!SECTION + + //SECTION - Form Groups + //S1 - State Variables + final List _formGroups = []; + //S1 - Methods + + /// Recursively returns the full path of the group ID. + String? getFullPathOfGroup(String targetGroupID, {List? formGroups, String currentPath = ''}) { + // Loop through each FormGroupStructure + for (final FormGroupStructure group in (formGroups ?? _formGroups)) { + // If the group ID matches, return the current path + if (group.id == targetGroupID) return '$currentPath${group.id}'; + + // Otherwise, check in its subgroups recursively + for (final (FormGroupStructure, int) subGroup in group.subGroups ?? <(FormGroupStructure, int)>[]) { + final String? subGroupPath = getFullPathOfGroup(targetGroupID, formGroups: [subGroup.$1], currentPath: '$currentPath${group.id}->'); + // Return the path if found + if (subGroupPath != null) { + return subGroupPath; + } + } + } + + // Return an empty string if the group ID is not found + return null; + } + + // _formController.initializeFormGroup( + // FormGroupStructure( + // id: 'mainGroup', + // fields: [ + // 'title', + // 'description', + // ], + // values: [ + // 'media', + // ], + // subGroups: [ + // (FormGroupStructure( + // id: 'variations', + // fields: ['title'], + // values: ['media'], + // ),3), + // ], + // ), + // initialCount: 2, + // ); + /// Does the controller has a field group with this ID. + // bool hasFieldGroup(String formGroupID, {bool isSubGroup = false}) => _getGroupStructure(formGroupID, isSubGroup: isSubGroup) != null; + + /// Get the formGroupStructure of this groupId + // String? findGroupPath(String groupID, List? groups) { + // if ((groups ?? _formGroups).map((FormGroupStructure a) => a.id).contains(groupID)) return groupID; + // findGroupPath( + // groupID, + // (groups ?? _formGroups) + // .map((FormGroupStructure f) => f.subGroups?.map(((FormGroupStructure, int) aa) => aa.$1).toList()) + // .toList() + // .reduce((List? a, List? b) => [...?a, ...?b]) + // ?.toList() ?? + // [], + // ); + // } + + /// Get the formGroupStructure of this groupId + FormGroupStructure? getGroupStructure(String groupID) { + // Get the full path of the group + final String? fullPath = getFullPathOfGroup(groupID, formGroups: _formGroups); + + // If no path is found, return null + if (fullPath == null) return null; + + // Split the path into segments + final List pathSegments = fullPath.split('->'); + + // We start with the root group (the first segment in the path) + FormGroupStructure? currentGroup = _formGroups.where((FormGroupStructure group) => group.id == pathSegments.first).firstOrNull; + + // If the root group is not found, return null + if (currentGroup == null) return null; + + // Traverse through the path segments to find the group or subgroup + for (int i = 1; i < pathSegments.length; i++) { + final String segment = pathSegments[i]; + + // Search for the subgroup within the current group + final (FormGroupStructure, int)? subGroup = currentGroup?.subGroups?.where(((FormGroupStructure, int) subGroup) => subGroup.$1.id == segment).firstOrNull; + + // If a subgroup is found, update currentGroup to the subgroup + if (subGroup != null) { + currentGroup = subGroup.$1; + } else { + // If no subgroup is found at this level, return null + return null; + } + } + + return currentGroup; + } + + // FormGroupStructure? _getGroupStructure(String formGroupID, {bool isSubGroup = false}) { + // FormGroupStructure? groupStructure; + // if (isSubGroup) { + // FormGroupStructure? parentGroup = + // _formGroups.where((FormGroupStructure f) => (f.subGroups?.map(((FormGroupStructure, int) ss) => ss.$1.id).contains(formGroupID) ?? false)).nonNulls.toList().firstOrNull; + // if (parentGroup != null && parentGroup.subGroups != null && parentGroup.subGroups!.map(((FormGroupStructure, int) i) => i.$1.id).contains(formGroupID)) { + // assert(parentGroup.subGroups!.where(((FormGroupStructure, int) f) => f.$1.id == formGroupID).nonNulls.toList().length == 1, 'Seems there are multible subgroups with this ID!'); + // groupStructure = parentGroup.subGroups!.where(((FormGroupStructure, int) f) => f.$1.id == formGroupID).nonNulls.toList().firstOrNull?.$1; + // } + // } else { + // groupStructure = _formGroups.where((FormGroupStructure f) => f.id == formGroupID).nonNulls.toList().firstOrNull; + // } + // // + // return groupStructure; + // } + + /// Get current Instance count of this formGroup + // int formGroupInstancesLength(String formGroupID, {bool isSubGroup = false}) { + // // Get the group structure with the same ID + // FormGroupStructure? structure = _getGroupStructure(formGroupID, isSubGroup: isSubGroup); + // assert(structure != null, 'The ${isSubGroup ? "SUBGroup" : "Group"} $formGroupID doesn\'t seem to be found, are you sure you initialized it?'); + + // Map firstSetOfFieldsWithID = Map.fromEntries( + // controllers.entries.where((MapEntry c) => RegExp(formGroupID + r'-#[\d+]-' + groupStructure!.fields.first).hasMatch(c.key)).nonNulls.toList(), + // ); + // } + + /// Prepare the groupStructure. + // void _addFieldsToGroup(String structureID, List fields, List? values, int index) { + // for (String fieldID in fields) { + // controller(standeredGroupFormat(structureID, index, fieldID)); + // } + // if (values != null && values.isNotEmpty) { + // for (String valueID in values) { + // controller(standeredGroupFormat(structureID, index, valueID)); + // } + // } + // } + + void _addGroupControllers(FormGroupStructure structure, int index, {String? parentPrefix}) { + final String baseID = parentPrefix ?? structure.id; + + for (final String fieldID in structure.fields) { + final String fullID = standeredGroupFormat(baseID, index, fieldID); + controller(fullID); + } + + for (final String valueID in structure.values ?? []) { + final String fullID = standeredGroupFormat(baseID, index, valueID); + controller(fullID); + } + } + + /// Initialize the formGroup Structure. + void initializeFormGroup(FormGroupStructure groupStructure, {int initialCount = 1}) { + assert(groupStructure.fields.isNotEmpty, '${groupStructure.id}: Group fields should NOT be empty.'); + + // Validate subgroups (if any) + groupStructure.subGroups?.map(((FormGroupStructure, int) a) => a.$1).forEach(((FormGroupStructure subGroup) { + assert(subGroup.fields.isNotEmpty, '${subGroup.id}: Subgroup fields should NOT be empty.'); + })); + + // Add structure to registry + _formGroups.add(groupStructure); + + for (int groupIndex = 0; groupIndex < initialCount; groupIndex++) { + // Add main group fields/values + _addGroupControllers(groupStructure, groupIndex); + + // Add subgroup fields/values + if (groupStructure.subGroups != null && groupStructure.subGroups!.isNotEmpty) { + for (final (FormGroupStructure subGroup, int subgroupInitialCount) in groupStructure.subGroups!) { + for (int subIndex = 0; subIndex < subgroupInitialCount; subIndex++) { + final String nestedID = standeredGroupFormat(groupStructure.id, groupIndex, subGroup.id); + _addGroupControllers(subGroup, subIndex, parentPrefix: nestedID); + } + } + } + } + } + + // void initializeFormGroup(FormGroupStructure groupStructure, {int initialCount = 1}) { + // assert(groupStructure.fields.isNotEmpty, '$groupStructure: Group Fields should NOT be empty.'); + // if (groupStructure.subGroups != null && groupStructure.subGroups!.isNotEmpty) { + // assert(!groupStructure.subGroups!.any(((FormGroupStructure, int) a) => a.$1.fields.isEmpty), '$groupStructure: Subgroup Fields should NOT be empty.'); + // } + + // // Add the group. + // _formGroups.add(groupStructure); + + // // Add the controllers and values. + // for (int groupInstanceIndex = 0; groupInstanceIndex < initialCount; groupInstanceIndex++) { + // _addFieldsToGroup(groupStructure.id, groupStructure.fields, groupStructure.values, groupInstanceIndex); + + // // Add subGroup data if non null + // if (groupStructure.subGroups != null && groupStructure.subGroups!.isNotEmpty) { + // for ((FormGroupStructure, int) subGroupStructure in groupStructure.subGroups!) { + // for (int subgroupInstanceIndex = 0; subgroupInstanceIndex < subGroupStructure.$2; subgroupInstanceIndex++) { + // _addFieldsToGroup(standeredGroupFormat(groupStructure.id, groupInstanceIndex, subGroupStructure.$1.id), subGroupStructure.$1.fields, subGroupStructure.$1.values, subgroupInstanceIndex); + // } + // } + // } + // } + // } + + // String addInstanceToFormGroup(String formGroupID, {bool isSubGroup = false}) { + // // Get the group structure with the same ID + // FormGroupStructure? structure = _getGroupStructure(formGroupID); + // assert(structure != null, 'The ${isSubGroup ? "SUBGroup" : "Group"} $formGroupID doesn\'t seem to be found, are you sure you initialized it?'); + + // // Get the current fields with this ID + // Map firstSetOfFieldsWithID = Map.fromEntries( + // controllers.entries.where((MapEntry c) => RegExp(formGroupID + r'-#[\d+]-' + groupStructure!.fields.first).hasMatch(c.key)).nonNulls.toList(), + // ); + + // // get the fields IDs + // List fieldsIDs = groupStructure!.fields.nonNulls.toList(); + // print('fieldIDs: $fieldsIDs'); + // // get the values IDs + // List valuesIDs = groupStructure.values?.nonNulls.toList() ?? []; + // print('valueIDs: $valuesIDs'); + + // // Add the controllers and values. + + // for (String fieldID in groupStructure.fields) { + // String finalFieldID = '${groupStructure.id}-#${firstSetOfFieldsWithID.length}-$fieldID'; + // controller(finalFieldID); + // } + // if (groupStructure.values != null && groupStructure.values!.isNotEmpty) { + // for (String valueID in groupStructure.values!) { + // String finalFieldID = '${groupStructure.id}-#${firstSetOfFieldsWithID.length}-$valueID'; + // controller(finalFieldID); + // } + // } + // return '${groupStructure.id}-#${firstSetOfFieldsWithID.length}-'; + // } + +// FormGroupValue getFormGroupValue(String groupID) { +// final FormGroupStructure structure = _formGroups.firstWhere( +// (FormGroupStructure group) => group.id == groupID, +// orElse: () => throw Exception("Group '$groupID' not initialized."), +// ); + +// final List instances = []; + +// for (int index = 0; index < structure.count; index++) { +// final Map values = {}; +// final Map fields = {}; + +// // Fields (Text) +// for (final fieldID in structure.fields) { +// final fullID = standeredGroupFormat(groupID, index, fieldID); +// fields[fieldID] = controller(fullID); +// } + +// // Values (Dynamic) +// for (final valueID in structure.values ?? []) { +// final fullID = standeredGroupFormat(groupID, index, valueID); +// values[valueID] = controller(fullID).value; +// } + +// // SubGroups +// final Map subGroups = {}; + +// if (structure.subGroups != null && structure.subGroups!.isNotEmpty) { +// for ((FormGroupStructure subGroup, int subCount) in structure.subGroups!) { +// final nestedID = standeredGroupFormat(groupID, index, subGroup.id); + +// final List subInstances = []; + +// for (int subIndex = 0; subIndex < subCount; subIndex++) { +// final Map subValues = {}; +// final Map subFields = {}; + +// for (final fieldID in subGroup.fields) { +// final subFullID = standeredGroupFormat(nestedID, subIndex, fieldID); +// subFields[fieldID] = controller(subFullID); +// } + +// for (final valueID in subGroup.values ?? []) { +// final subFullID = standeredGroupFormat(nestedID, subIndex, valueID); +// subValues[valueID] = controller(subFullID).value; +// } + +// subInstances.add(FormGroupInstance( +// fields: subFields, +// values: subValues, +// subGroups: const {}, // deeper nesting unsupported (for now) +// )); +// } + +// subGroups[subGroup.id] = FormGroupValue( +// groupID: subGroup.id, +// instancesCount: subCount, +// instances: subInstances, +// ); +// } +// } + +// instances.add(FormGroupInstance( +// fields: fields, +// values: values, +// subGroups: subGroups, +// )); +// } + +// return FormGroupValue( +// groupID: groupID, +// instancesCount: structure.count, +// instances: instances, +// ); +// } + + int getInstanceCount(String targetGroupID) { + // Helper method to check if a controller key matches the target group + bool isControllerForGroup(String controllerKey, String groupID) { + final RegExp regExp = RegExp(r'^' + RegExp.escape(groupID) + r'-#\d+-'); + return regExp.hasMatch(controllerKey); + } + + // Recursive helper function to find instances of a group and its subgroups + int countInstancesRecursively(FormGroupStructure groupStructure, List controllersKeys) { + // Start with the base count (for the current group itself) + int instanceCount = controllersKeys.where((String key) => isControllerForGroup(key, groupStructure.id)).length; + + // Recursively count instances in subgroups + if (groupStructure.subGroups != null) { + for ((FormGroupStructure, int) subGroup in groupStructure.subGroups!) { + instanceCount += countInstancesRecursively(subGroup.$1, controllersKeys); + } + } + + return instanceCount; + } + + // Flatten all controller keys into a list + final List controllerKeys = controllers.keys.toList(); + + // Find the group structure for the target group + final FormGroupStructure? groupStructure = getGroupStructure(targetGroupID); + if (groupStructure == null) return 0; + + // Use the recursive function to count instances for the target group + return countInstancesRecursively(groupStructure, controllerKeys); + } + + FormGroupValue? getFormGroupValue(String formGroupID, {bool isSubGroup = false}) { + // Get the group structure with the ID + FormGroupStructure? groupStructure = getGroupStructure(formGroupID); + + print('Got the structure: $groupStructure'); + + // Get the current fields with this ID + int instancesCount = getInstanceCount(formGroupID); + print('Got the instancesCount: $instancesCount'); + // Map firstSetOfFieldsWithID = Map.fromEntries( + // controllers.entries.where((MapEntry c) => RegExp(formGroupID + r'-#[\d+]-' + groupStructure!.fields.first).hasMatch(c.key)).nonNulls.toList(), + // ); + + // print('First set of fields: $firstSetOfFieldsWithID'); + // get the fields IDs + List fieldsIDs = groupStructure!.fields.nonNulls.toList(); + print('fieldIDs: $fieldsIDs'); + // get the values IDs + List valuesIDs = groupStructure.values?.nonNulls.toList() ?? []; + print('valueIDs: $valuesIDs'); + + // get the subGroups + List subValues = []; + if (groupStructure.subGroups != null && groupStructure.subGroups!.isNotEmpty) { + subValues = groupStructure.subGroups!.map(((FormGroupStructure, int) s) => getFormGroupValue(s.$1.id, isSubGroup: true)).nonNulls.toList(); + } + print('subValues: $subValues'); + + List instances = []; + for (int i = 0; i < instancesCount; i++) { + instances.add( + FormGroupInstance( + composedID: '$formGroupID-#$i-', + fields: Map.fromEntries(fieldsIDs.map((String id) => MapEntry('$formGroupID-#$i-$id', value('$formGroupID-#$i-$id'))).toList()), + values: valuesIDs.isNotEmpty + ? Map.fromEntries(valuesIDs.map((String a) => MapEntry('$formGroupID-#$i-$a', getValue('$formGroupID-#$i-$a'))).toList()) + : {}, + subGroups: subValues, + ), + ); + } + + FormGroupValue groupValue = FormGroupValue(groupID: formGroupID, instancesCount: instancesCount, instances: instances); + return groupValue; + } + + // void removeInstanceFromFormGroup(String formGroupID, int indexToRemove, {bool isSubGroup = false}) { + // // Get the group structure with the same ID + // FormGroupStructure? groupStructure; + // if (isSubGroup) { + // FormGroupStructure? parentGroup = _formGroups.where((FormGroupStructure f) => (f.subGroups?.map((FormGroupStructure ss) => ss.id).contains(formGroupID) ?? false)).nonNulls.toList().firstOrNull; + // if (parentGroup != null && parentGroup.subGroups != null && parentGroup.subGroups!.map((FormGroupStructure i) => i.id).contains(formGroupID)) { + // groupStructure = parentGroup.subGroups!.where((FormGroupStructure f) => f.id == formGroupID).nonNulls.toList().firstOrNull; + // } else { + // throw Exception('The group ID $formGroupID doesn\'t have any elements. Did you initialize the group?'); + // } + // } else { + // groupStructure = _formGroups.where((FormGroupStructure f) => f.id == formGroupID).nonNulls.toList().firstOrNull; + // if (groupStructure == null) { + // throw Exception('The group ID $formGroupID doesn\'t have any elements. Did you initialize the group?'); + // } + // } + + // // Get the current fields with this ID + // Map firstSetOfFieldsWithID = Map.fromEntries( + // controllers.entries.where((MapEntry c) => RegExp(formGroupID + r'-#[\d+]-' + groupStructure!.fields.first).hasMatch(c.key)).nonNulls.toList(), + // ); + + // if (indexToRemove >= firstSetOfFieldsWithID.length) { + // throw Exception('The index to remove is larger than the whole instances count.'); + // } else { + // // get the fields IDs + // List fieldsIDs = groupStructure!.fields.nonNulls.toList(); + // print('fieldIDs: $fieldsIDs'); + // // get the values IDs + // List valuesIDs = groupStructure.values?.nonNulls.toList() ?? []; + // print('valueIDs: $valuesIDs'); + + // if (indexToRemove == (firstSetOfFieldsWithID.length - 1)) { + // // Remove the last item + // for (String fieldID in groupStructure.fields) { + // removeController('${groupStructure.id}-#$indexToRemove-$fieldID'); + // } + // if (groupStructure.values != null && groupStructure.values!.isNotEmpty) { + // for (String valueID in groupStructure.values!) { + // removeController('${groupStructure.id}-#$indexToRemove-$valueID'); + // _hostedValues.remove('${groupStructure.id}-#$indexToRemove-$valueID'); + // } + // } + // } else { + // // Switch and remove + // int nextIndex = indexToRemove + 1; + // for (String fieldID in groupStructure.fields) { + // set('${groupStructure.id}-#$indexToRemove-$fieldID', value('${groupStructure.id}-#$nextIndex-$fieldID')); + // removeController('${groupStructure.id}-#$nextIndex-$fieldID'); + // } + // if (groupStructure.values != null && groupStructure.values!.isNotEmpty) { + // for (String valueID in groupStructure.values!) { + // _hostedValues.remove('${groupStructure.id}-#$indexToRemove-$valueID'); + // set('${groupStructure.id}-#$indexToRemove-$valueID', value('${groupStructure.id}-#$nextIndex-$valueID')); + // setValue('${groupStructure.id}-#$indexToRemove-$valueID', getValue('${groupStructure.id}-#$nextIndex-$valueID')); + // removeController('${groupStructure.id}-#$nextIndex-$valueID'); + // _hostedValues.remove('${groupStructure.id}-#$nextIndex-$valueID'); + // } + // } + // } + // // Remove last instance + // } + // } + //!SECTION //SECTION - Helper Methods + String standeredGroupFormat(String groupID, int groupIndex, String? fieldID) => '$groupID-#$groupIndex-${fieldID ?? ""}'; + void _addInitialControllers(Map? initialValues) { if (initialValues != null) { // Add in the initial field states... diff --git a/lib/src/form/src/form_group_wrapper.dart b/lib/src/form/src/form_group_wrapper.dart index 1fc9d57..266e4b5 100644 --- a/lib/src/form/src/form_group_wrapper.dart +++ b/lib/src/form/src/form_group_wrapper.dart @@ -105,8 +105,8 @@ class _FormGroupWrapperState extends State { () { String id = ''; setState(() { - id = widget.formController.addInstanceToFormGroup(widget.groupID,isSubGroup: widget.isSubGroup); - instances = widget.formController.getFormGroupValue(widget.groupID,isSubGroup: widget.isSubGroup)!.instances; + // id = widget.formController.addInstanceToFormGroup(widget.groupID,isSubGroup: widget.isSubGroup); + // instances = widget.formController.getFormGroupValue(widget.groupID,isSubGroup: widget.isSubGroup)!.instances; }); return id; }, @@ -118,8 +118,8 @@ class _FormGroupWrapperState extends State { void _removeItem(int i) { setState(() { - widget.formController.removeInstanceFromFormGroup(widget.groupID, i,isSubGroup: widget.isSubGroup); - instances = widget.formController.getFormGroupValue(widget.groupID,isSubGroup: widget.isSubGroup)!.instances; + // widget.formController.removeInstanceFromFormGroup(widget.groupID, i,isSubGroup: widget.isSubGroup); + // instances = widget.formController.getFormGroupValue(widget.groupID,isSubGroup: widget.isSubGroup)!.instances; }); } diff --git a/lib/src/form/src/models/form_group_structure.model.dart b/lib/src/form/src/models/form_group_structure.model.dart index 0bede00..9827edc 100644 --- a/lib/src/form/src/models/form_group_structure.model.dart +++ b/lib/src/form/src/models/form_group_structure.model.dart @@ -6,7 +6,7 @@ class FormGroupStructure { final String id; final List fields; final List? values; - final List? subGroups; + final List<(FormGroupStructure structure, int initialCount)>? subGroups; FormGroupStructure({ required this.id, required this.fields, @@ -18,7 +18,7 @@ class FormGroupStructure { String? id, List? fields, List? values, - List? subGroups, + List<(FormGroupStructure structure, int initialCount)>? subGroups, }) { return FormGroupStructure( id: id ?? this.id, @@ -33,7 +33,7 @@ class FormGroupStructure { 'id': id, 'fields': fields, 'values': values, - 'subGroups': subGroups?.map((FormGroupStructure x) => x.toMap()).toList(), + 'subGroups': subGroups?.map(((FormGroupStructure structure, int initialCount) x) => {'structure': x.$1.toMap(), 'initialCount': x.$2}).toList(), }; } @@ -42,7 +42,9 @@ class FormGroupStructure { id: map['id'] as String, fields: List.from(map['fields'] as List), values: map['values'] != null ? List.from(map['values'] as List) : null, - subGroups: map['subGroups'] != null ? (map['subGroups'] as List>).map((Map f) => FormGroupStructure.fromMap(f)).toList() : null, + subGroups: map['subGroups'] != null + ? (map['subGroups'] as List>).map((Map map) => (FormGroupStructure.fromMap(map['structure']), int.tryParse(map['initialCount']) ?? 0)).toList() + : null, ); } @@ -58,19 +60,12 @@ class FormGroupStructure { @override bool operator ==(covariant FormGroupStructure other) { if (identical(this, other)) return true; - - return - other.id == id && - listEquals(other.fields, fields) && - listEquals(other.values, values) && - listEquals(other.subGroups, subGroups); + + return other.id == id && listEquals(other.fields, fields) && listEquals(other.values, values) && listEquals(other.subGroups, subGroups); } @override int get hashCode { - return id.hashCode ^ - fields.hashCode ^ - values.hashCode ^ - subGroups.hashCode; + return id.hashCode ^ fields.hashCode ^ values.hashCode ^ subGroups.hashCode; } }