From 4238ce338ac8cf9f46ff402f7f5cff3186f59a3a Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 14 Nov 2022 09:05:45 -0600 Subject: [PATCH] desktop password protected send flow --- .../send_view/confirm_transaction_view.dart | 165 ++++++++++++----- .../sending_transaction_dialog.dart | 69 +++++-- .../sub_widgets/desktop_auth_send.dart | 173 ++++++++++++++++++ .../wallet_view/sub_widgets/desktop_send.dart | 2 + 4 files changed, 348 insertions(+), 61 deletions(-) create mode 100644 lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index eef0f84e6..0f1692c08 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -8,6 +8,7 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/sending_transaction_dialog.dart'; import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; +import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; import 'package:stackwallet/route_generator.dart'; @@ -23,6 +24,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -55,14 +58,16 @@ class _ConfirmTransactionViewState late final bool isDesktop; Future _attemptSend(BuildContext context) async { - unawaited(showDialog( - context: context, - useSafeArea: false, - barrierDismissible: false, - builder: (context) { - return const SendingTransactionDialog(); - }, - )); + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return const SendingTransactionDialog(); + }, + ), + ); final note = transactionInfo["note"] as String? ?? ""; final manager = @@ -115,25 +120,66 @@ class _ConfirmTransactionViewState useSafeArea: false, barrierDismissible: true, builder: (context) { - return StackDialog( - title: "Broadcast transaction failed", - message: e.toString(), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Ok", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), + if (isDesktop) { + return DesktopDialog( + maxWidth: 450, + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Broadcast transaction failed", + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 24, + ), + Text( + e.toString(), + style: STextStyles.smallMed14(context), + ), + const SizedBox( + height: 56, + ), + Row( + children: [ + const Spacer(), + Expanded( + child: PrimaryButton( + desktopMed: true, + label: "Ok", + onPressed: Navigator.of(context).pop, + ), + ), + ], + ) + ], + ), ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ); + ); + } else { + return StackDialog( + title: "Broadcast transaction failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + } }, ); } @@ -736,25 +782,56 @@ class _ConfirmTransactionViewState label: "Send", desktopMed: true, onPressed: () async { - final unlocked = await Navigator.push( - context, - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator.useMaterialPageRoute, - builder: (_) => const LockscreenView( - showBackButton: true, - popOnSuccess: true, - routeOnSuccessArguments: true, - routeOnSuccess: "", - biometricsCancelButtonString: "CANCEL", - biometricsLocalizedReason: - "Authenticate to send transaction", - biometricsAuthenticationTitle: "Confirm Transaction", + final dynamic unlocked; + + if (isDesktop) { + unlocked = await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: const [ + DesktopDialogCloseButton(), + ], + ), + const Padding( + padding: EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: DesktopAuthSend(), + ), + ], + ), ), - settings: - const RouteSettings(name: "/confirmsendlockscreen"), - ), - ); + ); + } else { + unlocked = await Navigator.push( + context, + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator.useMaterialPageRoute, + builder: (_) => const LockscreenView( + showBackButton: true, + popOnSuccess: true, + routeOnSuccessArguments: true, + routeOnSuccess: "", + biometricsCancelButtonString: "CANCEL", + biometricsLocalizedReason: + "Authenticate to send transaction", + biometricsAuthenticationTitle: "Confirm Transaction", + ), + settings: + const RouteSettings(name: "/confirmsendlockscreen"), + ), + ); + } if (unlocked is bool && unlocked && mounted) { unawaited(_attemptSend(context)); diff --git a/lib/pages/send_view/sub_widgets/sending_transaction_dialog.dart b/lib/pages/send_view/sub_widgets/sending_transaction_dialog.dart index 1eb106b53..e5c86fe2e 100644 --- a/lib/pages/send_view/sub_widgets/sending_transaction_dialog.dart +++ b/lib/pages/send_view/sub_widgets/sending_transaction_dialog.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; class SendingTransactionDialog extends StatefulWidget { @@ -43,24 +46,56 @@ class _RestoringDialogState extends State @override Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () async { - return false; - }, - child: StackDialog( - title: "Sending transaction", - // // TODO get message from design team - // message: "", - icon: RotationTransition( - turns: _spinAnimation, - child: SvgPicture.asset( - Assets.svg.arrowRotate, - color: Theme.of(context).extension()!.accentColorDark, - width: 24, - height: 24, + if (Util.isDesktop) { + return DesktopDialog( + child: Padding( + padding: const EdgeInsets.all(40), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Sending transaction", + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 40, + ), + RotationTransition( + turns: _spinAnimation, + child: SvgPicture.asset( + Assets.svg.arrowRotate, + color: Theme.of(context) + .extension()! + .accentColorDark, + width: 24, + height: 24, + ), + ), + ], ), ), - ), - ); + ); + } else { + return WillPopScope( + onWillPop: () async { + return false; + }, + child: StackDialog( + title: "Sending transaction", + // // TODO get message from design team + // message: "", + icon: RotationTransition( + turns: _spinAnimation, + child: SvgPicture.asset( + Assets.svg.arrowRotate, + color: + Theme.of(context).extension()!.accentColorDark, + width: 24, + height: 24, + ), + ), + ), + ); + } } } diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart new file mode 100644 index 000000000..566a82b35 --- /dev/null +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart @@ -0,0 +1,173 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; + +class DesktopAuthSend extends ConsumerStatefulWidget { + const DesktopAuthSend({Key? key}) : super(key: key); + + @override + ConsumerState createState() => _DesktopAuthSendState(); +} + +class _DesktopAuthSendState extends ConsumerState { + late final TextEditingController passwordController; + late final FocusNode passwordFocusNode; + + bool hidePassword = true; + + bool _confirmEnabled = false; + + Future verifyPassphrase() async { + return await ref + .read(storageCryptoHandlerProvider) + .verifyPassphrase(passwordController.text); + } + + @override + void initState() { + passwordController = TextEditingController(); + passwordFocusNode = FocusNode(); + + super.initState(); + } + + @override + void dispose() { + passwordController.dispose(); + passwordFocusNode.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + Assets.svg.keys, + width: 100, + ), + const SizedBox( + height: 56, + ), + Text( + "Confirm transaction", + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 16, + ), + Text( + "Enter your wallet password to send BTC", + style: STextStyles.desktopTextMedium(context).copyWith( + color: Theme.of(context).extension()!.textDark3, + ), + ), + const SizedBox( + height: 24, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("desktopLoginPasswordFieldKey"), + focusNode: passwordFocusNode, + controller: passwordController, + style: STextStyles.desktopTextMedium(context).copyWith( + height: 2, + ), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Enter password", + passwordFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: SizedBox( + height: 70, + child: Row( + children: [ + const SizedBox( + width: 24, + ), + GestureDetector( + key: const Key( + "restoreFromFilePasswordFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 24, + height: 24, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + ), + onChanged: (newValue) { + setState(() { + _confirmEnabled = passwordController.text.isNotEmpty; + }); + }, + ), + ), + const SizedBox( + height: 48, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + desktopMed: true, + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + enabled: _confirmEnabled, + label: "Confirm", + desktopMed: true, + onPressed: () async { + // TODO show spinner while verifying passphrase + + final passwordIsValid = await verifyPassphrase(); + + if (mounted) { + Navigator.of(context).pop(passwordIsValid); + } + }, + ), + ), + ], + ) + ], + ); + } +} diff --git a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 6f35a8570..071122def 100644 --- a/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -11,6 +11,7 @@ import 'package:stackwallet/models/send_view_auto_fill_data.dart'; import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; +import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart'; @@ -332,6 +333,7 @@ class _DesktopSendState extends ConsumerState { child: ConfirmTransactionView( transactionInfo: txData, walletId: walletId, + routeOnSuccessName: DesktopHomeView.routeName, ), ), ),