fix: show bottom sheet on long press and remove slidable
This commit is contained in:
@@ -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<CodeWidget> {
|
||||
}
|
||||
: 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<CodeWidget> {
|
||||
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<CodeWidget> {
|
||||
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);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
89
auth/lib/ui/components/grid_action_sheet_widget.dart
Normal file
89
auth/lib/ui/components/grid_action_sheet_widget.dart
Normal file
@@ -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<ButtonResult?> showGridActionSheet({
|
||||
required BuildContext context,
|
||||
required List<ButtonWidget> 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<ButtonWidget> 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<dynamic> showTextInputDialog(
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
///Will return null if dismissed by tapping outside
|
||||
Future<ButtonResult?> 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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user