Skip to content

Commit

Permalink
Add form-field state (#192)
Browse files Browse the repository at this point in the history
* Add form-field state

* Commit from GitHub Actions (Forui Presubmit)

* Fix failing tests and bump min flutter version

* Commit from GitHub Actions (Forui Samples Presubmit)

* Fix form_field

---------

Co-authored-by: Pante <[email protected]>
  • Loading branch information
Pante and Pante authored Sep 15, 2024
1 parent a71dd26 commit 8a39df6
Show file tree
Hide file tree
Showing 12 changed files with 150 additions and 73 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/forui_build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
working-directory: forui
strategy:
matrix:
flutter-version: [ 3.22.x, 3.x ]
flutter-version: [ 3.x ]
steps:
- uses: actions/checkout@v4
- uses: subosito/[email protected]
Expand All @@ -36,7 +36,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
flutter-version: [ 3.22.x, 3.x ]
flutter-version: [ 3.x ]
defaults:
run:
working-directory: forui/example
Expand All @@ -63,7 +63,7 @@ jobs:
working-directory: forui/example
strategy:
matrix:
flutter-version: [ 3.22.x, 3.x ]
flutter-version: [ 3.x ]
steps:
- uses: actions/checkout@v4
- uses: subosito/[email protected]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/samples_build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
working-directory: samples
strategy:
matrix:
flutter-version: [ 3.22.x, 3.x ]
flutter-version: [ 3.x ]
steps:
- uses: actions/checkout@v4
- uses: subosito/[email protected]
Expand Down
2 changes: 2 additions & 0 deletions forui/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## 0.5.0 (Next)

The minimum Flutter version has been increased from `3.19.0` to `3.24.0`.

### Additions

* Add `FButton.icon(...)`.
Expand Down
53 changes: 33 additions & 20 deletions forui/example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
url: "https://pub.dev"
source: hosted
version: "67.0.0"
version: "72.0.0"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.2"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
url: "https://pub.dev"
source: hosted
version: "6.4.1"
version: "6.7.0"
args:
dependency: transitive
description:
Expand Down Expand Up @@ -77,18 +82,18 @@ packages:
dependency: "direct dev"
description:
name: build_runner
sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7"
sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04
url: "https://pub.dev"
source: hosted
version: "2.4.11"
version: "2.4.12"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
url: "https://pub.dev"
source: hosted
version: "7.3.1"
version: "7.3.2"
built_collection:
dependency: transitive
description:
Expand Down Expand Up @@ -157,18 +162,18 @@ packages:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.5"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9"
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab"
url: "https://pub.dev"
source: hosted
version: "2.3.6"
version: "2.3.7"
fake_async:
dependency: transitive
description:
Expand All @@ -181,10 +186,10 @@ packages:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.3"
file:
dependency: transitive
description:
Expand Down Expand Up @@ -367,6 +372,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
macros:
dependency: transitive
description:
name: macros
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
url: "https://pub.dev"
source: hosted
version: "0.1.2-main.4"
matcher:
dependency: transitive
description:
Expand Down Expand Up @@ -395,10 +408,10 @@ packages:
dependency: transitive
description:
name: mime
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
version: "1.0.6"
mockito:
dependency: "direct dev"
description:
Expand Down Expand Up @@ -459,10 +472,10 @@ packages:
dependency: transitive
description:
name: path_provider_android
sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb"
sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7"
url: "https://pub.dev"
source: hosted
version: "2.2.9"
version: "2.2.10"
path_provider_foundation:
dependency: transitive
description:
Expand Down Expand Up @@ -749,5 +762,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.4.0 <4.0.0"
flutter: ">=3.22.0"
dart: ">=3.5.0-259.0.dev <4.0.0"
flutter: ">=3.24.0"
2 changes: 1 addition & 1 deletion forui/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ version: 1.0.0+1

environment:
sdk: '>=3.3.0 <4.0.0'
flutter: ">=3.19.0"
flutter: ">=3.24.0"

dependencies:
flutter:
Expand Down
14 changes: 3 additions & 11 deletions forui/lib/src/foundation/form_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,20 +72,12 @@ abstract class FFormField<T> extends StatelessWidget {
@override
Widget build(BuildContext context) => FormField<T>(
onSaved: onSave,
// TODO: Directly use forceErrorText when available. https://api.flutter.dev/flutter/widgets/FormField/forceErrorText.html
validator: forceErrorText == null ? validator : (_) => forceErrorText,
validator: validator,
initialValue: initialValue,
enabled: enabled,
autovalidateMode:
forceErrorText == null ? autovalidateMode : AutovalidateMode.always, // Workaround for forceErrorText.
autovalidateMode: autovalidateMode,
restorationId: restorationId,
builder: (state) {
if (forceErrorText != null && !state.hasError) {
state.validate(); // TODO: Remove workaround when forceErrorText is available.
}

return builder(context, state);
},
builder: (state) => builder(context, state),
);

/// The builder for the [FormField].
Expand Down
130 changes: 96 additions & 34 deletions forui/lib/src/widgets/select_group/select_group.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// ignore_for_file: invalid_use_of_protected_member

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

Expand All @@ -12,7 +14,9 @@ import 'package:forui/forui.dart';
/// See:
/// * https://forui.dev/docs/select-group for working examples.
/// * [FSelectGroupStyle] for customizing a select group's appearance.
class FSelectGroup<T> extends StatelessWidget {
class FSelectGroup<T> extends FormField<Set<T>> {
static Widget _defaultErrorBuilder(BuildContext context, String error) => Text(error);

/// The controller.
///
/// See:
Expand All @@ -29,63 +33,121 @@ class FSelectGroup<T> extends StatelessWidget {
/// The description displayed below the [label].
final Widget? description;

/// The error displayed below the [description].
///
/// If the value is present, the select group is in an error state.
final Widget? error;
/// The builder for errors displayed below the [description]. Defaults to displaying the error message.
final Widget Function(BuildContext, String) errorBuilder;

/// The items.
final List<FSelectGroupItem<T>> items;

/// Creates a [FSelectGroup].
const FSelectGroup({
FSelectGroup({
required this.controller,
required this.items,
this.style,
this.label,
this.description,
this.error,
this.errorBuilder = _defaultErrorBuilder,
super.onSaved,
super.validator,
super.initialValue,
super.forceErrorText,
super.enabled = true,
super.autovalidateMode,
super.restorationId,
super.key,
});
}) : super(
builder: (field) {
final state = field as _State;
final groupStyle = style ?? state.context.theme.selectGroupStyle;
final labelState = switch (state) {
_ when !enabled => FLabelState.disabled,
_ when state.errorText != null => FLabelState.error,
_ => FLabelState.enabled,
};

return FLabel(
axis: Axis.vertical,
state: labelState,
style: groupStyle.labelStyle,
label: label,
description: description,
error: labelState == FLabelState.error ? errorBuilder(state.context, state.errorText!) : null,
child: Column(
children: [
for (final item in items)
item.builder(
state.context,
controller.select,
controller.contains(item.value),
),
],
),
);
},
);

@override
Widget build(BuildContext context) {
final style = this.style ?? context.theme.selectGroupStyle;
final labelState = error != null ? FLabelState.error : FLabelState.enabled;

return FLabel(
axis: Axis.vertical,
state: labelState,
style: style.labelStyle,
label: label,
description: description,
error: error,
child: ListenableBuilder(
listenable: controller,
builder: (context, _) => Column(
children: [
for (final item in items)
item.builder(
context,
controller.select,
controller.contains(item.value),
),
],
),
),
);
}
FormFieldState<Set<T>> createState() => _State();

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('style', style))
..add(DiagnosticsProperty('controller', controller))
..add(ObjectFlagProperty.has('errorBuilder', errorBuilder))
..add(IterableProperty('items', items));
}
}

class _State<T> extends FormFieldState<Set<T>> {
@override
void initState() {
super.initState();
widget.controller.addListener(_handleControllerChanged);
}

@override
void didUpdateWidget(covariant FSelectGroup<T> old) {
super.didUpdateWidget(old);
if (widget.controller == old.controller) {
return;
}

widget.controller.addListener(_handleControllerChanged);
old.controller.removeListener(_handleControllerChanged);
}

@override
void didChange(Set<T>? values) {
super.didChange(values);
if (!setEquals(widget.controller.values, values)) {
widget.controller.values = values ?? {};
}
}

@override
void reset() {
// Set the controller value before calling super.reset() to let _handleControllerChanged suppress the change.
widget.controller.values = widget.initialValue ?? {};
super.reset();
}

void _handleControllerChanged() {
// Suppress changes that originated from within this class.
//
// In the case where a controller has been passed in to this widget, we register this change listener. In these
// cases, we'll also receive change notifications for changes originating from within this class -- for example, the
// reset() method. In such cases, the FormField value will already have been set.
if (widget.controller.values != value) {
didChange(widget.controller.values);
}
}

@override
FSelectGroup<T> get widget => super.widget as FSelectGroup<T>;
}

/// [FSelectGroup]'s style.
class FSelectGroupStyle with Diagnosticable {
/// The [FLabel]'s style.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ abstract class FSelectGroupController<T> with ChangeNotifier {
/// The currently selected values.
Set<T> get values => {..._values};

@protected
set values(Set<T> values) => {
_values.clear(),
_values.addAll(values),
notifyListeners(),
};

@override
bool operator ==(Object other) =>
identical(this, other) ||
Expand Down
Loading

0 comments on commit 8a39df6

Please sign in to comment.