@@ -8,6 +8,14 @@ import 'package:image_picker/image_picker.dart';
8
8
import 'image_source_sheet.dart' ;
9
9
10
10
/// Field for picking image(s) from Gallery or Camera.
11
+ ///
12
+ /// Field value is a list of objects.
13
+ ///
14
+ /// the widget can internally handle displaying objects of type [XFile] ,[Uint8List] ,[String] (for an image url),[ImageProvider] (for any flutter image), [Widget] (for any widget)
15
+ /// and appends [XFile] to the list for picked images.
16
+ ///
17
+ /// if you want to use a different object (e.g. a class from the backend that has imageId and imageUrl)
18
+ /// you need to implement [displayCustomType]
11
19
class FormBuilderImagePicker extends FormBuilderField <List <dynamic >> {
12
20
//TODO: Add documentation
13
21
final double previewWidth;
@@ -34,6 +42,13 @@ class FormBuilderImagePicker extends FormBuilderField<List<dynamic>> {
34
42
/// supported on the device. Defaults to `CameraDevice.rear` . See [ImagePicker] .
35
43
final CameraDevice preferredCameraDevice;
36
44
45
+ /// use this to get an image from a custom object to either [Uint8List] or [XFile] or [String] (url) or [ImageProvider]
46
+ ///
47
+ /// ```dart
48
+ /// (obj) => obj is MyApiFileClass ? obj.imageUrl : obj;
49
+ /// ```
50
+ final dynamic Function (dynamic obj)? displayCustomType;
51
+
37
52
final void Function (Image )? onImage;
38
53
final int ? maxImages;
39
54
final Widget cameraIcon;
@@ -42,7 +57,9 @@ class FormBuilderImagePicker extends FormBuilderField<List<dynamic>> {
42
57
final Widget galleryLabel;
43
58
final EdgeInsets bottomSheetPadding;
44
59
45
- /// Creates field for picking image(s) from Gallery or Camera.
60
+ /// fit for each image
61
+ final BoxFit fit;
62
+
46
63
FormBuilderImagePicker ({
47
64
Key ? key,
48
65
//From Super
@@ -58,6 +75,8 @@ class FormBuilderImagePicker extends FormBuilderField<List<dynamic>> {
58
75
VoidCallback ? onReset,
59
76
FocusNode ? focusNode,
60
77
WidgetBuilder ? loadingWidget,
78
+ this .fit = BoxFit .cover,
79
+ this .displayCustomType,
61
80
this .previewWidth = 130 ,
62
81
this .previewHeight = 130 ,
63
82
this .previewMargin,
@@ -93,64 +112,87 @@ class FormBuilderImagePicker extends FormBuilderField<List<dynamic>> {
93
112
final theme = Theme .of (state.context);
94
113
final disabledColor = theme.disabledColor;
95
114
final primaryColor = theme.primaryColor;
115
+ final value = state.effectiveValue;
116
+ final canUpload = state.enabled && ! state.hasMaxImages;
96
117
97
118
return InputDecorator (
98
119
decoration: state.decoration,
99
120
child: Container (
100
121
height: previewHeight,
101
- child: ListView (
122
+ child: ListView . builder (
102
123
scrollDirection: Axis .horizontal,
103
- children: [
104
- if (field.value != null )
105
- ...field.value! .map <Widget >((dynamic item) {
106
- assert (item is XFile ||
107
- item is String ||
108
- item is Uint8List );
109
- return Stack (
110
- alignment: Alignment .topRight,
111
- children: < Widget > [
112
- Container (
113
- width: previewWidth,
114
- height: previewHeight,
115
- margin: previewMargin,
116
- child: item is Uint8List
117
- ? Image .memory (item, fit: BoxFit .cover)
118
- : item is String
119
- ? Image .network (item, fit: BoxFit .cover)
120
- : XFileImage (
121
- file: item,
122
- fit: BoxFit .cover,
123
- loadingWidget: loadingWidget,
124
- ),
125
- ),
126
- if (state.enabled)
127
- InkWell (
128
- onTap: () {
129
- state.requestFocus ();
130
- field.didChange (
131
- < dynamic > [...? field.value]..remove (item));
132
- },
133
- child: Container (
134
- margin: const EdgeInsets .all (3 ),
135
- decoration: BoxDecoration (
136
- color: Colors .grey.withOpacity (.7 ),
137
- shape: BoxShape .circle,
138
- ),
139
- alignment: Alignment .center,
140
- height: 22 ,
141
- width: 22 ,
142
- child: const Icon (
143
- Icons .close,
144
- size: 18 ,
145
- color: Colors .white,
146
- ),
124
+ itemCount: value.length + (canUpload ? 1 : 0 ),
125
+ itemBuilder: (context, index) {
126
+ if (index < value.length) {
127
+ final item = value[index];
128
+ bool checkIfItemIsCustomType (dynamic e) => ! (e is XFile ||
129
+ e is String ||
130
+ e is Uint8List ||
131
+ e is ImageProvider ||
132
+ e is Widget );
133
+
134
+ final itemCustomType = checkIfItemIsCustomType (item);
135
+ var displayItem = item;
136
+ if (itemCustomType && displayCustomType != null ) {
137
+ displayItem = displayCustomType (item);
138
+ }
139
+ assert (
140
+ ! checkIfItemIsCustomType (displayItem),
141
+ 'Display item must be of type [Uint8List], [XFile], [String] (url), [ImageProvider] or [Widget]. '
142
+ 'Consider using displayCustomType to handle the type: ${displayItem .runtimeType }' ,
143
+ );
144
+ return Stack (
145
+ alignment: Alignment .topRight,
146
+ children: < Widget > [
147
+ Container (
148
+ width: previewWidth,
149
+ height: previewHeight,
150
+ margin: previewMargin,
151
+ child: displayItem is Widget
152
+ ? displayItem
153
+ : displayItem is ImageProvider
154
+ ? Image (image: displayItem, fit: fit)
155
+ : displayItem is Uint8List
156
+ ? Image .memory (displayItem, fit: fit)
157
+ : displayItem is String
158
+ ? Image .network (
159
+ displayItem,
160
+ fit: fit,
161
+ )
162
+ : XFileImage (
163
+ file: displayItem,
164
+ fit: fit,
165
+ loadingWidget: loadingWidget,
166
+ ),
167
+ ),
168
+ if (state.enabled)
169
+ InkWell (
170
+ onTap: () {
171
+ state.requestFocus ();
172
+ field.didChange (
173
+ value.toList ()..removeAt (index),
174
+ );
175
+ },
176
+ child: Container (
177
+ margin: const EdgeInsets .all (3 ),
178
+ decoration: BoxDecoration (
179
+ color: Colors .grey.withOpacity (.7 ),
180
+ shape: BoxShape .circle,
181
+ ),
182
+ alignment: Alignment .center,
183
+ height: 22 ,
184
+ width: 22 ,
185
+ child: const Icon (
186
+ Icons .close,
187
+ size: 18 ,
188
+ color: Colors .white,
147
189
),
148
190
),
149
- ] ,
150
- );
151
- }),
152
- if (state.enabled && ! state.hasMaxImages)
153
- GestureDetector (
191
+ ) ,
192
+ ],
193
+ );
194
+ } else {
195
+ return GestureDetector (
154
196
child: placeholderImage != null
155
197
? Image (
156
198
width: previewWidth,
@@ -186,16 +228,16 @@ class FormBuilderImagePicker extends FormBuilderField<List<dynamic>> {
186
228
galleryLabel: galleryLabel,
187
229
onImageSelected: (image) {
188
230
state.requestFocus ();
189
- field.didChange (
190
- < dynamic > [...? field.value, image]);
231
+ field.didChange (value.toList ()..add (image));
191
232
Navigator .pop (state.context);
192
233
},
193
234
);
194
235
},
195
236
);
196
237
},
197
- ),
198
- ],
238
+ );
239
+ }
240
+ },
199
241
),
200
242
),
201
243
);
@@ -208,10 +250,12 @@ class FormBuilderImagePicker extends FormBuilderField<List<dynamic>> {
208
250
209
251
class _FormBuilderImagePickerState
210
252
extends FormBuilderFieldState <FormBuilderImagePicker , List <dynamic >> {
211
- bool get hasMaxImages =>
212
- widget.maxImages != null &&
213
- value != null &&
214
- value! .length >= widget.maxImages! ;
253
+ List <dynamic > get effectiveValue =>
254
+ value? .where ((element) => element != null ).toList () ?? [];
255
+ bool get hasMaxImages {
256
+ final ev = effectiveValue;
257
+ return widget.maxImages != null && ev.length >= widget.maxImages! ;
258
+ }
215
259
}
216
260
217
261
class XFileImage extends StatefulWidget {
0 commit comments