mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-02-02 19:26:37 +00:00
desktop password protected send flow
This commit is contained in:
parent
9a9b10b1b3
commit
4238ce338a
4 changed files with 348 additions and 61 deletions
|
@ -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/pinpad_views/lock_screen_view.dart';
|
||||||
import 'package:stackwallet/pages/send_view/sub_widgets/sending_transaction_dialog.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/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/providers.dart';
|
||||||
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
|
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
|
||||||
import 'package:stackwallet/route_generator.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/utilities/util.dart';
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.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/desktop/primary_button.dart';
|
||||||
import 'package:stackwallet/widgets/rounded_container.dart';
|
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
@ -55,14 +58,16 @@ class _ConfirmTransactionViewState
|
||||||
late final bool isDesktop;
|
late final bool isDesktop;
|
||||||
|
|
||||||
Future<void> _attemptSend(BuildContext context) async {
|
Future<void> _attemptSend(BuildContext context) async {
|
||||||
unawaited(showDialog<dynamic>(
|
unawaited(
|
||||||
context: context,
|
showDialog<dynamic>(
|
||||||
useSafeArea: false,
|
context: context,
|
||||||
barrierDismissible: false,
|
useSafeArea: false,
|
||||||
builder: (context) {
|
barrierDismissible: false,
|
||||||
return const SendingTransactionDialog();
|
builder: (context) {
|
||||||
},
|
return const SendingTransactionDialog();
|
||||||
));
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final note = transactionInfo["note"] as String? ?? "";
|
final note = transactionInfo["note"] as String? ?? "";
|
||||||
final manager =
|
final manager =
|
||||||
|
@ -115,25 +120,66 @@ class _ConfirmTransactionViewState
|
||||||
useSafeArea: false,
|
useSafeArea: false,
|
||||||
barrierDismissible: true,
|
barrierDismissible: true,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return StackDialog(
|
if (isDesktop) {
|
||||||
title: "Broadcast transaction failed",
|
return DesktopDialog(
|
||||||
message: e.toString(),
|
maxWidth: 450,
|
||||||
rightButton: TextButton(
|
child: Padding(
|
||||||
style: Theme.of(context)
|
padding: const EdgeInsets.all(32),
|
||||||
.extension<StackColors>()!
|
child: Column(
|
||||||
.getSecondaryEnabledButtonColor(context),
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Text(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
"Ok",
|
children: [
|
||||||
style: STextStyles.button(context).copyWith(
|
Text(
|
||||||
color: Theme.of(context)
|
"Broadcast transaction failed",
|
||||||
.extension<StackColors>()!
|
style: STextStyles.desktopH3(context),
|
||||||
.accentColorDark),
|
),
|
||||||
|
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<StackColors>()!
|
||||||
|
.getSecondaryEnabledButtonColor(context),
|
||||||
|
child: Text(
|
||||||
|
"Ok",
|
||||||
|
style: STextStyles.button(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.accentColorDark),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -736,25 +782,56 @@ class _ConfirmTransactionViewState
|
||||||
label: "Send",
|
label: "Send",
|
||||||
desktopMed: true,
|
desktopMed: true,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final unlocked = await Navigator.push(
|
final dynamic unlocked;
|
||||||
context,
|
|
||||||
RouteGenerator.getRoute(
|
if (isDesktop) {
|
||||||
shouldUseMaterialRoute:
|
unlocked = await showDialog<bool?>(
|
||||||
RouteGenerator.useMaterialPageRoute,
|
context: context,
|
||||||
builder: (_) => const LockscreenView(
|
builder: (context) => DesktopDialog(
|
||||||
showBackButton: true,
|
maxWidth: 580,
|
||||||
popOnSuccess: true,
|
maxHeight: double.infinity,
|
||||||
routeOnSuccessArguments: true,
|
child: Column(
|
||||||
routeOnSuccess: "",
|
mainAxisSize: MainAxisSize.min,
|
||||||
biometricsCancelButtonString: "CANCEL",
|
children: [
|
||||||
biometricsLocalizedReason:
|
Row(
|
||||||
"Authenticate to send transaction",
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
biometricsAuthenticationTitle: "Confirm Transaction",
|
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) {
|
if (unlocked is bool && unlocked && mounted) {
|
||||||
unawaited(_attemptSend(context));
|
unawaited(_attemptSend(context));
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:stackwallet/utilities/assets.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/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';
|
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
|
|
||||||
class SendingTransactionDialog extends StatefulWidget {
|
class SendingTransactionDialog extends StatefulWidget {
|
||||||
|
@ -43,24 +46,56 @@ class _RestoringDialogState extends State<SendingTransactionDialog>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return WillPopScope(
|
if (Util.isDesktop) {
|
||||||
onWillPop: () async {
|
return DesktopDialog(
|
||||||
return false;
|
child: Padding(
|
||||||
},
|
padding: const EdgeInsets.all(40),
|
||||||
child: StackDialog(
|
child: Column(
|
||||||
title: "Sending transaction",
|
mainAxisSize: MainAxisSize.min,
|
||||||
// // TODO get message from design team
|
children: [
|
||||||
// message: "<PLACEHOLDER>",
|
Text(
|
||||||
icon: RotationTransition(
|
"Sending transaction",
|
||||||
turns: _spinAnimation,
|
style: STextStyles.desktopH3(context),
|
||||||
child: SvgPicture.asset(
|
),
|
||||||
Assets.svg.arrowRotate,
|
const SizedBox(
|
||||||
color: Theme.of(context).extension<StackColors>()!.accentColorDark,
|
height: 40,
|
||||||
width: 24,
|
),
|
||||||
height: 24,
|
RotationTransition(
|
||||||
|
turns: _spinAnimation,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
Assets.svg.arrowRotate,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.accentColorDark,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
} else {
|
||||||
|
return WillPopScope(
|
||||||
|
onWillPop: () async {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
child: StackDialog(
|
||||||
|
title: "Sending transaction",
|
||||||
|
// // TODO get message from design team
|
||||||
|
// message: "<PLACEHOLDER>",
|
||||||
|
icon: RotationTransition(
|
||||||
|
turns: _spinAnimation,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
Assets.svg.arrowRotate,
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<StackColors>()!.accentColorDark,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<DesktopAuthSend> createState() => _DesktopAuthSendState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DesktopAuthSendState extends ConsumerState<DesktopAuthSend> {
|
||||||
|
late final TextEditingController passwordController;
|
||||||
|
late final FocusNode passwordFocusNode;
|
||||||
|
|
||||||
|
bool hidePassword = true;
|
||||||
|
|
||||||
|
bool _confirmEnabled = false;
|
||||||
|
|
||||||
|
Future<bool> 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<StackColors>()!.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<StackColors>()!
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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/confirm_transaction_view.dart';
|
||||||
import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.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/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/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/providers.dart';
|
||||||
import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart';
|
import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart';
|
||||||
|
@ -332,6 +333,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
child: ConfirmTransactionView(
|
child: ConfirmTransactionView(
|
||||||
transactionInfo: txData,
|
transactionInfo: txData,
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
|
routeOnSuccessName: DesktopHomeView.routeName,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue