Skip to content

Commit df1cb99

Browse files
committed
UI improvements; Added attributes: previewImages, selector, fileType, fileExtension
Breaking change renamed maxImages to maxFiles
1 parent 8bd2125 commit df1cb99

File tree

2 files changed

+130
-95
lines changed

2 files changed

+130
-95
lines changed

lib/form_builder_file_picker.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export 'src/form_builder_form_picker.dart';
2+
export 'package:file_picker/file_picker.dart';

lib/src/form_builder_form_picker.dart

Lines changed: 129 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import 'dart:io';
44
import 'package:file_picker/file_picker.dart';
55
import 'package:flutter/material.dart';
66
import 'package:flutter_form_builder/flutter_form_builder.dart';
7-
import 'package:multi_image_picker/multi_image_picker.dart';
87
import 'package:permission_handler/permission_handler.dart';
98

109
class FormBuilderFilePicker extends StatefulWidget {
@@ -16,9 +15,12 @@ class FormBuilderFilePicker extends StatefulWidget {
1615
final ValueChanged onChanged;
1716
final ValueTransformer valueTransformer;
1817

19-
final int maxImages;
20-
final CupertinoOptions cupertinoOptions;
21-
final MaterialOptions materialOptions;
18+
final int maxFiles;
19+
final bool multiple;
20+
final bool previewImages;
21+
final Widget selector;
22+
final FileType fileType;
23+
final String fileExtension;
2224

2325
FormBuilderFilePicker({
2426
@required this.attribute,
@@ -28,10 +30,14 @@ class FormBuilderFilePicker extends StatefulWidget {
2830
this.decoration = const InputDecoration(),
2931
this.onChanged,
3032
this.valueTransformer,
31-
this.maxImages = 1,
32-
this.cupertinoOptions = const CupertinoOptions(),
33-
this.materialOptions = const MaterialOptions(),
34-
});
33+
this.maxFiles = 1,
34+
this.multiple = true,
35+
this.previewImages = true,
36+
this.selector = const Text('Select File(s)'),
37+
this.fileType = FileType.any,
38+
this.fileExtension,
39+
}) : assert(fileExtension != null || fileType != FileType.custom,
40+
"For custom fileType a fileExtension must be specified.");
3541

3642
@override
3743
_FormBuilderFilePickerState createState() => _FormBuilderFilePickerState();
@@ -41,13 +47,14 @@ class _FormBuilderFilePickerState extends State<FormBuilderFilePicker> {
4147
bool _readonly = false;
4248
final GlobalKey<FormFieldState> _fieldKey = GlobalKey<FormFieldState>();
4349
FormBuilderState _formState;
44-
Map<String, String> _images = {};
50+
Map<String, String> _files;
4551

4652
@override
4753
void initState() {
4854
_formState = FormBuilder.of(context);
4955
_formState?.registerFieldKey(widget.attribute, _fieldKey);
5056
_readonly = (_formState?.readOnly == true) ? true : widget.readonly;
57+
_files = widget.initialValue ?? {};
5158
super.initState();
5259
}
5360

@@ -57,7 +64,8 @@ class _FormBuilderFilePickerState extends State<FormBuilderFilePicker> {
5764
super.dispose();
5865
}
5966

60-
int get _remainingItemCount => widget.maxImages - _images.length;
67+
int get _remainingItemCount =>
68+
widget.maxFiles == null ? null : widget.maxFiles - _files.length;
6169

6270
@override
6371
Widget build(BuildContext context) {
@@ -86,79 +94,24 @@ class _FormBuilderFilePickerState extends State<FormBuilderFilePicker> {
8694
enabled: !_readonly,
8795
errorText: field.errorText,
8896
),
89-
child: Container(
90-
height: (_images.keys.length == 0) ? 50 : 200,
91-
child: Column(
92-
children: <Widget>[
93-
Row(
94-
mainAxisAlignment: MainAxisAlignment.spaceBetween,
95-
children: <Widget>[
96-
Text("${_images.length}/${widget.maxImages}"),
97-
FlatButton.icon(
98-
icon: Icon(Icons.add),
99-
label: Text("Select file(s)"),
100-
onPressed: (_readonly || _remainingItemCount <= 0)
101-
? null
102-
: () => pickFiles(field),
103-
),
104-
],
105-
),
106-
Expanded(
107-
child: SingleChildScrollView(
108-
child: Wrap(
109-
// scrollDirection: Axis.horizontal,
110-
children: List.generate(_images.keys.length, (index) {
111-
var key = _images.keys.toList(growable: false)[index];
112-
return Stack(
113-
alignment: Alignment.topRight,
114-
children: <Widget>[
115-
Container(
116-
height: MediaQuery.of(context).size.width / 4.5,
117-
width: MediaQuery.of(context).size.width / 4.5,
118-
margin: EdgeInsets.only(right: 2),
119-
child: (key.contains('.jpg') ||
120-
key.contains('.jpeg') ||
121-
key.contains('.png'))
122-
? Image.file(
123-
File(_images[key]),
124-
fit: BoxFit.cover,
125-
)
126-
: Container(
127-
child: Icon(
128-
Icons.insert_drive_file,
129-
color: Colors.white,
130-
size: 72,
131-
),
132-
color: Theme.of(context).primaryColor,
133-
),
134-
),
135-
if (!_readonly)
136-
InkWell(
137-
onTap: () => removeImageAtIndex(index, field),
138-
child: Container(
139-
margin: EdgeInsets.all(3),
140-
decoration: BoxDecoration(
141-
color: Colors.grey.withOpacity(.7),
142-
shape: BoxShape.circle,
143-
),
144-
alignment: Alignment.center,
145-
height: 22,
146-
width: 22,
147-
child: Icon(
148-
Icons.close,
149-
size: 18,
150-
color: Colors.white,
151-
),
152-
),
153-
),
154-
],
155-
);
156-
}),
157-
),
97+
child: Column(
98+
children: <Widget>[
99+
Row(
100+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
101+
children: <Widget>[
102+
if (widget.maxFiles != null)
103+
Text("${_files.length}/${widget.maxFiles}"),
104+
InkWell(
105+
child: widget.selector,
106+
onTap: (_readonly || _remainingItemCount <= 0)
107+
? null
108+
: () => pickFiles(field),
158109
),
159-
),
160-
],
161-
),
110+
],
111+
),
112+
SizedBox(height: 3),
113+
defaultFileViewer(_files, field),
114+
],
162115
),
163116
);
164117
},
@@ -179,7 +132,10 @@ class _FormBuilderFilePickerState extends State<FormBuilderFilePicker> {
179132
if (permissions[PermissionGroup.storage] != PermissionStatus.granted)
180133
throw new Exception("Permission not granted");
181134
}
182-
resultList = await FilePicker.getMultiFilePath();
135+
resultList = await FilePicker.getMultiFilePath(
136+
type: widget.fileType,
137+
fileExtension: widget.fileExtension,
138+
);
183139
} on Exception catch (e) {
184140
debugPrint(e.toString());
185141
}
@@ -188,21 +144,99 @@ class _FormBuilderFilePickerState extends State<FormBuilderFilePicker> {
188144
// setState to update our non-existent appearance.
189145
if (!mounted) return;
190146

191-
setState(() {
192-
if (resultList != null)
193-
_images.addAll(resultList); // .addAll(resultList);
194-
// if (error == null) _error = 'No Error Dectected';
195-
});
196-
field.didChange(_images);
197-
if (widget.onChanged != null) widget.onChanged(_images);
147+
if (resultList != null) {
148+
setState(() => _files.addAll(resultList));
149+
// TODO: Pick only remaining number
150+
field.didChange(_files);
151+
if (widget.onChanged != null) widget.onChanged(_files);
152+
}
198153
}
199154

200-
void removeImageAtIndex(int index, FormFieldState field) {
201-
var keysList = _images.keys.toList(growable: false);
155+
void removeFileAtIndex(int index, FormFieldState field) {
156+
var keysList = _files.keys.toList(growable: false);
202157
setState(() {
203-
_images.remove(keysList[index]);
158+
_files.remove(keysList[index]);
204159
});
205-
field.didChange(_images);
206-
if (widget.onChanged != null) widget.onChanged(_images);
160+
field.didChange(_files);
161+
if (widget.onChanged != null) widget.onChanged(_files);
162+
}
163+
164+
defaultFileViewer(Map<String, String> files, FormFieldState field) {
165+
return LayoutBuilder(
166+
builder: (context, constraints) {
167+
var count = 5;
168+
var spacing = 10;
169+
var itemSize = (constraints.biggest.width - (count * spacing)) / count;
170+
return Wrap(
171+
// scrollDirection: Axis.horizontal,
172+
alignment: WrapAlignment.start,
173+
runAlignment: WrapAlignment.start,
174+
runSpacing: 10,
175+
spacing: 10,
176+
children: List.generate(
177+
files.keys.length,
178+
(index) {
179+
var key = files.keys.toList(growable: false)[index];
180+
var fileExtension = key.split('.').last.toLowerCase();
181+
return Stack(
182+
alignment: Alignment.topRight,
183+
children: <Widget>[
184+
Container(
185+
height: itemSize,
186+
width: itemSize,
187+
alignment: Alignment.center,
188+
margin: EdgeInsets.only(right: 2),
189+
child: (['jpg', 'jpeg', 'png'].contains(fileExtension) &&
190+
widget.previewImages)
191+
? Image.file(
192+
File(files[key]),
193+
fit: BoxFit.cover,
194+
)
195+
: Container(
196+
child: Icon(
197+
getIconData(fileExtension),
198+
color: Colors.white,
199+
size: 72,
200+
),
201+
color: Theme.of(context).primaryColor,
202+
),
203+
),
204+
if (!_readonly)
205+
InkWell(
206+
onTap: () => removeFileAtIndex(index, field),
207+
child: Container(
208+
margin: EdgeInsets.all(3),
209+
decoration: BoxDecoration(
210+
color: Colors.grey.withOpacity(.7),
211+
shape: BoxShape.circle,
212+
),
213+
alignment: Alignment.center,
214+
height: 22,
215+
width: 22,
216+
child: Icon(
217+
Icons.close,
218+
size: 18,
219+
color: Colors.white,
220+
),
221+
),
222+
),
223+
],
224+
);
225+
},
226+
),
227+
);
228+
},
229+
);
230+
}
231+
232+
IconData getIconData(String fileExtension) {
233+
switch (fileExtension) {
234+
case 'jpg':
235+
case 'jpeg':
236+
case 'png':
237+
return Icons.image;
238+
default:
239+
return Icons.insert_drive_file;
240+
}
207241
}
208242
}

0 commit comments

Comments
 (0)