Skip to content

Commit 2257135

Browse files
authored
Merge branch 'Dev/v2' into Dev/v2
2 parents 0956275 + 6b5a1f3 commit 2257135

29 files changed

Lines changed: 1930 additions & 513 deletions

l10n/app_en.arb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,12 @@
195195
"categoryUpload_takePhoto": "Take Photo",
196196
"categoryUpload_takeVideo": "Take Video",
197197

198+
"uploadList_title": "Upload Status",
199+
"uploadList_uploading": "Uploading",
200+
"uploadList_history": "History",
201+
202+
"uploadList_empty": "Empty",
203+
198204

199205
"setDefaultCategory_select": "Please select an album or sub-album which will become the new root album.",
200206
"setDefaultCategory_title": "Default Album",
@@ -364,8 +370,8 @@
364370
"imageOptions_edit": "Edit",
365371
"imageOptions_download": "Download",
366372
"imageOptions_share": "Share",
367-
"imageOptions_addFavorites": "Add favorites",
368-
"imageOptions_removeFavorites": "Remove favorites",
373+
"imageOptions_addFavorites": "Add to favorites",
374+
"imageOptions_removeFavorites": "Remove from favorites",
369375
"imageOptions_setAlbumImage": "Set Album Thumbnail",
370376

371377

@@ -692,6 +698,7 @@
692698
"imageUploadCompleted_title": "Upload Completed",
693699
"imageUploadCompleted_message": "photo uploaded to your Piwigo server.",
694700
"imageUploadCompleted_message1": "photos uploaded to your Piwigo server.",
701+
"imageUploadCompleted_warning": "some of your photos could not be uploaded.",
695702

696703

697704
"uploadError_title": "Upload Error",

lib/api/albums.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Map<String, dynamic> tryParseJson(String data) {
1414
return json.decode(data);
1515
} on FormatException catch (e) {
1616
debugPrint('Invalid json data');
17+
debugPrint(data);
1718
int start = data.indexOf('{');
1819
int end = data.lastIndexOf('}');
1920
String parsedData = data.substring(start, end + 1);
@@ -213,6 +214,7 @@ Future<ApiResult<bool>> editAlbum({required String name, required int albumId, S
213214
'name': name,
214215
'comment': description,
215216
});
217+
216218
try {
217219
Response response = await ApiClient.post(
218220
data: formData,

lib/api/images.dart

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Future<ApiResult<ImageModel>> getImage(int imageId) async {
4747
return ApiResult(error: ApiErrors.error);
4848
}
4949

50-
Future<ApiResult<List<ImageModel>>> fetchImages(int albumID, int page) async {
50+
Future<ApiResult<List<ImageModel>>> fetchImages(int albumID, [int page = 0]) async {
5151
Map<String, dynamic> queries = {
5252
'format': 'json',
5353
'method': 'pwg.categories.getImages',
@@ -61,7 +61,7 @@ Future<ApiResult<List<ImageModel>>> fetchImages(int albumID, int page) async {
6161
Response response = await ApiClient.get(queryParameters: queries);
6262

6363
if (response.statusCode == 200) {
64-
var jsonImages = json.decode(response.data)["result"]["images"];
64+
var jsonImages = json.decode(response.data)['result']['images'];
6565
List<ImageModel> images = List<ImageModel>.from(
6666
jsonImages.map((image) => ImageModel.fromJson(image)),
6767
);
@@ -78,10 +78,12 @@ Future<ApiResult<List<ImageModel>>> fetchImages(int albumID, int page) async {
7878

7979
Future<ApiResult<Map>> searchImages(String searchQuery, [int page = 0]) async {
8080
Map<String, dynamic> query = {
81-
"format": "json",
82-
"method": "pwg.images.search",
83-
"query": searchQuery,
84-
"page": page,
81+
'format': 'json',
82+
'method': 'pwg.images.search',
83+
'query': searchQuery,
84+
'order': Preferences.getImageSort.value,
85+
'per_page': Settings.defaultElementPerPage,
86+
'page': page,
8587
};
8688

8789
try {
@@ -91,17 +93,17 @@ Future<ApiResult<Map>> searchImages(String searchQuery, [int page = 0]) async {
9193
final Map<String, dynamic> result = json.decode(response.data);
9294
if (result['err'] == 1002) {
9395
return ApiResult<Map>(data: {
94-
"total_count": 0,
95-
"images": [],
96+
'total_count': 0,
97+
'images': [],
9698
});
9799
}
98-
final jsonImages = result["result"]["images"];
100+
final jsonImages = result['result']['images'];
99101
List<ImageModel> images = List<ImageModel>.from(
100102
jsonImages.map((image) => ImageModel.fromJson(image)),
101103
);
102104
return ApiResult<Map>(data: {
103-
"total_count": result["result"]["paging"]["total_count"],
104-
"images": images,
105+
'total_count': result['result']['paging']['total_count'],
106+
'images': images,
105107
});
106108
}
107109
} on DioError catch (e) {
@@ -112,6 +114,46 @@ Future<ApiResult<Map>> searchImages(String searchQuery, [int page = 0]) async {
112114
return ApiResult(error: ApiErrors.searchImagesError);
113115
}
114116

117+
Future<ApiResult<Map>> fetchFavorites([int page = 0]) async {
118+
Map<String, dynamic> query = {
119+
'format': 'json',
120+
'method': 'pwg.users.favorites.getList',
121+
'order': Preferences.getImageSort.value,
122+
'per_page': Settings.defaultElementPerPage,
123+
'page': page,
124+
};
125+
126+
try {
127+
Response response = await ApiClient.get(queryParameters: query);
128+
129+
if (response.statusCode == 200) {
130+
final Map<String, dynamic> result = json.decode(response.data);
131+
if (result['stat'] == 'fail') {
132+
return ApiResult<Map>(data: {
133+
'total_count': 0,
134+
'images': [],
135+
});
136+
}
137+
final jsonImages = result['result']['images'];
138+
List<ImageModel> images = List<ImageModel>.from(
139+
jsonImages.map((image) {
140+
image['is_favorite'] = true;
141+
return ImageModel.fromJson(image);
142+
}),
143+
);
144+
return ApiResult<Map>(data: {
145+
'total_count': result['result']['paging']['count'],
146+
'images': images,
147+
});
148+
}
149+
} on DioError catch (e) {
150+
debugPrint('Fetch favorites: ${e.message}');
151+
} on Error catch (e) {
152+
debugPrint('Fetch favorites: ${e.stackTrace}');
153+
}
154+
return ApiResult(error: ApiErrors.error);
155+
}
156+
115157
Future<bool> _requestPermissions() async {
116158
var permission = await Permission.storage.status;
117159
if (permission != PermissionStatus.granted) {
@@ -198,14 +240,14 @@ Future<XFile?> downloadImage(String dirPath, ImageModel image) async {
198240
String localPath = path.join(dirPath, image.file);
199241
try {
200242
await ApiClient.download(
201-
path: image.derivatives.medium.url,
243+
path: image.elementUrl,
202244
outputPath: localPath,
203245
);
204246
return XFile(localPath);
205247
} on DioError catch (e) {
206-
debugPrint('Download images: ${e.message}');
248+
debugPrint("Download images: ${e.message}");
207249
} on Error catch (e) {
208-
debugPrint('Download images: ${e.stackTrace}');
250+
debugPrint("Download images: ${e.stackTrace}");
209251
}
210252
return null;
211253
}
@@ -415,7 +457,10 @@ Future<bool> editImage(ImageModel image, [Map<String, dynamic> info = const {}])
415457
final FormData formData = FormData.fromMap(form);
416458

417459
try {
418-
Response response = await ApiClient.post(data: formData, queryParameters: queries);
460+
Response response = await ApiClient.post(
461+
data: formData,
462+
queryParameters: queries,
463+
);
419464

420465
if (response.statusCode == 200) {
421466
return true;

lib/api/upload.dart

Lines changed: 54 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ import 'package:piwigo_ng/components/dialogs/confirm_dialog.dart';
1717
import 'package:piwigo_ng/services/preferences_service.dart';
1818
import 'package:piwigo_ng/services/upload_notifier.dart';
1919
import 'package:piwigo_ng/utils/localizations.dart';
20+
import 'package:piwigo_ng/views/upload/upload_status_page.dart';
2021
import 'package:provider/provider.dart';
2122

2223
import '../services/chunked_uploader.dart';
2324
import '../services/notification_service.dart';
2425

25-
Future<void> _showUploadNotification({bool success = true}) async {
26+
Future<void> _showUploadNotification([int nbError = 0, int nbImage = 0]) async {
2627
if (!Preferences.getUploadNotification) return;
2728
final android = AndroidNotificationDetails(
2829
'piwigo-ng-upload',
@@ -32,20 +33,33 @@ Future<void> _showUploadNotification({bool success = true}) async {
3233
importance: Importance.high,
3334
);
3435
final platform = NotificationDetails(android: android);
35-
await localNotification.show(
36-
1,
37-
success ? 'Success' : 'Failure',
38-
success ? appStrings.imageUploadCompleted_message : appStrings.uploadError_message,
39-
platform,
40-
);
36+
late String title;
37+
String? message;
38+
if (nbError == 0 && nbImage == 0) {
39+
// Upload cancelled
40+
title = appStrings.uploadCancelled_title;
41+
} else if (nbError == 0 && nbImage > 0) {
42+
// Upload completed
43+
title = appStrings.imageUploadCompleted_title;
44+
message = nbImage == 1 ? appStrings.imageUploadCompleted_message : appStrings.imageUploadCompleted_message1;
45+
} else if (nbError > 0 && nbImage != nbError) {
46+
// Upload partially completed
47+
title = appStrings.coreDataStore_WarningTitle;
48+
message = appStrings.imageUploadCompleted_warning;
49+
} else {
50+
// Upload failed
51+
title = appStrings.uploadError_title;
52+
message = appStrings.uploadError_message;
53+
}
54+
await localNotification.show(1, title, message, platform);
4155
}
4256

43-
Future<List<Map<String, dynamic>>> uploadPhotos(
57+
Future<List<int>> uploadPhotos(
4458
List<XFile> photos,
4559
int albumId, {
4660
Map<String, dynamic> info = const {},
4761
}) async {
48-
/// Check if Wifi is enabled and working
62+
// Check if Wifi is enabled and working
4963
if (Preferences.getWifiUpload) {
5064
var connectivity = await Connectivity().checkConnectivity();
5165
if (connectivity != ConnectivityResult.wifi) {
@@ -60,17 +74,17 @@ Future<List<Map<String, dynamic>>> uploadPhotos(
6074
}
6175
}
6276

63-
List<Map<String, dynamic>> result = [];
64-
List<int> uploadCompletedList = [];
77+
List<int> result = [];
6578
List<UploadItem> items = [];
6679
FlutterSecureStorage storage = const FlutterSecureStorage();
6780
String? url = await storage.read(key: 'SERVER_URL');
6881
if (url == null) return [];
6982
String? username = await storage.read(key: 'SERVER_USERNAME');
7083
String? password = await storage.read(key: 'SERVER_PASSWORD');
7184
UploadNotifier uploadNotifier = App.appKey.currentContext!.read<UploadNotifier>();
85+
int nbError = 0;
7286

73-
/// Creates Upload Item list for the upload notifier
87+
// Creates Upload Item list for the upload notifier
7488
for (var photo in photos) {
7589
File? compressedFile;
7690
if (Preferences.getRemoveMetadata) {
@@ -86,52 +100,55 @@ Future<List<Map<String, dynamic>>> uploadPhotos(
86100

87101
uploadNotifier.addItems(items);
88102

89-
/// Upload loop
90-
for (var item in items) {
103+
App.navigatorKey.currentState?.popAndPushNamed(UploadStatusPage.routeName);
104+
105+
await Future.wait(List<Future<void>>.generate(items.length, (index) async {
106+
UploadItem item = items[index];
91107
try {
92-
/// Make Request
108+
// Make Request
93109
Response? response = await uploadChunk(
94110
photo: item.file,
95111
category: albumId,
96112
url: url,
97113
username: username,
98114
password: password,
99115
info: info,
116+
cancelToken: item.cancelToken,
100117
onProgress: (progress) {
101-
// debugPrint("$progress");
102118
item.progress.sink.add(progress);
103119
},
104120
);
105-
if (response != null) {
106-
var data = json.decode(response.data);
107-
if (data['stat'] != 'fail') {
108-
result.add({
109-
'id': data['result']['id'],
110-
'url': data['result']['element_url'],
111-
});
112121

113-
/// Notify provider upload completed
114-
uploadNotifier.itemUploadCompleted(item);
115-
if (Preferences.getDeleteAfterUpload) {
116-
// todo: delete real file path, not the cached one.
117-
}
118-
} else {
122+
// Handle result
123+
if (response == null || json.decode(response.data)['stat'] == 'fail') {
124+
if (!item.cancelToken.isCancelled) {
119125
uploadNotifier.itemUploadCompleted(item, error: true);
126+
nbError++;
120127
}
121128
} else {
122-
uploadNotifier.itemUploadCompleted(item, error: true);
129+
var data = json.decode(response.data);
130+
result.add(data['result']['id']);
131+
132+
// Notify provider upload completed
133+
uploadNotifier.itemUploadCompleted(item);
134+
if (Preferences.getDeleteAfterUpload) {
135+
// todo: delete real file path, not the cached one.
136+
}
123137
}
138+
} on DioError catch (e) {
139+
debugPrint("${e.type}");
124140
} catch (e) {
125141
debugPrint("$e");
142+
nbError++;
126143
}
127-
}
128-
_showUploadNotification(success: result.isNotEmpty);
144+
}));
145+
146+
_showUploadNotification(nbError, result.length);
129147
if (result.isEmpty) return [];
130-
uploadCompletedList = result.map<int>((e) => e['id']).toList();
131148
try {
132-
await uploadCompleted(uploadCompletedList, albumId);
149+
await uploadCompleted(result, albumId);
133150
if (await methodExist('community.images.uploadCompleted')) {
134-
await communityUploadCompleted(uploadCompletedList, albumId);
151+
await communityUploadCompleted(result, albumId);
135152
}
136153
} on DioError catch (e) {
137154
debugPrint(e.message);
@@ -148,6 +165,7 @@ Future<Response?> uploadChunk({
148165
Function(double)? onProgress,
149166
String? username,
150167
String? password,
168+
CancelToken? cancelToken,
151169
}) async {
152170
Map<String, String> queries = {
153171
'format': 'json',
@@ -178,6 +196,7 @@ Future<Response?> uploadChunk({
178196
params: queries,
179197
method: 'POST',
180198
data: fields,
199+
cancelToken: cancelToken,
181200
contentType: Headers.formUrlEncodedContentType,
182201
onUploadProgress: (value) {
183202
if (onProgress != null) onProgress(value);

0 commit comments

Comments
 (0)