Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions lib/src/field/cubit/field_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ class FieldCubit<T, E extends Object> extends Cubit<FieldState<T, E>> {
asyncError: state.asyncError,
autovalidate: state.autovalidate,
readOnly: state.readOnly,
isDirty: true,
wasModified: value != _initialValue,
status:
validationError == null ? FieldStatus.valid : FieldStatus.invalid,
),
Expand All @@ -111,6 +113,8 @@ class FieldCubit<T, E extends Object> extends Cubit<FieldState<T, E>> {
autovalidate: state.autovalidate,
readOnly: state.readOnly,
status: FieldStatus.pending,
isDirty: true,
wasModified: value != _initialValue,
),
);

Expand All @@ -125,6 +129,8 @@ class FieldCubit<T, E extends Object> extends Cubit<FieldState<T, E>> {
autovalidate: state.autovalidate,
readOnly: state.readOnly,
status: FieldStatus.validating,
isDirty: state.isDirty,
wasModified: state.wasModified,
),
);

Expand All @@ -145,6 +151,8 @@ class FieldCubit<T, E extends Object> extends Cubit<FieldState<T, E>> {
asyncError: error,
autovalidate: state.autovalidate,
readOnly: state.readOnly,
isDirty: state.isDirty,
wasModified: state.wasModified,
status: error == null ? FieldStatus.valid : FieldStatus.invalid,
),
);
Expand All @@ -171,6 +179,8 @@ class FieldCubit<T, E extends Object> extends Cubit<FieldState<T, E>> {
autovalidate: state.autovalidate,
readOnly: state.readOnly,
status: FieldStatus.invalid,
isDirty: state.isDirty,
wasModified: state.wasModified,
),
);
}
Expand All @@ -192,6 +202,8 @@ class FieldCubit<T, E extends Object> extends Cubit<FieldState<T, E>> {
asyncError: state.asyncError,
autovalidate: state.autovalidate,
readOnly: state.readOnly,
isDirty: state.isDirty,
wasModified: state.wasModified,
status: error == null ? FieldStatus.valid : FieldStatus.invalid,
),
);
Expand All @@ -210,6 +222,8 @@ class FieldCubit<T, E extends Object> extends Cubit<FieldState<T, E>> {
autovalidate: autovalidate,
readOnly: state.readOnly,
status: state.status,
isDirty: state.isDirty,
wasModified: state.wasModified,
),
);
}
Expand All @@ -224,6 +238,8 @@ class FieldCubit<T, E extends Object> extends Cubit<FieldState<T, E>> {
autovalidate: state.autovalidate,
readOnly: true,
status: state.status,
isDirty: state.isDirty,
wasModified: state.wasModified,
),
);
}
Expand All @@ -237,6 +253,8 @@ class FieldCubit<T, E extends Object> extends Cubit<FieldState<T, E>> {
asyncError: state.asyncError,
autovalidate: state.autovalidate,
status: state.status,
isDirty: state.isDirty,
wasModified: state.wasModified,
),
);
}
Expand All @@ -248,6 +266,8 @@ class FieldCubit<T, E extends Object> extends Cubit<FieldState<T, E>> {
value: state.value,
autovalidate: state.autovalidate,
readOnly: state.readOnly,
isDirty: state.isDirty,
wasModified: state.wasModified,
),
);
}
Expand Down Expand Up @@ -289,6 +309,8 @@ class FieldState<T, E extends Object> with EquatableMixin {
this.autovalidate = false,
this.readOnly = false,
this.status = FieldStatus.valid,
this.isDirty = false,
this.wasModified = false,
});

/// Returns true if there are no errors.
Expand Down Expand Up @@ -337,6 +359,12 @@ class FieldState<T, E extends Object> with EquatableMixin {
/// The current status of the field.
final FieldStatus status;

/// Whether the value is different from the initial value.
final bool wasModified;

/// Whether this field has ever been changed via [FieldCubit.setValue].
final bool isDirty;

@override
List<Object?> get props => [
value,
Expand All @@ -345,5 +373,7 @@ class FieldState<T, E extends Object> with EquatableMixin {
autovalidate,
readOnly,
status,
isDirty,
wasModified,
];
}
45 changes: 27 additions & 18 deletions lib/src/form_group_cubit/form_group_cubit.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import 'dart:async';

import 'package:collection/collection.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:leancode_forms/src/field/cubit/field_cubit.dart';
import 'package:leancode_forms/src/utils/disposable.dart';
Expand Down Expand Up @@ -58,8 +56,6 @@ class FormGroupCubit extends Cubit<FormGroupState> with Disposable {
/// their validator called if they have autovalidate enabled.
final bool validateAll;

List<dynamic> _initialFieldsState = <dynamic>[];

StreamSubscription<dynamic>? _onFieldsChangeSubscription;
final _fieldsController = StreamController<dynamic>.broadcast();

Expand Down Expand Up @@ -114,6 +110,7 @@ class FormGroupCubit extends Cubit<FormGroupState> with Disposable {
emit(
FormGroupState(
wasModified: state.wasModified,
isDirty: state.isDirty,
fields: fields,
subforms: state.subforms,
validationEnabled: state.validationEnabled,
Expand All @@ -122,17 +119,10 @@ class FormGroupCubit extends Cubit<FormGroupState> with Disposable {

addDisposable(() => Future.wait(fields.map((e) => e.close())));

_initialFieldsState = getFieldValues();
// inform that the fields have changed
_fieldsController.add(null);
}

/// Returns a list of all field values.
@visibleForTesting
List<dynamic> getFieldValues() {
return state.fields.map<dynamic>((f) => f.state.value).toList();
}

/// Recursively calls validate on all subforms/fields if `state.validationEnabled` is true.
/// [enableAutovalidate] can enable autovalidate on this form.
///
Expand Down Expand Up @@ -210,6 +200,7 @@ class FormGroupCubit extends Cubit<FormGroupState> with Disposable {
emit(
FormGroupState(
wasModified: state.wasModified,
isDirty: state.isDirty,
fields: state.fields,
subforms: {...state.subforms, form},
validationEnabled: state.validationEnabled,
Expand All @@ -224,6 +215,7 @@ class FormGroupCubit extends Cubit<FormGroupState> with Disposable {
emit(
FormGroupState(
wasModified: state.wasModified,
isDirty: state.isDirty,
fields: state.fields,
subforms: {...state.subforms}..remove(form),
validationEnabled: state.validationEnabled,
Expand Down Expand Up @@ -257,6 +249,7 @@ class FormGroupCubit extends Cubit<FormGroupState> with Disposable {
emit(
FormGroupState(
wasModified: state.wasModified,
isDirty: state.isDirty,
fields: state.fields,
subforms: state.subforms,
validationEnabled: validationEnabled,
Expand All @@ -270,19 +263,30 @@ class FormGroupCubit extends Cubit<FormGroupState> with Disposable {
}

void _onFieldsStateChanged() {
if (validateAll) {
validateWithAutovalidate();
}

final subformsWereModified = state.subforms.any(
(subform) => subform.state.wasModified,
);
late final fieldsWereModified = !const DeepCollectionEquality()
.equals(_initialFieldsState, getFieldValues());

if (validateAll) {
validateWithAutovalidate();
}
final fieldsWereModified = state.fields.any(
(field) => field.state.wasModified,
);

final subformsAreDirty = state.subforms.any(
(subform) => subform.state.isDirty,
);

final fieldsAreDirty = state.fields.any(
(field) => field.state.isDirty,
);

emit(
FormGroupState(
wasModified: subformsWereModified || fieldsWereModified,
isDirty: subformsAreDirty || fieldsAreDirty,
fields: state.fields,
subforms: state.subforms,
validationEnabled: state.validationEnabled,
Expand All @@ -302,6 +306,7 @@ class FormGroupCubit extends Cubit<FormGroupState> with Disposable {
emit(
FormGroupState(
wasModified: state.wasModified,
isDirty: state.isDirty,
fields: state.fields,
subforms: state.subforms,
validationEnabled: state.validationEnabled,
Expand All @@ -322,16 +327,19 @@ class FormGroupState with EquatableMixin {
/// Creates a new [FormGroupState].
const FormGroupState({
this.wasModified = false,
this.isDirty = false,
this.fields = const [],
this.subforms = const {},
this.validationEnabled = true,
this.validating = false,
});

/// wasModified is true when any of the field values differ since the
/// last `registerFields` or when any of the subforms has wasModified=true.
/// Whether any of the fields differ from their initial value.
final bool wasModified;

/// Whether any of the fields or subforms were modified.
final bool isDirty;

/// List of all registered fields by this form.
final List<FieldCubit<dynamic, dynamic>> fields;

Expand All @@ -357,6 +365,7 @@ class FormGroupState with EquatableMixin {
@override
List<Object?> get props => [
wasModified,
isDirty,
fields,
subforms,
validationEnabled,
Expand Down
Loading
Loading