mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-22 06:28:39 +02:00
Cw 565 sign messages (#1378)
* version bump to 3.13.9, auth working on mac * bump flutter version in workflow file * workflow fix * test fix * downgrade flutter version * test fix * test fix * update gradle version * start working on ui for message signing * updates * sign working for a few wallet types * updates & verification for electrum currencies * nano support * sign/verify working on eth, bitcoin broken * update translations * Implement Verify Message for Monero * save [skip ci] * pub key extraction working * fixes for electrum signing * verify working for solana! * electrum still not working :( [skip ci] * electrum messages working! * fixes for updated dart version, localization file updates * remove accidental inclusion * missed some unimplemented throws * Update res/values/strings_de.arb Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com> * Apply suggestions from code review Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com> * review suggestions and updates [skip ci] * [skip ci] add polygon * [skip ci] merge mac-auth/update version * fix litecoin * bio auth mac fix * remove comment and change duration from 2 to 0 * cherry pick previous changes * litecoin fixes, sign form fixes, use new walletAddressPicker * support accounts * verify messages working for monero * working sign and verify messages for nano * electrum signing working [skip ci] * additional nano fixes * update translations * attempt to decode signatures with base64 * workaround for secure storage bug on mac * bump version to 3.19.5 (because breez will need this version anyways) * some code cleanup * some changess didn't get saved * just documenting the issue [skip ci] * undo accidental removal + minor code cleanup * merge conflicts * merge fixes [skip ci] * add tron support * [wip] fixing * remove duplicate references to electrum path for maintainability * fixes * minor fix * fixes * undo debug comment * update migration for all electrum based wallets * hotfixes * copy over the rest of the fixes * minor code cleanup [skip ci] * updates * electrum signing workinggit statusgit statusgit statusgit status! * copy same fixes for litecoin * litecoin fixes * add v to litecoin signatures * fix dependencies * fix bitcoin_base version * merge fix * dep override * fix conflicts with main * trial fix for android build * fixes * fix * dep fix, should build * fix signing for bitcoin cash * [skip ci] minor code cleanup * [skip ci] minor code cleanup 2 * forgot wonero, various other fixes * more fixes * fix solana (untested) --------- Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
Konstantin Ullrich
Omar Hatem
parent
eef319658a
commit
83ef61e928
@@ -275,7 +275,7 @@ class CWBitcoin extends Bitcoin {
|
||||
return [DerivationType.bip39, DerivationType.electrum];
|
||||
}
|
||||
|
||||
int _countOccurrences(String str, String charToCount) {
|
||||
int _countCharOccurrences(String str, String charToCount) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < str.length; i++) {
|
||||
if (str[i] == charToCount) {
|
||||
@@ -330,7 +330,7 @@ class CWBitcoin extends Bitcoin {
|
||||
);
|
||||
|
||||
String balancePath = dInfoCopy.derivationPath!;
|
||||
int derivationDepth = _countOccurrences(balancePath, "/");
|
||||
int derivationDepth = _countCharOccurrences(balancePath, '/');
|
||||
|
||||
// for BIP44
|
||||
if (derivationDepth == 3 || derivationDepth == 1) {
|
||||
|
||||
@@ -124,12 +124,12 @@ class DFXBuyProvider extends BuyProvider {
|
||||
switch (wallet.type) {
|
||||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
return wallet.signMessage(message);
|
||||
return await wallet.signMessage(message);
|
||||
case WalletType.monero:
|
||||
case WalletType.litecoin:
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.bitcoinCash:
|
||||
return wallet.signMessage(message, address: walletAddress);
|
||||
return await wallet.signMessage(message, address: walletAddress);
|
||||
default:
|
||||
throw Exception("WalletType is not available for DFX ${wallet.type}");
|
||||
}
|
||||
|
||||
@@ -37,15 +37,15 @@ class RobinhoodBuyProvider extends BuyProvider {
|
||||
|
||||
String get _apiSecret => secrets.exchangeHelperApiKey;
|
||||
|
||||
Future<String> getSignature(String message) {
|
||||
Future<String> getSignature(String message) async {
|
||||
switch (wallet.type) {
|
||||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
return wallet.signMessage(message);
|
||||
return await wallet.signMessage(message);
|
||||
case WalletType.litecoin:
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.bitcoinCash:
|
||||
return wallet.signMessage(message, address: wallet.walletAddresses.address);
|
||||
return await wallet.signMessage(message, address: wallet.walletAddresses.address);
|
||||
default:
|
||||
throw Exception("WalletType is not available for Robinhood ${wallet.type}");
|
||||
}
|
||||
|
||||
+11
-5
@@ -30,6 +30,12 @@ import 'package:cake_wallet/entities/contact.dart';
|
||||
import 'package:cake_wallet/entities/contact_record.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/receive/address_list_page.dart';
|
||||
import 'package:cake_wallet/view_model/link_view_model.dart';
|
||||
import 'package:cake_wallet/tron/tron.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';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/entities/qr_view_data.dart';
|
||||
import 'package:cake_wallet/entities/template.dart';
|
||||
@@ -159,7 +165,6 @@ import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
|
||||
@@ -179,7 +184,6 @@ import 'package:cake_wallet/store/templates/exchange_template_store.dart';
|
||||
import 'package:cake_wallet/store/templates/send_template_store.dart';
|
||||
import 'package:cake_wallet/store/wallet_list_store.dart';
|
||||
import 'package:cake_wallet/store/yat/yat_store.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/view_model/auth_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/backup_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_amount_view_model.dart';
|
||||
@@ -193,7 +197,6 @@ import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/link_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
||||
@@ -224,7 +227,6 @@ import 'package:cake_wallet/view_model/wallet_unlock_loadable_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_unlock_verifiable_view_model.dart';
|
||||
import 'package:cake_wallet/wownero/wownero.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/receive_page_option.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -853,6 +855,8 @@ Future<void> setup({
|
||||
getIt.registerFactoryParam<ContactPage, ContactRecord?, void>(
|
||||
(ContactRecord? contact, _) => ContactPage(getIt.get<ContactViewModel>(param1: contact)));
|
||||
|
||||
getIt.registerFactory(() => AddressListPage(getIt.get<WalletAddressListViewModel>()));
|
||||
|
||||
getIt.registerFactory(() {
|
||||
final appStore = getIt.get<AppStore>();
|
||||
return NodeListViewModel(_nodeSource, appStore);
|
||||
@@ -1271,9 +1275,11 @@ Future<void> setup({
|
||||
|
||||
getIt.registerFactory(
|
||||
() => WalletConnectConnectionsView(web3walletService: getIt.get<Web3WalletService>()));
|
||||
|
||||
|
||||
getIt.registerFactory(() => NFTViewModel(appStore, getIt.get<BottomSheetService>()));
|
||||
getIt.registerFactory<TorPage>(() => TorPage(getIt.get<AppStore>()));
|
||||
|
||||
getIt.registerFactory(() => SignViewModel(getIt.get<AppStore>().wallet!));
|
||||
|
||||
_isSetupFinished = true;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/nft_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/sign_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
|
||||
import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
|
||||
@@ -42,6 +43,8 @@ import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart';
|
||||
import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart';
|
||||
import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_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/order_details/order_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
||||
@@ -99,6 +102,7 @@ import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
||||
@@ -465,6 +469,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<ContactListPage>(param1: selectedCurrency));
|
||||
|
||||
case Routes.pickerWalletAddress:
|
||||
return MaterialPageRoute<void>(builder: (_) => getIt.get<AddressListPage>());
|
||||
|
||||
case Routes.addressBookAddContact:
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<ContactPage>(param1: settings.arguments as ContactRecord?));
|
||||
@@ -696,6 +703,13 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
case Routes.torPage:
|
||||
return MaterialPageRoute<void>(builder: (_) => getIt.get<TorPage>());
|
||||
|
||||
case Routes.signPage:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => SignPage(
|
||||
getIt.get<SignViewModel>(),
|
||||
),
|
||||
);
|
||||
|
||||
case Routes.connectDevices:
|
||||
final params = settings.arguments as ConnectDevicePageParams;
|
||||
return MaterialPageRoute<void>(
|
||||
|
||||
@@ -31,6 +31,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';
|
||||
@@ -103,5 +104,6 @@ class Routes {
|
||||
static const nftDetailsPage = '/nft_details_page';
|
||||
static const importNFTPage = '/import_nft_page';
|
||||
static const torPage = '/tor_page';
|
||||
static const signPage = '/sign_page';
|
||||
static const connectDevices = '/device/connect';
|
||||
}
|
||||
|
||||
@@ -322,31 +322,31 @@ class CakePayBuyCardDetailPage extends BasePage {
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
builder: (popupContext) {
|
||||
return Observer(
|
||||
builder: (_) => ConfirmSendingAlert(
|
||||
alertTitle: S.of(context).confirm_sending,
|
||||
paymentId: S.of(context).payment_id,
|
||||
alertTitle: S.of(popupContext).confirm_sending,
|
||||
paymentId: S.of(popupContext).payment_id,
|
||||
paymentIdValue: order?.orderId,
|
||||
expirationTime: cakePayPurchaseViewModel.formattedRemainingTime,
|
||||
onDispose: () => _handleDispose(disposer),
|
||||
amount: S.of(context).send_amount,
|
||||
amount: S.of(popupContext).send_amount,
|
||||
amountValue: pendingTransaction.amountFormatted,
|
||||
fiatAmountValue:
|
||||
cakePayPurchaseViewModel.sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||
fee: S.of(context).send_fee,
|
||||
fee: S.of(popupContext).send_fee,
|
||||
feeValue: pendingTransaction.feeFormatted,
|
||||
feeFiatAmount:
|
||||
cakePayPurchaseViewModel.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
feeRate: pendingTransaction.feeRate,
|
||||
outputs: cakePayPurchaseViewModel.sendViewModel.outputs,
|
||||
rightButtonText: S.of(context).send,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
rightButtonText: S.of(popupContext).send,
|
||||
leftButtonText: S.of(popupContext).cancel,
|
||||
actionRightButton: () async {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(popupContext).pop();
|
||||
await cakePayPurchaseViewModel.sendViewModel.commitTransaction();
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(context).pop()));
|
||||
actionLeftButton: () => Navigator.of(popupContext).pop()));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
@@ -64,6 +65,19 @@ class CakeFeaturesPage extends StatelessWidget {
|
||||
subTitle: S.of(context).nanogpt_subtitle,
|
||||
onTap: () => _launchUrl("cake.nano-gpt.com"),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Observer(
|
||||
builder: (context) {
|
||||
if (!dashboardViewModel.hasSignMessages) {
|
||||
return const SizedBox();
|
||||
}
|
||||
return DashBoardRoundedCardWidget(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.signPage),
|
||||
title: S.current.sign_verify_message,
|
||||
subTitle: S.current.sign_verify_message_sub,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/sign_form.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/verify_form.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
|
||||
|
||||
class SignPage extends BasePage {
|
||||
SignPage(this.signViewModel)
|
||||
: signFormKey = GlobalKey<SignFormState>(),
|
||||
verifyFormKey = GlobalKey<VerifyFormState>(),
|
||||
_pages = [],
|
||||
_controller = PageController(initialPage: 0) {
|
||||
_pages.add(SignForm(
|
||||
key: signFormKey,
|
||||
type: signViewModel.wallet.type,
|
||||
includeAddress: signViewModel.signIncludesAddress,
|
||||
));
|
||||
_pages.add(VerifyForm(
|
||||
key: verifyFormKey,
|
||||
type: signViewModel.wallet.type,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget middle(BuildContext context) => Observer(
|
||||
builder: (_) => Text(
|
||||
S.current.sign_verify_title,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Lato',
|
||||
color: titleColor(context),
|
||||
),
|
||||
));
|
||||
|
||||
final SignViewModel signViewModel;
|
||||
final PageController _controller;
|
||||
final List<Widget> _pages;
|
||||
final GlobalKey<SignFormState> signFormKey;
|
||||
final GlobalKey<VerifyFormState> verifyFormKey;
|
||||
bool _isEffectsInstalled = false;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
_setEffects(context);
|
||||
|
||||
return KeyboardActions(
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: FocusNode(),
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Container(
|
||||
height: 0,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: PageView.builder(
|
||||
onPageChanged: (page) {
|
||||
signViewModel.isSigning = page == 0;
|
||||
},
|
||||
controller: _controller,
|
||||
itemCount: _pages.length,
|
||||
itemBuilder: (_, index) => SingleChildScrollView(child: _pages[index]),
|
||||
),
|
||||
),
|
||||
if (_pages.length > 1)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: SmoothPageIndicator(
|
||||
controller: _controller,
|
||||
count: _pages.length,
|
||||
effect: ColorTransitionEffect(
|
||||
spacing: 6.0,
|
||||
radius: 6.0,
|
||||
dotWidth: 6.0,
|
||||
dotHeight: 6.0,
|
||||
dotColor: Theme.of(context).hintColor.withOpacity(0.5),
|
||||
activeDotColor: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20, bottom: 24, left: 24, right: 24),
|
||||
child: Column(
|
||||
children: [
|
||||
Observer(
|
||||
builder: (context) {
|
||||
return LoadingPrimaryButton(
|
||||
onPressed: () async {
|
||||
await _confirmForm(context);
|
||||
},
|
||||
text: signViewModel.isSigning
|
||||
? S.current.sign_message
|
||||
: S.current.verify_message,
|
||||
color: Theme.of(context)
|
||||
.extension<WalletListTheme>()!
|
||||
.createNewWalletButtonBackgroundColor,
|
||||
textColor: Theme.of(context)
|
||||
.extension<WalletListTheme>()!
|
||||
.restoreWalletButtonTextColor,
|
||||
isLoading: signViewModel.state is IsExecutingState,
|
||||
isDisabled: signViewModel.state is IsExecutingState,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _setEffects(BuildContext context) async {
|
||||
if (_isEffectsInstalled) {
|
||||
return;
|
||||
}
|
||||
_isEffectsInstalled = true;
|
||||
|
||||
reaction((_) => signViewModel.state, (ExecutionState state) {
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
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<void>(
|
||||
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<void> _confirmForm(BuildContext context) async {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
if (signViewModel.isSigning) {
|
||||
String message = signFormKey.currentState!.messageController.text;
|
||||
String? address;
|
||||
if (signViewModel.signIncludesAddress) {
|
||||
address = signFormKey.currentState!.addressController.text;
|
||||
}
|
||||
await signViewModel.sign(message, address: address);
|
||||
} else {
|
||||
String message = verifyFormKey.currentState!.messageController.text;
|
||||
String signature = verifyFormKey.currentState!.signatureController.text;
|
||||
String address = verifyFormKey.currentState!.addressController.text;
|
||||
await signViewModel.verify(message, signature, address: address);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/address_text_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class SignForm extends StatefulWidget {
|
||||
SignForm({
|
||||
Key? key,
|
||||
required this.type,
|
||||
required this.includeAddress,
|
||||
}) : super(key: key);
|
||||
|
||||
final WalletType type;
|
||||
final bool includeAddress;
|
||||
|
||||
@override
|
||||
SignFormState createState() => SignFormState();
|
||||
}
|
||||
|
||||
class SignFormState extends State<SignForm> {
|
||||
SignFormState()
|
||||
: formKey = GlobalKey<FormState>(),
|
||||
messageController = TextEditingController(),
|
||||
addressController = TextEditingController(),
|
||||
signatureController = TextEditingController();
|
||||
|
||||
final TextEditingController messageController;
|
||||
final TextEditingController addressController;
|
||||
final TextEditingController signatureController;
|
||||
final GlobalKey<FormState> formKey;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
child: Column(
|
||||
children: [
|
||||
Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
AddressTextField(
|
||||
controller: messageController,
|
||||
placeholder: S.current.message,
|
||||
options: [AddressTextFieldOption.paste],
|
||||
buttonColor: Theme.of(context).hintColor,
|
||||
),
|
||||
if (widget.includeAddress) ...[
|
||||
const SizedBox(height: 20),
|
||||
AddressTextField(
|
||||
controller: addressController,
|
||||
options: [
|
||||
AddressTextFieldOption.paste,
|
||||
AddressTextFieldOption.walletAddresses
|
||||
],
|
||||
buttonColor: Theme.of(context).hintColor,
|
||||
onSelectedContact: (contact) {
|
||||
addressController.text = contact.address;
|
||||
},
|
||||
selectedCurrency: walletTypeToCryptoCurrency(widget.type),
|
||||
),
|
||||
],
|
||||
],
|
||||
)),
|
||||
const SizedBox(height: 20),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
final text = signatureController.text;
|
||||
if (text.isEmpty) {
|
||||
return;
|
||||
}
|
||||
Clipboard.setData(ClipboardData(text: text));
|
||||
showBar<void>(context, S.of(context).transaction_details_copied(text));
|
||||
},
|
||||
child: BaseTextFormField(
|
||||
enabled: false,
|
||||
controller: signatureController,
|
||||
hintText: S.current.signature,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import 'package:cake_wallet/core/wallet_name_validator.dart';
|
||||
import 'package:cake_wallet/entities/generate_name.dart';
|
||||
import 'package:cake_wallet/entities/seed_type.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/address_text_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/picker.dart';
|
||||
import 'package:cake_wallet/src/widgets/seed_language_picker.dart';
|
||||
import 'package:cake_wallet/src/widgets/seed_widget.dart';
|
||||
import 'package:cake_wallet/themes/extensions/address_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/seed_type_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:polyseed/polyseed.dart';
|
||||
|
||||
class VerifyForm extends StatefulWidget {
|
||||
VerifyForm({
|
||||
Key? key,
|
||||
required this.type,
|
||||
}) : super(key: key);
|
||||
|
||||
final WalletType type;
|
||||
|
||||
@override
|
||||
VerifyFormState createState() => VerifyFormState();
|
||||
}
|
||||
|
||||
class VerifyFormState extends State<VerifyForm> {
|
||||
VerifyFormState()
|
||||
: formKey = GlobalKey<FormState>(),
|
||||
messageController = TextEditingController(),
|
||||
addressController = TextEditingController(),
|
||||
signatureController = TextEditingController();
|
||||
|
||||
final TextEditingController messageController;
|
||||
final TextEditingController addressController;
|
||||
final TextEditingController signatureController;
|
||||
final GlobalKey<FormState> formKey;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
child: Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
AddressTextField(
|
||||
controller: messageController,
|
||||
placeholder: S.current.message,
|
||||
options: [AddressTextFieldOption.paste],
|
||||
buttonColor: Theme.of(context).hintColor,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
AddressTextField(
|
||||
controller: addressController,
|
||||
options: [AddressTextFieldOption.paste, AddressTextFieldOption.walletAddresses],
|
||||
buttonColor: Theme.of(context).hintColor,
|
||||
onSelectedContact: (contact) {
|
||||
addressController.text = contact.address;
|
||||
},
|
||||
selectedCurrency: walletTypeToCryptoCurrency(widget.type),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
AddressTextField(
|
||||
controller: signatureController,
|
||||
placeholder: S.current.signature,
|
||||
options: [AddressTextFieldOption.paste],
|
||||
buttonColor: Theme.of(context).hintColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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: <Widget>[
|
||||
AddressList(
|
||||
addressListViewModel: addressListViewModel,
|
||||
onSelect: (String address) async {
|
||||
Navigator.of(context).pop(address);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
@@ -122,108 +123,7 @@ 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();
|
||||
|
||||
if (item is WalletAccountListHeader) {
|
||||
cell = HeaderTile(
|
||||
showTrailingButton: true,
|
||||
walletAddressListViewModel: addressListViewModel,
|
||||
trailingButtonTap: () async {
|
||||
if (addressListViewModel.type == WalletType.monero ||
|
||||
addressListViewModel.type == WalletType.wownero ||
|
||||
addressListViewModel.type == WalletType.haven) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) => getIt.get<MoneroAccountListPage>());
|
||||
} else {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) => getIt.get<NanoAccountListPage>());
|
||||
}
|
||||
},
|
||||
title: S.of(context).accounts,
|
||||
trailingIcon: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||
));
|
||||
}
|
||||
|
||||
if (item is WalletAddressListHeader) {
|
||||
final hasTitle = item.title != null;
|
||||
|
||||
cell = HeaderTile(
|
||||
title: hasTitle ? item.title! : S.of(context).addresses,
|
||||
walletAddressListViewModel: addressListViewModel,
|
||||
showTrailingButton:
|
||||
!addressListViewModel.isAutoGenerateSubaddressEnabled && !hasTitle,
|
||||
showSearchButton: true,
|
||||
trailingButtonTap: () =>
|
||||
Navigator.of(context).pushNamed(Routes.newSubaddress),
|
||||
trailingIcon: hasTitle
|
||||
? null
|
||||
: Icon(
|
||||
Icons.add,
|
||||
size: 20,
|
||||
color:
|
||||
Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (item is WalletAddressListItem) {
|
||||
cell = Observer(builder: (_) {
|
||||
final isCurrent = item.address == addressListViewModel.address.address;
|
||||
final backgroundColor = isCurrent
|
||||
? Theme.of(context)
|
||||
.extension<ReceivePageTheme>()!
|
||||
.currentTileBackgroundColor
|
||||
: Theme.of(context)
|
||||
.extension<ReceivePageTheme>()!
|
||||
.tilesBackgroundColor;
|
||||
final textColor = isCurrent
|
||||
? Theme.of(context)
|
||||
.extension<ReceivePageTheme>()!
|
||||
.currentTileTextColor
|
||||
: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
|
||||
|
||||
return AddressCell.fromItem(
|
||||
item,
|
||||
isCurrent: isCurrent,
|
||||
hasBalance: addressListViewModel.isElectrumWallet,
|
||||
backgroundColor: backgroundColor,
|
||||
textColor: textColor,
|
||||
onTap: item.isOneTimeReceiveAddress == true
|
||||
? null
|
||||
: (_) => addressListViewModel.setAddress(item),
|
||||
onEdit: item.isOneTimeReceiveAddress == true || item.isPrimary
|
||||
? null
|
||||
: () => Navigator.of(context)
|
||||
.pushNamed(Routes.newSubaddress, arguments: item),
|
||||
onDelete: !addressListViewModel.isSilentPayments || item.isPrimary
|
||||
? null
|
||||
: () => addressListViewModel.deleteAddress(item),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return index != 0
|
||||
? cell
|
||||
: ClipRRect(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(30), topRight: Radius.circular(30)),
|
||||
child: cell,
|
||||
);
|
||||
})),
|
||||
AddressList(addressListViewModel: addressListViewModel),
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
|
||||
child: Text(
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
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.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: true,
|
||||
walletAddressListViewModel: addressListViewModel,
|
||||
trailingButtonTap: () async {
|
||||
if (addressListViewModel.type == WalletType.monero ||
|
||||
addressListViewModel.type == WalletType.haven) {
|
||||
await showPopUp<void>(
|
||||
context: context, builder: (_) => getIt.get<MoneroAccountListPage>());
|
||||
} else {
|
||||
await showPopUp<void>(
|
||||
context: context, builder: (_) => getIt.get<NanoAccountListPage>());
|
||||
}
|
||||
},
|
||||
title: S.of(context).accounts,
|
||||
trailingIcon: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.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<ReceivePageTheme>()!.iconsColor,
|
||||
));
|
||||
}
|
||||
|
||||
if (item is WalletAddressListItem) {
|
||||
cell = Observer(builder: (_) {
|
||||
final isCurrent = item.address == addressListViewModel.address.address && editable;
|
||||
final backgroundColor = isCurrent
|
||||
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileBackgroundColor
|
||||
: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor;
|
||||
final textColor = isCurrent
|
||||
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileTextColor
|
||||
: Theme.of(context).extension<ReceivePageTheme>()!.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,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
@@ -34,6 +34,7 @@ class AddressTextField extends StatelessWidget {
|
||||
this.validator,
|
||||
this.onPushPasteButton,
|
||||
this.onPushAddressBookButton,
|
||||
this.onPushAddressPickerButton,
|
||||
this.onSelectedContact,
|
||||
this.selectedCurrency});
|
||||
|
||||
@@ -56,6 +57,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;
|
||||
|
||||
@@ -70,16 +72,13 @@ class AddressTextField extends StatelessWidget {
|
||||
enabled: isActive,
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
|
||||
style: textStyle ??
|
||||
TextStyle(
|
||||
fontSize: 16, color: Theme.of(context).extension<CakeTextTheme>()!.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
|
||||
@@ -104,90 +103,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<SendPageTheme>()!
|
||||
.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<SendPageTheme>()!
|
||||
.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<SendPageTheme>()!
|
||||
.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<SendPageTheme>()!
|
||||
.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<SendPageTheme>()!
|
||||
.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<SendPageTheme>()!
|
||||
.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<SendPageTheme>()!
|
||||
.textFieldButtonIconColor,
|
||||
)),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
],
|
||||
),
|
||||
))
|
||||
@@ -197,7 +228,7 @@ class AddressTextField extends StatelessWidget {
|
||||
|
||||
Future<void> _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) {
|
||||
@@ -224,6 +255,15 @@ class AddressTextField extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _presetWalletAddressPicker(BuildContext context) async {
|
||||
final address = await Navigator.of(context).pushNamed(Routes.pickerWalletAddress);
|
||||
|
||||
if (address is String) {
|
||||
controller?.text = address;
|
||||
onPushAddressPickerButton?.call(context);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _pasteAddress(BuildContext context) async {
|
||||
final clipboard = await Clipboard.getData('text/plain');
|
||||
final address = clipboard?.text ?? '';
|
||||
|
||||
@@ -182,7 +182,8 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
final _accountTransactions = _wallet.transactionHistory.transactions.values
|
||||
.where((tx) =>
|
||||
wow.wownero!.getTransactionInfoAccountId(tx) == wow.wownero!.getCurrentAccount(wallet).id)
|
||||
wow.wownero!.getTransactionInfoAccountId(tx) ==
|
||||
wow.wownero!.getCurrentAccount(wallet).id)
|
||||
.toList();
|
||||
|
||||
final sortedTransactions = [..._accountTransactions];
|
||||
@@ -482,6 +483,27 @@ abstract class DashboardViewModelBase with Store {
|
||||
@computed
|
||||
bool get hasPowNodes => wallet.type == WalletType.nano || wallet.type == WalletType.banano;
|
||||
|
||||
@computed
|
||||
bool get hasSignMessages {
|
||||
switch (wallet.type) {
|
||||
case WalletType.monero:
|
||||
case WalletType.litecoin:
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.bitcoinCash:
|
||||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
case WalletType.solana:
|
||||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
case WalletType.tron:
|
||||
case WalletType.wownero:
|
||||
return true;
|
||||
case WalletType.haven:
|
||||
case WalletType.none:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool get showRepWarning {
|
||||
if (wallet.type != WalletType.nano) {
|
||||
return false;
|
||||
@@ -575,7 +597,8 @@ abstract class DashboardViewModelBase with Store {
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.wownero) {
|
||||
return wow.wownero!.getTransactionInfoAccountId(tx) == wow.wownero!.getCurrentAccount(wallet).id;
|
||||
return wow.wownero!.getTransactionInfoAccountId(tx) ==
|
||||
wow.wownero!.getCurrentAccount(wallet).id;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -600,8 +623,8 @@ abstract class DashboardViewModelBase with Store {
|
||||
.getTransactionHistory(wallet)
|
||||
.transactions
|
||||
.values
|
||||
.where(
|
||||
(tx) => monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id)
|
||||
.where((tx) =>
|
||||
monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id)
|
||||
.toList();
|
||||
|
||||
transactions.addAll(_accountTransactions.map((transaction) => TransactionListItem(
|
||||
@@ -613,8 +636,9 @@ abstract class DashboardViewModelBase with Store {
|
||||
.getTransactionHistory(wallet)
|
||||
.transactions
|
||||
.values
|
||||
.where(
|
||||
(tx) => wow.wownero!.getTransactionInfoAccountId(tx) == wow.wownero!.getCurrentAccount(wallet).id)
|
||||
.where((tx) =>
|
||||
wow.wownero!.getTransactionInfoAccountId(tx) ==
|
||||
wow.wownero!.getCurrentAccount(wallet).id)
|
||||
.toList();
|
||||
|
||||
transactions.addAll(_accountTransactions.map((transaction) => TransactionListItem(
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'sign_view_model.g.dart';
|
||||
|
||||
class SignViewModel = SignViewModelBase with _$SignViewModel;
|
||||
|
||||
abstract class SignViewModelBase with Store {
|
||||
SignViewModelBase(this.wallet) : state = InitialExecutionState();
|
||||
|
||||
final WalletBase wallet;
|
||||
|
||||
@observable
|
||||
ExecutionState state;
|
||||
|
||||
@observable
|
||||
bool isSigning = true;
|
||||
|
||||
bool get signIncludesAddress => [
|
||||
WalletType.monero,
|
||||
WalletType.bitcoin,
|
||||
WalletType.bitcoinCash,
|
||||
WalletType.litecoin,
|
||||
WalletType.haven,
|
||||
].contains(wallet.type);
|
||||
|
||||
@action
|
||||
Future<void> sign(String message, {String? address}) async {
|
||||
state = IsExecutingState();
|
||||
try {
|
||||
final signature = await wallet.signMessage(message, address: address);
|
||||
state = ExecutedSuccessfullyState(payload: signature);
|
||||
} catch (e) {
|
||||
state = FailureState(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> verify(String message, String signature, {String? address}) async {
|
||||
state = IsExecutingState();
|
||||
try {
|
||||
final sig = await wallet.verifyMessage(message, signature, address: address);
|
||||
if (sig) {
|
||||
state = ExecutedSuccessfullyState();
|
||||
} else {
|
||||
state = FailureState(S.current.signature_invalid_error);
|
||||
}
|
||||
} catch (e) {
|
||||
state = FailureState(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/haven/haven.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/nano/nano.dart';
|
||||
import 'package:cake_wallet/polygon/polygon.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
@@ -438,6 +439,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.nano) {
|
||||
addressList.add(WalletAddressListItem(
|
||||
isPrimary: true,
|
||||
name: null,
|
||||
address: wallet.walletAddresses.address,
|
||||
));
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.tron) {
|
||||
final primaryAddress = tron!.getAddress(wallet);
|
||||
|
||||
|
||||
@@ -117,9 +117,7 @@ abstract class WalletCreationVMBase with Store {
|
||||
DerivationInfo? getDefaultDerivation() {
|
||||
switch (this.type) {
|
||||
case WalletType.nano:
|
||||
return DerivationInfo(
|
||||
derivationType: DerivationType.nano,
|
||||
);
|
||||
return DerivationInfo(derivationType: DerivationType.nano);
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.getElectrumDerivations()[DerivationType.electrum]!.first;
|
||||
@@ -131,9 +129,7 @@ abstract class WalletCreationVMBase with Store {
|
||||
DerivationInfo? getCommonRestoreDerivation() {
|
||||
switch (this.type) {
|
||||
case WalletType.nano:
|
||||
return DerivationInfo(
|
||||
derivationType: DerivationType.nano,
|
||||
);
|
||||
return DerivationInfo(derivationType: DerivationType.nano);
|
||||
case WalletType.bitcoin:
|
||||
return DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
|
||||
Reference in New Issue
Block a user