Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async input #25

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
- uses: actions/checkout@v4

- name: Configure FVM
uses: kuhnroyal/flutter-fvm-config-action@v1
uses: kuhnroyal/flutter-fvm-config-action@v2
id: fvm-config-action
with:
path: ".fvm/fvm_config.json"
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/storybook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
- uses: actions/checkout@v4

- name: Configure FVM
uses: kuhnroyal/flutter-fvm-config-action@v1
uses: kuhnroyal/flutter-fvm-config-action@v2
with:
path: ".fvm/fvm_config.json"

Expand All @@ -57,14 +57,14 @@ jobs:
run: flutter build web --base-href "/glade_forms/"

- name: Setup Pages
uses: actions/configure-pages@v3
uses: actions/configure-pages@v4

- name: Upload artifact
uses: actions/upload-pages-artifact@v2
uses: actions/upload-pages-artifact@v3
with:
# Upload entire repository
path: "storybook/build/web/"

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
uses: actions/deploy-pages@v4
51 changes: 48 additions & 3 deletions glade_forms/lib/src/core/glade_input.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:math';

import 'package:flutter/widgets.dart';
Expand All @@ -8,7 +9,7 @@ import 'package:glade_forms/src/core/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/model/glade_model.dart';
import 'package:glade_forms/src/model/model.dart';
import 'package:glade_forms/src/validator/validator.dart';
import 'package:glade_forms/src/validator/validator_result.dart';
import 'package:meta/meta.dart';
Expand All @@ -17,6 +18,7 @@ typedef ValueComparator<T> = bool Function(T? initial, T? value);
typedef ValidatorFactory<T> = ValidatorInstance<T> Function(GladeValidator<T> v);
typedef StringValidatorFactory = ValidatorInstance<String?> Function(StringValidator validator);
typedef OnChange<T> = void Function(ChangesInfo<T> info, InputDependencies dependencies);
typedef OnChangeAsync<T> = Future<void> Function(ChangesInfo<T> info, InputDependencies dependencies);
typedef ValueTransform<T> = T Function(T input);

typedef StringInput = GladeInput<String?>;
Expand Down Expand Up @@ -49,6 +51,9 @@ class GladeInput<T> extends ChangeNotifier {
/// Called when input's value changed.
OnChange<T>? onChange;

/// Called when input's value changed.
OnChangeAsync<T>? onChangeAsync;

/// Transforms passed value before assigning it into input.
ValueTransform<T> valueTransform;

Expand All @@ -71,7 +76,9 @@ class GladeInput<T> extends ChangeNotifier {
/// Input is in invalid state when there was conversion error.
ConvertError<T>? __conversionError;

GladeModel? _bindedModel;
GladeModelBase? _bindedModel;

bool _isChanging = false;

T? get initialValue => _initialValue;

Expand Down Expand Up @@ -101,6 +108,8 @@ class GladeInput<T> extends ChangeNotifier {
/// String representattion of [value].
String get stringValue => stringTovalueConverter?.convertBack(value) ?? value.toString();

bool get isChanging => _isChanging;

set value(T value) {
_previousValue = _value;

Expand Down Expand Up @@ -130,6 +139,8 @@ class GladeInput<T> extends ChangeNotifier {
_bindedModel?.notifyInputUpdated(this);

notifyListeners();

unawaited(_onChangeAsyncCall());
}

// ignore: avoid_setters_without_getters, ok for internal use
Expand All @@ -149,6 +160,7 @@ class GladeInput<T> extends ChangeNotifier {
required InputDependenciesFactory? dependenciesFactory,
required this.defaultTranslations,
required this.onChange,
required this.onChangeAsync,
required ValueTransform<T>? valueTransform,
T? initialValue,
TextEditingController? textEditingController,
Expand Down Expand Up @@ -188,6 +200,7 @@ class GladeInput<T> extends ChangeNotifier {
StringToTypeConverter<T>? valueConverter,
InputDependenciesFactory? dependencies,
OnChange<T>? onChange,
OnChangeAsync<T>? onChangeAsync,
TextEditingController? textEditingController,
bool createTextController = true,
ValueTransform<T>? valueTransform,
Expand All @@ -206,6 +219,7 @@ class GladeInput<T> extends ChangeNotifier {
stringTovalueConverter: valueConverter,
dependenciesFactory: dependencies,
onChange: onChange,
onChangeAsync: onChangeAsync,
textEditingController: textEditingController,
createTextController: createTextController,
valueTransform: valueTransform,
Expand All @@ -228,6 +242,7 @@ class GladeInput<T> extends ChangeNotifier {
StringToTypeConverter<T>? valueConverter,
InputDependenciesFactory? dependencies,
OnChange<T>? onChange,
OnChangeAsync<T>? onChangeAsync,
TextEditingController? textEditingController,
bool createTextController = true,
ValueTransform<T>? valueTransform,
Expand All @@ -243,6 +258,7 @@ class GladeInput<T> extends ChangeNotifier {
pure: pure,
dependencies: dependencies,
onChange: onChange,
onChangeAsync: onChangeAsync,
textEditingController: textEditingController,
createTextController: createTextController,
valueTransform: valueTransform,
Expand All @@ -261,6 +277,7 @@ class GladeInput<T> extends ChangeNotifier {
StringToTypeConverter<T>? valueConverter,
InputDependenciesFactory? dependencies,
OnChange<T>? onChange,
OnChangeAsync<T>? onChangeAsync,
TextEditingController? textEditingController,
bool createTextController = true,
ValueTransform<T>? valueTransform,
Expand All @@ -276,14 +293,15 @@ class GladeInput<T> extends ChangeNotifier {
pure: pure,
dependencies: dependencies,
onChange: onChange,
onChangeAsync: onChangeAsync,
textEditingController: textEditingController,
createTextController: createTextController,
valueTransform: valueTransform,
);

@internal
// ignore: use_setters_to_change_properties, as method.
void bindToModel(GladeModel model) => _bindedModel = model;
void bindToModel(GladeModelBase model) => _bindedModel = model;

static GladeInput<int> intInput({
required int value,
Expand All @@ -295,6 +313,7 @@ class GladeInput<T> extends ChangeNotifier {
ValueComparator<int>? valueComparator,
InputDependenciesFactory? dependencies,
OnChange<int>? onChange,
OnChangeAsync<int>? onChangeAsync,
TextEditingController? textEditingController,
bool createTextController = true,
ValueTransform<int>? valueTransform,
Expand All @@ -310,6 +329,7 @@ class GladeInput<T> extends ChangeNotifier {
dependencies: dependencies,
valueConverter: GladeTypeConverters.intConverter,
onChange: onChange,
onChangeAsync: onChangeAsync,
textEditingController: textEditingController,
createTextController: createTextController,
valueTransform: valueTransform,
Expand All @@ -325,6 +345,7 @@ class GladeInput<T> extends ChangeNotifier {
ValueComparator<bool>? valueComparator,
InputDependenciesFactory? dependencies,
OnChange<bool>? onChange,
OnChangeAsync<bool>? onChangeAsync,
TextEditingController? textEditingController,
bool createTextController = true,
ValueTransform<bool>? valueTransform,
Expand All @@ -340,6 +361,7 @@ class GladeInput<T> extends ChangeNotifier {
dependencies: dependencies,
valueConverter: GladeTypeConverters.boolConverter,
onChange: onChange,
onChangeAsync: onChangeAsync,
textEditingController: textEditingController,
createTextController: createTextController,
valueTransform: valueTransform,
Expand All @@ -355,6 +377,7 @@ class GladeInput<T> extends ChangeNotifier {
DefaultTranslations? defaultTranslations,
InputDependenciesFactory? dependencies,
OnChange<String?>? onChange,
OnChangeAsync<String?>? onChangeAsync,
TextEditingController? textEditingController,
bool createTextController = true,
bool isRequired = true,
Expand All @@ -374,6 +397,7 @@ class GladeInput<T> extends ChangeNotifier {
inputKey: inputKey,
dependenciesFactory: dependencies,
onChange: onChange,
onChangeAsync: onChangeAsync,
textEditingController: textEditingController,
createTextController: createTextController,
valueComparator: valueComparator,
Expand Down Expand Up @@ -480,6 +504,7 @@ class GladeInput<T> extends ChangeNotifier {
bool? isPure,
DefaultTranslations? defaultTranslations,
OnChange<T>? onChange,
OnChangeAsync<T>? onChangeAsync,
TextEditingController? textEditingController,
// ignore: avoid-unused-parameters, it is here just to be linter happy ¯\_(ツ)_/¯
bool? createTextController,
Expand All @@ -497,11 +522,31 @@ class GladeInput<T> extends ChangeNotifier {
isPure: isPure ?? this.isPure,
defaultTranslations: defaultTranslations ?? this.defaultTranslations,
onChange: onChange ?? this.onChange,
onChangeAsync: onChangeAsync ?? this.onChangeAsync,
textEditingController: textEditingController ?? this._textEditingController,
valueTransform: valueTransform ?? this.valueTransform,
);
}

Future<void> _onChangeAsyncCall() async {
_isChanging = true;

// propagate input's changes
await onChangeAsync?.call(
ChangesInfo(
previousValue: _previousValue,
value: value,
initialValue: initialValue,
validatorResult: validate(),
),
dependenciesFactory(),
);

_isChanging = false;

notifyListeners();
}

/// Translates input's errors (validation or conversion).
String? _translate({String delimiter = '.', Object? customError}) {
final err = customError ?? validatorResult;
Expand Down
84 changes: 4 additions & 80 deletions glade_forms/lib/src/model/glade_model.dart
Original file line number Diff line number Diff line change
@@ -1,95 +1,19 @@
import 'package:flutter/foundation.dart';
import 'package:glade_forms/src/core/core.dart';
import 'package:glade_forms/src/model/model.dart';
import 'package:meta/meta.dart';

abstract class GladeModel extends ChangeNotifier {
List<GladeInput<Object?>> _lastUpdates = [];
bool _groupEdit = false;

bool get isValid => inputs.every((input) => input.isValid);

bool get isNotValid => !isValid;

bool get isPure => inputs.every((input) => input.isPure);

bool get isUnchanged => inputs.every((input) => input.isUnchanged);

bool get isDirty => !isPure;

List<GladeInput<Object?>> get inputs;

List<String> get lastUpdatedInputKeys => _lastUpdates.map((e) => e.inputKey).toList();

/// Formats errors from `inputs`.
String get formattedValidationErrors => inputs.map((e) {
if (e.hasConversionError) return '${e.inputKey} - CONVERSION ERROR';

if (e.validatorResult.isInvalid) {
return '${e.inputKey} - ${e.errorFormatted()}';
}

return '${e.inputKey} - VALID';
}).join('\n');

List<Object?> get errors => inputs.map((e) => e.validatorResult).toList();

abstract class GladeModel extends GladeModelBase {
GladeModel() {
initialize();
}

/// Initialize model's inputs.
///
/// `super.initialize()` must be called in the end.
@override
@mustCallSuper
@mustBeOverridden
@protected
void initialize() {
assert(
inputs.map((e) => e.inputKey).length == inputs.map((e) => e.inputKey).toSet().length,
'Model contains inputs with duplicated key!',
);

for (final input in inputs) {
input.bindToModel(this);
}
}

/// Updates model's input with String? value using its converter.
void stringFieldUpdateInput<INPUT extends GladeInput<Object?>>(INPUT input, String? value) {
if (input.value == value) return;

input.updateValueWithString(value);
notifyListeners();
}

/// Updates model's input value.
void updateInput<INPUT extends GladeInput<T?>, T>(INPUT input, T value) {
if (input.value == value) return;

_lastUpdates = [input];

input.value = value;
notifyListeners();
}

@internal
void notifyInputUpdated(GladeInput<Object?> input) {
if (_groupEdit) {
_lastUpdates.add(input);
} else {
_lastUpdates = [input];
notifyListeners();
}
}

/// Use it to update multiple inputs at once before these changes are popragated through notifyListeners().
void groupEdit(void Function() edit) {
_groupEdit = true;

edit();

_groupEdit = false;

notifyListeners();
super.initialize();
}
}
20 changes: 20 additions & 0 deletions glade_forms/lib/src/model/glade_model_async.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'dart:async';

import 'package:glade_forms/src/model/model.dart';
import 'package:meta/meta.dart';

abstract class GladeModelAsync extends GladeModelBase {
GladeModelAsync() {
unawaited(initializeAsync());
}

/// Initialize model's inputs.
///
/// `super.initialize()` must be called in the end.
@mustCallSuper
@mustBeOverridden
@protected
Future<void> initializeAsync() async {
super.initialize();
}
}
Loading
Loading