Merge pull request #41 from ente-io/move_files
Support for moving files between albums
This commit is contained in:
commit
c416ca1e94
4 changed files with 131 additions and 7 deletions
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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');
|
||||
},
|
||||
|
|
Loading…
Add table
Reference in a new issue