Files
astromic_helpers/lib/src/form/src/controller.dart
2025-04-08 15:20:52 +02:00

662 lines
28 KiB
Dart

//s1 Imports
//s2 Core Package Imports
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:flutter/scheduler.dart';
//s2 1st-party Package Imports
import 'package:form_controller/form_controller.dart';
//s2 3rd-party Package Imports
//s2 Dependancies Imports
//s3 Routes
//s3 Services
//s3 Models & Widgets
import 'enums/enums.exports.dart';
import 'models/form_group_instance.model.dart';
import 'models/form_group_structure.model.dart';
import 'models/form_group_value.model.dart';
//s1 Exports
/// A specialized form controller to handle form states,
class AstromicFormController extends FormController {
AstromicFormController({
Map<String, (String initialText, bool initialObscurity)>? initialValues,
this.errorStream,
}) : super(initialValues) {
// Add states and messages based on initial controller.
_addInitialControllers(initialValues);
}
//SECTION - Overrides
@override
TextEditingController controller(String id, {String? initialText, bool isObscure = false}) {
TextEditingController ret = super.controller(id, initialText: initialText, isObscure: isObscure);
//
if (getState(id) == null) {
fieldStates.addEntries(<MapEntry<String, AstromicFieldState>>[MapEntry<String, AstromicFieldState>(id, AstromicFieldState.idle)]);
fieldMessages.addEntries(<MapEntry<String, String?>>[MapEntry<String, String?>(id, null)]);
}
SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
if (ret.text.isEmpty) {
ret.text = initialText ?? '';
}
});
return ret;
}
@override
void removeController(String id) {
super.removeController(id);
if (fieldStates.containsKey(id)) {
fieldStates.remove(id);
}
if (fieldMessages.containsKey(id)) {
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.id}: Group fields should NOT be empty.');
// Validate subgroups (if any)
_validateSubGroups(groupStructure);
// Add structure to registry
_formGroups.add(groupStructure);
// Initialize the group instances
_initializeGroupControllers(groupStructure, initialCount);
}
/// Recursively initialize controllers for the group and its subgroups
void _initializeGroupControllers(FormGroupStructure groupStructure, int initialCount, {String parentPrefix = ''}) {
// Add main group fields/values
for (int groupIndex = 0; groupIndex < initialCount; groupIndex++) {
_addGroupControllers(groupStructure, groupIndex, parentPrefix: parentPrefix);
// Recursively handle subgroups
if (groupStructure.subGroups != null && groupStructure.subGroups!.isNotEmpty) {
for (final (FormGroupStructure subGroup, int subgroupInitialCount) in groupStructure.subGroups!) {
final String subgroupPrefix = parentPrefix.isEmpty
? standeredGroupFormat(groupStructure.id, groupIndex, subGroup.id)
: '$parentPrefix->${standeredGroupFormat(groupStructure.id, groupIndex, subGroup.id)}'; // Add to parentPrefix only once
// Initialize subgroup controllers recursively
for (int subIndex = 0; subIndex < subgroupInitialCount; subIndex++) {
_initializeGroupControllers(subGroup, subgroupInitialCount, parentPrefix: subgroupPrefix);
}
}
}
}
}
/// Validate subgroups recursively
void _validateSubGroups(FormGroupStructure groupStructure) {
groupStructure.subGroups?.forEach((subGroupTuple) {
final subGroup = subGroupTuple.$1;
assert(subGroup.fields.isNotEmpty, '${subGroup.id}: Subgroup fields should NOT be empty.');
// Recursively validate subgroups of subgroups
if (subGroup.subGroups != null) {
_validateSubGroups(subGroup);
}
});
}
// 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...
fieldStates.addEntries(initialValues.entries.map((MapEntry<String, (String, bool)> e) => MapEntry<String, AstromicFieldState>(
e.key, // controller ID
AstromicFieldState.idle, // Initial state of any new controller is Idle
)));
// Add in the initial field messages...
fieldMessages.addEntries(initialValues.entries.toList().map((MapEntry<String, (String, bool)> e) => MapEntry<String, String?>(
e.key, // Controller ID
null, // The initial message it has which is Null
)));
}
}
//!SECTION
}