Skip to content

Commit 9c76208

Browse files
feat: add cupertino checkbox field
1 parent 998a947 commit 9c76208

File tree

5 files changed

+305
-0
lines changed

5 files changed

+305
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ ___
3030

3131
The currently supported fields include:
3232

33+
- `FormBuilderCupertinoCheckbox` - Single checkbox field
3334
- `FormBuilderCupertinoSegmentedControl` - Segmented control using `CupertinoSegmentedControl`
3435
- `FormBuilderCupertinoSlidingSegmentedControl` - Segmented control bar using `CupertinoSlidingSegmentedControl`
3536
- `FormBuilderCupertinoSlider` - For selection of a numerical value on a slider

example/lib/main.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ class _MyHomePageState extends State<MyHomePage> {
4141
key: _formKey,
4242
child: Column(
4343
children: [
44+
FormBuilderCupertinoCheckbox(
45+
name: 'checkbox',
46+
prefix: const Text('Select/Unselect'),
47+
),
48+
const SizedBox(height: 16),
4449
FormBuilderCupertinoSegmentedControl<int>(
4550
name: 'segmented_control',
4651
shouldExpandedField: true,

lib/form_builder_cupertino_fields.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
library form_builder_cupertino_fields;
22

3+
export 'src/fields/form_builder_cupertino_checkbox.dart';
34
export 'src/fields/form_builder_cupertino_segmented_control.dart';
45
export 'src/fields/form_builder_cupertino_slider.dart';
56
export 'src/fields/form_builder_cupertino_sliding_segmented_control.dart';
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import 'package:flutter/cupertino.dart';
2+
import 'package:flutter_form_builder/flutter_form_builder.dart';
3+
4+
/// On/Off Cupertino switch field
5+
class FormBuilderCupertinoCheckbox extends FormBuilderField<bool> {
6+
/// The color to use when this radio button is selected.
7+
///
8+
/// Defaults to [CupertinoColors.activeBlue].
9+
final Color? activeColor;
10+
11+
/// Defines whether the field input expands to fill the entire width
12+
/// of the row field.
13+
///
14+
/// By default `false`
15+
final bool shouldExpandedField;
16+
17+
/// A widget that is displayed at the start of the row.
18+
///
19+
/// The [prefix] parameter is displayed at the start of the row. Standard iOS
20+
/// guidelines encourage passing a [Text] widget to [prefix] to detail the
21+
/// nature of the row's [child] widget. If null, the [child] widget will take
22+
/// up all horizontal space in the row.
23+
final Widget? prefix;
24+
25+
/// Content padding for the row.
26+
///
27+
/// Defaults to the standard iOS padding for form rows. If no edge insets are
28+
/// intended, explicitly pass [EdgeInsets.zero] to [contentPadding].
29+
final EdgeInsetsGeometry? contentPadding;
30+
31+
/// A widget that is displayed underneath the [prefix] and [child] widgets.
32+
///
33+
/// The [helper] appears in primary label coloring, and is meant to inform the
34+
/// user about interaction with the child widget. The row becomes taller in
35+
/// order to display the [helper] widget underneath [prefix] and [child]. If
36+
/// null, the row is shorter.
37+
final Widget? helper;
38+
39+
/// A builder widget that is displayed underneath the [prefix] and [child] widgets.
40+
///
41+
/// The [error] widget is primarily used to inform users of input errors. When
42+
/// a [Text] is given to [error], it will be shown in
43+
/// [CupertinoColors.destructiveRed] coloring and medium-weighted font. The
44+
/// row becomes taller in order to display the [helper] widget underneath
45+
/// [prefix] and [child]. If null, the row is shorter.
46+
final Widget? Function(String error)? errorBuilder;
47+
48+
/// The color to use when this radio button is not selected.
49+
///
50+
/// Defaults to [CupertinoColors.white].
51+
final Color? inactiveColor;
52+
53+
/// The color for the radio's border when it has the input focus.
54+
///
55+
/// If null, then a paler form of the [activeColor] will be used.
56+
final Color? focusColor;
57+
58+
/// The color to use for the check icon when this checkbox is checked.
59+
///
60+
/// If null, then the value of [CupertinoColors.white] is used.
61+
final Color? checkColor;
62+
63+
/// If true, the checkbox's [value] can be true, false, or null.
64+
///
65+
/// [CupertinoCheckbox] displays a dash when its value is null.
66+
///
67+
/// When a tri-state checkbox ([tristate] is true) is tapped, its [onChanged]
68+
/// callback will be applied to true if the current value is false, to null if
69+
/// value is true, and to false if value is null (i.e. it cycles through false
70+
/// => true => null => false when tapped).
71+
///
72+
/// If tristate is false (the default), [value] must not be null, and
73+
/// [onChanged] will only toggle between true and false.
74+
final bool tristate;
75+
76+
/// The color and width of the checkbox's border.
77+
///
78+
/// If this property is null, then the side defaults to a one pixel wide
79+
/// black, solid border.
80+
final BorderSide? side;
81+
82+
/// The shape of the checkbox.
83+
///
84+
/// If this property is null then the shape defaults to a
85+
/// [RoundedRectangleBorder] with a circular corner radius of 4.0.
86+
final OutlinedBorder? shape;
87+
88+
/// Creates On/Off Cupertino switch field
89+
FormBuilderCupertinoCheckbox({
90+
super.key,
91+
required super.name,
92+
super.validator,
93+
super.initialValue,
94+
super.onChanged,
95+
super.valueTransformer,
96+
super.enabled,
97+
super.onSaved,
98+
super.autovalidateMode,
99+
super.onReset,
100+
super.focusNode,
101+
super.restorationId,
102+
this.activeColor,
103+
this.shouldExpandedField = false,
104+
this.errorBuilder,
105+
this.helper,
106+
this.contentPadding,
107+
this.prefix,
108+
this.focusColor,
109+
this.inactiveColor,
110+
this.checkColor,
111+
this.shape,
112+
this.side,
113+
this.tristate = false,
114+
}) : super(
115+
builder: (FormFieldState<bool?> field) {
116+
final state = field as _FormBuilderCupertinoCheckboxState;
117+
118+
final fieldWidget = CupertinoCheckbox(
119+
focusColor: focusColor,
120+
focusNode: state.effectiveFocusNode,
121+
inactiveColor: inactiveColor,
122+
value: state.value ?? false,
123+
checkColor: checkColor,
124+
shape: shape,
125+
side: side,
126+
tristate: tristate,
127+
onChanged: state.enabled
128+
? (value) {
129+
field.didChange(value);
130+
}
131+
: null,
132+
activeColor: activeColor,
133+
);
134+
return CupertinoFormRow(
135+
error: state.hasError
136+
? errorBuilder != null
137+
? errorBuilder(state.errorText ?? '')
138+
: Text(state.errorText ?? '')
139+
: null,
140+
helper: helper,
141+
padding: contentPadding,
142+
prefix: prefix,
143+
child: shouldExpandedField
144+
? SizedBox(width: double.infinity, child: fieldWidget)
145+
: fieldWidget,
146+
);
147+
},
148+
);
149+
150+
@override
151+
FormBuilderFieldState<FormBuilderCupertinoCheckbox, bool> createState() =>
152+
_FormBuilderCupertinoCheckboxState();
153+
}
154+
155+
class _FormBuilderCupertinoCheckboxState
156+
extends FormBuilderFieldState<FormBuilderCupertinoCheckbox, bool> {}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import 'package:flutter/cupertino.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
import 'package:flutter_form_builder/flutter_form_builder.dart';
4+
import 'package:form_builder_cupertino_fields/form_builder_cupertino_fields.dart';
5+
import '../form_builder_tester.dart';
6+
7+
void main() {
8+
group('initialValue -', () {
9+
testWidgets('should initial value when set initialValue',
10+
(WidgetTester tester) async {
11+
const widgetName = 'sc1';
12+
final switchKey = GlobalKey<FormBuilderFieldState>();
13+
final testWidget = FormBuilderCupertinoCheckbox(
14+
name: widgetName,
15+
initialValue: true,
16+
key: switchKey,
17+
);
18+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
19+
20+
expect(switchKey.currentState!.value, isTrue);
21+
});
22+
});
23+
24+
group('errors -', () {
25+
testWidgets('should error text when value is false',
26+
(WidgetTester tester) async {
27+
const widgetName = 'sc1';
28+
const errorTextField = 'error text field';
29+
final switchKey = GlobalKey<FormBuilderFieldState>();
30+
final testWidget = FormBuilderCupertinoCheckbox(
31+
name: widgetName,
32+
key: switchKey,
33+
validator: (value) => value == null || !value ? errorTextField : null,
34+
);
35+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
36+
37+
switchKey.currentState!.validate();
38+
await tester.pumpAndSettle();
39+
40+
expect(find.text(errorTextField), findsOneWidget);
41+
42+
await tester.tap(find.byType(CupertinoCheckbox));
43+
await tester.pumpAndSettle();
44+
45+
switchKey.currentState!.validate();
46+
await tester.pumpAndSettle();
47+
48+
expect(find.text(errorTextField), findsNothing);
49+
});
50+
testWidgets('should custom error text when invalidate field',
51+
(WidgetTester tester) async {
52+
const widgetName = 'sc1';
53+
const errorTextField = 'error text field';
54+
final switchKey = GlobalKey<FormBuilderFieldState>();
55+
final testWidget = FormBuilderCupertinoCheckbox(
56+
name: widgetName,
57+
key: switchKey,
58+
);
59+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
60+
61+
switchKey.currentState!.invalidate(errorTextField);
62+
await tester.pumpAndSettle();
63+
64+
expect(find.text(errorTextField), findsOneWidget);
65+
});
66+
testWidgets('should not show error text when value is true',
67+
(WidgetTester tester) async {
68+
const widgetName = 'sc1';
69+
final switchKey = GlobalKey<FormBuilderFieldState>();
70+
const errorTextField = 'error text field';
71+
final testWidget = FormBuilderCupertinoCheckbox(
72+
name: widgetName,
73+
key: switchKey,
74+
validator: (value) => value == null || !value ? errorTextField : null,
75+
);
76+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
77+
78+
await tester.tap(find.byType(CupertinoCheckbox));
79+
await tester.pumpAndSettle();
80+
81+
switchKey.currentState!.validate();
82+
await tester.pumpAndSettle();
83+
84+
expect(find.text(errorTextField), findsNothing);
85+
});
86+
});
87+
88+
group('reset -', () {
89+
testWidgets('Should reset to null when call reset', (tester) async {
90+
const widgetName = 'sc1';
91+
final switchKey = GlobalKey<FormBuilderFieldState>();
92+
final testWidget = FormBuilderCupertinoCheckbox(
93+
name: widgetName,
94+
key: switchKey,
95+
);
96+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
97+
98+
switchKey.currentState?.setValue(true);
99+
await tester.pumpAndSettle();
100+
switchKey.currentState?.reset();
101+
102+
expect(switchKey.currentState?.value, null);
103+
});
104+
testWidgets('Should reset to initial when call reset', (tester) async {
105+
const widgetName = 'sc1';
106+
final switchKey = GlobalKey<FormBuilderFieldState>();
107+
const initialValue = true;
108+
final testWidget = FormBuilderCupertinoCheckbox(
109+
name: widgetName,
110+
key: switchKey,
111+
initialValue: initialValue,
112+
);
113+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
114+
115+
switchKey.currentState?.setValue(false);
116+
await tester.pumpAndSettle();
117+
switchKey.currentState?.reset();
118+
119+
expect(switchKey.currentState?.value, equals(initialValue));
120+
});
121+
testWidgets(
122+
'Should reset custom error when invalidate field and then reset',
123+
(tester) async {
124+
const widgetName = 'sc1';
125+
final switchKey = GlobalKey<FormBuilderFieldState>();
126+
const errorTextField = 'error text field';
127+
final testWidget = FormBuilderCupertinoCheckbox(
128+
name: widgetName,
129+
key: switchKey,
130+
);
131+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
132+
133+
switchKey.currentState?.invalidate(errorTextField);
134+
await tester.pumpAndSettle();
135+
136+
// Reset custom error
137+
switchKey.currentState?.reset();
138+
await tester.pumpAndSettle();
139+
expect(find.text(errorTextField), findsNothing);
140+
});
141+
});
142+
}

0 commit comments

Comments
 (0)