@@ -12,6 +12,7 @@ import 'package:image_picker/image_picker.dart';
1212import 'package:path_provider/path_provider.dart' ;
1313import 'package:permission_handler/permission_handler.dart' ;
1414import 'package:piwigo_ng/api/api_client.dart' ;
15+ import 'package:piwigo_ng/api/api_interceptor.dart' ;
1516import 'package:piwigo_ng/api/authentication.dart' ;
1617import 'package:piwigo_ng/app.dart' ;
1718import 'package:piwigo_ng/components/dialogs/confirm_dialog.dart' ;
@@ -25,6 +26,7 @@ import 'package:shared_preferences/shared_preferences.dart';
2526import '../services/chunked_uploader.dart' ;
2627import '../services/notification_service.dart' ;
2728
29+ /// Handle Android API 33 permissions
2830Future <bool > askMediaPermission () async {
2931 bool storage = true ;
3032 bool videos = true ;
@@ -55,12 +57,13 @@ Future<bool> askMediaPermission() async {
5557 return false ;
5658}
5759
60+ /// Prepare and upload with [uploadChunk] a list of files.
5861Future <List <int >> uploadPhotos (
5962 List <XFile > photos,
6063 int albumId, {
6164 Map <String , dynamic > info = const {},
6265}) async {
63- // Check if Wifi is enabled and working
66+ // Check if Wifi is enabled and working before processing
6467 if (Preferences .getWifiUpload) {
6568 var connectivity = await Connectivity ().checkConnectivity ();
6669 if (connectivity != ConnectivityResult .wifi) {
@@ -75,6 +78,7 @@ Future<List<int>> uploadPhotos(
7578 }
7679 }
7780
81+ // Initialize variables
7882 List <int > result = [];
7983 List <UploadItem > items = [];
8084 FlutterSecureStorage storage = const FlutterSecureStorage ();
@@ -88,26 +92,31 @@ Future<List<int>> uploadPhotos(
8892
8993 // Creates Upload Item list for the upload notifier
9094 for (var photo in photos) {
91- File ? compressedFile;
92- if (Preferences .getRemoveMetadata) {
93- compressedFile = await compressFile (photo);
94- } else {
95- compressedFile = File (photo.path);
95+ File ? uploadFile;
96+
97+ // Compress file
98+ uploadFile = await compressFile (photo);
99+ if (uploadFile == null ) {
100+ uploadFile = File (photo.path);
96101 }
102+
97103 items.add (UploadItem (
98- file: compressedFile ,
104+ file: uploadFile ,
99105 albumId: albumId,
100106 ));
101107 }
102108
109+ // Add items to the queue
103110 uploadNotifier.addItems (items);
104111
112+ // Closes the Upload Configuration page and opens the Upload Status page
105113 App .navigatorKey.currentState? .popAndPushNamed (UploadStatusPage .routeName);
106114
115+ // Iterate on each item
107116 await Future .wait (List <Future <void >>.generate (items.length, (index) async {
108117 UploadItem item = items[index];
109118 try {
110- // Make Request
119+ // Upload image
111120 Response ? response = await uploadChunk (
112121 photo: item.file,
113122 category: albumId,
@@ -131,7 +140,7 @@ Future<List<int>> uploadPhotos(
131140 var data = json.decode (response.data);
132141 result.add (data['result' ]['id' ]);
133142
134- // Notify provider upload completed
143+ // Notify provider the upload has completed.
135144 uploadNotifier.itemUploadCompleted (item);
136145 if (Preferences .getDeleteAfterUpload) {
137146 // todo: delete real file path, not the cached one.
@@ -143,18 +152,22 @@ Future<List<int>> uploadPhotos(
143152 uploadNotifier.itemUploadCompleted (item, error: true );
144153 nbError++ ;
145154 } catch (e) {
155+ debugPrint ("$e " );
146156 if (e is Error ) {
147- debugPrint ("$e " );
148157 debugPrint ("${e .stackTrace }" );
149158 }
150- debugPrint ("$e " );
151159 uploadNotifier.itemUploadCompleted (item, error: true );
152160 nbError++ ;
153161 }
154162 }));
155163
164+ // Send notifications
156165 showUploadNotification (nbError, result.length);
166+
167+ // If no image was successfully uploaded, no call for "uploadCompleted"
157168 if (result.isEmpty) return [];
169+
170+ // Empty Piwigo lounge
158171 try {
159172 await uploadCompleted (result, albumId);
160173 if (await methodExist ('community.images.uploadCompleted' )) {
@@ -167,6 +180,7 @@ Future<List<int>> uploadPhotos(
167180 return result;
168181}
169182
183+ /// Upload images as chunks using [ChunkedUploader]
170184Future <Response ?> uploadChunk ({
171185 required File photo,
172186 required int category,
@@ -178,30 +192,40 @@ Future<Response?> uploadChunk({
178192 CancelToken ? cancelToken,
179193}) async {
180194 SharedPreferences prefs = await SharedPreferences .getInstance ();
195+
196+ // Request query parameters
181197 Map <String , String > queries = {
182198 'format' : 'json' ,
183199 'method' : 'pwg.images.uploadAsync' ,
184200 };
201+
202+ // Initialize fields
185203 Map <String , dynamic > fields = {
186204 'username' : username,
187205 'password' : password,
188206 'filename' : photo.path.split ('/' ).last,
189207 'category' : category,
190208 };
191209
210+ // Filter fields
192211 if (info['name' ] != '' && info['name' ] != null ) fields['name' ] = info['name' ];
193212 if (info['comment' ] != '' && info['comment' ] != null )
194213 fields['comment' ] = info['comment' ];
195214 if (info['tag_ids' ]? .isNotEmpty ?? false )
196215 fields['tag_ids' ] = info['tag_ids' ].join (',' );
197216 if (info['level' ] != - 1 ) fields['level' ] = info['level' ];
198217
199- ChunkedUploader chunkedUploader = ChunkedUploader (Dio (
218+ // Create dio client
219+ Dio dio = Dio (
200220 BaseOptions (
201221 baseUrl: url,
202222 ),
203- ));
223+ )..interceptors. add ( ApiInterceptor () );
204224
225+ // Initialize chunk uploader service
226+ ChunkedUploader chunkedUploader = ChunkedUploader (dio);
227+
228+ // Upload image as chunks
205229 return await chunkedUploader.upload (
206230 path: '/ws.php' ,
207231 filePath: photo.absolute.path,
@@ -217,25 +241,41 @@ Future<Response?> uploadChunk({
217241 );
218242}
219243
220- Future <File > compressFile (XFile file) async {
244+ /// Compress before upload, enabled with [Preferences.getCompressUpload] parameter.
245+ Future <File ?> compressFile (XFile file) async {
221246 try {
247+ SharedPreferences prefs = await SharedPreferences .getInstance ();
248+
249+ // Get original file path
222250 final filePath = file.path;
251+
252+ // Directory output
223253 var dir = await getTemporaryDirectory ();
254+
255+ // Extract file name and extension
224256 final String filename = filePath.split ('/' ).last;
257+
258+ // Output file path
225259 final outPath = "${dir .absolute .path }/$filename " ;
226260
261+ // Get compress parameters
262+ double quality = prefs.getDouble (Preferences .uploadQualityKey) ?? 1.0 ;
263+ bool removeMetadata = prefs.getBool (Preferences .removeMetadataKey) ?? false ;
264+
265+ // Compress with quality parameter and exif metadata
227266 var result = await FlutterImageCompress .compressAndGetFile (
228267 filePath,
229268 outPath,
230- quality: (Preferences .getUploadQuality * 100 ).round (),
231- keepExif: false ,
269+ quality: (quality * 100 ).round (),
270+ keepExif: removeMetadata ,
232271 );
272+
233273 debugPrint ("Upload Compress $result " );
234- if (result != null ) return result;
274+ return result;
235275 } catch (e) {
236276 debugPrint (e.toString ());
237277 }
238- return File (file.path) ;
278+ return null ;
239279}
240280
241281Future <bool > uploadCompleted (List <int > imageId, int categoryId) async {
0 commit comments