@@ -4,7 +4,6 @@ import 'dart:io';
4
4
import 'package:file_picker/file_picker.dart' ;
5
5
import 'package:flutter/material.dart' ;
6
6
import 'package:flutter_form_builder/flutter_form_builder.dart' ;
7
- import 'package:multi_image_picker/multi_image_picker.dart' ;
8
7
import 'package:permission_handler/permission_handler.dart' ;
9
8
10
9
class FormBuilderFilePicker extends StatefulWidget {
@@ -16,9 +15,12 @@ class FormBuilderFilePicker extends StatefulWidget {
16
15
final ValueChanged onChanged;
17
16
final ValueTransformer valueTransformer;
18
17
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;
22
24
23
25
FormBuilderFilePicker ({
24
26
@required this .attribute,
@@ -28,10 +30,14 @@ class FormBuilderFilePicker extends StatefulWidget {
28
30
this .decoration = const InputDecoration (),
29
31
this .onChanged,
30
32
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." );
35
41
36
42
@override
37
43
_FormBuilderFilePickerState createState () => _FormBuilderFilePickerState ();
@@ -41,13 +47,14 @@ class _FormBuilderFilePickerState extends State<FormBuilderFilePicker> {
41
47
bool _readonly = false ;
42
48
final GlobalKey <FormFieldState > _fieldKey = GlobalKey <FormFieldState >();
43
49
FormBuilderState _formState;
44
- Map <String , String > _images = {} ;
50
+ Map <String , String > _files ;
45
51
46
52
@override
47
53
void initState () {
48
54
_formState = FormBuilder .of (context);
49
55
_formState? .registerFieldKey (widget.attribute, _fieldKey);
50
56
_readonly = (_formState? .readOnly == true ) ? true : widget.readonly;
57
+ _files = widget.initialValue ?? {};
51
58
super .initState ();
52
59
}
53
60
@@ -57,7 +64,8 @@ class _FormBuilderFilePickerState extends State<FormBuilderFilePicker> {
57
64
super .dispose ();
58
65
}
59
66
60
- int get _remainingItemCount => widget.maxImages - _images.length;
67
+ int get _remainingItemCount =>
68
+ widget.maxFiles == null ? null : widget.maxFiles - _files.length;
61
69
62
70
@override
63
71
Widget build (BuildContext context) {
@@ -86,79 +94,24 @@ class _FormBuilderFilePickerState extends State<FormBuilderFilePicker> {
86
94
enabled: ! _readonly,
87
95
errorText: field.errorText,
88
96
),
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),
158
109
),
159
- ),
160
- ],
161
- ),
110
+ ],
111
+ ),
112
+ SizedBox (height: 3 ),
113
+ defaultFileViewer (_files, field),
114
+ ],
162
115
),
163
116
);
164
117
},
@@ -179,7 +132,10 @@ class _FormBuilderFilePickerState extends State<FormBuilderFilePicker> {
179
132
if (permissions[PermissionGroup .storage] != PermissionStatus .granted)
180
133
throw new Exception ("Permission not granted" );
181
134
}
182
- resultList = await FilePicker .getMultiFilePath ();
135
+ resultList = await FilePicker .getMultiFilePath (
136
+ type: widget.fileType,
137
+ fileExtension: widget.fileExtension,
138
+ );
183
139
} on Exception catch (e) {
184
140
debugPrint (e.toString ());
185
141
}
@@ -188,21 +144,99 @@ class _FormBuilderFilePickerState extends State<FormBuilderFilePicker> {
188
144
// setState to update our non-existent appearance.
189
145
if (! mounted) return ;
190
146
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
+ }
198
153
}
199
154
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 );
202
157
setState (() {
203
- _images .remove (keysList[index]);
158
+ _files .remove (keysList[index]);
204
159
});
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
+ }
207
241
}
208
242
}
0 commit comments