diff --git a/lib/di.dart b/lib/di.dart index 815d565bc..40d75008e 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -37,7 +37,10 @@ import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dar import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart'; +import 'package:cake_wallet/src/screens/receive/lightning_invoice_page.dart'; +import 'package:cake_wallet/src/screens/receive/lightning_receive_page.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart'; +import 'package:cake_wallet/src/screens/send/lightning_send_page.dart'; import 'package:cake_wallet/src/screens/settings/display_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart'; import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart'; @@ -89,6 +92,8 @@ import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart'; import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; +import 'package:cake_wallet/view_model/lightning_invoice_page_view_model.dart'; +import 'package:cake_wallet/view_model/lightning_view_model.dart'; import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart'; import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart'; import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart'; @@ -1195,5 +1200,42 @@ Future setup({ getIt.registerFactory(() => NFTViewModel(appStore, getIt.get())); getIt.registerFactory(() => TorPage(getIt.get())); + getIt.registerFactory(() => LightningReceiveOnchainPage( + addressListViewModel: getIt.get(), + receiveOptionViewModel: ReceiveOptionViewModel(getIt.get().wallet!, ReceivePageOption.lightningOnchain), + )); + + getIt.registerFactoryParam, void>((args, _) { + final address = args.first as String; + final pageOption = args.last as ReceivePageOption; + return LightningInvoicePageViewModel( + address, + getIt.get(), + getIt.get().wallet!, + getIt.get(), + pageOption, + ); + }); + + // getIt.registerFactory(() => LightningInvoicePage( + // lightningInvoicePageViewModel: getIt.get(), + // lightningViewModel: LightningViewModel(), + // receiveOptionViewModel: ReceiveOptionViewModel(getIt.get().wallet!, ReceivePageOption.lightningInvoice), + // )); + + + + + getIt.registerFactoryParam, void>((List args, _) { + final pageOption = args.last as ReceivePageOption; + return LightningInvoicePage( + lightningInvoicePageViewModel: getIt.get(param1: args), + lightningViewModel: LightningViewModel(), + receiveOptionViewModel: getIt.get(param1: pageOption)); + }); + + + // getIt.registerFactory(() => LightningSendPage(authService: , sendViewModel: , initialPaymentRequest: ,)); + _isSetupFinished = true; } diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart index 88e070625..366ae4ccb 100644 --- a/lib/entities/main_actions.dart +++ b/lib/entities/main_actions.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/entities/receive_page_option.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; @@ -56,7 +57,11 @@ class MainActions { image: 'assets/images/received.png', onTap: (BuildContext context, DashboardViewModel viewModel) async { if (viewModel.wallet.type == WalletType.lightning) { - Navigator.pushNamed(context, Routes.lightningReceive); + Navigator.pushNamed( + context, + Routes.lightningInvoice, + arguments: [viewModel.address, ReceivePageOption.lightningInvoice], + ); return; } Navigator.pushNamed(context, Routes.addressPage); diff --git a/lib/entities/receive_page_option.dart b/lib/entities/receive_page_option.dart index 3ee9abe96..099915bd4 100644 --- a/lib/entities/receive_page_option.dart +++ b/lib/entities/receive_page_option.dart @@ -1,8 +1,9 @@ - enum ReceivePageOption { mainnet, anonPayInvoice, - anonPayDonationLink; + anonPayDonationLink, + lightningInvoice, + lightningOnchain; @override String toString() { @@ -17,6 +18,12 @@ enum ReceivePageOption { case ReceivePageOption.anonPayDonationLink: label = 'Trocador AnonPay Donation Link'; break; + case ReceivePageOption.lightningInvoice: + label = 'Sats via Invoice'; + break; + case ReceivePageOption.lightningOnchain: + label = 'Sats via BTC address'; + break; } return label; } diff --git a/lib/lightning/cw_lightning.dart b/lib/lightning/cw_lightning.dart index 7e5bf4f17..32b11463e 100644 --- a/lib/lightning/cw_lightning.dart +++ b/lib/lightning/cw_lightning.dart @@ -100,7 +100,7 @@ class CWLightning extends Lightning { } @override - // @computed + @computed List getSubAddresses(Object wallet) { final electrumWallet = wallet as ElectrumWallet; return electrumWallet.walletAddresses.addresses diff --git a/lib/router.dart b/lib/router.dart index 5fdb48f99..93be92f88 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -18,12 +18,15 @@ import 'package:cake_wallet/src/screens/dashboard/pages/nft_details_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/lightning_invoice_page.dart'; +import 'package:cake_wallet/src/screens/receive/lightning_receive_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'; import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart'; import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart'; +import 'package:cake_wallet/src/screens/send/lightning_send_page.dart'; import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/display_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart'; @@ -150,7 +153,8 @@ Route createRoute(RouteSettings settings) { final walletNewVM = getIt.get(param1: type); final seedTypeViewModel = getIt.get(); - return CupertinoPageRoute(builder: (_) => NewWalletPage(walletNewVM, seedTypeViewModel)); + return CupertinoPageRoute( + builder: (_) => NewWalletPage(walletNewVM, seedTypeViewModel)); case Routes.setupPin: Function(PinCodeState, String)? callback; @@ -393,8 +397,7 @@ Route createRoute(RouteSettings settings) { case Routes.buySellPage: final args = settings.arguments as bool; - return MaterialPageRoute( - builder: (_) => getIt.get(param1: args)); + return MaterialPageRoute(builder: (_) => getIt.get(param1: args)); case Routes.buyWebView: final args = settings.arguments as List; @@ -417,8 +420,7 @@ Route createRoute(RouteSettings settings) { case Routes.preSeedPage: return MaterialPageRoute( - builder: (_) => getIt.get( - param1: settings.arguments as int)); + builder: (_) => getIt.get(param1: settings.arguments as int)); case Routes.backup: return CupertinoPageRoute( @@ -636,15 +638,20 @@ Route createRoute(RouteSettings settings) { case Routes.lightningSend: return CupertinoPageRoute( - fullscreenDialog: true, builder: (_) => getIt.get()); + fullscreenDialog: true, builder: (_) => getIt.get()); - case Routes.lightningReceive: + case Routes.lightningReceiveOnchain: return CupertinoPageRoute( - fullscreenDialog: true, builder: (_) => getIt.get()); + fullscreenDialog: true, builder: (_) => getIt.get()); - case Routes.lightningSettings: + case Routes.lightningInvoice: + final args = settings.arguments as List; return CupertinoPageRoute( - fullscreenDialog: true, builder: (_) => getIt.get()); + fullscreenDialog: true, builder: (_) => getIt.get(param1: args)); + + // case Routes.lightningSettings: + // return CupertinoPageRoute( + // fullscreenDialog: true, builder: (_) => getIt.get()); default: return MaterialPageRoute( diff --git a/lib/routes.dart b/lib/routes.dart index 01b8f2f82..d50b46b10 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -107,6 +107,7 @@ class Routes { static const importNFTPage = '/import_nft_page'; static const torPage = '/tor_page'; static const lightningSend = '/lightning_send'; - static const lightningReceive = '/lightning_receive'; + static const lightningInvoice = '/lightning_invoice'; + static const lightningReceiveOnchain = '/lightning_receive_onchain'; static const lightningSettings = '/lightning_settings'; } diff --git a/lib/src/screens/dashboard/pages/address_page.dart b/lib/src/screens/dashboard/pages/address_page.dart index a80de6f94..4c4d50ce5 100644 --- a/lib/src/screens/dashboard/pages/address_page.dart +++ b/lib/src/screens/dashboard/pages/address_page.dart @@ -159,65 +159,6 @@ class AddressPage extends BasePage { amountController: _amountController, isLight: dashboardViewModel.settingsStore.currentTheme.type == ThemeType.light))), - SizedBox(height: 16), - IconButton( - padding: EdgeInsets.zero, - constraints: BoxConstraints(), - highlightColor: Colors.transparent, - splashColor: Colors.transparent, - iconSize: 40, - onPressed: () async { - print("pressed"); - // ReceivePaymentRequest req = const ReceivePaymentRequest( - // amountMsat: 3000000, - // description: "Invoice for 3000 sats", - // ); - // ReceivePaymentResponse receivePaymentResponse = - // await BreezSDK().receivePayment(req: req); - - // print(receivePaymentResponse.lnInvoice); - - final sdk = await BreezSDK(); - - sdk.nodeStateStream.listen((event) { - // print("Node state: $event"); - if (event == null) return; - int balanceSat = event.maxPayableMsat ~/ 1000; - print("sats: $balanceSat"); - }); - - // ServiceHealthCheckResponse healthCheck = await sdk.serviceHealthCheck(); - // print("Current service status is: ${healthCheck.status}"); - - // ReceivePaymentRequest req = ReceivePaymentRequest( - // amountMsat: 123 * 1000, - // description: "Invoice for 123 sats", - // ); - // final s = await sdk.receivePayment(req: req); - // print(s.lnInvoice.bolt11); - - // ReceiveOnchainRequest req = const ReceiveOnchainRequest(); - // SwapInfo swapInfo = await sdk.receiveOnchain(req: req); - // // Send your funds to the below bitcoin address - // String address = swapInfo.bitcoinAddress; - // print(address); - // print("Minimum amount allowed to deposit in sats: ${swapInfo.minAllowedDeposit}"); - // print("Maximum amount allowed to deposit in sats: ${swapInfo.maxAllowedDeposit}"); - - ListPaymentsRequest lReq = ListPaymentsRequest(); - - var list = await sdk.listPayments(req: lReq); - print(list[0].amountMsat); - - var data = await sdk.fetchNodeData(); - print(data); - }, - icon: Icon( - Icons.lightbulb_outline_rounded, - size: 48, - color: pageIconColor(context), - ), - ), SizedBox( height: 40, ), diff --git a/lib/src/screens/receive/lightning_invoice_page.dart b/lib/src/screens/receive/lightning_invoice_page.dart new file mode 100644 index 000000000..a352348e5 --- /dev/null +++ b/lib/src/screens/receive/lightning_invoice_page.dart @@ -0,0 +1,227 @@ +import 'package:cake_wallet/src/screens/receive/widgets/lightning_input_form.dart'; +import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart'; +import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; +import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart'; +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/entities/preferences_key.dart'; +import 'package:cake_wallet/entities/receive_page_option.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/anonpay_input_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/utils/responsive_layout_util.dart'; +import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart'; +import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart'; +import 'package:cake_wallet/view_model/lightning_invoice_page_view_model.dart'; +import 'package:cake_wallet/view_model/lightning_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:keyboard_actions/keyboard_actions.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/trail_button.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; +import 'package:mobx/mobx.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class LightningInvoicePage extends BasePage { + LightningInvoicePage({ + required this.lightningViewModel, + required this.lightningInvoicePageViewModel, + required this.receiveOptionViewModel, + }) : _amountFocusNode = FocusNode() {} + + final _nameController = TextEditingController(); + final _emailController = TextEditingController(); + final _descriptionController = TextEditingController(); + final _amountController = TextEditingController(); + final FocusNode _amountFocusNode; + + final LightningViewModel lightningViewModel; + final LightningInvoicePageViewModel lightningInvoicePageViewModel; + final ReceiveOptionViewModel receiveOptionViewModel; + final _formKey = GlobalKey(); + + bool effectsInstalled = false; + + @override + bool get gradientAll => true; + + @override + bool get resizeToAvoidBottomInset => false; + + @override + bool get extendBodyBehindAppBar => true; + + @override + AppBarStyle get appBarStyle => AppBarStyle.transparent; + + @override + void onClose(BuildContext context) => Navigator.popUntil(context, (route) => route.isFirst); + + @override + Widget middle(BuildContext context) => PresentReceiveOptionPicker( + receiveOptionViewModel: receiveOptionViewModel, color: titleColor(context)); + + @override + Widget trailing(BuildContext context) => TrailButton( + caption: S.of(context).clear, + onPressed: () { + _formKey.currentState?.reset(); + // lightningViewModel.reset(); + }); + + Future _onNavigateBack(BuildContext context) async { + onClose(context); + return false; + } + + @override + Widget body(BuildContext context) { + WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context)); + + return WillPopScope( + onWillPop: () => _onNavigateBack(context), + child: KeyboardActions( + disableScroll: true, + config: KeyboardActionsConfig( + keyboardActionsPlatform: KeyboardActionsPlatform.IOS, + keyboardBarColor: Theme.of(context).extension()!.keyboardBarColor, + nextFocus: false, + actions: [ + KeyboardActionsItem( + focusNode: _amountFocusNode, + toolbarButtons: [(_) => KeyboardDoneButton()], + ), + ]), + child: Container( + color: Theme.of(context).colorScheme.background, + child: ScrollableWithBottomSection( + contentPadding: EdgeInsets.only(bottom: 24), + content: Container( + decoration: responsiveLayoutUtil.shouldRenderMobileUI + ? BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)), + gradient: LinearGradient( + colors: [ + Theme.of(context) + .extension()! + .firstGradientTopPanelColor, + Theme.of(context) + .extension()! + .secondGradientTopPanelColor, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ) + : null, + child: Observer(builder: (_) { + return Padding( + padding: EdgeInsets.fromLTRB(24, 120, 24, 0), + child: LightningInvoiceForm( + descriptionController: _descriptionController, + amountController: _amountController, + depositAmountFocus: _amountFocusNode, + formKey: _formKey, + lightningInvoicePageViewModel: lightningInvoicePageViewModel, + ), + ); + }), + ), + bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), + bottomSection: Observer(builder: (_) { + return Column( + children: [ + Padding( + padding: EdgeInsets.only(bottom: 15), + child: Center( + child: Text( + S.of(context).anonpay_description("an invoice", "pay"), + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context) + .extension()! + .receiveAmountColor, + fontWeight: FontWeight.w500, + fontSize: 12), + ), + ), + ), + // LoadingPrimaryButton( + // text: isInvoice + // ? S.of(context).create_invoice + // : S.of(context).create_donation_link, + // onPressed: () { + // FocusScope.of(context).unfocus(); + // anonInvoicePageViewModel.setRequestParams( + // inputAmount: _amountController.text, + // inputName: _nameController.text, + // inputEmail: _emailController.text, + // inputDescription: _descriptionController.text, + // ); + // if (anonInvoicePageViewModel.receipientEmail.isNotEmpty && + // _formKey.currentState != null && + // !_formKey.currentState!.validate()) { + // return; + // } + // if (isInvoice) { + // anonInvoicePageViewModel.createInvoice(); + // } else { + // anonInvoicePageViewModel.generateDonationLink(); + // } + // }, + // color: Theme.of(context).primaryColor, + // textColor: Colors.white, + // isLoading: anonInvoicePageViewModel.state is IsExecutingState, + // ), + ], + ); + }), + ), + ), + ), + ); + } + + void _setReactions(BuildContext context) { + if (effectsInstalled) { + return; + } + + reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) { + switch (option) { + case ReceivePageOption.lightningInvoice: + break; + case ReceivePageOption.lightningOnchain: + Navigator.popAndPushNamed(context, Routes.lightningReceiveOnchain); + break; + default: + } + }); + + // reaction((_) => anonInvoicePageViewModel.state, (ExecutionState state) { + // if (state is ExecutedSuccessfullyState) { + // Navigator.pushNamed(context, Routes.anonPayReceivePage, arguments: state.payload); + // } + // if (state is FailureState) { + // showPopUp( + // context: context, + // builder: (BuildContext context) { + // return AlertWithOneAction( + // alertTitle: S.of(context).error, + // alertContent: state.error.toString(), + // buttonText: S.of(context).ok, + // buttonAction: () => Navigator.of(context).pop()); + // }); + // } + // }); + + effectsInstalled = true; + } +} diff --git a/lib/src/screens/receive/lightning_receive_page.dart b/lib/src/screens/receive/lightning_receive_page.dart index a80f6f88c..e77e8411e 100644 --- a/lib/src/screens/receive/lightning_receive_page.dart +++ b/lib/src/screens/receive/lightning_receive_page.dart @@ -1,32 +1,13 @@ -import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.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'; -import 'package:cake_wallet/themes/extensions/receive_page_theme.dart'; +import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart'; import 'package:cake_wallet/src/widgets/gradient_background.dart'; -import 'package:cake_wallet/src/widgets/section_divider.dart'; -import 'package:cake_wallet/themes/theme_base.dart'; -import 'package:cake_wallet/utils/share_util.dart'; -import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cw_core/wallet_type.dart'; +import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart'; -import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart'; -import 'package:cake_wallet/src/screens/receive/widgets/address_cell.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:cake_wallet/src/screens/receive/widgets/qr_widget.dart'; -import 'package:keyboard_actions/keyboard_actions.dart'; -class LightningReceivePage extends BasePage { - LightningReceivePage({required this.addressListViewModel}) +class LightningReceiveOnchainPage extends BasePage { + LightningReceiveOnchainPage({required this.addressListViewModel, required this.receiveOptionViewModel}) : _cryptoAmountFocus = FocusNode(), _amountController = TextEditingController(), _formKey = GlobalKey() { @@ -38,6 +19,7 @@ class LightningReceivePage extends BasePage { } final WalletAddressListViewModel addressListViewModel; + final ReceiveOptionViewModel receiveOptionViewModel; final TextEditingController _amountController; final GlobalKey _formKey; static const _heroTag = 'receive_page'; @@ -53,192 +35,28 @@ class LightningReceivePage extends BasePage { final FocusNode _cryptoAmountFocus; + // @override + // Widget middle(BuildContext context) { + // return Text( + // title, + // style: TextStyle( + // fontSize: 18.0, + // fontWeight: FontWeight.bold, + // fontFamily: 'Lato', + // color: pageIconColor(context)), + // ); + // } + @override - Widget middle(BuildContext context) { - return Text( - title, - style: TextStyle( - fontSize: 18.0, - fontWeight: FontWeight.bold, - fontFamily: 'Lato', - color: pageIconColor(context)), - ); - } + Widget middle(BuildContext context) => PresentReceiveOptionPicker( + color: titleColor(context), receiveOptionViewModel: receiveOptionViewModel); @override Widget Function(BuildContext, Widget) get rootWrapper => - (BuildContext context, Widget scaffold) => - GradientBackground(scaffold: scaffold); - - @override - Widget trailing(BuildContext context) { - return Material( - color: Colors.transparent, - child: Semantics( - label: S.of(context).share, - child: IconButton( - padding: EdgeInsets.zero, - constraints: BoxConstraints(), - highlightColor: Colors.transparent, - splashColor: Colors.transparent, - iconSize: 25, - onPressed: () { - ShareUtil.share( - text: addressListViewModel.uri.toString(), - context: context, - ); - }, - icon: Icon( - Icons.share, - size: 20, - color: pageIconColor(context), - ), - ), - )); - } + (BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold); @override Widget body(BuildContext context) { - final isElectrumWallet = addressListViewModel.isElectrumWallet; - return (addressListViewModel.type == WalletType.monero || - addressListViewModel.type == WalletType.haven || - addressListViewModel.type == WalletType.nano || - isElectrumWallet) - ? KeyboardActions( - config: KeyboardActionsConfig( - keyboardActionsPlatform: KeyboardActionsPlatform.IOS, - keyboardBarColor: Theme.of(context).extension()!.keyboardBarColor, - nextFocus: false, - actions: [ - KeyboardActionsItem( - focusNode: _cryptoAmountFocus, - toolbarButtons: [(_) => KeyboardDoneButton()], - ) - ]), - child: SingleChildScrollView( - child: Column( - children: [ - Padding( - padding: EdgeInsets.fromLTRB(24, 50, 24, 24), - child: QRWidget( - addressListViewModel: addressListViewModel, - formKey: _formKey, - heroTag: _heroTag, - amountTextFieldFocusNode: _cryptoAmountFocus, - 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.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, - ); - })), - ], - ), - )) - : Padding( - padding: EdgeInsets.fromLTRB(24, 24, 24, 32), - child: Column( - children: [ - Expanded( - flex: 7, - child: QRWidget( - formKey: _formKey, - heroTag: _heroTag, - addressListViewModel: addressListViewModel, - amountTextFieldFocusNode: _cryptoAmountFocus, - amountController: _amountController, - isLight: currentTheme.type == ThemeType.light), - ), - Expanded( - flex: 2, - child: SizedBox(), - ), - Text(S.of(context).electrum_address_disclaimer, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 15, - color: Theme.of(context).extension()!.labelTextColor)), - ], - ), - ); + return SizedBox(); } } diff --git a/lib/src/screens/receive/widgets/anonpay_currency_input_field.dart b/lib/src/screens/receive/widgets/anonpay_currency_input_field.dart index be39ac3bb..f1e5a216b 100644 --- a/lib/src/screens/receive/widgets/anonpay_currency_input_field.dart +++ b/lib/src/screens/receive/widgets/anonpay_currency_input_field.dart @@ -9,13 +9,13 @@ import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; class AnonpayCurrencyInputField extends StatelessWidget { const AnonpayCurrencyInputField( {super.key, - required this.onTapPicker, + this.onTapPicker, required this.selectedCurrency, required this.focusNode, required this.controller, required this.minAmount, required this.maxAmount}); - final Function() onTapPicker; + final Function()? onTapPicker; final Currency selectedCurrency; final FocusNode focusNode; final TextEditingController controller; @@ -34,40 +34,50 @@ class AnonpayCurrencyInputField extends StatelessWidget { decoration: BoxDecoration( border: Border( bottom: BorderSide( - color: - Theme.of(context).extension()!.textFieldBorderBottomPanelColor, + color: Theme.of(context) + .extension()! + .textFieldBorderBottomPanelColor, width: 1)), ), child: Padding( padding: EdgeInsets.only(top: 20), child: Row( children: [ - Container( - padding: EdgeInsets.only(right: 8), - height: 32, - child: InkWell( - onTap: onTapPicker, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.only(right: 5), - child: arrowBottomPurple, - ), - Text(selectedCurrency.name.toUpperCase(), - style: TextStyle( - fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white)) - ]), - ), - ), + if (onTapPicker != null) + Container( + padding: EdgeInsets.only(right: 8), + height: 32, + child: InkWell( + onTap: onTapPicker, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only(right: 5), + child: arrowBottomPurple, + ), + Text(selectedCurrency.name.toUpperCase(), + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white)) + ]), + ), + ) + else + Text(selectedCurrency.name.toUpperCase(), + style: TextStyle( + fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white)), selectedCurrency.tag != null ? Padding( padding: const EdgeInsets.only(right: 3.0), child: Container( height: 32, decoration: BoxDecoration( - color: Theme.of(context).extension()!.textFieldButtonColor, + color: Theme.of(context) + .extension()! + .textFieldButtonColor, borderRadius: BorderRadius.all(Radius.circular(6))), child: Center( child: Padding( @@ -77,7 +87,9 @@ class AnonpayCurrencyInputField extends StatelessWidget { style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, - color: Theme.of(context).extension()!.textFieldButtonIconColor, + color: Theme.of(context) + .extension()! + .textFieldButtonIconColor, ), ), ), @@ -115,7 +127,8 @@ class AnonpayCurrencyInputField extends StatelessWidget { placeholderTextStyle: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, - color: Theme.of(context).extension()!.hintTextColor, + color: + Theme.of(context).extension()!.hintTextColor, ), validator: null, ), diff --git a/lib/src/screens/receive/widgets/lightning_input_form.dart b/lib/src/screens/receive/widgets/lightning_input_form.dart new file mode 100644 index 000000000..e9991f7d8 --- /dev/null +++ b/lib/src/screens/receive/widgets/lightning_input_form.dart @@ -0,0 +1,76 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/anonpay_currency_input_field.dart'; +import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; +import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart'; +import 'package:cake_wallet/typography.dart'; +import 'package:cake_wallet/view_model/lightning_invoice_page_view_model.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class LightningInvoiceForm extends StatelessWidget { + LightningInvoiceForm({ + super.key, + required this.formKey, + required this.lightningInvoicePageViewModel, + required this.amountController, + required this.descriptionController, + required this.depositAmountFocus, + }) : _descriptionFocusNode = FocusNode() { + amountController.text = lightningInvoicePageViewModel.amount; + descriptionController.text = lightningInvoicePageViewModel.description; + } + + final TextEditingController amountController; + final TextEditingController descriptionController; + final LightningInvoicePageViewModel lightningInvoicePageViewModel; + final FocusNode depositAmountFocus; + final FocusNode _descriptionFocusNode; + final GlobalKey formKey; + + @override + Widget build(BuildContext context) { + return Form( + key: formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).invoice_details, + style: textMediumSemiBold(), + ), + Observer(builder: (_) { + return AnonpayCurrencyInputField( + controller: amountController, + focusNode: depositAmountFocus, + maxAmount: lightningInvoicePageViewModel.maximum?.toString() ?? '...', + minAmount: lightningInvoicePageViewModel.minimum?.toString() ?? '...', + selectedCurrency: CryptoCurrency.btc, + ); + }), + SizedBox( + height: 24, + ), + BaseTextFormField( + controller: descriptionController, + focusNode: _descriptionFocusNode, + textInputAction: TextInputAction.next, + borderColor: + Theme.of(context).extension()!.textFieldBorderTopPanelColor, + suffixIcon: SizedBox(width: 36), + hintText: S.of(context).optional_description, + placeholderTextStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.hintTextColor, + ), + textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white), + validator: null, + ), + SizedBox( + height: 52, + ), + ], + )); + } +} diff --git a/lib/src/screens/send/lightning_send_page.dart b/lib/src/screens/send/lightning_send_page.dart new file mode 100644 index 000000000..aca4d330b --- /dev/null +++ b/lib/src/screens/send/lightning_send_page.dart @@ -0,0 +1,525 @@ +import 'package:cake_wallet/core/auth_service.dart'; +import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/entities/template.dart'; +import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart'; +import 'package:cake_wallet/src/screens/send/widgets/send_card.dart'; +import 'package:cake_wallet/src/widgets/add_template_button.dart'; +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; +import 'package:cake_wallet/src/widgets/picker.dart'; +import 'package:cake_wallet/src/widgets/template_tile.dart'; +import 'package:cake_wallet/themes/extensions/seed_widget_theme.dart'; +import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/payment_request.dart'; +import 'package:cake_wallet/utils/request_review_handler.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/view_model/send/send_view_model.dart'; +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; +import 'package:cake_wallet/src/widgets/trail_button.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart'; +import 'package:smooth_page_indicator/smooth_page_indicator.dart'; +import 'package:cw_core/crypto_currency.dart'; + +class LightningSendPage extends BasePage { + LightningSendPage({ + required this.sendViewModel, + required this.authService, + this.initialPaymentRequest, + }) : _formKey = GlobalKey(); + + final SendViewModel sendViewModel; + final AuthService authService; + final GlobalKey _formKey; + final controller = PageController(initialPage: 0); + final PaymentRequest? initialPaymentRequest; + + bool _effectsInstalled = false; + + @override + String get title => S.current.send; + + @override + bool get gradientAll => true; + + @override + bool get resizeToAvoidBottomInset => false; + + @override + bool get extendBodyBehindAppBar => true; + + @override + Widget? leading(BuildContext context) { + final _backButton = Icon( + Icons.arrow_back_ios, + color: titleColor(context), + size: 16, + ); + final _closeButton = + currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage; + + bool isMobileView = responsiveLayoutUtil.shouldRenderMobileUI; + + return MergeSemantics( + child: SizedBox( + height: isMobileView ? 37 : 45, + width: isMobileView ? 37 : 45, + child: ButtonTheme( + minWidth: double.minPositive, + child: Semantics( + label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back, + child: TextButton( + style: ButtonStyle( + overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent), + ), + onPressed: () => onClose(context), + child: !isMobileView ? _closeButton : _backButton, + ), + ), + ), + ), + ); + } + + @override + AppBarStyle get appBarStyle => AppBarStyle.transparent; + + double _sendCardHeight(BuildContext context) { + final double initialHeight = sendViewModel.hasCoinControl ? 500 : 465; + + if (!responsiveLayoutUtil.shouldRenderMobileUI) { + return initialHeight - 66; + } + return initialHeight; + } + + @override + void onClose(BuildContext context) { + sendViewModel.onClose(); + Navigator.of(context).pop(); + } + + @override + Widget? middle(BuildContext context) { + final supMiddle = super.middle(context); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Observer( + builder: (_) => SyncIndicatorIcon(isSynced: sendViewModel.isReadyForSend), + ), + ), + if (supMiddle != null) supMiddle + ], + ); + } + + @override + Widget trailing(context) => Observer(builder: (_) { + return sendViewModel.isBatchSending + ? TrailButton( + caption: S.of(context).remove, + onPressed: () { + var pageToJump = (controller.page?.round() ?? 0) - 1; + pageToJump = pageToJump > 0 ? pageToJump : 0; + final output = _defineCurrentOutput(); + sendViewModel.removeOutput(output); + controller.jumpToPage(pageToJump); + }) + : TrailButton( + caption: S.of(context).clear, + onPressed: () { + final output = _defineCurrentOutput(); + _formKey.currentState?.reset(); + output.reset(); + }); + }); + + @override + Widget body(BuildContext context) { + _setEffects(context); + + return GestureDetector( + onLongPress: () => + sendViewModel.balanceViewModel.isReversing = !sendViewModel.balanceViewModel.isReversing, + onLongPressUp: () => + sendViewModel.balanceViewModel.isReversing = !sendViewModel.balanceViewModel.isReversing, + child: Form( + key: _formKey, + child: ScrollableWithBottomSection( + contentPadding: EdgeInsets.only(bottom: 24), + content: FocusTraversalGroup( + policy: OrderedTraversalPolicy(), + child: Column( + children: [ + Container( + height: _sendCardHeight(context), + child: Observer( + builder: (_) { + return PageView.builder( + scrollDirection: Axis.horizontal, + controller: controller, + itemCount: sendViewModel.outputs.length, + itemBuilder: (context, index) { + final output = sendViewModel.outputs[index]; + + return SendCard( + key: output.key, + output: output, + sendViewModel: sendViewModel, + initialPaymentRequest: initialPaymentRequest, + ); + }); + }, + )), + Padding( + padding: EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10), + child: Container( + height: 10, + child: Observer( + builder: (_) { + final count = sendViewModel.outputs.length; + + return count > 1 + ? SmoothPageIndicator( + controller: controller, + count: count, + effect: ScrollingDotsEffect( + spacing: 6.0, + radius: 6.0, + dotWidth: 6.0, + dotHeight: 6.0, + dotColor: Theme.of(context) + .extension()! + .indicatorDotColor, + activeDotColor: Theme.of(context) + .extension()! + .templateBackgroundColor), + ) + : Offstage(); + }, + ), + ), + ), + Container( + height: 40, + width: double.infinity, + padding: EdgeInsets.only(left: 24), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Observer( + builder: (_) { + final templates = sendViewModel.templates; + final itemCount = templates.length; + + return Row( + children: [ + AddTemplateButton( + onTap: () => Navigator.of(context).pushNamed(Routes.sendTemplate), + currentTemplatesLength: templates.length, + ), + ListView.builder( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: itemCount, + itemBuilder: (context, index) { + final template = templates[index]; + return TemplateTile( + key: UniqueKey(), + to: template.name, + hasMultipleRecipients: template.additionalRecipients != null && + template.additionalRecipients!.length > 1, + amount: template.isCurrencySelected + ? template.amount + : template.amountFiat, + from: template.isCurrencySelected + ? template.cryptoCurrency + : template.fiatCurrency, + onTap: () async { + if (template.additionalRecipients?.isNotEmpty ?? false) { + sendViewModel.clearOutputs(); + + for (int i = 0; + i < template.additionalRecipients!.length; + i++) { + Output output; + try { + output = sendViewModel.outputs[i]; + } catch (e) { + sendViewModel.addOutput(); + output = sendViewModel.outputs[i]; + } + + await _setInputsFromTemplate( + context, + output: output, + template: template.additionalRecipients![i], + ); + } + } else { + final output = _defineCurrentOutput(); + await _setInputsFromTemplate( + context, + output: output, + template: template, + ); + } + }, + onRemove: () { + showPopUp( + context: context, + builder: (dialogContext) { + return AlertWithTwoActions( + alertTitle: S.of(context).template, + alertContent: S.of(context).confirm_delete_template, + rightButtonText: S.of(context).delete, + leftButtonText: S.of(context).cancel, + actionRightButton: () { + Navigator.of(dialogContext).pop(); + sendViewModel.sendTemplateViewModel + .removeTemplate(template: template); + }, + actionLeftButton: () => + Navigator.of(dialogContext).pop()); + }, + ); + }, + ); + }, + ), + ], + ); + }, + ), + ), + ), + ], + ), + ), + bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), + bottomSection: Column( + children: [ + if (sendViewModel.hasCurrecyChanger) + Observer( + builder: (_) => Padding( + padding: EdgeInsets.only(bottom: 12), + child: PrimaryButton( + onPressed: () => presentCurrencyPicker(context), + text: 'Change your asset (${sendViewModel.selectedCryptoCurrency})', + color: Colors.transparent, + textColor: + Theme.of(context).extension()!.hintTextColor, + ))), + if (sendViewModel.sendTemplateViewModel.hasMultiRecipient) + Padding( + padding: EdgeInsets.only(bottom: 12), + child: PrimaryButton( + onPressed: () { + sendViewModel.addOutput(); + Future.delayed(const Duration(milliseconds: 250), () { + controller.jumpToPage(sendViewModel.outputs.length - 1); + }); + }, + text: S.of(context).add_receiver, + color: Colors.transparent, + textColor: Theme.of(context).extension()!.hintTextColor, + isDottedBorder: true, + borderColor: + Theme.of(context).extension()!.templateDottedBorderColor, + )), + Observer( + builder: (_) { + return LoadingPrimaryButton( + onPressed: () async { + if (_formKey.currentState != null && !_formKey.currentState!.validate()) { + if (sendViewModel.outputs.length > 1) { + showErrorValidationAlert(context); + } + + return; + } + + final notValidItems = sendViewModel.outputs + .where((item) => item.address.isEmpty || item.cryptoAmount.isEmpty) + .toList(); + + if (notValidItems.isNotEmpty) { + showErrorValidationAlert(context); + return; + } + + final check = sendViewModel.shouldDisplayTotp(); + authService.authenticateAction( + context, + conditionToDetermineIfToUse2FA: check, + onAuthSuccess: (value) async { + if (value) { + await sendViewModel.createTransaction(); + } + }, + ); + }, + text: S.of(context).send, + color: Theme.of(context).primaryColor, + textColor: Colors.white, + isLoading: sendViewModel.state is IsExecutingState || + sendViewModel.state is TransactionCommitting, + isDisabled: !sendViewModel.isReadyForSend, + ); + }, + ) + ], + )), + ), + ); + } + + void _setEffects(BuildContext context) { + if (_effectsInstalled) { + return; + } + + reaction((_) => sendViewModel.state, (ExecutionState state) { + if (state is FailureState) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: state.error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + }); + } + + if (state is ExecutedSuccessfullyState) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (context.mounted) { + showPopUp( + context: context, + builder: (BuildContext _dialogContext) { + return ConfirmSendingAlert( + alertTitle: S.of(_dialogContext).confirm_sending, + amount: S.of(_dialogContext).send_amount, + amountValue: sendViewModel.pendingTransaction!.amountFormatted, + fiatAmountValue: sendViewModel.pendingTransactionFiatAmountFormatted, + fee: S.of(_dialogContext).send_fee, + feeValue: sendViewModel.pendingTransaction!.feeFormatted, + feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted, + outputs: sendViewModel.outputs, + rightButtonText: S.of(_dialogContext).send, + leftButtonText: S.of(_dialogContext).cancel, + actionRightButton: () { + Navigator.of(_dialogContext).pop(); + sendViewModel.commitTransaction(); + showPopUp( + context: context, + builder: (BuildContext _dialogContext) { + return Observer(builder: (_) { + final state = sendViewModel.state; + + if (state is FailureState) { + Navigator.of(_dialogContext).pop(); + } + + if (state is TransactionCommitted) { + return AlertWithOneAction( + alertTitle: '', + alertContent: S.of(_dialogContext).send_success( + sendViewModel.selectedCryptoCurrency.toString()), + buttonText: S.of(_dialogContext).ok, + buttonAction: () { + Navigator.of(_dialogContext).pop(); + RequestReviewHandler.requestReview(); + }); + } + + return Offstage(); + }); + }); + }, + actionLeftButton: () => Navigator.of(_dialogContext).pop()); + }); + } + }); + } + + if (state is TransactionCommitted) { + WidgetsBinding.instance.addPostFrameCallback((_) { + sendViewModel.clearOutputs(); + }); + } + }); + + _effectsInstalled = true; + } + + Future _setInputsFromTemplate(BuildContext context, + {required Output output, required Template template}) async { + output.address = template.address; + + if (template.isCurrencySelected) { + sendViewModel.setSelectedCryptoCurrency(template.cryptoCurrency); + output.setCryptoAmount(template.amount); + } else { + final fiatFromTemplate = + FiatCurrency.all.singleWhere((element) => element.title == template.fiatCurrency); + + sendViewModel.setFiatCurrency(fiatFromTemplate); + output.setFiatAmount(template.amountFiat); + } + + output.resetParsedAddress(); + await output.fetchParsedAddress(context); + } + + Output _defineCurrentOutput() { + if (controller.page == null) { + throw Exception('Controller page is null'); + } + final itemCount = controller.page!.round(); + return sendViewModel.outputs[itemCount]; + } + + void showErrorValidationAlert(BuildContext context) async { + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: 'Please, check receiver forms', + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } + + void presentCurrencyPicker(BuildContext context) async { + await showPopUp( + builder: (_) => Picker( + items: sendViewModel.currencies, + displayItem: (Object item) => item.toString(), + selectedAtIndex: + sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency), + title: S.of(context).please_select, + mainAxisAlignment: MainAxisAlignment.center, + onItemSelected: (CryptoCurrency cur) => sendViewModel.selectedCryptoCurrency = cur, + ), + context: context); + } +} diff --git a/lib/view_model/lightning_invoice_page_view_model.dart b/lib/view_model/lightning_invoice_page_view_model.dart new file mode 100644 index 000000000..656f8e05a --- /dev/null +++ b/lib/view_model/lightning_invoice_page_view_model.dart @@ -0,0 +1,140 @@ +import 'package:cake_wallet/anonpay/anonpay_api.dart'; +import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; +import 'package:cake_wallet/anonpay/anonpay_request.dart'; +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/entities/preferences_key.dart'; +import 'package:cake_wallet/entities/receive_page_option.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/currency.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:hive/hive.dart'; +import 'package:mobx/mobx.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +part 'lightning_invoice_page_view_model.g.dart'; + +class LightningInvoicePageViewModel = LightningInvoicePageViewModelBase with _$LightningInvoicePageViewModel; + +abstract class LightningInvoicePageViewModelBase with Store { + LightningInvoicePageViewModelBase( + this.address, + this.settingsStore, + this._wallet, + // this._anonpayInvoiceInfoSource, + this.sharedPreferences, + this.pageOption, + ) : description = '', + amount = '', + state = InitialExecutionState(), + selectedCurrency = walletTypeToCryptoCurrency(_wallet.type), + cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type) { + // _getPreviousDonationLink(); + // _fetchLimits(); + } + + List get currencies => [walletTypeToCryptoCurrency(_wallet.type), ...FiatCurrency.all]; + final String address; + final SettingsStore settingsStore; + final WalletBase _wallet; + // final Box _anonpayInvoiceInfoSource; + final SharedPreferences sharedPreferences; + final ReceivePageOption pageOption; + + @observable + Currency selectedCurrency; + + CryptoCurrency cryptoCurrency; + + @observable + String description; + + @observable + String amount; + + @observable + ExecutionState state; + + @computed + int get selectedCurrencyIndex => currencies.indexOf(selectedCurrency); + + @observable + double? minimum; + + @observable + double? maximum; + + @action + void selectCurrency(Currency currency) { + selectedCurrency = currency; + maximum = minimum = null; + if (currency is CryptoCurrency) { + cryptoCurrency = currency; + } else { + cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type); + } + + _fetchLimits(); + } + + @action + Future createInvoice() async { + state = IsExecutingState(); + if (amount.isNotEmpty) { + final amountInCrypto = double.tryParse(amount); + if (amountInCrypto == null) { + state = FailureState('Amount is invalid'); + return; + } + if (minimum != null && amountInCrypto < minimum!) { + state = FailureState('Amount is too small'); + return; + } + if (maximum != null && amountInCrypto > maximum!) { + state = FailureState('Amount is too big'); + return; + } + } + // final result = await anonPayApi.createInvoice(AnonPayRequest( + // cryptoCurrency: cryptoCurrency, + // address: address, + // amount: amount.isEmpty ? null : amount, + // description: description, + // fiatEquivalent: + // selectedCurrency is FiatCurrency ? (selectedCurrency as FiatCurrency).raw : null, + // )); + + // _anonpayInvoiceInfoSource.add(result); + + // state = ExecutedSuccessfullyState(payload: result); + } + + @action + void setRequestParams({ + required String inputAmount, + required String inputDescription, + }) { + description = inputDescription; + amount = inputAmount; + } + + Future _fetchLimits() async { + // final limit = await anonPayApi.fetchLimits( + // cryptoCurrency: cryptoCurrency, + // fiatCurrency: selectedCurrency is FiatCurrency ? selectedCurrency as FiatCurrency : null, + // ); + // minimum = limit.min; + // maximum = limit.max != null ? limit.max! / 4 : null; + } + + @action + void reset() { + selectedCurrency = walletTypeToCryptoCurrency(_wallet.type); + cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type); + description = ''; + amount = ''; + _fetchLimits(); + } +} diff --git a/lib/view_model/lightning_view_model.dart b/lib/view_model/lightning_view_model.dart new file mode 100644 index 000000000..5c3c0ae48 --- /dev/null +++ b/lib/view_model/lightning_view_model.dart @@ -0,0 +1,73 @@ +import 'dart:async'; +import 'package:breez_sdk/breez_sdk.dart'; +import 'package:breez_sdk/bridge_generated.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/view_model/auth_state.dart'; +import 'package:cake_wallet/core/auth_service.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/entities/biometric_auth.dart'; +import 'package:cake_wallet/store/settings_store.dart'; + +part 'lightning_view_model.g.dart'; + +class LightningViewModel = LightningViewModelBase with _$LightningViewModel; + +abstract class LightningViewModelBase with Store { + LightningViewModelBase() {} + + // @observable + // ExecutionState state; + + @action + Future receiveOnChain() async { + print("pressed"); + // ReceivePaymentRequest req = const ReceivePaymentRequest( + // amountMsat: 3000000, + // description: "Invoice for 3000 sats", + // ); + // ReceivePaymentResponse receivePaymentResponse = + // await BreezSDK().receivePayment(req: req); + + // print(receivePaymentResponse.lnInvoice); + + final sdk = await BreezSDK(); + + sdk.nodeStateStream.listen((event) { + // print("Node state: $event"); + if (event == null) return; + int balanceSat = event.maxPayableMsat ~/ 1000; + print("sats: $balanceSat"); + }); + + // ServiceHealthCheckResponse healthCheck = await sdk.serviceHealthCheck(); + // print("Current service status is: ${healthCheck.status}"); + + // ReceivePaymentRequest req = ReceivePaymentRequest( + // amountMsat: 123 * 1000, + // description: "Invoice for 123 sats", + // ); + // final s = await sdk.receivePayment(req: req); + // print(s.lnInvoice.bolt11); + + // ReceiveOnchainRequest req = const ReceiveOnchainRequest(); + // SwapInfo swapInfo = await sdk.receiveOnchain(req: req); + // // Send your funds to the below bitcoin address + // String address = swapInfo.bitcoinAddress; + // print(address); + // print("Minimum amount allowed to deposit in sats: ${swapInfo.minAllowedDeposit}"); + // print("Maximum amount allowed to deposit in sats: ${swapInfo.maxAllowedDeposit}"); + + ListPaymentsRequest lReq = ListPaymentsRequest(); + + var list = await sdk.listPayments(req: lReq); + print(list[0].amountMsat); + + var data = await sdk.fetchNodeData(); + print(data); + + + } +}