Skip to content

Commit b6d9171

Browse files
committed
Auto upload permissions and image exist
1 parent eb58a97 commit b6d9171

8 files changed

Lines changed: 198 additions & 77 deletions

File tree

android/app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
package="com.remi.piwigo_ng">
33

44
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
5-
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
6-
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
7-
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
8-
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
5+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
6+
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" android:minSdkVersion="33"/>
7+
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" android:minSdkVersion="33"/>
8+
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" android:minSdkVersion="33"/>
99
<uses-permission android:name="android.permission.CAMERA" />
1010
<uses-permission android:name="android.permission.INTERNET"/>
1111
<uses-permission android:name="android.permission.VIBRATE" />

lib/api/authentication.dart

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:dio/dio.dart';
44
import 'package:flutter/material.dart';
55
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
66
import 'package:piwigo_ng/api/api_error.dart';
7+
import 'package:piwigo_ng/api/upload.dart';
78
import 'package:piwigo_ng/models/info_model.dart';
89
import 'package:piwigo_ng/models/status_model.dart';
910
import 'package:piwigo_ng/services/preferences_service.dart';
@@ -54,6 +55,7 @@ Future<ApiResult<bool>> loginUser(
5455
data: true,
5556
);
5657
}
58+
askMediaPermission();
5759
return ApiResult<bool>(
5860
data: false,
5961
error: ApiErrors.wrongServerUrl,
@@ -86,16 +88,14 @@ Future<ApiResult<bool>> loginUser(
8688
}
8789
ApiResult<StatusModel> status = await sessionStatus();
8890
if (status.hasData) {
89-
Preferences.saveId(status.data!, username: username, password: password);
91+
Preferences.saveId(status.data!,
92+
username: username, password: password);
9093
}
94+
askMediaPermission();
9195
return ApiResult<bool>(
9296
data: true,
9397
);
9498
}
95-
return ApiResult<bool>(
96-
data: false,
97-
error: ApiErrors.wrongLoginId,
98-
);
9999
} on DioError catch (e) {
100100
debugPrint(e.message);
101101
} catch (e) {
@@ -108,7 +108,10 @@ Future<ApiResult<bool>> loginUser(
108108
}
109109

110110
Future<ApiResult<StatusModel>> sessionStatus() async {
111-
Map<String, String> queries = {'format': 'json', 'method': 'pwg.session.getStatus'};
111+
Map<String, String> queries = {
112+
'format': 'json',
113+
'method': 'pwg.session.getStatus'
114+
};
112115

113116
try {
114117
Response response = await ApiClient.get(queryParameters: queries);
@@ -133,7 +136,10 @@ Future<ApiResult<StatusModel>> sessionStatus() async {
133136
}
134137

135138
Future<String?> communityStatus() async {
136-
Map<String, String> queries = {'format': 'json', 'method': 'community.session.getStatus'};
139+
Map<String, String> queries = {
140+
'format': 'json',
141+
'method': 'community.session.getStatus'
142+
};
137143

138144
try {
139145
Response response = await ApiClient.get(queryParameters: queries);
@@ -171,12 +177,16 @@ Future<ApiResult<InfoModel>> getInfo() async {
171177
}
172178

173179
Future<ApiResult<List<String>>> getMethods() async {
174-
Map<String, String> queries = {'format': 'json', 'method': 'reflection.getMethodList'};
180+
Map<String, String> queries = {
181+
'format': 'json',
182+
'method': 'reflection.getMethodList'
183+
};
175184

176185
try {
177186
Response response = await ApiClient.get(queryParameters: queries);
178187
Map<String, dynamic> data = json.decode(response.data);
179-
final List<String> methods = data['result']['methods'].map<String>((e) => e.toString()).toList();
188+
final List<String> methods =
189+
data['result']['methods'].map<String>((e) => e.toString()).toList();
180190
return ApiResult<List<String>>(data: methods);
181191
} on DioError catch (e) {
182192
debugPrint(e.message);

lib/api/images.dart

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:convert';
2+
import 'dart:io';
23

34
import 'package:dio/dio.dart';
45
import 'package:file_picker/file_picker.dart';
@@ -7,10 +8,10 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
78
import 'package:image_picker/image_picker.dart';
89
import 'package:path/path.dart' as path;
910
import 'package:path_provider/path_provider.dart';
10-
import 'package:permission_handler/permission_handler.dart';
1111
import 'package:piwigo_ng/api/api_error.dart';
1212
import 'package:piwigo_ng/models/album_model.dart';
1313
import 'package:piwigo_ng/models/image_model.dart';
14+
import 'package:piwigo_ng/services/chunked_uploader.dart';
1415
import 'package:piwigo_ng/services/notification_service.dart';
1516
import 'package:piwigo_ng/services/preferences_service.dart';
1617
import 'package:piwigo_ng/utils/localizations.dart';
@@ -47,7 +48,8 @@ Future<ApiResult<ImageModel>> getImage(int imageId) async {
4748
return ApiResult(error: ApiErrors.error);
4849
}
4950

50-
Future<ApiResult<List<ImageModel>>> fetchImages(int albumID, [int page = 0]) async {
51+
Future<ApiResult<List<ImageModel>>> fetchImages(int albumID,
52+
[int page = 0]) async {
5153
Map<String, dynamic> queries = {
5254
'format': 'json',
5355
'method': 'pwg.categories.getImages',
@@ -154,21 +156,12 @@ Future<ApiResult<Map>> fetchFavorites([int page = 0]) async {
154156
return ApiResult(error: ApiErrors.error);
155157
}
156158

157-
Future<bool> _requestPermissions() async {
158-
var permission = await Permission.storage.status;
159-
if (permission != PermissionStatus.granted) {
160-
await Permission.storage.request();
161-
permission = await Permission.storage.status;
162-
}
163-
164-
return permission == PermissionStatus.granted;
165-
}
166-
167159
Future<String?> pickDirectoryPath() async {
168160
return await FilePicker.platform.getDirectoryPath();
169161
}
170162

171-
Future<void> _showDownloadNotification({bool success = true, String? payload}) async {
163+
Future<void> _showDownloadNotification(
164+
{bool success = true, String? payload}) async {
172165
if (!Preferences.getDownloadNotification) return;
173166
final android = AndroidNotificationDetails(
174167
'piwigo-ng-download',
@@ -179,8 +172,12 @@ Future<void> _showDownloadNotification({bool success = true, String? payload}) a
179172
);
180173
await showLocalNotification(
181174
id: 0,
182-
title: success ? appStrings.downloadImageSuccess_title : appStrings.downloadImageFail_title,
183-
body: success ? appStrings.downloadImageSuccess_message : appStrings.deleteImageFail_message,
175+
title: success
176+
? appStrings.downloadImageSuccess_title
177+
: appStrings.downloadImageFail_title,
178+
body: success
179+
? appStrings.downloadImageSuccess_message
180+
: appStrings.deleteImageFail_message,
184181
details: android,
185182
payload: payload,
186183
);
@@ -317,7 +314,8 @@ Future<int> removeImages(List<ImageModel> images, int albumId) async {
317314
}
318315

319316
Future<bool> removeImage(ImageModel image, int albumId) async {
320-
final List<int> albums = image.categories.map<int>((album) => album['id']).toList();
317+
final List<int> albums =
318+
image.categories.map<int>((album) => album['id']).toList();
321319
albums.removeWhere((album) => album == albumId);
322320

323321
if (albums.isEmpty) {
@@ -335,7 +333,8 @@ Future<bool> removeImage(ImageModel image, int albumId) async {
335333
});
336334

337335
try {
338-
Response response = await ApiClient.post(data: formData, queryParameters: queries);
336+
Response response =
337+
await ApiClient.post(data: formData, queryParameters: queries);
339338

340339
if (response.statusCode == 200) {
341340
return true;
@@ -348,7 +347,8 @@ Future<bool> removeImage(ImageModel image, int albumId) async {
348347
return false;
349348
}
350349

351-
Future<int> moveImages(List<ImageModel> images, int oldAlbumId, int newAlbumId) async {
350+
Future<int> moveImages(
351+
List<ImageModel> images, int oldAlbumId, int newAlbumId) async {
352352
int nbMoved = 0;
353353
for (var image in images) {
354354
bool response = await moveImage(image, oldAlbumId, newAlbumId);
@@ -360,7 +360,8 @@ Future<int> moveImages(List<ImageModel> images, int oldAlbumId, int newAlbumId)
360360
}
361361

362362
Future<bool> moveImage(ImageModel image, int oldAlbumId, int newAlbumId) async {
363-
final List<int> albums = image.categories.map<int>((album) => album['id']).toList();
363+
final List<int> albums =
364+
image.categories.map<int>((album) => album['id']).toList();
364365
albums.removeWhere((id) => id == oldAlbumId);
365366
albums.add(newAlbumId);
366367
Map<String, String> queries = {
@@ -375,7 +376,8 @@ Future<bool> moveImage(ImageModel image, int oldAlbumId, int newAlbumId) async {
375376
});
376377

377378
try {
378-
Response response = await ApiClient.post(data: formData, queryParameters: queries);
379+
Response response =
380+
await ApiClient.post(data: formData, queryParameters: queries);
379381

380382
if (response.statusCode == 200) {
381383
return true;
@@ -391,7 +393,8 @@ Future<bool> moveImage(ImageModel image, int oldAlbumId, int newAlbumId) async {
391393
Future<int> assignImages(List<ImageModel> images, int albumId) async {
392394
int nbAssigned = 0;
393395
for (ImageModel image in images) {
394-
final List<int> categories = image.categories.map<int>((album) => album['id']).toList();
396+
final List<int> categories =
397+
image.categories.map<int>((album) => album['id']).toList();
395398
categories.add(albumId);
396399
bool response = await assignImage(image.id, categories);
397400
if (response == true) {
@@ -414,7 +417,8 @@ Future<bool> assignImage(int imageId, List<int> categories) async {
414417
});
415418

416419
try {
417-
Response response = await ApiClient.post(data: formData, queryParameters: queries);
420+
Response response =
421+
await ApiClient.post(data: formData, queryParameters: queries);
418422

419423
if (response.statusCode == 200) {
420424
return true;
@@ -427,7 +431,8 @@ Future<bool> assignImage(int imageId, List<int> categories) async {
427431
return false;
428432
}
429433

430-
Future<int> editImages(List<ImageModel> images, [Map<String, dynamic> info = const {}]) async {
434+
Future<int> editImages(List<ImageModel> images,
435+
[Map<String, dynamic> info = const {}]) async {
431436
int nbEdited = 0;
432437
for (ImageModel image in images) {
433438
bool response = await editImage(image, info);
@@ -438,7 +443,8 @@ Future<int> editImages(List<ImageModel> images, [Map<String, dynamic> info = con
438443
return nbEdited;
439444
}
440445

441-
Future<bool> editImage(ImageModel image, [Map<String, dynamic> info = const {}]) async {
446+
Future<bool> editImage(ImageModel image,
447+
[Map<String, dynamic> info = const {}]) async {
442448
final Map<String, String> queries = {
443449
'format': 'json',
444450
'method': 'pwg.images.setInfo',
@@ -472,3 +478,39 @@ Future<bool> editImage(ImageModel image, [Map<String, dynamic> info = const {}])
472478
}
473479
return false;
474480
}
481+
482+
Future<int?> checkImageExist(File file) async {}
483+
484+
/// Return a list of files that are not in the server
485+
Future<List<File>> checkImagesNotExist(List<File> files) async {
486+
Map<String, File> md5sumList = {};
487+
488+
for (File file in files) {
489+
String md5sum = await ChunkedUploader.generateMd5(file.openRead());
490+
md5sumList[md5sum] = file;
491+
}
492+
493+
final Map<String, String> queries = {
494+
'format': 'json',
495+
'method': 'pwg.images.exist',
496+
'md5sum_list': md5sumList.keys.join(','),
497+
};
498+
499+
try {
500+
Response response = await ApiClient.get(
501+
queryParameters: queries,
502+
);
503+
504+
Map<String, dynamic> data = json.decode(response.data);
505+
if (data['stat'] == 'fail') return [];
506+
print(data['result']);
507+
Map<String, dynamic> existResult = data['result'];
508+
existResult.removeWhere((key, value) => value != null);
509+
return existResult.keys.map((md5sum) => md5sumList[md5sum]!).toList();
510+
} on DioError catch (e) {
511+
debugPrint('Edit images: ${e.message}');
512+
} on Error catch (e) {
513+
debugPrint('Edit images: ${e.stackTrace}');
514+
}
515+
return [];
516+
}

lib/api/upload.dart

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import 'dart:convert';
22
import 'dart:io';
33

44
import 'package:connectivity_plus/connectivity_plus.dart';
5+
import 'package:device_info_plus/device_info_plus.dart';
56
import 'package:dio/dio.dart';
67
import 'package:flutter/material.dart';
78
import 'package:flutter/services.dart';
89
import 'package:flutter_image_compress/flutter_image_compress.dart';
910
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
1011
import 'package:image_picker/image_picker.dart';
1112
import 'package:path_provider/path_provider.dart';
13+
import 'package:permission_handler/permission_handler.dart';
1214
import 'package:piwigo_ng/api/api_client.dart';
1315
import 'package:piwigo_ng/api/authentication.dart';
1416
import 'package:piwigo_ng/app.dart';
@@ -23,6 +25,36 @@ import 'package:shared_preferences/shared_preferences.dart';
2325
import '../services/chunked_uploader.dart';
2426
import '../services/notification_service.dart';
2527

28+
Future<bool> askMediaPermission() async {
29+
bool storage = true;
30+
bool videos = true;
31+
bool photos = true;
32+
33+
// Only check for storage < Android 13
34+
AndroidDeviceInfo androidInfo = await DeviceInfoPlugin().androidInfo;
35+
if (androidInfo.version.sdkInt >= 33) {
36+
videos = await Permission.videos.status.isGranted;
37+
photos = await Permission.photos.status.isGranted;
38+
39+
if (!videos) {
40+
videos = await Permission.videos.request().isGranted;
41+
}
42+
if (!photos) {
43+
photos = await Permission.photos.request().isGranted;
44+
}
45+
} else {
46+
storage = await Permission.storage.status.isGranted;
47+
if (!storage) {
48+
storage = await Permission.storage.request().isGranted;
49+
}
50+
}
51+
52+
if (storage && (videos || photos)) {
53+
return true;
54+
}
55+
return false;
56+
}
57+
2658
Future<List<int>> uploadPhotos(
2759
List<XFile> photos,
2860
int albumId, {
@@ -50,7 +82,8 @@ Future<List<int>> uploadPhotos(
5082
if (url == null) return [];
5183
String? username = await storage.read(key: 'SERVER_USERNAME');
5284
String? password = await storage.read(key: 'SERVER_PASSWORD');
53-
UploadNotifier uploadNotifier = App.appKey.currentContext!.read<UploadNotifier>();
85+
UploadNotifier uploadNotifier =
86+
App.appKey.currentContext!.read<UploadNotifier>();
5487
int nbError = 0;
5588

5689
// Creates Upload Item list for the upload notifier
@@ -157,8 +190,10 @@ Future<Response?> uploadChunk({
157190
};
158191

159192
if (info['name'] != '' && info['name'] != null) fields['name'] = info['name'];
160-
if (info['comment'] != '' && info['comment'] != null) fields['comment'] = info['comment'];
161-
if (info['tag_ids']?.isNotEmpty ?? false) fields['tag_ids'] = info['tag_ids'].join(',');
193+
if (info['comment'] != '' && info['comment'] != null)
194+
fields['comment'] = info['comment'];
195+
if (info['tag_ids']?.isNotEmpty ?? false)
196+
fields['tag_ids'] = info['tag_ids'].join(',');
162197
if (info['level'] != -1) fields['level'] = info['level'];
163198

164199
ChunkedUploader chunkedUploader = ChunkedUploader(Dio(
@@ -216,7 +251,8 @@ Future<bool> uploadCompleted(List<int> imageId, int categoryId) async {
216251
});
217252

218253
try {
219-
Response response = await ApiClient.post(data: formData, queryParameters: queries);
254+
Response response =
255+
await ApiClient.post(data: formData, queryParameters: queries);
220256
if (response.statusCode == 200) {
221257
return true;
222258
}
@@ -238,7 +274,8 @@ Future<bool> communityUploadCompleted(List<int> imageId, int categoryId) async {
238274
'category_id': categoryId,
239275
});
240276
try {
241-
Response response = await ApiClient.post(data: formData, queryParameters: queries);
277+
Response response =
278+
await ApiClient.post(data: formData, queryParameters: queries);
242279
if (response.statusCode == 200) {
243280
return true;
244281
}

0 commit comments

Comments
 (0)