662 lines
28 KiB
Dart
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
|
|
}
|