diff --git a/.vscode/settings.json b/.vscode/settings.json index b25a507..0b9fca7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "dart.flutterSdkPath": ".fvm/versions/3.16.5", - "dart.lineLength": 120 + "dart.lineLength": 120, + "yaml.schemaStore.enable": false } \ No newline at end of file diff --git a/glade_forms/CHANGELOG.md b/glade_forms/CHANGELOG.md index ed75660..fc7d8a6 100644 --- a/glade_forms/CHANGELOG.md +++ b/glade_forms/CHANGELOG.md @@ -2,7 +2,9 @@ - **[Breaking]**: TextEditingController is no more created automatically. When TextEditingController is used, input's behavior is slightly changed. See README.md for full info. - **[Breaking]**: GladeInput's controller is now private. Use factory constructors to create input. - **[Breaking]**: `Extra` parameter removed -- **[Breaking]**: `dependencies` are no loonger passed into `onChange` and in validator. +- **[Breaking]**: `dependencies` are no longer passed into `onChange` and in validator. +- **[Add]**: onDependencyChange - callback when dependency was udpated. +- **Improvement**: GladeModelDebugInfo now colorize String values to visualize whitespace. ## 1.6.0 - **Improvement**: GladeModelDebugInfo is more colorful and polished. diff --git a/glade_forms/lib/src/core/glade_input.dart b/glade_forms/lib/src/core/glade_input.dart index 3b83dde..37c59a8 100644 --- a/glade_forms/lib/src/core/glade_input.dart +++ b/glade_forms/lib/src/core/glade_input.dart @@ -23,9 +23,7 @@ typedef ValueTransform = T Function(T input); typedef StringInput = GladeInput; -T _defaultTransform(T input) => input; - -class GladeInput extends ChangeNotifier { +class GladeInput { /// Compares initial and current value. @protected // ignore: prefer-correct-callback-field-name, ok name @@ -64,7 +62,7 @@ class GladeInput extends ChangeNotifier { /// Transforms passed value before assigning it into input. // ignore: prefer-correct-callback-field-name, ok name - ValueTransform valueTransform; + final ValueTransform? _valueTransform; final bool _useTextEditingController; @@ -92,6 +90,8 @@ class GladeInput extends ChangeNotifier { GladeModel? _bindedModel; + InputDependencies get dependencies => dependenciesFactory(); + T? get initialValue => _initialValue; TextEditingController? get controller => _textEditingController; @@ -170,7 +170,7 @@ class GladeInput extends ChangeNotifier { _initialValue = initialValue, dependenciesFactory = dependenciesFactory ?? (() => []), inputKey = inputKey ?? '__${T.runtimeType}__${Random().nextInt(100000000)}', - valueTransform = valueTransform ?? _defaultTransform, + _valueTransform = valueTransform, _textEditingController = textEditingController ?? (useTextEditingController ? TextEditingController( @@ -188,12 +188,6 @@ class GladeInput extends ChangeNotifier { if (_useTextEditingController) { _textEditingController?.addListener(_onTextControllerChange); } - - final dependencies = this.dependenciesFactory(); - - for (final dependency in dependencies) { - dependency.addListener(() => _onDependencyUpdate(dependency.inputKey)); - } } /// At least one of [value] or [initialValue] MUST be set. @@ -589,23 +583,14 @@ class GladeInput extends ChangeNotifier { onChange: onChange ?? this.onChange, onDependencyChange: onDependencyChange ?? this.onDependencyChange, textEditingController: textEditingController ?? this._textEditingController, - valueTransform: valueTransform ?? this.valueTransform, + valueTransform: valueTransform ?? this._valueTransform, trackUnchanged: trackUnchanged ?? this.trackUnchanged, ); } @mustCallSuper - @override void dispose() { _textEditingController?.removeListener(_onTextControllerChange); - - final dependencies = dependenciesFactory(); - - for (final dependency in dependencies) { - dependency.removeListener(() => _onDependencyUpdate(dependency.inputKey)); - } - - super.dispose(); } void _syncValueWithController(T value, {required bool shouldTriggerOnChange}) { @@ -639,8 +624,7 @@ class GladeInput extends ChangeNotifier { void _setValue(T value, {required bool shouldTriggerOnChange}) { _previousValue = _value; - - _value = valueTransform(value); + _value = _useTextEditingController ? value : (_valueTransform?.call(value) ?? value); _isPure = false; __conversionError = null; @@ -659,12 +643,6 @@ class GladeInput extends ChangeNotifier { } _bindedModel?.notifyInputUpdated(this); - - notifyListeners(); - } - - void _onDependencyUpdate(String inputKey) { - onDependencyChange?.call(inputKey); } // * diff --git a/glade_forms/lib/src/model/glade_model.dart b/glade_forms/lib/src/model/glade_model.dart index 6dab0d4..d336dbe 100644 --- a/glade_forms/lib/src/model/glade_model.dart +++ b/glade_forms/lib/src/model/glade_model.dart @@ -98,6 +98,7 @@ abstract class GladeModel extends ChangeNotifier { _lastUpdates.add(input); } else { _lastUpdates = [input]; + notifyDependecies(); notifyListeners(); } } @@ -110,6 +111,18 @@ abstract class GladeModel extends ChangeNotifier { _groupEdit = false; + notifyDependecies(); + notifyListeners(); } + + void notifyDependecies() { + for (final updatedInput in _lastUpdates) { + for (final input in inputs) { + if (input.dependencies.any((i) => i.inputKey == updatedInput.inputKey)) { + input.onDependencyChange?.call(updatedInput.inputKey); + } + } + } + } } 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 978f385..b585c2a 100644 --- a/glade_forms/lib/src/widgets/glade_model_debug_info.dart +++ b/glade_forms/lib/src/widgets/glade_model_debug_info.dart @@ -80,30 +80,40 @@ class GladeModelDebugInfo extends StatelessWidget { onPressed: () { final _ = showDialog( context: context, - builder: (context) => const AlertDialog( + builder: (context) => AlertDialog( content: Column( children: [ - Text("Each column's value has tooltip. Use it to display full value."), - Text('Bool values with black color mark untracked values within model.'), - Row( + Align( + alignment: Alignment.centerRight, + child: IconButton( + onPressed: () => Navigator.of(context).pop(), + icon: const Icon(Icons.close), + ), + ), + const Text("Each column's value has tooltip. Use it to display full value."), + const Text('Bool values with black color mark untracked values within model.'), + const Row( children: [ Icon(Icons.check, color: Colors.green), Text('True value, tracked'), ], ), - Row( + const Row( children: [ Icon(Icons.close, color: Colors.red), Text('False value, tracked'), ], ), - Row( + const Row( children: [ Icon(Icons.check, color: Colors.black), Icon(Icons.close, color: Colors.black), Text('True/False untracked'), ], ), + const SizedBox(height: 20), + const Text('String value is annotated with colored background such as'), + const _StringValue(x: ' There is whitespace at the beginning.'), ], ), ), @@ -214,9 +224,9 @@ class _Table extends StatelessWidget { if (showIsValid) _RowValue(value: x.isValid), if (showValidationError) _RowValue(value: x.errorFormatted()), if (showConversionError) _RowValue(value: x.hasConversionError), - if (showValue) _RowValue(value: x.value), - if (showInitialValue) _RowValue(value: x.initialValue), - if (showControllerText) _RowValue(value: x.controller?.text), + if (showValue) _RowValue(value: x.value, colorizedValue: true), + if (showInitialValue) _RowValue(value: x.initialValue, colorizedValue: true), + if (showControllerText) _RowValue(value: x.controller?.text, colorizedValue: true), ], ), ), @@ -242,8 +252,15 @@ class _RowValue extends StatelessWidget { final bool tracked; final bool wrap; final bool center; + final bool colorizedValue; - const _RowValue({required this.value, this.wrap = false, this.tracked = true, this.center = true}); + const _RowValue({ + required this.value, + this.wrap = false, + this.tracked = true, + this.center = true, + this.colorizedValue = false, + }); @override Widget build(BuildContext context) { @@ -253,6 +270,19 @@ class _RowValue extends StatelessWidget { return _BoolIcon(value: x, colorize: tracked); } + if (value case final String? x when x != null && colorizedValue) { + return Align( + alignment: center ? Alignment.center : Alignment.centerLeft, + child: Tooltip( + message: x, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 200), + child: _StringValue(x: x), + ), + ), + ); + } + return Align( alignment: center ? Alignment.center : Alignment.centerLeft, child: Tooltip( @@ -270,6 +300,21 @@ class _RowValue extends StatelessWidget { } } +class _StringValue extends StatelessWidget { + final String? x; + + const _StringValue({required this.x}); + + @override + Widget build(BuildContext context) { + return Text( + x ?? '', + style: + Theme.of(context).textTheme.bodyMedium?.copyWith(backgroundColor: const Color.fromARGB(255, 196, 222, 184)), + ); + } +} + class _BoolIcon extends StatelessWidget { final bool value; final bool colorize; diff --git a/glade_forms/pubspec.yaml b/glade_forms/pubspec.yaml index cf77343..da3acac 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: 1.6.0 +version: 2.0.0 repository: https://github.com/netglade/glade_forms issue_tracker: https://github.com/netglade/glade_forms/issues screenshots: diff --git a/storybook/lib/usecases/dependencies/checkbox_dependency_change.dart b/storybook/lib/usecases/dependencies/checkbox_dependency_change.dart index 3eec218..7d549f7 100644 --- a/storybook/lib/usecases/dependencies/checkbox_dependency_change.dart +++ b/storybook/lib/usecases/dependencies/checkbox_dependency_change.dart @@ -90,7 +90,7 @@ In this example both bussiness rules controll checkbox via onChange and onDepend return UsecaseContainer( shortDescription: "Age input depends on checkbox's value automatically", description: markdownData, - className: 'checkbox_dependency_change.dart', + className: 'dependencies/checkbox_dependency_change.dart', child: GladeFormBuilder.create( // ignore: avoid-undisposed-instances, handled by GladeFormBuilder create: (context) => AgeRestrictedModel(), diff --git a/storybook/lib/usecases/onchange/one_checkbox_deps_validation.dart b/storybook/lib/usecases/onchange/one_checkbox_deps_validation.dart index 5cb6319..5f0a762 100644 --- a/storybook/lib/usecases/onchange/one_checkbox_deps_validation.dart +++ b/storybook/lib/usecases/onchange/one_checkbox_deps_validation.dart @@ -74,7 +74,7 @@ If *VIP content* is checked, **age** must be over 18. return UsecaseContainer( shortDescription: "Age input validation depends on checkbox's value", description: markdownData, - className: 'one_checkbox_deps_validation.dart', + className: 'onchange/one_checkbox_deps_validation.dart', child: GladeFormBuilder.create( // ignore: avoid-undisposed-instances, handled by GladeFormBuilder create: (context) => AgeRestrictedModel(), diff --git a/storybook/lib/usecases/onchange/two_way_checkbox_change.dart b/storybook/lib/usecases/onchange/two_way_checkbox_change.dart index 18c6d58..28de5f5 100644 --- a/storybook/lib/usecases/onchange/two_way_checkbox_change.dart +++ b/storybook/lib/usecases/onchange/two_way_checkbox_change.dart @@ -86,7 +86,7 @@ If *age* is changed to value under 18, *vip content* is unchecked and vice-versa return UsecaseContainer( shortDescription: "Age input depends on checkbox's value automatically", description: markdownData, - className: 'two_way_checkbox_change.dart', + className: 'onchange/two_way_checkbox_change.dart', child: GladeFormBuilder.create( // ignore: avoid-undisposed-instances, handled by GladeFormBuilder create: (context) => AgeRestrictedModel(), diff --git a/storybook/pubspec.yaml b/storybook/pubspec.yaml index bd50fa5..0f6f025 100644 --- a/storybook/pubspec.yaml +++ b/storybook/pubspec.yaml @@ -28,3 +28,6 @@ flutter: assets: - assets/translations/ - lib/usecases/ + - lib/usecases/dependencies/ + - lib/usecases/onchange/ + - lib/usecases/regress/