From 1adf9ae1b523ebe0aad1c71f9c4775bca9eed641 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Mon, 8 Apr 2024 16:46:34 -0700 Subject: [PATCH] start working on ui for message signing --- lib/di.dart | 5 + lib/routes.dart | 1 + .../dashboard/pages/market_place_page.dart | 14 + lib/src/screens/dashboard/sign_page.dart | 263 ++++++++++++++++++ .../dashboard/dashboard_view_model.dart | 3 + lib/view_model/dashboard/sign_view_model.dart | 15 + 6 files changed, 301 insertions(+) create mode 100644 lib/src/screens/dashboard/sign_page.dart create mode 100644 lib/view_model/dashboard/sign_view_model.dart diff --git a/lib/di.dart b/lib/di.dart index 5262a01e6..6b8d0dad9 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -13,7 +13,9 @@ import 'package:cake_wallet/core/yat_service.dart'; 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/transaction_details/rbf_details_page.dart'; +import 'package:cake_wallet/view_model/dashboard/sign_message_view_model.dart'; import 'package:cw_core/receive_page_option.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/nano/nano.dart'; @@ -1204,6 +1206,9 @@ Future setup({ getIt.registerFactory( () => WalletConnectConnectionsView(web3walletService: getIt.get())); + getIt.registerFactoryParam((signViewModel, _) => + SignPage(signViewModel)); + getIt.registerFactory(() => NFTViewModel(appStore, getIt.get())); getIt.registerFactory(() => TorPage(getIt.get())); diff --git a/lib/routes.dart b/lib/routes.dart index 9c4e21651..759efc771 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -107,4 +107,5 @@ class Routes { static const nftDetailsPage = '/nft_details_page'; static const importNFTPage = '/import_nft_page'; static const torPage = '/tor_page'; + static const signPage = '/sign_page'; } diff --git a/lib/src/screens/dashboard/pages/market_place_page.dart b/lib/src/screens/dashboard/pages/market_place_page.dart index 1bdcb61b4..ddddd8015 100644 --- a/lib/src/screens/dashboard/pages/market_place_page.dart +++ b/lib/src/screens/dashboard/pages/market_place_page.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; @@ -66,6 +67,19 @@ class MarketPlacePage extends StatelessWidget { title: S.of(context).cake_pay_web_cards_title, subTitle: S.of(context).cake_pay_web_cards_subtitle, ), + + Observer( + builder: (context) { + if (!dashboardViewModel.hasSignMessages) { + return const SizedBox(); + } + return DashBoardRoundedCardWidget( + onTap: () => Navigator.of(context).pushNamed(Routes.signPage), + title: "T: Sign or verify message", + subTitle: "T: Sign or verify a message using your private key", + ); + }, + ), ], ), ), diff --git a/lib/src/screens/dashboard/sign_page.dart b/lib/src/screens/dashboard/sign_page.dart new file mode 100644 index 000000000..025a5d778 --- /dev/null +++ b/lib/src/screens/dashboard/sign_page.dart @@ -0,0 +1,263 @@ +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/nano/nano.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/restore/wallet_restore_from_keys_form.dart'; +import 'package:cake_wallet/src/screens/restore/wallet_restore_from_seed_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/store/app_store.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:cake_wallet/view_model/restore/restore_mode.dart'; +import 'package:cake_wallet/view_model/seed_type_view_model.dart'; +import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; +import 'package:cw_core/nano_account_info_response.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.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:polyseed/polyseed.dart'; +import 'package:smooth_page_indicator/smooth_page_indicator.dart'; + +class SignPage extends BasePage { + SignPage(this.signViewModel) + : walletRestoreFromSeedFormKey = GlobalKey(), + walletRestoreFromKeysFormKey = GlobalKey(), + _pages = [], + _blockHeightFocusNode = FocusNode(), + _controller = PageController(initialPage: 0) { + // walletRestoreViewModel.availableModes.forEach((mode) { + // switch (mode) { + // case WalletRestoreMode.seed: + // _pages.add(WalletRestoreFromSeedForm( + // seedTypeViewModel: seedTypeViewModel, + // displayBlockHeightSelector: + // walletRestoreViewModel.hasBlockchainHeightLanguageSelector, + // displayLanguageSelector: walletRestoreViewModel.hasSeedLanguageSelector, + // type: walletRestoreViewModel.type, + // key: walletRestoreFromSeedFormKey, + // blockHeightFocusNode: _blockHeightFocusNode, + // onHeightOrDateEntered: (value) { + // }, + // onSeedChange: (String seed) { + // final isPolyseed = + // walletRestoreViewModel.type == WalletType.monero && Polyseed.isValidSeed(seed); + // _validateOnChange(isPolyseed: isPolyseed); + // }, + // onLanguageChange: (String language) { + // final isPolyseed = language.startsWith("POLYSEED_"); + // _validateOnChange(isPolyseed: isPolyseed); + // })); + // break; + // case WalletRestoreMode.keys: + // _pages.add(WalletRestoreFromKeysFrom( + // key: walletRestoreFromKeysFormKey, + // walletRestoreViewModel: walletRestoreViewModel, + // onPrivateKeyChange: (String seed) { + // if (walletRestoreViewModel.type == WalletType.nano || + // walletRestoreViewModel.type == WalletType.banano) { + // } + // }, + // displayPrivateKeyField: walletRestoreViewModel.hasRestoreFromPrivateKey, + // onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value)); + // break; + // default: + // break; + // } + // }); + } + + @override + Widget middle(BuildContext context) => Observer( + builder: (_) => Text( + "Sign / Verify", + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + fontFamily: 'Lato', + color: titleColor(context), + ), + )); + + final SignViewModel signViewModel; + final PageController _controller; + final List _pages; + final GlobalKey walletRestoreFromSeedFormKey; + final GlobalKey walletRestoreFromKeysFormKey; + final FocusNode _blockHeightFocusNode; + DerivationType derivationType = DerivationType.unknown; + String? derivationPath = null; + + @override + Widget body(BuildContext context) { + // reaction((_) => walletRestoreViewModel.state, (ExecutionState state) { + // if (state is FailureState) { + // WidgetsBinding.instance.addPostFrameCallback((_) { + // showPopUp( + // context: context, + // builder: (_) { + // return AlertWithOneAction( + // alertTitle: S.current.new_wallet, + // alertContent: state.error, + // 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 = ''; + // }); + + return KeyboardActions( + config: KeyboardActionsConfig( + keyboardActionsPlatform: KeyboardActionsPlatform.IOS, + keyboardBarColor: Theme.of(context).extension()!.keyboardBarColor, + nextFocus: false, + actions: [ + KeyboardActionsItem( + focusNode: _blockHeightFocusNode, + 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) { + // walletRestoreViewModel.mode = + // page == 0 ? WalletRestoreMode.seed : WalletRestoreMode.keys; + }, + 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: S.of(context).restore_recover, + color: Theme.of(context) + .extension()! + .createNewWalletButtonBackgroundColor, + textColor: Theme.of(context) + .extension()! + .restoreWalletButtonTextColor, + // isLoading: walletRestoreViewModel.state is IsExecutingState, + // isDisabled: !walletRestoreViewModel.isButtonEnabled, + ); + }, + ), + const SizedBox(height: 25), + GestureDetector( + onTap: () { + // Navigator.of(context) + // .pushNamed(Routes.advancedPrivacySettings, arguments: { + // 'type': walletRestoreViewModel.type, + // 'useTestnet': walletRestoreViewModel.useTestnet, + // 'toggleTestnet': walletRestoreViewModel.toggleUseTestnet + // }); + }, + child: Text(S.of(context).advanced_settings), + ), + ], + ), + ) + ], + ), + ), + ), + ), + ); + } + + void _validateOnChange({bool isPolyseed = false}) {} + + Future _confirmForm(BuildContext context) async { + // // Dismissing all visible keyboard to provide context for navigation + // FocusManager.instance.primaryFocus?.unfocus(); + + // late BuildContext? formContext; + // late GlobalKey? formKey; + // late String name; + // if (walletRestoreViewModel.mode == WalletRestoreMode.seed) { + // formContext = walletRestoreFromSeedFormKey.currentContext; + // formKey = walletRestoreFromSeedFormKey.currentState!.formKey; + // name = walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.value.text; + // } else if (walletRestoreViewModel.mode == WalletRestoreMode.keys) { + // formContext = walletRestoreFromKeysFormKey.currentContext; + // formKey = walletRestoreFromKeysFormKey.currentState!.formKey; + // name = walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.value.text; + // } + + // if (!formKey!.currentState!.validate()) { + // return; + // } + } + + Future showNameExistsAlert(BuildContext context) { + return showPopUp( + context: context, + builder: (_) { + return AlertWithOneAction( + alertTitle: '', + alertContent: S.of(context).wallet_name_exists, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } +} diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 66d179523..cb895a534 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -370,6 +370,9 @@ abstract class DashboardViewModelBase with Store { @computed bool get hasPowNodes => wallet.type == WalletType.nano || wallet.type == WalletType.banano; + @computed + bool get hasSignMessages => [WalletType.monero].contains(wallet.type); + Future reconnect() async { final node = appStore.settingsStore.getCurrentNode(wallet.type); await wallet.connectToNode(node: node); diff --git a/lib/view_model/dashboard/sign_view_model.dart b/lib/view_model/dashboard/sign_view_model.dart new file mode 100644 index 000000000..33f1f0377 --- /dev/null +++ b/lib/view_model/dashboard/sign_view_model.dart @@ -0,0 +1,15 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cw_core/receive_page_option.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) {} + + final WalletBase _wallet; +}