Skip to content

Commit bd2b7c3

Browse files
committed
added changelog
1 parent f425cf5 commit bd2b7c3

File tree

4 files changed

+128
-65
lines changed

4 files changed

+128
-65
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
## [2.0.0] - 28-Oct-2021
22
* Use XFile instead of the old picker
33
* Proper null safety
4-
4+
* Added `displayCustomType` to accept any type for the values
5+
* Used ListView.builder for lazy loading items
56
## [1.0.0-nullsafety.0] - 03-Apr-2021
67
* Started working on null-safety
78

example/lib/main.dart

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_form_builder/flutter_form_builder.dart';
3+
34
import 'package:form_builder_image_picker/form_builder_image_picker.dart';
45

56
void main() {
@@ -20,6 +21,15 @@ class MyApp extends StatelessWidget {
2021
}
2122
}
2223

24+
class ApiImage {
25+
final String imageUrl;
26+
final String id;
27+
ApiImage({
28+
required this.imageUrl,
29+
required this.id,
30+
});
31+
}
32+
2333
class MyHomePage extends StatelessWidget {
2434
final _formKey = GlobalKey<FormBuilderState>();
2535

@@ -39,18 +49,26 @@ class MyHomePage extends StatelessWidget {
3949
children: <Widget>[
4050
FormBuilderImagePicker(
4151
name: 'photos',
52+
displayCustomType: (obj) =>
53+
obj is ApiImage ? obj.imageUrl : obj,
4254
decoration: const InputDecoration(labelText: 'Pick Photos'),
43-
maxImages: 3,
55+
maxImages: 5,
4456
initialValue: [
45-
'https://images.pexels.com/photos/7078045/pexels-photo-7078045.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260'
57+
'https://images.pexels.com/photos/7078045/pexels-photo-7078045.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260',
58+
Text('this is an image\nas a widget !'),
59+
ApiImage(
60+
id: 'whatever',
61+
imageUrl:
62+
'https://images.pexels.com/photos/8311418/pexels-photo-8311418.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260',
63+
),
4664
],
4765
),
4866
const SizedBox(height: 15),
4967
ElevatedButton(
5068
child: Text('Submit'),
5169
onPressed: () {
52-
if (_formKey.currentState.saveAndValidate()) {
53-
print(_formKey.currentState.value);
70+
if (_formKey.currentState?.saveAndValidate() == true) {
71+
print(_formKey.currentState!.value);
5472
}
5573
},
5674
),

example/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
55
version: 1.0.0+1
66

77
environment:
8-
sdk: ">=2.7.0 <3.0.0"
8+
sdk: ">=2.12.0 <3.0.0"
99

1010
dependencies:
1111
flutter:

lib/src/form_builder_image_picker.dart

Lines changed: 103 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ import 'package:image_picker/image_picker.dart';
88
import 'image_source_sheet.dart';
99

1010
/// 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]
1119
class FormBuilderImagePicker extends FormBuilderField<List<dynamic>> {
1220
//TODO: Add documentation
1321
final double previewWidth;
@@ -34,6 +42,13 @@ class FormBuilderImagePicker extends FormBuilderField<List<dynamic>> {
3442
/// supported on the device. Defaults to `CameraDevice.rear`. See [ImagePicker].
3543
final CameraDevice preferredCameraDevice;
3644

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+
3752
final void Function(Image)? onImage;
3853
final int? maxImages;
3954
final Widget cameraIcon;
@@ -42,7 +57,9 @@ class FormBuilderImagePicker extends FormBuilderField<List<dynamic>> {
4257
final Widget galleryLabel;
4358
final EdgeInsets bottomSheetPadding;
4459

45-
/// Creates field for picking image(s) from Gallery or Camera.
60+
/// fit for each image
61+
final BoxFit fit;
62+
4663
FormBuilderImagePicker({
4764
Key? key,
4865
//From Super
@@ -58,6 +75,8 @@ class FormBuilderImagePicker extends FormBuilderField<List<dynamic>> {
5875
VoidCallback? onReset,
5976
FocusNode? focusNode,
6077
WidgetBuilder? loadingWidget,
78+
this.fit = BoxFit.cover,
79+
this.displayCustomType,
6180
this.previewWidth = 130,
6281
this.previewHeight = 130,
6382
this.previewMargin,
@@ -93,64 +112,87 @@ class FormBuilderImagePicker extends FormBuilderField<List<dynamic>> {
93112
final theme = Theme.of(state.context);
94113
final disabledColor = theme.disabledColor;
95114
final primaryColor = theme.primaryColor;
115+
final value = state.effectiveValue;
116+
final canUpload = state.enabled && !state.hasMaxImages;
96117

97118
return InputDecorator(
98119
decoration: state.decoration,
99120
child: Container(
100121
height: previewHeight,
101-
child: ListView(
122+
child: ListView.builder(
102123
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,
147189
),
148190
),
149-
],
150-
);
151-
}),
152-
if (state.enabled && !state.hasMaxImages)
153-
GestureDetector(
191+
),
192+
],
193+
);
194+
} else {
195+
return GestureDetector(
154196
child: placeholderImage != null
155197
? Image(
156198
width: previewWidth,
@@ -186,16 +228,16 @@ class FormBuilderImagePicker extends FormBuilderField<List<dynamic>> {
186228
galleryLabel: galleryLabel,
187229
onImageSelected: (image) {
188230
state.requestFocus();
189-
field.didChange(
190-
<dynamic>[...?field.value, image]);
231+
field.didChange(value.toList()..add(image));
191232
Navigator.pop(state.context);
192233
},
193234
);
194235
},
195236
);
196237
},
197-
),
198-
],
238+
);
239+
}
240+
},
199241
),
200242
),
201243
);
@@ -208,10 +250,12 @@ class FormBuilderImagePicker extends FormBuilderField<List<dynamic>> {
208250

209251
class _FormBuilderImagePickerState
210252
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+
}
215259
}
216260

217261
class XFileImage extends StatefulWidget {

0 commit comments

Comments
 (0)