Selaa lähdekoodia

Merge remote-tracking branch 'origin/main' into fix_potential_null_check_failure

Neeraj Gupta 2 vuotta sitten
vanhempi
commit
7a99447ad3
33 muutettua tiedostoa jossa 792 lisäystä ja 1205 poistoa
  1. 0 7
      ios/Podfile.lock
  2. 14 7
      ios/Runner.xcodeproj/project.pbxproj
  3. 2 0
      lib/models/collection.dart
  4. 8 0
      lib/models/gallery_type.dart
  5. 7 5
      lib/services/user_service.dart
  6. 1 1
      lib/ui/account/delete_account_page.dart
  7. 2 2
      lib/ui/account/password_reentry_page.dart
  8. 1 1
      lib/ui/account/verify_recovery_page.dart
  9. 28 12
      lib/ui/actions/collection/collection_sharing_actions.dart
  10. 0 76
      lib/ui/common/dialogs.dart
  11. 21 8
      lib/ui/components/action_sheet_widget.dart
  12. 104 0
      lib/ui/components/album_list_item_widget.dart
  13. 37 0
      lib/ui/components/bottom_of_title_bar_widget.dart
  14. 11 6
      lib/ui/components/button_widget.dart
  15. 73 0
      lib/ui/components/new_album_list_widget.dart
  16. 286 237
      lib/ui/create_collection_sheet.dart
  17. 18 3
      lib/ui/home_widget.dart
  18. 1 1
      lib/ui/payment/child_subscription_widget.dart
  19. 3 3
      lib/ui/payment/stripe_subscription_page.dart
  20. 8 41
      lib/ui/settings/account_section_widget.dart
  21. 54 17
      lib/ui/viewer/actions/file_selection_actions_widget.dart
  22. 27 22
      lib/ui/viewer/actions/file_selection_overlay_bar.dart
  23. 5 11
      lib/ui/viewer/file/fading_app_bar.dart
  24. 5 11
      lib/ui/viewer/file/fading_bottom_bar.dart
  25. 8 5
      lib/ui/viewer/file/no_thumbnail_widget.dart
  26. 1 1
      lib/ui/viewer/gallery/gallery_app_bar_widget.dart
  27. 0 684
      lib/ui/viewer/gallery/gallery_overlay_widget.dart
  28. 3 6
      lib/ui/viewer/gallery/trash_page.dart
  29. 19 28
      lib/utils/delete_file_util.dart
  30. 44 1
      lib/utils/dialog_util.dart
  31. 1 1
      lib/utils/email_util.dart
  32. 0 7
      pubspec.lock
  33. 0 1
      pubspec.yaml

+ 0 - 7
ios/Podfile.lock

@@ -102,9 +102,6 @@ PODS:
     - Flutter
   - in_app_purchase_storekit (0.0.1):
     - Flutter
-  - keyboard_visibility (0.5.0):
-    - Flutter
-    - Reachability
   - libwebp (1.2.3):
     - libwebp/demux (= 1.2.3)
     - libwebp/mux (= 1.2.3)
@@ -196,7 +193,6 @@ DEPENDENCIES:
   - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
   - image_editor (from `.symlinks/plugins/image_editor/ios`)
   - in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/ios`)
-  - keyboard_visibility (from `.symlinks/plugins/keyboard_visibility/ios`)
   - local_auth (from `.symlinks/plugins/local_auth/ios`)
   - media_extension (from `.symlinks/plugins/media_extension/ios`)
   - motionphoto (from `.symlinks/plugins/motionphoto/ios`)
@@ -275,8 +271,6 @@ EXTERNAL SOURCES:
     :path: ".symlinks/plugins/image_editor/ios"
   in_app_purchase_storekit:
     :path: ".symlinks/plugins/in_app_purchase_storekit/ios"
-  keyboard_visibility:
-    :path: ".symlinks/plugins/keyboard_visibility/ios"
   local_auth:
     :path: ".symlinks/plugins/local_auth/ios"
   media_extension:
@@ -342,7 +336,6 @@ SPEC CHECKSUMS:
   GoogleUtilities: 1d20a6ad97ef46f67bbdec158ce00563a671ebb7
   image_editor: eab82a302a6623a866da5145b7c4c0ee8a4ffbb4
   in_app_purchase_storekit: d7fcf4646136ec258e237872755da8ea6c1b6096
-  keyboard_visibility: 96a24de806fe6823c3ad956c01ba2ec6d056616f
   libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c
   local_auth: 1740f55d7af0a2e2a8684ce225fe79d8931e808c
   Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d

+ 14 - 7
ios/Runner.xcodeproj/project.pbxproj

@@ -16,7 +16,7 @@
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
 		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
-		DA6BE5E826B3BC8600656280 /* (null) in Resources */ = {isa = PBXBuildFile; };
+		DA6BE5E826B3BC8600656280 /* BuildFile in Resources */ = {isa = PBXBuildFile; };
 /* End PBXBuildFile section */
 
 /* Begin PBXCopyFilesBuildPhase section */
@@ -213,7 +213,7 @@
 				3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
 				97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
 				97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
-				DA6BE5E826B3BC8600656280 /* (null) in Resources */,
+				DA6BE5E826B3BC8600656280 /* BuildFile in Resources */,
 				277218A0270F596900FFE3CC /* GoogleService-Info.plist in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -287,7 +287,6 @@
 				"${BUILT_PRODUCTS_DIR}/fluttertoast/fluttertoast.framework",
 				"${BUILT_PRODUCTS_DIR}/image_editor/image_editor.framework",
 				"${BUILT_PRODUCTS_DIR}/in_app_purchase_storekit/in_app_purchase_storekit.framework",
-				"${BUILT_PRODUCTS_DIR}/keyboard_visibility/keyboard_visibility.framework",
 				"${BUILT_PRODUCTS_DIR}/libwebp/libwebp.framework",
 				"${BUILT_PRODUCTS_DIR}/local_auth/local_auth.framework",
 				"${BUILT_PRODUCTS_DIR}/media_extension/media_extension.framework",
@@ -342,7 +341,6 @@
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fluttertoast.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_editor.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/in_app_purchase_storekit.framework",
-				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/keyboard_visibility.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libwebp.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/local_auth.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_extension.framework",
@@ -501,7 +499,10 @@
 					"$(PROJECT_DIR)/Flutter",
 				);
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
@@ -658,7 +659,10 @@
 					"$(PROJECT_DIR)/Flutter",
 				);
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
@@ -692,7 +696,10 @@
 					"$(PROJECT_DIR)/Flutter",
 				);
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",

+ 2 - 0
lib/models/collection.dart

@@ -88,6 +88,8 @@ class Collection {
     return (owner?.id ?? 0) == userID;
   }
 
+  String get collectionName => name ?? "Unnamed collection";
+
   void updateSharees(List<User> newSharees) {
     sharees?.clear();
     sharees?.addAll(newSharees);

+ 8 - 0
lib/models/gallery_type.dart

@@ -184,4 +184,12 @@ extension GalleyTypeExtension on GalleryType {
   bool showUnFavoriteOption() {
     return this == GalleryType.favorite;
   }
+
+  bool showRestoreOption() {
+    return this == GalleryType.trash;
+  }
+
+  bool showPermanentlyDeleteOption() {
+    return this == GalleryType.trash;
+  }
 }

+ 7 - 5
lib/services/user_service.dart

@@ -197,21 +197,23 @@ class UserService {
   }
 
   Future<void> logout(BuildContext context) async {
-    final dialog = createProgressDialog(context, "Logging out...");
-    await dialog.show();
     try {
       final response = await _enteDio.post("/users/logout");
       if (response.statusCode == 200) {
         await Configuration.instance.logout();
-        await dialog.hide();
         Navigator.of(context).popUntil((route) => route.isFirst);
       } else {
         throw Exception("Log out action failed");
       }
     } catch (e) {
       _logger.severe(e);
-      await dialog.hide();
-      showGenericErrorDialog(context: context);
+      //This future is for waiting for the dialog from which logout() is called
+      //to close and only then to show the error dialog.
+      Future.delayed(
+        const Duration(milliseconds: 150),
+        () => showGenericErrorDialog(context: context),
+      );
+      rethrow;
     }
   }
 

+ 1 - 1
lib/ui/account/delete_account_page.dart

@@ -131,7 +131,7 @@ class DeleteAccountPage extends StatelessWidget {
     );
 
     if (hasAuthenticated) {
-      final choice = await showNewChoiceDialog(
+      final choice = await showChoiceDialog(
         context,
         title: 'Are you sure you want to delete your account?',
         body:

+ 2 - 2
lib/ui/account/password_reentry_page.dart

@@ -80,7 +80,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
           } on KeyDerivationError catch (e, s) {
             _logger.severe("Password verification failed", e, s);
             await dialog.hide();
-            final dialogChoice = await showNewChoiceDialog(
+            final dialogChoice = await showChoiceDialog(
               context,
               title: "Recreate password",
               body: "The current device is not powerful enough to verify your "
@@ -102,7 +102,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
           } catch (e, s) {
             _logger.severe("Password verification failed", e, s);
             await dialog.hide();
-            final dialogChoice = await showNewChoiceDialog(
+            final dialogChoice = await showChoiceDialog(
               context,
               title: "Incorrect password",
               body: "Please try again",

+ 1 - 1
lib/ui/account/verify_recovery_page.dart

@@ -74,7 +74,7 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
           "The recovery key you entered is not valid. Please make sure it "
           "contains 24 words, and check the spelling of each.\n\nIf you "
           "entered an older recovery code, make sure it is 64 characters long, and check each of them.";
-      final result = await showNewChoiceDialog(
+      final result = await showChoiceDialog(
         context,
         title: "Invalid key",
         body: errMessage,

+ 28 - 12
lib/ui/actions/collection/collection_sharing_actions.dart

@@ -11,6 +11,8 @@ import 'package:photos/models/magic_metadata.dart';
 import 'package:photos/services/collections_service.dart';
 import 'package:photos/services/hidden_service.dart';
 import 'package:photos/services/user_service.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';
@@ -50,13 +52,13 @@ class CollectionActions {
           ),
           const ButtonWidget(
             buttonType: ButtonType.secondary,
-            buttonAction: ButtonAction.second,
+            buttonAction: ButtonAction.cancel,
             isInAlert: true,
             shouldStickToDarkTheme: true,
             labelText: "Cancel",
           )
         ],
-        title: "Remove public link?",
+        title: "Remove public link",
         body:
             'This will remove the public link for accessing "${collection.name}".',
       );
@@ -64,19 +66,18 @@ class CollectionActions {
         if (result == ButtonAction.error) {
           showGenericErrorDialog(context: context);
         }
-        // return
         return result == ButtonAction.first;
+      } else {
+        return false;
       }
     }
     final dialog = createProgressDialog(
       context,
-      enable ? "Creating link..." : "Disabling link...",
+      "Creating link...",
     );
     try {
       await dialog.show();
-      enable
-          ? await CollectionsService.instance.createShareUrl(collection)
-          : await CollectionsService.instance.disableShareUrl(collection);
+      await CollectionsService.instance.createShareUrl(collection);
       dialog.hide();
       return true;
     } catch (e) {
@@ -141,7 +142,7 @@ class CollectionActions {
     Collection collection,
     User user,
   ) async {
-    final result = await showNewChoiceDialog(
+    final result = await showChoiceDialog(
       context,
       title: "Remove",
       body: "${user.email} will be removed",
@@ -270,6 +271,7 @@ class CollectionActions {
     BuildContext bContext,
     Collection collection,
   ) async {
+    final textTheme = getEnteTextTheme(bContext);
     final currentUserID = Configuration.instance.getUserID()!;
     if (collection.owner!.id != currentUserID) {
       throw AssertionError("Can not delete album owned by others");
@@ -322,10 +324,24 @@ class CollectionActions {
           isInAlert: true,
         ),
       ],
-      title: "Delete album?",
-      body: "This album will be deleted. Do you also want to delete the "
-          "photos (and videos) that are present in this album?",
-      bodyHighlight: "They will be deleted from all albums.",
+      bodyWidget: RichText(
+        text: TextSpan(
+          style: textTheme.body.copyWith(color: textMutedDark),
+          children: <TextSpan>[
+            const TextSpan(
+              text: 'Also delete the photos (and videos) present in this '
+                  'album from ',
+            ),
+            TextSpan(
+              text: 'all',
+              style: textTheme.body.copyWith(color: textBaseDark),
+            ),
+            const TextSpan(
+              text: ' other albums they are part of?',
+            ),
+          ],
+        ),
+      ),
       actionSheetType: ActionSheetType.defaultActionSheet,
     );
     if (actionResult != null && actionResult == ButtonAction.error) {

+ 0 - 76
lib/ui/common/dialogs.dart

@@ -1,76 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:photos/ente_theme_data.dart';
-
-enum DialogUserChoice { firstChoice, secondChoice }
-
-enum ActionType {
-  confirm,
-  critical,
-}
-
-// if dialog is dismissed by tapping outside, this will return null
-Future<DialogUserChoice?> showChoiceDialog<T>(
-  BuildContext context,
-  String title,
-  String content, {
-  String firstAction = 'Ok',
-  Color? firstActionColor,
-  String secondAction = 'Cancel',
-  Color? secondActionColor,
-  ActionType actionType = ActionType.confirm,
-}) {
-  final AlertDialog alert = AlertDialog(
-    title: Text(
-      title,
-      style: TextStyle(
-        color: actionType == ActionType.critical
-            ? Colors.red
-            : Theme.of(context).colorScheme.primary,
-      ),
-    ),
-    content: Text(
-      content,
-      style: const TextStyle(
-        height: 1.4,
-      ),
-    ),
-    actions: [
-      TextButton(
-        child: Text(
-          firstAction,
-          style: TextStyle(
-            color: firstActionColor ??
-                (actionType == ActionType.critical
-                    ? Colors.red
-                    : Theme.of(context).colorScheme.onSurface),
-          ),
-        ),
-        onPressed: () {
-          Navigator.of(context, rootNavigator: true)
-              .pop(DialogUserChoice.firstChoice);
-        },
-      ),
-      TextButton(
-        child: Text(
-          secondAction,
-          style: TextStyle(
-            color: secondActionColor ??
-                Theme.of(context).colorScheme.greenAlternative,
-          ),
-        ),
-        onPressed: () {
-          Navigator.of(context, rootNavigator: true)
-              .pop(DialogUserChoice.secondChoice);
-        },
-      ),
-    ],
-  );
-
-  return showDialog(
-    context: context,
-    builder: (BuildContext context) {
-      return alert;
-    },
-    barrierColor: Colors.black87,
-  );
-}

+ 21 - 8
lib/ui/components/action_sheet_widget.dart

@@ -23,6 +23,7 @@ Future<ButtonAction?> showActionSheet({
   bool isDismissible = true,
   bool isCheckIconGreen = false,
   String? title,
+  Widget? bodyWidget,
   String? body,
   String? bodyHighlight,
 }) {
@@ -36,6 +37,7 @@ Future<ButtonAction?> showActionSheet({
     builder: (_) {
       return ActionSheetWidget(
         title: title,
+        bodyWidget: bodyWidget,
         body: body,
         bodyHighlight: bodyHighlight,
         actionButtons: buttons,
@@ -48,6 +50,7 @@ Future<ButtonAction?> showActionSheet({
 
 class ActionSheetWidget extends StatelessWidget {
   final String? title;
+  final Widget? bodyWidget;
   final String? body;
   final String? bodyHighlight;
   final List<ButtonWidget> actionButtons;
@@ -59,6 +62,7 @@ class ActionSheetWidget extends StatelessWidget {
     required this.actionSheetType,
     required this.isCheckIconGreen,
     this.title,
+    this.bodyWidget,
     this.body,
     this.bodyHighlight,
     super.key,
@@ -66,7 +70,8 @@ class ActionSheetWidget extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    final isTitleAndBodyNull = title == null && body == null;
+    final isTitleAndBodyNull =
+        title == null && bodyWidget == null && body == null;
     final blur = MediaQuery.of(context).platformBrightness == Brightness.light
         ? blurMuted
         : blurBase;
@@ -103,6 +108,7 @@ class ActionSheetWidget extends StatelessWidget {
                             padding: const EdgeInsets.only(bottom: 28),
                             child: ContentContainerWidget(
                               title: title,
+                              bodyWidget: bodyWidget,
                               body: body,
                               bodyHighlight: bodyHighlight,
                               actionSheetType: actionSheetType,
@@ -125,14 +131,17 @@ class ActionSheetWidget extends StatelessWidget {
 
 class ContentContainerWidget extends StatelessWidget {
   final String? title;
+  final Widget? bodyWidget;
   final String? body;
   final String? bodyHighlight;
   final ActionSheetType actionSheetType;
   final bool isCheckIconGreen;
+
   const ContentContainerWidget({
     required this.actionSheetType,
     required this.isCheckIconGreen,
     this.title,
+    this.bodyWidget,
     this.body,
     this.bodyHighlight,
     super.key,
@@ -141,6 +150,8 @@ class ContentContainerWidget extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     final textTheme = getEnteTextTheme(context);
+    final bool bodyMissing = body == null && bodyWidget == null;
+    debugPrint("body missing $bodyMissing");
     return Column(
       mainAxisSize: MainAxisSize.min,
       //todo: set cross axis to center when icon should be shown in place of body
@@ -155,17 +166,19 @@ class ContentContainerWidget extends StatelessWidget {
                 style: textTheme.largeBold
                     .copyWith(color: textBaseDark), //constant color
               ),
-        title == null || body == null
+        title == null || bodyMissing
             ? const SizedBox.shrink()
             : const SizedBox(height: 19),
         actionSheetType == ActionSheetType.defaultActionSheet
-            ? body == null
+            ? bodyMissing
                 ? const SizedBox.shrink()
-                : Text(
-                    body!,
-                    style: textTheme.body
-                        .copyWith(color: textMutedDark), //constant color
-                  )
+                : (bodyWidget != null
+                    ? bodyWidget!
+                    : Text(
+                        body!,
+                        style: textTheme.body
+                            .copyWith(color: textMutedDark), //constant color
+                      ))
             : Icon(
                 Icons.check_outlined,
                 size: 48,

+ 104 - 0
lib/ui/components/album_list_item_widget.dart

@@ -0,0 +1,104 @@
+import 'package:flutter/material.dart';
+import 'package:logging/logging.dart';
+import 'package:photos/db/files_db.dart';
+import 'package:photos/models/collection_items.dart';
+import 'package:photos/theme/ente_theme.dart';
+import 'package:photos/ui/viewer/file/no_thumbnail_widget.dart';
+import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
+
+///https://www.figma.com/file/SYtMyLBs5SAOkTbfMMzhqt/ente-Visual-Design?node-id=7480%3A33462&t=H5AvR79OYDnB9ekw-4
+class AlbumListItemWidget extends StatelessWidget {
+  final CollectionWithThumbnail item;
+  const AlbumListItemWidget(
+    this.item, {
+    super.key,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    final textTheme = getEnteTextTheme(context);
+    final colorScheme = getEnteColorScheme(context);
+    const sideOfThumbnail = 60.0;
+    return LayoutBuilder(
+      builder: (context, constraints) {
+        return Stack(
+          alignment: Alignment.center,
+          children: [
+            Row(
+              children: [
+                ClipRRect(
+                  borderRadius: const BorderRadius.horizontal(
+                    left: Radius.circular(4),
+                  ),
+                  child: SizedBox(
+                    height: sideOfThumbnail,
+                    width: sideOfThumbnail,
+                    child: item.thumbnail != null
+                        ? ThumbnailWidget(
+                            item.thumbnail,
+                            showFavForAlbumOnly: true,
+                          )
+                        : const NoThumbnailWidget(
+                            addBorder: false,
+                          ),
+                  ),
+                ),
+                Padding(
+                  padding: const EdgeInsets.only(left: 12),
+                  child: Column(
+                    crossAxisAlignment: CrossAxisAlignment.start,
+                    children: [
+                      Text(item.collection.collectionName),
+                      FutureBuilder<int>(
+                        future: FilesDB.instance.collectionFileCount(
+                          item.collection.id,
+                        ),
+                        builder: (context, snapshot) {
+                          if (snapshot.hasData) {
+                            final text =
+                                snapshot.data == 1 ? " memory" : " memories";
+                            return Text(
+                              snapshot.data.toString() + text,
+                              style: textTheme.small.copyWith(
+                                color: colorScheme.textMuted,
+                              ),
+                            );
+                          } else {
+                            if (snapshot.hasError) {
+                              Logger("AlbumListItemWidget").severe(
+                                "Failed to fetch file count of collection",
+                                snapshot.error,
+                              );
+                            }
+                            return Text(
+                              "",
+                              style: textTheme.small.copyWith(
+                                color: colorScheme.textMuted,
+                              ),
+                            );
+                          }
+                        },
+                      )
+                    ],
+                  ),
+                ),
+              ],
+            ),
+            IgnorePointer(
+              child: Container(
+                decoration: BoxDecoration(
+                  borderRadius: const BorderRadius.all(Radius.circular(4)),
+                  border: Border.all(
+                    color: colorScheme.strokeFainter,
+                  ),
+                ),
+                height: sideOfThumbnail,
+                width: constraints.maxWidth,
+              ),
+            ),
+          ],
+        );
+      },
+    );
+  }
+}

+ 37 - 0
lib/ui/components/bottom_of_title_bar_widget.dart

@@ -0,0 +1,37 @@
+import 'package:flutter/material.dart';
+import 'package:photos/theme/ente_theme.dart';
+import 'package:photos/ui/components/title_bar_title_widget.dart';
+
+///https://www.figma.com/file/SYtMyLBs5SAOkTbfMMzhqt/ente-Visual-Design?node-id=7309%3A29088&t=ReeZ2Big8xSsemZb-4
+class BottomOfTitleBarWidget extends StatelessWidget {
+  final TitleBarTitleWidget? title;
+  final String? caption;
+  const BottomOfTitleBarWidget({this.title, this.caption, super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return Row(
+      children: [
+        Flexible(
+          child: Padding(
+            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
+            child: Column(
+              crossAxisAlignment: CrossAxisAlignment.start,
+              children: [
+                title ?? const SizedBox.shrink(),
+                caption != null
+                    ? Text(
+                        caption!,
+                        style: getEnteTextTheme(context).small.copyWith(
+                              color: getEnteColorScheme(context).textMuted,
+                            ),
+                      )
+                    : const SizedBox.shrink(),
+              ],
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+}

+ 11 - 6
lib/ui/components/button_widget.dart

@@ -443,8 +443,7 @@ class _ButtonChildWidgetState extends State<ButtonChildWidget> {
                       : 0,
                 ), () {
               widget.isInAlert
-                  ? Navigator.of(context, rootNavigator: true)
-                      .pop(widget.buttonAction)
+                  ? _popWithButtonAction(context, widget.buttonAction)
                   : null;
               if (mounted) {
                 setState(() {
@@ -461,9 +460,7 @@ class _ButtonChildWidgetState extends State<ButtonChildWidget> {
           widget.isInAlert
               ? Future.delayed(
                   const Duration(seconds: 0),
-                  () => Navigator.of(context, rootNavigator: true).pop(
-                    ButtonAction.error,
-                  ),
+                  () => _popWithButtonAction(context, ButtonAction.error),
                 )
               : null;
         });
@@ -472,12 +469,20 @@ class _ButtonChildWidgetState extends State<ButtonChildWidget> {
       if (widget.isInAlert) {
         Future.delayed(
           Duration(seconds: widget.shouldShowSuccessConfirmation ? 1 : 0),
-          () => Navigator.of(context).pop(widget.buttonAction),
+          () => _popWithButtonAction(context, widget.buttonAction),
         );
       }
     }
   }
 
+  void _popWithButtonAction(BuildContext context, ButtonAction? buttonAction) {
+    Navigator.of(context).canPop()
+        ? Navigator.of(context).pop(
+            buttonAction,
+          )
+        : null;
+  }
+
   void _onTapDown(details) {
     setState(() {
       buttonColor = widget.buttonStyle.pressedButtonColor ??

+ 73 - 0
lib/ui/components/new_album_list_widget.dart

@@ -0,0 +1,73 @@
+import 'package:dotted_border/dotted_border.dart';
+import 'package:flutter/material.dart';
+import 'package:photos/theme/ente_theme.dart';
+
+///https://www.figma.com/file/SYtMyLBs5SAOkTbfMMzhqt/ente-Visual-Design?node-id=10854%3A57947&t=H5AvR79OYDnB9ekw-4
+class NewAlbumListItemWidget extends StatelessWidget {
+  const NewAlbumListItemWidget({
+    super.key,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    final textTheme = getEnteTextTheme(context);
+    final colorScheme = getEnteColorScheme(context);
+    const sideOfThumbnail = 60.0;
+    return LayoutBuilder(
+      builder: (context, constraints) {
+        return Stack(
+          alignment: Alignment.center,
+          children: [
+            Row(
+              children: [
+                ClipRRect(
+                  borderRadius: const BorderRadius.horizontal(
+                    left: Radius.circular(4),
+                  ),
+                  child: SizedBox(
+                    height: sideOfThumbnail,
+                    width: sideOfThumbnail,
+                    child: Icon(
+                      Icons.add_outlined,
+                      color: colorScheme.strokeMuted,
+                    ),
+                  ),
+                ),
+                Padding(
+                  padding: const EdgeInsets.only(left: 12),
+                  child: Text(
+                    "New album",
+                    style:
+                        textTheme.body.copyWith(color: colorScheme.textMuted),
+                  ),
+                ),
+              ],
+            ),
+            IgnorePointer(
+              child: DottedBorder(
+                dashPattern: const [4],
+                color: colorScheme.strokeFainter,
+                strokeWidth: 1,
+                padding: const EdgeInsets.all(0),
+                borderType: BorderType.RRect,
+                radius: const Radius.circular(4),
+                child: SizedBox(
+                  //Have to decrease the height and width by 1 pt as the stroke
+                  //dotted border gives is of strokeAlign.center, so 0.5 inside and
+                  // outside. Here for the row, stroke should be inside so we
+                  //decrease the size of this sizedBox by 1 (so it shrinks 0.5 from
+                  //every side) so that the strokeAlign.center of this sizedBox
+                  //looks like a strokeAlign.inside in the row.
+                  height: sideOfThumbnail - 1,
+                  //This width will work for this only if the row widget takes up the
+                  //full size it's parent (stack).
+                  width: constraints.maxWidth - 1,
+                ),
+              ),
+            ),
+          ],
+        );
+      },
+    );
+  }
+}

+ 286 - 237
lib/ui/create_collection_page.dart → lib/ui/create_collection_sheet.dart

@@ -1,8 +1,10 @@
+import 'dart:math';
+
 import 'package:collection/collection.dart';
 import 'package:flutter/material.dart';
 import 'package:logging/logging.dart';
+import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
 import 'package:photos/db/files_db.dart';
-import 'package:photos/ente_theme_data.dart';
 import 'package:photos/models/collection.dart';
 import 'package:photos/models/collection_items.dart';
 import 'package:photos/models/file.dart';
@@ -10,10 +12,15 @@ import 'package:photos/models/selected_files.dart';
 import 'package:photos/services/collections_service.dart';
 import 'package:photos/services/ignored_files_service.dart';
 import 'package:photos/services/remote_sync_service.dart';
-import 'package:photos/ui/common/gradient_button.dart';
+import 'package:photos/theme/colors.dart';
+import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/common/loading_widget.dart';
-import 'package:photos/ui/viewer/file/no_thumbnail_widget.dart';
-import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
+import 'package:photos/ui/components/album_list_item_widget.dart';
+import 'package:photos/ui/components/bottom_of_title_bar_widget.dart';
+import 'package:photos/ui/components/button_widget.dart';
+import 'package:photos/ui/components/models/button_type.dart';
+import 'package:photos/ui/components/new_album_list_widget.dart';
+import 'package:photos/ui/components/title_bar_title_widget.dart';
 import 'package:photos/ui/viewer/gallery/collection_page.dart';
 import 'package:photos/utils/dialog_util.dart';
 import 'package:photos/utils/navigation_util.dart';
@@ -24,197 +31,201 @@ import 'package:receive_sharing_intent/receive_sharing_intent.dart';
 enum CollectionActionType { addFiles, moveFiles, restoreFiles, unHide }
 
 String _actionName(CollectionActionType type, bool plural) {
+  bool addTitleSuffix = false;
   final titleSuffix = (plural ? "s" : "");
   String text = "";
   switch (type) {
     case CollectionActionType.addFiles:
-      text = "Add file";
+      text = "Add item";
+      addTitleSuffix = true;
       break;
     case CollectionActionType.moveFiles:
-      text = "Move file";
+      text = "Move item";
+      addTitleSuffix = true;
       break;
     case CollectionActionType.restoreFiles:
-      text = "Restore file";
+      text = "Restore to album";
       break;
     case CollectionActionType.unHide:
-      text = "Unhide file";
+      text = "Unhide to album";
       break;
   }
-  return text + titleSuffix;
+  return addTitleSuffix ? text + titleSuffix : text;
 }
 
-class CreateCollectionPage extends StatefulWidget {
+void createCollectionSheet(
+  SelectedFiles? selectedFiles,
+  List<SharedMediaFile>? sharedFiles,
+  BuildContext context, {
+  CollectionActionType actionType = CollectionActionType.addFiles,
+  bool showOptionToCreateNewAlbum = true,
+}) {
+  showBarModalBottomSheet(
+    context: context,
+    builder: (context) {
+      return CreateCollectionSheet(
+        selectedFiles: selectedFiles,
+        sharedFiles: sharedFiles,
+        actionType: actionType,
+        showOptionToCreateNewAlbum: showOptionToCreateNewAlbum,
+      );
+    },
+    shape: const RoundedRectangleBorder(
+      side: BorderSide(width: 0),
+      borderRadius: BorderRadius.vertical(
+        top: Radius.circular(5),
+      ),
+    ),
+    topControl: const SizedBox.shrink(),
+    backgroundColor: getEnteColorScheme(context).backgroundElevated,
+    barrierColor: backdropFaintDark,
+    enableDrag: false,
+  );
+}
+
+class CreateCollectionSheet extends StatefulWidget {
   final SelectedFiles? selectedFiles;
   final List<SharedMediaFile>? sharedFiles;
   final CollectionActionType actionType;
-
-  const CreateCollectionPage(
-    this.selectedFiles,
-    this.sharedFiles, {
-    Key? key,
-    this.actionType = CollectionActionType.addFiles,
-  }) : super(key: key);
+  final bool showOptionToCreateNewAlbum;
+  const CreateCollectionSheet({
+    required this.selectedFiles,
+    required this.sharedFiles,
+    required this.actionType,
+    required this.showOptionToCreateNewAlbum,
+    super.key,
+  });
 
   @override
-  State<CreateCollectionPage> createState() => _CreateCollectionPageState();
+  State<CreateCollectionSheet> createState() => _CreateCollectionSheetState();
 }
 
-class _CreateCollectionPageState extends State<CreateCollectionPage> {
-  final _logger = Logger((_CreateCollectionPageState).toString());
-  late String _albumName;
+class _CreateCollectionSheetState extends State<CreateCollectionSheet> {
+  final _logger = Logger((_CreateCollectionSheetState).toString());
 
   @override
   Widget build(BuildContext context) {
     final filesCount = widget.sharedFiles != null
         ? widget.sharedFiles!.length
         : widget.selectedFiles!.files.length;
-    return Scaffold(
-      appBar: AppBar(
-        title: Text(_actionName(widget.actionType, filesCount > 1)),
-      ),
-      body: _getBody(context),
-    );
-  }
-
-  Widget _getBody(BuildContext context) {
-    return SingleChildScrollView(
-      child: Column(
-        mainAxisSize: MainAxisSize.min,
-        children: [
-          Row(
-            children: [
-              Expanded(
-                child: Padding(
-                  padding: const EdgeInsets.only(
-                    top: 30,
-                    bottom: 12,
-                    left: 40,
-                    right: 40,
-                  ),
-                  child: GradientButton(
-                    onTap: () async {
-                      _showNameAlbumDialog();
-                    },
-                    iconData: Icons.create_new_folder_outlined,
-                    text: "To a new album",
-                  ),
-                ),
-              ),
-            ],
+    return Row(
+      mainAxisAlignment: MainAxisAlignment.center,
+      children: [
+        ConstrainedBox(
+          constraints: BoxConstraints(
+            maxWidth: min(428, MediaQuery.of(context).size.width),
           ),
-          const Padding(
-            padding: EdgeInsets.fromLTRB(40, 24, 40, 20),
-            child: Align(
-              alignment: Alignment.centerLeft,
-              child: Text(
-                "To an existing album",
-                style: TextStyle(
-                  fontWeight: FontWeight.bold,
+          child: Padding(
+            padding: const EdgeInsets.fromLTRB(0, 32, 0, 8),
+            child: Column(
+              mainAxisSize: MainAxisSize.min,
+              children: [
+                BottomOfTitleBarWidget(
+                  title: TitleBarTitleWidget(
+                    title: _actionName(widget.actionType, filesCount > 1),
+                  ),
+                  caption: "Create or select album",
                 ),
-              ),
-            ),
-          ),
-          Padding(
-            padding: const EdgeInsets.fromLTRB(20, 4, 20, 0),
-            child: _getExistingCollectionsWidget(),
-          ),
-        ],
-      ),
-    );
-  }
-
-  Widget _getExistingCollectionsWidget() {
-    return FutureBuilder<List<CollectionWithThumbnail>>(
-      future: _getCollectionsWithThumbnail(),
-      builder: (context, snapshot) {
-        if (snapshot.hasError) {
-          return Text(snapshot.error.toString());
-        } else if (snapshot.hasData) {
-          return ListView.builder(
-            itemBuilder: (context, index) {
-              return _buildCollectionItem(snapshot.data![index]);
-            },
-            itemCount: snapshot.data!.length,
-            shrinkWrap: true,
-            physics: const NeverScrollableScrollPhysics(),
-          );
-        } else {
-          return const EnteLoadingWidget();
-        }
-      },
-    );
-  }
-
-  Widget _buildCollectionItem(CollectionWithThumbnail item) {
-    return Container(
-      padding: const EdgeInsets.only(left: 24, bottom: 16),
-      child: GestureDetector(
-        behavior: HitTestBehavior.translucent,
-        child: Row(
-          children: <Widget>[
-            ClipRRect(
-              borderRadius: BorderRadius.circular(2.0),
-              child: SizedBox(
-                height: 64,
-                width: 64,
-                key: Key("collection_item:" + (item.thumbnail?.tag ?? "")),
-                child: item.thumbnail != null
-                    ? ThumbnailWidget(
-                        item.thumbnail,
-                        showFavForAlbumOnly: true,
+                Flexible(
+                  child: Column(
+                    mainAxisSize: MainAxisSize.min,
+                    children: [
+                      Flexible(
+                        child: Padding(
+                          padding: const EdgeInsets.fromLTRB(16, 24, 4, 0),
+                          child: Scrollbar(
+                            radius: const Radius.circular(2),
+                            child: Padding(
+                              padding: const EdgeInsets.only(right: 12),
+                              child: FutureBuilder(
+                                future: _getCollectionsWithThumbnail(),
+                                builder: (context, snapshot) {
+                                  if (snapshot.hasError) {
+                                    //Need to show an error on the UI here
+                                    return const SizedBox.shrink();
+                                  } else if (snapshot.hasData) {
+                                    final collectionsWithThumbnail = snapshot
+                                        .data as List<CollectionWithThumbnail>;
+                                    return ListView.separated(
+                                      itemBuilder: (context, index) {
+                                        if (index == 0 &&
+                                            widget.showOptionToCreateNewAlbum) {
+                                          return GestureDetector(
+                                            onTap: () {
+                                              _showNameAlbumDialog();
+                                            },
+                                            behavior: HitTestBehavior.opaque,
+                                            child:
+                                                const NewAlbumListItemWidget(),
+                                          );
+                                        }
+                                        final item = collectionsWithThumbnail[
+                                            index -
+                                                (widget.showOptionToCreateNewAlbum
+                                                    ? 1
+                                                    : 0)];
+                                        return GestureDetector(
+                                          behavior: HitTestBehavior.opaque,
+                                          onTap: () =>
+                                              _albumListItemOnTap(item),
+                                          child: AlbumListItemWidget(
+                                            item,
+                                          ),
+                                        );
+                                      },
+                                      separatorBuilder: (context, index) =>
+                                          const SizedBox(
+                                        height: 8,
+                                      ),
+                                      itemCount:
+                                          collectionsWithThumbnail.length +
+                                              (widget.showOptionToCreateNewAlbum
+                                                  ? 1
+                                                  : 0),
+                                      shrinkWrap: true,
+                                      physics: const BouncingScrollPhysics(),
+                                    );
+                                  } else {
+                                    return const EnteLoadingWidget();
+                                  }
+                                },
+                              ),
+                            ),
+                          ),
+                        ),
+                      ),
+                      SafeArea(
+                        child: Container(
+                          //inner stroke of 1pt + 15 pts of top padding = 16 pts
+                          padding: const EdgeInsets.fromLTRB(16, 15, 16, 8),
+                          decoration: BoxDecoration(
+                            border: Border(
+                              top: BorderSide(
+                                color: getEnteColorScheme(context).strokeFaint,
+                              ),
+                            ),
+                          ),
+                          child: const ButtonWidget(
+                            buttonType: ButtonType.secondary,
+                            buttonAction: ButtonAction.cancel,
+                            isInAlert: true,
+                            labelText: "Cancel",
+                          ),
+                        ),
                       )
-                    : const NoThumbnailWidget(),
-              ),
-            ),
-            const Padding(padding: EdgeInsets.all(8)),
-            Expanded(
-              child: Text(
-                item.collection.name!,
-                style: const TextStyle(
-                  fontSize: 16,
+                    ],
+                  ),
                 ),
-              ),
+              ],
             ),
-          ],
+          ),
         ),
-        onTap: () async {
-          if (await _runCollectionAction(item.collection.id)) {
-            showShortToast(
-              context,
-              widget.actionType == CollectionActionType.addFiles
-                  ? "Added successfully to " + item.collection.name!
-                  : "Moved successfully to " + item.collection.name!,
-            );
-            _navigateToCollection(item.collection);
-          }
-        },
-      ),
+      ],
     );
   }
 
-  Future<List<CollectionWithThumbnail>> _getCollectionsWithThumbnail() async {
-    final List<CollectionWithThumbnail> collectionsWithThumbnail =
-        await CollectionsService.instance.getCollectionsWithThumbnails(
-      // in collections where user is a collaborator, only addTo and remove
-      // action can to be performed
-      includeCollabCollections:
-          widget.actionType == CollectionActionType.addFiles,
-    );
-    collectionsWithThumbnail.removeWhere(
-      (element) => (element.collection.type == CollectionType.favorites ||
-          element.collection.type == CollectionType.uncategorized ||
-          element.collection.isSharedFilesCollection()),
-    );
-    collectionsWithThumbnail.sort((first, second) {
-      return compareAsciiLowerCaseNatural(
-        first.collection.name ?? "",
-        second.collection.name ?? "",
-      );
-    });
-    return collectionsWithThumbnail;
-  }
-
   void _showNameAlbumDialog() async {
+    String? albumName;
     final AlertDialog alert = AlertDialog(
       title: const Text("Album title"),
       content: TextFormField(
@@ -223,9 +234,7 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
           contentPadding: EdgeInsets.all(8),
         ),
         onChanged: (value) {
-          setState(() {
-            _albumName = value;
-          });
+          albumName = value;
         },
         autofocus: true,
         keyboardType: TextInputType.text,
@@ -236,26 +245,28 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
           child: Text(
             "Ok",
             style: TextStyle(
-              color: Theme.of(context).colorScheme.greenAlternative,
+              color: getEnteColorScheme(context).primary500,
             ),
           ),
           onPressed: () async {
-            Navigator.of(context, rootNavigator: true).pop('dialog');
-            final collection = await _createAlbum(_albumName);
-            if (collection != null) {
-              if (await _runCollectionAction(collection.id)) {
-                if (widget.actionType == CollectionActionType.restoreFiles) {
-                  showShortToast(
-                    context,
-                    'Restored files to album ' + _albumName,
-                  );
-                } else {
-                  showShortToast(
-                    context,
-                    "Album '" + _albumName + "' created.",
-                  );
+            if (albumName != null && albumName!.isNotEmpty) {
+              Navigator.of(context, rootNavigator: true).pop('dialog');
+              final collection = await _createAlbum(albumName!);
+              if (collection != null) {
+                if (await _runCollectionAction(collection.id)) {
+                  if (widget.actionType == CollectionActionType.restoreFiles) {
+                    showShortToast(
+                      context,
+                      'Restored files to album ' + albumName!,
+                    );
+                  } else {
+                    showShortToast(
+                      context,
+                      "Album '" + albumName! + "' created.",
+                    );
+                  }
+                  _navigateToCollection(collection);
                 }
-                _navigateToCollection(collection);
               }
             }
           },
@@ -271,6 +282,60 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
     );
   }
 
+  Future<Collection?> _createAlbum(String albumName) async {
+    Collection? collection;
+    final dialog = createProgressDialog(context, "Creating album...");
+    await dialog.show();
+    try {
+      collection = await CollectionsService.instance.createAlbum(albumName);
+    } catch (e, s) {
+      _logger.severe(e, s);
+      await dialog.hide();
+      showGenericErrorDialog(context: context);
+    } finally {
+      await dialog.hide();
+    }
+    return collection;
+  }
+
+  Future<void> _albumListItemOnTap(CollectionWithThumbnail item) async {
+    if (await _runCollectionAction(
+      item.collection.id,
+    )) {
+      showShortToast(
+        context,
+        widget.actionType == CollectionActionType.addFiles
+            ? "Added successfully to " + item.collection.name!
+            : "Moved successfully to " + item.collection.name!,
+      );
+      _navigateToCollection(
+        item.collection,
+      );
+    }
+  }
+
+  Future<List<CollectionWithThumbnail>> _getCollectionsWithThumbnail() async {
+    final List<CollectionWithThumbnail> collectionsWithThumbnail =
+        await CollectionsService.instance.getCollectionsWithThumbnails(
+      // in collections where user is a collaborator, only addTo and remove
+      // action can to be performed
+      includeCollabCollections:
+          widget.actionType == CollectionActionType.addFiles,
+    );
+    collectionsWithThumbnail.removeWhere(
+      (element) => (element.collection.type == CollectionType.favorites ||
+          element.collection.type == CollectionType.uncategorized ||
+          element.collection.isSharedFilesCollection()),
+    );
+    collectionsWithThumbnail.sort((first, second) {
+      return compareAsciiLowerCaseNatural(
+        first.collection.name ?? "",
+        second.collection.name ?? "",
+      );
+    });
+    return collectionsWithThumbnail;
+  }
+
   void _navigateToCollection(Collection collection) {
     Navigator.pop(context);
     routeToPage(
@@ -294,59 +359,6 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
     }
   }
 
-  Future<bool> _moveFilesToCollection(int toCollectionID) async {
-    final String message = widget.actionType == CollectionActionType.moveFiles
-        ? "Moving files to album..."
-        : "Unhiding files to album";
-    final dialog = createProgressDialog(context, message);
-    await dialog.show();
-    try {
-      final int fromCollectionID =
-          widget.selectedFiles!.files.first.collectionID!;
-      await CollectionsService.instance.move(
-        toCollectionID,
-        fromCollectionID,
-        widget.selectedFiles!.files.toList(),
-      );
-      await dialog.hide();
-      RemoteSyncService.instance.sync(silently: true);
-      widget.selectedFiles?.clearAll();
-
-      return true;
-    } on AssertionError catch (e) {
-      await dialog.hide();
-      showErrorDialog(context, "Oops", e.message as String?);
-      return false;
-    } catch (e, s) {
-      _logger.severe("Could not move to album", e, s);
-      await dialog.hide();
-      showGenericErrorDialog(context: context);
-      return false;
-    }
-  }
-
-  Future<bool> _restoreFilesToCollection(int toCollectionID) async {
-    final dialog = createProgressDialog(context, "Restoring files...");
-    await dialog.show();
-    try {
-      await CollectionsService.instance
-          .restore(toCollectionID, widget.selectedFiles!.files.toList());
-      RemoteSyncService.instance.sync(silently: true);
-      widget.selectedFiles?.clearAll();
-      await dialog.hide();
-      return true;
-    } on AssertionError catch (e) {
-      await dialog.hide();
-      showErrorDialog(context, "Oops", e.message as String?);
-      return false;
-    } catch (e, s) {
-      _logger.severe("Could not move to album", e, s);
-      await dialog.hide();
-      showGenericErrorDialog(context: context);
-      return false;
-    }
-  }
-
   Future<bool> _addToCollection(int collectionID) async {
     final dialog = createProgressDialog(context, "Uploading files to album...");
     await dialog.show();
@@ -407,19 +419,56 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
     return false;
   }
 
-  Future<Collection?> _createAlbum(String albumName) async {
-    Collection? collection;
-    final dialog = createProgressDialog(context, "Creating album...");
+  Future<bool> _moveFilesToCollection(int toCollectionID) async {
+    final String message = widget.actionType == CollectionActionType.moveFiles
+        ? "Moving files to album..."
+        : "Unhiding files to album";
+    final dialog = createProgressDialog(context, message);
     await dialog.show();
     try {
-      collection = await CollectionsService.instance.createAlbum(albumName);
+      final int fromCollectionID =
+          widget.selectedFiles!.files.first.collectionID!;
+      await CollectionsService.instance.move(
+        toCollectionID,
+        fromCollectionID,
+        widget.selectedFiles!.files.toList(),
+      );
+      await dialog.hide();
+      RemoteSyncService.instance.sync(silently: true);
+      widget.selectedFiles?.clearAll();
+
+      return true;
+    } on AssertionError catch (e) {
+      await dialog.hide();
+      showErrorDialog(context, "Oops", e.message as String?);
+      return false;
     } catch (e, s) {
-      _logger.severe(e, s);
+      _logger.severe("Could not move to album", e, s);
       await dialog.hide();
       showGenericErrorDialog(context: context);
-    } finally {
+      return false;
+    }
+  }
+
+  Future<bool> _restoreFilesToCollection(int toCollectionID) async {
+    final dialog = createProgressDialog(context, "Restoring files...");
+    await dialog.show();
+    try {
+      await CollectionsService.instance
+          .restore(toCollectionID, widget.selectedFiles!.files.toList());
+      RemoteSyncService.instance.sync(silently: true);
+      widget.selectedFiles?.clearAll();
+      await dialog.hide();
+      return true;
+    } on AssertionError catch (e) {
+      await dialog.hide();
+      showErrorDialog(context, "Oops", e.message as String?);
+      return false;
+    } catch (e, s) {
+      _logger.severe("Could not move to album", e, s);
       await dialog.hide();
+      showGenericErrorDialog(context: context);
+      return false;
     }
-    return collection;
   }
 }

+ 18 - 3
lib/ui/home_widget.dart

@@ -28,7 +28,7 @@ import 'package:photos/theme/colors.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/collections_gallery_widget.dart';
 import 'package:photos/ui/common/bottom_shadow.dart';
-import 'package:photos/ui/create_collection_page.dart';
+import 'package:photos/ui/create_collection_sheet.dart';
 import 'package:photos/ui/extents_page_view.dart';
 import 'package:photos/ui/home/grant_permissions_widget.dart';
 import 'package:photos/ui/home/header_widget.dart';
@@ -72,6 +72,7 @@ class _HomeWidgetState extends State<HomeWidget> {
   // ignore: unused_field
   StreamSubscription? _intentDataStreamSubscription;
   List<SharedMediaFile>? _sharedFiles;
+  bool _shouldRenderCreateCollectionSheet = false;
 
   late StreamSubscription<TabChangedEvent> _tabChangedEventSubscription;
   late StreamSubscription<SubscriptionPurchasedEvent>
@@ -236,6 +237,7 @@ class _HomeWidgetState extends State<HomeWidget> {
         ReceiveSharingIntent.getMediaStream().listen(
       (List<SharedMediaFile> value) {
         setState(() {
+          _shouldRenderCreateCollectionSheet = true;
           _sharedFiles = value;
         });
       },
@@ -317,9 +319,22 @@ class _HomeWidgetState extends State<HomeWidget> {
       return const LoadingPhotosWidget();
     }
 
-    if (_sharedFiles != null && _sharedFiles!.isNotEmpty) {
+    if (_sharedFiles != null &&
+        _sharedFiles!.isNotEmpty &&
+        _shouldRenderCreateCollectionSheet) {
+      //The gallery is getting rebuilt for some reason when the keyboard is up.
+      //So to stop showing multiple CreateCollectionSheets, this flag
+      //needs to be set to false the first time it is rendered.
+      _shouldRenderCreateCollectionSheet = false;
       ReceiveSharingIntent.reset();
-      return CreateCollectionPage(null, _sharedFiles);
+      Future.delayed(const Duration(milliseconds: 10), () {
+        createCollectionSheet(
+          null,
+          _sharedFiles,
+          context,
+          actionType: CollectionActionType.addFiles,
+        );
+      });
     }
     final isBottomInsetPresent = MediaQuery.of(context).viewPadding.bottom != 0;
 

+ 1 - 1
lib/ui/payment/child_subscription_widget.dart

@@ -118,7 +118,7 @@ class ChildSubscriptionWidget extends StatelessWidget {
   }
 
   Future<void> _leaveFamilyPlan(BuildContext context) async {
-    final choice = await showNewChoiceDialog(
+    final choice = await showChoiceDialog(
       context,
       title: "Leave family",
       body: "Are you sure that you want to leave the family plan?",

+ 3 - 3
lib/ui/payment/stripe_subscription_page.dart

@@ -344,7 +344,7 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
       onPressed: () async {
         bool confirmAction = false;
         if (isRenewCancelled) {
-          final choice = await showNewChoiceDialog(
+          final choice = await showChoiceDialog(
             context,
             title: title,
             body: "Are you sure you want to renew?",
@@ -352,7 +352,7 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
           );
           confirmAction = choice == ButtonAction.first;
         } else {
-          final choice = await showNewChoiceDialog(
+          final choice = await showChoiceDialog(
             context,
             title: title,
             body: "Are you sure you want to cancel?",
@@ -429,7 +429,7 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
               String stripPurChaseAction = 'buy';
               if (_isStripeSubscriber && _hasActiveSubscription) {
                 // confirm if user wants to change plan or not
-                final result = await showNewChoiceDialog(
+                final result = await showChoiceDialog(
                   context,
                   title: "Confirm plan change",
                   body: "Are you sure you want to change your plan?",

+ 8 - 41
lib/ui/settings/account_section_widget.dart

@@ -2,7 +2,6 @@ import 'dart:async';
 
 import 'package:flutter/material.dart';
 import 'package:flutter_sodium/flutter_sodium.dart';
-import 'package:photos/ente_theme_data.dart';
 import 'package:photos/services/local_authentication_service.dart';
 import 'package:photos/services/user_service.dart';
 import 'package:photos/theme/ente_theme.dart';
@@ -156,46 +155,14 @@ class AccountSectionWidget extends StatelessWidget {
     );
   }
 
-  Future<void> _onLogoutTapped(BuildContext context) async {
-    final AlertDialog alert = AlertDialog(
-      title: const Text(
-        "Logout",
-        style: TextStyle(
-          color: Colors.red,
-        ),
-      ),
-      content: const Text("Are you sure you want to logout?"),
-      actions: [
-        TextButton(
-          child: const Text(
-            "Yes, logout",
-            style: TextStyle(
-              color: Colors.red,
-            ),
-          ),
-          onPressed: () async {
-            Navigator.of(context, rootNavigator: true).pop('dialog');
-            await UserService.instance.logout(context);
-          },
-        ),
-        TextButton(
-          child: Text(
-            "No",
-            style: TextStyle(
-              color: Theme.of(context).colorScheme.greenAlternative,
-            ),
-          ),
-          onPressed: () {
-            Navigator.of(context, rootNavigator: true).pop('dialog');
-          },
-        ),
-      ],
-    );
-
-    await showDialog(
-      context: context,
-      builder: (BuildContext context) {
-        return alert;
+  void _onLogoutTapped(BuildContext context) {
+    showChoiceActionSheet(
+      context,
+      title: "Are you sure you want to logout?",
+      firstButtonLabel: "Yes, logout",
+      isCritical: true,
+      firstButtonOnTap: () async {
+        await UserService.instance.logout(context);
       },
     );
   }

+ 54 - 17
lib/ui/viewer/actions/file_selection_actions_widget.dart

@@ -1,7 +1,6 @@
 import 'package:fast_base58/fast_base58.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
-import 'package:page_transition/page_transition.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/models/collection.dart';
 import 'package:photos/models/device_collection.dart';
@@ -20,7 +19,7 @@ import 'package:photos/ui/components/blur_menu_item_widget.dart';
 import 'package:photos/ui/components/bottom_action_bar/expanded_menu_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_page.dart';
+import 'package:photos/ui/create_collection_sheet.dart';
 import 'package:photos/ui/sharing/manage_links_widget.dart';
 import 'package:photos/utils/delete_file_util.dart';
 import 'package:photos/utils/magic_util.dart';
@@ -234,6 +233,28 @@ class _FileSelectionActionWidgetState extends State<FileSelectionActionWidget> {
       );
     }
 
+    if (widget.type.showRestoreOption()) {
+      secondList.add(
+        BlurMenuItemWidget(
+          leadingIcon: Icons.visibility,
+          labelText: "Restore",
+          menuItemColor: colorScheme.fillFaint,
+          onTap: _restore,
+        ),
+      );
+    }
+
+    if (widget.type.showPermanentlyDeleteOption()) {
+      secondList.add(
+        BlurMenuItemWidget(
+          leadingIcon: Icons.delete_forever_outlined,
+          labelText: "Permanently delete",
+          menuItemColor: colorScheme.fillFaint,
+          onTap: _permanentlyDelete,
+        ),
+      );
+    }
+
     if (firstList.isNotEmpty || secondList.isNotEmpty) {
       if (firstList.isNotEmpty) {
         items.add(firstList);
@@ -255,7 +276,12 @@ class _FileSelectionActionWidgetState extends State<FileSelectionActionWidget> {
       widget.selectedFiles
           .unSelectAll(split.ownedByOtherUsers.toSet(), skipNotify: true);
     }
-    await _selectionCollectionForAction(CollectionActionType.moveFiles);
+    createCollectionSheet(
+      widget.selectedFiles,
+      null,
+      context,
+      actionType: CollectionActionType.moveFiles,
+    );
   }
 
   Future<void> _addToAlbum() async {
@@ -263,7 +289,11 @@ class _FileSelectionActionWidgetState extends State<FileSelectionActionWidget> {
       widget.selectedFiles
           .unSelectAll(split.ownedByOtherUsers.toSet(), skipNotify: true);
     }
-    await _selectionCollectionForAction(CollectionActionType.addFiles);
+    createCollectionSheet(
+      widget.selectedFiles,
+      null,
+      context,
+    );
   }
 
   Future<void> _onDeleteClick() async {
@@ -339,7 +369,12 @@ class _FileSelectionActionWidgetState extends State<FileSelectionActionWidget> {
       widget.selectedFiles
           .unSelectAll(split.ownedByOtherUsers.toSet(), skipNotify: true);
     }
-    await _selectionCollectionForAction(CollectionActionType.unHide);
+    createCollectionSheet(
+      widget.selectedFiles,
+      null,
+      context,
+      actionType: CollectionActionType.unHide,
+    );
   }
 
   Future<void> _onCreatedSharedLinkClicked() async {
@@ -408,19 +443,21 @@ class _FileSelectionActionWidgetState extends State<FileSelectionActionWidget> {
     }
   }
 
-  Future<Object?> _selectionCollectionForAction(
-    CollectionActionType type,
-  ) async {
-    return Navigator.push(
+  void _restore() {
+    createCollectionSheet(
+      widget.selectedFiles,
+      null,
       context,
-      PageTransition(
-        type: PageTransitionType.bottomToTop,
-        child: CreateCollectionPage(
-          widget.selectedFiles,
-          null,
-          actionType: type,
-        ),
-      ),
+      actionType: CollectionActionType.restoreFiles,
     );
   }
+
+  Future<void> _permanentlyDelete() async {
+    if (await deleteFromTrash(
+      context,
+      widget.selectedFiles.files.toList(),
+    )) {
+      widget.selectedFiles.clearAll();
+    }
+  }
 }

+ 27 - 22
lib/ui/viewer/actions/file_selection_overlay_bar.dart

@@ -1,5 +1,4 @@
 import 'package:flutter/material.dart';
-import 'package:page_transition/page_transition.dart';
 import 'package:photos/models/collection.dart';
 import 'package:photos/models/device_collection.dart';
 import 'package:photos/models/gallery_type.dart';
@@ -8,7 +7,7 @@ import 'package:photos/models/selected_files.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/components/bottom_action_bar/bottom_action_bar_widget.dart';
 import 'package:photos/ui/components/icon_button_widget.dart';
-import 'package:photos/ui/create_collection_page.dart';
+import 'package:photos/ui/create_collection_sheet.dart';
 import 'package:photos/ui/viewer/actions/file_selection_actions_widget.dart';
 import 'package:photos/utils/delete_file_util.dart';
 import 'package:photos/utils/magic_util.dart';
@@ -85,9 +84,31 @@ class _FileSelectionOverlayBarState extends State<FileSelectionOverlayBar> {
           icon: Icons.visibility_off_outlined,
           iconButtonType: IconButtonType.primary,
           iconColor: iconColor,
-          onTap: () => _selectionCollectionForAction(
-            CollectionActionType.unHide,
-          ),
+          onTap: () {
+            createCollectionSheet(
+              widget.selectedFiles,
+              null,
+              context,
+              actionType: CollectionActionType.unHide,
+            );
+          },
+        ),
+      );
+    }
+    if (widget.galleryType == GalleryType.trash) {
+      iconsButton.add(
+        IconButtonWidget(
+          icon: Icons.delete_forever_outlined,
+          iconButtonType: IconButtonType.primary,
+          iconColor: iconColor,
+          onTap: () async {
+            if (await deleteFromTrash(
+              context,
+              widget.selectedFiles.files.toList(),
+            )) {
+              widget.selectedFiles.clearAll();
+            }
+          },
         ),
       );
     }
@@ -95,7 +116,7 @@ class _FileSelectionOverlayBarState extends State<FileSelectionOverlayBar> {
       IconButtonWidget(
         icon: Icons.adaptive.share_outlined,
         iconButtonType: IconButtonType.primary,
-        iconColor: getEnteColorScheme(context).blurStrokeBase,
+        iconColor: iconColor,
         onTap: () => shareSelected(
           context,
           shareButtonKey,
@@ -143,22 +164,6 @@ class _FileSelectionOverlayBarState extends State<FileSelectionOverlayBar> {
     widget.selectedFiles.clearAll();
   }
 
-  Future<Object?> _selectionCollectionForAction(
-    CollectionActionType type,
-  ) async {
-    return Navigator.push(
-      context,
-      PageTransition(
-        type: PageTransitionType.bottomToTop,
-        child: CreateCollectionPage(
-          widget.selectedFiles,
-          null,
-          actionType: type,
-        ),
-      ),
-    );
-  }
-
   _selectedFilesListener() {
     widget.selectedFiles.files.isNotEmpty
         ? _bottomPosition.value = 0.0

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

@@ -6,7 +6,6 @@ import 'package:flutter/material.dart';
 import 'package:like_button/like_button.dart';
 import 'package:logging/logging.dart';
 import 'package:media_extension/media_extension.dart';
-import 'package:page_transition/page_transition.dart';
 import 'package:path/path.dart' as file_path;
 import 'package:photo_manager/photo_manager.dart';
 import 'package:photos/core/event_bus.dart';
@@ -26,7 +25,7 @@ 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_page.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';
@@ -272,16 +271,11 @@ class FadingAppBarState extends State<FadingAppBar> {
   Future<void> _handleUnHideRequest(BuildContext context) async {
     final s = SelectedFiles();
     s.files.add(widget.file);
-    Navigator.push(
+    createCollectionSheet(
+      s,
+      null,
       context,
-      PageTransition(
-        type: PageTransitionType.bottomToTop,
-        child: CreateCollectionPage(
-          s,
-          null,
-          actionType: CollectionActionType.unHide,
-        ),
-      ),
+      actionType: CollectionActionType.unHide,
     );
   }
 

+ 5 - 11
lib/ui/viewer/file/fading_bottom_bar.dart

@@ -3,7 +3,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:page_transition/page_transition.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/models/file.dart';
 import 'package:photos/models/file_type.dart';
@@ -13,7 +12,7 @@ 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/create_collection_page.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';
@@ -238,16 +237,11 @@ class FadingBottomBarState extends State<FadingBottomBar> {
             onPressed: () {
               final selectedFiles = SelectedFiles();
               selectedFiles.toggleSelection(widget.file);
-              Navigator.push(
+              createCollectionSheet(
+                selectedFiles,
+                null,
                 context,
-                PageTransition(
-                  type: PageTransitionType.bottomToTop,
-                  child: CreateCollectionPage(
-                    selectedFiles,
-                    null,
-                    actionType: CollectionActionType.restoreFiles,
-                  ),
-                ),
+                actionType: CollectionActionType.restoreFiles,
               );
             },
           ),

+ 8 - 5
lib/ui/viewer/file/no_thumbnail_widget.dart

@@ -2,7 +2,8 @@ import 'package:flutter/material.dart';
 import 'package:photos/theme/ente_theme.dart';
 
 class NoThumbnailWidget extends StatelessWidget {
-  const NoThumbnailWidget({Key? key}) : super(key: key);
+  final bool addBorder;
+  const NoThumbnailWidget({this.addBorder = true, Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
@@ -10,10 +11,12 @@ class NoThumbnailWidget extends StatelessWidget {
     return Container(
       decoration: BoxDecoration(
         borderRadius: BorderRadius.circular(1),
-        border: Border.all(
-          color: enteColorScheme.strokeFaint,
-          width: 1,
-        ),
+        border: addBorder
+            ? Border.all(
+                color: enteColorScheme.strokeFaint,
+                width: 1,
+              )
+            : null,
         color: enteColorScheme.fillFaint,
       ),
       child: Center(

+ 1 - 1
lib/ui/viewer/gallery/gallery_app_bar_widget.dart

@@ -138,7 +138,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
   }
 
   Future<dynamic> _leaveAlbum(BuildContext context) async {
-    final result = await showNewChoiceDialog(
+    final result = await showChoiceDialog(
       context,
       title: "Leave shared album",
       body: "You will leave the album, and it will stop being visible to you",

+ 0 - 684
lib/ui/viewer/gallery/gallery_overlay_widget.dart

@@ -1,684 +0,0 @@
-import 'dart:async';
-import 'dart:io';
-import 'dart:ui';
-
-import 'package:flutter/cupertino.dart';
-import 'package:flutter/material.dart';
-import 'package:logging/logging.dart';
-import 'package:page_transition/page_transition.dart';
-import 'package:photos/core/configuration.dart';
-import 'package:photos/core/event_bus.dart';
-import 'package:photos/ente_theme_data.dart';
-import 'package:photos/events/subscription_purchased_event.dart';
-import 'package:photos/models/collection.dart';
-import 'package:photos/models/gallery_type.dart';
-import 'package:photos/models/magic_metadata.dart';
-import 'package:photos/models/selected_files.dart';
-import 'package:photos/services/collections_service.dart';
-import 'package:photos/services/hidden_service.dart';
-import 'package:photos/ui/create_collection_page.dart';
-import 'package:photos/utils/delete_file_util.dart';
-import 'package:photos/utils/dialog_util.dart';
-import 'package:photos/utils/magic_util.dart';
-import 'package:photos/utils/share_util.dart';
-import 'package:photos/utils/toast_util.dart';
-
-class GalleryOverlayWidget extends StatefulWidget {
-  final GalleryType type;
-  final SelectedFiles selectedFiles;
-  final String? path;
-  final Collection? collection;
-
-  const GalleryOverlayWidget(
-    this.type,
-    this.selectedFiles, {
-    this.path,
-    this.collection,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  State<GalleryOverlayWidget> createState() => _GalleryOverlayWidgetState();
-}
-
-class _GalleryOverlayWidgetState extends State<GalleryOverlayWidget> {
-  late StreamSubscription _userAuthEventSubscription;
-  late Function() _selectedFilesListener;
-  final GlobalKey shareButtonKey = GlobalKey();
-
-  @override
-  void initState() {
-    _selectedFilesListener = () {
-      setState(() {});
-    };
-    widget.selectedFiles.addListener(_selectedFilesListener);
-    _userAuthEventSubscription =
-        Bus.instance.on<SubscriptionPurchasedEvent>().listen((event) {
-      setState(() {});
-    });
-    super.initState();
-  }
-
-  @override
-  void dispose() {
-    _userAuthEventSubscription.cancel();
-    widget.selectedFiles.removeListener(_selectedFilesListener);
-    super.dispose();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    final bool filesAreSelected = widget.selectedFiles.files.isNotEmpty;
-    final bottomPadding = Platform.isAndroid ? 0.0 : 12.0;
-    return Padding(
-      padding: EdgeInsets.only(bottom: bottomPadding),
-      child: AnimatedContainer(
-        duration: const Duration(milliseconds: 300),
-        curve: Curves.easeInOut,
-        height: filesAreSelected ? 108 : 0,
-        child: AnimatedOpacity(
-          duration: const Duration(milliseconds: 100),
-          opacity: filesAreSelected ? 1.0 : 0.0,
-          curve: Curves.easeIn,
-          child: IgnorePointer(
-            ignoring: !filesAreSelected,
-            child: OverlayWidget(
-              widget.type,
-              widget.selectedFiles,
-              path: widget.path,
-              collection: widget.collection,
-            ),
-          ),
-        ),
-      ),
-    );
-  }
-}
-
-class OverlayWidget extends StatefulWidget {
-  final GalleryType type;
-  final SelectedFiles selectedFiles;
-  final String? path;
-  final Collection? collection;
-
-  const OverlayWidget(
-    this.type,
-    this.selectedFiles, {
-    this.path,
-    this.collection,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  State<OverlayWidget> createState() => _OverlayWidgetState();
-}
-
-class _OverlayWidgetState extends State<OverlayWidget> {
-  final _logger = Logger("GalleryOverlay");
-  late StreamSubscription _userAuthEventSubscription;
-  late Function() _selectedFilesListener;
-  final GlobalKey shareButtonKey = GlobalKey();
-
-  @override
-  void initState() {
-    _selectedFilesListener = () {
-      setState(() {});
-    };
-    widget.selectedFiles.addListener(_selectedFilesListener);
-    _userAuthEventSubscription =
-        Bus.instance.on<SubscriptionPurchasedEvent>().listen((event) {
-      setState(() {});
-    });
-    super.initState();
-  }
-
-  @override
-  void dispose() {
-    _userAuthEventSubscription.cancel();
-    widget.selectedFiles.removeListener(_selectedFilesListener);
-    super.dispose();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return Container(
-      color: Colors.transparent,
-      child: ListView(
-        //ListView is for animation to work without render overflow
-        physics: const NeverScrollableScrollPhysics(),
-        children: [
-          Padding(
-            padding: const EdgeInsets.symmetric(horizontal: 12),
-            child: Container(
-              decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
-              child: ClipRRect(
-                borderRadius: BorderRadius.circular(8),
-                child: BackdropFilter(
-                  filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
-                  child: Container(
-                    color: Theme.of(context)
-                        .colorScheme
-                        .frostyBlurBackdropFilterColor,
-                    width: double.infinity,
-                    child: Row(
-                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                      children: [
-                        Padding(
-                          padding: const EdgeInsets.fromLTRB(13, 13, 0, 13),
-                          child: Text(
-                            widget.selectedFiles.files.length.toString() +
-                                ' selected',
-                            style: Theme.of(context)
-                                .textTheme
-                                .subtitle2!
-                                .copyWith(
-                                  fontWeight: FontWeight.w600,
-                                  color:
-                                      Theme.of(context).colorScheme.iconColor,
-                                ),
-                          ),
-                        ),
-                        Row(
-                          children: _getActions(context),
-                        )
-                      ],
-                    ),
-                  ),
-                ),
-              ),
-            ),
-          ),
-          const Padding(padding: EdgeInsets.symmetric(vertical: 8)),
-          Row(
-            mainAxisAlignment: MainAxisAlignment.center,
-            children: [
-              ClipRRect(
-                borderRadius: BorderRadius.circular(24),
-                child: GestureDetector(
-                  onTap: _clearSelectedFiles,
-                  child: BackdropFilter(
-                    filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
-                    child: Container(
-                      padding: const EdgeInsets.symmetric(vertical: 8),
-                      //height: 32,
-                      width: 86,
-                      color: Theme.of(context)
-                          .colorScheme
-                          .frostyBlurBackdropFilterColor,
-                      child: Center(
-                        child: Text(
-                          'Cancel',
-                          style: Theme.of(context)
-                              .textTheme
-                              .subtitle2!
-                              .copyWith(
-                                fontWeight: FontWeight.w600,
-                                color: Theme.of(context).colorScheme.iconColor,
-                              ),
-                        ),
-                      ),
-                    ),
-                  ),
-                ),
-              ),
-            ],
-          ),
-        ],
-      ),
-    );
-  }
-
-  void _clearSelectedFiles() {
-    widget.selectedFiles.clearAll();
-  }
-
-  Future<void> _createCollectionAction(CollectionActionType type) async {
-    Navigator.push(
-      context,
-      PageTransition(
-        type: PageTransitionType.bottomToTop,
-        child: CreateCollectionPage(
-          widget.selectedFiles,
-          null,
-          actionType: type,
-        ),
-      ),
-    );
-  }
-
-  Future<void> _moveFiles() async {
-    unawaited(
-      Navigator.push(
-        context,
-        PageTransition(
-          type: PageTransitionType.bottomToTop,
-          child: CreateCollectionPage(
-            widget.selectedFiles,
-            null,
-            actionType: CollectionActionType.moveFiles,
-          ),
-        ),
-      ),
-    );
-  }
-
-  List<Widget> _getActions(BuildContext context) {
-    final List<Widget> actions = <Widget>[];
-    if (widget.type == GalleryType.trash) {
-      _addTrashAction(actions);
-      return actions;
-    }
-    // skip add button for incoming collection till this feature is implemented
-    if (Configuration.instance.hasConfiguredAccount() &&
-        widget.type != GalleryType.sharedCollection &&
-        widget.type != GalleryType.hidden) {
-      IconData iconData = Platform.isAndroid ? Icons.add : CupertinoIcons.add;
-      // show upload icon instead of add for files selected in local gallery
-      if (widget.type == GalleryType.localFolder) {
-        iconData = Icons.cloud_upload_outlined;
-      }
-      actions.add(
-        Tooltip(
-          message: "add",
-          child: IconButton(
-            color: Theme.of(context).colorScheme.iconColor,
-            icon: Icon(iconData),
-            onPressed: () async {
-              await onActionSelected("add");
-            },
-          ),
-        ),
-      );
-    }
-
-    if (Configuration.instance.hasConfiguredAccount() &&
-        widget.type == GalleryType.hidden) {
-      actions.add(
-        Tooltip(
-          message: "Unhide",
-          child: IconButton(
-            color: Theme.of(context).colorScheme.iconColor,
-            icon: const Icon(Icons.visibility),
-            onPressed: () {
-              _createCollectionAction(CollectionActionType.unHide);
-            },
-          ),
-        ),
-      );
-    }
-    if (Configuration.instance.hasConfiguredAccount() &&
-        widget.type == GalleryType.ownedCollection &&
-        widget.collection!.type != CollectionType.favorites) {
-      actions.add(
-        Tooltip(
-          message: "Move",
-          child: IconButton(
-            color: Theme.of(context).colorScheme.iconColor,
-            icon: Icon(
-              Platform.isAndroid
-                  ? Icons.arrow_forward
-                  : CupertinoIcons.arrow_right,
-            ),
-            onPressed: () {
-              onActionSelected('move');
-            },
-          ),
-        ),
-      );
-    }
-    actions.add(
-      Tooltip(
-        message: "Share",
-        child: IconButton(
-          color: Theme.of(context).colorScheme.iconColor,
-          key: shareButtonKey,
-          icon: Icon(Platform.isAndroid ? Icons.share : CupertinoIcons.share),
-          onPressed: () {
-            _shareSelected(context);
-          },
-        ),
-      ),
-    );
-    if (widget.type == GalleryType.homepage ||
-        widget.type == GalleryType.archive ||
-        widget.type == GalleryType.hidden ||
-        widget.type == GalleryType.localFolder ||
-        widget.type == GalleryType.searchResults) {
-      actions.add(
-        Tooltip(
-          message: "Delete",
-          child: IconButton(
-            color: Theme.of(context).colorScheme.iconColor,
-            icon:
-                Icon(Platform.isAndroid ? Icons.delete : CupertinoIcons.delete),
-            onPressed: () {
-              _showDeleteSheet(context);
-            },
-          ),
-        ),
-      );
-    } else if (widget.type == GalleryType.ownedCollection) {
-      if (widget.collection!.type == CollectionType.folder) {
-        actions.add(
-          Tooltip(
-            message: "Delete",
-            child: IconButton(
-              color: Theme.of(context).colorScheme.iconColor,
-              icon: Icon(
-                Platform.isAndroid ? Icons.delete : CupertinoIcons.delete,
-              ),
-              onPressed: () {
-                _showDeleteSheet(context);
-              },
-            ),
-          ),
-        );
-      } else {
-        actions.add(
-          Tooltip(
-            message: "Remove",
-            child: IconButton(
-              color: Theme.of(context).colorScheme.iconColor,
-              icon: const Icon(
-                Icons.remove_circle_rounded,
-              ),
-              onPressed: () {
-                _showRemoveFromCollectionSheet(context);
-              },
-            ),
-          ),
-        );
-      }
-    }
-
-    if (widget.type == GalleryType.homepage ||
-        widget.type == GalleryType.archive) {
-      final bool showArchive = widget.type == GalleryType.homepage;
-      if (showArchive) {
-        actions.add(
-          Tooltip(
-            message: 'Archive',
-            child: IconButton(
-              color: Theme.of(context).colorScheme.iconColor,
-              icon: const Icon(
-                Icons.archive_outlined,
-              ),
-              onPressed: () {
-                onActionSelected('archive');
-              },
-            ),
-          ),
-        );
-      } else {
-        actions.insert(
-          0,
-          Tooltip(
-            message: 'Unarchive',
-            child: IconButton(
-              color: Theme.of(context).colorScheme.iconColor,
-              icon: const Icon(
-                Icons.unarchive,
-              ),
-              onPressed: () {
-                onActionSelected('unarchive');
-              },
-            ),
-          ),
-        );
-      }
-    }
-
-    return actions;
-  }
-
-  Future<void> onActionSelected(String value) async {
-    debugPrint("Action Selected $value");
-    switch (value.toLowerCase()) {
-      case 'hide':
-        await _handleHideRequest(context);
-        break;
-      case 'add':
-        await _createCollectionAction(CollectionActionType.addFiles);
-        break;
-      case 'move':
-        await _moveFiles();
-        break;
-      case 'archive':
-        await _handleVisibilityChangeRequest(context, visibilityArchive);
-        break;
-      case 'unarchive':
-        await _handleVisibilityChangeRequest(context, visibilityVisible);
-        break;
-      default:
-        break;
-    }
-  }
-
-  void _addTrashAction(List<Widget> actions) {
-    actions.add(
-      Tooltip(
-        message: "Restore",
-        child: IconButton(
-          color: Theme.of(context).colorScheme.iconColor,
-          icon: const Icon(
-            Icons.restore,
-          ),
-          onPressed: () {
-            Navigator.push(
-              context,
-              PageTransition(
-                type: PageTransitionType.bottomToTop,
-                child: CreateCollectionPage(
-                  widget.selectedFiles,
-                  null,
-                  actionType: CollectionActionType.restoreFiles,
-                ),
-              ),
-            );
-          },
-        ),
-      ),
-    );
-    actions.add(
-      Tooltip(
-        message: "Delete permanently",
-        child: IconButton(
-          color: Theme.of(context).colorScheme.iconColor,
-          icon: const Icon(
-            Icons.delete_forever,
-          ),
-          onPressed: () async {
-            if (await deleteFromTrash(
-              context,
-              widget.selectedFiles.files.toList(),
-            )) {
-              _clearSelectedFiles();
-            }
-          },
-        ),
-      ),
-    );
-  }
-
-  Future<void> _handleVisibilityChangeRequest(
-    BuildContext context,
-    int newVisibility,
-  ) async {
-    try {
-      await changeVisibility(
-        context,
-        widget.selectedFiles.files.toList(),
-        newVisibility,
-      );
-    } catch (e, s) {
-      _logger.severe("failed to update file visibility", e, s);
-      await showGenericErrorDialog(context: context);
-    } finally {
-      _clearSelectedFiles();
-    }
-  }
-
-  // note: Keeping this method here so that it can be used whenever we move to
-  // to bottom UI
-  Future<void> _handleHideRequest(BuildContext context) async {
-    try {
-      final hideResult = await CollectionsService.instance
-          .hideFiles(context, widget.selectedFiles.files.toList());
-      if (hideResult) {
-        _clearSelectedFiles();
-      }
-    } catch (e, s) {
-      _logger.severe("failed to update file visibility", e, s);
-      await showGenericErrorDialog(context: context);
-    }
-  }
-
-  void _shareSelected(BuildContext context) {
-    share(
-      context,
-      widget.selectedFiles.files.toList(),
-      shareButtonKey: shareButtonKey,
-    );
-  }
-
-  void _showDeleteSheet(BuildContext context) {
-    final count = widget.selectedFiles.files.length;
-    bool containsUploadedFile = false, containsLocalFile = false;
-    for (final file in widget.selectedFiles.files) {
-      if (file.uploadedFileID != null) {
-        containsUploadedFile = true;
-      }
-      if (file.localID != null) {
-        containsLocalFile = true;
-      }
-    }
-    final actions = <Widget>[];
-    if (containsUploadedFile && containsLocalFile) {
-      actions.add(
-        CupertinoActionSheetAction(
-          isDestructiveAction: true,
-          onPressed: () async {
-            Navigator.of(context, rootNavigator: true).pop();
-            await deleteFilesOnDeviceOnly(
-              context,
-              widget.selectedFiles.files.toList(),
-            );
-            _clearSelectedFiles();
-            showToast(context, "Files deleted from device");
-          },
-          child: const Text("Device"),
-        ),
-      );
-      actions.add(
-        CupertinoActionSheetAction(
-          isDestructiveAction: true,
-          onPressed: () async {
-            Navigator.of(context, rootNavigator: true).pop();
-            await deleteFilesFromRemoteOnly(
-              context,
-              widget.selectedFiles.files.toList(),
-            );
-            _clearSelectedFiles();
-            showShortToast(context, "Moved to trash");
-          },
-          child: const Text("ente"),
-        ),
-      );
-      actions.add(
-        CupertinoActionSheetAction(
-          isDestructiveAction: true,
-          onPressed: () async {
-            Navigator.of(context, rootNavigator: true).pop();
-            await deleteFilesFromEverywhere(
-              context,
-              widget.selectedFiles.files.toList(),
-            );
-            _clearSelectedFiles();
-          },
-          child: const Text("Everywhere"),
-        ),
-      );
-    } else {
-      actions.add(
-        CupertinoActionSheetAction(
-          isDestructiveAction: true,
-          onPressed: () async {
-            Navigator.of(context, rootNavigator: true).pop();
-            await deleteFilesFromEverywhere(
-              context,
-              widget.selectedFiles.files.toList(),
-            );
-            _clearSelectedFiles();
-          },
-          child: const Text("Delete"),
-        ),
-      );
-    }
-    final action = CupertinoActionSheet(
-      title: Text(
-        "Delete " +
-            count.toString() +
-            " file" +
-            (count == 1 ? "" : "s") +
-            (containsUploadedFile && containsLocalFile ? " from" : "?"),
-      ),
-      actions: actions,
-      cancelButton: CupertinoActionSheetAction(
-        child: const Text("Cancel"),
-        onPressed: () {
-          Navigator.of(context, rootNavigator: true).pop();
-        },
-      ),
-    );
-    showCupertinoModalPopup(
-      context: context,
-      builder: (_) => action,
-      barrierColor: Colors.black.withOpacity(0.75),
-    );
-  }
-
-  void _showRemoveFromCollectionSheet(BuildContext context) {
-    final count = widget.selectedFiles.files.length;
-    final action = CupertinoActionSheet(
-      title: Text(
-        "Remove " +
-            count.toString() +
-            " file" +
-            (count == 1 ? "" : "s") +
-            " from " +
-            widget.collection!.name! +
-            "?",
-      ),
-      actions: <Widget>[
-        CupertinoActionSheetAction(
-          isDestructiveAction: true,
-          onPressed: () async {
-            Navigator.of(context, rootNavigator: true).pop();
-            final dialog = createProgressDialog(context, "Removing files...");
-            await dialog.show();
-            try {
-              await CollectionsService.instance.removeFromCollection(
-                widget.collection!.id,
-                widget.selectedFiles.files.toList(),
-              );
-              await dialog.hide();
-              widget.selectedFiles.clearAll();
-            } catch (e, s) {
-              _logger.severe(e, s);
-              await dialog.hide();
-              showGenericErrorDialog(context: context);
-            }
-          },
-          child: const Text("Remove"),
-        ),
-      ],
-      cancelButton: CupertinoActionSheetAction(
-        child: const Text("Cancel"),
-        onPressed: () {
-          Navigator.of(context, rootNavigator: true).pop();
-        },
-      ),
-    );
-    showCupertinoModalPopup(context: context, builder: (_) => action);
-  }
-}

+ 3 - 6
lib/ui/viewer/gallery/trash_page.dart

@@ -9,9 +9,9 @@ import 'package:photos/events/force_reload_trash_page_event.dart';
 import 'package:photos/models/gallery_type.dart';
 import 'package:photos/models/selected_files.dart';
 import 'package:photos/ui/common/bottom_shadow.dart';
+import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart';
 import 'package:photos/ui/viewer/gallery/gallery.dart';
 import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart';
-import 'package:photos/ui/viewer/gallery/gallery_overlay_widget.dart';
 import 'package:photos/utils/delete_file_util.dart';
 
 class TrashPage extends StatefulWidget {
@@ -109,10 +109,7 @@ class _TrashPageState extends State<TrashPage> {
               ),
             ),
           ),
-          GalleryOverlayWidget(
-            widget.overlayType,
-            widget._selectedFiles,
-          )
+          FileSelectionOverlayBar(GalleryType.trash, widget._selectedFiles)
         ],
       ),
     );
@@ -126,7 +123,7 @@ class _TrashPageState extends State<TrashPage> {
           return Padding(
             padding: const EdgeInsets.all(16),
             child: Text(
-              'Items show the number the days remaining before permanent deletion',
+              'Items show the number of days remaining before permanent deletion',
               style:
                   Theme.of(context).textTheme.caption!.copyWith(fontSize: 16),
             ),

+ 19 - 28
lib/utils/delete_file_util.dart

@@ -20,7 +20,6 @@ import 'package:photos/models/trash_item_request.dart';
 import 'package:photos/services/remote_sync_service.dart';
 import 'package:photos/services/sync_service.dart';
 import 'package:photos/services/trash_sync_service.dart';
-import 'package:photos/ui/common/dialogs.dart';
 import 'package:photos/ui/common/linear_progress_dialog.dart';
 import 'package:photos/ui/components/action_sheet_widget.dart';
 import 'package:photos/ui/components/button_widget.dart';
@@ -35,8 +34,6 @@ Future<void> deleteFilesFromEverywhere(
   BuildContext context,
   List<File> files,
 ) async {
-  final dialog = createProgressDialog(context, "Deleting...");
-  await dialog.show();
   _logger.info("Trying to deleteFilesFromEverywhere " + files.toString());
   final List<String> localAssetIDs = [];
   final List<String> localSharedMediaIDs = [];
@@ -60,7 +57,6 @@ Future<void> deleteFilesFromEverywhere(
   if (hasLocalOnlyFiles && Platform.isAndroid) {
     final shouldProceed = await shouldProceedWithDeletion(context);
     if (!shouldProceed) {
-      await dialog.hide();
       return;
     }
   }
@@ -102,12 +98,9 @@ Future<void> deleteFilesFromEverywhere(
           uploadedFilesToBeTrashed.map((item) => item.fileID).toList();
       await TrashSyncService.instance
           .trashFilesOnServer(uploadedFilesToBeTrashed);
-      // await SyncService.instance
-      //     .deleteFilesOnServer(fileIDs);
       await FilesDB.instance.deleteMultipleUploadedFiles(fileIDs);
     } catch (e) {
       _logger.severe(e);
-      await dialog.hide();
       showGenericErrorDialog(context: context);
       rethrow;
     }
@@ -138,7 +131,6 @@ Future<void> deleteFilesFromEverywhere(
       showShortToast(context, "Moved to trash");
     }
   }
-  await dialog.hide();
   if (uploadedFilesToBeTrashed.isNotEmpty) {
     RemoteSyncService.instance.sync(silently: true);
   }
@@ -153,8 +145,6 @@ Future<void> deleteFilesFromRemoteOnly(
     showToast(context, "Selected files are not on ente");
     return;
   }
-  final dialog = createProgressDialog(context, "Deleting...");
-  await dialog.show();
   _logger.info(
     "Trying to deleteFilesFromRemoteOnly " +
         files.map((f) => f.uploadedFileID).toString(),
@@ -172,7 +162,6 @@ Future<void> deleteFilesFromRemoteOnly(
     await FilesDB.instance.deleteMultipleUploadedFiles(uploadedFileIDs);
   } catch (e, s) {
     _logger.severe("Failed to delete files from remote", e, s);
-    await dialog.hide();
     showGenericErrorDialog(context: context);
     rethrow;
   }
@@ -194,7 +183,6 @@ Future<void> deleteFilesFromRemoteOnly(
     ),
   );
   SyncService.instance.sync();
-  await dialog.hide();
   RemoteSyncService.instance.sync(silently: true);
 }
 
@@ -202,8 +190,6 @@ Future<void> deleteFilesOnDeviceOnly(
   BuildContext context,
   List<File> files,
 ) async {
-  final dialog = createProgressDialog(context, "Deleting...");
-  await dialog.show();
   _logger.info("Trying to deleteFilesOnDeviceOnly" + files.toString());
   final List<String> localAssetIDs = [];
   final List<String> localSharedMediaIDs = [];
@@ -227,7 +213,6 @@ Future<void> deleteFilesOnDeviceOnly(
   if (hasLocalOnlyFiles && Platform.isAndroid) {
     final shouldProceed = await shouldProceedWithDeletion(context);
     if (!shouldProceed) {
-      await dialog.hide();
       return;
     }
   }
@@ -258,18 +243,19 @@ Future<void> deleteFilesOnDeviceOnly(
       ),
     );
   }
-  await dialog.hide();
 }
 
 Future<bool> deleteFromTrash(BuildContext context, List<File> files) async {
-  final result = await showNewChoiceDialog(
+  bool didDeletionStart = false;
+  final result = await showChoiceDialog(
     context,
-    title: "Delete permanently",
+    title: "Permanently delete?",
     body: "This action cannot be undone",
     firstButtonLabel: "Delete",
     isCritical: true,
     firstButtonOnTap: () async {
       try {
+        didDeletionStart = true;
         await TrashSyncService.instance.deleteFromTrash(files);
         Bus.instance.fire(
           FilesUpdatedEvent(
@@ -289,16 +275,18 @@ Future<bool> deleteFromTrash(BuildContext context, List<File> files) async {
     return false;
   }
   if (result == null || result == ButtonAction.cancel) {
-    return false;
+    return didDeletionStart ? true : false;
   } else {
     return true;
   }
 }
 
 Future<bool> emptyTrash(BuildContext context) async {
-  final result = await showNewChoiceDialog(
+  final result = await showChoiceDialog(
     context,
-    title: "Empty trash",
+    title: "Empty trash?",
+    body:
+        "All items in trash will be permanently deleted\n\nThis action cannot be undone",
     firstButtonLabel: "Empty",
     isCritical: true,
     firstButtonOnTap: () async {
@@ -481,20 +469,23 @@ Future<List<String>> _tryDeleteSharedMediaFiles(List<String> localIDs) {
 Future<bool> shouldProceedWithDeletion(BuildContext context) async {
   final choice = await showChoiceDialog(
     context,
-    "Are you sure?",
-    "Some of the files you are trying to delete are only available on your device and cannot be recovered if deleted",
-    firstAction: "Cancel",
-    secondAction: "Delete",
-    secondActionColor: Colors.red,
+    title: "Are you sure?",
+    body:
+        "Some of the files you are trying to delete are only available on your device and cannot be recovered if deleted",
+    firstButtonLabel: "Delete",
+    isCritical: true,
   );
-  return choice == DialogUserChoice.secondChoice;
+  if (choice == null) {
+    return false;
+  } else {
+    return choice == ButtonAction.first;
+  }
 }
 
 Future<void> showDeleteSheet(
   BuildContext context,
   SelectedFiles selectedFiles,
 ) async {
-  final count = selectedFiles.files.length;
   bool containsUploadedFile = false, containsLocalFile = false;
   for (final file in selectedFiles.files) {
     if (file.uploadedFileID != null) {

+ 44 - 1
lib/utils/dialog_util.dart

@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
 import 'package:photos/core/constants.dart';
 import 'package:photos/ui/common/loading_widget.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/dialog_widget.dart';
 import 'package:photos/ui/components/models/button_type.dart';
@@ -90,7 +91,7 @@ DialogWidget choiceDialog({
 }
 
 ///Will return null if dismissed by tapping outside
-Future<ButtonAction?> showNewChoiceDialog(
+Future<ButtonAction?> showChoiceDialog(
   BuildContext context, {
   required String title,
   String? body,
@@ -132,6 +133,48 @@ Future<ButtonAction?> showNewChoiceDialog(
   );
 }
 
+///Will return null if dismissed by tapping outside
+Future<ButtonAction?> showChoiceActionSheet(
+  BuildContext context, {
+  required String title,
+  String? body,
+  required String firstButtonLabel,
+  String secondButtonLabel = "Cancel",
+  ButtonType firstButtonType = ButtonType.neutral,
+  ButtonType secondButtonType = ButtonType.secondary,
+  ButtonAction firstButtonAction = ButtonAction.first,
+  ButtonAction secondButtonAction = ButtonAction.cancel,
+  FutureVoidCallback? firstButtonOnTap,
+  FutureVoidCallback? secondButtonOnTap,
+  bool isCritical = false,
+  IconData? icon,
+  bool isDismissible = true,
+}) async {
+  final buttons = [
+    ButtonWidget(
+      buttonType: isCritical ? ButtonType.critical : firstButtonType,
+      labelText: firstButtonLabel,
+      isInAlert: true,
+      onTap: firstButtonOnTap,
+      buttonAction: firstButtonAction,
+    ),
+    ButtonWidget(
+      buttonType: secondButtonType,
+      labelText: secondButtonLabel,
+      isInAlert: true,
+      onTap: secondButtonOnTap,
+      buttonAction: secondButtonAction,
+    ),
+  ];
+  return showActionSheet(
+    context: context,
+    title: title,
+    body: body,
+    buttons: buttons,
+    isDismissible: isDismissible,
+  );
+}
+
 ProgressDialog createProgressDialog(
   BuildContext context,
   String message, {

+ 1 - 1
lib/utils/email_util.dart

@@ -253,7 +253,7 @@ Future<String> _clientInfo() async {
 }
 
 void _showNoMailAppsDialog(BuildContext context, String toEmail) {
-  showNewChoiceDialog(
+  showChoiceDialog(
     context,
     icon: Icons.email_outlined,
     title: 'Please email us at $toEmail',

+ 0 - 7
pubspec.lock

@@ -700,13 +700,6 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "4.7.0"
-  keyboard_visibility:
-    dependency: "direct main"
-    description:
-      name: keyboard_visibility
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "0.5.6"
   like_button:
     dependency: "direct main"
     description:

+ 0 - 1
pubspec.yaml

@@ -71,7 +71,6 @@ dependencies:
   implicitly_animated_reorderable_list: ^0.4.0
   in_app_purchase: ^3.0.7
   intl: ^0.17.0
-  keyboard_visibility: ^0.5.6
   like_button: ^2.0.2
   loading_animations: ^2.1.0
   local_auth: ^1.1.5