瀏覽代碼

Merge Collaboration UI Improvements #833

Collaboration UI Improvements
Neeraj Gupta 2 年之前
父節點
當前提交
cf9e0e4ca7

+ 62 - 34
lib/ui/actions/collection/collection_sharing_actions.dart

@@ -181,18 +181,17 @@ class CollectionActions {
         showGenericErrorDialog(context: context);
       }
       return result == ButtonAction.first;
-    } else {
-      return false;
     }
+    return false;
   }
 
-  Future<bool?> addEmailToCollection(
+  // addEmailToCollection returns true if add operation was successful
+  Future<bool> addEmailToCollection(
     BuildContext context,
     Collection collection,
-    String email, {
-    CollectionParticipantRole role = CollectionParticipantRole.viewer,
+    String email,
+    CollectionParticipantRole role, {
     bool showProgress = false,
-    String? publicKey,
   }) async {
     if (!isValidEmail(email)) {
       await showErrorDialog(
@@ -200,31 +199,29 @@ class CollectionActions {
         "Invalid email address",
         "Please enter a valid email address.",
       );
-      return null;
-    } else if (email == Configuration.instance.getEmail()) {
+      return false;
+    } else if (email.trim() == Configuration.instance.getEmail()) {
       await showErrorDialog(context, "Oops", "You cannot share with yourself");
-      return null;
+      return false;
     }
 
     ProgressDialog? dialog;
-    if (publicKey == null) {
-      if (showProgress) {
-        dialog = createProgressDialog(context, "Searching for user...");
-        await dialog.show();
-      }
+    String? publicKey;
+    if (showProgress) {
+      dialog = createProgressDialog(context, "Sharing...", isDismissible: true);
+      await dialog.show();
+    }
 
-      try {
-        publicKey = await UserService.instance.getPublicKey(email);
-        await dialog?.hide();
-      } catch (e) {
-        await dialog?.hide();
-        logger.severe("Failed to get public key", e);
-        showGenericErrorDialog(context: context);
-        return false;
-      }
+    try {
+      publicKey = await UserService.instance.getPublicKey(email);
+    } catch (e) {
+      await dialog?.hide();
+      logger.severe("Failed to get public key", e);
+      showGenericErrorDialog(context: context);
+      return false;
     }
-    // getPublicKey can return null
-    // ignore: unnecessary_null_comparison
+    // getPublicKey can return null when no user is associated with given
+    // email id
     if (publicKey == null || publicKey == '') {
       // todo: neeraj replace this as per the design where a new screen
       // is used for error. Do this change along with handling of network errors
@@ -250,16 +247,8 @@ class CollectionActions {
           ),
         ],
       );
-      return null;
+      return false;
     } else {
-      if (showProgress) {
-        dialog = createProgressDialog(
-          context,
-          "Sharing...",
-          isDismissible: true,
-        );
-        await dialog.show();
-      }
       try {
         final newSharees = await CollectionsService.instance
             .share(collection.id, email, publicKey, role);
@@ -279,6 +268,7 @@ class CollectionActions {
     }
   }
 
+  // deleteCollectionSheet returns true if the album is successfully deleted
   Future<bool> deleteCollectionSheet(
     BuildContext bContext,
     Collection collection,
@@ -288,6 +278,13 @@ class CollectionActions {
     if (collection.owner!.id != currentUserID) {
       throw AssertionError("Can not delete album owned by others");
     }
+    if (collection.hasSharees) {
+      final bool confirmDelete =
+          await _confirmSharedAlbumDeletion(bContext, collection);
+      if (!confirmDelete) {
+        return false;
+      }
+    }
     final actionResult = await showActionSheet(
       context: bContext,
       buttons: [
@@ -368,6 +365,37 @@ class CollectionActions {
     return false;
   }
 
+  // _confirmSharedAlbumDeletion should be shown when user tries to delete an
+  // album shared with other ente users.
+  Future<bool> _confirmSharedAlbumDeletion(
+    BuildContext context,
+    Collection collection,
+  ) async {
+    final ButtonAction? result = await showActionSheet(
+      context: context,
+      buttons: [
+        const ButtonWidget(
+          buttonType: ButtonType.critical,
+          isInAlert: true,
+          shouldStickToDarkTheme: true,
+          buttonAction: ButtonAction.first,
+          labelText: "Delete album",
+        ),
+        const ButtonWidget(
+          buttonType: ButtonType.secondary,
+          buttonAction: ButtonAction.cancel,
+          isInAlert: true,
+          shouldStickToDarkTheme: true,
+          labelText: "Cancel",
+        )
+      ],
+      title: "Delete shared album?",
+      body: "The album will be deleted for everyone\n\nYou will lose access to "
+          "shared photos in this album that are owned by others",
+    );
+    return result != null && result == ButtonAction.first;
+  }
+
   /*
   _moveFilesFromCurrentCollection removes the file from the current
   collection. Based on the file and collection ownership, files will be

+ 2 - 2
lib/ui/sharing/add_partipant_page.dart

@@ -187,11 +187,11 @@ class _AddParticipantPage extends State<AddParticipantPage> {
                               context,
                               widget.collection,
                               emailToAdd,
-                              role: widget.isAddingViewer
+                              widget.isAddingViewer
                                   ? CollectionParticipantRole.viewer
                                   : CollectionParticipantRole.collaborator,
                             );
-                            if (result != null && result && mounted) {
+                            if (result && mounted) {
                               Navigator.of(context).pop(true);
                             }
                           },

+ 14 - 14
lib/ui/sharing/album_participants_page.dart

@@ -155,14 +155,15 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
                             currentUserID: currentUserID,
                           ),
                           menuItemColor: getEnteColorScheme(context).fillFaint,
-                          pressedColor: getEnteColorScheme(context).fillFaint,
                           trailingIcon: isOwner ? Icons.chevron_right : null,
                           trailingIconIsMuted: true,
-                          onTap: () async {
-                            if (isOwner) {
-                              await _navigateToManageUser(currentUser);
-                            }
-                          },
+                          onTap: isOwner
+                              ? () async {
+                                  if (isOwner) {
+                                    await _navigateToManageUser(currentUser);
+                                  }
+                                }
+                              : null,
                           isTopBorderRadiusRemoved: listIndex > 0,
                           isBottomBorderRadiusRemoved: true,
                           borderRadius: 8,
@@ -183,7 +184,6 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
                       ),
                       leadingIcon: Icons.add_outlined,
                       menuItemColor: getEnteColorScheme(context).fillFaint,
-                      pressedColor: getEnteColorScheme(context).fillFaint,
                       onTap: () async {
                         await _navigateToAddUser(false);
                       },
@@ -229,14 +229,15 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
                             currentUserID: currentUserID,
                           ),
                           menuItemColor: getEnteColorScheme(context).fillFaint,
-                          pressedColor: getEnteColorScheme(context).fillFaint,
                           trailingIcon: isOwner ? Icons.chevron_right : null,
                           trailingIconIsMuted: true,
-                          onTap: () async {
-                            if (isOwner) {
-                              await _navigateToManageUser(currentUser);
-                            }
-                          },
+                          onTap: isOwner
+                              ? () async {
+                                  if (isOwner) {
+                                    await _navigateToManageUser(currentUser);
+                                  }
+                                }
+                              : null,
                           isTopBorderRadiusRemoved: listIndex > 0,
                           isBottomBorderRadiusRemoved: !isLastItem,
                           borderRadius: 8,
@@ -257,7 +258,6 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
                       ),
                       leadingIcon: Icons.add_outlined,
                       menuItemColor: getEnteColorScheme(context).fillFaint,
-                      pressedColor: getEnteColorScheme(context).fillFaint,
                       onTap: () async {
                         await _navigateToAddUser(true);
                       },

+ 51 - 16
lib/ui/sharing/manage_album_participant.dart

@@ -4,12 +4,16 @@ import 'package:photos/services/collections_service.dart';
 import 'package:photos/theme/colors.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/actions/collection/collection_sharing_actions.dart';
+import 'package:photos/ui/components/action_sheet_widget.dart';
+import 'package:photos/ui/components/button_widget.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/divider_widget.dart';
 import 'package:photos/ui/components/menu_item_widget.dart';
 import 'package:photos/ui/components/menu_section_description_widget.dart';
 import 'package:photos/ui/components/menu_section_title.dart';
+import 'package:photos/ui/components/models/button_type.dart';
 import 'package:photos/ui/components/title_bar_title_widget.dart';
+import 'package:photos/utils/dialog_util.dart';
 
 class ManageIndividualParticipant extends StatefulWidget {
   final Collection collection;
@@ -34,6 +38,7 @@ class _ManageIndividualParticipantState
   Widget build(BuildContext context) {
     final colorScheme = getEnteColorScheme(context);
     final textTheme = getEnteTextTheme(context);
+    bool isConvertToViewSuccess = false;
     return Scaffold(
       appBar: AppBar(),
       body: Padding(
@@ -69,7 +74,6 @@ class _ManageIndividualParticipantState
               ),
               leadingIcon: Icons.edit_outlined,
               menuItemColor: getEnteColorScheme(context).fillFaint,
-              pressedColor: getEnteColorScheme(context).fillFaint,
               trailingIcon: widget.user.isCollaborator ? Icons.check : null,
               onTap: widget.user.isCollaborator
                   ? null
@@ -79,10 +83,10 @@ class _ManageIndividualParticipantState
                         context,
                         widget.collection,
                         widget.user.email,
-                        role: CollectionParticipantRole.collaborator,
+                        CollectionParticipantRole.collaborator,
                         showProgress: true,
                       );
-                      if ((result ?? false) && mounted) {
+                      if (result && mounted) {
                         widget.user.role = CollectionParticipantRole
                             .collaborator
                             .toStringVal();
@@ -102,23 +106,55 @@ class _ManageIndividualParticipantState
               leadingIcon: Icons.photo_outlined,
               leadingIconColor: getEnteColorScheme(context).strokeBase,
               menuItemColor: getEnteColorScheme(context).fillFaint,
-              pressedColor: getEnteColorScheme(context).fillFaint,
               trailingIcon: widget.user.isViewer ? Icons.check : null,
               onTap: widget.user.isViewer
                   ? null
                   : () async {
-                      final result =
-                          await collectionActions.addEmailToCollection(
-                        context,
-                        widget.collection,
-                        widget.user.email,
-                        role: CollectionParticipantRole.viewer,
-                        showProgress: true,
+                      final ButtonAction? result = await showActionSheet(
+                        context: context,
+                        buttons: [
+                          ButtonWidget(
+                            buttonType: ButtonType.critical,
+                            isInAlert: true,
+                            shouldStickToDarkTheme: true,
+                            buttonAction: ButtonAction.first,
+                            shouldSurfaceExecutionStates: true,
+                            labelText: "Yes, convert to viewer",
+                            onTap: () async {
+                              isConvertToViewSuccess =
+                                  await collectionActions.addEmailToCollection(
+                                context,
+                                widget.collection,
+                                widget.user.email,
+                                CollectionParticipantRole.viewer,
+                              );
+                            },
+                          ),
+                          const ButtonWidget(
+                            buttonType: ButtonType.secondary,
+                            buttonAction: ButtonAction.cancel,
+                            isInAlert: true,
+                            shouldStickToDarkTheme: true,
+                            labelText: "Cancel",
+                          )
+                        ],
+                        title: "Change permissions?",
+                        body:
+                            '${widget.user.email} will not be able to add more photos to this album\n\nThey will still be able to remove existing photos added by them',
                       );
-                      if ((result ?? false) && mounted) {
-                        widget.user.role =
-                            CollectionParticipantRole.viewer.toStringVal();
-                        setState(() => {});
+                      if (result != null) {
+                        if (result == ButtonAction.error) {
+                          showGenericErrorDialog(context: context);
+                        }
+                        if (result == ButtonAction.first &&
+                            isConvertToViewSuccess &&
+                            mounted) {
+                          // reset value
+                          isConvertToViewSuccess = false;
+                          widget.user.role =
+                              CollectionParticipantRole.viewer.toStringVal();
+                          setState(() => {});
+                        }
                       }
                     },
               isTopBorderRadiusRemoved: true,
@@ -138,7 +174,6 @@ class _ManageIndividualParticipantState
               leadingIcon: Icons.not_interested_outlined,
               leadingIconColor: warning500,
               menuItemColor: getEnteColorScheme(context).fillFaint,
-              pressedColor: getEnteColorScheme(context).fillFaint,
               onTap: () async {
                 final result = await collectionActions.removeParticipant(
                   context,

+ 0 - 4
lib/ui/sharing/manage_links_widget.dart

@@ -81,7 +81,6 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
                     ),
                     alignCaptionedTextToLeft: true,
                     menuItemColor: getEnteColorScheme(context).fillFaint,
-                    pressedColor: getEnteColorScheme(context).fillFaint,
                     trailingWidget: Switch.adaptive(
                       value: widget.collection!.publicURLs?.firstOrNull
                               ?.enableCollect ??
@@ -153,7 +152,6 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
                     isBottomBorderRadiusRemoved: true,
                     isTopBorderRadiusRemoved: true,
                     menuItemColor: getEnteColorScheme(context).fillFaint,
-                    pressedColor: getEnteColorScheme(context).fillFaint,
                     trailingWidget: Switch.adaptive(
                       value: widget.collection!.publicURLs?.firstOrNull
                               ?.enableDownload ??
@@ -185,7 +183,6 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
                     alignCaptionedTextToLeft: true,
                     isTopBorderRadiusRemoved: true,
                     menuItemColor: getEnteColorScheme(context).fillFaint,
-                    pressedColor: getEnteColorScheme(context).fillFaint,
                     trailingWidget: Switch.adaptive(
                       value: widget.collection!.publicURLs?.firstOrNull
                               ?.passwordEnabled ??
@@ -224,7 +221,6 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
                     leadingIcon: Icons.remove_circle_outline,
                     leadingIconColor: warning500,
                     menuItemColor: getEnteColorScheme(context).fillFaint,
-                    pressedColor: getEnteColorScheme(context).fillFaint,
                     onTap: () async {
                       final bool result = await sharingActions.disableUrl(
                         context,

+ 20 - 24
lib/ui/sharing/share_collection_page.dart

@@ -72,7 +72,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
         ),
         leadingIcon: Icons.add,
         menuItemColor: getEnteColorScheme(context).fillFaint,
-        pressedColor: getEnteColorScheme(context).fillFaint,
         borderRadius: 4.0,
         isTopBorderRadiusRemoved: _sharees.isNotEmpty,
         isBottomBorderRadiusRemoved: true,
@@ -102,7 +101,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
         ),
         leadingIcon: Icons.add,
         menuItemColor: getEnteColorScheme(context).fillFaint,
-        pressedColor: getEnteColorScheme(context).fillFaint,
         borderRadius: 4.0,
         isTopBorderRadiusRemoved: _sharees.isNotEmpty,
         onTap: () async {
@@ -132,9 +130,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
         height: 24,
       ),
       MenuSectionTitle(
-        title: hasUrl
-            ? "Public link enabled"
-            : (_sharees.isEmpty ? "Or share a link" : "Share a link"),
+        title: hasUrl ? "Public link enabled" : "Share a link",
         iconData: Icons.public,
       ),
     ]);
@@ -149,7 +145,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
             leadingIcon: Icons.error_outline,
             leadingIconColor: getEnteColorScheme(context).warning500,
             menuItemColor: getEnteColorScheme(context).fillFaint,
-            pressedColor: getEnteColorScheme(context).fillFaint,
             onTap: () async {},
             isBottomBorderRadiusRemoved: true,
           ),
@@ -169,7 +164,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
               ),
               leadingIcon: Icons.copy,
               menuItemColor: getEnteColorScheme(context).fillFaint,
-              pressedColor: getEnteColorScheme(context).fillFaint,
               onTap: () async {
                 await Clipboard.setData(ClipboardData(text: url));
                 showShortToast(context, "Link copied to clipboard");
@@ -187,7 +181,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
               ),
               leadingIcon: Icons.adaptive.share,
               menuItemColor: getEnteColorScheme(context).fillFaint,
-              pressedColor: getEnteColorScheme(context).fillFaint,
               onTap: () async {
                 shareText(url);
               },
@@ -212,7 +205,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
             leadingIcon: Icons.link,
             trailingIcon: Icons.navigate_next,
             menuItemColor: getEnteColorScheme(context).fillFaint,
-            pressedColor: getEnteColorScheme(context).fillFaint,
             trailingIconIsMuted: true,
             onTap: () async {
               routeToPage(
@@ -237,7 +229,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
           ),
           leadingIcon: Icons.link,
           menuItemColor: getEnteColorScheme(context).fillFaint,
-          pressedColor: getEnteColorScheme(context).fillFaint,
           isBottomBorderRadiusRemoved: true,
           onTap: () async {
             final bool result =
@@ -247,9 +238,17 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
             }
           },
         ),
-        DividerWidget(
-          dividerType: DividerType.menu,
-          bgColor: getEnteColorScheme(context).fillFaint,
+        _sharees.isEmpty
+            ? const MenuSectionDescriptionWidget(
+                content: "Share with non-ente users",
+              )
+            : const SizedBox.shrink(),
+        const SizedBox(
+          height: 24,
+        ),
+        const MenuSectionTitle(
+          title: "Collaborative link",
+          iconData: Icons.public,
         ),
         MenuItemWidget(
           captionedTextWidget: const CaptionedTextWidget(
@@ -258,7 +257,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
           ),
           leadingIcon: Icons.link,
           menuItemColor: getEnteColorScheme(context).fillFaint,
-          pressedColor: getEnteColorScheme(context).fillFaint,
           onTap: () async {
             final bool result = await collectionActions.enableUrl(
               context,
@@ -270,15 +268,14 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
             }
           },
         ),
+        _sharees.isEmpty
+            ? const MenuSectionDescriptionWidget(
+                content:
+                    "Create a link to allow people to add and view photos in "
+                    "your shared album without needing an ente app or account. Great for collecting event photos.",
+              )
+            : const SizedBox.shrink(),
       ]);
-      if (_sharees.isEmpty && !hasUrl) {
-        children.add(
-          const MenuSectionDescriptionWidget(
-            content:
-                "Links allow people without an ente account to view and add photos to your shared albums.",
-          ),
-        );
-      }
     }
 
     return Scaffold(
@@ -297,6 +294,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
               padding:
                   const EdgeInsets.symmetric(vertical: 4.0, horizontal: 16),
               child: Column(
+                crossAxisAlignment: CrossAxisAlignment.start,
                 children: children,
               ),
             ),
@@ -335,7 +333,6 @@ class EmailItemWidget extends StatelessWidget {
             ),
             leadingIconSize: 24,
             menuItemColor: getEnteColorScheme(context).fillFaint,
-            pressedColor: getEnteColorScheme(context).fillFaint,
             trailingIconIsMuted: true,
             trailingIcon: Icons.chevron_right,
             onTap: () async {
@@ -361,7 +358,6 @@ class EmailItemWidget extends StatelessWidget {
             ),
             leadingIcon: Icons.people_outline,
             menuItemColor: getEnteColorScheme(context).fillFaint,
-            pressedColor: getEnteColorScheme(context).fillFaint,
             trailingIconIsMuted: true,
             trailingIcon: Icons.chevron_right,
             onTap: () async {

+ 0 - 2
lib/ui/tools/debug/app_storage_viewer.dart

@@ -161,8 +161,6 @@ class _AppStorageViewerState extends State<AppStorageViewer> {
                             ),
                             menuItemColor:
                                 getEnteColorScheme(context).fillFaint,
-                            pressedColor:
-                                getEnteColorScheme(context).fillFaintPressed,
                             borderRadius: 8,
                             onTap: () async {
                               for (var pathItem in paths) {

+ 31 - 14
lib/ui/viewer/gallery/gallery_app_bar_widget.dart

@@ -18,6 +18,7 @@ import 'package:photos/services/sync_service.dart';
 import 'package:photos/services/update_service.dart';
 import 'package:photos/ui/actions/collection/collection_sharing_actions.dart';
 import 'package:photos/ui/common/rename_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';
@@ -138,21 +139,37 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
   }
 
   Future<dynamic> _leaveAlbum(BuildContext context) async {
-    final result = await showChoiceDialog(
-      context,
-      title: "Leave shared album",
-      body: "You will leave the album, and it will stop being visible to you",
-      firstButtonLabel: "Yes, leave",
-      isCritical: true,
-      firstButtonOnTap: () async {
-        await CollectionsService.instance.leaveAlbum(widget.collection!);
-        if (mounted) {
-          Navigator.of(context).pop();
-        }
-      },
+    final ButtonAction? result = await showActionSheet(
+      context: context,
+      buttons: [
+        ButtonWidget(
+          buttonType: ButtonType.critical,
+          isInAlert: true,
+          shouldStickToDarkTheme: true,
+          buttonAction: ButtonAction.first,
+          shouldSurfaceExecutionStates: true,
+          labelText: "Leave album",
+          onTap: () async {
+            await CollectionsService.instance.leaveAlbum(widget.collection!);
+          },
+        ),
+        const ButtonWidget(
+          buttonType: ButtonType.secondary,
+          buttonAction: ButtonAction.cancel,
+          isInAlert: true,
+          shouldStickToDarkTheme: true,
+          labelText: "Cancel",
+        )
+      ],
+      title: "Leave shared album?",
+      body: "Photos added by you will be removed from the album",
     );
-    if (result == ButtonAction.error) {
-      showGenericErrorDialog(context: context);
+    if (result != null && mounted) {
+      if (result == ButtonAction.error) {
+        showGenericErrorDialog(context: context);
+      } else if (result == ButtonAction.first) {
+        Navigator.of(context).pop();
+      }
     }
   }
 

+ 1 - 1
pubspec.yaml

@@ -12,7 +12,7 @@ description: ente photos application
 # Read more about iOS versioning at
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 
-version: 0.7.16+416
+version: 0.7.18+418
 
 environment:
   sdk: '>=2.17.0 <3.0.0'