Merge pull request #41 from ente-io/move_files

Support for moving files between albums
This commit is contained in:
Neeraj Gupta 2021-09-13 18:16:49 +05:30 committed by GitHub
commit c416ca1e94
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 131 additions and 7 deletions

View file

@ -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';
@ -339,6 +340,61 @@ class CollectionsService {
});
}
Future<void> move(
int toCollectionID, int fromCollectionID, List<File> files) {
_validateMoveRequest(toCollectionID, fromCollectionID, files);
final params = <String, dynamic>{};
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 locally.
// Ideally, remoteSync should take care of it.
Bus.instance.fire(CollectionUpdatedEvent(fromCollectionID, files,
type: EventType.deleted));
});
}
void _validateMoveRequest(
int toCollectionID, int fromCollectionID, List<File> 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<void> removeFromCollection(int collectionID, List<File> files) async {
final params = <String, dynamic>{};
params["collectionID"] = collectionID;

View file

@ -19,10 +19,15 @@ 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<SharedMediaFile> 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
@ -37,7 +42,9 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
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),
);
@ -140,8 +147,10 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
],
),
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 +200,7 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
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 +229,36 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
)));
}
Future<bool> _addOrMoveToCollection(int collectionID) async {
return widget.actionType == CollectionActionType.addFiles
? _addToCollection(collectionID)
: _moveFilesToCollection(collectionID);
}
Future<bool> _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.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, "oops", e.message);
return false;
} catch (e, s) {
_logger.severe("Could not move to album", e, s);
showGenericErrorDialog(context);
return false;
}
}
Future<bool> _addToCollection(int collectionID) async {
final dialog = createProgressDialog(context, "uploading files to album...");
await dialog.show();

View file

@ -164,6 +164,18 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
)));
}
Future<void> _moveFiles() async {
Navigator.push(
context,
PageTransition(
type: PageTransitionType.bottomToTop,
child: CreateCollectionPage(
widget.selectedFiles,
null,
actionType: CollectionActionType.moveFiles,
)));
}
List<Widget> _getActions(BuildContext context) {
List<Widget> actions = <Widget>[];
// skip add button for incoming collection till this feature is implemented
@ -177,6 +189,17 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
},
));
}
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),

View file

@ -25,13 +25,19 @@ ProgressDialog createProgressDialog(BuildContext context, String message) {
return dialog;
}
Future<dynamic> showErrorDialog(BuildContext context, String title, String content) {
Future<dynamic> 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');
},