Ver Fonte

Merge Collaboration Changes #831

Collaboration Changes
Neeraj Gupta há 2 anos atrás
pai
commit
741742b23d

+ 5 - 1
lib/services/collections_service.dart

@@ -592,12 +592,16 @@ class CollectionsService {
     }
     }
   }
   }
 
 
-  Future<void> createShareUrl(Collection collection) async {
+  Future<void> createShareUrl(
+    Collection collection, {
+    bool enableCollect = false,
+  }) async {
     try {
     try {
       final response = await _enteDio.post(
       final response = await _enteDio.post(
         "/collections/share-url",
         "/collections/share-url",
         data: {
         data: {
           "collectionID": collection.id,
           "collectionID": collection.id,
+          "enableCollect": enableCollect,
         },
         },
       );
       );
       collection.publicURLs?.add(PublicURL.fromMap(response.data["result"]));
       collection.publicURLs?.add(PublicURL.fromMap(response.data["result"]));

+ 6 - 2
lib/services/user_service.dart

@@ -117,6 +117,8 @@ class UserService {
     }
     }
   }
   }
 
 
+  // getPublicKey returns null value if email id is not
+  // associated with another ente account
   Future<String?> getPublicKey(String email) async {
   Future<String?> getPublicKey(String email) async {
     try {
     try {
       final response = await _enteDio.get(
       final response = await _enteDio.get(
@@ -127,8 +129,10 @@ class UserService {
       await PublicKeysDB.instance.setKey(PublicKey(email, publicKey));
       await PublicKeysDB.instance.setKey(PublicKey(email, publicKey));
       return publicKey;
       return publicKey;
     } on DioError catch (e) {
     } on DioError catch (e) {
-      _logger.info(e);
-      return null;
+      if (e.response != null && e.response?.statusCode == 404) {
+        return null;
+      }
+      rethrow;
     }
     }
   }
   }
 
 

+ 120 - 108
lib/ui/actions/collection/collection_sharing_actions.dart

@@ -13,8 +13,10 @@ import 'package:photos/services/hidden_service.dart';
 import 'package:photos/services/user_service.dart';
 import 'package:photos/services/user_service.dart';
 import 'package:photos/theme/colors.dart';
 import 'package:photos/theme/colors.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/theme/ente_theme.dart';
+import 'package:photos/ui/common/progress_dialog.dart';
 import 'package:photos/ui/components/action_sheet_widget.dart';
 import 'package:photos/ui/components/action_sheet_widget.dart';
 import 'package:photos/ui/components/button_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';
 import 'package:photos/ui/components/models/button_type.dart';
 import 'package:photos/ui/payment/subscription.dart';
 import 'package:photos/ui/payment/subscription.dart';
 import 'package:photos/utils/date_time_util.dart';
 import 'package:photos/utils/date_time_util.dart';
@@ -29,55 +31,22 @@ class CollectionActions {
 
 
   CollectionActions(this.collectionsService);
   CollectionActions(this.collectionsService);
 
 
-  Future<bool> publicLinkToggle(
+  Future<bool> enableUrl(
     BuildContext context,
     BuildContext context,
-    Collection collection,
-    bool enable,
-  ) async {
-    // confirm if user wants to disable the url
-    if (!enable) {
-      final ButtonAction? result = await showActionSheet(
-        context: context,
-        buttons: [
-          ButtonWidget(
-            buttonType: ButtonType.critical,
-            isInAlert: true,
-            shouldStickToDarkTheme: true,
-            buttonAction: ButtonAction.first,
-            shouldSurfaceExecutionStates: true,
-            labelText: "Yes, remove",
-            onTap: () async {
-              await CollectionsService.instance.disableShareUrl(collection);
-            },
-          ),
-          const ButtonWidget(
-            buttonType: ButtonType.secondary,
-            buttonAction: ButtonAction.cancel,
-            isInAlert: true,
-            shouldStickToDarkTheme: true,
-            labelText: "Cancel",
-          )
-        ],
-        title: "Remove public link",
-        body:
-            'This will remove the public link for accessing "${collection.name}".',
-      );
-      if (result != null) {
-        if (result == ButtonAction.error) {
-          showGenericErrorDialog(context: context);
-        }
-        return result == ButtonAction.first;
-      } else {
-        return false;
-      }
-    }
+    Collection collection, {
+    bool enableCollect = false,
+  }) async {
     final dialog = createProgressDialog(
     final dialog = createProgressDialog(
       context,
       context,
       "Creating link...",
       "Creating link...",
+      isDismissible: true,
     );
     );
     try {
     try {
       await dialog.show();
       await dialog.show();
-      await CollectionsService.instance.createShareUrl(collection);
+      await CollectionsService.instance.createShareUrl(
+        collection,
+        enableCollect: enableCollect,
+      );
       dialog.hide();
       dialog.hide();
       return true;
       return true;
     } catch (e) {
     } catch (e) {
@@ -92,6 +61,43 @@ class CollectionActions {
     }
     }
   }
   }
 
 
+  Future<bool> disableUrl(BuildContext context, Collection collection) async {
+    final ButtonAction? result = await showActionSheet(
+      context: context,
+      buttons: [
+        ButtonWidget(
+          buttonType: ButtonType.critical,
+          isInAlert: true,
+          shouldStickToDarkTheme: true,
+          buttonAction: ButtonAction.first,
+          shouldSurfaceExecutionStates: true,
+          labelText: "Yes, remove",
+          onTap: () async {
+            await CollectionsService.instance.disableShareUrl(collection);
+          },
+        ),
+        const ButtonWidget(
+          buttonType: ButtonType.secondary,
+          buttonAction: ButtonAction.cancel,
+          isInAlert: true,
+          shouldStickToDarkTheme: true,
+          labelText: "Cancel",
+        )
+      ],
+      title: "Remove public link",
+      body:
+          'This will remove the public link for accessing "${collection.name}".',
+    );
+    if (result != null) {
+      if (result == ButtonAction.error) {
+        showGenericErrorDialog(context: context);
+      }
+      return result == ButtonAction.first;
+    } else {
+      return false;
+    }
+  }
+
   Future<Collection?> createSharedCollectionLink(
   Future<Collection?> createSharedCollectionLink(
     BuildContext context,
     BuildContext context,
     List<File> files,
     List<File> files,
@@ -137,33 +143,44 @@ class CollectionActions {
   }
   }
 
 
   // removeParticipant remove the user from a share album
   // removeParticipant remove the user from a share album
-  Future<bool?> removeParticipant(
+  Future<bool> removeParticipant(
     BuildContext context,
     BuildContext context,
     Collection collection,
     Collection collection,
     User user,
     User user,
   ) async {
   ) async {
-    final result = await showChoiceDialog(
-      context,
-      title: "Remove",
-      body: "${user.email} will be removed",
-      firstButtonLabel: "Yes, remove",
-      firstButtonOnTap: () async {
-        try {
-          final newSharees = await CollectionsService.instance
-              .unshare(collection.id, user.email);
-          collection.updateSharees(newSharees);
-        } catch (e, s) {
-          Logger("EmailItemWidget").severe(e, s);
-          rethrow;
-        }
-      },
+    final ButtonAction? result = await showActionSheet(
+      context: context,
+      buttons: [
+        ButtonWidget(
+          buttonType: ButtonType.critical,
+          isInAlert: true,
+          shouldStickToDarkTheme: true,
+          buttonAction: ButtonAction.first,
+          shouldSurfaceExecutionStates: true,
+          labelText: "Yes, remove",
+          onTap: () async {
+            final newSharees = await CollectionsService.instance
+                .unshare(collection.id, user.email);
+            collection.updateSharees(newSharees);
+          },
+        ),
+        const ButtonWidget(
+          buttonType: ButtonType.secondary,
+          buttonAction: ButtonAction.cancel,
+          isInAlert: true,
+          shouldStickToDarkTheme: true,
+          labelText: "Cancel",
+        )
+      ],
+      title: "Remove?",
+      body: '${user.email} will be removed from this shared album\n\nAny '
+          'photos added by them will also be removed from the album',
     );
     );
-    if (result == ButtonAction.error) {
-      await showGenericErrorDialog(context: context);
-      return false;
-    }
-    if (result == ButtonAction.first) {
-      return true;
+    if (result != null) {
+      if (result == ButtonAction.error) {
+        showGenericErrorDialog(context: context);
+      }
+      return result == ButtonAction.first;
     } else {
     } else {
       return false;
       return false;
     }
     }
@@ -174,6 +191,7 @@ class CollectionActions {
     Collection collection,
     Collection collection,
     String email, {
     String email, {
     CollectionParticipantRole role = CollectionParticipantRole.viewer,
     CollectionParticipantRole role = CollectionParticipantRole.viewer,
+    bool showProgress = false,
     String? publicKey,
     String? publicKey,
   }) async {
   }) async {
     if (!isValidEmail(email)) {
     if (!isValidEmail(email)) {
@@ -186,76 +204,70 @@ class CollectionActions {
     } else if (email == Configuration.instance.getEmail()) {
     } else if (email == Configuration.instance.getEmail()) {
       await showErrorDialog(context, "Oops", "You cannot share with yourself");
       await showErrorDialog(context, "Oops", "You cannot share with yourself");
       return null;
       return null;
-    } else {
-      // if (collection.getSharees().any((user) => user.email == email)) {
-      //   showErrorDialog(
-      //     context,
-      //     "Oops",
-      //     "You're already sharing this with " + email,
-      //   );
-      //   return null;
-      // }
     }
     }
+
+    ProgressDialog? dialog;
     if (publicKey == null) {
     if (publicKey == null) {
-      final dialog = createProgressDialog(context, "Searching for user...");
-      await dialog.show();
+      if (showProgress) {
+        dialog = createProgressDialog(context, "Searching for user...");
+        await dialog.show();
+      }
+
       try {
       try {
         publicKey = await UserService.instance.getPublicKey(email);
         publicKey = await UserService.instance.getPublicKey(email);
-        await dialog.hide();
+        await dialog?.hide();
       } catch (e) {
       } catch (e) {
+        await dialog?.hide();
         logger.severe("Failed to get public key", e);
         logger.severe("Failed to get public key", e);
         showGenericErrorDialog(context: context);
         showGenericErrorDialog(context: context);
-        await dialog.hide();
+        return false;
       }
       }
     }
     }
     // getPublicKey can return null
     // getPublicKey can return null
     // ignore: unnecessary_null_comparison
     // ignore: unnecessary_null_comparison
     if (publicKey == null || publicKey == '') {
     if (publicKey == null || publicKey == '') {
-      final dialog = AlertDialog(
-        title: const Text("Invite to ente?"),
-        content: Text(
-          "Looks like " +
-              email +
-              " hasn't signed up for ente yet. would you like to invite them?",
-          style: const TextStyle(
-            height: 1.4,
-          ),
-        ),
-        actions: [
-          TextButton(
-            child: Text(
-              "Invite",
-              style: TextStyle(
-                color: Theme.of(context).colorScheme.greenAlternative,
-              ),
-            ),
-            onPressed: () {
+      // 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
+      await showDialogWidget(
+        context: context,
+        title: "Invite to ente",
+        icon: Icons.info_outline,
+        body: "$email does not have an ente account\n\nSend them an invite to"
+            " add them after they sign up",
+        isDismissible: true,
+        buttons: [
+          ButtonWidget(
+            buttonType: ButtonType.neutral,
+            icon: Icons.adaptive.share,
+            labelText: "Send invite",
+            isInAlert: true,
+            onTap: () async {
               shareText(
               shareText(
-                "Hey, I have some photos to share. Please install https://ente.io so that I can share them privately.",
+                "Download ente so we can easily share original quality photos"
+                " and videos\n\nhttps://ente.io/#download",
               );
               );
             },
             },
           ),
           ),
         ],
         ],
       );
       );
-      await showDialog(
-        context: context,
-        builder: (BuildContext context) {
-          return dialog;
-        },
-      );
       return null;
       return null;
     } else {
     } else {
-      final dialog = createProgressDialog(context, "Sharing...");
-      await dialog.show();
+      if (showProgress) {
+        dialog = createProgressDialog(
+          context,
+          "Sharing...",
+          isDismissible: true,
+        );
+        await dialog.show();
+      }
       try {
       try {
         final newSharees = await CollectionsService.instance
         final newSharees = await CollectionsService.instance
             .share(collection.id, email, publicKey, role);
             .share(collection.id, email, publicKey, role);
+        await dialog?.hide();
         collection.updateSharees(newSharees);
         collection.updateSharees(newSharees);
-        await dialog.hide();
-        showShortToast(context, "Shared successfully!");
         return true;
         return true;
       } catch (e) {
       } catch (e) {
-        await dialog.hide();
+        await dialog?.hide();
         if (e is SharingNotPermittedForFreeAccountsError) {
         if (e is SharingNotPermittedForFreeAccountsError) {
           _showUnSupportedAlert(context);
           _showUnSupportedAlert(context);
         } else {
         } else {

+ 9 - 3
lib/ui/create_collection_sheet.dart

@@ -361,7 +361,12 @@ class _CreateCollectionSheetState extends State<CreateCollectionSheet> {
   }
   }
 
 
   Future<bool> _addToCollection(int collectionID) async {
   Future<bool> _addToCollection(int collectionID) async {
-    final dialog = createProgressDialog(context, "Uploading files to album...");
+    final dialog = createProgressDialog(
+      context,
+      "Uploading files to album"
+      "...",
+      isDismissible: true,
+    );
     await dialog.show();
     await dialog.show();
     try {
     try {
       final List<File> files = [];
       final List<File> files = [];
@@ -434,7 +439,7 @@ class _CreateCollectionSheetState extends State<CreateCollectionSheet> {
     final String message = widget.actionType == CollectionActionType.moveFiles
     final String message = widget.actionType == CollectionActionType.moveFiles
         ? "Moving files to album..."
         ? "Moving files to album..."
         : "Unhiding files to album";
         : "Unhiding files to album";
-    final dialog = createProgressDialog(context, message);
+    final dialog = createProgressDialog(context, message, isDismissible: true);
     await dialog.show();
     await dialog.show();
     try {
     try {
       final int fromCollectionID =
       final int fromCollectionID =
@@ -462,7 +467,8 @@ class _CreateCollectionSheetState extends State<CreateCollectionSheet> {
   }
   }
 
 
   Future<bool> _restoreFilesToCollection(int toCollectionID) async {
   Future<bool> _restoreFilesToCollection(int toCollectionID) async {
-    final dialog = createProgressDialog(context, "Restoring files...");
+    final dialog = createProgressDialog(context, "Restoring files...",
+        isDismissible: true);
     await dialog.show();
     await dialog.show();
     try {
     try {
       await CollectionsService.instance
       await CollectionsService.instance

+ 62 - 86
lib/ui/sharing/add_partipant_page.dart

@@ -5,28 +5,29 @@ import 'package:photos/models/collection.dart';
 import 'package:photos/services/collections_service.dart';
 import 'package:photos/services/collections_service.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/actions/collection/collection_sharing_actions.dart';
 import 'package:photos/ui/actions/collection/collection_sharing_actions.dart';
-import 'package:photos/ui/common/gradient_button.dart';
+import 'package:photos/ui/components/button_widget.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/divider_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_item_widget.dart';
 import 'package:photos/ui/components/menu_section_description_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/menu_section_title.dart';
+import 'package:photos/ui/components/models/button_type.dart';
 import 'package:photos/ui/sharing/user_avator_widget.dart';
 import 'package:photos/ui/sharing/user_avator_widget.dart';
 
 
 class AddParticipantPage extends StatefulWidget {
 class AddParticipantPage extends StatefulWidget {
   final Collection collection;
   final Collection collection;
+  final bool isAddingViewer;
 
 
-  const AddParticipantPage(this.collection, {super.key});
+  const AddParticipantPage(this.collection, this.isAddingViewer, {super.key});
 
 
   @override
   @override
   State<StatefulWidget> createState() => _AddParticipantPage();
   State<StatefulWidget> createState() => _AddParticipantPage();
 }
 }
 
 
 class _AddParticipantPage extends State<AddParticipantPage> {
 class _AddParticipantPage extends State<AddParticipantPage> {
-  late bool selectAsViewer;
   String selectedEmail = '';
   String selectedEmail = '';
   String _email = '';
   String _email = '';
-  bool hideListOfEmails = false;
+  bool isEmailListEmpty = false;
   bool _emailIsValid = false;
   bool _emailIsValid = false;
   bool isKeypadOpen = false;
   bool isKeypadOpen = false;
   late CollectionActions collectionActions;
   late CollectionActions collectionActions;
@@ -37,7 +38,6 @@ class _AddParticipantPage extends State<AddParticipantPage> {
 
 
   @override
   @override
   void initState() {
   void initState() {
-    selectAsViewer = true;
     collectionActions = CollectionActions(CollectionsService.instance);
     collectionActions = CollectionActions(CollectionsService.instance);
     super.initState();
     super.initState();
   }
   }
@@ -52,13 +52,13 @@ class _AddParticipantPage extends State<AddParticipantPage> {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
     isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
     final enteTextTheme = getEnteTextTheme(context);
     final enteTextTheme = getEnteTextTheme(context);
+    final enteColorScheme = getEnteColorScheme(context);
     final List<User> suggestedUsers = _getSuggestedUser();
     final List<User> suggestedUsers = _getSuggestedUser();
-    hideListOfEmails = suggestedUsers.isEmpty;
-    debugPrint("hide list $hideListOfEmails");
+    isEmailListEmpty = suggestedUsers.isEmpty;
     return Scaffold(
     return Scaffold(
       resizeToAvoidBottomInset: isKeypadOpen,
       resizeToAvoidBottomInset: isKeypadOpen,
       appBar: AppBar(
       appBar: AppBar(
-        title: const Text("Add people"),
+        title: Text(widget.isAddingViewer ? "Add viewer" : "Add collaborator"),
       ),
       ),
       body: Column(
       body: Column(
         mainAxisAlignment: MainAxisAlignment.start,
         mainAxisAlignment: MainAxisAlignment.start,
@@ -69,7 +69,8 @@ class _AddParticipantPage extends State<AddParticipantPage> {
             padding: const EdgeInsets.symmetric(horizontal: 16.0),
             padding: const EdgeInsets.symmetric(horizontal: 16.0),
             child: Text(
             child: Text(
               "Add a new email",
               "Add a new email",
-              style: enteTextTheme.body,
+              style: enteTextTheme.small
+                  .copyWith(color: enteColorScheme.textMuted),
             ),
             ),
           ),
           ),
           const SizedBox(height: 4),
           const SizedBox(height: 4),
@@ -77,20 +78,32 @@ class _AddParticipantPage extends State<AddParticipantPage> {
             padding: const EdgeInsets.symmetric(horizontal: 16.0),
             padding: const EdgeInsets.symmetric(horizontal: 16.0),
             child: _getEmailField(),
             child: _getEmailField(),
           ),
           ),
-          (hideListOfEmails)
-              ? const Expanded(child: SizedBox())
+          (isEmailListEmpty && widget.isAddingViewer)
+              ? const Expanded(child: SizedBox.shrink())
               : Expanded(
               : Expanded(
                   child: Padding(
                   child: Padding(
                     padding: const EdgeInsets.symmetric(horizontal: 16.0),
                     padding: const EdgeInsets.symmetric(horizontal: 16.0),
                     child: Column(
                     child: Column(
                       children: [
                       children: [
-                        const SizedBox(height: 24),
-                        const MenuSectionTitle(
-                          title: "or pick an existing one",
-                        ),
+                        !isEmailListEmpty
+                            ? const MenuSectionTitle(
+                                title: "Or pick an existing one",
+                              )
+                            : const SizedBox.shrink(),
                         Expanded(
                         Expanded(
                           child: ListView.builder(
                           child: ListView.builder(
                             itemBuilder: (context, index) {
                             itemBuilder: (context, index) {
+                              if (index >= suggestedUsers.length) {
+                                return const Padding(
+                                  padding: EdgeInsets.symmetric(
+                                    vertical: 8.0,
+                                  ),
+                                  child: MenuSectionDescriptionWidget(
+                                    content:
+                                        "Collaborators can add photos and videos to the shared album.",
+                                  ),
+                                );
+                              }
                               final currentUser = suggestedUsers[index];
                               final currentUser = suggestedUsers[index];
                               return Column(
                               return Column(
                                 children: [
                                 children: [
@@ -136,8 +149,8 @@ class _AddParticipantPage extends State<AddParticipantPage> {
                                 ],
                                 ],
                               );
                               );
                             },
                             },
-                            itemCount: suggestedUsers.length,
-
+                            itemCount: suggestedUsers.length +
+                                (widget.isAddingViewer ? 0 : 1),
                             // physics: const ClampingScrollPhysics(),
                             // physics: const ClampingScrollPhysics(),
                           ),
                           ),
                         ),
                         ),
@@ -145,9 +158,6 @@ class _AddParticipantPage extends State<AddParticipantPage> {
                     ),
                     ),
                   ),
                   ),
                 ),
                 ),
-          const DividerWidget(
-            dividerType: DividerType.solid,
-          ),
           SafeArea(
           SafeArea(
             child: Padding(
             child: Padding(
               padding: const EdgeInsets.only(
               padding: const EdgeInsets.only(
@@ -159,70 +169,34 @@ class _AddParticipantPage extends State<AddParticipantPage> {
               child: Column(
               child: Column(
                 crossAxisAlignment: CrossAxisAlignment.start,
                 crossAxisAlignment: CrossAxisAlignment.start,
                 children: [
                 children: [
-                  const MenuSectionTitle(title: "Add as"),
-                  MenuItemWidget(
-                    captionedTextWidget: const CaptionedTextWidget(
-                      title: "Collaborator",
-                    ),
-                    leadingIcon: Icons.edit_outlined,
-                    menuItemColor: getEnteColorScheme(context).fillFaint,
-                    pressedColor: getEnteColorScheme(context).fillFaint,
-                    trailingIcon: !selectAsViewer ? Icons.check : null,
-                    onTap: () async {
-                      setState(() => {selectAsViewer = false});
-                    },
-                    isBottomBorderRadiusRemoved: true,
-                  ),
-                  DividerWidget(
-                    dividerType: DividerType.menu,
-                    bgColor: getEnteColorScheme(context).fillFaint,
-                  ),
-                  MenuItemWidget(
-                    captionedTextWidget: const CaptionedTextWidget(
-                      title: "Viewer",
-                    ),
-                    leadingIcon: Icons.photo_outlined,
-                    menuItemColor: getEnteColorScheme(context).fillFaint,
-                    pressedColor: getEnteColorScheme(context).fillFaint,
-                    trailingIcon: selectAsViewer ? Icons.check : null,
-                    onTap: () async {
-                      setState(() => {selectAsViewer = true});
-                      // showShortToast(context, "yet to implement");
-                    },
-                    isTopBorderRadiusRemoved: true,
-                  ),
-                  !isKeypadOpen
-                      ? const MenuSectionDescriptionWidget(
-                          content:
-                              "Collaborators can add photos and videos to the shared album.",
-                        )
-                      : const SizedBox.shrink(),
-                  const SizedBox(height: 12),
-                  SizedBox(
-                    width: double.infinity,
-                    child: GradientButton(
-                      onTap: (selectedEmail == '' && !_emailIsValid)
-                          ? null
-                          : () async {
-                              final emailToAdd =
-                                  selectedEmail == '' ? _email : selectedEmail;
-                              final result =
-                                  await collectionActions.addEmailToCollection(
-                                context,
-                                widget.collection,
-                                emailToAdd,
-                                role: selectAsViewer
-                                    ? CollectionParticipantRole.viewer
-                                    : CollectionParticipantRole.collaborator,
-                              );
-                              if (result != null && result && mounted) {
-                                Navigator.of(context).pop(true);
-                              }
-                            },
-                      text: selectAsViewer ? "Add viewer" : "Add collaborator",
-                    ),
-                  ),
                   const SizedBox(height: 8),
                   const SizedBox(height: 8),
+                  ButtonWidget(
+                    buttonType: ButtonType.primary,
+                    buttonSize: ButtonSize.large,
+                    labelText: widget.isAddingViewer
+                        ? "Add viewer"
+                        : "Add collaborator",
+                    isDisabled: (selectedEmail == '' && !_emailIsValid),
+                    onTap: (selectedEmail == '' && !_emailIsValid)
+                        ? null
+                        : () async {
+                            final emailToAdd =
+                                selectedEmail == '' ? _email : selectedEmail;
+                            final result =
+                                await collectionActions.addEmailToCollection(
+                              context,
+                              widget.collection,
+                              emailToAdd,
+                              role: widget.isAddingViewer
+                                  ? CollectionParticipantRole.viewer
+                                  : CollectionParticipantRole.collaborator,
+                            );
+                            if (result != null && result && mounted) {
+                              Navigator.of(context).pop(true);
+                            }
+                          },
+                  ),
+                  const SizedBox(height: 20),
                 ],
                 ],
               ),
               ),
             ),
             ),
@@ -317,9 +291,11 @@ class _AddParticipantPage extends State<AddParticipantPage> {
       }
       }
     }
     }
     if (_textController.text.trim().isNotEmpty) {
     if (_textController.text.trim().isNotEmpty) {
-      suggestedUsers.removeWhere((element) => !element.email
-          .toLowerCase()
-          .contains(_textController.text.trim().toLowerCase()));
+      suggestedUsers.removeWhere(
+        (element) => !element.email
+            .toLowerCase()
+            .contains(_textController.text.trim().toLowerCase()),
+      );
     }
     }
     suggestedUsers.sort((a, b) => a.email.compareTo(b.email));
     suggestedUsers.sort((a, b) => a.email.compareTo(b.email));
 
 

+ 7 - 4
lib/ui/sharing/album_participants_page.dart

@@ -51,7 +51,7 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
   Future<void> _navigateToAddUser(bool addingViewer) async {
   Future<void> _navigateToAddUser(bool addingViewer) async {
     await routeToPage(
     await routeToPage(
       context,
       context,
-      AddParticipantPage(widget.collection),
+      AddParticipantPage(widget.collection, addingViewer),
     );
     );
     if (mounted) {
     if (mounted) {
       setState(() => {});
       setState(() => {});
@@ -72,7 +72,9 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
     final splitResult =
     final splitResult =
         widget.collection.getSharees().splitMatch((x) => x.isViewer);
         widget.collection.getSharees().splitMatch((x) => x.isViewer);
     final List<User> viewers = splitResult.matched;
     final List<User> viewers = splitResult.matched;
+    viewers.sort((a, b) => a.email.compareTo(b.email));
     final List<User> collaborators = splitResult.unmatched;
     final List<User> collaborators = splitResult.unmatched;
+    collaborators.sort((a, b) => a.email.compareTo(b.email));
 
 
     return Scaffold(
     return Scaffold(
       body: CustomScrollView(
       body: CustomScrollView(
@@ -174,8 +176,9 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
                   } else if (index == (1 + collaborators.length) && isOwner) {
                   } else if (index == (1 + collaborators.length) && isOwner) {
                     return MenuItemWidget(
                     return MenuItemWidget(
                       captionedTextWidget: CaptionedTextWidget(
                       captionedTextWidget: CaptionedTextWidget(
-                        title:
-                            collaborators.isNotEmpty ? "Add more" : "Add email",
+                        title: collaborators.isNotEmpty
+                            ? "Add more"
+                            : "Add collaborator",
                         makeTextBold: true,
                         makeTextBold: true,
                       ),
                       ),
                       leadingIcon: Icons.add_outlined,
                       leadingIcon: Icons.add_outlined,
@@ -249,7 +252,7 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
                   } else if (index == (1 + viewers.length) && isOwner) {
                   } else if (index == (1 + viewers.length) && isOwner) {
                     return MenuItemWidget(
                     return MenuItemWidget(
                       captionedTextWidget: CaptionedTextWidget(
                       captionedTextWidget: CaptionedTextWidget(
-                        title: viewers.isNotEmpty ? "Add more" : "Add Viewer",
+                        title: viewers.isNotEmpty ? "Add more" : "Add viewer",
                         makeTextBold: true,
                         makeTextBold: true,
                       ),
                       ),
                       leadingIcon: Icons.add_outlined,
                       leadingIcon: Icons.add_outlined,

+ 3 - 1
lib/ui/sharing/manage_album_participant.dart

@@ -80,6 +80,7 @@ class _ManageIndividualParticipantState
                         widget.collection,
                         widget.collection,
                         widget.user.email,
                         widget.user.email,
                         role: CollectionParticipantRole.collaborator,
                         role: CollectionParticipantRole.collaborator,
+                        showProgress: true,
                       );
                       );
                       if ((result ?? false) && mounted) {
                       if ((result ?? false) && mounted) {
                         widget.user.role = CollectionParticipantRole
                         widget.user.role = CollectionParticipantRole
@@ -112,6 +113,7 @@ class _ManageIndividualParticipantState
                         widget.collection,
                         widget.collection,
                         widget.user.email,
                         widget.user.email,
                         role: CollectionParticipantRole.viewer,
                         role: CollectionParticipantRole.viewer,
+                        showProgress: true,
                       );
                       );
                       if ((result ?? false) && mounted) {
                       if ((result ?? false) && mounted) {
                         widget.user.role =
                         widget.user.role =
@@ -144,7 +146,7 @@ class _ManageIndividualParticipantState
                   widget.user,
                   widget.user,
                 );
                 );
 
 
-                if ((result ?? false) && mounted) {
+                if ((result) && mounted) {
                   Navigator.of(context).pop(true);
                   Navigator.of(context).pop(true);
                 }
                 }
               },
               },

+ 1 - 2
lib/ui/sharing/manage_links_widget.dart

@@ -226,10 +226,9 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
                     menuItemColor: getEnteColorScheme(context).fillFaint,
                     menuItemColor: getEnteColorScheme(context).fillFaint,
                     pressedColor: getEnteColorScheme(context).fillFaint,
                     pressedColor: getEnteColorScheme(context).fillFaint,
                     onTap: () async {
                     onTap: () async {
-                      final bool result = await sharingActions.publicLinkToggle(
+                      final bool result = await sharingActions.disableUrl(
                         context,
                         context,
                         widget.collection!,
                         widget.collection!,
-                        false,
                       );
                       );
                       if (result && mounted) {
                       if (result && mounted) {
                         Navigator.of(context).pop();
                         Navigator.of(context).pop();

+ 60 - 7
lib/ui/sharing/share_collection_page.dart

@@ -66,8 +66,8 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
 
 
     children.add(
     children.add(
       MenuItemWidget(
       MenuItemWidget(
-        captionedTextWidget: CaptionedTextWidget(
-          title: _sharees.isEmpty ? "Add email" : "Add more",
+        captionedTextWidget: const CaptionedTextWidget(
+          title: "Add viewer",
           makeTextBold: true,
           makeTextBold: true,
         ),
         ),
         leadingIcon: Icons.add,
         leadingIcon: Icons.add,
@@ -75,8 +75,39 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
         pressedColor: getEnteColorScheme(context).fillFaint,
         pressedColor: getEnteColorScheme(context).fillFaint,
         borderRadius: 4.0,
         borderRadius: 4.0,
         isTopBorderRadiusRemoved: _sharees.isNotEmpty,
         isTopBorderRadiusRemoved: _sharees.isNotEmpty,
+        isBottomBorderRadiusRemoved: true,
         onTap: () async {
         onTap: () async {
-          routeToPage(context, AddParticipantPage(widget.collection)).then(
+          routeToPage(
+            context,
+            AddParticipantPage(widget.collection, true),
+          ).then(
+            (value) => {
+              if (mounted) {setState(() => {})}
+            },
+          );
+        },
+      ),
+    );
+    children.add(
+      DividerWidget(
+        dividerType: DividerType.menu,
+        bgColor: getEnteColorScheme(context).fillFaint,
+      ),
+    );
+    children.add(
+      MenuItemWidget(
+        captionedTextWidget: const CaptionedTextWidget(
+          title: "Add collaborator",
+          makeTextBold: true,
+        ),
+        leadingIcon: Icons.add,
+        menuItemColor: getEnteColorScheme(context).fillFaint,
+        pressedColor: getEnteColorScheme(context).fillFaint,
+        borderRadius: 4.0,
+        isTopBorderRadiusRemoved: _sharees.isNotEmpty,
+        onTap: () async {
+          routeToPage(context, AddParticipantPage(widget.collection, false))
+              .then(
             (value) => {
             (value) => {
               if (mounted) {setState(() => {})}
               if (mounted) {setState(() => {})}
             },
             },
@@ -198,26 +229,48 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
         ],
         ],
       );
       );
     } else {
     } else {
-      children.add(
+      children.addAll([
         MenuItemWidget(
         MenuItemWidget(
           captionedTextWidget: const CaptionedTextWidget(
           captionedTextWidget: const CaptionedTextWidget(
             title: "Create public link",
             title: "Create public link",
+            makeTextBold: true,
           ),
           ),
           leadingIcon: Icons.link,
           leadingIcon: Icons.link,
           menuItemColor: getEnteColorScheme(context).fillFaint,
           menuItemColor: getEnteColorScheme(context).fillFaint,
           pressedColor: getEnteColorScheme(context).fillFaint,
           pressedColor: getEnteColorScheme(context).fillFaint,
+          isBottomBorderRadiusRemoved: true,
           onTap: () async {
           onTap: () async {
-            final bool result = await collectionActions.publicLinkToggle(
+            final bool result =
+                await collectionActions.enableUrl(context, widget.collection);
+            if (result && mounted) {
+              setState(() => {});
+            }
+          },
+        ),
+        DividerWidget(
+          dividerType: DividerType.menu,
+          bgColor: getEnteColorScheme(context).fillFaint,
+        ),
+        MenuItemWidget(
+          captionedTextWidget: const CaptionedTextWidget(
+            title: "Collect photos",
+            makeTextBold: true,
+          ),
+          leadingIcon: Icons.link,
+          menuItemColor: getEnteColorScheme(context).fillFaint,
+          pressedColor: getEnteColorScheme(context).fillFaint,
+          onTap: () async {
+            final bool result = await collectionActions.enableUrl(
               context,
               context,
               widget.collection,
               widget.collection,
-              true,
+              enableCollect: true,
             );
             );
             if (result && mounted) {
             if (result && mounted) {
               setState(() => {});
               setState(() => {});
             }
             }
           },
           },
         ),
         ),
-      );
+      ]);
       if (_sharees.isEmpty && !hasUrl) {
       if (_sharees.isEmpty && !hasUrl) {
         children.add(
         children.add(
           const MenuSectionDescriptionWidget(
           const MenuSectionDescriptionWidget(