From dd486a29e75cfa45335c4a544363ce09b01d5801 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Thu, 18 Apr 2024 13:04:05 -0700 Subject: [PATCH] litecoin fixes, sign form fixes, use new walletAddressPicker --- cw_bitcoin/lib/litecoin_wallet.dart | 27 ++- lib/di.dart | 3 + lib/router.dart | 4 + lib/routes.dart | 1 + lib/src/screens/dashboard/sign_page.dart | 96 ++++----- .../screens/dashboard/widgets/sign_form.dart | 2 +- .../dashboard/widgets/verify_form.dart | 2 +- .../screens/receive/address_list_page.dart | 31 +++ lib/src/screens/receive/receive_page.dart | 86 +------- .../screens/receive/widgets/address_list.dart | 121 +++++++++++ lib/src/widgets/address_text_field.dart | 194 +++++++++++------- 11 files changed, 354 insertions(+), 213 deletions(-) create mode 100644 lib/src/screens/receive/address_list_page.dart create mode 100644 lib/src/screens/receive/widgets/address_list.dart diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index a466714fc..7a4e0d0cc 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -9,6 +9,7 @@ import 'package:blockchain_utils/exception/exception.dart'; import 'package:blockchain_utils/hex/hex.dart'; import 'package:blockchain_utils/numbers/bigint_utils.dart'; import 'package:blockchain_utils/signer/bitcoin_signer.dart'; +import 'package:blockchain_utils/signer/ecdsa_signing_key.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -137,7 +138,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { final HD = index == null ? hd : hd.derive(index); final priv = ECPrivate.fromHex(HD.privKey!); String messagePrefix = '\x19Litecoin Signed Message:\n'; - return priv.signMessage(utf8.encode(message), messagePrefix: messagePrefix); + + final privateKey = ECDSAPrivateKey.fromBytes( + priv.toBytes(), + Curves.generatorSecp256k1, + ); + final signature = + signLitecoinMessage(utf8.encode(message), messagePrefix, privateKey: privateKey); + return BytesUtils.toHexString(signature); } List _magicPrefix(List message, List messagePrefix) { @@ -146,6 +154,23 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { return [...messagePrefix, ...encodeLength, ...message]; } + List signLitecoinMessage(List message, String messagePrefix, + {required ECDSAPrivateKey privateKey}) { + final messgaeHash = QuickCrypto.sha256Hash(magicMessage(message, messagePrefix)); + final signingKey = EcdsaSigningKey(privateKey); + ECDSASignature ecdsaSign = + signingKey.signDigestDeterminstic(digest: messgaeHash, hashFunc: () => SHA256()); + final n = Curves.generatorSecp256k1.order! >> 1; + BigInt newS; + if (ecdsaSign.s.compareTo(n) > 0) { + newS = Curves.generatorSecp256k1.order! - ecdsaSign.s; + } else { + newS = ecdsaSign.s; + } + final newSignature = ECDSASignature(ecdsaSign.r, newS); + return newSignature.toBytes(BitcoinSignerUtils.baselen); + } + List magicMessage(List message, String messagePrefix) { final prefixBytes = StringUtils.encode(messagePrefix); final magic = _magicPrefix(message, prefixBytes); diff --git a/lib/di.dart b/lib/di.dart index 912284bdc..78250fba2 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -14,6 +14,7 @@ import 'package:cake_wallet/entities/background_tasks.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/src/screens/dashboard/sign_page.dart'; +import 'package:cake_wallet/src/screens/receive/address_list_page.dart'; import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart'; import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart'; import 'package:cw_core/receive_page_option.dart'; @@ -757,6 +758,8 @@ Future setup({ getIt.registerFactoryParam( (ContactRecord? contact, _) => ContactPage(getIt.get(param1: contact))); + getIt.registerFactory(() => AddressListPage(getIt.get())); + getIt.registerFactory(() { final appStore = getIt.get(); return NodeListViewModel(_nodeSource, appStore); diff --git a/lib/router.dart b/lib/router.dart index 46054cf81..efa086b98 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -19,6 +19,7 @@ import 'package:cake_wallet/src/screens/dashboard/sign_page.dart'; import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart'; import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart'; import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart'; +import 'package:cake_wallet/src/screens/receive/address_list_page.dart'; import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart'; @@ -377,6 +378,9 @@ Route createRoute(RouteSettings settings) { return MaterialPageRoute( builder: (_) => getIt.get(param1: selectedCurrency)); + case Routes.pickerWalletAddress: + return MaterialPageRoute(builder: (_) => getIt.get()); + case Routes.addressBookAddContact: return CupertinoPageRoute( builder: (_) => getIt.get(param1: settings.arguments as ContactRecord?)); diff --git a/lib/routes.dart b/lib/routes.dart index 759efc771..3ebb80cb1 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -29,6 +29,7 @@ class Routes { static const nanoAccountCreation = '/nano_account_new'; static const addressBook = '/address_book'; static const pickerAddressBook = '/picker_address_book'; + static const pickerWalletAddress = '/picker_wallet_address'; static const addressBookAddContact = '/address_book_add_contact'; static const showKeys = '/show_keys'; static const exchangeConfirm = '/exchange_confirm'; diff --git a/lib/src/screens/dashboard/sign_page.dart b/lib/src/screens/dashboard/sign_page.dart index 31f262297..2cee0cda0 100644 --- a/lib/src/screens/dashboard/sign_page.dart +++ b/lib/src/screens/dashboard/sign_page.dart @@ -51,59 +51,11 @@ class SignPage extends BasePage { final List _pages; final GlobalKey signFormKey; final GlobalKey verifyFormKey; + bool _isEffectsInstalled = false; @override Widget body(BuildContext context) { - reaction((_) => signViewModel.state, (ExecutionState state) { - if (state is FailureState) { - WidgetsBinding.instance.addPostFrameCallback((_) { - showPopUp( - context: context, - builder: (_) { - return AlertWithOneAction( - alertTitle: S.current.error, - alertContent: state.error, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop(), - ); - }); - }); - } - if (state is ExecutedSuccessfullyState) { - if (signViewModel.isSigning) { - signFormKey.currentState!.signatureController.text = state.payload as String; - } else { - WidgetsBinding.instance.addPostFrameCallback((_) { - showPopUp( - context: context, - builder: (_) { - return AlertWithOneAction( - alertTitle: S.current.successful, - alertContent: S.current.message_verified, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop(), - ); - }); - }); - } - } - }); - - // reaction((_) => walletRestoreViewModel.mode, (WalletRestoreMode mode) { - // walletRestoreViewModel.isButtonEnabled = false; - - // walletRestoreFromSeedFormKey - // .currentState!.blockchainHeightKey.currentState!.restoreHeightController.text = ''; - // walletRestoreFromSeedFormKey - // .currentState!.blockchainHeightKey.currentState!.dateController.text = ''; - // walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text = ''; - - // walletRestoreFromKeysFormKey - // .currentState!.blockchainHeightKey.currentState!.restoreHeightController.text = ''; - // walletRestoreFromKeysFormKey - // .currentState!.blockchainHeightKey.currentState!.dateController.text = ''; - // walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text = ''; - // }); + _setEffects(context); return KeyboardActions( config: KeyboardActionsConfig( @@ -173,7 +125,7 @@ class SignPage extends BasePage { .extension()! .restoreWalletButtonTextColor, isLoading: signViewModel.state is IsExecutingState, - // isDisabled: !signViewModel.isButtonEnabled, + isDisabled: signViewModel.state is IsExecutingState, ); }, ), @@ -188,6 +140,48 @@ class SignPage extends BasePage { ); } + void _setEffects(BuildContext context) async { + if (_isEffectsInstalled) { + return; + } + _isEffectsInstalled = true; + + reaction((_) => signViewModel.state, (ExecutionState state) { + if (state is FailureState) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showPopUp( + context: context, + builder: (_) { + return AlertWithOneAction( + alertTitle: S.current.error, + alertContent: state.error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ); + }); + }); + } + if (state is ExecutedSuccessfullyState) { + if (signViewModel.isSigning) { + signFormKey.currentState!.signatureController.text = state.payload as String; + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + showPopUp( + context: context, + builder: (_) { + return AlertWithOneAction( + alertTitle: S.current.successful, + alertContent: S.current.message_verified, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ); + }); + }); + } + } + }); + } + Future _confirmForm(BuildContext context) async { FocusManager.instance.primaryFocus?.unfocus(); diff --git a/lib/src/screens/dashboard/widgets/sign_form.dart b/lib/src/screens/dashboard/widgets/sign_form.dart index beac7052a..6a022305a 100644 --- a/lib/src/screens/dashboard/widgets/sign_form.dart +++ b/lib/src/screens/dashboard/widgets/sign_form.dart @@ -75,7 +75,7 @@ class SignFormState extends State { const SizedBox(height: 20), AddressTextField( controller: addressController, - options: [AddressTextFieldOption.paste, AddressTextFieldOption.addressBook], + options: [AddressTextFieldOption.paste, AddressTextFieldOption.walletAddresses], buttonColor: Theme.of(context).hintColor, onSelectedContact: (contact) { addressController.text = contact.address; diff --git a/lib/src/screens/dashboard/widgets/verify_form.dart b/lib/src/screens/dashboard/widgets/verify_form.dart index 84c31cf69..c2dc12949 100644 --- a/lib/src/screens/dashboard/widgets/verify_form.dart +++ b/lib/src/screens/dashboard/widgets/verify_form.dart @@ -70,7 +70,7 @@ class VerifyFormState extends State { const SizedBox(height: 20), AddressTextField( controller: addressController, - options: [AddressTextFieldOption.paste, AddressTextFieldOption.addressBook], + options: [AddressTextFieldOption.paste, AddressTextFieldOption.walletAddresses], buttonColor: Theme.of(context).hintColor, onSelectedContact: (contact) { addressController.text = contact.address; diff --git a/lib/src/screens/receive/address_list_page.dart b/lib/src/screens/receive/address_list_page.dart new file mode 100644 index 000000000..5f6794715 --- /dev/null +++ b/lib/src/screens/receive/address_list_page.dart @@ -0,0 +1,31 @@ +import 'package:cake_wallet/src/screens/receive/widgets/address_list.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +class AddressListPage extends BasePage { + AddressListPage(this.addressListViewModel); + + final WalletAddressListViewModel addressListViewModel; + + @override + String get title => S.current.accounts_subaddresses; + + @override + Widget body(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [ + AddressList( + addressListViewModel: addressListViewModel, + onSelect: (String address) async { + Navigator.of(context).pop(address); + }, + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index ecba4acf5..476100cb9 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/address_list.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/themes/extensions/balance_page_theme.dart'; import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; @@ -123,89 +124,10 @@ class ReceivePage extends BasePage { amountController: _amountController, isLight: currentTheme.type == ThemeType.light), ), - Observer( - builder: (_) => ListView.separated( - padding: EdgeInsets.all(0), - separatorBuilder: (context, _) => const HorizontalSectionDivider(), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: addressListViewModel.items.length, - itemBuilder: (context, index) { - final item = addressListViewModel.items[index]; - Widget cell = Container(); + AddressList( + addressListViewModel: addressListViewModel, + ), - if (item is WalletAccountListHeader) { - cell = HeaderTile( - showTrailingButton: true, - walletAddressListViewModel: addressListViewModel, - trailingButtonTap: () async { - if (addressListViewModel.type == WalletType.monero || - addressListViewModel.type == WalletType.haven) { - await showPopUp( - context: context, - builder: (_) => getIt.get()); - } else { - await showPopUp( - context: context, - builder: (_) => getIt.get()); - } - }, - title: S.of(context).accounts, - trailingIcon: Icon( - Icons.arrow_forward_ios, - size: 14, - color: Theme.of(context).extension()!.iconsColor, - )); - } - - if (item is WalletAddressListHeader) { - cell = HeaderTile( - title: S.of(context).addresses, - walletAddressListViewModel: addressListViewModel, - showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled, - showSearchButton: true, - trailingButtonTap: () => - Navigator.of(context).pushNamed(Routes.newSubaddress), - trailingIcon: Icon( - Icons.add, - size: 20, - color: Theme.of(context) - .extension()! - .iconsColor, - )); - } - - if (item is WalletAddressListItem) { - cell = Observer(builder: (_) { - final isCurrent = - item.address == addressListViewModel.address.address; - final backgroundColor = isCurrent - ? Theme.of(context).extension()!.currentTileBackgroundColor - : Theme.of(context).extension()!.tilesBackgroundColor; - final textColor = isCurrent - ? Theme.of(context).extension()!.currentTileTextColor - : Theme.of(context).extension()!.tilesTextColor; - - return AddressCell.fromItem(item, - isCurrent: isCurrent, - hasBalance: addressListViewModel.isElectrumWallet, - backgroundColor: backgroundColor, - textColor: textColor, - onTap: (_) => addressListViewModel.setAddress(item), - onEdit: () => Navigator.of(context) - .pushNamed(Routes.newSubaddress, arguments: item)); - }); - } - - return index != 0 - ? cell - : ClipRRect( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(30), - topRight: Radius.circular(30)), - child: cell, - ); - })), ], ), )); diff --git a/lib/src/screens/receive/widgets/address_list.dart b/lib/src/screens/receive/widgets/address_list.dart new file mode 100644 index 000000000..7bee6a8bd --- /dev/null +++ b/lib/src/screens/receive/widgets/address_list.dart @@ -0,0 +1,121 @@ +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart'; +import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart'; +import 'package:cake_wallet/src/widgets/section_divider.dart'; +import 'package:cake_wallet/themes/extensions/receive_page_theme.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class AddressList extends StatelessWidget { + const AddressList({ + super.key, + // required this.onTapPicker, + required this.addressListViewModel, + this.onSelect, + }); + + final WalletAddressListViewModel addressListViewModel; + final Function(String)? onSelect; + + @override + Widget build(BuildContext context) { + bool editable = onSelect == null; + return Observer( + builder: (_) => ListView.separated( + padding: EdgeInsets.all(0), + separatorBuilder: (context, _) => const HorizontalSectionDivider(), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: addressListViewModel.items.length, + itemBuilder: (context, index) { + final item = addressListViewModel.items[index]; + Widget cell = Container(); + + if (item is WalletAccountListHeader) { + cell = HeaderTile( + showTrailingButton: editable, + walletAddressListViewModel: addressListViewModel, + trailingButtonTap: () async { + if (addressListViewModel.type == WalletType.monero || + addressListViewModel.type == WalletType.haven) { + await showPopUp( + context: context, builder: (_) => getIt.get()); + } else { + await showPopUp( + context: context, builder: (_) => getIt.get()); + } + }, + title: S.of(context).accounts, + trailingIcon: Icon( + Icons.arrow_forward_ios, + size: 14, + color: Theme.of(context).extension()!.iconsColor, + )); + } + + if (item is WalletAddressListHeader) { + cell = HeaderTile( + title: S.of(context).addresses, + walletAddressListViewModel: addressListViewModel, + showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled, + showSearchButton: true, + trailingButtonTap: () => Navigator.of(context).pushNamed(Routes.newSubaddress), + trailingIcon: Icon( + Icons.add, + size: 20, + color: Theme.of(context).extension()!.iconsColor, + )); + } + + if (item is WalletAddressListItem) { + cell = Observer(builder: (_) { + final isCurrent = item.address == addressListViewModel.address.address && editable; + final backgroundColor = isCurrent + ? Theme.of(context).extension()!.currentTileBackgroundColor + : Theme.of(context).extension()!.tilesBackgroundColor; + final textColor = isCurrent + ? Theme.of(context).extension()!.currentTileTextColor + : Theme.of(context).extension()!.tilesTextColor; + + return AddressCell.fromItem( + item, + isCurrent: isCurrent, + hasBalance: addressListViewModel.isElectrumWallet, + backgroundColor: backgroundColor, + textColor: textColor, + onTap: (_) { + if (onSelect != null) { + onSelect!(item.address); + return; + } + addressListViewModel.setAddress(item); + }, + onEdit: editable + ? () => Navigator.of(context).pushNamed(Routes.newSubaddress, arguments: item) + : null, + ); + }); + } + + return index != 0 + ? cell + : ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(30), topRight: Radius.circular(30)), + child: cell, + ); + }, + ), + ); + } +} diff --git a/lib/src/widgets/address_text_field.dart b/lib/src/widgets/address_text_field.dart index 0467b18a2..badba77d2 100644 --- a/lib/src/widgets/address_text_field.dart +++ b/lib/src/widgets/address_text_field.dart @@ -12,7 +12,7 @@ import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; import 'package:cake_wallet/utils/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart'; -enum AddressTextFieldOption { paste, qrCode, addressBook } +enum AddressTextFieldOption { paste, qrCode, addressBook, walletAddresses } class AddressTextField extends StatelessWidget { AddressTextField( @@ -31,6 +31,7 @@ class AddressTextField extends StatelessWidget { this.validator, this.onPushPasteButton, this.onPushAddressBookButton, + this.onPushAddressPickerButton, this.onSelectedContact, this.selectedCurrency}); @@ -53,6 +54,7 @@ class AddressTextField extends StatelessWidget { final FocusNode? focusNode; final Function(BuildContext context)? onPushPasteButton; final Function(BuildContext context)? onPushAddressBookButton; + final Function(BuildContext context)? onPushAddressPickerButton; final Function(ContactBase contact)? onSelectedContact; final CryptoCurrency? selectedCurrency; @@ -67,16 +69,13 @@ class AddressTextField extends StatelessWidget { enabled: isActive, controller: controller, focusNode: focusNode, - style: textStyle ?? TextStyle( fontSize: 16, color: Theme.of(context).extension()!.titleColor), decoration: InputDecoration( - suffixIcon: SizedBox( width: prefixIconWidth * options.length + (spaceBetweenPrefixIcons * options.length), ), - hintStyle: hintStyle ?? TextStyle(fontSize: 16, color: Theme.of(context).hintColor), hintText: placeholder ?? S.current.widgets_address, focusedBorder: isBorderExist @@ -101,90 +100,122 @@ class AddressTextField extends StatelessWidget { top: 2, right: 0, child: SizedBox( - width: prefixIconWidth * options.length + (spaceBetweenPrefixIcons * options.length), + width: + (prefixIconWidth * options.length) + (spaceBetweenPrefixIcons * options.length), child: Row( mainAxisAlignment: responsiveLayoutUtil.shouldRenderMobileUI ? MainAxisAlignment.spaceBetween : MainAxisAlignment.end, children: [ - SizedBox(width: 5), if (this.options.contains(AddressTextFieldOption.paste)) ...[ + SizedBox(width: 5), Container( - width: prefixIconWidth, - height: prefixIconHeight, - padding: EdgeInsets.only(top: 0), - child: Semantics( - label: S.of(context).paste, - child: InkWell( - onTap: () async => _pasteAddress(context), - child: Container( - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - color: buttonColor ?? - Theme.of(context).dialogTheme.backgroundColor, - borderRadius: BorderRadius.all(Radius.circular(6))), - child: Image.asset( - 'assets/images/paste_ios.png', - color: iconColor ?? - Theme.of(context) - .extension()! - .textFieldButtonIconColor, - )), - ), - )), + width: prefixIconWidth, + height: prefixIconHeight, + padding: EdgeInsets.only(top: 0), + child: Semantics( + label: S.of(context).paste, + child: InkWell( + onTap: () async => _pasteAddress(context), + child: Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: + buttonColor ?? Theme.of(context).dialogTheme.backgroundColor, + borderRadius: BorderRadius.all(Radius.circular(6))), + child: Image.asset( + 'assets/images/paste_ios.png', + color: iconColor ?? + Theme.of(context) + .extension()! + .textFieldButtonIconColor, + )), + ), + ), + ), ], if (this.options.contains(AddressTextFieldOption.qrCode) && DeviceInfo.instance.isMobile) ...[ - Container( - width: prefixIconWidth, - height: prefixIconHeight, - padding: EdgeInsets.only(top: 0), - child: Semantics( - label: S.of(context).scan_qr_code, - child: InkWell( - onTap: () async => _presentQRScanner(context), - child: Container( - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - color: buttonColor ?? - Theme.of(context).dialogTheme.backgroundColor, - borderRadius: BorderRadius.all(Radius.circular(6))), - child: Image.asset( - 'assets/images/qr_code_icon.png', - color: iconColor ?? - Theme.of(context) - .extension()! - .textFieldButtonIconColor, - )), - ), - )) - ] else SizedBox(width: 5), - if (this.options.contains(AddressTextFieldOption.addressBook)) ...[ Container( - width: prefixIconWidth, - height: prefixIconHeight, - padding: EdgeInsets.only(top: 0), - child: Semantics( - label: S.of(context).address_book, - child: InkWell( - onTap: () async => _presetAddressBookPicker(context), - child: Container( - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - color: buttonColor ?? - Theme.of(context).dialogTheme.backgroundColor, - borderRadius: BorderRadius.all(Radius.circular(6))), - child: Image.asset( - 'assets/images/open_book.png', - color: iconColor ?? - Theme.of(context) - .extension()! - .textFieldButtonIconColor, - )), - ), - )) - ] + width: prefixIconWidth, + height: prefixIconHeight, + padding: EdgeInsets.only(top: 0), + child: Semantics( + label: S.of(context).scan_qr_code, + child: InkWell( + onTap: () async => _presentQRScanner(context), + child: Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: + buttonColor ?? Theme.of(context).dialogTheme.backgroundColor, + borderRadius: BorderRadius.all(Radius.circular(6))), + child: Image.asset( + 'assets/images/qr_code_icon.png', + color: iconColor ?? + Theme.of(context) + .extension()! + .textFieldButtonIconColor, + )), + ), + ), + ), + ], + if (this.options.contains(AddressTextFieldOption.addressBook)) ...[ + SizedBox(width: 5), + Container( + width: prefixIconWidth, + height: prefixIconHeight, + padding: EdgeInsets.only(top: 0), + child: Semantics( + label: S.of(context).address_book, + child: InkWell( + onTap: () async => _presetAddressBookPicker(context), + child: Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: + buttonColor ?? Theme.of(context).dialogTheme.backgroundColor, + borderRadius: BorderRadius.all(Radius.circular(6))), + child: Image.asset( + 'assets/images/open_book.png', + color: iconColor ?? + Theme.of(context) + .extension()! + .textFieldButtonIconColor, + )), + ), + ), + ), + ], + if (this.options.contains(AddressTextFieldOption.walletAddresses)) ...[ + SizedBox(width: 5), + Container( + width: prefixIconWidth, + height: prefixIconHeight, + padding: EdgeInsets.only(top: 0), + child: Semantics( + label: S.of(context).address_book, + child: InkWell( + onTap: () async => _presetWalletAddressPicker(context), + child: Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: + buttonColor ?? Theme.of(context).dialogTheme.backgroundColor, + borderRadius: BorderRadius.all(Radius.circular(6))), + child: Image.asset( + 'assets/images/open_book.png', + color: iconColor ?? + Theme.of(context) + .extension()! + .textFieldButtonIconColor, + )), + ), + ), + ) + ], ], ), )) @@ -194,7 +225,7 @@ class AddressTextField extends StatelessWidget { Future _presentQRScanner(BuildContext context) async { bool isCameraPermissionGranted = - await PermissionHandler.checkPermission(Permission.camera, context); + await PermissionHandler.checkPermission(Permission.camera, context); if (!isCameraPermissionGranted) return; final code = await presentQRScanner(); if (code.isEmpty) { @@ -221,6 +252,15 @@ class AddressTextField extends StatelessWidget { } } + Future _presetWalletAddressPicker(BuildContext context) async { + final address = await Navigator.of(context).pushNamed(Routes.pickerWalletAddress); + + if (address is String) { + controller?.text = address; + onPushAddressPickerButton?.call(context); + } + } + Future _pasteAddress(BuildContext context) async { final clipboard = await Clipboard.getData('text/plain'); final address = clipboard?.text ?? '';