Support for editing date of memory
This commit is contained in:
parent
eff885f696
commit
aa145e0217
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -6,7 +6,7 @@ const kVisibilityArchive = 1;
|
|||
|
||||
const kMagicKeyVisibility = 'visibility';
|
||||
|
||||
const kPubMagicKeyEditedTime = 'et';
|
||||
const kPubMagicKeyEditedTime = 'editedTime';
|
||||
|
||||
class MagicMetadata {
|
||||
// 0 -> visible
|
||||
|
|
|
@ -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>{};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
lib/utils/magic_util.dart
Normal file
71
lib/utils/magic_util.dart
Normal file
|
@ -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;
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue