Explorar el Código

Loading and success states for MenuItemWidget

ashilkn hace 2 años
padre
commit
bc3f0ed464

+ 1 - 1
lib/ui/advanced_settings_screen.dart

@@ -98,7 +98,7 @@ class _AdvancedSettingsScreenState extends State<AdvancedSettingsScreen> {
                               ),
                               borderRadius: 8,
                               alignCaptionedTextToLeft: true,
-                              onTap: () {
+                              onTap: () async {
                                 routeToPage(context, const AppStorageViewer());
                               },
                             ),

+ 54 - 3
lib/ui/components/menu_item_widget/menu_item_child_widgets.dart

@@ -1,13 +1,17 @@
 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;
   const TrailingWidget({
+    required this.executionStateNotifier,
     this.trailingIcon,
     this.trailingIconColor,
     this.trailingWidget,
@@ -20,23 +24,70 @@ class TrailingWidget extends StatefulWidget {
 }
 
 class _TrailingWidgetState extends State<TrailingWidget> {
+  Widget? trailingWidget;
+  @override
+  void initState() {
+    widget.executionStateNotifier.addListener(_executionStateListener);
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    widget.executionStateNotifier.removeListener(_executionStateListener);
+    super.dispose();
+  }
+
   @override
   Widget build(BuildContext context) {
+    if (trailingWidget == null) {
+      _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) {
-      return Padding(
+      trailingWidget = Padding(
         padding: EdgeInsets.only(
           right: widget.trailingExtraMargin,
         ),
         child: Icon(
           widget.trailingIcon,
           color: widget.trailingIconIsMuted
-              ? colorScheme.strokeMuted
+              ? getEnteColorScheme(context).strokeMuted
               : widget.trailingIconColor,
         ),
       );
     } else {
-      return widget.trailingWidget ?? const SizedBox.shrink();
+      trailingWidget = widget.trailingWidget ?? const SizedBox.shrink();
     }
   }
 }

+ 38 - 2
lib/ui/components/menu_item_widget/menu_item_widget.dart

@@ -2,6 +2,16 @@ import 'package:expandable/expandable.dart';
 import 'package:flutter/material.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;
@@ -25,7 +35,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;
@@ -68,6 +78,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;
 
   @override
@@ -92,6 +106,7 @@ class _MenuItemWidgetState extends State<MenuItemWidget> {
     if (widget.expandableController != null) {
       widget.expandableController!.dispose();
     }
+    executionStateNotifier.dispose();
     super.dispose();
   }
 
@@ -100,7 +115,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,
@@ -152,6 +167,7 @@ class _MenuItemWidgetState extends State<MenuItemWidget> {
             )
           else
             TrailingWidget(
+              executionStateNotifier: executionStateNotifier,
               trailingIcon: widget.trailingIcon,
               trailingIconColor: widget.trailingIconColor,
               trailingWidget: widget.trailingWidget,
@@ -163,6 +179,26 @@ class _MenuItemWidgetState extends State<MenuItemWidget> {
     );
   }
 
+  Future<void> _onTap() async {
+    _debouncer.run(
+      () => Future(
+        () {
+          executionStateNotifier.value = ExecutionState.inProgress;
+        },
+      ),
+    );
+    await widget.onTap
+        ?.call()
+        .onError((error, stackTrace) => _debouncer.cancelDebounce());
+    _debouncer.cancelDebounce();
+    if (executionStateNotifier.value == ExecutionState.inProgress) {
+      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,

+ 1 - 1
lib/ui/settings/about_section_widget.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) {

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

@@ -128,7 +128,7 @@ class AccountSectionWidget extends StatelessWidget {
           pressedColor: getEnteColorScheme(context).fillFaint,
           trailingIcon: Icons.chevron_right_outlined,
           trailingIconIsMuted: true,
-          onTap: () {
+          onTap: () async {
             _onLogoutTapped(context);
           },
         ),
@@ -140,7 +140,7 @@ class AccountSectionWidget extends StatelessWidget {
           pressedColor: getEnteColorScheme(context).fillFaint,
           trailingIcon: Icons.chevron_right_outlined,
           trailingIconIsMuted: true,
-          onTap: () {
+          onTap: () async {
             routeToPage(context, const DeleteAccountPage());
           },
         ),

+ 2 - 2
lib/ui/settings/backup_section_widget.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(),

+ 3 - 3
lib/ui/settings/general_section_widget.dart

@@ -34,7 +34,7 @@ class GeneralSectionWidget extends StatelessWidget {
           pressedColor: getEnteColorScheme(context).fillFaint,
           trailingIcon: Icons.chevron_right_outlined,
           trailingIconIsMuted: true,
-          onTap: () {
+          onTap: () async {
             _onManageSubscriptionTapped(context);
           },
         ),
@@ -46,7 +46,7 @@ class GeneralSectionWidget extends StatelessWidget {
           pressedColor: getEnteColorScheme(context).fillFaint,
           trailingIcon: Icons.chevron_right_outlined,
           trailingIconIsMuted: true,
-          onTap: () {
+          onTap: () async {
             _onFamilyPlansTapped(context);
           },
         ),
@@ -58,7 +58,7 @@ class GeneralSectionWidget extends StatelessWidget {
           pressedColor: getEnteColorScheme(context).fillFaint,
           trailingIcon: Icons.chevron_right_outlined,
           trailingIconIsMuted: true,
-          onTap: () {
+          onTap: () async {
             _onAdvancedTapped(context);
           },
         ),

+ 1 - 1
lib/ui/settings/social_section_widget.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);
       },
     );

+ 1 - 1
lib/ui/settings/support_section_widget.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) {