Browse Source

Merge pull request #879 from ente-io/delete_button

Memories FileViewer: Add Delete and Info button
Neeraj Gupta 2 years ago
parent
commit
add5442607

+ 13 - 0
lib/services/memories_service.dart

@@ -1,8 +1,10 @@
 import 'package:flutter/foundation.dart';
 import 'package:logging/logging.dart';
 import 'package:photos/core/constants.dart';
+import "package:photos/core/event_bus.dart";
 import 'package:photos/db/files_db.dart';
 import 'package:photos/db/memories_db.dart';
+import "package:photos/events/files_updated_event.dart";
 import 'package:photos/models/filters/important_items_filter.dart';
 import 'package:photos/models/memory.dart';
 import 'package:photos/services/collections_service.dart';
@@ -34,6 +36,17 @@ class MemoriesService extends ChangeNotifier {
         DateTime.now().microsecondsSinceEpoch - (7 * microSecondsInDay),
       );
     });
+    Bus.instance.on<FilesUpdatedEvent>().where((event) {
+      return event.type == EventType.deletedFromEverywhere;
+    }).listen((event) {
+      final generatedIDs = event.updatedFiles
+          .where((element) => element.generatedID != null)
+          .map((e) => e.generatedID!)
+          .toSet();
+      _cachedMemories?.removeWhere((element) {
+        return generatedIDs.contains(element.file.generatedID);
+      });
+    });
   }
 
   void clearCache() {

+ 143 - 0
lib/ui/actions/file/file_actions.dart

@@ -0,0 +1,143 @@
+import "package:flutter/cupertino.dart";
+import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
+import "package:photos/models/file.dart";
+import "package:photos/models/file_type.dart";
+import "package:photos/theme/colors.dart";
+import "package:photos/theme/ente_theme.dart";
+import "package:photos/ui/components/action_sheet_widget.dart";
+import "package:photos/ui/components/button_widget.dart";
+import "package:photos/ui/components/models/button_type.dart";
+import "package:photos/ui/viewer/file/file_info_widget.dart";
+import "package:photos/utils/delete_file_util.dart";
+import "package:photos/utils/dialog_util.dart";
+import "package:photos/utils/toast_util.dart";
+
+Future<void> showSingleFileDeleteSheet(
+  BuildContext context,
+  File file, {
+  Function(File)? onFileRemoved,
+}) async {
+  final List<ButtonWidget> buttons = [];
+  final String fileType = file.fileType == FileType.video ? "video" : "photo";
+  final bool isBothLocalAndRemote =
+      file.uploadedFileID != null && file.localID != null;
+  final bool isLocalOnly = file.uploadedFileID == null && file.localID != null;
+  final bool isRemoteOnly = file.uploadedFileID != null && file.localID == null;
+  const String bodyHighlight = "It will be deleted from all albums.";
+  String body = "";
+  if (isBothLocalAndRemote) {
+    body = "This $fileType is in both ente and your device.";
+  } else if (isRemoteOnly) {
+    body = "This $fileType will be deleted from ente.";
+  } else if (isLocalOnly) {
+    body = "This $fileType will be deleted from your device.";
+  } else {
+    throw AssertionError("Unexpected state");
+  }
+  // Add option to delete from ente
+  if (isBothLocalAndRemote || isRemoteOnly) {
+    buttons.add(
+      ButtonWidget(
+        labelText: isBothLocalAndRemote ? "Delete from ente" : "Yes, delete",
+        buttonType: ButtonType.neutral,
+        buttonSize: ButtonSize.large,
+        shouldStickToDarkTheme: true,
+        buttonAction: ButtonAction.first,
+        shouldSurfaceExecutionStates: true,
+        isInAlert: true,
+        onTap: () async {
+          await deleteFilesFromRemoteOnly(context, [file]);
+          showShortToast(context, "Moved to trash");
+          if (isRemoteOnly) {
+            Navigator.of(context, rootNavigator: true).pop();
+            if (onFileRemoved != null) {
+              onFileRemoved(file);
+            }
+          }
+        },
+      ),
+    );
+  }
+  // Add option to delete from local
+  if (isBothLocalAndRemote || isLocalOnly) {
+    buttons.add(
+      ButtonWidget(
+        labelText: isBothLocalAndRemote ? "Delete from device" : "Yes, delete",
+        buttonType: ButtonType.neutral,
+        buttonSize: ButtonSize.large,
+        shouldStickToDarkTheme: true,
+        buttonAction: ButtonAction.second,
+        shouldSurfaceExecutionStates: false,
+        isInAlert: true,
+        onTap: () async {
+          await deleteFilesOnDeviceOnly(context, [file]);
+          if (isLocalOnly) {
+            Navigator.of(context, rootNavigator: true).pop();
+            if (onFileRemoved != null) {
+              onFileRemoved(file);
+            }
+          }
+        },
+      ),
+    );
+  }
+  if (isBothLocalAndRemote) {
+    buttons.add(
+      ButtonWidget(
+        labelText: "Delete from both",
+        buttonType: ButtonType.neutral,
+        buttonSize: ButtonSize.large,
+        shouldStickToDarkTheme: true,
+        buttonAction: ButtonAction.third,
+        shouldSurfaceExecutionStates: true,
+        isInAlert: true,
+        onTap: () async {
+          await deleteFilesFromEverywhere(context, [file]);
+          Navigator.of(context, rootNavigator: true).pop();
+          if (onFileRemoved != null) {
+            onFileRemoved(file);
+          }
+        },
+      ),
+    );
+  }
+  buttons.add(
+    const ButtonWidget(
+      labelText: "Cancel",
+      buttonType: ButtonType.secondary,
+      buttonSize: ButtonSize.large,
+      shouldStickToDarkTheme: true,
+      buttonAction: ButtonAction.fourth,
+      isInAlert: true,
+    ),
+  );
+  final actionResult = await showActionSheet(
+    context: context,
+    buttons: buttons,
+    actionSheetType: ActionSheetType.defaultActionSheet,
+    body: body,
+    bodyHighlight: bodyHighlight,
+  );
+  if (actionResult?.action != null &&
+      actionResult!.action == ButtonAction.error) {
+    showGenericErrorDialog(context: context);
+  }
+}
+
+Future<void> showInfoSheet(BuildContext context, File file) async {
+  final colorScheme = getEnteColorScheme(context);
+  return showBarModalBottomSheet(
+    topControl: const SizedBox.shrink(),
+    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0)),
+    backgroundColor: colorScheme.backgroundElevated,
+    barrierColor: backdropFaintDark,
+    context: context,
+    builder: (BuildContext context) {
+      return Padding(
+        padding:
+            EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
+        child: FileInfoWidget(file),
+      );
+    },
+  );
+}

+ 60 - 13
lib/ui/home/memories_widget.dart

@@ -1,8 +1,11 @@
+import "dart:io";
+
+import "package:flutter/cupertino.dart";
 import 'package:flutter/material.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/models/memory.dart';
 import 'package:photos/services/memories_service.dart';
-import 'package:photos/ui/extents_page_view.dart';
+import "package:photos/ui/actions/file/file_actions.dart";
 import 'package:photos/ui/viewer/file/file_widget.dart';
 import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
 import 'package:photos/utils/date_time_util.dart';
@@ -315,6 +318,22 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
     );
   }
 
+  void onFileDeleted() {
+    if (widget.memories.length == 1) {
+      Navigator.pop(context);
+    } else {
+      setState(() {
+        if (_index != 0) {
+          _pageController?.jumpToPage(_index - 1);
+        }
+        widget.memories.removeAt(_index);
+        if (_index != 0) {
+          _index--;
+        }
+      });
+    }
+  }
+
   Hero _buildInfoText() {
     return Hero(
       tag: widget.title,
@@ -346,17 +365,46 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
 
   Widget _buildBottomIcons() {
     final file = widget.memories[_index].file;
-    return Container(
-      alignment: Alignment.bottomRight,
-      padding: const EdgeInsets.fromLTRB(0, 0, 26, 20),
-      child: IconButton(
-        icon: Icon(
-          Icons.adaptive.share,
-          color: Colors.white, //same for both themes
+    return SafeArea(
+      child: Container(
+        alignment: Alignment.bottomRight,
+        padding: const EdgeInsets.fromLTRB(26, 0, 26, 20),
+        child: Row(
+          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+          children: [
+            IconButton(
+              icon: const Icon(
+                Icons.delete_outline,
+                color: Colors.white, //same for both themes
+              ),
+              onPressed: () async {
+                await showSingleFileDeleteSheet(
+                  context,
+                  file,
+                  onFileRemoved: (file) => {onFileDeleted()},
+                );
+              },
+            ),
+            IconButton(
+              icon: Icon(
+                Platform.isAndroid ? Icons.info_outline : CupertinoIcons.info,
+                color: Colors.white, //same for both themes
+              ),
+              onPressed: () {
+                showInfoSheet(context, file);
+              },
+            ),
+            IconButton(
+              icon: Icon(
+                Icons.adaptive.share,
+                color: Colors.white, //same for both themes
+              ),
+              onPressed: () {
+                share(context, [file]);
+              },
+            ),
+          ],
         ),
-        onPressed: () {
-          share(context, [file]);
-        },
       ),
     );
   }
@@ -381,7 +429,7 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
 
   Widget _buildSwiper() {
     _pageController = PageController(initialPage: _index);
-    return ExtentsPageView.extents(
+    return PageView.builder(
       itemBuilder: (BuildContext context, int index) {
         if (index < widget.memories.length - 1) {
           final nextFile = widget.memories[index + 1].file;
@@ -405,7 +453,6 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
       },
       itemCount: widget.memories.length,
       controller: _pageController,
-      extents: 1,
       onPageChanged: (index) async {
         await MemoriesService.instance.markMemoryAsSeen(widget.memories[index]);
         if (mounted) {

+ 1 - 1
lib/ui/viewer/actions/file_selection_actions_widget.dart

@@ -51,7 +51,7 @@ class _FileSelectionActionWidgetState extends State<FileSelectionActionWidget> {
   late CollectionActions collectionActions;
   late bool isCollectionOwner;
 
-  // _cachedCollectionForSharedLink is primarly used to avoid creating duplicate
+  // _cachedCollectionForSharedLink is primarily used to avoid creating duplicate
   // links if user keeps on creating Create link button after selecting
   // few files. This link is reset on any selection changed;
   Collection? _cachedCollectionForSharedLink;

+ 5 - 106
lib/ui/viewer/file/fading_app_bar.dart

@@ -21,13 +21,10 @@ import 'package:photos/services/favorites_service.dart';
 import 'package:photos/services/hidden_service.dart';
 import 'package:photos/services/ignored_files_service.dart';
 import 'package:photos/services/local_sync_service.dart';
+import "package:photos/ui/actions/file/file_actions.dart";
 import 'package:photos/ui/common/progress_dialog.dart';
-import 'package:photos/ui/components/action_sheet_widget.dart';
-import 'package:photos/ui/components/button_widget.dart';
-import 'package:photos/ui/components/models/button_type.dart';
 import 'package:photos/ui/create_collection_sheet.dart';
 import 'package:photos/ui/viewer/file/custom_app_bar.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/toast_util.dart';
@@ -339,109 +336,11 @@ class FadingAppBarState extends State<FadingAppBar> {
   }
 
   Future<void> _showSingleFileDeleteSheet(File file) async {
-    final List<ButtonWidget> buttons = [];
-    final String fileType = file.fileType == FileType.video ? "video" : "photo";
-    final bool isBothLocalAndRemote =
-        file.uploadedFileID != null && file.localID != null;
-    final bool isLocalOnly =
-        file.uploadedFileID == null && file.localID != null;
-    final bool isRemoteOnly =
-        file.uploadedFileID != null && file.localID == null;
-    const String bodyHighlight = "It will be deleted from all albums.";
-    String body = "";
-    if (isBothLocalAndRemote) {
-      body = "This $fileType is in both ente and your device.";
-    } else if (isRemoteOnly) {
-      body = "This $fileType will be deleted from ente.";
-    } else if (isLocalOnly) {
-      body = "This $fileType will be deleted from your device.";
-    } else {
-      throw AssertionError("Unexpected state");
-    }
-    // Add option to delete from ente
-    if (isBothLocalAndRemote || isRemoteOnly) {
-      buttons.add(
-        ButtonWidget(
-          labelText: isBothLocalAndRemote ? "Delete from ente" : "Yes, delete",
-          buttonType: ButtonType.neutral,
-          buttonSize: ButtonSize.large,
-          shouldStickToDarkTheme: true,
-          buttonAction: ButtonAction.first,
-          shouldSurfaceExecutionStates: true,
-          isInAlert: true,
-          onTap: () async {
-            await deleteFilesFromRemoteOnly(context, [file]);
-            showShortToast(context, "Moved to trash");
-            if (isRemoteOnly) {
-              Navigator.of(context, rootNavigator: true).pop();
-              widget.onFileRemoved(file);
-            }
-          },
-        ),
-      );
-    }
-    // Add option to delete from local
-    if (isBothLocalAndRemote || isLocalOnly) {
-      buttons.add(
-        ButtonWidget(
-          labelText:
-              isBothLocalAndRemote ? "Delete from device" : "Yes, delete",
-          buttonType: ButtonType.neutral,
-          buttonSize: ButtonSize.large,
-          shouldStickToDarkTheme: true,
-          buttonAction: ButtonAction.second,
-          shouldSurfaceExecutionStates: false,
-          isInAlert: true,
-          onTap: () async {
-            await deleteFilesOnDeviceOnly(context, [file]);
-            if (isLocalOnly) {
-              Navigator.of(context, rootNavigator: true).pop();
-              widget.onFileRemoved(file);
-            }
-          },
-        ),
-      );
-    }
-
-    if (isBothLocalAndRemote) {
-      buttons.add(
-        ButtonWidget(
-          labelText: "Delete from both",
-          buttonType: ButtonType.neutral,
-          buttonSize: ButtonSize.large,
-          shouldStickToDarkTheme: true,
-          buttonAction: ButtonAction.third,
-          shouldSurfaceExecutionStates: true,
-          isInAlert: true,
-          onTap: () async {
-            await deleteFilesFromEverywhere(context, [file]);
-            Navigator.of(context, rootNavigator: true).pop();
-            widget.onFileRemoved(file);
-          },
-        ),
-      );
-    }
-    buttons.add(
-      const ButtonWidget(
-        labelText: "Cancel",
-        buttonType: ButtonType.secondary,
-        buttonSize: ButtonSize.large,
-        shouldStickToDarkTheme: true,
-        buttonAction: ButtonAction.fourth,
-        isInAlert: true,
-      ),
-    );
-    final actionResult = await showActionSheet(
-      context: context,
-      buttons: buttons,
-      actionSheetType: ActionSheetType.defaultActionSheet,
-      body: body,
-      bodyHighlight: bodyHighlight,
+    await showSingleFileDeleteSheet(
+      context,
+      file,
+      onFileRemoved: widget.onFileRemoved,
     );
-    if (actionResult?.action != null &&
-        actionResult!.action == ButtonAction.error) {
-      showGenericErrorDialog(context: context);
-    }
   }
 
   Future<void> _download(File file) async {

+ 2 - 17
lib/ui/viewer/file/fading_bottom_bar.dart

@@ -2,7 +2,6 @@ import 'dart:io';
 
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
-import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/models/file.dart';
 import 'package:photos/models/file_type.dart';
@@ -12,8 +11,8 @@ import 'package:photos/models/trash_file.dart';
 import 'package:photos/services/collections_service.dart';
 import 'package:photos/theme/colors.dart';
 import 'package:photos/theme/ente_theme.dart';
+import "package:photos/ui/actions/file/file_actions.dart";
 import 'package:photos/ui/create_collection_sheet.dart';
-import 'package:photos/ui/viewer/file/file_info_widget.dart';
 import 'package:photos/utils/delete_file_util.dart';
 import 'package:photos/utils/magic_util.dart';
 import 'package:photos/utils/share_util.dart';
@@ -273,20 +272,6 @@ class FadingBottomBarState extends State<FadingBottomBar> {
   }
 
   Future<void> _displayInfo(File file) async {
-    final colorScheme = getEnteColorScheme(context);
-    return showBarModalBottomSheet(
-      topControl: const SizedBox.shrink(),
-      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0)),
-      backgroundColor: colorScheme.backgroundElevated,
-      barrierColor: backdropFaintDark,
-      context: context,
-      builder: (BuildContext context) {
-        return Padding(
-          padding:
-              EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
-          child: FileInfoWidget(file),
-        );
-      },
-    );
+    await showInfoSheet(context, file);
   }
 }