Skip to content

Commit dca29c3

Browse files
authored
Merge pull request #47 from udos86/15.0.0
v15.0.0
2 parents 687bf29 + 460023b commit dca29c3

9 files changed

Lines changed: 376 additions & 108 deletions

File tree

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
- uses: actions/checkout@v3
1515
- uses: subosito/flutter-action@v2
1616
with:
17-
flutter-version: '3.16.4'
17+
flutter-version: '3.16.5'
1818
channel: 'stable'
1919

2020
- name: Install Dependencies

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
## [15.0.0] - 01/xx/2024
1+
## [15.0.0] - 01/08/2024
22

33
* upgrades to Flutter `3.16.5`
44
* adds new feature **Conditional Form Fields**
55
* adds `conditions` property to `FastFormField`
6-
* adds `FastCondition` class
6+
* adds `FastCondition`, `FastConditionList` classes
7+
* adds `FastConditionMatch` enum
8+
* adds `FastConditionHandler` and `FastConditionTest` typedefs
9+
* adds `testConditions` function to `FastformFieldState`
710
* adds new `FastSegmentendButton` that wraps Material [`SegmentedButton`](https://api.flutter.dev/flutter/material/SegmentedButton-class.html?gclid=CjwKCAiAs6-sBhBmEiwA1Nl8s5c8wRhB056CtAeuZP1-m_XUYi-jYGrVMh1lMnkwRmvKwlTelErtixoCAS4QAvD_BwE&gclsrc=aw.ds)
811
* adds `showInputDecoration` property to `FastCheckbox`, `FastSwitch`, `FastCalendar`, `FastChoiceChips`, `FastRadioGroup`, `FastRangeSlider` and `FastSlider`
912
* adds `canPop` and `onPopInvoked` properties to `FastForm`
@@ -19,6 +22,7 @@
1922
* replaces `FastSliderPrefixBuilder` and `FastSliderSuffixBuilder` with `FastSliderWidgetBuilder` typedef
2023
* adds `FastDatePickerWidgetBuilder` typedef
2124
* fixes generic typing in `FastChoiceChips` and `FastSegmentedControl`
25+
* fixes hard-coded text colors in `checkboxTitleBuilder` and `switchTitleBuilder`
2226

2327
## [14.1.0] - 12/18/2023
2428

README.md

Lines changed: 258 additions & 44 deletions
Large diffs are not rendered by default.

example/lib/main.dart

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -233,13 +233,14 @@ class FormPage extends StatelessWidget {
233233
),
234234
],
235235
conditions: {
236-
FastCondition.disabled: [
236+
FastCondition.disabled: FastConditionList([
237237
FastCondition(
238-
fieldName: 'segmented_button',
239-
condition: (value, field) {
240-
return value is Set<String> && value.isNotEmpty;
241-
}),
242-
]
238+
target: 'segmented_button',
239+
test: (value, field) {
240+
return value is Set<String> && value.isNotEmpty;
241+
},
242+
),
243+
]),
243244
},
244245
validator: (value) => value == null || value.isEmpty
245246
? 'Please select at least one chip'
@@ -260,13 +261,15 @@ class FormPage extends StatelessWidget {
260261
prefix: const Icon(Icons.calendar_today),
261262
buildCounter: inputCounterWidgetBuilder,
262263
inputFormatters: const [],
263-
validator: Validators.compose([
264-
Validators.required((value) => 'Field is required'),
265-
Validators.minLength(
266-
7,
267-
(value, minLength) =>
268-
'Field must contain at least $minLength characters')
269-
]),
264+
validator: Validators.compose(
265+
[
266+
Validators.required((value) => 'Field is required'),
267+
Validators.minLength(
268+
7,
269+
(value, minLength) =>
270+
'Field must contain at least $minLength characters')
271+
],
272+
),
270273
),
271274
const FastDropdown(
272275
name: 'dropdown',

lib/src/check/checkbox.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,14 @@ Widget? checkboxPrefixBuilder(FastCheckboxState field) {
145145
Widget? checkboxTitleBuilder(FastCheckboxState field) {
146146
final FastCheckboxState(:value!) = field;
147147
final FastCheckbox(:titleText) = field.widget;
148+
final theme = Theme.of(field.context);
149+
final color = theme.textTheme.titleMedium?.color ?? Colors.black;
148150

149151
if (titleText is String) {
150152
return Text(
151153
titleText,
152154
style: TextStyle(
153-
color: value ? Colors.black : Colors.grey,
155+
color: value ? color : theme.disabledColor,
154156
),
155157
);
156158
}

lib/src/check/switch.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,14 @@ Widget? switchPrefixBuilder(FastSwitchState field) {
157157
Widget? switchTitleBuilder(FastSwitchState field) {
158158
final FastSwitchState(:value!, :widget) = field;
159159
final FastSwitch(:titleText) = widget;
160+
final theme = Theme.of(field.context);
161+
final color = theme.textTheme.titleMedium?.color ?? Colors.black;
160162

161163
if (titleText is String) {
162164
return Text(
163165
titleText,
164166
style: TextStyle(
165-
color: value ? Colors.black : Colors.grey,
167+
color: value ? color : theme.disabledColor,
166168
),
167169
);
168170
}

lib/src/form.dart

Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class FastFormState extends State<FastForm> {
7777
void onChanged() {
7878
widget.onChanged?.call(values);
7979
for (final field in _fields.values) {
80-
field.condition();
80+
field.testConditions();
8181
}
8282
}
8383

@@ -115,30 +115,76 @@ class _FastFormScope extends InheritedWidget {
115115
bool updateShouldNotify(InheritedWidget oldWidget) => true;
116116
}
117117

118+
/// A [Function] that tests whether a single [FastCondition] is met.
119+
typedef FastConditionTest = bool Function(
120+
dynamic value, FastFormFieldState field);
121+
122+
/// A [Function] that defines a conditional state of a [FastFormField].
123+
///
124+
/// Implements, what happens when a condition is met or not.
125+
///
126+
/// Typically linked to [FastConditionList].
127+
///
128+
/// Called at the end of every [FastFormFieldState.testConditions] run.
118129
typedef FastConditionHandler = void Function(
119130
bool isMet, FastFormFieldState field);
120131

132+
/// A single condition to be met for a conditional state to occur.
121133
@immutable
122134
class FastCondition {
123135
const FastCondition({
124-
required this.fieldName,
125-
this.required = false,
126-
required this.condition,
136+
required this.target,
137+
required this.test,
127138
});
128139

129-
final String fieldName;
130-
final bool required;
131-
final bool Function(dynamic value, FastFormFieldState field) condition;
140+
/// The name of the [FastFormField] this [test] should be called upon.
141+
final String target;
142+
143+
final FastConditionTest test;
132144

145+
/// A [FastConditionHandler] that disables a [FastFormField] based
146+
/// on whether a [FastCondition] is met.
133147
static void disabled(bool isMet, FastFormFieldState field) {
134148
field.enabled = !isMet;
135149
}
136150

151+
/// A [FastConditionHandler] that enables a [FastFormField] based
152+
/// on whether a [FastCondition] is met.
137153
static void enabled(bool isMet, FastFormFieldState field) {
138154
field.enabled = isMet;
139155
}
140156
}
141157

158+
/// An enum to define a match logic for a [List] of multiple [FastCondition]
159+
/// elements.
160+
///
161+
/// Typically specifies how all individual [FastCondition.test] results in a
162+
/// [FastConditionList] are evaluated to determine whether a condition in
163+
/// [FastFormField.conditions] is met.
164+
enum FastConditionMatch {
165+
/// Every [FastCondition.test] in a [FastConditionList] must be true to met
166+
/// the condition.
167+
every,
168+
169+
/// At least one [FastCondition.test] in a [FastConditionList] must be true to
170+
/// met the condition.
171+
any,
172+
}
173+
174+
/// A wrapper class for a [List] of [FastCondition] elements.
175+
@immutable
176+
class FastConditionList {
177+
const FastConditionList(
178+
this.conditions, {
179+
this.match = FastConditionMatch.any,
180+
});
181+
182+
final List<FastCondition> conditions;
183+
184+
/// Defaults to [FastConditionMatch.any].
185+
final FastConditionMatch match;
186+
}
187+
142188
/// A single fast form field.
143189
///
144190
/// Works identical to built-in [FormField].
@@ -169,7 +215,7 @@ abstract class FastFormField<T> extends FormField<T> {
169215

170216
/// null represents a non-adaptive form field widget
171217
final bool? adaptive;
172-
final Map<FastConditionHandler, List<FastCondition>>? conditions;
218+
final Map<FastConditionHandler, FastConditionList>? conditions;
173219
final EdgeInsetsGeometry? contentPadding;
174220
final InputDecoration? decoration;
175221
final String? helperText;
@@ -304,39 +350,40 @@ abstract class FastFormFieldState<T> extends FormFieldState<T> {
304350
});
305351
}
306352

307-
void condition() {
353+
/// Determines for every [MapEntry] in [FastFormField.conditions] if the
354+
/// respective condition is met.
355+
///
356+
/// Finally calls the corresponding [FastConditionHandler], passing the
357+
/// result of the condition matching.
358+
void testConditions() {
308359
final FastFormField<T>(:conditions) = widget;
309360
if (conditions == null || conditions.isEmpty) return;
310361

311-
final Map<FastConditionHandler, bool> map = {};
312-
313-
for (final MapEntry(key: handler, :value) in conditions.entries) {
314-
map[handler] = false;
362+
for (final MapEntry(key: handler, value: list) in conditions.entries) {
363+
final FastConditionList(:conditions, match: logic) = list;
364+
final bool isMet;
315365

316-
for (final FastCondition(:condition, :fieldName, :required) in value) {
317-
final field = form?.getFieldByName(fieldName);
318-
if (field == null) continue;
366+
test(condition) => _testCondition(condition);
319367

320-
final isMet = condition(field.value, field);
321-
322-
if (isMet && required) {
323-
map.update(handler, (value) => true);
324-
} else if (isMet && !required) {
325-
map.update(handler, (value) => true);
326-
} else if (!isMet && required) {
327-
map.update(handler, (value) => false);
328-
break;
329-
} else if (!isMet && !required) {
330-
map.update(handler, (value) => value || false);
331-
}
368+
switch (logic) {
369+
case FastConditionMatch.any:
370+
isMet = conditions.any(test);
371+
case FastConditionMatch.every:
372+
isMet = conditions.every(test);
332373
}
333-
}
334374

335-
for (final MapEntry(key: handler, value: isMet) in map.entries) {
336375
handler(isMet, this);
337376
}
338377
}
339378

379+
bool _testCondition(FastCondition condition) {
380+
final FastCondition(:target, :test) = condition;
381+
final field = form?.getFieldByName(target);
382+
if (field == null) throw ArgumentError('Target $target is null.');
383+
384+
return test(field.value, field);
385+
}
386+
340387
void _onFocusChanged() {
341388
setState(() {
342389
if (focusNode.hasFocus) {

pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: flutter_fast_forms
2-
description: Flutter Fast Forms is the only Dart package you'll ever need to build Flutter forms fast.
3-
version: 14.1.0
2+
description: Flutter Fast Forms is the only Dart package you need to build Flutter forms fast.
3+
version: 15.0.0
44
repository: https://github.com/udos86/flutter-fast-forms
55

66
environment:

test/conditional_form_fields_test.dart

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ void main() {
88
late FastSwitch fastSwitch;
99
late FastCondition checkboxCondition;
1010
late FastCondition switchCondition;
11-
late FastCondition switchRequiredCondition;
1211

1312
const Map<FastConditionHandler, bool> cases = {
1413
FastCondition.disabled: false,
@@ -27,19 +26,13 @@ void main() {
2726
);
2827

2928
checkboxCondition = FastCondition(
30-
fieldName: 'checkbox',
31-
condition: (value, field) => value is bool && value,
29+
target: 'checkbox',
30+
test: (value, field) => value is bool && value,
3231
);
3332

3433
switchCondition = FastCondition(
35-
fieldName: 'switch',
36-
condition: (value, field) => value is bool && value,
37-
);
38-
39-
switchRequiredCondition = FastCondition(
40-
fieldName: 'switch',
41-
required: true,
42-
condition: (value, field) => value is bool && value,
34+
target: 'switch',
35+
test: (value, field) => value is bool && value,
4336
);
4437
});
4538

@@ -53,7 +46,7 @@ void main() {
5346
enabled: !enabled,
5447
name: 'text_field',
5548
conditions: {
56-
handler: [switchCondition],
49+
handler: FastConditionList([switchCondition]),
5750
},
5851
),
5952
]));
@@ -77,7 +70,7 @@ void main() {
7770
enabled: !enabled,
7871
name: 'text_field',
7972
conditions: {
80-
handler: [checkboxCondition, switchCondition],
73+
handler: FastConditionList([checkboxCondition, switchCondition]),
8174
},
8275
),
8376
]));
@@ -108,7 +101,10 @@ void main() {
108101
enabled: !enabled,
109102
name: 'text_field',
110103
conditions: {
111-
handler: [checkboxCondition, switchRequiredCondition],
104+
handler: FastConditionList(
105+
[checkboxCondition, switchCondition],
106+
match: FastConditionMatch.every,
107+
),
112108
},
113109
),
114110
]));

0 commit comments

Comments
 (0)