This commit is contained in:
2025-04-08 15:04:59 +02:00
parent 5f57f0596b
commit 9bd62ae5df
3 changed files with 551 additions and 365 deletions

View File

@@ -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<String, AstromicFieldState> fieldStates = <String, AstromicFieldState>{};
final Map<String, String?> fieldMessages = <String, String?>{};
final Map<String, (dynamic, bool)> _hostedValues = <String, (dynamic, bool)>{};
final Stream<List<(String internalCode, String? message)>>? errorStream;
final List<FormGroupStructure> _formGroups = <FormGroupStructure>[];
// 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<String, (String initialText, bool initialObscurity)>? 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<String, String> firstSetOfFieldsWithID = Map<String, String>.fromEntries(
controllers.entries.where((MapEntry<String, String> c) => RegExp(formGroupID + r'-#[\d+]-' + groupStructure!.fields.first).hasMatch(c.key)).nonNulls.toList(),
);
print('First set of fields: $firstSetOfFieldsWithID');
// get the fields IDs
List<String> fieldsIDs = groupStructure!.fields.nonNulls.toList();
print('fieldIDs: $fieldsIDs');
// get the values IDs
List<String> valuesIDs = groupStructure.values?.nonNulls.toList() ?? <String>[];
print('valueIDs: $valuesIDs');
// get the subGroups
List<FormGroupValue> subValues = <FormGroupValue>[];
if (groupStructure.subGroups != null && groupStructure.subGroups!.isNotEmpty) {
subValues = groupStructure.subGroups!.map((FormGroupStructure s) => getFormGroupValue(s.id, isSubGroup: true)).nonNulls.toList();
}
print('subValues: $subValues');
List<FormGroupInstance> instances = <FormGroupInstance>[];
for (int i = 0; i < firstSetOfFieldsWithID.length; i++) {
instances.add(
FormGroupInstance(
composedID: '$formGroupID-#$i-',
fields: Map<String, String>.fromEntries(fieldsIDs.map((String id) => MapEntry<String, String>('$formGroupID-#$i-$id', value('$formGroupID-#$i-$id'))).toList()),
values: valuesIDs.isNotEmpty
? Map<String, dynamic>.fromEntries(valuesIDs.map((String a) => MapEntry<String, dynamic>('$formGroupID-#$i-$a', getValue<dynamic>('$formGroupID-#$i-$a'))).toList())
: <String, dynamic>{},
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<String, String> firstSetOfFieldsWithID = Map<String, String>.fromEntries(
controllers.entries.where((MapEntry<String, String> c) => RegExp(formGroupID + r'-#[\d+]-' + groupStructure!.fields.first).hasMatch(c.key)).nonNulls.toList(),
);
// get the fields IDs
List<String> fieldsIDs = groupStructure!.fields.nonNulls.toList();
print('fieldIDs: $fieldsIDs');
// get the values IDs
List<String> valuesIDs = groupStructure.values?.nonNulls.toList() ?? <String>[];
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<String, String> firstSetOfFieldsWithID = Map<String, String>.fromEntries(
controllers.entries.where((MapEntry<String, String> 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<String> fieldsIDs = groupStructure!.fields.nonNulls.toList();
print('fieldIDs: $fieldsIDs');
// get the values IDs
List<String> valuesIDs = groupStructure.values?.nonNulls.toList() ?? <String>[];
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<FormGroupStructure> 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<String, bool> 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<String,Map<String,dynamic>> 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<String> 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<T>(String id, bool isRequired) {
if (!_hostedValues.keys.toList().contains(id)) {
return _hostedValues.addEntries(<MapEntry<String, (T?, bool)>>[MapEntry<String, (T?, bool)>(id, (null, isRequired))]);
}
}
/// Get the value of a hosted state variable using it's ID.
T? getValue<T>(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<T>(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<String, AstromicFieldState> fieldStates = <String, AstromicFieldState>{};
final Map<String, String?> fieldMessages = <String, String?>{};
//S1 - Streams
static final StreamController<(String, AstromicFieldState)> _stateStreamController = StreamController<(String id, AstromicFieldState)>.broadcast();
final Stream<(String id, AstromicFieldState)> stateStream = _stateStreamController.stream;
final Stream<List<(String internalCode, String? message)>>? 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<String, (dynamic, bool)> _hostedValues = <String, (dynamic, bool)>{};
//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<T>(String id, bool isRequired) {
if (!_hostedValues.keys.toList().contains(id)) {
return _hostedValues.addEntries(<MapEntry<String, (T?, bool)>>[MapEntry<String, (T?, bool)>(id, (null, isRequired))]);
}
}
/// Get the value of a hosted state variable using it's ID.
T? getValue<T>(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<T>(String id, T data, {bool isRequired = false}) {
prepareValue(id, isRequired);
_hostedValues[id] = (data, isRequired);
_hostedValueValidationStreamController.add((id, false));
}
/// Validate hosted values.
bool validateValues(List<String> 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<FormGroupStructure> _formGroups = <FormGroupStructure>[];
//S1 - Methods
/// Recursively returns the full path of the group ID.
String? getFullPathOfGroup(String targetGroupID, {List<FormGroupStructure>? 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: <FormGroupStructure>[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<FormGroupStructure>? 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<FormGroupStructure>? a, List<FormGroupStructure>? b) => <FormGroupStructure>[...?a, ...?b])
// ?.toList() ??
// <FormGroupStructure>[],
// );
// }
/// 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<String> 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<String, String> firstSetOfFieldsWithID = Map<String, String>.fromEntries(
// controllers.entries.where((MapEntry<String, String> c) => RegExp(formGroupID + r'-#[\d+]-' + groupStructure!.fields.first).hasMatch(c.key)).nonNulls.toList(),
// );
// }
/// Prepare the groupStructure.
// void _addFieldsToGroup(String structureID, List<String> fields, List<String>? 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 ?? <String>[]) {
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<String, String> firstSetOfFieldsWithID = Map<String, String>.fromEntries(
// controllers.entries.where((MapEntry<String, String> c) => RegExp(formGroupID + r'-#[\d+]-' + groupStructure!.fields.first).hasMatch(c.key)).nonNulls.toList(),
// );
// // get the fields IDs
// List<String> fieldsIDs = groupStructure!.fields.nonNulls.toList();
// print('fieldIDs: $fieldsIDs');
// // get the values IDs
// List<String> valuesIDs = groupStructure.values?.nonNulls.toList() ?? <String>[];
// 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<FormGroupInstance> instances = <FormGroupInstance>[];
// for (int index = 0; index < structure.count; index++) {
// final Map<String, dynamic> values = {};
// final Map<String, TextEditingController> 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<String, FormGroupValue> 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<FormGroupInstance> subInstances = [];
// for (int subIndex = 0; subIndex < subCount; subIndex++) {
// final Map<String, dynamic> subValues = {};
// final Map<String, TextEditingController> 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<String> 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<String> 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<String, String> firstSetOfFieldsWithID = Map<String, String>.fromEntries(
// controllers.entries.where((MapEntry<String, String> c) => RegExp(formGroupID + r'-#[\d+]-' + groupStructure!.fields.first).hasMatch(c.key)).nonNulls.toList(),
// );
// print('First set of fields: $firstSetOfFieldsWithID');
// get the fields IDs
List<String> fieldsIDs = groupStructure!.fields.nonNulls.toList();
print('fieldIDs: $fieldsIDs');
// get the values IDs
List<String> valuesIDs = groupStructure.values?.nonNulls.toList() ?? <String>[];
print('valueIDs: $valuesIDs');
// get the subGroups
List<FormGroupValue> subValues = <FormGroupValue>[];
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<FormGroupInstance> instances = <FormGroupInstance>[];
for (int i = 0; i < instancesCount; i++) {
instances.add(
FormGroupInstance(
composedID: '$formGroupID-#$i-',
fields: Map<String, String>.fromEntries(fieldsIDs.map((String id) => MapEntry<String, String>('$formGroupID-#$i-$id', value('$formGroupID-#$i-$id'))).toList()),
values: valuesIDs.isNotEmpty
? Map<String, dynamic>.fromEntries(valuesIDs.map((String a) => MapEntry<String, dynamic>('$formGroupID-#$i-$a', getValue<dynamic>('$formGroupID-#$i-$a'))).toList())
: <String, dynamic>{},
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<String, String> firstSetOfFieldsWithID = Map<String, String>.fromEntries(
// controllers.entries.where((MapEntry<String, String> 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<String> fieldsIDs = groupStructure!.fields.nonNulls.toList();
// print('fieldIDs: $fieldsIDs');
// // get the values IDs
// List<String> valuesIDs = groupStructure.values?.nonNulls.toList() ?? <String>[];
// 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<String, (String, bool)>? initialValues) {
if (initialValues != null) {
// Add in the initial field states...

View File

@@ -105,8 +105,8 @@ class _FormGroupWrapperState extends State<FormGroupWrapper> {
() {
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<FormGroupWrapper> {
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;
});
}

View File

@@ -6,7 +6,7 @@ class FormGroupStructure {
final String id;
final List<String> fields;
final List<String>? values;
final List<FormGroupStructure>? subGroups;
final List<(FormGroupStructure structure, int initialCount)>? subGroups;
FormGroupStructure({
required this.id,
required this.fields,
@@ -18,7 +18,7 @@ class FormGroupStructure {
String? id,
List<String>? fields,
List<String>? values,
List<FormGroupStructure>? 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) => <String, dynamic>{'structure': x.$1.toMap(), 'initialCount': x.$2}).toList(),
};
}
@@ -42,7 +42,9 @@ class FormGroupStructure {
id: map['id'] as String,
fields: List<String>.from(map['fields'] as List<String>),
values: map['values'] != null ? List<String>.from(map['values'] as List<String>) : null,
subGroups: map['subGroups'] != null ? (map['subGroups'] as List<Map<String, dynamic>>).map((Map<String, dynamic> f) => FormGroupStructure.fromMap(f)).toList() : null,
subGroups: map['subGroups'] != null
? (map['subGroups'] as List<Map<String, dynamic>>).map((Map<String, dynamic> 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;
}
}