Procházet zdrojové kódy

Merge branch 'main' into fix_radius

Neeraj Gupta před 2 roky
rodič
revize
77eeaa4c06

+ 4 - 26
lib/ui/actions/collection/collection_sharing_actions.dart

@@ -36,21 +36,13 @@ class CollectionActions {
     Collection collection, {
     bool enableCollect = false,
   }) async {
-    final dialog = createProgressDialog(
-      context,
-      "Creating link...",
-      isDismissible: true,
-    );
     try {
-      await dialog.show();
       await CollectionsService.instance.createShareUrl(
         collection,
         enableCollect: enableCollect,
       );
-      dialog.hide();
       return true;
     } catch (e) {
-      dialog.hide();
       if (e is SharingNotPermittedForFreeAccountsError) {
         _showUnSupportedAlert(context);
       } else {
@@ -372,25 +364,11 @@ class CollectionActions {
     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",
-        )
-      ],
+    final ButtonAction? result = await showChoiceActionSheet(
+      context,
+      isCritical: true,
       title: "Delete shared album?",
+      firstButtonLabel: "Delete 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",
     );

+ 2 - 2
lib/ui/advanced_settings_screen.dart

@@ -6,7 +6,7 @@ import 'package:photos/events/force_reload_home_gallery_event.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/icon_button_widget.dart';
-import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
 import 'package:photos/ui/components/title_bar_title_widget.dart';
 import 'package:photos/ui/components/title_bar_widget.dart';
 import 'package:photos/ui/tools/debug/app_storage_viewer.dart';
@@ -98,7 +98,7 @@ class _AdvancedSettingsScreenState extends State<AdvancedSettingsScreen> {
                               ),
                               singleBorderRadius: 8,
                               alignCaptionedTextToLeft: true,
-                              onTap: () {
+                              onTap: () async {
                                 routeToPage(context, const AppStorageViewer());
                               },
                             ),

+ 1 - 1
lib/ui/backup_settings_screen.dart

@@ -6,7 +6,7 @@ import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/divider_widget.dart';
 import 'package:photos/ui/components/icon_button_widget.dart';
-import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
 import 'package:photos/ui/components/menu_section_description_widget.dart';
 import 'package:photos/ui/components/title_bar_title_widget.dart';
 import 'package:photos/ui/components/title_bar_widget.dart';

+ 1 - 1
lib/ui/components/album_list_item_widget.dart

@@ -37,7 +37,7 @@ class AlbumListItemWidget extends StatelessWidget {
                         ? ThumbnailWidget(
                             item.thumbnail,
                             showFavForAlbumOnly: true,
-                            shouldShowOwnerAvatar: true,
+                            shouldShowOwnerAvatar: false,
                           )
                         : const NoThumbnailWidget(
                             addBorder: false,

+ 1 - 1
lib/ui/components/expandable_menu_item_widget.dart

@@ -2,7 +2,7 @@ import 'package:expandable/expandable.dart';
 import 'package:flutter/material.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
-import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
 import 'package:photos/ui/settings/common_settings.dart';
 import 'package:photos/ui/settings/inherited_settings_state.dart';
 

+ 173 - 0
lib/ui/components/menu_item_widget/menu_item_child_widgets.dart

@@ -0,0 +1,173 @@
+import 'package:flutter/material.dart';
+import 'package:photos/theme/ente_theme.dart';
+import 'package:photos/ui/common/loading_widget.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
+
+class TrailingWidget extends StatefulWidget {
+  final ValueNotifier executionStateNotifier;
+  final IconData? trailingIcon;
+  final Color? trailingIconColor;
+  final Widget? trailingWidget;
+  final bool trailingIconIsMuted;
+  final double trailingExtraMargin;
+  final bool showExecutionStates;
+  const TrailingWidget({
+    required this.executionStateNotifier,
+    this.trailingIcon,
+    this.trailingIconColor,
+    this.trailingWidget,
+    required this.trailingIconIsMuted,
+    required this.trailingExtraMargin,
+    required this.showExecutionStates,
+    super.key,
+  });
+  @override
+  State<TrailingWidget> createState() => _TrailingWidgetState();
+}
+
+class _TrailingWidgetState extends State<TrailingWidget> {
+  Widget? trailingWidget;
+  @override
+  void initState() {
+    widget.showExecutionStates
+        ? widget.executionStateNotifier.addListener(_executionStateListener)
+        : null;
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    widget.executionStateNotifier.removeListener(_executionStateListener);
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    if (trailingWidget == null || !widget.showExecutionStates) {
+      _setTrailingIcon();
+    }
+    return AnimatedSwitcher(
+      duration: const Duration(milliseconds: 175),
+      switchInCurve: Curves.easeInExpo,
+      switchOutCurve: Curves.easeOutExpo,
+      child: trailingWidget,
+    );
+  }
+
+  void _executionStateListener() {
+    final colorScheme = getEnteColorScheme(context);
+    setState(() {
+      if (widget.executionStateNotifier.value == ExecutionState.idle) {
+        _setTrailingIcon();
+      } else if (widget.executionStateNotifier.value ==
+          ExecutionState.inProgress) {
+        trailingWidget = EnteLoadingWidget(
+          color: colorScheme.strokeMuted,
+        );
+      } else if (widget.executionStateNotifier.value ==
+          ExecutionState.successful) {
+        trailingWidget = Icon(
+          Icons.check_outlined,
+          size: 22,
+          color: colorScheme.primary500,
+        );
+      } else {
+        trailingWidget = const SizedBox.shrink();
+      }
+    });
+  }
+
+  void _setTrailingIcon() {
+    if (widget.trailingIcon != null) {
+      trailingWidget = Padding(
+        padding: EdgeInsets.only(
+          right: widget.trailingExtraMargin,
+        ),
+        child: Icon(
+          widget.trailingIcon,
+          color: widget.trailingIconIsMuted
+              ? getEnteColorScheme(context).strokeMuted
+              : widget.trailingIconColor,
+        ),
+      );
+    } else {
+      trailingWidget = widget.trailingWidget ?? const SizedBox.shrink();
+    }
+  }
+}
+
+class ExpansionTrailingIcon extends StatelessWidget {
+  final bool isExpanded;
+  final IconData? trailingIcon;
+  final Color? trailingIconColor;
+  const ExpansionTrailingIcon({
+    required this.isExpanded,
+    this.trailingIcon,
+    this.trailingIconColor,
+    super.key,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return AnimatedOpacity(
+      duration: const Duration(milliseconds: 100),
+      curve: Curves.easeInOut,
+      opacity: isExpanded ? 0 : 1,
+      child: AnimatedSwitcher(
+        transitionBuilder: (child, animation) {
+          return ScaleTransition(scale: animation, child: child);
+        },
+        duration: const Duration(milliseconds: 200),
+        switchInCurve: Curves.easeOut,
+        child: isExpanded
+            ? const SizedBox.shrink()
+            : Icon(
+                trailingIcon,
+                color: trailingIconColor,
+              ),
+      ),
+    );
+  }
+}
+
+class LeadingWidget extends StatelessWidget {
+  final IconData? leadingIcon;
+  final Color? leadingIconColor;
+
+  final Widget? leadingIconWidget;
+  // leadIconSize deafult value is 20.
+  final double leadingIconSize;
+  const LeadingWidget({
+    required this.leadingIconSize,
+    this.leadingIcon,
+    this.leadingIconColor,
+    this.leadingIconWidget,
+    super.key,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.only(right: 10),
+      child: SizedBox(
+        height: leadingIconSize,
+        width: leadingIconSize,
+        child: leadingIcon == null
+            ? (leadingIconWidget != null
+                ? FittedBox(
+                    fit: BoxFit.contain,
+                    child: leadingIconWidget,
+                  )
+                : const SizedBox.shrink())
+            : FittedBox(
+                fit: BoxFit.contain,
+                child: Icon(
+                  leadingIcon,
+                  color: leadingIconColor ??
+                      getEnteColorScheme(context).strokeBase,
+                ),
+              ),
+      ),
+    );
+  }
+}

+ 90 - 57
lib/ui/components/menu_item_widget.dart → lib/ui/components/menu_item_widget/menu_item_widget.dart

@@ -1,7 +1,17 @@
 import 'package:expandable/expandable.dart';
 import 'package:flutter/material.dart';
-import 'package:photos/ente_theme_data.dart';
 import 'package:photos/theme/ente_theme.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_child_widgets.dart';
+import 'package:photos/utils/debouncer.dart';
+
+enum ExecutionState {
+  idle,
+  inProgress,
+  error,
+  successful;
+}
+
+typedef FutureVoidCallback = Future<void> Function();
 
 class MenuItemWidget extends StatefulWidget {
   final Widget captionedTextWidget;
@@ -26,7 +36,7 @@ class MenuItemWidget extends StatefulWidget {
 
   /// If provided, add this much extra spacing to the right of the trailing icon.
   final double trailingExtraMargin;
-  final VoidCallback? onTap;
+  final FutureVoidCallback? onTap;
   final VoidCallback? onDoubleTap;
   final Color? menuItemColor;
   final bool alignCaptionedTextToLeft;
@@ -45,6 +55,18 @@ class MenuItemWidget extends StatefulWidget {
   /// disable gesture detector if not used
   final bool isGestureDetectorDisabled;
 
+  ///Success state will not be shown if this flag is set to true, only idle and
+  ///loading state
+  final bool showOnlyLoadingState;
+
+  final bool surfaceExecutionStates;
+
+  ///To show success state even when execution time < debouce time, set this
+  ///flag to true. If the loading state needs to be shown and success state not,
+  ///set the showOnlyLoadingState flag to true, setting this flag to false won't
+  ///help.
+  final bool alwaysShowSuccessState;
+
   const MenuItemWidget({
     required this.captionedTextWidget,
     this.isExpandable = false,
@@ -68,6 +90,9 @@ class MenuItemWidget extends StatefulWidget {
     this.isBottomBorderRadiusRemoved = false,
     this.isTopBorderRadiusRemoved = false,
     this.isGestureDetectorDisabled = false,
+    this.showOnlyLoadingState = false,
+    this.surfaceExecutionStates = true,
+    this.alwaysShowSuccessState = false,
     Key? key,
   }) : super(key: key);
 
@@ -76,6 +101,10 @@ class MenuItemWidget extends StatefulWidget {
 }
 
 class _MenuItemWidgetState extends State<MenuItemWidget> {
+  final _debouncer = Debouncer(const Duration(milliseconds: 300));
+  ValueNotifier<ExecutionState> executionStateNotifier =
+      ValueNotifier(ExecutionState.idle);
+
   Color? menuItemColor;
   late double borderRadius;
 
@@ -105,6 +134,7 @@ class _MenuItemWidgetState extends State<MenuItemWidget> {
     if (widget.expandableController != null) {
       widget.expandableController!.dispose();
     }
+    executionStateNotifier.dispose();
     super.dispose();
   }
 
@@ -113,7 +143,7 @@ class _MenuItemWidgetState extends State<MenuItemWidget> {
     return widget.isExpandable || widget.isGestureDetectorDisabled
         ? menuItemWidget(context)
         : GestureDetector(
-            onTap: widget.onTap,
+            onTap: _onTap,
             onDoubleTap: widget.onDoubleTap,
             onTapDown: _onTapDown,
             onTapUp: _onTapUp,
@@ -123,7 +153,6 @@ class _MenuItemWidgetState extends State<MenuItemWidget> {
   }
 
   Widget menuItemWidget(BuildContext context) {
-    final enteColorScheme = Theme.of(context).colorScheme.enteTheme.colorScheme;
     final circularRadius = Radius.circular(borderRadius);
     final isExpanded = widget.expandableController?.value;
     final bottomBorderRadius =
@@ -151,66 +180,70 @@ class _MenuItemWidgetState extends State<MenuItemWidget> {
         children: [
           widget.alignCaptionedTextToLeft && widget.leadingIcon == null
               ? const SizedBox.shrink()
-              : Padding(
-                  padding: const EdgeInsets.only(right: 10),
-                  child: SizedBox(
-                    height: widget.leadingIconSize,
-                    width: widget.leadingIconSize,
-                    child: widget.leadingIcon == null
-                        ? (widget.leadingIconWidget != null
-                            ? FittedBox(
-                                fit: BoxFit.contain,
-                                child: widget.leadingIconWidget,
-                              )
-                            : const SizedBox.shrink())
-                        : FittedBox(
-                            fit: BoxFit.contain,
-                            child: Icon(
-                              widget.leadingIcon,
-                              color: widget.leadingIconColor ??
-                                  enteColorScheme.strokeBase,
-                            ),
-                          ),
-                  ),
+              : LeadingWidget(
+                  leadingIconSize: widget.leadingIconSize,
+                  leadingIcon: widget.leadingIcon,
+                  leadingIconColor: widget.leadingIconColor,
+                  leadingIconWidget: widget.leadingIconWidget,
                 ),
           widget.captionedTextWidget,
-          widget.expandableController != null
-              ? AnimatedOpacity(
-                  duration: const Duration(milliseconds: 100),
-                  curve: Curves.easeInOut,
-                  opacity: isExpanded! ? 0 : 1,
-                  child: AnimatedSwitcher(
-                    transitionBuilder: (child, animation) {
-                      return ScaleTransition(scale: animation, child: child);
-                    },
-                    duration: const Duration(milliseconds: 200),
-                    switchInCurve: Curves.easeOut,
-                    child: isExpanded
-                        ? const SizedBox.shrink()
-                        : Icon(
-                            widget.trailingIcon,
-                            color: widget.trailingIconColor,
-                          ),
-                  ),
-                )
-              : widget.trailingIcon != null
-                  ? Padding(
-                      padding: EdgeInsets.only(
-                        right: widget.trailingExtraMargin,
-                      ),
-                      child: Icon(
-                        widget.trailingIcon,
-                        color: widget.trailingIconIsMuted
-                            ? enteColorScheme.strokeMuted
-                            : widget.trailingIconColor,
-                      ),
-                    )
-                  : widget.trailingWidget ?? const SizedBox.shrink(),
+          if (widget.expandableController != null)
+            ExpansionTrailingIcon(
+              isExpanded: isExpanded!,
+              trailingIcon: widget.trailingIcon,
+              trailingIconColor: widget.trailingIconColor,
+            )
+          else
+            TrailingWidget(
+              executionStateNotifier: executionStateNotifier,
+              trailingIcon: widget.trailingIcon,
+              trailingIconColor: widget.trailingIconColor,
+              trailingWidget: widget.trailingWidget,
+              trailingIconIsMuted: widget.trailingIconIsMuted,
+              trailingExtraMargin: widget.trailingExtraMargin,
+              showExecutionStates: widget.surfaceExecutionStates,
+              key: ValueKey(widget.trailingIcon.hashCode),
+            ),
         ],
       ),
     );
   }
 
+  Future<void> _onTap() async {
+    _debouncer.run(
+      () => Future(
+        () {
+          executionStateNotifier.value = ExecutionState.inProgress;
+        },
+      ),
+    );
+    await widget.onTap?.call().then(
+      (value) {
+        widget.alwaysShowSuccessState
+            ? executionStateNotifier.value = ExecutionState.successful
+            : null;
+      },
+      onError: (error, stackTrace) => _debouncer.cancelDebounce(),
+    );
+    _debouncer.cancelDebounce();
+    if (widget.alwaysShowSuccessState) {
+      Future.delayed(const Duration(seconds: 2), () {
+        executionStateNotifier.value = ExecutionState.idle;
+      });
+      return;
+    }
+    if (executionStateNotifier.value == ExecutionState.inProgress) {
+      if (widget.showOnlyLoadingState) {
+        executionStateNotifier.value = ExecutionState.idle;
+      } else {
+        executionStateNotifier.value = ExecutionState.successful;
+        Future.delayed(const Duration(seconds: 2), () {
+          executionStateNotifier.value = ExecutionState.idle;
+        });
+      }
+    }
+  }
+
   void _onTapDown(details) {
     setState(() {
       if (widget.pressedColor == null) {

+ 2 - 2
lib/ui/components/toggle_switch_widget.dart

@@ -9,12 +9,12 @@ enum ExecutionState {
   successful,
 }
 
-typedef FutureVoidCallBack = Future<void> Function();
+typedef FutureVoidCallback = Future<void> Function();
 typedef BoolCallBack = bool Function();
 
 class ToggleSwitchWidget extends StatefulWidget {
   final BoolCallBack value;
-  final FutureVoidCallBack onChanged;
+  final FutureVoidCallback onChanged;
   const ToggleSwitchWidget({
     required this.value,
     required this.onChanged,

+ 2 - 2
lib/ui/settings/about_section_widget.dart

@@ -4,7 +4,7 @@ import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/common/web_page.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/expandable_menu_item_widget.dart';
-import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
 import 'package:photos/ui/settings/app_update_dialog.dart';
 import 'package:photos/ui/settings/common_settings.dart';
 import 'package:photos/utils/dialog_util.dart';
@@ -113,7 +113,7 @@ class AboutMenuItemWidget extends StatelessWidget {
       pressedColor: getEnteColorScheme(context).fillFaint,
       trailingIcon: Icons.chevron_right_outlined,
       trailingIconIsMuted: true,
-      onTap: () {
+      onTap: () async {
         Navigator.of(context).push(
           MaterialPageRoute(
             builder: (BuildContext context) {

+ 6 - 3
lib/ui/settings/account_section_widget.dart

@@ -11,7 +11,7 @@ import 'package:photos/ui/account/password_entry_page.dart';
 import 'package:photos/ui/account/recovery_key_page.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/expandable_menu_item_widget.dart';
-import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
 import 'package:photos/ui/settings/common_settings.dart';
 import 'package:photos/utils/dialog_util.dart';
 import 'package:photos/utils/navigation_util.dart';
@@ -39,6 +39,7 @@ class AccountSectionWidget extends StatelessWidget {
           pressedColor: getEnteColorScheme(context).fillFaint,
           trailingIcon: Icons.chevron_right_outlined,
           trailingIconIsMuted: true,
+          showOnlyLoadingState: true,
           onTap: () async {
             final hasAuthenticated = await LocalAuthenticationService.instance
                 .requestLocalAuthentication(
@@ -75,6 +76,7 @@ class AccountSectionWidget extends StatelessWidget {
           pressedColor: getEnteColorScheme(context).fillFaint,
           trailingIcon: Icons.chevron_right_outlined,
           trailingIconIsMuted: true,
+          showOnlyLoadingState: true,
           onTap: () async {
             final hasAuthenticated = await LocalAuthenticationService.instance
                 .requestLocalAuthentication(
@@ -101,6 +103,7 @@ class AccountSectionWidget extends StatelessWidget {
           pressedColor: getEnteColorScheme(context).fillFaint,
           trailingIcon: Icons.chevron_right_outlined,
           trailingIconIsMuted: true,
+          showOnlyLoadingState: true,
           onTap: () async {
             final hasAuthenticated = await LocalAuthenticationService.instance
                 .requestLocalAuthentication(
@@ -128,7 +131,7 @@ class AccountSectionWidget extends StatelessWidget {
           pressedColor: getEnteColorScheme(context).fillFaint,
           trailingIcon: Icons.chevron_right_outlined,
           trailingIconIsMuted: true,
-          onTap: () {
+          onTap: () async {
             _onLogoutTapped(context);
           },
         ),
@@ -140,7 +143,7 @@ class AccountSectionWidget extends StatelessWidget {
           pressedColor: getEnteColorScheme(context).fillFaint,
           trailingIcon: Icons.chevron_right_outlined,
           trailingIconIsMuted: true,
-          onTap: () {
+          onTap: () async {
             routeToPage(context, const DeleteAccountPage());
           },
         ),

+ 5 - 11
lib/ui/settings/backup_section_widget.dart

@@ -12,7 +12,7 @@ import 'package:photos/ui/backup_settings_screen.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/dialog_widget.dart';
 import 'package:photos/ui/components/expandable_menu_item_widget.dart';
-import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
 import 'package:photos/ui/components/models/button_type.dart';
 import 'package:photos/ui/settings/common_settings.dart';
 import 'package:photos/ui/tools/deduplicate_page.dart';
@@ -49,7 +49,7 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
         pressedColor: getEnteColorScheme(context).fillFaint,
         trailingIcon: Icons.chevron_right_outlined,
         trailingIconIsMuted: true,
-        onTap: () {
+        onTap: () async {
           routeToPage(
             context,
             const BackupFolderSelectionPage(
@@ -66,7 +66,7 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
         pressedColor: getEnteColorScheme(context).fillFaint,
         trailingIcon: Icons.chevron_right_outlined,
         trailingIconIsMuted: true,
-        onTap: () {
+        onTap: () async {
           routeToPage(
             context,
             const BackupSettingsScreen(),
@@ -85,19 +85,16 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
           pressedColor: getEnteColorScheme(context).fillFaint,
           trailingIcon: Icons.chevron_right_outlined,
           trailingIconIsMuted: true,
+          showOnlyLoadingState: true,
           onTap: () async {
-            final dialog = createProgressDialog(context, "Calculating...");
-            await dialog.show();
             BackupStatus status;
             try {
               status = await SyncService.instance.getBackupStatus();
             } catch (e) {
-              await dialog.hide();
               showGenericErrorDialog(context: context);
               return;
             }
 
-            await dialog.hide();
             if (status.localIDs.isEmpty) {
               showErrorDialog(
                 context,
@@ -121,20 +118,17 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
           pressedColor: getEnteColorScheme(context).fillFaint,
           trailingIcon: Icons.chevron_right_outlined,
           trailingIconIsMuted: true,
+          showOnlyLoadingState: true,
           onTap: () async {
-            final dialog = createProgressDialog(context, "Calculating...");
-            await dialog.show();
             List<DuplicateFiles> duplicates;
             try {
               duplicates =
                   await DeduplicationService.instance.getDuplicateFiles();
             } catch (e) {
-              await dialog.hide();
               showGenericErrorDialog(context: context);
               return;
             }
 
-            await dialog.hide();
             if (duplicates.isEmpty) {
               showErrorDialog(
                 context,

+ 1 - 1
lib/ui/settings/debug_section_widget.dart

@@ -8,7 +8,7 @@ import 'package:photos/services/update_service.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/expandable_menu_item_widget.dart';
-import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
 import 'package:photos/ui/settings/common_settings.dart';
 import 'package:photos/utils/toast_util.dart';
 

+ 6 - 9
lib/ui/settings/general_section_widget.dart

@@ -5,10 +5,9 @@ import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/advanced_settings_screen.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/expandable_menu_item_widget.dart';
-import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
 import 'package:photos/ui/payment/subscription.dart';
 import 'package:photos/ui/settings/common_settings.dart';
-import 'package:photos/utils/dialog_util.dart';
 import 'package:photos/utils/navigation_util.dart';
 
 class GeneralSectionWidget extends StatelessWidget {
@@ -34,7 +33,7 @@ class GeneralSectionWidget extends StatelessWidget {
           pressedColor: getEnteColorScheme(context).fillFaint,
           trailingIcon: Icons.chevron_right_outlined,
           trailingIconIsMuted: true,
-          onTap: () {
+          onTap: () async {
             _onManageSubscriptionTapped(context);
           },
         ),
@@ -46,8 +45,9 @@ class GeneralSectionWidget extends StatelessWidget {
           pressedColor: getEnteColorScheme(context).fillFaint,
           trailingIcon: Icons.chevron_right_outlined,
           trailingIconIsMuted: true,
-          onTap: () {
-            _onFamilyPlansTapped(context);
+          showOnlyLoadingState: true,
+          onTap: () async {
+            await _onFamilyPlansTapped(context);
           },
         ),
         sectionOptionSpacing,
@@ -58,7 +58,7 @@ class GeneralSectionWidget extends StatelessWidget {
           pressedColor: getEnteColorScheme(context).fillFaint,
           trailingIcon: Icons.chevron_right_outlined,
           trailingIconIsMuted: true,
-          onTap: () {
+          onTap: () async {
             _onAdvancedTapped(context);
           },
         ),
@@ -78,11 +78,8 @@ class GeneralSectionWidget extends StatelessWidget {
   }
 
   Future<void> _onFamilyPlansTapped(BuildContext context) async {
-    final dialog = createProgressDialog(context, "Please wait...");
-    await dialog.show();
     final userDetails =
         await UserService.instance.getUserDetailsV2(memoryCount: false);
-    await dialog.hide();
     BillingService.instance.launchFamilyPortal(context, userDetails);
   }
 

+ 2 - 1
lib/ui/settings/security_section_widget.dart

@@ -11,7 +11,7 @@ import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/account/sessions_page.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/expandable_menu_item_widget.dart';
-import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
 import 'package:photos/ui/components/toggle_switch_widget.dart';
 import 'package:photos/ui/settings/common_settings.dart';
 
@@ -119,6 +119,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
         pressedColor: getEnteColorScheme(context).fillFaint,
         trailingIcon: Icons.chevron_right_outlined,
         trailingIconIsMuted: true,
+        showOnlyLoadingState: true,
         onTap: () async {
           final hasAuthenticated = await LocalAuthenticationService.instance
               .requestLocalAuthentication(

+ 2 - 2
lib/ui/settings/social_section_widget.dart

@@ -3,7 +3,7 @@ import 'package:photos/services/update_service.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/expandable_menu_item_widget.dart';
-import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
 import 'package:photos/ui/settings/common_settings.dart';
 import 'package:url_launcher/url_launcher_string.dart';
 
@@ -69,7 +69,7 @@ class SocialsMenuItemWidget extends StatelessWidget {
       pressedColor: getEnteColorScheme(context).fillFaint,
       trailingIcon: Icons.chevron_right_outlined,
       trailingIconIsMuted: true,
-      onTap: () {
+      onTap: () async {
         launchUrlString(urlSring);
       },
     );

+ 2 - 2
lib/ui/settings/support_section_widget.dart

@@ -7,7 +7,7 @@ import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/common/web_page.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/expandable_menu_item_widget.dart';
-import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
 import 'package:photos/ui/settings/about_section_widget.dart';
 import 'package:photos/ui/settings/common_settings.dart';
 import 'package:photos/utils/email_util.dart';
@@ -54,7 +54,7 @@ class SupportSectionWidget extends StatelessWidget {
           pressedColor: getEnteColorScheme(context).fillFaint,
           trailingIcon: Icons.chevron_right_outlined,
           trailingIconIsMuted: true,
-          onTap: () {
+          onTap: () async {
             Navigator.of(context).push(
               MaterialPageRoute(
                 builder: (BuildContext context) {

+ 1 - 1
lib/ui/settings/theme_switch_widget.dart

@@ -5,7 +5,7 @@ import 'package:photos/ente_theme_data.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/expandable_menu_item_widget.dart';
-import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
 import 'package:photos/ui/settings/common_settings.dart';
 
 class ThemeSwitchWidget extends StatefulWidget {

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

@@ -8,7 +8,7 @@ import 'package:photos/ui/actions/collection/collection_sharing_actions.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_item_widget/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';

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

@@ -5,7 +5,7 @@ import 'package:photos/models/collection.dart';
 import 'package:photos/theme/ente_theme.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_item_widget/menu_item_widget.dart';
 import 'package:photos/ui/components/menu_section_title.dart';
 import 'package:photos/ui/components/title_bar_title_widget.dart';
 import 'package:photos/ui/components/title_bar_widget.dart';
@@ -160,7 +160,7 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
                           onTap: isOwner
                               ? () async {
                                   if (isOwner) {
-                                    await _navigateToManageUser(currentUser);
+                                    _navigateToManageUser(currentUser);
                                   }
                                 }
                               : null,
@@ -185,7 +185,7 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
                       leadingIcon: Icons.add_outlined,
                       menuItemColor: getEnteColorScheme(context).fillFaint,
                       onTap: () async {
-                        await _navigateToAddUser(false);
+                        _navigateToAddUser(false);
                       },
                       isTopBorderRadiusRemoved: collaborators.isNotEmpty,
                       singleBorderRadius: 8,
@@ -259,7 +259,7 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
                       leadingIcon: Icons.add_outlined,
                       menuItemColor: getEnteColorScheme(context).fillFaint,
                       onTap: () async {
-                        await _navigateToAddUser(true);
+                        _navigateToAddUser(true);
                       },
                       isTopBorderRadiusRemoved: viewers.isNotEmpty,
                       singleBorderRadius: 8,

+ 26 - 43
lib/ui/sharing/manage_album_participant.dart

@@ -4,14 +4,12 @@ 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_item_widget/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';
 
@@ -84,7 +82,6 @@ class _ManageIndividualParticipantState
                         widget.collection,
                         widget.user.email,
                         CollectionParticipantRole.collaborator,
-                        showProgress: true,
                       );
                       if (result && mounted) {
                         widget.user.role = CollectionParticipantRole
@@ -107,53 +104,38 @@ class _ManageIndividualParticipantState
               leadingIconColor: getEnteColorScheme(context).strokeBase,
               menuItemColor: getEnteColorScheme(context).fillFaint,
               trailingIcon: widget.user.isViewer ? Icons.check : null,
+              showOnlyLoadingState: true,
               onTap: widget.user.isViewer
                   ? null
                   : () async {
-                      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",
-                          )
-                        ],
+                      final ButtonAction? result = await showChoiceActionSheet(
+                        context,
                         title: "Change permissions?",
+                        firstButtonLabel: "Yes, convert to viewer",
                         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',
+                        isCritical: true,
                       );
                       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(() => {});
+                        if (result == ButtonAction.first) {
+                          try {
+                            isConvertToViewSuccess =
+                                await collectionActions.addEmailToCollection(
+                              context,
+                              widget.collection,
+                              widget.user.email,
+                              CollectionParticipantRole.viewer,
+                            );
+                          } catch (e) {
+                            showGenericErrorDialog(context: context);
+                          }
+                          if (isConvertToViewSuccess && mounted) {
+                            // reset value
+                            isConvertToViewSuccess = false;
+                            widget.user.role =
+                                CollectionParticipantRole.viewer.toStringVal();
+                            setState(() => {});
+                          }
                         }
                       }
                     },
@@ -174,6 +156,7 @@ class _ManageIndividualParticipantState
               leadingIcon: Icons.not_interested_outlined,
               leadingIconColor: warning500,
               menuItemColor: getEnteColorScheme(context).fillFaint,
+              surfaceExecutionStates: false,
               onTap: () async {
                 final result = await collectionActions.removeParticipant(
                   context,

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

@@ -14,7 +14,7 @@ import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/actions/collection/collection_sharing_actions.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_item_widget/menu_item_widget.dart';
 import 'package:photos/ui/components/menu_section_description_widget.dart';
 import 'package:photos/utils/crypto_util.dart';
 import 'package:photos/utils/date_time_util.dart';
@@ -112,6 +112,7 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
                     ),
                     trailingIcon: Icons.chevron_right,
                     menuItemColor: enteColorScheme.fillFaint,
+                    surfaceExecutionStates: false,
                     onTap: () async {
                       await showPicker();
                     },
@@ -139,6 +140,7 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
                     onTap: () async {
                       await _showDeviceLimitPicker();
                     },
+                    surfaceExecutionStates: false,
                   ),
                   DividerWidget(
                     dividerType: DividerType.menuNoIcon,
@@ -221,6 +223,7 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
                     leadingIcon: Icons.remove_circle_outline,
                     leadingIconColor: warning500,
                     menuItemColor: getEnteColorScheme(context).fillFaint,
+                    surfaceExecutionStates: false,
                     onTap: () async {
                       final bool result = await sharingActions.disableUrl(
                         context,

+ 4 - 1
lib/ui/sharing/share_collection_page.dart

@@ -8,7 +8,7 @@ import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/actions/collection/collection_sharing_actions.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_item_widget/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/sharing/add_partipant_page.dart';
@@ -162,6 +162,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
               ),
               leadingIcon: Icons.copy,
               menuItemColor: getEnteColorScheme(context).fillFaint,
+              showOnlyLoadingState: true,
               onTap: () async {
                 await Clipboard.setData(ClipboardData(text: url));
                 showShortToast(context, "Link copied to clipboard");
@@ -228,6 +229,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
           leadingIcon: Icons.link,
           menuItemColor: getEnteColorScheme(context).fillFaint,
           isBottomBorderRadiusRemoved: true,
+          showOnlyLoadingState: true,
           onTap: () async {
             final bool result =
                 await collectionActions.enableUrl(context, widget.collection);
@@ -255,6 +257,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
           ),
           leadingIcon: Icons.link,
           menuItemColor: getEnteColorScheme(context).fillFaint,
+          showOnlyLoadingState: true,
           onTap: () async {
             final bool result = await collectionActions.enableUrl(
               context,

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

@@ -9,7 +9,7 @@ import 'package:photos/services/feature_flag_service.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
 import 'package:photos/ui/components/icon_button_widget.dart';
-import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
 import 'package:photos/ui/components/menu_section_title.dart';
 import 'package:photos/ui/components/title_bar_title_widget.dart';
 import 'package:photos/ui/components/title_bar_widget.dart';
@@ -162,6 +162,7 @@ class _AppStorageViewerState extends State<AppStorageViewer> {
                             menuItemColor:
                                 getEnteColorScheme(context).fillFaint,
                             singleBorderRadius: 8,
+                            alwaysShowSuccessState: true,
                             onTap: () async {
                               for (var pathItem in paths) {
                                 if (pathItem.allowCacheClear) {

+ 3 - 1
lib/ui/tools/debug/path_storage_viewer.dart

@@ -6,7 +6,7 @@ import 'package:flutter/services.dart';
 import 'package:logging/logging.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
-import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
 import 'package:photos/utils/data_util.dart';
 import 'package:photos/utils/directory_content.dart';
 
@@ -76,6 +76,7 @@ class _PathStorageViewerState extends State<PathStorageViewer> {
 
   Widget _buildMenuItemWidget(DirectoryStat? stat, Object? err) {
     return MenuItemWidget(
+      key: UniqueKey(),
       alignCaptionedTextToLeft: true,
       captionedTextWidget: CaptionedTextWidget(
         title: widget.item.title,
@@ -102,6 +103,7 @@ class _PathStorageViewerState extends State<PathStorageViewer> {
       menuItemColor: getEnteColorScheme(context).fillFaint,
       isBottomBorderRadiusRemoved: widget.removeBottomRadius,
       isTopBorderRadiusRemoved: widget.removeTopRadius,
+      showOnlyLoadingState: true,
       onTap: () async {
         if (kDebugMode) {
           await Clipboard.setData(ClipboardData(text: widget.item.path));

+ 2 - 1
lib/ui/viewer/gallery/device_folder_page.dart

@@ -15,7 +15,7 @@ import 'package:photos/services/ignored_files_service.dart';
 import 'package:photos/services/remote_sync_service.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/ui/components/captioned_text_widget.dart';
-import 'package:photos/ui/components/menu_item_widget.dart';
+import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
 import 'package:photos/ui/components/menu_section_description_widget.dart';
 import 'package:photos/ui/components/toggle_switch_widget.dart';
 import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart';
@@ -238,6 +238,7 @@ class _ResetIgnoredFilesWidgetState extends State<ResetIgnoredFilesWidget> {
           singleBorderRadius: 8.0,
           menuItemColor: getEnteColorScheme(context).fillFaint,
           leadingIcon: Icons.cloud_off_outlined,
+          alwaysShowSuccessState: true,
           onTap: () async {
             await _removeFilesFromIgnoredFiles(
               widget.filesInDeviceCollection,