dev
dart 253 lines 10 KB
Raw
1 import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
2 import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
3 import 'package:cake_wallet/core/execution_state.dart';
4 import 'package:cake_wallet/di.dart';
5 import 'package:cake_wallet/entities/preferences_key.dart';
6 import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
7 import 'package:cake_wallet/utils/feature_flag.dart';
8 import 'package:cw_core/receive_page_option.dart';
9 import 'package:cake_wallet/routes.dart';
10 import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart';
11 import 'package:cake_wallet/src/screens/receive/widgets/anonpay_input_form.dart';
12 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
13 import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
14 import 'package:cake_wallet/utils/responsive_layout_util.dart';
15 import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
16 import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
17 import 'package:flutter/material.dart';
18 import 'package:flutter_mobx/flutter_mobx.dart';
19 import 'package:keyboard_actions/keyboard_actions.dart';
20 import 'package:cake_wallet/src/screens/base_page.dart';
21 import 'package:cake_wallet/src/widgets/trail_button.dart';
22 import 'package:cake_wallet/utils/show_pop_up.dart';
23 import 'package:cake_wallet/generated/i18n.dart';
24 import 'package:cake_wallet/src/widgets/primary_button.dart';
25 import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
26 import 'package:mobx/mobx.dart';
27 import 'package:shared_preferences/shared_preferences.dart';
28
29 class AnonPayInvoicePage extends BasePage {
30 AnonPayInvoicePage(
31 this.anonInvoicePageViewModel,
32 this.receiveOptionViewModel,
33 ) : _amountFocusNode = FocusNode() {}
34
35 final _nameController = TextEditingController();
36 final _emailController = TextEditingController();
37 final _descriptionController = TextEditingController();
38 final _amountController = TextEditingController();
39 final FocusNode _amountFocusNode;
40
41 final AnonInvoicePageViewModel anonInvoicePageViewModel;
42 final ReceiveOptionViewModel receiveOptionViewModel;
43 final _formKey = GlobalKey<FormState>();
44
45 bool effectsInstalled = false;
46
47 @override
48 bool get gradientAll => true;
49
50 @override
51 bool get resizeToAvoidBottomInset => false;
52
53 @override
54 bool get extendBodyBehindAppBar => true;
55
56 @override
57 AppBarStyle get appBarStyle =>
58 FeatureFlag.hasNewUiExtraPages ? AppBarStyle.completelyTransparent : AppBarStyle.transparent;
59
60 @override
61 void onClose(BuildContext context) => FeatureFlag.hasNewUiExtraPages
62 ? Navigator.of(context, rootNavigator: true).pop()
63 : Navigator.popUntil(context, (route) => route.isFirst);
64
65 @override
66 Widget middle(BuildContext context) => PresentReceiveOptionPicker(
67 receiveOptionViewModel: receiveOptionViewModel, color: titleColor(context));
68
69 @override
70 Widget trailing(BuildContext context) => TrailButton(
71 caption: S.of(context).clear,
72 onPressed: () {
73 _formKey.currentState?.reset();
74 anonInvoicePageViewModel.reset();
75 });
76
77 Future<bool> _onNavigateBack(BuildContext context) async {
78 onClose(context);
79 return false;
80 }
81
82 @override
83 Widget body(BuildContext context) {
84 WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context));
85
86 return WillPopScope(
87 onWillPop: () => _onNavigateBack(context),
88 child: KeyboardActions(
89 disableScroll: true,
90 config: KeyboardActionsConfig(
91 keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
92 keyboardBarColor: Theme.of(context).colorScheme.surfaceVariant,
93 nextFocus: false,
94 actions: [
95 KeyboardActionsItem(
96 focusNode: _amountFocusNode,
97 toolbarButtons: [(_) => KeyboardDoneButton()],
98 ),
99 ],
100 ),
101 child: Container(
102 color: Theme.of(context).colorScheme.surface,
103 child: ScrollableWithBottomSection(
104 contentPadding: EdgeInsets.only(bottom: 24),
105 content: Container(
106 decoration: responsiveLayoutUtil.shouldRenderMobileUI
107 ? BoxDecoration(
108 borderRadius: BorderRadius.only(
109 bottomLeft: Radius.circular(24),
110 bottomRight: Radius.circular(24),
111 ),
112 color: Theme.of(context).colorScheme.surfaceContainerLow,
113 )
114 : null,
115 child: Observer(builder: (_) {
116 return Padding(
117 padding: EdgeInsets.fromLTRB(24, 120, 24, 0),
118 child: AnonInvoiceForm(
119 nameController: _nameController,
120 descriptionController: _descriptionController,
121 amountController: _amountController,
122 emailController: _emailController,
123 depositAmountFocus: _amountFocusNode,
124 formKey: _formKey,
125 isInvoice: receiveOptionViewModel.selectedReceiveOption ==
126 ReceivePageOption.anonPayInvoice,
127 anonInvoicePageViewModel: anonInvoicePageViewModel,
128 ),
129 );
130 }),
131 ),
132 bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
133 bottomSection: Observer(builder: (_) {
134 final isInvoice =
135 receiveOptionViewModel.selectedReceiveOption == ReceivePageOption.anonPayInvoice;
136 return Column(
137 children: <Widget>[
138 Padding(
139 padding: EdgeInsets.only(bottom: 15),
140 child: Center(
141 child: Text(
142 isInvoice
143 ? S.of(context).anonpay_description("an invoice", "pay")
144 : S.of(context).anonpay_description("a donation link", "donate"),
145 textAlign: TextAlign.center,
146 style: Theme.of(context).textTheme.bodySmall?.copyWith(
147 color: Theme.of(context).colorScheme.onSurfaceVariant,
148 fontWeight: FontWeight.w500,
149 ),
150 ),
151 ),
152 ),
153 LoadingPrimaryButton(
154 text: isInvoice
155 ? S.of(context).create_invoice
156 : S.of(context).create_donation_link,
157 onPressed: () {
158 FocusScope.of(context).unfocus();
159 anonInvoicePageViewModel.setRequestParams(
160 inputAmount: _amountController.text,
161 inputName: _nameController.text,
162 inputEmail: _emailController.text,
163 inputDescription: _descriptionController.text,
164 );
165 if (anonInvoicePageViewModel.receipientEmail.isNotEmpty &&
166 _formKey.currentState != null &&
167 !_formKey.currentState!.validate()) {
168 return;
169 }
170 if (isInvoice) {
171 anonInvoicePageViewModel.createInvoice();
172 } else {
173 anonInvoicePageViewModel.generateDonationLink();
174 }
175 },
176 color: Theme.of(context).colorScheme.primary,
177 textColor: Theme.of(context).colorScheme.onPrimary,
178 isLoading: anonInvoicePageViewModel.state is IsExecutingState,
179 ),
180 ],
181 );
182 }),
183 ),
184 ),
185 ),
186 );
187 }
188
189 void _setReactions(BuildContext context) {
190 if (effectsInstalled) {
191 return;
192 }
193
194 reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
195 switch (option) {
196 case ReceivePageOption.mainnet:
197 Navigator.popAndPushNamed(context, Routes.addressPage);
198 break;
199 case ReceivePageOption.anonPayDonationLink:
200 final sharedPreferences = getIt.get<SharedPreferences>();
201 final clearnetUrl = sharedPreferences.getString(PreferencesKey.clearnetDonationLink);
202 final onionUrl = sharedPreferences.getString(PreferencesKey.onionDonationLink);
203 final donationWalletName =
204 sharedPreferences.getString(PreferencesKey.donationLinkWalletName);
205
206 if (clearnetUrl != null &&
207 onionUrl != null &&
208 anonInvoicePageViewModel.currentWalletName == donationWalletName) {
209 Navigator.pushReplacementNamed(
210 context,
211 Routes.anonPayReceivePage,
212 arguments: AnonPayReceivePageArgs(
213 invoiceInfo: AnonpayDonationLinkInfo(
214 clearnetUrl: clearnetUrl,
215 onionUrl: onionUrl,
216 address: anonInvoicePageViewModel.address,
217 ),
218 qrImage: anonInvoicePageViewModel.qrImage,
219 ),
220 );
221 }
222 break;
223 default:
224 }
225 });
226
227 reaction((_) => anonInvoicePageViewModel.state, (ExecutionState state) {
228 if (state is ExecutedSuccessfullyState) {
229 Navigator.pushNamed(
230 context,
231 Routes.anonPayReceivePage,
232 arguments: AnonPayReceivePageArgs(
233 invoiceInfo: state.payload as AnonpayInvoiceInfo,
234 qrImage: anonInvoicePageViewModel.qrImage,
235 ),
236 );
237 }
238 if (state is FailureState) {
239 showPopUp<void>(
240 context: context,
241 builder: (BuildContext context) {
242 return AlertWithOneAction(
243 alertTitle: S.of(context).error,
244 alertContent: state.error.toString(),
245 buttonText: S.of(context).ok,
246 buttonAction: () => Navigator.of(context).pop());
247 });
248 }
249 });
250
251 effectsInstalled = true;
252 }
253 }