瀏覽代碼

Support for editing date of memory

Neeraj Gupta 3 年之前
父節點
當前提交
aa145e0217

+ 9 - 1
lib/db/files_db.dart

@@ -1003,6 +1003,14 @@ class FilesDB {
     row[columnMMdEncodedJson] = file.mMdEncodedJson ?? '{}';
     row[columnMMdVisibility] =
         file.magicMetadata?.visibility ?? kVisibilityVisible;
+    row[columnPubMMdVersion] = file.pubMmdVersion ?? 0;
+    row[columnPubMMdEncodedJson] = file.pubMmdEncodedJson ?? '{}';
+    if (file.pubMagicMetadata != null &&
+        file.pubMagicMetadata.editedTime != null) {
+      // override existing creationTime to avoid re-writing all queries related
+      // to loading the gallery
+      row[columnCreationTime] = file.pubMagicMetadata.editedTime;
+    }
     return row;
   }
 
@@ -1036,7 +1044,7 @@ class FilesDB {
         file.magicMetadata?.visibility ?? kVisibilityVisible;
 
     row[columnPubMMdVersion] = file.pubMmdVersion ?? 0;
-    row[columnPubMMdEncodedJson] == file.pubMmdEncodedJson ?? '{}';
+    row[columnPubMMdEncodedJson] = file.pubMmdEncodedJson ?? '{}';
     if (file.pubMagicMetadata != null &&
         file.pubMagicMetadata.editedTime != null) {
       // override existing creationTime to avoid re-writing all queries related

+ 1 - 1
lib/models/file.dart

@@ -48,7 +48,7 @@ class File {
   int pubMmdVersion = 0;
   PubMagicMetadata _pubMmd;
   PubMagicMetadata get pubMagicMetadata =>
-      _pubMmd ?? MagicMetadata.fromEncodedJson(pubMmdEncodedJson ?? '{}');
+      _pubMmd ?? PubMagicMetadata.fromEncodedJson(pubMmdEncodedJson ?? '{}');
   set pubMagicMetadata(val) => _pubMmd = val;
 
   static const kCurrentMetadataVersion = 1;

+ 1 - 1
lib/models/magic_metadata.dart

@@ -6,7 +6,7 @@ const kVisibilityArchive = 1;
 
 const kMagicKeyVisibility = 'visibility';
 
-const kPubMagicKeyEditedTime = 'et';
+const kPubMagicKeyEditedTime = 'editedTime';
 
 class MagicMetadata {
   // 0 -> visible

+ 61 - 2
lib/services/file_magic_service.dart

@@ -30,8 +30,7 @@ class FileMagicService {
       FileMagicService._privateConstructor();
 
   Future<void> changeVisibility(List<File> files, int visibility) async {
-    Map<String, dynamic> update = {};
-    update[kMagicKeyVisibility] = visibility;
+    Map<String, dynamic> update = {kMagicKeyVisibility: visibility};
     await _updateMagicData(files, update);
     // h4ck: Remove archived elements from the UI. If this was an archival,
     // ArchivePage will reload the new items anyway
@@ -42,6 +41,66 @@ class FileMagicService {
     }
   }
 
+  Future<void> updatePublicMagicMetadata(List<File> files, Map<String, dynamic> newMetadataUpdate) async
+  {
+    final params = <String, dynamic>{};
+    params['metadataList'] = [];
+    final int ownerID = Configuration.instance.getUserID();
+    try {
+      for (final file in files) {
+        if (file.uploadedFileID == null) {
+          throw AssertionError(
+              "operation is only supported on backed up files");
+        } else if (file.ownerID != ownerID) {
+          throw AssertionError("cannot modify memories not owned by you");
+        }
+        // read the existing magic metadata and apply new updates to existing data
+        // current update is simple replace. This will be enhanced in the future,
+        // as required.
+        Map<String, dynamic> jsonToUpdate = jsonDecode(file.pubMmdEncodedJson);
+        newMetadataUpdate.forEach((key, value) {
+          jsonToUpdate[key] = value;
+        });
+
+        // update the local information so that it's reflected on UI
+        file.pubMmdEncodedJson = jsonEncode(jsonToUpdate);
+        file.pubMagicMetadata = PubMagicMetadata.fromJson(jsonToUpdate);
+
+        final fileKey = decryptFileKey(file);
+        final encryptedMMd = await CryptoUtil.encryptChaCha(
+            utf8.encode(jsonEncode(jsonToUpdate)), fileKey);
+        params['metadataList'].add(UpdateMagicMetadataRequest(
+            id: file.uploadedFileID,
+            magicMetadata: MetadataRequest(
+              version: file.pubMmdVersion,
+              count: jsonToUpdate.length,
+              data: Sodium.bin2base64(encryptedMMd.encryptedData),
+              header: Sodium.bin2base64(encryptedMMd.header),
+            )));
+        file.pubMmdVersion = file.pubMmdVersion + 1;
+      }
+
+      await _dio.put(
+        Configuration.instance.getHttpEndpoint() + "/files/public-magic-metadata",
+        data: params,
+        options: Options(
+            headers: {"X-Auth-Token": Configuration.instance.getToken()}),
+      );
+      // update the state of the selected file. Same file in other collection
+      // should be eventually synced after remote sync has completed
+      await _filesDB.insertMultiple(files);
+      RemoteSyncService.instance.sync(silently: true);
+    } on DioError catch (e) {
+      if (e.response != null && e.response.statusCode == 409) {
+        RemoteSyncService.instance.sync(silently: true);
+      }
+      rethrow;
+    } catch (e, s) {
+      _logger.severe("failed to sync magic metadata", e, s);
+      rethrow;
+    }
+
+  }
   Future<void> _updateMagicData(
       List<File> files, Map<String, dynamic> newMetadataUpdate) async {
     final params = <String, dynamic>{};

+ 39 - 1
lib/ui/fading_app_bar.dart

@@ -7,6 +7,7 @@ import 'package:like_button/like_button.dart';
 import 'package:logging/logging.dart';
 import 'package:photo_manager/photo_manager.dart';
 import 'package:photos/core/event_bus.dart';
+import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';
 import 'package:photos/db/files_db.dart';
 import 'package:photos/events/local_photos_updated_event.dart';
 import 'package:photos/models/file.dart';
@@ -20,6 +21,7 @@ import 'package:photos/utils/date_time_util.dart';
 import 'package:photos/utils/delete_file_util.dart';
 import 'package:photos/utils/dialog_util.dart';
 import 'package:photos/utils/file_util.dart';
+import 'package:photos/utils/magic_util.dart';
 import 'package:photos/utils/toast_util.dart';
 
 class FadingAppBar extends StatefulWidget implements PreferredSizeWidget {
@@ -118,7 +120,7 @@ class FadingAppBarState extends State<FadingAppBar> {
             ),
           );
         }
-        // only show delete option for files owned by the user
+        // options for files owned by the user
         if (widget.file.ownerID == null ||
             widget.file.ownerID == widget.userID) {
           items.add(
@@ -137,6 +139,24 @@ class FadingAppBarState extends State<FadingAppBar> {
               ),
             ),
           );
+          if(widget.file.uploadedFileID != null) {
+            items.add(
+              PopupMenuItem(
+                value: 3,
+                child: Row(
+                  children: [
+                    Icon(Platform.isAndroid
+                        ? Icons.access_time_rounded
+                        : CupertinoIcons.time),
+                    Padding(
+                      padding: EdgeInsets.all(8),
+                    ),
+                    Text("edit date"),
+                  ],
+                ),
+              ),
+            );
+          }
         }
         return items;
       },
@@ -145,6 +165,8 @@ class FadingAppBarState extends State<FadingAppBar> {
           _download(widget.file);
         } else if (value == 2) {
           _showDeleteSheet(widget.file);
+        } else if(value == 3) {
+          _showDatePicker(widget.file);
         }
       },
     ));
@@ -219,6 +241,22 @@ class FadingAppBarState extends State<FadingAppBar> {
     );
   }
 
+  void _showDatePicker(File file) {
+    DatePicker.showDatePicker(context,
+        showTitleActions: true,
+        minTime: DateTime(1900, 1, 1),
+        maxTime: DateTime.now().add(Duration(days: 1)),
+        onConfirm: (date) async {
+      if (await editTime(
+          context, List.of([widget.file]), date.microsecondsSinceEpoch)) {
+        widget.file.creationTime = date.microsecondsSinceEpoch;
+        setState(() {});
+      }
+    },
+        currentTime: DateTime.fromMicrosecondsSinceEpoch(file.creationTime),
+        locale: LocaleType.en);
+  }
+
   void _showDeleteSheet(File file) {
     final List<Widget> actions = [];
     if (file.uploadedFileID == null) {

+ 1 - 1
lib/ui/fading_bottom_bar.dart

@@ -11,7 +11,7 @@ import 'package:photos/models/selected_files.dart';
 import 'package:photos/models/trash_file.dart';
 import 'package:photos/ui/create_collection_page.dart';
 import 'package:photos/ui/file_info_dialog.dart';
-import 'package:photos/utils/archive_util.dart';
+import 'package:photos/utils/magic_util.dart';
 import 'package:photos/utils/delete_file_util.dart';
 import 'package:photos/utils/share_util.dart';
 

+ 1 - 1
lib/ui/gallery_app_bar_widget.dart

@@ -15,7 +15,7 @@ import 'package:photos/services/collections_service.dart';
 import 'package:photos/ui/change_collection_name_dialog.dart';
 import 'package:photos/ui/create_collection_page.dart';
 import 'package:photos/ui/share_collection_widget.dart';
-import 'package:photos/utils/archive_util.dart';
+import 'package:photos/utils/magic_util.dart';
 import 'package:photos/utils/delete_file_util.dart';
 import 'package:photos/utils/dialog_util.dart';
 import 'package:photos/utils/share_util.dart';

+ 0 - 29
lib/utils/archive_util.dart

@@ -1,29 +0,0 @@
-import 'package:flutter/widgets.dart';
-import 'package:fluttertoast/fluttertoast.dart';
-import 'package:logging/logging.dart';
-import 'package:photos/models/file.dart';
-import 'package:photos/models/magic_metadata.dart';
-import 'package:photos/services/file_magic_service.dart';
-import 'package:photos/utils/dialog_util.dart';
-import 'package:photos/utils/toast_util.dart';
-
-Future<void> changeVisibility(
-    BuildContext context, List<File> files, int newVisibility) async {
-  final dialog = createProgressDialog(context,
-      newVisibility == kVisibilityArchive ? "archiving..." : "unarchiving...");
-  await dialog.show();
-  try {
-    await FileMagicService.instance.changeVisibility(files, newVisibility);
-    showToast(
-        newVisibility == kVisibilityArchive
-            ? "successfully archived"
-            : "successfully unarchived",
-        toastLength: Toast.LENGTH_SHORT);
-
-    await dialog.hide();
-  } catch (e, s) {
-    Logger("ArchiveUtil").severe("failed to update file visibility", e, s);
-    await dialog.hide();
-    rethrow;
-  }
-}

+ 71 - 0
lib/utils/magic_util.dart

@@ -0,0 +1,71 @@
+import 'package:flutter/widgets.dart';
+import 'package:fluttertoast/fluttertoast.dart';
+import 'package:logging/logging.dart';
+import 'package:photos/core/event_bus.dart';
+import 'package:photos/events/force_reload_home_gallery_event.dart';
+import 'package:photos/models/file.dart';
+import 'package:photos/models/magic_metadata.dart';
+import 'package:photos/services/file_magic_service.dart';
+import 'package:photos/utils/dialog_util.dart';
+import 'package:photos/utils/toast_util.dart';
+
+final _logger = Logger('MagicUtil');
+
+Future<void> changeVisibility(
+    BuildContext context, List<File> files, int newVisibility) async {
+  final dialog = createProgressDialog(context,
+      newVisibility == kVisibilityArchive ? "archiving..." : "unarchiving...");
+  await dialog.show();
+  try {
+    await FileMagicService.instance.changeVisibility(files, newVisibility);
+    showToast(
+        newVisibility == kVisibilityArchive
+            ? "successfully archived"
+            : "successfully unarchived",
+        toastLength: Toast.LENGTH_SHORT);
+
+    await dialog.hide();
+  } catch (e, s) {
+    _logger.severe("failed to update file visibility", e, s);
+    await dialog.hide();
+    rethrow;
+  }
+}
+
+Future<bool> editTime(
+    BuildContext context, List<File> files, int editedTime) async {
+  try {
+    await _updatePublicMetadata(
+        context, files, kPubMagicKeyEditedTime, editedTime);
+    return true;
+  } catch (e, s) {
+    showToast('something went wrong');
+    return false;
+  }
+}
+
+Future<void> _updatePublicMetadata(
+    BuildContext context, List<File> files, String key, dynamic value) async {
+  if (files.isEmpty) {
+    return;
+  }
+  final dialog = createProgressDialog(context, 'please wait');
+  await dialog.show();
+  try {
+    Map<String, dynamic> update = {key: value};
+    await FileMagicService.instance.updatePublicMagicMetadata(files, update);
+    showToast('done', toastLength: Toast.LENGTH_SHORT);
+    await dialog.hide();
+    if (_shouldReloadGallery(key)) {
+      Bus.instance.fire(ForceReloadHomeGalleryEvent());
+    }
+  } catch (e, s) {
+    _logger.severe("failed to update $key = $value", e, s);
+    await dialog.hide();
+    rethrow;
+  }
+}
+
+bool _shouldReloadGallery(String key) {
+  return key == kPubMagicKeyEditedTime;
+}

+ 7 - 0
pubspec.lock

@@ -316,6 +316,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "3.1.0"
+  flutter_datetime_picker:
+    dependency: "direct main"
+    description:
+      name: flutter_datetime_picker
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.5.1"
   flutter_easyloading:
     dependency: "direct main"
     description:

+ 1 - 0
pubspec.yaml

@@ -43,6 +43,7 @@ dependencies:
   flutter_localizations:
     sdk: flutter
   flutter_cache_manager: ^3.0.1
+  flutter_datetime_picker: ^1.5.1
   flutter_easyloading: ^3.0.0
   flutter_email_sender: ^5.0.2
   flutter_image_compress: