From 0e4be1cc362972231c166d300767097b3f58b4e6 Mon Sep 17 00:00:00 2001 From: Petr Nymsa Date: Sat, 25 Jan 2025 22:22:55 +0100 Subject: [PATCH 1/8] Add date time input --- glade_forms/lib/src/core/core.dart | 12 +- .../src/core/{ => error}/convert_error.dart | 2 +- .../core/{ => error}/error_translator.dart | 2 +- .../core/{ => error}/glade_error_keys.dart | 0 .../core/{ => error}/glade_input_error.dart | 4 +- .../lib/src/core/{ => input}/glade_input.dart | 89 ++++- .../lib/src/core/input_dependencies.dart | 2 +- .../src/core/string_to_type_converter.dart | 4 +- .../lib/src/{core => utils}/object_ex.dart | 0 .../lib/src/{core => utils}/type_helper.dart | 0 .../specialized/date_time_validator.dart | 66 ++++ .../{ => specialized}/int_validator.dart | 0 .../{ => specialized}/string_validator.dart | 2 +- glade_forms/lib/src/validator/validator.dart | 4 +- .../glade_validator_error.dart | 2 +- glade_forms/pubspec.yaml | 2 + .../test/date_time_validator_test.dart | 303 ++++++++++++++++++ glade_forms/test/int_validator_test.dart | 2 +- 18 files changed, 475 insertions(+), 21 deletions(-) rename glade_forms/lib/src/core/{ => error}/convert_error.dart (94%) rename glade_forms/lib/src/core/{ => error}/error_translator.dart (91%) rename glade_forms/lib/src/core/{ => error}/glade_error_keys.dart (100%) rename glade_forms/lib/src/core/{ => error}/glade_input_error.dart (84%) rename glade_forms/lib/src/core/{ => input}/glade_input.dart (88%) rename glade_forms/lib/src/{core => utils}/object_ex.dart (100%) rename glade_forms/lib/src/{core => utils}/type_helper.dart (100%) create mode 100644 glade_forms/lib/src/validator/specialized/date_time_validator.dart rename glade_forms/lib/src/validator/{ => specialized}/int_validator.dart (100%) rename glade_forms/lib/src/validator/{ => specialized}/string_validator.dart (98%) create mode 100644 glade_forms/test/date_time_validator_test.dart diff --git a/glade_forms/lib/src/core/core.dart b/glade_forms/lib/src/core/core.dart index 85f80e3..acca4b3 100644 --- a/glade_forms/lib/src/core/core.dart +++ b/glade_forms/lib/src/core/core.dart @@ -1,9 +1,9 @@ +export '../utils/type_helper.dart'; export 'changes_info.dart'; -export 'convert_error.dart'; -export 'error_translator.dart'; -export 'glade_error_keys.dart'; -export 'glade_input.dart'; -export 'glade_input_error.dart'; +export 'error/convert_error.dart'; +export 'error/error_translator.dart'; +export 'error/glade_error_keys.dart'; +export 'error/glade_input_error.dart'; +export 'input/glade_input.dart'; export 'input_dependencies.dart'; export 'string_to_type_converter.dart'; -export 'type_helper.dart'; diff --git a/glade_forms/lib/src/core/convert_error.dart b/glade_forms/lib/src/core/error/convert_error.dart similarity index 94% rename from glade_forms/lib/src/core/convert_error.dart rename to glade_forms/lib/src/core/error/convert_error.dart index 4243aa2..8dda998 100644 --- a/glade_forms/lib/src/core/convert_error.dart +++ b/glade_forms/lib/src/core/error/convert_error.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:glade_forms/src/core/glade_input_error.dart'; +import 'package:glade_forms/src/core/error/glade_input_error.dart'; /// Before validation when converer from string to prpoer type failed. typedef OnConvertError = String Function(String? rawInput, {Object? key}); diff --git a/glade_forms/lib/src/core/error_translator.dart b/glade_forms/lib/src/core/error/error_translator.dart similarity index 91% rename from glade_forms/lib/src/core/error_translator.dart rename to glade_forms/lib/src/core/error/error_translator.dart index 2bfbced..c477d78 100644 --- a/glade_forms/lib/src/core/error_translator.dart +++ b/glade_forms/lib/src/core/error/error_translator.dart @@ -1,6 +1,6 @@ // ignore_for_file: prefer-match-file-name -import 'package:glade_forms/src/core/glade_input_error.dart'; +import 'package:glade_forms/src/core/error/glade_input_error.dart'; import 'package:glade_forms/src/core/input_dependencies.dart'; typedef ErrorTranslator = String Function( diff --git a/glade_forms/lib/src/core/glade_error_keys.dart b/glade_forms/lib/src/core/error/glade_error_keys.dart similarity index 100% rename from glade_forms/lib/src/core/glade_error_keys.dart rename to glade_forms/lib/src/core/error/glade_error_keys.dart diff --git a/glade_forms/lib/src/core/glade_input_error.dart b/glade_forms/lib/src/core/error/glade_input_error.dart similarity index 84% rename from glade_forms/lib/src/core/glade_input_error.dart rename to glade_forms/lib/src/core/error/glade_input_error.dart index 349d251..6e1bbce 100644 --- a/glade_forms/lib/src/core/glade_input_error.dart +++ b/glade_forms/lib/src/core/error/glade_input_error.dart @@ -1,5 +1,5 @@ -import 'package:glade_forms/src/core/convert_error.dart'; -import 'package:glade_forms/src/core/glade_error_keys.dart'; +import 'package:glade_forms/src/core/error/convert_error.dart'; +import 'package:glade_forms/src/core/error/glade_error_keys.dart'; import 'package:glade_forms/src/validator/validator.dart'; abstract class GladeInputError { diff --git a/glade_forms/lib/src/core/glade_input.dart b/glade_forms/lib/src/core/input/glade_input.dart similarity index 88% rename from glade_forms/lib/src/core/glade_input.dart rename to glade_forms/lib/src/core/input/glade_input.dart index b68393b..1c6bd4e 100644 --- a/glade_forms/lib/src/core/glade_input.dart +++ b/glade_forms/lib/src/core/input/glade_input.dart @@ -4,12 +4,13 @@ import 'package:collection/collection.dart'; import 'package:flutter/widgets.dart'; import 'package:glade_forms/src/converters/glade_type_converters.dart'; import 'package:glade_forms/src/core/changes_info.dart'; -import 'package:glade_forms/src/core/convert_error.dart'; -import 'package:glade_forms/src/core/error_translator.dart'; +import 'package:glade_forms/src/core/error/convert_error.dart'; +import 'package:glade_forms/src/core/error/error_translator.dart'; import 'package:glade_forms/src/core/input_dependencies.dart'; import 'package:glade_forms/src/core/string_to_type_converter.dart'; -import 'package:glade_forms/src/core/type_helper.dart'; +import 'package:glade_forms/src/utils/type_helper.dart'; import 'package:glade_forms/src/model/glade_model.dart'; +import 'package:glade_forms/src/validator/specialized/date_time_validator.dart'; import 'package:glade_forms/src/validator/validator.dart'; import 'package:glade_forms/src/validator/validator_result.dart'; import 'package:meta/meta.dart'; @@ -18,6 +19,8 @@ typedef ValueComparator = bool Function(T? initial, T? value); typedef ValidatorFactory = ValidatorInstance Function(GladeValidator v); typedef StringValidatorFactory = ValidatorInstance Function(StringValidator validator); typedef IntValidatorFactory = ValidatorInstance Function(IntValidator validator); +typedef DateTimeValidatorFactory = ValidatorInstance Function(DateTimeValidator validator); +typedef DateTimeValidatorFactoryNullable = ValidatorInstance Function(DateTimeValidatorNullable validator); typedef OnChange = void Function(ChangesInfo info); typedef OnDependencyChange = void Function(List updateInputKeys); typedef ValueTransform = T Function(T input); @@ -26,6 +29,8 @@ typedef StringInput = GladeInput; typedef StringInputNullable = GladeInput; typedef IntInput = GladeInput; typedef BooleanInput = GladeInput; +typedef DateTimeInput = GladeInput; +typedef DateTimeNullableInput = GladeInput; class GladeInput { /// Compares initial and current value. @@ -406,6 +411,84 @@ class GladeInput { trackUnchanged: trackUnchanged, ); + static DateTimeInput dateTimeInput({ + required DateTime value, + String? inputKey, + DateTime? initialValue, + DateTimeValidatorFactory? validator, + bool pure = true, + ErrorTranslator? translateError, + DefaultTranslations? defaultTranslations, + ValueComparator? valueComparator, + InputDependenciesFactory? dependencies, + OnChange? onChange, + OnDependencyChange? onDependencyChange, + TextEditingController? textEditingController, + bool useTextEditingController = false, + ValueTransform? valueTransform, + bool trackUnchanged = true, + }) { + final validatorInstance = validator?.call(DateTimeValidator()) ?? DateTimeValidator().build(); + + return GladeInput._( + value: value, + initialValue: initialValue ?? value, + validatorInstance: validatorInstance, + isPure: pure, + translateError: translateError, + defaultTranslations: defaultTranslations, + valueComparator: valueComparator, + inputKey: inputKey, + dependenciesFactory: dependencies, + stringToValueConverter: GladeTypeConverters.dateTimeIso8601, + onChange: onChange, + onDependencyChange: onDependencyChange, + textEditingController: textEditingController, + useTextEditingController: useTextEditingController, + valueTransform: valueTransform, + trackUnchanged: trackUnchanged, + ); + } + + static DateTimeNullableInput dateTimeInputNullable({ + required DateTime? value, + String? inputKey, + DateTime? initialValue, + DateTimeValidatorFactoryNullable? validator, + bool pure = true, + ErrorTranslator? translateError, + DefaultTranslations? defaultTranslations, + ValueComparator? valueComparator, + InputDependenciesFactory? dependencies, + OnChange? onChange, + OnDependencyChange? onDependencyChange, + TextEditingController? textEditingController, + bool useTextEditingController = false, + ValueTransform? valueTransform, + bool trackUnchanged = true, + }) { + final validatorInstance = validator?.call(DateTimeValidatorNullable()) ?? DateTimeValidatorNullable().build(); + + return GladeInput._( + value: value, + initialValue: initialValue ?? value, + validatorInstance: validatorInstance, + isPure: pure, + translateError: translateError, + defaultTranslations: defaultTranslations, + valueComparator: valueComparator, + inputKey: inputKey, + dependenciesFactory: dependencies, + stringToValueConverter: GladeTypeConverters.dateTimeIso8601, + onChange: onChange, + onDependencyChange: onDependencyChange, + textEditingController: textEditingController, + useTextEditingController: useTextEditingController, + valueTransform: valueTransform, + trackUnchanged: trackUnchanged, + ); + } + static StringInput stringInput({ String? inputKey, String? value, diff --git a/glade_forms/lib/src/core/input_dependencies.dart b/glade_forms/lib/src/core/input_dependencies.dart index e4ebf12..1c17c8d 100644 --- a/glade_forms/lib/src/core/input_dependencies.dart +++ b/glade_forms/lib/src/core/input_dependencies.dart @@ -2,7 +2,7 @@ import 'package:collection/collection.dart'; import 'package:glade_forms/src/core/core.dart'; -import 'package:glade_forms/src/core/object_ex.dart'; +import 'package:glade_forms/src/utils/object_ex.dart'; typedef InputDependencies = List>; typedef InputDependenciesFactory = InputDependencies Function(); diff --git a/glade_forms/lib/src/core/string_to_type_converter.dart b/glade_forms/lib/src/core/string_to_type_converter.dart index 95c5a7c..581f70b 100644 --- a/glade_forms/lib/src/core/string_to_type_converter.dart +++ b/glade_forms/lib/src/core/string_to_type_converter.dart @@ -1,5 +1,5 @@ -import 'package:glade_forms/src/core/convert_error.dart'; -import 'package:glade_forms/src/core/glade_error_keys.dart'; +import 'package:glade_forms/src/core/error/convert_error.dart'; +import 'package:glade_forms/src/core/error/glade_error_keys.dart'; typedef OnErrorCallback = ConvertError Function(String? rawValue, Object error); diff --git a/glade_forms/lib/src/core/object_ex.dart b/glade_forms/lib/src/utils/object_ex.dart similarity index 100% rename from glade_forms/lib/src/core/object_ex.dart rename to glade_forms/lib/src/utils/object_ex.dart diff --git a/glade_forms/lib/src/core/type_helper.dart b/glade_forms/lib/src/utils/type_helper.dart similarity index 100% rename from glade_forms/lib/src/core/type_helper.dart rename to glade_forms/lib/src/utils/type_helper.dart diff --git a/glade_forms/lib/src/validator/specialized/date_time_validator.dart b/glade_forms/lib/src/validator/specialized/date_time_validator.dart new file mode 100644 index 0000000..d4d01fc --- /dev/null +++ b/glade_forms/lib/src/validator/specialized/date_time_validator.dart @@ -0,0 +1,66 @@ +// ignore_for_file: prefer-single-declaration-per-file + +import 'package:glade_forms/src/src.dart'; +import 'package:netglade_utils/netglade_utils.dart'; + +class DateTimeValidator extends GladeValidator { + /// Compares given value with [start] and [end] values. With [inclusiveInterval] set to true(default), the comparison is inclusive. + void isBetween({ + required DateTime start, + required DateTime end, + OnValidateError? devError, + Object? key, + ShouldValidateCallback? shouldValidate, + bool inclusiveInterval = true, + bool includeTime = true, + }) => + satisfy( + (value) { + final comparedValue = includeTime ? value : value.withoutTime; + final startValue = includeTime ? start : start.withoutTime; + final endValue = includeTime ? end : end.withoutTime; + + return inclusiveInterval + ? comparedValue.isAfterOrSame(startValue, includeTime: includeTime) && + comparedValue.isBeforeOrSame(endValue, includeTime: includeTime) + : comparedValue.isAfter(startValue) && value.isBefore(endValue); + }, + devError: devError ?? + (value) => + 'Value ${value ?? 'NULL'} (inclusiveInterval: $inclusiveInterval) is not in between $start and $end.', + key: key ?? GladeErrorKeys.intCompareError, + shouldValidate: shouldValidate, + ); +} + +class DateTimeValidatorNullable extends GladeValidator { + /// Compares given value with [start] and [end] values. With [inclusiveInterval] set to true(default), the comparison is inclusive. + void isBetween({ + required DateTime start, + required DateTime end, + OnValidateError? devError, + Object? key, + ShouldValidateCallback? shouldValidate, + bool inclusiveInterval = true, + bool includeTime = true, + }) => + satisfy( + (value) { + if (value == null) return false; + + final comparedValue = includeTime ? value : value.withoutTime; + final startValue = includeTime ? start : start.withoutTime; + final endValue = includeTime ? end : end.withoutTime; + + return inclusiveInterval + ? comparedValue.isAfterOrSame(startValue, includeTime: includeTime) && + comparedValue.isBeforeOrSame(endValue, includeTime: includeTime) + : comparedValue.isAfter(startValue) && value.isBefore(endValue); + }, + devError: devError ?? + (value) => + 'Value ${value ?? 'NULL'} (inclusiveInterval: $inclusiveInterval) is not in between $start and $end.', + key: key ?? GladeErrorKeys.intCompareError, + shouldValidate: shouldValidate, + ); +} diff --git a/glade_forms/lib/src/validator/int_validator.dart b/glade_forms/lib/src/validator/specialized/int_validator.dart similarity index 100% rename from glade_forms/lib/src/validator/int_validator.dart rename to glade_forms/lib/src/validator/specialized/int_validator.dart diff --git a/glade_forms/lib/src/validator/string_validator.dart b/glade_forms/lib/src/validator/specialized/string_validator.dart similarity index 98% rename from glade_forms/lib/src/validator/string_validator.dart rename to glade_forms/lib/src/validator/specialized/string_validator.dart index 8e23624..208bcd3 100644 --- a/glade_forms/lib/src/validator/string_validator.dart +++ b/glade_forms/lib/src/validator/specialized/string_validator.dart @@ -1,4 +1,4 @@ -import 'package:glade_forms/src/core/glade_error_keys.dart'; +import 'package:glade_forms/src/core/error/glade_error_keys.dart'; import 'package:glade_forms/src/validator/validator.dart'; class StringValidator extends GladeValidator { diff --git a/glade_forms/lib/src/validator/validator.dart b/glade_forms/lib/src/validator/validator.dart index e4dc6d4..2245ad8 100644 --- a/glade_forms/lib/src/validator/validator.dart +++ b/glade_forms/lib/src/validator/validator.dart @@ -1,7 +1,7 @@ export 'glade_validator.dart'; -export 'int_validator.dart'; export 'part/part.dart'; export 'regex_patterns.dart'; -export 'string_validator.dart'; +export 'specialized/int_validator.dart'; +export 'specialized/string_validator.dart'; export 'validator_error/validator_error.dart'; export 'validator_instance.dart'; diff --git a/glade_forms/lib/src/validator/validator_error/glade_validator_error.dart b/glade_forms/lib/src/validator/validator_error/glade_validator_error.dart index 4660ce0..5cfaf3e 100644 --- a/glade_forms/lib/src/validator/validator_error/glade_validator_error.dart +++ b/glade_forms/lib/src/validator/validator_error/glade_validator_error.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:glade_forms/src/core/glade_input_error.dart'; +import 'package:glade_forms/src/core/error/glade_input_error.dart'; import 'package:glade_forms/src/validator/validator_error/value_null_error.dart'; /// When validation failed but we already have propert [T] value. diff --git a/glade_forms/pubspec.yaml b/glade_forms/pubspec.yaml index cef5f9a..1f488b1 100644 --- a/glade_forms/pubspec.yaml +++ b/glade_forms/pubspec.yaml @@ -13,11 +13,13 @@ resolution: workspace # Add regular dependencies here. dependencies: + clock: ^1.1.1 collection: ^1.17.1 equatable: ^2.0.5 flutter: sdk: flutter meta: ^1.9.1 + netglade_utils: ^2.4.1 provider: ^6.0.5 dev_dependencies: diff --git a/glade_forms/test/date_time_validator_test.dart b/glade_forms/test/date_time_validator_test.dart new file mode 100644 index 0000000..928a6f3 --- /dev/null +++ b/glade_forms/test/date_time_validator_test.dart @@ -0,0 +1,303 @@ +// ignore_for_file: avoid-positional-record-field-access, avoid_redundant_argument_values, avoid-passing-default-values, avoid_redundant_argument_values + +import 'package:glade_forms/src/validator/specialized/date_time_validator.dart'; +import 'package:test/test.dart'; + +void main() { + group('inBetween', () { + for (final testCase in [ + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 2, 15, 15, 20), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 2, 15, 16, 20), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 2, 15, 15, 19), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 31, 18, 20, 24), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 25, 18, 20, 24), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 31, 18, 20, 23), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 31, 18, 20, 25), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 2), + isValid: false, + ), + ].indexed) { + test( + '(${testCase.$1}): ${testCase.$2.test} should be valid between ${testCase.$2.start} - ${testCase.$2.end} when testing inclusive and with included time', + () { + final validator = (DateTimeValidator() + // ignore: avoid_redundant_argument_values, be explcit in tests. + ..isBetween(start: testCase.$2.start, end: testCase.$2.end, includeTime: true, inclusiveInterval: true)) + .build(); + + final result = validator.validate(testCase.$2.test); + + expect(result.isValid, equals(testCase.$2.isValid)); + expect(result.isInvalid, equals(!testCase.$2.isValid)); + }, + ); + } + + for (final testCase in [ + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 2, 15, 15, 20), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 2, 15, 16, 20), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 2, 15, 15, 19), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 31, 18, 20, 24), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 25, 18, 20, 24), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 31, 18, 20, 23), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 31, 18, 20, 25), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 2), + isValid: false, + ), + ].indexed) { + test( + '(${testCase.$1}): ${testCase.$2.test} should be valid between ${testCase.$2.start} - ${testCase.$2.end} when testing inclusive and but without included time', + () { + final validator = (DateTimeValidator() + // ignore: avoid_redundant_argument_values, be explcit in tests., + ..isBetween( + start: testCase.$2.start, end: testCase.$2.end, includeTime: false, inclusiveInterval: true)) + .build(); + + final result = validator.validate(testCase.$2.test); + + expect(result.isValid, equals(testCase.$2.isValid)); + expect(result.isInvalid, equals(!testCase.$2.isValid)); + }, + ); + } + + for (final testCase in [ + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 2, 15, 15, 20), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 2, 15, 16, 20), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 2, 15, 15, 19), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 31, 18, 20, 24), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 25, 18, 20, 24), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 31, 18, 20, 23), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 31, 18, 20, 25), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 2), + isValid: false, + ), + ].indexed) { + test( + '(${testCase.$1}): ${testCase.$2.test} should be valid between ${testCase.$2.start} - ${testCase.$2.end} when testing without inclusive and without included time', + () { + final validator = (DateTimeValidator() + // ignore: avoid_redundant_argument_values, be explcit in tests., + ..isBetween( + start: testCase.$2.start, end: testCase.$2.end, includeTime: false, inclusiveInterval: false)) + .build(); + + final result = validator.validate(testCase.$2.test); + + expect(result.isValid, equals(testCase.$2.isValid)); + expect(result.isInvalid, equals(!testCase.$2.isValid)); + }, + ); + } + + for (final testCase in [ + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 2, 15, 15, 20), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 2, 15, 16, 20), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 2, 15, 15, 19), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 31, 18, 20, 24), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 25, 18, 20, 24), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 31, 18, 20, 23), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 1, 31, 18, 20, 25), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + end: DateTime(2025, 1, 31, 18, 20, 24), + test: DateTime(2025, 2), + isValid: false, + ), + ].indexed) { + test( + '(${testCase.$1}): ${testCase.$2.test} should be valid between ${testCase.$2.start} - ${testCase.$2.end} when testing without inclusive but with included time', + () { + final validator = (DateTimeValidator() + ..isBetween( + start: testCase.$2.start, + end: testCase.$2.end, + inclusiveInterval: false, + // ignore: avoid_redundant_argument_values, be explicti + includeTime: true, + )) + .build(); + + final result = validator.validate(testCase.$2.test); + + expect(result.isValid, equals(testCase.$2.isValid)); + expect(result.isInvalid, equals(!testCase.$2.isValid)); + }, + ); + } + }); +} diff --git a/glade_forms/test/int_validator_test.dart b/glade_forms/test/int_validator_test.dart index 9d09b35..96a737f 100644 --- a/glade_forms/test/int_validator_test.dart +++ b/glade_forms/test/int_validator_test.dart @@ -1,6 +1,6 @@ // ignore_for_file: avoid-unsafe-collection-methods -import 'package:glade_forms/src/validator/int_validator.dart'; +import 'package:glade_forms/src/validator/specialized/int_validator.dart'; import 'package:test/test.dart'; void main() { From 93bf156db349415fed5afeb59828791c590a6904 Mon Sep 17 00:00:00 2001 From: Petr Nymsa Date: Sun, 26 Jan 2025 15:59:44 +0100 Subject: [PATCH 2/8] Refactor typedefs into sub-classes. Add DateTime specialised variant --- glade_forms/CHANGELOG.md | 20 +- glade_forms/README.md | 31 +- glade_forms/example/lib/example.dart | 16 +- .../src/converters/glade_type_converters.dart | 22 ++ glade_forms/lib/src/core/core.dart | 8 +- glade_forms/lib/src/core/error/error.dart | 4 + .../lib/src/core/input/glade_bool_input.dart | 25 ++ .../src/core/input/glade_date_time_input.dart | 54 +++ .../lib/src/core/input/glade_input.dart | 321 +++--------------- .../lib/src/core/input/glade_int_input.dart | 54 +++ .../src/core/input/glade_string_input.dart | 30 ++ glade_forms/lib/src/core/input/input.dart | 5 + .../lib/src/validator/glade_validator.dart | 1 + .../specialized/date_time_validator.dart | 3 + .../validator/specialized/int_validator.dart | 73 +++- .../specialized/string_validator.dart | 2 + .../src/widgets/glade_model_debug_info.dart | 4 +- glade_forms/pubspec.yaml | 2 +- .../test/date_time_validator_test.dart | 11 +- glade_forms/test/glade_input_test.dart | 32 +- glade_forms/test/model/glade_model_test.dart | 6 +- glade_forms/test/model/group_edit_test.dart | 10 +- storybook/lib/debug_model.dart | 40 +-- .../complex_object_mapping_example.dart | 2 +- .../checkbox_dependency_change.dart | 25 +- .../one_checkbox_deps_validation.dart | 25 +- .../onchange/two_way_checkbox_change.dart | 30 +- .../lib/usecases/quickstart_example.dart | 16 +- .../issue48_text_controller_example.dart | 22 +- 29 files changed, 464 insertions(+), 430 deletions(-) create mode 100644 glade_forms/lib/src/core/error/error.dart create mode 100644 glade_forms/lib/src/core/input/glade_bool_input.dart create mode 100644 glade_forms/lib/src/core/input/glade_date_time_input.dart create mode 100644 glade_forms/lib/src/core/input/glade_int_input.dart create mode 100644 glade_forms/lib/src/core/input/glade_string_input.dart create mode 100644 glade_forms/lib/src/core/input/input.dart diff --git a/glade_forms/CHANGELOG.md b/glade_forms/CHANGELOG.md index c7d1be4..18a0bd8 100644 --- a/glade_forms/CHANGELOG.md +++ b/glade_forms/CHANGELOG.md @@ -1,3 +1,19 @@ +## 4.0.0 + +**Breaking change release** + +- Specialized versions of inputs such as `IntInput` or `StringInput` were renamed to `Glade*Input`. +- Removed specialized version factories. Now specialized versions are sub-classes of GladeInput + - This removes the weird possibility to create calls such as `StringInput.intInput()` which in the end threw a runtime exception due to type mismatch. + +- **Added** `GladeDateTimeInput` - specialized GladeInput for DateTime inputs. +- **Added** `inclusive` argument for `int` validations. +- `GladeIntInput` and `GladeDateTimeInput` offer *Nullable versions to support null values + - `StringInput` does not offer a nullable version as we believe that in most cases you don't really need to differentiate between a null string and an empty string. Feel free to open an issue if you disagree. +- + + + ## 3.1.1 - Add typedefs `IntInput` and `BooleanInput` - Fix GladeModelDebugInfo colors in DarkMode. @@ -5,7 +21,7 @@ ## 3.1.0 - updated dependencies -# 3.0.1 +## 3.0.1 - **[Fix]**: GladeFormProvider is missing key property [Fix 73](https://github.com/netglade/glade_forms/issues/73) - **[Fix]**: enable value transform with text editing controller [Fix 72](https://github.com/netglade/glade_forms/issues/72) - **[Fix]**: Input subscribed to its own changes in onDependencyChange [Fix 76](https://github.com/netglade/glade_forms/issues/76) @@ -13,7 +29,7 @@ ## 3.0.0 -**Breaking release** +**Breaking change release** - **[Add]**: Add `allowBlank` parameter to `isEmpty` string validator. - **[Add]**: Add `IntInput` as a specialized variant of GladeInput which has additional, int related, validations such as `isBetween`, `isMin`, `isMax` diff --git a/glade_forms/README.md b/glade_forms/README.md index 1fe4089..9ed45b9 100644 --- a/glade_forms/README.md +++ b/glade_forms/README.md @@ -19,7 +19,9 @@ A universal way to define form validators with support of translations. - [✨ Features](#-features) - [GladeInput](#gladeinput) - [String based input and TextEditingController](#string-based-input-and-texteditingcontroller) - - [StringInput](#stringinput) + - [GladeStringInput](#gladestringinput) + - [GladeIntInput](#gladeintinput) + - [More specialised variants](#more-specialised-variants) - [Validation](#validation) - [Skipping Specific Validation](#skipping-specific-validation) - [Using validators without GladeInput](#using-validators-without-gladeinput) @@ -55,18 +57,18 @@ setup a Model class that holds glade inputs together. ```dart class _Model extends GladeModel { - late GladeInput name; - late GladeInput age; - late GladeInput email; + late GladeStringInput name; + late GladeIntInput age; + late GladeStringInput email; @override List> get inputs => [name, age, email]; @override void initialize() { - name = GladeInput.stringInput(); - age = GladeInput.intInput(value: 0, useTextEditingController: true); - email = GladeInput.stringInput(validator: (validator) => (validator..isEmail()).build()); + name = GladeStringInput(); + age = GladeIntInput(value: 0, useTextEditingController: true); + email = GladeStringInput(validator: (validator) => (validator..isEmail()).build()); super.initialize(); } @@ -127,7 +129,7 @@ On each input we can define - **initialValue** - Initial input's value. Used with valueComparator and for computing `isUnchanged`. - **validator** - Input's value must satisfy validation to be *valid* input. - **translateError** - If there are validation errors, this function is use to translate those errors. - - **dependencies** (WIP) - Each input can depend on another inputs for listening changes. + - **dependencies** - Each input can depend on another inputs for listening changes. - **stringToValueConverter** - If input is used by TextField and `T` is not a `String`, value converter should be provided. - **valueComparator** - Sometimes it is handy to provide `initialValue` which will be never updated after input is mutated. `valueComparator` should be provided to compare `initialValue` and `value` if `T` is not comparable type by default. Note that GladeForms handle deep equality of collections and assumes that complex types are comparable by values. - **valueTransform** - transform `T` value into different `T` value. An example of usage can be sanitazation of string input (trim(),...). @@ -147,7 +149,7 @@ NOTE: Prior to *GladeForms 2.0*, each input generated its own TextEditingControl ---- -With the introduction of *GladeForms 2.0*, inputs by default (excluding the StringInput variant), do not create a TextEditingController. As a result, developers are required to use `updateValue()`, `updateValueWithString()` or directly set the `value` (via setter) to update the input's value. +With the introduction of *GladeForms 2.0*, inputs by default (excluding the `GladeStringInput` variant), do not create a TextEditingController. As a result, developers are required to use `updateValue()`, `updateValueWithString()` or directly set the `value` (via setter) to update the input's value. If your implementation involves an input paired with a TextField (or any similar widget that utilizes a TextEditingController), you should set `useTextEditingController` to true. @@ -158,13 +160,13 @@ Activating the useTextEditingController mode for a GladeInput results in a few b - Consequently, developers are advised to provide only the controller property and a validator to the widget. - While the use of updateValue (or similar methods) and resetToPure remains possible, be aware that these actions will override the text in the controller and reset text selection and other keyboard-related features. -#### StringInput +#### GladeStringInput -StringInput is specialized variant of GladeInput which has additional, string related, validations such as `isEmail`, `isUrl`, `maxLength` and more. +GladeStringInput is specialized variant of GladeInput which has additional, string related, validations such as `isEmail`, `isUrl`, `maxLength` and more. -Moreover `StringInput` by default uses TextEditingController under the hood. +Moreover `GladeStringInput` by default uses TextEditingController under the hood. -#### IntInput +#### GladeIntInput IntInput is specialized variant of GladeInput which has additional, int related, validations such as `isBetween`, `isMin`, `isMax` and more. @@ -179,6 +181,9 @@ final validator = (IntValidator()..isMax(max: 10)).build(); final result = validator.validate(5); // valid ``` +#### More specialised variants +Check out also `GladeBoolInput` and `GladeDateTimeInput` specialised variants. + ### Validation Validation is defined through part methods on ValidatorFactory such as `notNull()`, `satisfy()` and other parts. diff --git a/glade_forms/example/lib/example.dart b/glade_forms/example/lib/example.dart index 5b6d137..e0c3d08 100644 --- a/glade_forms/example/lib/example.dart +++ b/glade_forms/example/lib/example.dart @@ -3,20 +3,20 @@ import 'package:glade_forms/glade_forms.dart'; // ! When updating dont forget to update README.md quickstart as well class _Model extends GladeModel { - late StringInput name; - late IntInput age; - late StringInput email; - late IntInput income; + late GladeStringInput name; + late GladeIntInput age; + late GladeStringInput email; + late GladeIntInput income; @override List> get inputs => [name, age, email, income]; @override void initialize() { - name = GladeInput.stringInput(inputKey: 'name'); - age = GladeInput.intInput(value: 0, inputKey: 'age'); - email = GladeInput.stringInput(validator: (validator) => (validator..isEmail()).build(), inputKey: 'email'); - income = GladeInput.intInput( + name = GladeStringInput(inputKey: 'name'); + age = GladeIntInput(value: 0, inputKey: 'age'); + email = GladeStringInput(validator: (validator) => (validator..isEmail()).build(), inputKey: 'email'); + income = GladeIntInput( value: 10000, validator: (validator) => (validator..isMin(min: 1000)).build(), inputKey: 'income', diff --git a/glade_forms/lib/src/converters/glade_type_converters.dart b/glade_forms/lib/src/converters/glade_type_converters.dart index 39238e0..9272bb0 100644 --- a/glade_forms/lib/src/converters/glade_type_converters.dart +++ b/glade_forms/lib/src/converters/glade_type_converters.dart @@ -12,6 +12,17 @@ abstract final class GladeTypeConverters { converterBack: (rawInput) => rawInput.toString(), ); + static final intConverterNullable = StringToTypeConverter( + converter: (rawValue, cantConvert) { + if (rawValue == null) { + return null; + } + + return int.tryParse(rawValue) ?? cantConvert('Can not convert', rawValue: rawValue); + }, + converterBack: (rawInput) => rawInput?.toString(), + ); + static final boolConverter = StringToTypeConverter( converter: (rawValue, cantConvert) { if (rawValue == null) { @@ -33,4 +44,15 @@ abstract final class GladeTypeConverters { }, converterBack: (rawInput) => rawInput.toIso8601String(), ); + + static final dateTimeIso8601Nullable = StringToTypeConverter( + converter: (rawValue, cantConvert) { + if (rawValue == null) { + return null; + } + + return DateTime.tryParse(rawValue) ?? cantConvert('Can not convert', rawValue: rawValue); + }, + converterBack: (rawInput) => rawInput?.toIso8601String(), + ); } diff --git a/glade_forms/lib/src/core/core.dart b/glade_forms/lib/src/core/core.dart index acca4b3..6f50e13 100644 --- a/glade_forms/lib/src/core/core.dart +++ b/glade_forms/lib/src/core/core.dart @@ -1,9 +1,5 @@ -export '../utils/type_helper.dart'; export 'changes_info.dart'; -export 'error/convert_error.dart'; -export 'error/error_translator.dart'; -export 'error/glade_error_keys.dart'; -export 'error/glade_input_error.dart'; -export 'input/glade_input.dart'; +export 'error/error.dart'; +export 'input/input.dart'; export 'input_dependencies.dart'; export 'string_to_type_converter.dart'; diff --git a/glade_forms/lib/src/core/error/error.dart b/glade_forms/lib/src/core/error/error.dart new file mode 100644 index 0000000..bc00fc5 --- /dev/null +++ b/glade_forms/lib/src/core/error/error.dart @@ -0,0 +1,4 @@ +export 'convert_error.dart'; +export 'error_translator.dart'; +export 'glade_error_keys.dart'; +export 'glade_input_error.dart'; diff --git a/glade_forms/lib/src/core/input/glade_bool_input.dart b/glade_forms/lib/src/core/input/glade_bool_input.dart new file mode 100644 index 0000000..4d48645 --- /dev/null +++ b/glade_forms/lib/src/core/input/glade_bool_input.dart @@ -0,0 +1,25 @@ +import 'package:glade_forms/glade_forms.dart'; + +class GladeBoolInput extends GladeInput { + GladeBoolInput({ + super.inputKey, + super.value, + super.initialValue, + ValidatorFactory? validator, + super.isPure, + super.translateError, + super.valueComparator, + StringToTypeConverter? stringToValueConverter, + super.dependencies, + super.onChange, + super.onDependencyChange, + super.textEditingController, + super.useTextEditingController = false, + super.valueTransform, + super.defaultTranslations, + super.trackUnchanged = true, + }) : super.internalCreate( + stringToValueConverter: stringToValueConverter ?? GladeTypeConverters.boolConverter, + validatorInstance: validator?.call(GladeValidator()) ?? GladeValidator().build(), + ); +} diff --git a/glade_forms/lib/src/core/input/glade_date_time_input.dart b/glade_forms/lib/src/core/input/glade_date_time_input.dart new file mode 100644 index 0000000..ce2eb23 --- /dev/null +++ b/glade_forms/lib/src/core/input/glade_date_time_input.dart @@ -0,0 +1,54 @@ +// ignore_for_file: prefer-single-declaration-per-file + +import 'package:glade_forms/src/converters/converters.dart'; +import 'package:glade_forms/src/core/input/glade_input.dart'; +import 'package:glade_forms/src/core/string_to_type_converter.dart'; +import 'package:glade_forms/src/validator/specialized/date_time_validator.dart'; + +class GladeDateTimeInput extends GladeInput { + GladeDateTimeInput({ + super.inputKey, + super.value, + super.initialValue, + DateTimeValidatorFactory? validator, + super.isPure, + super.translateError, + super.valueComparator, + StringToTypeConverter? stringToValueConverter, + super.dependencies, + super.onChange, + super.onDependencyChange, + super.textEditingController, + super.useTextEditingController = false, + super.valueTransform, + super.defaultTranslations, + super.trackUnchanged = true, + }) : super.internalCreate( + stringToValueConverter: stringToValueConverter ?? GladeTypeConverters.dateTimeIso8601, + validatorInstance: validator?.call(DateTimeValidator()) ?? DateTimeValidator().build(), + ); +} + +class GladeDateTimeInputNullable extends GladeInput { + GladeDateTimeInputNullable({ + super.inputKey, + super.value, + super.initialValue, + DateTimeValidatorFactoryNullable? validator, + super.isPure, + super.translateError, + super.valueComparator, + StringToTypeConverter? stringToValueConverter, + super.dependencies, + super.onChange, + super.onDependencyChange, + super.textEditingController, + super.useTextEditingController = false, + super.valueTransform, + super.defaultTranslations, + super.trackUnchanged = true, + }) : super.internalCreate( + stringToValueConverter: stringToValueConverter ?? GladeTypeConverters.dateTimeIso8601Nullable, + validatorInstance: validator?.call(DateTimeValidatorNullable()) ?? DateTimeValidatorNullable().build(), + ); +} diff --git a/glade_forms/lib/src/core/input/glade_input.dart b/glade_forms/lib/src/core/input/glade_input.dart index 1c6bd4e..0a914ec 100644 --- a/glade_forms/lib/src/core/input/glade_input.dart +++ b/glade_forms/lib/src/core/input/glade_input.dart @@ -2,36 +2,23 @@ import 'dart:math'; import 'package:collection/collection.dart'; import 'package:flutter/widgets.dart'; -import 'package:glade_forms/src/converters/glade_type_converters.dart'; import 'package:glade_forms/src/core/changes_info.dart'; import 'package:glade_forms/src/core/error/convert_error.dart'; import 'package:glade_forms/src/core/error/error_translator.dart'; import 'package:glade_forms/src/core/input_dependencies.dart'; import 'package:glade_forms/src/core/string_to_type_converter.dart'; -import 'package:glade_forms/src/utils/type_helper.dart'; import 'package:glade_forms/src/model/glade_model.dart'; -import 'package:glade_forms/src/validator/specialized/date_time_validator.dart'; +import 'package:glade_forms/src/utils/type_helper.dart'; import 'package:glade_forms/src/validator/validator.dart'; import 'package:glade_forms/src/validator/validator_result.dart'; import 'package:meta/meta.dart'; typedef ValueComparator = bool Function(T? initial, T? value); -typedef ValidatorFactory = ValidatorInstance Function(GladeValidator v); -typedef StringValidatorFactory = ValidatorInstance Function(StringValidator validator); -typedef IntValidatorFactory = ValidatorInstance Function(IntValidator validator); -typedef DateTimeValidatorFactory = ValidatorInstance Function(DateTimeValidator validator); -typedef DateTimeValidatorFactoryNullable = ValidatorInstance Function(DateTimeValidatorNullable validator); + typedef OnChange = void Function(ChangesInfo info); typedef OnDependencyChange = void Function(List updateInputKeys); typedef ValueTransform = T Function(T input); -typedef StringInput = GladeInput; -typedef StringInputNullable = GladeInput; -typedef IntInput = GladeInput; -typedef BooleanInput = GladeInput; -typedef DateTimeInput = GladeInput; -typedef DateTimeNullableInput = GladeInput; - class GladeInput { /// Compares initial and current value. @protected @@ -153,31 +140,32 @@ class GladeInput { _bindedModel?.notifyInputUpdated(this); } - GladeInput._({ - required T value, + @internal + GladeInput.internalCreate({ required this.validatorInstance, - required bool isPure, - required this.valueComparator, - required String? inputKey, - required this.translateError, - required this.stringToValueConverter, - required InputDependenciesFactory? dependenciesFactory, - required this.defaultTranslations, - required this.onChange, - required this.onDependencyChange, - required ValueTransform? valueTransform, + String? inputKey, + T? value, T? initialValue, + bool isPure = true, + this.translateError, + this.valueComparator, + this.stringToValueConverter, + InputDependenciesFactory? dependencies, + this.onChange, + this.onDependencyChange, TextEditingController? textEditingController, bool useTextEditingController = false, + ValueTransform? valueTransform, + this.defaultTranslations, this.trackUnchanged = true, }) : assert( - dependenciesFactory == null || (onDependencyChange != null), - 'When dependencies are provided, provide onDependencyChange as well', + value != null || initialValue != null || TypeHelper.typeIsNullable(), + 'If type is not nullable, at least one of value or initialValue must be set (affected input: $inputKey)', ), _isPure = isPure, - _value = value, + _value = (value ?? initialValue) as T, _initialValue = initialValue, - dependenciesFactory = dependenciesFactory ?? (() => []), + dependenciesFactory = dependencies ?? (() => []), inputKey = inputKey ?? '__${T.runtimeType}__${Random().nextInt(100000000)}', _valueTransform = valueTransform, _textEditingController = textEditingController ?? @@ -190,6 +178,7 @@ class GladeInput { }, ) : null), + // ignore: avoid_bool_literals_in_conditional_expressions, cant be simplified. _useTextEditingController = textEditingController != null ? true : useTextEditingController { validatorInstance.bindInput(this); @@ -202,21 +191,13 @@ class GladeInput { /// At least one of [value] or [initialValue] MUST be set. factory GladeInput.create({ String? inputKey, - - /// Sets current value of input. - /// When value is null, it is set to [initialValue]. T? value, - - /// Initial value when GenericInput is created. - /// - /// This value can potentially differ from [value]. - /// Used for computing [isUnchanged]. T? initialValue, ValidatorFactory? validator, - bool pure = true, + bool isPure = true, ErrorTranslator? translateError, ValueComparator? valueComparator, - StringToTypeConverter? valueConverter, + StringToTypeConverter? stringToValueConverter, InputDependenciesFactory? dependencies, OnChange? onChange, OnDependencyChange? onDependencyChange, @@ -225,33 +206,25 @@ class GladeInput { ValueTransform? valueTransform, DefaultTranslations? defaultTranslations, bool trackUnchanged = true, - }) { - assert( - value != null || initialValue != null || TypeHelper.typeIsNullable(), - 'If type is not nullable, at least one of value or initialValue must be set', - ); - - final validatorInstance = validator?.call(GladeValidator()) ?? GladeValidator().build(); - - return GladeInput._( - value: (value ?? initialValue) as T, - isPure: pure, - validatorInstance: validatorInstance, - initialValue: initialValue, - translateError: translateError, - valueComparator: valueComparator, - inputKey: inputKey, - stringToValueConverter: valueConverter, - dependenciesFactory: dependencies, - onChange: onChange, - onDependencyChange: onDependencyChange, - textEditingController: textEditingController, - useTextEditingController: useTextEditingController, - valueTransform: valueTransform, - defaultTranslations: defaultTranslations, - trackUnchanged: trackUnchanged, - ); - } + }) => + GladeInput.internalCreate( + validatorInstance: validator?.call(GladeValidator()) ?? GladeValidator().build(), + inputKey: inputKey, + value: value, + initialValue: initialValue, + isPure: isPure, + translateError: translateError, + valueComparator: valueComparator, + stringToValueConverter: stringToValueConverter, + dependencies: dependencies, + onChange: onChange, + onDependencyChange: onDependencyChange, + textEditingController: textEditingController, + useTextEditingController: useTextEditingController, + valueTransform: valueTransform, + defaultTranslations: defaultTranslations, + trackUnchanged: trackUnchanged, + ); /// /// Useful for input which allows null value without additional validations. @@ -265,7 +238,7 @@ class GladeInput { ErrorTranslator? translateError, DefaultTranslations? defaultTranslations, ValueComparator? valueComparator, - StringToTypeConverter? valueConverter, + StringToTypeConverter? stringToValueConverter, InputDependenciesFactory? dependencies, OnChange? onChange, OnDependencyChange? onDependencyChange, @@ -281,9 +254,9 @@ class GladeInput { translateError: translateError, defaultTranslations: defaultTranslations, valueComparator: valueComparator, - valueConverter: valueConverter, + stringToValueConverter: stringToValueConverter, inputKey: inputKey, - pure: pure, + isPure: pure, dependencies: dependencies, onChange: onChange, onDependencyChange: onDependencyChange, @@ -304,7 +277,7 @@ class GladeInput { ErrorTranslator? translateError, DefaultTranslations? defaultTranslations, ValueComparator? valueComparator, - StringToTypeConverter? valueConverter, + StringToTypeConverter? stringToValueConverter, InputDependenciesFactory? dependencies, OnChange? onChange, OnDependencyChange? onDependencyChange, @@ -320,9 +293,9 @@ class GladeInput { translateError: translateError, defaultTranslations: defaultTranslations, valueComparator: valueComparator, - valueConverter: valueConverter, + stringToValueConverter: stringToValueConverter, inputKey: inputKey, - pure: pure, + isPure: pure, dependencies: dependencies, onChange: onChange, onDependencyChange: onDependencyChange, @@ -336,200 +309,6 @@ class GladeInput { // ignore: use_setters_to_change_properties, as method. void bindToModel(GladeModel model) => _bindedModel = model; - static IntInput intInput({ - required int value, - String? inputKey, - int? initialValue, - IntValidatorFactory? validator, - bool pure = true, - ErrorTranslator? translateError, - DefaultTranslations? defaultTranslations, - ValueComparator? valueComparator, - InputDependenciesFactory? dependencies, - OnChange? onChange, - OnDependencyChange? onDependencyChange, - TextEditingController? textEditingController, - bool useTextEditingController = false, - ValueTransform? valueTransform, - bool trackUnchanged = true, - }) { - final validatorInstance = validator?.call(IntValidator()) ?? IntValidator().build(); - - return GladeInput._( - value: value, - initialValue: initialValue ?? value, - validatorInstance: validatorInstance, - isPure: pure, - translateError: translateError, - defaultTranslations: defaultTranslations, - valueComparator: valueComparator, - inputKey: inputKey, - dependenciesFactory: dependencies, - stringToValueConverter: GladeTypeConverters.intConverter, - onChange: onChange, - onDependencyChange: onDependencyChange, - textEditingController: textEditingController, - useTextEditingController: useTextEditingController, - valueTransform: valueTransform, - trackUnchanged: trackUnchanged, - ); - } - - static BooleanInput boolInput({ - required bool value, - String? inputKey, - bool? initialValue, - ValidatorFactory? validator, - bool pure = true, - ErrorTranslator? translateError, - DefaultTranslations? defaultTranslations, - ValueComparator? valueComparator, - InputDependenciesFactory? dependencies, - OnChange? onChange, - OnDependencyChange? onDependencyChange, - TextEditingController? textEditingController, - bool useTextEditingController = false, - ValueTransform? valueTransform, - bool trackUnchanged = true, - }) => - GladeInput.create( - value: value, - initialValue: initialValue ?? value, - validator: validator, - pure: pure, - translateError: translateError, - defaultTranslations: defaultTranslations, - valueComparator: valueComparator, - inputKey: inputKey, - dependencies: dependencies, - valueConverter: GladeTypeConverters.boolConverter, - onChange: onChange, - onDependencyChange: onDependencyChange, - textEditingController: textEditingController, - useTextEditingController: useTextEditingController, - valueTransform: valueTransform, - trackUnchanged: trackUnchanged, - ); - - static DateTimeInput dateTimeInput({ - required DateTime value, - String? inputKey, - DateTime? initialValue, - DateTimeValidatorFactory? validator, - bool pure = true, - ErrorTranslator? translateError, - DefaultTranslations? defaultTranslations, - ValueComparator? valueComparator, - InputDependenciesFactory? dependencies, - OnChange? onChange, - OnDependencyChange? onDependencyChange, - TextEditingController? textEditingController, - bool useTextEditingController = false, - ValueTransform? valueTransform, - bool trackUnchanged = true, - }) { - final validatorInstance = validator?.call(DateTimeValidator()) ?? DateTimeValidator().build(); - - return GladeInput._( - value: value, - initialValue: initialValue ?? value, - validatorInstance: validatorInstance, - isPure: pure, - translateError: translateError, - defaultTranslations: defaultTranslations, - valueComparator: valueComparator, - inputKey: inputKey, - dependenciesFactory: dependencies, - stringToValueConverter: GladeTypeConverters.dateTimeIso8601, - onChange: onChange, - onDependencyChange: onDependencyChange, - textEditingController: textEditingController, - useTextEditingController: useTextEditingController, - valueTransform: valueTransform, - trackUnchanged: trackUnchanged, - ); - } - - static DateTimeNullableInput dateTimeInputNullable({ - required DateTime? value, - String? inputKey, - DateTime? initialValue, - DateTimeValidatorFactoryNullable? validator, - bool pure = true, - ErrorTranslator? translateError, - DefaultTranslations? defaultTranslations, - ValueComparator? valueComparator, - InputDependenciesFactory? dependencies, - OnChange? onChange, - OnDependencyChange? onDependencyChange, - TextEditingController? textEditingController, - bool useTextEditingController = false, - ValueTransform? valueTransform, - bool trackUnchanged = true, - }) { - final validatorInstance = validator?.call(DateTimeValidatorNullable()) ?? DateTimeValidatorNullable().build(); - - return GladeInput._( - value: value, - initialValue: initialValue ?? value, - validatorInstance: validatorInstance, - isPure: pure, - translateError: translateError, - defaultTranslations: defaultTranslations, - valueComparator: valueComparator, - inputKey: inputKey, - dependenciesFactory: dependencies, - stringToValueConverter: GladeTypeConverters.dateTimeIso8601, - onChange: onChange, - onDependencyChange: onDependencyChange, - textEditingController: textEditingController, - useTextEditingController: useTextEditingController, - valueTransform: valueTransform, - trackUnchanged: trackUnchanged, - ); - } - - static StringInput stringInput({ - String? inputKey, - String? value, - String? initialValue, - StringValidatorFactory? validator, - bool pure = true, - ErrorTranslator? translateError, - DefaultTranslations? defaultTranslations, - InputDependenciesFactory? dependencies, - OnChange? onChange, - OnDependencyChange? onDependencyChange, - TextEditingController? textEditingController, - bool useTextEditingController = true, - bool isRequired = true, - ValueTransform? valueTransform, - ValueComparator? valueComparator, - bool trackUnchanged = true, - }) { - final requiredInstance = validator?.call(StringValidator()..notEmpty()) ?? (StringValidator()..notEmpty()).build(); - final optionalInstance = validator?.call(StringValidator()) ?? StringValidator().build(); - - return GladeInput._( - value: value ?? initialValue ?? '', - isPure: pure, - initialValue: initialValue ?? '', - validatorInstance: isRequired ? requiredInstance : optionalInstance, - translateError: translateError, - defaultTranslations: defaultTranslations, - inputKey: inputKey, - dependenciesFactory: dependencies, - onChange: onChange, - onDependencyChange: onDependencyChange, - textEditingController: textEditingController, - useTextEditingController: useTextEditingController, - valueComparator: valueComparator, - stringToValueConverter: null, - valueTransform: valueTransform, - trackUnchanged: trackUnchanged, - ); - } - // * // * Public methods // * @@ -675,7 +454,7 @@ class GladeInput { ValueComparator? valueComparator, ValidatorInstance? validatorInstance, StringToTypeConverter? stringToValueConverter, - InputDependenciesFactory? dependenciesFactory, + InputDependenciesFactory? dependencies, T? initialValue, ErrorTranslator? translateError, T? value, @@ -689,12 +468,12 @@ class GladeInput { ValueTransform? valueTransform, bool? trackUnchanged, }) { - return GladeInput._( + return GladeInput.internalCreate( value: value ?? this.value, valueComparator: valueComparator ?? this.valueComparator, validatorInstance: validatorInstance ?? this.validatorInstance, stringToValueConverter: stringToValueConverter ?? this.stringToValueConverter, - dependenciesFactory: dependenciesFactory ?? this.dependenciesFactory, + dependencies: dependencies ?? this.dependenciesFactory, inputKey: inputKey ?? this.inputKey, initialValue: initialValue ?? this.initialValue, translateError: translateError ?? this.translateError, diff --git a/glade_forms/lib/src/core/input/glade_int_input.dart b/glade_forms/lib/src/core/input/glade_int_input.dart new file mode 100644 index 0000000..1a533ca --- /dev/null +++ b/glade_forms/lib/src/core/input/glade_int_input.dart @@ -0,0 +1,54 @@ +// ignore_for_file: prefer-single-declaration-per-file + +import 'package:glade_forms/src/converters/converters.dart'; +import 'package:glade_forms/src/core/input/glade_input.dart'; +import 'package:glade_forms/src/core/string_to_type_converter.dart'; +import 'package:glade_forms/src/validator/specialized/int_validator.dart'; + +class GladeIntInput extends GladeInput { + GladeIntInput({ + super.inputKey, + super.value, + super.initialValue, + IntValidatorFactory? validator, + super.isPure, + super.translateError, + super.valueComparator, + StringToTypeConverter? stringToValueConverter, + super.dependencies, + super.onChange, + super.onDependencyChange, + super.textEditingController, + super.useTextEditingController = false, + super.valueTransform, + super.defaultTranslations, + super.trackUnchanged = true, + }) : super.internalCreate( + stringToValueConverter: stringToValueConverter ?? GladeTypeConverters.intConverter, + validatorInstance: validator?.call(IntValidator()) ?? IntValidator().build(), + ); +} + +class GladeIntInputNullabe extends GladeInput { + GladeIntInputNullabe({ + super.inputKey, + super.value, + super.initialValue, + IntValidatorFactoryNullable? validator, + super.isPure, + super.translateError, + super.valueComparator, + StringToTypeConverter? stringToValueConverter, + super.dependencies, + super.onChange, + super.onDependencyChange, + super.textEditingController, + super.useTextEditingController = false, + super.valueTransform, + super.defaultTranslations, + super.trackUnchanged = true, + }) : super.internalCreate( + stringToValueConverter: stringToValueConverter ?? GladeTypeConverters.intConverterNullable, + validatorInstance: validator?.call(IntValidatorNullable()) ?? IntValidatorNullable().build(), + ); +} diff --git a/glade_forms/lib/src/core/input/glade_string_input.dart b/glade_forms/lib/src/core/input/glade_string_input.dart new file mode 100644 index 0000000..12992e6 --- /dev/null +++ b/glade_forms/lib/src/core/input/glade_string_input.dart @@ -0,0 +1,30 @@ +import 'package:glade_forms/src/core/input/glade_input.dart'; +import 'package:glade_forms/src/validator/specialized/string_validator.dart'; + +class GladeStringInput extends GladeInput { + GladeStringInput({ + super.inputKey, + String? value, + String? initialValue, + StringValidatorFactory? validator, + super.isPure, + super.translateError, + super.valueComparator, + super.stringToValueConverter, + super.dependencies, + super.onChange, + super.onDependencyChange, + super.textEditingController, + super.useTextEditingController = true, + super.valueTransform, + super.defaultTranslations, + super.trackUnchanged = true, + bool isRequired = true, + }) : super.internalCreate( + value: value ?? initialValue ?? '', + initialValue: initialValue ?? '', + validatorInstance: isRequired + ? (validator?.call(StringValidator()..notEmpty()) ?? (StringValidator()..notEmpty()).build()) + : (validator?.call(StringValidator()) ?? StringValidator().build()), + ); +} diff --git a/glade_forms/lib/src/core/input/input.dart b/glade_forms/lib/src/core/input/input.dart new file mode 100644 index 0000000..aabb36d --- /dev/null +++ b/glade_forms/lib/src/core/input/input.dart @@ -0,0 +1,5 @@ +export 'glade_bool_input.dart'; +export 'glade_date_time_input.dart'; +export 'glade_input.dart'; +export 'glade_int_input.dart'; +export 'glade_string_input.dart'; diff --git a/glade_forms/lib/src/validator/glade_validator.dart b/glade_forms/lib/src/validator/glade_validator.dart index 2933a04..d84ed83 100644 --- a/glade_forms/lib/src/validator/glade_validator.dart +++ b/glade_forms/lib/src/validator/glade_validator.dart @@ -7,6 +7,7 @@ import 'package:glade_forms/src/validator/validator_instance.dart'; typedef ValidateFunction = GladeValidatorError? Function(T value); typedef ValidateFunctionWithKey = GladeValidatorError? Function(T value, Object? key); +typedef ValidatorFactory = ValidatorInstance Function(GladeValidator v); class GladeValidator { List> parts = []; diff --git a/glade_forms/lib/src/validator/specialized/date_time_validator.dart b/glade_forms/lib/src/validator/specialized/date_time_validator.dart index d4d01fc..de3f7a0 100644 --- a/glade_forms/lib/src/validator/specialized/date_time_validator.dart +++ b/glade_forms/lib/src/validator/specialized/date_time_validator.dart @@ -3,6 +3,9 @@ import 'package:glade_forms/src/src.dart'; import 'package:netglade_utils/netglade_utils.dart'; +typedef DateTimeValidatorFactory = ValidatorInstance Function(DateTimeValidator validator); +typedef DateTimeValidatorFactoryNullable = ValidatorInstance Function(DateTimeValidatorNullable validator); + class DateTimeValidator extends GladeValidator { /// Compares given value with [start] and [end] values. With [inclusiveInterval] set to true(default), the comparison is inclusive. void isBetween({ diff --git a/glade_forms/lib/src/validator/specialized/int_validator.dart b/glade_forms/lib/src/validator/specialized/int_validator.dart index 7e610f4..309a4ab 100644 --- a/glade_forms/lib/src/validator/specialized/int_validator.dart +++ b/glade_forms/lib/src/validator/specialized/int_validator.dart @@ -1,5 +1,10 @@ +// ignore_for_file: prefer-single-declaration-per-file + import 'package:glade_forms/src/src.dart'; +typedef IntValidatorFactory = ValidatorInstance Function(IntValidator validator); +typedef IntValidatorFactoryNullable = ValidatorInstance Function(IntValidatorNullable validator); + class IntValidator extends GladeValidator { /// Compares given value with [min] and [max] values. With [inclusiveInterval] set to true(default), the comparison is inclusive. void isBetween({ @@ -22,12 +27,13 @@ class IntValidator extends GladeValidator { /// Compares given value with [min] value. void isMin({ required int min, + bool isInclusive = true, OnValidateError? devError, Object? key, ShouldValidateCallback? shouldValidate, }) => satisfy( - (value) => value >= min, + (value) => isInclusive ? value >= min : value > min, devError: devError ?? (value) => 'Value ${value ?? 'NULL'} is less than $min.', key: key ?? GladeErrorKeys.intCompareError, shouldValidate: shouldValidate, @@ -36,12 +42,75 @@ class IntValidator extends GladeValidator { /// Compares given value with [max] value. void isMax({ required int max, + bool isInclusive = true, OnValidateError? devError, Object? key, ShouldValidateCallback? shouldValidate, }) => satisfy( - (value) => value <= max, + (value) => isInclusive ? value <= max : value < max, + devError: devError ?? (value) => 'Value ${value ?? 'NULL'} is bigger than $max.', + key: key ?? GladeErrorKeys.intCompareError, + shouldValidate: shouldValidate, + ); +} + +class IntValidatorNullable extends GladeValidator { + /// Compares given value with [min] and [max] values. With [inclusiveInterval] set to true(default), the comparison is inclusive. + void isBetween({ + required int min, + required int max, + OnValidateError? devError, + Object? key, + ShouldValidateCallback? shouldValidate, + bool inclusiveInterval = true, + }) => + satisfy( + (value) { + if (value == null) return false; + + return inclusiveInterval ? value >= min && value <= max : value > min && value < max; + }, + devError: devError ?? + (value) => + 'Value ${value ?? 'NULL'} (inclusiveInterval: $inclusiveInterval) is not in between $min and $max.', + key: key ?? GladeErrorKeys.intCompareError, + shouldValidate: shouldValidate, + ); + + /// Compares given value with [min] value. + void isMin({ + required int min, + bool isInclusive = true, + OnValidateError? devError, + Object? key, + ShouldValidateCallback? shouldValidate, + }) => + satisfy( + (value) { + if (value == null) return false; + + return isInclusive ? value >= min : value > min; + }, + devError: devError ?? (value) => 'Value ${value ?? 'NULL'} is less than $min.', + key: key ?? GladeErrorKeys.intCompareError, + shouldValidate: shouldValidate, + ); + + /// Compares given value with [max] value. + void isMax({ + required int max, + bool isInclusive = true, + OnValidateError? devError, + Object? key, + ShouldValidateCallback? shouldValidate, + }) => + satisfy( + (value) { + if (value == null) return false; + + return isInclusive ? value <= max : value < max; + }, devError: devError ?? (value) => 'Value ${value ?? 'NULL'} is bigger than $max.', key: key ?? GladeErrorKeys.intCompareError, shouldValidate: shouldValidate, diff --git a/glade_forms/lib/src/validator/specialized/string_validator.dart b/glade_forms/lib/src/validator/specialized/string_validator.dart index 208bcd3..f3fffb9 100644 --- a/glade_forms/lib/src/validator/specialized/string_validator.dart +++ b/glade_forms/lib/src/validator/specialized/string_validator.dart @@ -1,6 +1,8 @@ import 'package:glade_forms/src/core/error/glade_error_keys.dart'; import 'package:glade_forms/src/validator/validator.dart'; +typedef StringValidatorFactory = ValidatorInstance Function(StringValidator validator); + class StringValidator extends GladeValidator { /// Given value can't be empty string (or null). void notEmpty({ diff --git a/glade_forms/lib/src/widgets/glade_model_debug_info.dart b/glade_forms/lib/src/widgets/glade_model_debug_info.dart index 9738949..1bdbc8f 100644 --- a/glade_forms/lib/src/widgets/glade_model_debug_info.dart +++ b/glade_forms/lib/src/widgets/glade_model_debug_info.dart @@ -199,7 +199,7 @@ class _Table extends StatelessWidget { children: [ // Columns header. TableRow( - decoration: const BoxDecoration(color: Colors.black, border: Border(bottom: BorderSide())), + decoration: BoxDecoration(color: Theme.of(context).canvasColor, border: const Border(bottom: BorderSide())), children: [ const _ColumnHeader('Input'), if (showIsUnchanged) const _ColumnHeader('isUnchanged'), @@ -214,7 +214,7 @@ class _Table extends StatelessWidget { for (final (index, x) in inputs.indexed) TableRow( decoration: BoxDecoration( - color: index.isEven ? Theme.of(context).canvasColor : Theme.of(context).canvasColor.darken(0.2), + color: index.isEven ? Theme.of(context).colorScheme.surface : Theme.of(context).colorScheme.onSecondary, ), children: [ Padding( diff --git a/glade_forms/pubspec.yaml b/glade_forms/pubspec.yaml index 1f488b1..858a6f4 100644 --- a/glade_forms/pubspec.yaml +++ b/glade_forms/pubspec.yaml @@ -1,6 +1,6 @@ name: glade_forms description: A universal way to define form validators with support of translations. -version: 3.1.1 +version: 4.0.0 repository: https://github.com/netglade/glade_forms issue_tracker: https://github.com/netglade/glade_forms/issues screenshots: diff --git a/glade_forms/test/date_time_validator_test.dart b/glade_forms/test/date_time_validator_test.dart index 928a6f3..a04297a 100644 --- a/glade_forms/test/date_time_validator_test.dart +++ b/glade_forms/test/date_time_validator_test.dart @@ -139,7 +139,10 @@ void main() { final validator = (DateTimeValidator() // ignore: avoid_redundant_argument_values, be explcit in tests., ..isBetween( - start: testCase.$2.start, end: testCase.$2.end, includeTime: false, inclusiveInterval: true)) + start: testCase.$2.start, + end: testCase.$2.end, + includeTime: false, + )) .build(); final result = validator.validate(testCase.$2.test); @@ -212,7 +215,11 @@ void main() { final validator = (DateTimeValidator() // ignore: avoid_redundant_argument_values, be explcit in tests., ..isBetween( - start: testCase.$2.start, end: testCase.$2.end, includeTime: false, inclusiveInterval: false)) + start: testCase.$2.start, + end: testCase.$2.end, + includeTime: false, + inclusiveInterval: false, + )) .build(); final result = validator.validate(testCase.$2.test); diff --git a/glade_forms/test/glade_input_test.dart b/glade_forms/test/glade_input_test.dart index 779961a..13382dc 100644 --- a/glade_forms/test/glade_input_test.dart +++ b/glade_forms/test/glade_input_test.dart @@ -10,8 +10,8 @@ void main() { group('onChange tests', () { test('Setter always trigger change', () { - final dependentInput = GladeInput.intInput(value: -1); - final input = GladeInput.intInput( + final dependentInput = GladeIntInput(value: -1); + final input = GladeIntInput( value: 0, onChange: (info) { dependentInput.value = info.value; @@ -28,8 +28,8 @@ void main() { }); test('updateValue by default trigger change', () { - final dependentInput = GladeInput.intInput(value: -1); - final input = GladeInput.intInput( + final dependentInput = GladeIntInput(value: -1); + final input = GladeIntInput( value: 0, onChange: (info) { dependentInput.value = info.value; @@ -46,8 +46,8 @@ void main() { }); test('updateValue disables trigger onChange', () { - final dependentInput = GladeInput.intInput(value: -1); - final input = GladeInput.intInput( + final dependentInput = GladeIntInput(value: -1); + final input = GladeIntInput( value: 0, onChange: (info) { dependentInput.value = info.value; @@ -67,8 +67,8 @@ void main() { group('onChange with Controller tests', () { test('Controller always trigger change', () { - final dependentInput = GladeInput.intInput(value: -1); - final input = GladeInput.intInput( + final dependentInput = GladeIntInput(value: -1); + final input = GladeIntInput( value: 0, useTextEditingController: true, onChange: (info) { @@ -86,8 +86,8 @@ void main() { }); test('Setter always trigger change', () { - final dependentInput = GladeInput.intInput(value: -1); - final input = GladeInput.intInput( + final dependentInput = GladeIntInput(value: -1); + final input = GladeIntInput( value: 0, useTextEditingController: true, onChange: (info) { @@ -105,8 +105,8 @@ void main() { }); test('updateValue by default trigger change', () { - final dependentInput = GladeInput.intInput(value: -1); - final input = GladeInput.intInput( + final dependentInput = GladeIntInput(value: -1); + final input = GladeIntInput( value: 0, useTextEditingController: true, onChange: (info) { @@ -124,8 +124,8 @@ void main() { }); test('updateValue disables trigger onChange', () { - final dependentInput = GladeInput.intInput(value: -1); - final input = GladeInput.intInput( + final dependentInput = GladeIntInput(value: -1); + final input = GladeIntInput( value: 0, useTextEditingController: true, onChange: (info) { @@ -146,7 +146,7 @@ void main() { group('Pure test', () { test('ResetToPure', () { - final input = GladeInput.intInput( + final input = GladeIntInput( value: 100, initialValue: 10, useTextEditingController: true, @@ -166,7 +166,7 @@ void main() { }); test('SetAsNewPure', () { - final input = GladeInput.intInput( + final input = GladeIntInput( initialValue: 20, value: 100, useTextEditingController: true, diff --git a/glade_forms/test/model/glade_model_test.dart b/glade_forms/test/model/glade_model_test.dart index 8989ee3..8005dad 100644 --- a/glade_forms/test/model/glade_model_test.dart +++ b/glade_forms/test/model/glade_model_test.dart @@ -21,9 +21,9 @@ class _ModeWithDependencies extends GladeModel { @override void initialize() { - a = GladeInput.intInput(value: 0, inputKey: 'a'); - b = GladeInput.intInput(value: 1, inputKey: 'b'); - c = GladeInput.intInput( + a = GladeIntInput(value: 0, inputKey: 'a'); + b = GladeIntInput(value: 1, inputKey: 'b'); + c = GladeIntInput( value: 1, inputKey: 'c', dependencies: () => [a, b], diff --git a/glade_forms/test/model/group_edit_test.dart b/glade_forms/test/model/group_edit_test.dart index f08c941..95bfa52 100644 --- a/glade_forms/test/model/group_edit_test.dart +++ b/glade_forms/test/model/group_edit_test.dart @@ -12,8 +12,8 @@ class _Model extends GladeModel { @override void initialize() { - a = GladeInput.intInput(value: 0, inputKey: 'a'); - b = GladeInput.intInput(value: 1, inputKey: 'b'); + a = GladeIntInput(value: 0, inputKey: 'a'); + b = GladeIntInput(value: 1, inputKey: 'b'); super.initialize(); } @@ -35,9 +35,9 @@ class _ModeWithDependencies extends GladeModel { @override void initialize() { - a = GladeInput.intInput(value: 0, inputKey: 'a'); - b = GladeInput.intInput(value: 1, inputKey: 'b'); - c = GladeInput.intInput( + a = GladeIntInput(value: 0, inputKey: 'a'); + b = GladeIntInput(value: 1, inputKey: 'b'); + c = GladeIntInput( value: 1, inputKey: 'c', dependencies: () => [a, b], diff --git a/storybook/lib/debug_model.dart b/storybook/lib/debug_model.dart index fe7679b..f761f9a 100644 --- a/storybook/lib/debug_model.dart +++ b/storybook/lib/debug_model.dart @@ -11,47 +11,49 @@ class _ErrorKeys { } class AgeRestrictedModel extends GladeModel { - late StringInput nameInput; - late StringInput fooInput; - late GladeInput ageInput; - late GladeInput vipInput; + late GladeStringInput nameInput; + late GladeStringInput fooInput; + late GladeIntInput ageInput; + late GladeBoolInput vipInput; @override List> get inputs => [nameInput, fooInput, ageInput, vipInput]; @override void initialize() { - nameInput = GladeInput.stringInput( + nameInput = GladeStringInput( inputKey: 'name-input', defaultTranslations: DefaultTranslations( defaultValueIsNullOrEmptyMessage: LocaleKeys.empty.tr(), ), ); - fooInput = GladeInput.stringInput( + fooInput = GladeStringInput( inputKey: 'foo-input', defaultTranslations: DefaultTranslations( defaultValueIsNullOrEmptyMessage: LocaleKeys.empty.tr(), ), trackUnchanged: false, ); - ageInput = GladeInput.create( + ageInput = GladeIntInput( validator: (v) => (v ..notNull() - ..satisfy( - (value) { - if (!vipInput.value) { - return true; - } + ..isMin(min: 18, shouldValidate: (_) => vipInput.value) + // ..satisfy( + // (value) { + // if (!vipInput.value) { + // return true; + // } - return value >= 18; - }, - devError: (_) => 'When VIP enabled you must be at least 18 years old.', - key: _ErrorKeys.ageRestriction, - )) + // return value >= 18; + // }, + // devError: (_) => 'When VIP enabled you must be at least 18 years old.', + // key: _ErrorKeys.ageRestriction, + // ) + ) .build(), value: 0, dependencies: () => [vipInput], - valueConverter: GladeTypeConverters.intConverter, + stringToValueConverter: GladeTypeConverters.intConverter, inputKey: 'age-input', translateError: (error, key, devMessage, dependencies) { if (key == _ErrorKeys.ageRestriction) return LocaleKeys.ageRestriction_under18.tr(); @@ -64,7 +66,7 @@ class AgeRestrictedModel extends GladeModel { vipInput.value = info.value >= 18; }, ); - vipInput = GladeInput.create( + vipInput = GladeBoolInput( validator: (v) => (v..notNull()).build(), value: false, inputKey: 'vip-input', diff --git a/storybook/lib/usecases/complex_object_mapping_example.dart b/storybook/lib/usecases/complex_object_mapping_example.dart index 212afbb..89ebe54 100644 --- a/storybook/lib/usecases/complex_object_mapping_example.dart +++ b/storybook/lib/usecases/complex_object_mapping_example.dart @@ -33,7 +33,7 @@ class _Model extends GladeModel { validator: (v) => v.build(), value: [], inputKey: 'stats', - valueConverter: StringToTypeConverter( + stringToValueConverter: StringToTypeConverter( converter: (rawInput, cantConvert) { final r = RegExp(r'^\d+(,\s*\d+\s*)*$'); final input = rawInput; diff --git a/storybook/lib/usecases/dependencies/checkbox_dependency_change.dart b/storybook/lib/usecases/dependencies/checkbox_dependency_change.dart index ad24e5e..9d921e8 100644 --- a/storybook/lib/usecases/dependencies/checkbox_dependency_change.dart +++ b/storybook/lib/usecases/dependencies/checkbox_dependency_change.dart @@ -11,38 +11,31 @@ abstract final class _ErrorKeys { } class AgeRestrictedModel extends GladeModel { - late StringInput nameInput; - late GladeInput ageInput; - late GladeInput vipInput; + late GladeStringInput nameInput; + late GladeIntInput ageInput; + late GladeBoolInput vipInput; @override List> get inputs => [nameInput, ageInput, vipInput]; @override void initialize() { - nameInput = GladeInput.stringInput( + nameInput = GladeStringInput( inputKey: 'name-input', defaultTranslations: DefaultTranslations( defaultValueIsNullOrEmptyMessage: LocaleKeys.empty.tr(), ), ); - ageInput = GladeInput.create( + ageInput = GladeIntInput( validator: (v) => (v ..notNull() - ..satisfy( - (value) { - if (!vipInput.value) { - return true; - } - - return value >= 18; - }, - devError: (_) => 'When VIP enabled you must be at least 18 years old.', + ..isMin( + min: 18, + shouldValidate: (_) => vipInput.value, key: _ErrorKeys.ageRestriction, )) .build(), value: 0, - valueConverter: GladeTypeConverters.intConverter, inputKey: 'age-input', translateError: (error, key, devMessage, dependencies) { if (key == _ErrorKeys.ageRestriction) return LocaleKeys.ageRestriction_under18.tr(); @@ -53,7 +46,7 @@ class AgeRestrictedModel extends GladeModel { }, useTextEditingController: true, ); - vipInput = GladeInput.create( + vipInput = GladeBoolInput( validator: (v) => (v..notNull()).build(), value: false, inputKey: 'vip-input', diff --git a/storybook/lib/usecases/onchange/one_checkbox_deps_validation.dart b/storybook/lib/usecases/onchange/one_checkbox_deps_validation.dart index 1e20d31..82d5f08 100644 --- a/storybook/lib/usecases/onchange/one_checkbox_deps_validation.dart +++ b/storybook/lib/usecases/onchange/one_checkbox_deps_validation.dart @@ -11,38 +11,27 @@ abstract final class _ErrorKeys { } class AgeRestrictedModel extends GladeModel { - late StringInput nameInput; - late GladeInput ageInput; - late GladeInput vipInput; + late GladeStringInput nameInput; + late GladeIntInput ageInput; + late GladeBoolInput vipInput; @override List> get inputs => [nameInput, ageInput, vipInput]; @override void initialize() { - nameInput = GladeInput.stringInput( + nameInput = GladeStringInput( inputKey: 'name-input', defaultTranslations: DefaultTranslations( defaultValueIsNullOrEmptyMessage: LocaleKeys.empty.tr(), ), ); - ageInput = GladeInput.create( + ageInput = GladeIntInput( validator: (v) => (v ..notNull() - ..satisfy( - (value) { - if (!vipInput.value) { - return true; - } - - return value >= 18; - }, - devError: (_) => 'When VIP enabled you must be at least 18 years old.', - key: _ErrorKeys.ageRestriction, - )) + ..isMin(min: 18, shouldValidate: (_) => vipInput.value, key: _ErrorKeys.ageRestriction)) .build(), value: 0, - valueConverter: GladeTypeConverters.intConverter, inputKey: 'age-input', translateError: (error, key, devMessage, dependencies) { if (key == _ErrorKeys.ageRestriction) return LocaleKeys.ageRestriction_under18.tr(); @@ -52,7 +41,7 @@ class AgeRestrictedModel extends GladeModel { return devMessage; }, ); - vipInput = GladeInput.create( + vipInput = GladeBoolInput( validator: (v) => (v..notNull()).build(), value: false, inputKey: 'vip-input', diff --git a/storybook/lib/usecases/onchange/two_way_checkbox_change.dart b/storybook/lib/usecases/onchange/two_way_checkbox_change.dart index eaa8593..1ef8313 100644 --- a/storybook/lib/usecases/onchange/two_way_checkbox_change.dart +++ b/storybook/lib/usecases/onchange/two_way_checkbox_change.dart @@ -11,38 +11,27 @@ abstract final class _ErrorKeys { } class AgeRestrictedModel extends GladeModel { - late StringInput nameInput; - late GladeInput ageInput; - late GladeInput vipInput; + late GladeStringInput nameInput; + late GladeIntInput ageInput; + late GladeBoolInput vipInput; @override List> get inputs => [nameInput, ageInput, vipInput]; @override void initialize() { - nameInput = GladeInput.stringInput( + nameInput = GladeStringInput( inputKey: 'name-input', defaultTranslations: DefaultTranslations( defaultValueIsNullOrEmptyMessage: LocaleKeys.empty.tr(), ), ); - ageInput = GladeInput.create( + ageInput = GladeIntInput( validator: (v) => (v ..notNull() - ..satisfy( - (value) { - if (!vipInput.value) { - return true; - } - - return value >= 18; - }, - devError: (_) => 'When VIP enabled you must be at least 18 years old.', - key: _ErrorKeys.ageRestriction, - )) + ..isMin(min: 18, shouldValidate: (_) => vipInput.value, key: _ErrorKeys.ageRestriction)) .build(), value: 0, - valueConverter: GladeTypeConverters.intConverter, inputKey: 'age-input', translateError: (error, key, devMessage, dependencies) { if (key == _ErrorKeys.ageRestriction) return LocaleKeys.ageRestriction_under18.tr(); @@ -51,14 +40,9 @@ class AgeRestrictedModel extends GladeModel { return devMessage; }, - // * cyclic dependency between ageInput and vipInput - // Better to use onDependencyChange inside vipInput - // onChange: (info) { - // vipInput.value = info.value >= 18; - // }, useTextEditingController: true, ); - vipInput = GladeInput.create( + vipInput = GladeBoolInput( validator: (v) => (v..notNull()).build(), value: false, inputKey: 'vip-input', diff --git a/storybook/lib/usecases/quickstart_example.dart b/storybook/lib/usecases/quickstart_example.dart index f022faa..06e7784 100644 --- a/storybook/lib/usecases/quickstart_example.dart +++ b/storybook/lib/usecases/quickstart_example.dart @@ -3,20 +3,20 @@ import 'package:glade_forms/glade_forms.dart'; import 'package:glade_forms_storybook/shared/usecase_container.dart'; class _Model extends GladeModel { - late StringInput name; - late IntInput age; - late StringInput email; - late IntInput income; + late GladeStringInput name; + late GladeIntInput age; + late GladeStringInput email; + late GladeIntInput income; @override List> get inputs => [name, age, email, income]; @override void initialize() { - name = GladeInput.stringInput(inputKey: 'name'); - age = GladeInput.intInput(value: 0, inputKey: 'age', useTextEditingController: true); - email = GladeInput.stringInput(validator: (validator) => (validator..isEmail()).build(), inputKey: 'email'); - income = GladeInput.intInput( + name = GladeStringInput(inputKey: 'name'); + age = GladeIntInput(value: 0, inputKey: 'age', useTextEditingController: true); + email = GladeStringInput(validator: (validator) => (validator..isEmail()).build(), inputKey: 'email'); + income = GladeIntInput( value: 10000, validator: (validator) => (validator..isMin(min: 1000)).build(), inputKey: 'income', diff --git a/storybook/lib/usecases/regress/issue48_text_controller_example.dart b/storybook/lib/usecases/regress/issue48_text_controller_example.dart index 6da7c1d..fb92eea 100644 --- a/storybook/lib/usecases/regress/issue48_text_controller_example.dart +++ b/storybook/lib/usecases/regress/issue48_text_controller_example.dart @@ -6,20 +6,20 @@ import 'package:glade_forms/glade_forms.dart'; import 'package:glade_forms_storybook/shared/usecase_container.dart'; class _Model extends GladeModel { - late StringInput nameWithController; - late StringInput nameWrong; - late GladeInput age; - late StringInput email; + late GladeStringInput nameWithController; + late GladeStringInput nameWrong; + late GladeIntInput age; + late GladeStringInput email; @override List> get inputs => [nameWithController, age, email, nameWrong]; @override void initialize() { - nameWithController = GladeInput.stringInput(inputKey: 'name'); - nameWrong = GladeInput.stringInput(inputKey: 'wrong_name'); - age = GladeInput.intInput(value: 0, inputKey: 'age'); - email = GladeInput.stringInput(validator: (validator) => (validator..isEmail()).build(), inputKey: 'email'); + nameWithController = GladeStringInput(inputKey: 'name'); + nameWrong = GladeStringInput(inputKey: 'wrong_name'); + age = GladeIntInput(value: 0, inputKey: 'age'); + email = GladeStringInput(validator: (validator) => (validator..isEmail()).build(), inputKey: 'email'); super.initialize(); } @@ -30,12 +30,6 @@ class Issue48TextControllerExample extends StatelessWidget { @override Widget build(BuildContext context) { - // return Scaffold( - // body: Center( - // child: TextField(controller: wrongField), - // ), - // ); - return UsecaseContainer( shortDescription: 'Issue 48', description: 'https://github.com/netglade/glade_forms/issues/48', From 47a792d01a8d64aae6cc9888ec0354c0c635cd58 Mon Sep 17 00:00:00 2001 From: Petr Nymsa Date: Wed, 29 Jan 2025 16:15:02 +0100 Subject: [PATCH 3/8] Add isAfter, isBefore shortcut validations. Code cleanup --- glade_forms/CHANGELOG.md | 1 + glade_forms/example/pubspec.yaml | 3 +- .../lib/src/core/error/glade_error_keys.dart | 5 + .../specialized/date_time_validator.dart | 100 +++- .../validator/specialized/int_validator.dart | 8 +- .../src/widgets/glade_model_debug_info.dart | 12 - .../test/date_time_validator_test.dart | 428 ++++++++++++++++-- storybook/analysis_options.yaml | 5 +- storybook/lib/debug_model.dart | 16 +- storybook/lib/main.dart | 4 +- .../checkbox_dependency_change.dart | 2 - .../onchange/two_way_checkbox_change.dart | 2 - 12 files changed, 516 insertions(+), 70 deletions(-) diff --git a/glade_forms/CHANGELOG.md b/glade_forms/CHANGELOG.md index 18a0bd8..c081f0a 100644 --- a/glade_forms/CHANGELOG.md +++ b/glade_forms/CHANGELOG.md @@ -5,6 +5,7 @@ - Specialized versions of inputs such as `IntInput` or `StringInput` were renamed to `Glade*Input`. - Removed specialized version factories. Now specialized versions are sub-classes of GladeInput - This removes the weird possibility to create calls such as `StringInput.intInput()` which in the end threw a runtime exception due to type mismatch. +- Renamed `valueConverter` in `create()` factory to match internal name `stringToValueConverter` which is more explicit - **Added** `GladeDateTimeInput` - specialized GladeInput for DateTime inputs. - **Added** `inclusive` argument for `int` validations. diff --git a/glade_forms/example/pubspec.yaml b/glade_forms/example/pubspec.yaml index bdfdad1..b26c830 100644 --- a/glade_forms/example/pubspec.yaml +++ b/glade_forms/example/pubspec.yaml @@ -10,4 +10,5 @@ resolution: workspace dependencies: flutter: sdk: flutter - glade_forms: ^3.1.0 + glade_forms: + path: ../ diff --git a/glade_forms/lib/src/core/error/glade_error_keys.dart b/glade_forms/lib/src/core/error/glade_error_keys.dart index 7c86cd8..f886701 100644 --- a/glade_forms/lib/src/core/error/glade_error_keys.dart +++ b/glade_forms/lib/src/core/error/glade_error_keys.dart @@ -10,4 +10,9 @@ abstract final class GladeErrorKeys { static const String valueIsNull = 'value-is-null'; static const String valueIsEmpty = 'value-is-empty'; static const String intCompareError = 'int-compare-error'; + static const String intCompareMaxError = 'int-compare-max-error'; + static const String intCompareMinError = 'int-compare-min-error'; + static const String dateTimeIsBetweenError = 'datetime-compare-isbetween-error'; + static const String dateTimeIsAfterError = 'datetime-compare-isafter-error'; + static const String dateTimeIsBeforeError = 'datetime-compare-isbefore-error'; } diff --git a/glade_forms/lib/src/validator/specialized/date_time_validator.dart b/glade_forms/lib/src/validator/specialized/date_time_validator.dart index de3f7a0..a75923c 100644 --- a/glade_forms/lib/src/validator/specialized/date_time_validator.dart +++ b/glade_forms/lib/src/validator/specialized/date_time_validator.dart @@ -31,7 +31,53 @@ class DateTimeValidator extends GladeValidator { devError: devError ?? (value) => 'Value ${value ?? 'NULL'} (inclusiveInterval: $inclusiveInterval) is not in between $start and $end.', - key: key ?? GladeErrorKeys.intCompareError, + key: key ?? GladeErrorKeys.dateTimeIsBetweenError, + shouldValidate: shouldValidate, + ); + + void isAfter({ + required DateTime start, + OnValidateError? devError, + Object? key, + ShouldValidateCallback? shouldValidate, + bool inclusiveInterval = true, + bool includeTime = true, + }) => + satisfy( + (value) { + final comparedValue = includeTime ? value : value.withoutTime; + final startValue = includeTime ? start : start.withoutTime; + + return inclusiveInterval + ? comparedValue.isAfterOrSame(startValue, includeTime: includeTime) + : comparedValue.isAfter(startValue); + }, + devError: devError ?? + (value) => 'Value ${value ?? 'NULL'} (inclusiveInterval: $inclusiveInterval) is not after $start', + key: key ?? GladeErrorKeys.dateTimeIsAfterError, + shouldValidate: shouldValidate, + ); + + void isBefore({ + required DateTime end, + OnValidateError? devError, + Object? key, + ShouldValidateCallback? shouldValidate, + bool inclusiveInterval = true, + bool includeTime = true, + }) => + satisfy( + (value) { + final comparedValue = includeTime ? value : value.withoutTime; + final endValue = includeTime ? end : end.withoutTime; + + return inclusiveInterval + ? comparedValue.isBeforeOrSame(endValue, includeTime: includeTime) + : comparedValue.isBefore(endValue); + }, + devError: devError ?? + (value) => 'Value ${value ?? 'NULL'} (inclusiveInterval: $inclusiveInterval) is not before $end', + key: key ?? GladeErrorKeys.dateTimeIsBeforeError, shouldValidate: shouldValidate, ); } @@ -63,7 +109,57 @@ class DateTimeValidatorNullable extends GladeValidator { devError: devError ?? (value) => 'Value ${value ?? 'NULL'} (inclusiveInterval: $inclusiveInterval) is not in between $start and $end.', - key: key ?? GladeErrorKeys.intCompareError, + key: key ?? GladeErrorKeys.dateTimeIsBetweenError, + shouldValidate: shouldValidate, + ); + + void isAfter({ + required DateTime start, + OnValidateError? devError, + Object? key, + ShouldValidateCallback? shouldValidate, + bool inclusiveInterval = true, + bool includeTime = true, + }) => + satisfy( + (value) { + if (value == null) return false; + + final comparedValue = includeTime ? value : value.withoutTime; + final startValue = includeTime ? start : start.withoutTime; + + return inclusiveInterval + ? comparedValue.isAfterOrSame(startValue, includeTime: includeTime) + : comparedValue.isAfter(startValue); + }, + devError: devError ?? + (value) => 'Value ${value ?? 'NULL'} (inclusiveInterval: $inclusiveInterval) is not after $start', + key: key ?? GladeErrorKeys.dateTimeIsAfterError, + shouldValidate: shouldValidate, + ); + + void isBefore({ + required DateTime end, + OnValidateError? devError, + Object? key, + ShouldValidateCallback? shouldValidate, + bool inclusiveInterval = true, + bool includeTime = true, + }) => + satisfy( + (value) { + if (value == null) return false; + + final comparedValue = includeTime ? value : value.withoutTime; + final endValue = includeTime ? end : end.withoutTime; + + return inclusiveInterval + ? comparedValue.isBeforeOrSame(endValue, includeTime: includeTime) + : comparedValue.isBefore(endValue); + }, + devError: devError ?? + (value) => 'Value ${value ?? 'NULL'} (inclusiveInterval: $inclusiveInterval) is not before $end', + key: key ?? GladeErrorKeys.dateTimeIsBeforeError, shouldValidate: shouldValidate, ); } diff --git a/glade_forms/lib/src/validator/specialized/int_validator.dart b/glade_forms/lib/src/validator/specialized/int_validator.dart index 309a4ab..5d2dc22 100644 --- a/glade_forms/lib/src/validator/specialized/int_validator.dart +++ b/glade_forms/lib/src/validator/specialized/int_validator.dart @@ -35,7 +35,7 @@ class IntValidator extends GladeValidator { satisfy( (value) => isInclusive ? value >= min : value > min, devError: devError ?? (value) => 'Value ${value ?? 'NULL'} is less than $min.', - key: key ?? GladeErrorKeys.intCompareError, + key: key ?? GladeErrorKeys.intCompareMinError, shouldValidate: shouldValidate, ); @@ -50,7 +50,7 @@ class IntValidator extends GladeValidator { satisfy( (value) => isInclusive ? value <= max : value < max, devError: devError ?? (value) => 'Value ${value ?? 'NULL'} is bigger than $max.', - key: key ?? GladeErrorKeys.intCompareError, + key: key ?? GladeErrorKeys.intCompareMaxError, shouldValidate: shouldValidate, ); } @@ -93,7 +93,7 @@ class IntValidatorNullable extends GladeValidator { return isInclusive ? value >= min : value > min; }, devError: devError ?? (value) => 'Value ${value ?? 'NULL'} is less than $min.', - key: key ?? GladeErrorKeys.intCompareError, + key: key ?? GladeErrorKeys.intCompareMinError, shouldValidate: shouldValidate, ); @@ -112,7 +112,7 @@ class IntValidatorNullable extends GladeValidator { return isInclusive ? value <= max : value < max; }, devError: devError ?? (value) => 'Value ${value ?? 'NULL'} is bigger than $max.', - key: key ?? GladeErrorKeys.intCompareError, + key: key ?? GladeErrorKeys.intCompareMaxError, shouldValidate: shouldValidate, ); } diff --git a/glade_forms/lib/src/widgets/glade_model_debug_info.dart b/glade_forms/lib/src/widgets/glade_model_debug_info.dart index 1bdbc8f..38ee318 100644 --- a/glade_forms/lib/src/widgets/glade_model_debug_info.dart +++ b/glade_forms/lib/src/widgets/glade_model_debug_info.dart @@ -396,15 +396,3 @@ class _DangerStrips extends StatelessWidget { return stripes; } } - -extension _ColorExtensions on Color { - /// Returns color darkened by [amount]. - Color darken([double amount = 0.1]) { - assert(amount >= 0 && amount <= 1, 'amount must be between 0 and 1'); - - final hsl = HSLColor.fromColor(this); - final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0)); - - return hslDark.toColor(); - } -} diff --git a/glade_forms/test/date_time_validator_test.dart b/glade_forms/test/date_time_validator_test.dart index a04297a..40cceb9 100644 --- a/glade_forms/test/date_time_validator_test.dart +++ b/glade_forms/test/date_time_validator_test.dart @@ -61,18 +61,25 @@ void main() { isValid: false, ), ].indexed) { + final index = testCase.$1; + final data = testCase.$2; + final start = data.start; + final end = data.end; + final testDate = data.test; + final isValid = data.isValid; + test( - '(${testCase.$1}): ${testCase.$2.test} should be valid between ${testCase.$2.start} - ${testCase.$2.end} when testing inclusive and with included time', + '($index): $testDate should be valid between $start - $end when testing inclusive and with included time', () { final validator = (DateTimeValidator() - // ignore: avoid_redundant_argument_values, be explcit in tests. - ..isBetween(start: testCase.$2.start, end: testCase.$2.end, includeTime: true, inclusiveInterval: true)) + // ignore: avoid_redundant_argument_values, be explicit in tests. + ..isBetween(start: start, end: end, includeTime: true, inclusiveInterval: true)) .build(); - final result = validator.validate(testCase.$2.test); + final result = validator.validate(testDate); - expect(result.isValid, equals(testCase.$2.isValid)); - expect(result.isInvalid, equals(!testCase.$2.isValid)); + expect(result.isValid, equals(isValid)); + expect(result.isInvalid, equals(!isValid)); }, ); } @@ -133,22 +140,29 @@ void main() { isValid: false, ), ].indexed) { + final index = testCase.$1; + final data = testCase.$2; + final start = data.start; + final end = data.end; + final testDate = data.test; + final isValid = data.isValid; + test( - '(${testCase.$1}): ${testCase.$2.test} should be valid between ${testCase.$2.start} - ${testCase.$2.end} when testing inclusive and but without included time', + '($index): $testDate should be valid between $start - $end when testing inclusive and but without included time', () { final validator = (DateTimeValidator() - // ignore: avoid_redundant_argument_values, be explcit in tests., ..isBetween( - start: testCase.$2.start, - end: testCase.$2.end, + start: start, + end: end, + // ignore: avoid_redundant_argument_values, be explcit in tests., includeTime: false, )) .build(); - final result = validator.validate(testCase.$2.test); + final result = validator.validate(testDate); - expect(result.isValid, equals(testCase.$2.isValid)); - expect(result.isInvalid, equals(!testCase.$2.isValid)); + expect(result.isValid, equals(isValid)); + expect(result.isInvalid, equals(!isValid)); }, ); } @@ -209,23 +223,30 @@ void main() { isValid: false, ), ].indexed) { + final index = testCase.$1; + final data = testCase.$2; + final start = data.start; + final end = data.end; + final testDate = data.test; + final isValid = data.isValid; + test( - '(${testCase.$1}): ${testCase.$2.test} should be valid between ${testCase.$2.start} - ${testCase.$2.end} when testing without inclusive and without included time', + '($index): $testDate should be valid between $start - $end when testing without inclusive and without included time', () { final validator = (DateTimeValidator() - // ignore: avoid_redundant_argument_values, be explcit in tests., + // ignore: avoid_redundant_argument_values, be explicit in tests. ..isBetween( - start: testCase.$2.start, - end: testCase.$2.end, + start: start, + end: end, includeTime: false, inclusiveInterval: false, )) .build(); - final result = validator.validate(testCase.$2.test); + final result = validator.validate(testDate); - expect(result.isValid, equals(testCase.$2.isValid)); - expect(result.isInvalid, equals(!testCase.$2.isValid)); + expect(result.isValid, equals(isValid)); + expect(result.isInvalid, equals(!isValid)); }, ); } @@ -286,23 +307,376 @@ void main() { isValid: false, ), ].indexed) { + final index = testCase.$1; + final data = testCase.$2; + final start = data.start; + final end = data.end; + final testDate = data.test; + final isValid = data.isValid; + test( - '(${testCase.$1}): ${testCase.$2.test} should be valid between ${testCase.$2.start} - ${testCase.$2.end} when testing without inclusive but with included time', + '($index): $testDate should be valid between $start - $end when testing without inclusive but with included time', () { final validator = (DateTimeValidator() ..isBetween( - start: testCase.$2.start, - end: testCase.$2.end, + start: start, + end: end, inclusiveInterval: false, - // ignore: avoid_redundant_argument_values, be explicti + // ignore: avoid_redundant_argument_values, be explicit includeTime: true, )) .build(); - final result = validator.validate(testCase.$2.test); + final result = validator.validate(testDate); + + expect(result.isValid, equals(isValid)); + expect(result.isInvalid, equals(!isValid)); + }, + ); + } + }); + + group('isAfter', () { + for (final testCase in [ + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 2, 15, 15, 21), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 2, 15, 15, 20), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 2, 15, 15, 19), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 3, 15, 15, 20), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2024, 12, 31, 23, 59, 59), + isValid: false, + ), + ].indexed) { + final index = testCase.$1; + final data = testCase.$2; + final start = data.start; + final testDate = data.test; + final isValid = data.isValid; + + test( + '($index): $testDate should be valid after $start when testing inclusive and with included time', + () { + final validator = (DateTimeValidator()..isAfter(start: start)).build(); + + final result = validator.validate(testDate); + + expect(result.isValid, equals(isValid)); + expect(result.isInvalid, equals(!isValid)); + }, + ); + } + + for (final testCase in [ + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 2, 15, 15, 21), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 2, 15, 15, 20), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 2, 15, 15, 19), + isValid: false, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 3, 15, 15, 20), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2024, 12, 31, 23, 59, 59), + isValid: false, + ), + ].indexed) { + final index = testCase.$1; + final data = testCase.$2; + final start = data.start; + final testDate = data.test; + final isValid = data.isValid; + + test( + '($index): $testDate should be valid after $start when testing exclusive and with included time', + () { + final validator = (DateTimeValidator()..isAfter(start: start, inclusiveInterval: false)).build(); + + final result = validator.validate(testDate); + + expect(result.isValid, equals(isValid)); + expect(result.isInvalid, equals(!isValid)); + }, + ); + } + + for (final testCase in [ + ( + start: DateTime(2025, 1, 2), + test: DateTime(2025, 1, 3), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2), + test: DateTime(2025, 1, 2), + isValid: true, + ), + (start: DateTime(2025, 1, 2), test: DateTime(2025), isValid: false), + ( + start: DateTime(2025, 1, 2), + test: DateTime(2026, 1, 2), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2), + test: DateTime(2024, 12, 31), + isValid: false, + ), + ].indexed) { + final index = testCase.$1; + final data = testCase.$2; + final start = data.start; + final testDate = data.test; + final isValid = data.isValid; + + test( + '($index): $testDate should be valid after $start when testing inclusive and without included time', + () { + final validator = (DateTimeValidator()..isAfter(start: start, includeTime: false)).build(); + + final result = validator.validate(testDate); + + expect(result.isValid, equals(isValid)); + expect(result.isInvalid, equals(!isValid)); + }, + ); + } + + for (final testCase in [ + ( + start: DateTime(2025, 1, 2), + test: DateTime(2025, 1, 3), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2), + test: DateTime(2025, 1, 2), + isValid: false, + ), + (start: DateTime(2025, 1, 2), test: DateTime(2025), isValid: false), + ( + start: DateTime(2025, 1, 2), + test: DateTime(2026, 1, 2), + isValid: true, + ), + ( + start: DateTime(2025, 1, 2), + test: DateTime(2024, 12, 31), + isValid: false, + ), + ].indexed) { + final index = testCase.$1; + final data = testCase.$2; + final start = data.start; + final testDate = data.test; + final isValid = data.isValid; + + test( + '($index): $testDate should be valid after $start when testing exclusive and without included time', + () { + final validator = + (DateTimeValidator()..isAfter(start: start, includeTime: false, inclusiveInterval: false)).build(); + + final result = validator.validate(testDate); + + expect(result.isValid, equals(isValid)); + expect(result.isInvalid, equals(!isValid)); + }, + ); + } + }); + + group('isBefore', () { + for (final testCase in [ + ( + end: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 2, 15, 15, 19), + isValid: true, + ), + ( + end: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 2, 15, 15, 20), + isValid: true, + ), + ( + end: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 2, 15, 15, 21), + isValid: false, + ), + ( + end: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 1, 15, 15, 20), + isValid: true, + ), + ( + end: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 3, 15, 15, 20), + isValid: false, + ), + ].indexed) { + final index = testCase.$1; + final data = testCase.$2; + final end = data.end; + final testDate = data.test; + final isValid = data.isValid; + + test( + '($index): $testDate should be valid before $end when testing inclusive and with included time', + () { + final validator = (DateTimeValidator()..isBefore(end: end)).build(); + + final result = validator.validate(testDate); + + expect(result.isValid, equals(isValid)); + expect(result.isInvalid, equals(!isValid)); + }, + ); + } + + for (final testCase in [ + ( + end: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 2, 15, 15, 19), + isValid: true, + ), + ( + end: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 2, 15, 15, 20), + isValid: false, + ), + ( + end: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 2, 15, 15, 21), + isValid: false, + ), + ( + end: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 1, 15, 15, 20), + isValid: true, + ), + ( + end: DateTime(2025, 1, 2, 15, 15, 20), + test: DateTime(2025, 1, 3, 15, 15, 20), + isValid: false, + ), + ].indexed) { + final index = testCase.$1; + final data = testCase.$2; + final end = data.end; + final testDate = data.test; + final isValid = data.isValid; + + test( + '($index): $testDate should be valid before $end when testing exclusive and with included time', + () { + final validator = (DateTimeValidator()..isBefore(end: end, inclusiveInterval: false)).build(); + + final result = validator.validate(testDate); + + expect(result.isValid, equals(isValid)); + expect(result.isInvalid, equals(!isValid)); + }, + ); + } + + for (final testCase in [ + (end: DateTime(2025, 1, 2), test: DateTime(2025), isValid: true), + (end: DateTime(2025, 1, 2), test: DateTime(2025, 1, 2), isValid: true), + (end: DateTime(2025, 1, 2), test: DateTime(2025), isValid: true), + ( + end: DateTime(2025, 1, 2), + test: DateTime(2026, 1, 2), + isValid: false, + ), + ( + end: DateTime(2025, 1, 2), + test: DateTime(2024, 12, 31), + isValid: true, + ), + ].indexed) { + final index = testCase.$1; + final data = testCase.$2; + final end = data.end; + final testDate = data.test; + final isValid = data.isValid; + + test( + '($index): $testDate should be valid before $end when testing inclusive and without included time', + () { + final validator = (DateTimeValidator()..isBefore(end: end, includeTime: false)).build(); + + final result = validator.validate(testDate); + + expect(result.isValid, equals(isValid)); + expect(result.isInvalid, equals(!isValid)); + }, + ); + } + + for (final testCase in [ + (end: DateTime(2025, 1, 2), test: DateTime(2025), isValid: true), + ( + end: DateTime(2025, 1, 2), + test: DateTime(2025, 1, 2), + isValid: false, + ), + (end: DateTime(2025, 1, 2), test: DateTime(2025), isValid: true), + ( + end: DateTime(2025, 1, 2), + test: DateTime(2026, 1, 2), + isValid: false, + ), + ( + end: DateTime(2025, 1, 2), + test: DateTime(2024, 12, 31), + isValid: true, + ), + ].indexed) { + final index = testCase.$1; + final data = testCase.$2; + final end = data.end; + final testDate = data.test; + final isValid = data.isValid; + + test( + '($index): $testDate should be valid before $end when testing exclusive and without included time', + () { + final validator = + (DateTimeValidator()..isBefore(end: end, includeTime: false, inclusiveInterval: false)).build(); + + final result = validator.validate(testDate); - expect(result.isValid, equals(testCase.$2.isValid)); - expect(result.isInvalid, equals(!testCase.$2.isValid)); + expect(result.isValid, equals(isValid)); + expect(result.isInvalid, equals(!isValid)); }, ); } diff --git a/storybook/analysis_options.yaml b/storybook/analysis_options.yaml index 9d15bb8..c43ab10 100644 --- a/storybook/analysis_options.yaml +++ b/storybook/analysis_options.yaml @@ -10,7 +10,4 @@ dart_code_metrics: extends: - package:netglade_analysis/dcm.yaml rules: - prefer-single-declaration-per-file: false - avoid-commented-out-code: false # code examples in comments - prefer-boolean-prefixes: false # TODO - prefer-abstract-final-static-class: false # TODO + prefer-single-declaration-per-file: false diff --git a/storybook/lib/debug_model.dart b/storybook/lib/debug_model.dart index f761f9a..e38f598 100644 --- a/storybook/lib/debug_model.dart +++ b/storybook/lib/debug_model.dart @@ -6,7 +6,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:glade_forms/glade_forms.dart'; import 'package:glade_forms_storybook/generated/locale_keys.g.dart'; -class _ErrorKeys { +abstract final class _ErrorKeys { static const String ageRestriction = 'age-restriction'; } @@ -37,19 +37,7 @@ class AgeRestrictedModel extends GladeModel { ageInput = GladeIntInput( validator: (v) => (v ..notNull() - ..isMin(min: 18, shouldValidate: (_) => vipInput.value) - // ..satisfy( - // (value) { - // if (!vipInput.value) { - // return true; - // } - - // return value >= 18; - // }, - // devError: (_) => 'When VIP enabled you must be at least 18 years old.', - // key: _ErrorKeys.ageRestriction, - // ) - ) + ..isMin(min: 18, shouldValidate: (_) => vipInput.value)) .build(), value: 0, dependencies: () => [vipInput], diff --git a/storybook/lib/main.dart b/storybook/lib/main.dart index 1c018ab..1c1008f 100644 --- a/storybook/lib/main.dart +++ b/storybook/lib/main.dart @@ -19,9 +19,9 @@ void main() async { final _ = WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); - const debugModel = String.fromEnvironment('DEBUG_MODEL', defaultValue: 'false') == 'true'; + const isDebugModel = String.fromEnvironment('DEBUG_MODEL', defaultValue: 'false') == 'true'; - if (debugModel) { + if (isDebugModel) { runApp(const _DebugModelApp()); } diff --git a/storybook/lib/usecases/dependencies/checkbox_dependency_change.dart b/storybook/lib/usecases/dependencies/checkbox_dependency_change.dart index 9d921e8..5b90e2c 100644 --- a/storybook/lib/usecases/dependencies/checkbox_dependency_change.dart +++ b/storybook/lib/usecases/dependencies/checkbox_dependency_change.dart @@ -96,13 +96,11 @@ In this example both bussiness rules controll checkbox via onChange and onDepend TextFormField( controller: formModel.nameInput.controller, decoration: const InputDecoration(labelText: 'Name'), - // onChanged: formModel.nameInput.updateValueWithString, validator: formModel.nameInput.textFormFieldInputValidator, ), TextFormField( controller: formModel.ageInput.controller, decoration: const InputDecoration(labelText: 'Age'), - // onChanged: formModel.ageInput.updateValueWithString, validator: (v) => formModel.ageInput.textFormFieldInputValidator(v), ), CheckboxListTile( diff --git a/storybook/lib/usecases/onchange/two_way_checkbox_change.dart b/storybook/lib/usecases/onchange/two_way_checkbox_change.dart index 1ef8313..eaded0f 100644 --- a/storybook/lib/usecases/onchange/two_way_checkbox_change.dart +++ b/storybook/lib/usecases/onchange/two_way_checkbox_change.dart @@ -90,13 +90,11 @@ If *age* is changed to value under 18, *vip content* is unchecked and vice-versa TextFormField( controller: formModel.nameInput.controller, decoration: const InputDecoration(labelText: 'Name'), - // onChanged: formModel.nameInput.updateValueWithString, validator: formModel.nameInput.textFormFieldInputValidator, ), TextFormField( controller: formModel.ageInput.controller, decoration: const InputDecoration(labelText: 'Age'), - // onChanged: formModel.ageInput.updateValueWithString, validator: (v) => formModel.ageInput.textFormFieldInputValidator(v), ), CheckboxListTile( From a14e487ba711bf7030705cd4ce07a0996fff3948 Mon Sep 17 00:00:00 2001 From: Petr Nymsa Date: Wed, 29 Jan 2025 16:25:22 +0100 Subject: [PATCH 4/8] Add required constructor variants --- .../src/core/input/glade_date_time_input.dart | 23 +++++++++++++++ .../lib/src/core/input/glade_int_input.dart | 29 +++++++++++++++++-- .../src/core/input/glade_string_input.dart | 28 +++++++++++++++--- .../validator/specialized/int_validator.dart | 2 +- 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/glade_forms/lib/src/core/input/glade_date_time_input.dart b/glade_forms/lib/src/core/input/glade_date_time_input.dart index ce2eb23..df43602 100644 --- a/glade_forms/lib/src/core/input/glade_date_time_input.dart +++ b/glade_forms/lib/src/core/input/glade_date_time_input.dart @@ -51,4 +51,27 @@ class GladeDateTimeInputNullable extends GladeInput { stringToValueConverter: stringToValueConverter ?? GladeTypeConverters.dateTimeIso8601Nullable, validatorInstance: validator?.call(DateTimeValidatorNullable()) ?? DateTimeValidatorNullable().build(), ); + + GladeDateTimeInputNullable.required({ + super.inputKey, + super.value, + super.initialValue, + DateTimeValidatorFactoryNullable? validator, + super.isPure, + super.translateError, + super.valueComparator, + StringToTypeConverter? stringToValueConverter, + super.dependencies, + super.onChange, + super.onDependencyChange, + super.textEditingController, + super.useTextEditingController = false, + super.valueTransform, + super.defaultTranslations, + super.trackUnchanged = true, + }) : super.internalCreate( + stringToValueConverter: stringToValueConverter ?? GladeTypeConverters.dateTimeIso8601Nullable, + validatorInstance: validator?.call(DateTimeValidatorNullable()..notNull()) ?? + (DateTimeValidatorNullable()..notNull()).build(), + ); } diff --git a/glade_forms/lib/src/core/input/glade_int_input.dart b/glade_forms/lib/src/core/input/glade_int_input.dart index 1a533ca..e8ba36f 100644 --- a/glade_forms/lib/src/core/input/glade_int_input.dart +++ b/glade_forms/lib/src/core/input/glade_int_input.dart @@ -29,8 +29,8 @@ class GladeIntInput extends GladeInput { ); } -class GladeIntInputNullabe extends GladeInput { - GladeIntInputNullabe({ +class GladeIntInputNullable extends GladeInput { + GladeIntInputNullable({ super.inputKey, super.value, super.initialValue, @@ -38,7 +38,7 @@ class GladeIntInputNullabe extends GladeInput { super.isPure, super.translateError, super.valueComparator, - StringToTypeConverter? stringToValueConverter, + StringToTypeConverter? stringToValueConverter, super.dependencies, super.onChange, super.onDependencyChange, @@ -51,4 +51,27 @@ class GladeIntInputNullabe extends GladeInput { stringToValueConverter: stringToValueConverter ?? GladeTypeConverters.intConverterNullable, validatorInstance: validator?.call(IntValidatorNullable()) ?? IntValidatorNullable().build(), ); + + GladeIntInputNullable.required({ + super.inputKey, + super.value, + super.initialValue, + IntValidatorFactoryNullable? validator, + super.isPure, + super.translateError, + super.valueComparator, + StringToTypeConverter? stringToValueConverter, + super.dependencies, + super.onChange, + super.onDependencyChange, + super.textEditingController, + super.useTextEditingController = false, + super.valueTransform, + super.defaultTranslations, + super.trackUnchanged = true, + }) : super.internalCreate( + stringToValueConverter: stringToValueConverter ?? GladeTypeConverters.intConverterNullable, + validatorInstance: + validator?.call(IntValidatorNullable()..notNull()) ?? (IntValidatorNullable()..notNull()).build(), + ); } diff --git a/glade_forms/lib/src/core/input/glade_string_input.dart b/glade_forms/lib/src/core/input/glade_string_input.dart index 12992e6..ff04a59 100644 --- a/glade_forms/lib/src/core/input/glade_string_input.dart +++ b/glade_forms/lib/src/core/input/glade_string_input.dart @@ -19,12 +19,32 @@ class GladeStringInput extends GladeInput { super.valueTransform, super.defaultTranslations, super.trackUnchanged = true, - bool isRequired = true, }) : super.internalCreate( value: value ?? initialValue ?? '', initialValue: initialValue ?? '', - validatorInstance: isRequired - ? (validator?.call(StringValidator()..notEmpty()) ?? (StringValidator()..notEmpty()).build()) - : (validator?.call(StringValidator()) ?? StringValidator().build()), + validatorInstance: validator?.call(StringValidator()) ?? StringValidator().build(), + ); + + GladeStringInput.required({ + super.inputKey, + String? value, + String? initialValue, + StringValidatorFactory? validator, + super.isPure, + super.translateError, + super.valueComparator, + super.stringToValueConverter, + super.dependencies, + super.onChange, + super.onDependencyChange, + super.textEditingController, + super.useTextEditingController = true, + super.valueTransform, + super.defaultTranslations, + super.trackUnchanged = true, + }) : super.internalCreate( + value: value ?? initialValue ?? '', + initialValue: initialValue ?? '', + validatorInstance: validator?.call(StringValidator()..notEmpty()) ?? (StringValidator()..notEmpty()).build(), ); } diff --git a/glade_forms/lib/src/validator/specialized/int_validator.dart b/glade_forms/lib/src/validator/specialized/int_validator.dart index 5d2dc22..3a64708 100644 --- a/glade_forms/lib/src/validator/specialized/int_validator.dart +++ b/glade_forms/lib/src/validator/specialized/int_validator.dart @@ -3,7 +3,7 @@ import 'package:glade_forms/src/src.dart'; typedef IntValidatorFactory = ValidatorInstance Function(IntValidator validator); -typedef IntValidatorFactoryNullable = ValidatorInstance Function(IntValidatorNullable validator); +typedef IntValidatorFactoryNullable = ValidatorInstance Function(IntValidatorNullable validator); class IntValidator extends GladeValidator { /// Compares given value with [min] and [max] values. With [inclusiveInterval] set to true(default), the comparison is inclusive. From 83a7ff8433949508e028519d2b93ec314bd7cdbe Mon Sep 17 00:00:00 2001 From: Petr Nymsa Date: Wed, 29 Jan 2025 16:30:19 +0100 Subject: [PATCH 5/8] Revert to isRequired argument instead of factory constructor --- .../lib/src/core/input/glade_bool_input.dart | 5 ++- .../src/core/input/glade_date_time_input.dart | 34 +++++-------------- .../lib/src/core/input/glade_int_input.dart | 33 +++++------------- .../src/core/input/glade_string_input.dart | 28 +++------------ 4 files changed, 25 insertions(+), 75 deletions(-) diff --git a/glade_forms/lib/src/core/input/glade_bool_input.dart b/glade_forms/lib/src/core/input/glade_bool_input.dart index 4d48645..89edd16 100644 --- a/glade_forms/lib/src/core/input/glade_bool_input.dart +++ b/glade_forms/lib/src/core/input/glade_bool_input.dart @@ -18,8 +18,11 @@ class GladeBoolInput extends GladeInput { super.valueTransform, super.defaultTranslations, super.trackUnchanged = true, + bool isRequired = true, }) : super.internalCreate( stringToValueConverter: stringToValueConverter ?? GladeTypeConverters.boolConverter, - validatorInstance: validator?.call(GladeValidator()) ?? GladeValidator().build(), + validatorInstance: isRequired + ? (validator?.call(GladeValidator()..notNull()) ?? (GladeValidator()..notNull()).build()) + : (validator?.call(GladeValidator()) ?? GladeValidator().build()), ); } diff --git a/glade_forms/lib/src/core/input/glade_date_time_input.dart b/glade_forms/lib/src/core/input/glade_date_time_input.dart index df43602..60df68b 100644 --- a/glade_forms/lib/src/core/input/glade_date_time_input.dart +++ b/glade_forms/lib/src/core/input/glade_date_time_input.dart @@ -23,9 +23,12 @@ class GladeDateTimeInput extends GladeInput { super.valueTransform, super.defaultTranslations, super.trackUnchanged = true, + bool isRequired = true, }) : super.internalCreate( stringToValueConverter: stringToValueConverter ?? GladeTypeConverters.dateTimeIso8601, - validatorInstance: validator?.call(DateTimeValidator()) ?? DateTimeValidator().build(), + validatorInstance: isRequired + ? (validator?.call(DateTimeValidator()..notNull()) ?? (DateTimeValidator()..notNull()).build()) + : (validator?.call(DateTimeValidator()) ?? DateTimeValidator().build()), ); } @@ -47,31 +50,12 @@ class GladeDateTimeInputNullable extends GladeInput { super.valueTransform, super.defaultTranslations, super.trackUnchanged = true, + bool isRquired = false, }) : super.internalCreate( stringToValueConverter: stringToValueConverter ?? GladeTypeConverters.dateTimeIso8601Nullable, - validatorInstance: validator?.call(DateTimeValidatorNullable()) ?? DateTimeValidatorNullable().build(), - ); - - GladeDateTimeInputNullable.required({ - super.inputKey, - super.value, - super.initialValue, - DateTimeValidatorFactoryNullable? validator, - super.isPure, - super.translateError, - super.valueComparator, - StringToTypeConverter? stringToValueConverter, - super.dependencies, - super.onChange, - super.onDependencyChange, - super.textEditingController, - super.useTextEditingController = false, - super.valueTransform, - super.defaultTranslations, - super.trackUnchanged = true, - }) : super.internalCreate( - stringToValueConverter: stringToValueConverter ?? GladeTypeConverters.dateTimeIso8601Nullable, - validatorInstance: validator?.call(DateTimeValidatorNullable()..notNull()) ?? - (DateTimeValidatorNullable()..notNull()).build(), + validatorInstance: isRquired + ? (validator?.call(DateTimeValidatorNullable()..notNull()) ?? + (DateTimeValidatorNullable()..notNull()).build()) + : (validator?.call(DateTimeValidatorNullable()) ?? DateTimeValidatorNullable().build()), ); } diff --git a/glade_forms/lib/src/core/input/glade_int_input.dart b/glade_forms/lib/src/core/input/glade_int_input.dart index e8ba36f..cff3d8f 100644 --- a/glade_forms/lib/src/core/input/glade_int_input.dart +++ b/glade_forms/lib/src/core/input/glade_int_input.dart @@ -23,9 +23,12 @@ class GladeIntInput extends GladeInput { super.valueTransform, super.defaultTranslations, super.trackUnchanged = true, + bool isRequired = true, }) : super.internalCreate( stringToValueConverter: stringToValueConverter ?? GladeTypeConverters.intConverter, - validatorInstance: validator?.call(IntValidator()) ?? IntValidator().build(), + validatorInstance: isRequired + ? (validator?.call(IntValidator()..notNull()) ?? (IntValidator()..notNull()).build()) + : (validator?.call(IntValidator()) ?? IntValidator().build()), ); } @@ -47,31 +50,11 @@ class GladeIntInputNullable extends GladeInput { super.valueTransform, super.defaultTranslations, super.trackUnchanged = true, + bool isRequired = false, }) : super.internalCreate( stringToValueConverter: stringToValueConverter ?? GladeTypeConverters.intConverterNullable, - validatorInstance: validator?.call(IntValidatorNullable()) ?? IntValidatorNullable().build(), - ); - - GladeIntInputNullable.required({ - super.inputKey, - super.value, - super.initialValue, - IntValidatorFactoryNullable? validator, - super.isPure, - super.translateError, - super.valueComparator, - StringToTypeConverter? stringToValueConverter, - super.dependencies, - super.onChange, - super.onDependencyChange, - super.textEditingController, - super.useTextEditingController = false, - super.valueTransform, - super.defaultTranslations, - super.trackUnchanged = true, - }) : super.internalCreate( - stringToValueConverter: stringToValueConverter ?? GladeTypeConverters.intConverterNullable, - validatorInstance: - validator?.call(IntValidatorNullable()..notNull()) ?? (IntValidatorNullable()..notNull()).build(), + validatorInstance: isRequired + ? (validator?.call(IntValidatorNullable()..notNull()) ?? (IntValidatorNullable()..notNull()).build()) + : (validator?.call(IntValidatorNullable()) ?? IntValidatorNullable().build()), ); } diff --git a/glade_forms/lib/src/core/input/glade_string_input.dart b/glade_forms/lib/src/core/input/glade_string_input.dart index ff04a59..42d5b68 100644 --- a/glade_forms/lib/src/core/input/glade_string_input.dart +++ b/glade_forms/lib/src/core/input/glade_string_input.dart @@ -19,32 +19,12 @@ class GladeStringInput extends GladeInput { super.valueTransform, super.defaultTranslations, super.trackUnchanged = true, + bool isRequired = true, }) : super.internalCreate( value: value ?? initialValue ?? '', initialValue: initialValue ?? '', - validatorInstance: validator?.call(StringValidator()) ?? StringValidator().build(), - ); - - GladeStringInput.required({ - super.inputKey, - String? value, - String? initialValue, - StringValidatorFactory? validator, - super.isPure, - super.translateError, - super.valueComparator, - super.stringToValueConverter, - super.dependencies, - super.onChange, - super.onDependencyChange, - super.textEditingController, - super.useTextEditingController = true, - super.valueTransform, - super.defaultTranslations, - super.trackUnchanged = true, - }) : super.internalCreate( - value: value ?? initialValue ?? '', - initialValue: initialValue ?? '', - validatorInstance: validator?.call(StringValidator()..notEmpty()) ?? (StringValidator()..notEmpty()).build(), + validatorInstance: isRequired + ? (validator?.call(StringValidator()..notNull()) ?? (StringValidator()..notNull()).build()) + : (validator?.call(StringValidator()) ?? StringValidator().build()), ); } From 30b82bbe8c0ff3f60d2e4c01b547063f301f61de Mon Sep 17 00:00:00 2001 From: Petr Nymsa Date: Wed, 29 Jan 2025 17:10:41 +0100 Subject: [PATCH 6/8] Update colors in debug info widget --- .../src/widgets/glade_model_debug_info.dart | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/glade_forms/lib/src/widgets/glade_model_debug_info.dart b/glade_forms/lib/src/widgets/glade_model_debug_info.dart index 38ee318..1b4cdfc 100644 --- a/glade_forms/lib/src/widgets/glade_model_debug_info.dart +++ b/glade_forms/lib/src/widgets/glade_model_debug_info.dart @@ -192,6 +192,9 @@ class _Table extends StatelessWidget { @override Widget build(BuildContext context) { final inputs = model.inputs.where((element) => !hiddenKeys.contains(element.inputKey)); + final rowColor = Theme.of(context).colorScheme.surface; + final alternativeRowColor = + MediaQuery.platformBrightnessOf(context) == Brightness.dark ? rowColor.lighten() : rowColor.darken(); return Table( defaultColumnWidth: scrollable ? const IntrinsicColumnWidth() : const FlexColumnWidth(), @@ -214,7 +217,7 @@ class _Table extends StatelessWidget { for (final (index, x) in inputs.indexed) TableRow( decoration: BoxDecoration( - color: index.isEven ? Theme.of(context).colorScheme.surface : Theme.of(context).colorScheme.onSecondary, + color: index.isEven ? rowColor : alternativeRowColor, ), children: [ Padding( @@ -396,3 +399,24 @@ class _DangerStrips extends StatelessWidget { return stripes; } } + +extension on Color { + /// Returns color darkened by [amount]. + Color darken([double amount = 0.1]) { + assert(amount >= 0 && amount <= 1, 'amount must be between 0 and 1'); + + final hsl = HSLColor.fromColor(this); + final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0)); + + return hslDark.toColor(); + } + + Color lighten([double amount = 0.1]) { + assert(amount >= 0 && amount <= 1, 'amount must be between 0 and 1'); + + final hsl = HSLColor.fromColor(this); + final hslLight = hsl.withLightness((hsl.lightness + amount).clamp(0.0, 1.0)); + + return hslLight.toColor(); + } +} From d6f8799cf21f0a6686ee96f3d0661ee90fa02f2c Mon Sep 17 00:00:00 2001 From: Petr Nymsa Date: Wed, 29 Jan 2025 17:14:12 +0100 Subject: [PATCH 7/8] Fix StringInput isRequired validation --- glade_forms/lib/src/core/input/glade_string_input.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glade_forms/lib/src/core/input/glade_string_input.dart b/glade_forms/lib/src/core/input/glade_string_input.dart index 42d5b68..12992e6 100644 --- a/glade_forms/lib/src/core/input/glade_string_input.dart +++ b/glade_forms/lib/src/core/input/glade_string_input.dart @@ -24,7 +24,7 @@ class GladeStringInput extends GladeInput { value: value ?? initialValue ?? '', initialValue: initialValue ?? '', validatorInstance: isRequired - ? (validator?.call(StringValidator()..notNull()) ?? (StringValidator()..notNull()).build()) + ? (validator?.call(StringValidator()..notEmpty()) ?? (StringValidator()..notEmpty()).build()) : (validator?.call(StringValidator()) ?? StringValidator().build()), ); } From 43ff55c7da8c71ea0aaa30b88ca5b62f8fa4a69b Mon Sep 17 00:00:00 2001 From: Petr Nymsa Date: Wed, 29 Jan 2025 17:33:20 +0100 Subject: [PATCH 8/8] Add isPositive and isNegative int validators --- glade_forms/CHANGELOG.md | 2 +- .../lib/src/core/error/glade_error_keys.dart | 2 + .../validator/specialized/int_validator.dart | 56 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/glade_forms/CHANGELOG.md b/glade_forms/CHANGELOG.md index c081f0a..2b3a7fd 100644 --- a/glade_forms/CHANGELOG.md +++ b/glade_forms/CHANGELOG.md @@ -11,7 +11,7 @@ - **Added** `inclusive` argument for `int` validations. - `GladeIntInput` and `GladeDateTimeInput` offer *Nullable versions to support null values - `StringInput` does not offer a nullable version as we believe that in most cases you don't really need to differentiate between a null string and an empty string. Feel free to open an issue if you disagree. -- +- **Added** Add `isPositive()` and `isNegative()` to Int validator. diff --git a/glade_forms/lib/src/core/error/glade_error_keys.dart b/glade_forms/lib/src/core/error/glade_error_keys.dart index f886701..73c04cb 100644 --- a/glade_forms/lib/src/core/error/glade_error_keys.dart +++ b/glade_forms/lib/src/core/error/glade_error_keys.dart @@ -12,6 +12,8 @@ abstract final class GladeErrorKeys { static const String intCompareError = 'int-compare-error'; static const String intCompareMaxError = 'int-compare-max-error'; static const String intCompareMinError = 'int-compare-min-error'; + static const String intCompareIsPositiveError = 'int-compare-ispositive-error'; + static const String intCompareIsNegativeError = 'int-compare-isnegative-error'; static const String dateTimeIsBetweenError = 'datetime-compare-isbetween-error'; static const String dateTimeIsAfterError = 'datetime-compare-isafter-error'; static const String dateTimeIsBeforeError = 'datetime-compare-isbefore-error'; diff --git a/glade_forms/lib/src/validator/specialized/int_validator.dart b/glade_forms/lib/src/validator/specialized/int_validator.dart index 3a64708..b0402fd 100644 --- a/glade_forms/lib/src/validator/specialized/int_validator.dart +++ b/glade_forms/lib/src/validator/specialized/int_validator.dart @@ -53,6 +53,34 @@ class IntValidator extends GladeValidator { key: key ?? GladeErrorKeys.intCompareMaxError, shouldValidate: shouldValidate, ); + + void isPositive({ + bool includeZero = true, + OnValidateError? devError, + Object? key, + ShouldValidateCallback? shouldValidate, + }) => + isMin( + min: 0, + isInclusive: includeZero, + devError: devError, + key: key ?? GladeErrorKeys.intCompareIsPositiveError, + shouldValidate: shouldValidate, + ); + + void isNegative({ + bool includeZero = true, + OnValidateError? devError, + Object? key, + ShouldValidateCallback? shouldValidate, + }) => + isMax( + max: 0, + isInclusive: includeZero, + devError: devError, + key: key ?? GladeErrorKeys.intCompareIsNegativeError, + shouldValidate: shouldValidate, + ); } class IntValidatorNullable extends GladeValidator { @@ -115,4 +143,32 @@ class IntValidatorNullable extends GladeValidator { key: key ?? GladeErrorKeys.intCompareMaxError, shouldValidate: shouldValidate, ); + + void isPositive({ + bool includeZero = true, + OnValidateError? devError, + Object? key, + ShouldValidateCallback? shouldValidate, + }) => + isMin( + min: 0, + isInclusive: includeZero, + devError: devError, + key: key ?? GladeErrorKeys.intCompareIsPositiveError, + shouldValidate: shouldValidate, + ); + + void isNegative({ + bool includeZero = true, + OnValidateError? devError, + Object? key, + ShouldValidateCallback? shouldValidate, + }) => + isMax( + max: 0, + isInclusive: includeZero, + devError: devError, + key: key ?? GladeErrorKeys.intCompareIsNegativeError, + shouldValidate: shouldValidate, + ); }