diff --git a/auth/lib/ui/code_widget.dart b/auth/lib/ui/code_widget.dart index fa2460afad..6ce7c2ad79 100644 --- a/auth/lib/ui/code_widget.dart +++ b/auth/lib/ui/code_widget.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:io'; -import 'dart:ui' as ui; import 'package:auto_size_text/auto_size_text.dart'; import 'package:clipboard/clipboard.dart'; @@ -24,7 +23,6 @@ import 'package:ente_auth/utils/totp_util.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_context_menu/flutter_context_menu.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:logging/logging.dart'; import 'package:move_to_background/move_to_background.dart'; @@ -214,7 +212,48 @@ class _CodeWidgetState extends State { } : null, onLongPress: () { - _copyCurrentOTPToClipboard(); + showOptionsForCode( + context, + isPinned: widget.code.isPinned, + isTrashed: widget.code.isTrashed, + onEdit: () async { + Future.delayed( + const Duration(milliseconds: 200), + () => _onEditPressed(null), + ); + }, + onShare: () async { + Future.delayed( + const Duration(milliseconds: 200), + () => _onSharePressed(null), + ); + }, + onPin: () async { + await _onPinPressed(null); + }, + onTrashed: () async { + Future.delayed( + const Duration(milliseconds: 200), + () => _onTrashPressed(null), + ); + }, + onDelete: () async { + Future.delayed( + const Duration(milliseconds: 200), + () => _onDeletePressed(null), + ); + }, + onRestore: () async { + _onRestoreClicked(null); + }, + onShowQR: () async { + Future.delayed( + const Duration(milliseconds: 200), + () => _onShowQrPressed(null), + ); + }, + ); + // _copyCurrentOTPToClipboard(); }, child: getCardContents(l10n), ), @@ -260,8 +299,8 @@ class _CodeWidgetState extends State { label: l10n.edit, icon: Icons.edit, onSelected: () => _onEditPressed(null), - ), - if (widget.code.isTrashed) + ) + else MenuItem( label: l10n.restore, icon: Icons.restore_outlined, @@ -284,106 +323,8 @@ class _CodeWidgetState extends State { child: clippedCard(l10n), ); } - final double slideSpace = isCompactMode ? 4 : 8; - double extendRatio = isCompactMode ? 0.70 : 0.90; - if (widget.code.isTrashed) { - extendRatio = 0.50; - } - return Slidable( - key: ValueKey(widget.code.hashCode), - endActionPane: ActionPane( - extentRatio: extendRatio, - motion: const ScrollMotion(), - children: [ - if (!widget.code.isTrashed && widget.code.type.isTOTPCompatible) - SizedBox(width: slideSpace), - if (!widget.code.isTrashed && widget.code.type.isTOTPCompatible) - SlidableAction( - onPressed: _onSharePressed, - backgroundColor: Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(8)), - foregroundColor: - Theme.of(context).colorScheme.inverseBackgroundColor, - icon: Icons.adaptive.share_outlined, - padding: const EdgeInsets.only(left: 4, right: 0), - spacing: 8, - ), - if (!widget.code.isTrashed) SizedBox(width: slideSpace), - if (!widget.code.isTrashed) - CustomSlidableAction( - onPressed: _onPinPressed, - backgroundColor: Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(8)), - foregroundColor: - Theme.of(context).colorScheme.inverseBackgroundColor, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (widget.code.isPinned) - SvgPicture.asset( - "assets/svg/pin-active.svg", - colorFilter: ui.ColorFilter.mode( - Theme.of(context).colorScheme.primary, - BlendMode.srcIn, - ), - ) - else - SvgPicture.asset( - "assets/svg/pin-inactive.svg", - colorFilter: ui.ColorFilter.mode( - Theme.of(context).colorScheme.primary, - BlendMode.srcIn, - ), - ), - ], - ), - padding: const EdgeInsets.only(left: 4, right: 0), - ), - if (!widget.code.isTrashed) SizedBox(width: slideSpace), - if (!widget.code.isTrashed) - SlidableAction( - onPressed: _onEditPressed, - backgroundColor: Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(8)), - foregroundColor: - Theme.of(context).colorScheme.inverseBackgroundColor, - icon: Icons.edit_outlined, - padding: const EdgeInsets.only(left: 4, right: 0), - spacing: 8, - ), - if (widget.code.isTrashed) SizedBox(width: slideSpace), - if (widget.code.isTrashed) - SlidableAction( - onPressed: _onRestoreClicked, - backgroundColor: Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(8)), - foregroundColor: - Theme.of(context).colorScheme.inverseBackgroundColor, - icon: Icons.restore_outlined, - padding: const EdgeInsets.only(left: 4, right: 0), - spacing: 8, - ), - SizedBox(width: slideSpace), - SlidableAction( - onPressed: widget.code.isTrashed - ? _onDeletePressed - : _onTrashPressed, - backgroundColor: Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(8)), - foregroundColor: colorScheme.deleteCodeTextColor, - icon: widget.code.isTrashed - ? Icons.delete_forever - : Icons.delete, - padding: const EdgeInsets.only(left: 0, right: 0), - spacing: 8, - ), - ], - ), - child: Builder( - builder: (context) => clippedCard(l10n), - ), - ); + return clippedCard(l10n); }, ), ); diff --git a/auth/lib/ui/components/buttons/button_widget.dart b/auth/lib/ui/components/buttons/button_widget.dart index 9e589cd7a0..5c6e622913 100644 --- a/auth/lib/ui/components/buttons/button_widget.dart +++ b/auth/lib/ui/components/buttons/button_widget.dart @@ -14,7 +14,7 @@ import 'package:flutter/scheduler.dart'; enum ButtonSize { small, large } -enum ButtonAction { first, second, third, fourth, cancel, error } +enum ButtonAction { first, second, third, fourth, fifth, cancel, error } class ButtonWidget extends StatelessWidget { final IconData? icon; diff --git a/auth/lib/ui/components/grid_action_sheet_widget.dart b/auth/lib/ui/components/grid_action_sheet_widget.dart new file mode 100644 index 0000000000..696da31c48 --- /dev/null +++ b/auth/lib/ui/components/grid_action_sheet_widget.dart @@ -0,0 +1,89 @@ +import 'dart:ui'; + +import 'package:ente_auth/theme/colors.dart'; +import 'package:ente_auth/theme/effects.dart'; +import 'package:ente_auth/ui/components/action_sheet_widget.dart'; +import 'package:ente_auth/ui/components/buttons/button_widget.dart'; +import 'package:ente_auth/ui/components/components_constants.dart'; +import 'package:ente_auth/ui/components/models/button_result.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; + +///Returns null if dismissed +Future showGridActionSheet({ + required BuildContext context, + required List buttons, + ActionSheetType actionSheetType = ActionSheetType.defaultActionSheet, + bool enableDrag = true, + bool isDismissible = true, + bool isCheckIconGreen = false, +}) { + return showMaterialModalBottomSheet( + backgroundColor: Colors.transparent, + barrierColor: backdropFaintDark, + useRootNavigator: true, + context: context, + isDismissible: isDismissible, + enableDrag: enableDrag, + builder: (_) { + return GridActionSheetWidget( + actionButtons: buttons, + actionSheetType: actionSheetType, + isCheckIconGreen: isCheckIconGreen, + ); + }, + ); +} + +class GridActionSheetWidget extends StatelessWidget { + final List actionButtons; + final ActionSheetType actionSheetType; + final bool isCheckIconGreen; + + const GridActionSheetWidget({ + required this.actionButtons, + required this.actionSheetType, + required this.isCheckIconGreen, + super.key, + }); + + @override + Widget build(BuildContext context) { + final blur = MediaQuery.of(context).platformBrightness == Brightness.light + ? blurMuted + : blurBase; + final extraWidth = MediaQuery.of(context).size.width - restrictedMaxWidth; + final double? horizontalPadding = extraWidth > 0 ? extraWidth / 2 : null; + return Padding( + padding: EdgeInsets.fromLTRB( + horizontalPadding ?? 12, + 0, + horizontalPadding ?? 12, + 32, + ), + child: Container( + decoration: BoxDecoration(boxShadow: shadowMenuLight), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur), + child: Container( + color: backdropMutedDark, + child: AlignedGridView.count( + padding: const EdgeInsets.all(24), + shrinkWrap: true, + crossAxisCount: 2, + crossAxisSpacing: 6, + mainAxisSpacing: 6, + physics: const AlwaysScrollableScrollPhysics(), + itemBuilder: (_, index) => actionButtons[index % 6], + itemCount: actionButtons.length, + ), + ), + ), + ), + ), + ); + } +} diff --git a/auth/lib/utils/dialog_util.dart b/auth/lib/utils/dialog_util.dart index 6c58e1f861..8f5f04a143 100644 --- a/auth/lib/utils/dialog_util.dart +++ b/auth/lib/utils/dialog_util.dart @@ -11,6 +11,7 @@ import 'package:ente_auth/ui/components/action_sheet_widget.dart'; import 'package:ente_auth/ui/components/buttons/button_widget.dart'; import 'package:ente_auth/ui/components/components_constants.dart'; import 'package:ente_auth/ui/components/dialog_widget.dart'; +import 'package:ente_auth/ui/components/grid_action_sheet_widget.dart'; import 'package:ente_auth/ui/components/models/button_result.dart'; import 'package:ente_auth/ui/components/models/button_type.dart'; import 'package:ente_auth/utils/email_util.dart'; @@ -424,3 +425,75 @@ Future showTextInputDialog( }, ); } + +///Will return null if dismissed by tapping outside +Future showOptionsForCode( + BuildContext context, { + FutureVoidCallback? onShare, + FutureVoidCallback? onPin, + FutureVoidCallback? onShowQR, + FutureVoidCallback? onEdit, + FutureVoidCallback? onRestore, + FutureVoidCallback? onDelete, + FutureVoidCallback? onTrashed, + bool isPinned = false, + bool isTrashed = false, +}) async { + final buttons = [ + ButtonWidget( + buttonType: ButtonType.neutral, + labelText: context.l10n.share, + isInAlert: true, + icon: Icons.adaptive.share_outlined, + onTap: onShare, + buttonAction: ButtonAction.first, + ), + ButtonWidget( + buttonType: ButtonType.neutral, + isInAlert: true, + labelText: 'QR', + icon: Icons.qr_code_2_outlined, + onTap: onShowQR, + buttonAction: ButtonAction.second, + ), + ButtonWidget( + buttonType: ButtonType.neutral, + isInAlert: true, + labelText: isPinned ? context.l10n.unpinText : context.l10n.pinText, + icon: isPinned ? Icons.push_pin : Icons.push_pin_outlined, + onTap: onPin, + buttonAction: ButtonAction.third, + ), + if (isTrashed) + ButtonWidget( + isInAlert: true, + buttonType: ButtonType.neutral, + labelText: context.l10n.restore, + icon: Icons.restore_outlined, + onTap: onRestore, + buttonAction: ButtonAction.fourth, + ) + else + ButtonWidget( + isInAlert: true, + buttonType: ButtonType.neutral, + labelText: context.l10n.edit, + icon: Icons.edit, + onTap: onEdit, + buttonAction: ButtonAction.fourth, + ), + ButtonWidget( + isInAlert: true, + buttonType: ButtonType.neutral, + labelText: isTrashed ? context.l10n.delete : context.l10n.trash, + icon: isTrashed ? Icons.delete_forever : Icons.delete, + onTap: isTrashed ? onDelete : onTrashed, + buttonAction: ButtonAction.fifth, + ), + ]; + return showGridActionSheet( + context: context, + buttons: buttons, + isDismissible: true, + ); +} diff --git a/auth/pubspec.lock b/auth/pubspec.lock index bd9e708671..f5f89d3266 100644 --- a/auth/pubspec.lock +++ b/auth/pubspec.lock @@ -687,14 +687,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.2" - flutter_slidable: - dependency: "direct main" - description: - name: flutter_slidable - sha256: "673403d2eeef1f9e8483bd6d8d92aae73b1d8bd71f382bc3930f699c731bc27c" - url: "https://pub.dev" - source: hosted - version: "3.1.0" flutter_speed_dial: dependency: "direct main" description: diff --git a/auth/pubspec.yaml b/auth/pubspec.yaml index da22c3473e..8e5e57b21b 100644 --- a/auth/pubspec.yaml +++ b/auth/pubspec.yaml @@ -59,7 +59,6 @@ dependencies: sdk: flutter flutter_native_splash: ^2.2.13 flutter_secure_storage: ^9.0.0 - flutter_slidable: ^3.0.1 flutter_speed_dial: ^7.0.0 flutter_staggered_grid_view: ^0.7.0 flutter_svg: ^2.0.5