From a104eca8ff08879bffe8ea871301e226e4534355 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sun, 12 Sep 2021 10:38:30 +0530 Subject: [PATCH 1/8] Add API for collection/move-files --- lib/services/collections_service.dart | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/lib/services/collections_service.dart b/lib/services/collections_service.dart index cb0a82f96..7f53fb328 100644 --- a/lib/services/collections_service.dart +++ b/lib/services/collections_service.dart @@ -339,6 +339,39 @@ class CollectionsService { }); } + Future moveFilesBetweenCollection( + int toCollectionID, int fromCollectionID, List files) { + final params = {}; + params["toCollectionID"] = toCollectionID; + params["fromCollectionID"] = fromCollectionID; + params["files"] = []; + for (final file in files) { + final fileKey = decryptFileKey(file); + file.generatedID = null; // So that a new entry is created in the FilesDB + file.collectionID = toCollectionID; + final encryptedKeyData = + CryptoUtil.encryptSync(fileKey, getCollectionKey(toCollectionID)); + file.encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData); + file.keyDecryptionNonce = Sodium.bin2base64(encryptedKeyData.nonce); + params["files"].add(CollectionFileItem( + file.uploadedFileID, file.encryptedKey, file.keyDecryptionNonce) + .toMap()); + } + return _dio + .post( + Configuration.instance.getHttpEndpoint() + "/collections/move-files", + data: params, + options: + Options(headers: {"X-Auth-Token": Configuration.instance.getToken()}), + ) + .then((value) async { + // insert files to new collection + await _filesDB.insertMultiple(files); + Bus.instance.fire(CollectionUpdatedEvent(toCollectionID, files)); + // todo: remove files from existing collection. RemoteSync should eventually remote + }); + } + Future removeFromCollection(int collectionID, List files) async { final params = {}; params["collectionID"] = collectionID; From 4a2d6b04db0e85856d8129b6c5b8f8f82b4b8976 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 13 Sep 2021 09:27:57 +0530 Subject: [PATCH 2/8] move-files UI: add option to move files between collection --- lib/services/collections_service.dart | 6 +++- lib/ui/create_collection_page.dart | 40 ++++++++++++++++++++++++--- lib/ui/gallery_app_bar_widget.dart | 22 +++++++++++++++ 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/lib/services/collections_service.dart b/lib/services/collections_service.dart index 7f53fb328..3103ab24e 100644 --- a/lib/services/collections_service.dart +++ b/lib/services/collections_service.dart @@ -12,6 +12,7 @@ import 'package:photos/core/network.dart'; import 'package:photos/db/collections_db.dart'; import 'package:photos/db/files_db.dart'; import 'package:photos/events/collection_updated_event.dart'; +import 'package:photos/events/files_updated_event.dart'; import 'package:photos/events/local_photos_updated_event.dart'; import 'package:photos/models/collection.dart'; import 'package:photos/models/collection_file_item.dart'; @@ -368,7 +369,10 @@ class CollectionsService { // insert files to new collection await _filesDB.insertMultiple(files); Bus.instance.fire(CollectionUpdatedEvent(toCollectionID, files)); - // todo: remove files from existing collection. RemoteSync should eventually remote + // todo: remove files from existing collection locally. + // Ideally, remoteSync should take care of it. + Bus.instance.fire(CollectionUpdatedEvent(fromCollectionID, files, + type: EventType.deleted)); }); } diff --git a/lib/ui/create_collection_page.dart b/lib/ui/create_collection_page.dart index e87528136..b35c1ab55 100644 --- a/lib/ui/create_collection_page.dart +++ b/lib/ui/create_collection_page.dart @@ -19,10 +19,16 @@ import 'package:photos/utils/share_util.dart'; import 'package:photos/utils/toast_util.dart'; import 'package:receive_sharing_intent/receive_sharing_intent.dart'; +enum CollectionActionType { addFiles, moveFiles } + class CreateCollectionPage extends StatefulWidget { final SelectedFiles selectedFiles; final List sharedFiles; - const CreateCollectionPage(this.selectedFiles, this.sharedFiles, {Key key}) + final CollectionActionType actionType; + + const CreateCollectionPage(this.selectedFiles, this.sharedFiles, + {Key key, + this.actionType = CollectionActionType.addFiles}) : super(key: key); @override @@ -140,8 +146,10 @@ class _CreateCollectionPageState extends State { ], ), onTap: () async { - if (await _addToCollection(item.collection.id)) { - showToast("added successfully to '" + item.collection.name); + if (await _addOrMoveToCollection(item.collection.id)) { + showToast(widget.actionType == CollectionActionType.addFiles + ? "added successfully to " + item.collection.name + : "moved successfully to " + item.collection.name); _navigateToCollection(item.collection); } }, @@ -191,7 +199,7 @@ class _CreateCollectionPageState extends State { Navigator.of(context, rootNavigator: true).pop('dialog'); final collection = await _createAlbum(_albumName); if (collection != null) { - if (await _addToCollection(collection.id)) { + if (await _addOrMoveToCollection(collection.id)) { showToast("album '" + _albumName + "' created."); _navigateToCollection(collection); } @@ -220,6 +228,30 @@ class _CreateCollectionPageState extends State { ))); } + Future _addOrMoveToCollection(int collectionID) async { + return widget.actionType == CollectionActionType.addFiles + ? _addToCollection(collectionID) + : _moveFilesToCollection(collectionID); + } + + Future _moveFilesToCollection(int toCollectionID) async { + final dialog = createProgressDialog(context, "moving files to album..."); + await dialog.show(); + try { + int fromCollectionID = widget.selectedFiles.files.first.collectionID; + await CollectionsService.instance.moveFilesBetweenCollection(toCollectionID, fromCollectionID, widget.selectedFiles.files.toList()); + RemoteSyncService.instance.sync(silently: true); + widget.selectedFiles?.clearAll(); + await dialog.hide(); + return true; + } catch(e, s) { + _logger.severe("Could not move to album", e, s); + await dialog.hide(); + showGenericErrorDialog(context); + return false; + } + } + Future _addToCollection(int collectionID) async { final dialog = createProgressDialog(context, "uploading files to album..."); await dialog.show(); diff --git a/lib/ui/gallery_app_bar_widget.dart b/lib/ui/gallery_app_bar_widget.dart index 68a3639bd..f68503a64 100644 --- a/lib/ui/gallery_app_bar_widget.dart +++ b/lib/ui/gallery_app_bar_widget.dart @@ -164,8 +164,30 @@ class _GalleryAppBarWidgetState extends State { ))); } + Future _moveFiles() async { + Navigator.push( + context, + PageTransition( + type: PageTransitionType.bottomToTop, + child: CreateCollectionPage( + widget.selectedFiles, + null, + actionType: CollectionActionType.moveFiles, + ))); + } + List _getActions(BuildContext context) { List actions = []; + if (Configuration.instance.hasConfiguredAccount()) { + actions.add(IconButton( + icon: Icon(Platform.isAndroid + ? Icons.arrow_right_alt_rounded + : CupertinoIcons.arrow_right), + onPressed: () { + _moveFiles(); + }, + )); + } // skip add button for incoming collection till this feature is implemented if (Configuration.instance.hasConfiguredAccount() && widget.type != GalleryAppBarType.shared_collection) { From dfa13d9bf73a99cd8f2c2d3584935aa003080f25 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 13 Sep 2021 11:22:50 +0530 Subject: [PATCH 3/8] move-files: add basic validation on client --- lib/services/collections_service.dart | 19 +++++++++++++++++++ lib/ui/create_collection_page.dart | 9 ++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/services/collections_service.dart b/lib/services/collections_service.dart index 3103ab24e..da19205ba 100644 --- a/lib/services/collections_service.dart +++ b/lib/services/collections_service.dart @@ -342,6 +342,7 @@ class CollectionsService { Future moveFilesBetweenCollection( int toCollectionID, int fromCollectionID, List files) { + _validateMoveRequest(toCollectionID, fromCollectionID, files); final params = {}; params["toCollectionID"] = toCollectionID; params["fromCollectionID"] = fromCollectionID; @@ -376,6 +377,24 @@ class CollectionsService { }); } + void _validateMoveRequest( + int toCollectionID, int fromCollectionID, List files) { + if (toCollectionID == fromCollectionID) { + throw AssertionError("can't move to same album"); + } + for (final file in files) { + if (file.uploadedFileID == null) { + throw AssertionError("can only move uploaded memories"); + } + if (file.collectionID != fromCollectionID) { + throw AssertionError("all memories should belong to the same album"); + } + if (file.ownerID != Configuration.instance.getUserID()) { + throw AssertionError("can only move memories uploaded by you"); + } + } + } + Future removeFromCollection(int collectionID, List files) async { final params = {}; params["collectionID"] = collectionID; diff --git a/lib/ui/create_collection_page.dart b/lib/ui/create_collection_page.dart index b35c1ab55..f0ee4b0b2 100644 --- a/lib/ui/create_collection_page.dart +++ b/lib/ui/create_collection_page.dart @@ -238,15 +238,18 @@ class _CreateCollectionPageState extends State { final dialog = createProgressDialog(context, "moving files to album..."); await dialog.show(); try { - int fromCollectionID = widget.selectedFiles.files.first.collectionID; - await CollectionsService.instance.moveFilesBetweenCollection(toCollectionID, fromCollectionID, widget.selectedFiles.files.toList()); + int fromCollectionID = widget.selectedFiles.files?.first?.collectionID; + await CollectionsService.instance.moveFilesBetweenCollection(toCollectionID, fromCollectionID, widget.selectedFiles.files?.toList()); RemoteSyncService.instance.sync(silently: true); widget.selectedFiles?.clearAll(); await dialog.hide(); return true; + } on AssertionError catch (e, s) { + await dialog.hide(); + showErrorDialog(context, "something went wrong", e.message); + return false; } catch(e, s) { _logger.severe("Could not move to album", e, s); - await dialog.hide(); showGenericErrorDialog(context); return false; } From 3099a1a9ae21449040067e4cf162536436cb47d1 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 13 Sep 2021 12:07:34 +0530 Subject: [PATCH 4/8] move-files: show option for owned_collection only --- lib/ui/gallery_app_bar_widget.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ui/gallery_app_bar_widget.dart b/lib/ui/gallery_app_bar_widget.dart index f68503a64..57891c6e3 100644 --- a/lib/ui/gallery_app_bar_widget.dart +++ b/lib/ui/gallery_app_bar_widget.dart @@ -178,7 +178,8 @@ class _GalleryAppBarWidgetState extends State { List _getActions(BuildContext context) { List actions = []; - if (Configuration.instance.hasConfiguredAccount()) { + if (Configuration.instance.hasConfiguredAccount() && + widget.type == GalleryAppBarType.owned_collection) { actions.add(IconButton( icon: Icon(Platform.isAndroid ? Icons.arrow_right_alt_rounded From 5ecb730c723babeb714e7945de32b23652e12f3d Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 13 Sep 2021 12:44:43 +0530 Subject: [PATCH 5/8] update page title based on actionType --- lib/ui/create_collection_page.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ui/create_collection_page.dart b/lib/ui/create_collection_page.dart index f0ee4b0b2..c4ec39188 100644 --- a/lib/ui/create_collection_page.dart +++ b/lib/ui/create_collection_page.dart @@ -43,7 +43,9 @@ class _CreateCollectionPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text("add files"), + title: Text(widget.actionType == CollectionActionType.addFiles + ? "add files" + : "move files"), ), body: _getBody(context), ); From 4213e2348adf1bfc2ad603314db90ea6e21fbd81 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Mon, 13 Sep 2021 14:10:35 +0530 Subject: [PATCH 6/8] Reorder move button --- lib/ui/gallery_app_bar_widget.dart | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/ui/gallery_app_bar_widget.dart b/lib/ui/gallery_app_bar_widget.dart index 57891c6e3..fdcac574f 100644 --- a/lib/ui/gallery_app_bar_widget.dart +++ b/lib/ui/gallery_app_bar_widget.dart @@ -178,17 +178,6 @@ class _GalleryAppBarWidgetState extends State { List _getActions(BuildContext context) { List actions = []; - if (Configuration.instance.hasConfiguredAccount() && - widget.type == GalleryAppBarType.owned_collection) { - actions.add(IconButton( - icon: Icon(Platform.isAndroid - ? Icons.arrow_right_alt_rounded - : CupertinoIcons.arrow_right), - onPressed: () { - _moveFiles(); - }, - )); - } // skip add button for incoming collection till this feature is implemented if (Configuration.instance.hasConfiguredAccount() && widget.type != GalleryAppBarType.shared_collection) { @@ -200,6 +189,17 @@ class _GalleryAppBarWidgetState extends State { }, )); } + if (Configuration.instance.hasConfiguredAccount() && + widget.type == GalleryAppBarType.owned_collection) { + actions.add(IconButton( + icon: Icon(Platform.isAndroid + ? Icons.arrow_right_alt_rounded + : CupertinoIcons.arrow_right), + onPressed: () { + _moveFiles(); + }, + )); + } actions.add(IconButton( icon: Icon( Platform.isAndroid ? Icons.share_outlined : CupertinoIcons.share), From f2dacffc56bc18d28f5a75bc8a103b45176c5665 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Mon, 13 Sep 2021 14:17:39 +0530 Subject: [PATCH 7/8] Set color of the dialog button text --- lib/utils/dialog_util.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index b7b42b711..7d5682836 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -25,13 +25,19 @@ ProgressDialog createProgressDialog(BuildContext context, String message) { return dialog; } -Future showErrorDialog(BuildContext context, String title, String content) { +Future showErrorDialog( + BuildContext context, String title, String content) { AlertDialog alert = AlertDialog( title: Text(title), content: Text(content), actions: [ TextButton( - child: Text("ok"), + child: Text( + "ok", + style: TextStyle( + color: Colors.white, + ), + ), onPressed: () { Navigator.of(context, rootNavigator: true).pop('dialog'); }, From 5068d9e6b73fa396a372ee4c1667f529e488969b Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Mon, 13 Sep 2021 14:17:57 +0530 Subject: [PATCH 8/8] Minor string changes --- lib/services/collections_service.dart | 2 +- lib/ui/create_collection_page.dart | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/services/collections_service.dart b/lib/services/collections_service.dart index da19205ba..daf1f7a90 100644 --- a/lib/services/collections_service.dart +++ b/lib/services/collections_service.dart @@ -340,7 +340,7 @@ class CollectionsService { }); } - Future moveFilesBetweenCollection( + Future move( int toCollectionID, int fromCollectionID, List files) { _validateMoveRequest(toCollectionID, fromCollectionID, files); final params = {}; diff --git a/lib/ui/create_collection_page.dart b/lib/ui/create_collection_page.dart index c4ec39188..d432fde72 100644 --- a/lib/ui/create_collection_page.dart +++ b/lib/ui/create_collection_page.dart @@ -27,8 +27,7 @@ class CreateCollectionPage extends StatefulWidget { final CollectionActionType actionType; const CreateCollectionPage(this.selectedFiles, this.sharedFiles, - {Key key, - this.actionType = CollectionActionType.addFiles}) + {Key key, this.actionType = CollectionActionType.addFiles}) : super(key: key); @override @@ -241,16 +240,19 @@ class _CreateCollectionPageState extends State { await dialog.show(); try { int fromCollectionID = widget.selectedFiles.files?.first?.collectionID; - await CollectionsService.instance.moveFilesBetweenCollection(toCollectionID, fromCollectionID, widget.selectedFiles.files?.toList()); + await CollectionsService.instance.move( + toCollectionID, + fromCollectionID, + widget.selectedFiles.files?.toList()); RemoteSyncService.instance.sync(silently: true); widget.selectedFiles?.clearAll(); await dialog.hide(); return true; } on AssertionError catch (e, s) { await dialog.hide(); - showErrorDialog(context, "something went wrong", e.message); + showErrorDialog(context, "oops", e.message); return false; - } catch(e, s) { + } catch (e, s) { _logger.severe("Could not move to album", e, s); showGenericErrorDialog(context); return false;