dev
dart 434 lines 17.6 KB
Raw
1 import 'package:cake_wallet/core/execution_state.dart';
2 import 'package:cake_wallet/generated/i18n.dart';
3 import 'package:cake_wallet/routes.dart';
4 import 'package:cake_wallet/src/screens/base_page.dart';
5 import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
6 import 'package:cake_wallet/src/screens/integrations/deuro/widgets/info_chip.dart';
7 import 'package:cake_wallet/src/screens/integrations/deuro/widgets/interest_card_widget.dart';
8 import 'package:cake_wallet/src/screens/integrations/deuro/widgets/savings_card_widget.dart';
9 import 'package:cake_wallet/src/screens/integrations/deuro/widgets/savings_edit_sheet.dart';
10 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
11 import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
12 import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart';
13 import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
14 import 'package:cake_wallet/src/widgets/bottom_sheet/tooltip_bottom_sheet.dart';
15 import 'package:cake_wallet/src/widgets/gradient_background.dart';
16 import 'package:cake_wallet/utils/responsive_layout_util.dart';
17 import 'package:cake_wallet/utils/show_pop_up.dart';
18 import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
19 import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
20 import 'package:cw_core/crypto_currency.dart';
21 import 'package:cw_core/pending_transaction.dart';
22 import 'package:cw_core/wallet_type.dart';
23 import 'package:flutter/cupertino.dart';
24 import 'package:flutter/material.dart';
25 import 'package:flutter_mobx/flutter_mobx.dart';
26 import 'package:mobx/mobx.dart';
27 import 'package:url_launcher/url_launcher_string.dart';
28
29 class DEuroSavingsPage extends BasePage {
30 final DEuroViewModel _dEuroViewModel;
31
32 DEuroSavingsPage(this._dEuroViewModel);
33
34 @override
35 bool get gradientBackground => true;
36
37 @override
38 Widget Function(BuildContext, Widget) get rootWrapper =>
39 (context, scaffold) => GradientBackground(scaffold: scaffold);
40
41 @override
42 String get title => S.current.deuro_savings;
43
44 @override
45 Widget body(BuildContext context) {
46 WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context, _dEuroViewModel));
47
48 return RefreshIndicator(
49 displacement: responsiveLayoutUtil.screenHeight * 0.1,
50 onRefresh: _dEuroViewModel.reloadSavingsUserData,
51 child: CustomScrollView(
52 slivers: <Widget>[
53 SliverFillRemaining(
54 child: SizedBox(
55 width: double.infinity,
56 child: Column(
57 children: <Widget>[
58 Observer(
59 builder: (_) => SavingsCard(
60 interestRate: "${_dEuroViewModel.interestRateFormated}%",
61 savingsBalance:
62 _dEuroViewModel.savingsBalance.toStringWithPrecision(fractionalDigits: 6),
63 fiatSavingsBalance: _dEuroViewModel.fiatSavingsBalanceFormated,
64 currency: CryptoCurrency.deuro,
65 fiatCurrency: _dEuroViewModel.isFiatDisabled ? null : _dEuroViewModel.fiat,
66 onAddSavingsPressed: () => _onSavingsAdd(context),
67 onRemoveSavingsPressed: () => _onSavingsRemove(context),
68 onApproveSavingsPressed: () => _onApproval(context),
69 onTooltipPressed: () => _onSavingsTooltipPressed(context),
70 isEnabled: _dEuroViewModel.isEnabled,
71 isLoading: _dEuroViewModel.isLoading,
72 onWithdrawV1Pressed: () => _onWithdrawV1(context),
73 savingsBalanceV1: _dEuroViewModel.savingsBalanceV1
74 ?.toStringWithPrecision(fractionalDigits: 6),
75 fiatSavingsBalanceV1: _dEuroViewModel.fiatSavingsBalanceV1Formated,
76 ),
77 ),
78 Observer(
79 builder: (_) => InterestCardWidget(
80 title: S.of(context).deuro_savings_collect_interest,
81 fiatAccruedInterest: _dEuroViewModel.fiatAccruedInterestFormated,
82 fiatCurrency: _dEuroViewModel.isFiatDisabled ? null : _dEuroViewModel.fiat,
83 accruedInterest: _dEuroViewModel.accruedInterestFormated,
84 onCollectInterest: () => _onCollectInterest(context),
85 onReinvestInterest: () => _onReinvestInterest(context),
86 onTooltipPressed: () => _onInterestTooltipPressed(context),
87 isEnabled: _dEuroViewModel.isSavingsActionsEnabled,
88 ),
89 ),
90 Spacer(),
91 SafeArea(
92 child: Padding(
93 padding: EdgeInsets.only(bottom: 30),
94 child: Row(
95 mainAxisAlignment: MainAxisAlignment.center,
96 children: [
97 InfoChip(
98 label: S.of(context).deuro_about_deuro,
99 icon: CupertinoIcons.info,
100 onPressed: () => _showWelcomeTooltip(context),
101 ),
102 SizedBox(width: 10),
103 InfoChip(
104 label: S.of(context).website,
105 icon: CupertinoIcons.globe,
106 onPressed: () => launchUrlString("https://deuro.com/"),
107 )
108 ],
109 ),
110 ),
111 ),
112 ],
113 ),
114 ),
115 ),
116 ],
117 ),
118 );
119 }
120
121 bool _editSheetIsOpen = false;
122
123 Future<void> _onSavingsAdd(BuildContext context) async {
124 if (_editSheetIsOpen) return;
125 _editSheetIsOpen = true;
126 final amount = await _showEditBottomSheet(context, isAdding: true);
127 if (amount != null) {
128 await _requireHardwareWallet(context);
129 _dEuroViewModel.prepareSavingsEdit(amount, true);
130 }
131 _editSheetIsOpen = false;
132 }
133
134 Future<void> _onWithdrawV1(BuildContext context) async {
135 if (_editSheetIsOpen) return;
136 _editSheetIsOpen = true;
137 try {
138 await _requireHardwareWallet(context);
139 await _dEuroViewModel.prepareSavingsV1Withdraw();
140 } catch (_) {}
141 _editSheetIsOpen = false;
142 }
143
144 Future<void> _onSavingsRemove(BuildContext context) async {
145 if (_editSheetIsOpen) return;
146 _editSheetIsOpen = true;
147 final amount = await _showEditBottomSheet(context, isAdding: false);
148 if (amount != null) {
149 await _requireHardwareWallet(context);
150 _dEuroViewModel.prepareSavingsEdit(amount, false);
151 }
152 _editSheetIsOpen = false;
153 }
154
155 Future<void> _onReinvestInterest(BuildContext context) async {
156 if (_editSheetIsOpen) return;
157 _editSheetIsOpen = true;
158 await _requireHardwareWallet(context);
159 await _dEuroViewModel.prepareReinvestInterest();
160 _editSheetIsOpen = false;
161 }
162
163 Future<void> _onCollectInterest(BuildContext context) async {
164 if (_editSheetIsOpen) return;
165 _editSheetIsOpen = true;
166 await _requireHardwareWallet(context);
167 await _dEuroViewModel.prepareCollectInterest();
168 _editSheetIsOpen = false;
169 }
170
171 Future<void> _onApproval(BuildContext context) async {
172 await _requireHardwareWallet(context);
173 _dEuroViewModel.prepareApproval();
174 }
175
176 Future<void> _requireHardwareWallet(BuildContext context) async {
177 if (_dEuroViewModel.wallet.isHardwareWallet) {
178 if (!_dEuroViewModel.hardwareWalletViewModel!.isConnected(_dEuroViewModel.wallet.type)) {
179 await Navigator.of(context).pushNamed(Routes.connectDevices,
180 arguments: ConnectDevicePageParams(
181 walletType: _dEuroViewModel.wallet.type,
182 hardwareWalletType: _dEuroViewModel.wallet.walletInfo.hardwareWalletType!,
183 onConnectDevice: (context, _) {
184 _dEuroViewModel.hardwareWalletViewModel!.initWallet(_dEuroViewModel.wallet);
185 Navigator.of(context).pop();
186 },
187 ));
188 } else {
189 _dEuroViewModel.hardwareWalletViewModel!.initWallet(_dEuroViewModel.wallet);
190 }
191 }
192 }
193
194 bool _isReactionsSet = false;
195
196 void _setReactions(BuildContext context, DEuroViewModel dEuroViewModel) {
197 if (_isReactionsSet) return;
198 if (_dEuroViewModel.isFistTime) _showWelcomeTooltip(context);
199
200 reaction((_) => dEuroViewModel.transaction, (PendingTransaction? tx) async {
201 if (tx == null) return;
202 String title;
203
204 switch (_dEuroViewModel.actionType) {
205 case DEuroActionType.deposit:
206 title = S.of(context).deuro_savings_add;
207 break;
208 case DEuroActionType.withdraw:
209 title = S.of(context).deuro_savings_remove;
210 break;
211 case DEuroActionType.reinvest:
212 title = S.of(context).deuro_reinvest_interest;
213 break;
214 default:
215 title = S.of(context).confirm_transaction;
216 break;
217 }
218
219 final result = await showModalBottomSheet<bool>(
220 context: context,
221 isDismissible: false,
222 isScrollControlled: true,
223 builder: (BuildContext bottomSheetContext) => ConfirmSendingBottomSheet(
224 key: ValueKey('savings_page_confirm_sending_dialog_key'),
225 footerType: FooterType.slideActionButton,
226 titleText: title,
227 walletType: WalletType.ethereum,
228 titleIconPath: CryptoCurrency.deuro.iconPath,
229 currency: CryptoCurrency.deuro,
230 amount: S.of(bottomSheetContext).send_amount,
231 amountValue: _dEuroViewModel.actionType == DEuroActionType.reinvest
232 ? _dEuroViewModel.accruedInterestFormated
233 : tx.amount.toStringWithSymbol(),
234 fiatAmountValue: _dEuroViewModel.actionType == DEuroActionType.reinvest
235 ? _dEuroViewModel.fiatAccruedInterestFormated
236 : _dEuroViewModel.pendingTransactionFiatAmountFormatted,
237 fee: S.of(bottomSheetContext).send_estimated_fee,
238 feeValue: tx.feeFormatted,
239 feeFiatAmount: _dEuroViewModel.pendingTransactionFeeFiatAmountFormatted,
240 outputs: [],
241 onSlideActionComplete: () async {
242 Navigator.of(bottomSheetContext).pop(true);
243 dEuroViewModel.commitTransaction();
244 },
245 change: tx.change,
246 ),
247 );
248
249 if (result == null) dEuroViewModel.dismissTransaction();
250 });
251
252 reaction((_) => dEuroViewModel.approvalTransaction, (PendingTransaction? tx) async {
253 if (tx == null) return;
254 final result = await showModalBottomSheet<bool>(
255 context: context,
256 isDismissible: false,
257 isScrollControlled: true,
258 builder: (BuildContext bottomSheetContext) => ConfirmSendingBottomSheet(
259 key: ValueKey('savings_page_confirm_approval_dialog_key'),
260 footerType: FooterType.slideActionButton,
261 titleText: S.of(bottomSheetContext).approve_tokens,
262 walletType: WalletType.ethereum,
263 titleIconPath: CryptoCurrency.deuro.iconPath,
264 currency: CryptoCurrency.deuro,
265 amount: S.of(bottomSheetContext).send_amount,
266 amountValue: tx.amountFormatted,
267 fiatAmountValue: "",
268 fee: S.of(bottomSheetContext).send_estimated_fee,
269 feeValue: tx.fee.toStringWithSymbol(),
270 feeFiatAmount: "",
271 outputs: [],
272 onSlideActionComplete: () {
273 Navigator.of(bottomSheetContext).pop(true);
274 dEuroViewModel.commitApprovalTransaction();
275 },
276 change: tx.change,
277 explanation: S.of(context).deuro_savings_approve_app_description,
278 ),
279 );
280
281 if (result == null) dEuroViewModel.dismissTransaction();
282 });
283
284 reaction((_) => dEuroViewModel.state, (ExecutionState state) async {
285 if (state is TransactionCommitted) {
286 WidgetsBinding.instance.addPostFrameCallback((_) async {
287 if (!context.mounted) return;
288
289 await showModalBottomSheet<void>(
290 context: context,
291 isDismissible: false,
292 builder: (BuildContext bottomSheetContext) => InfoBottomSheet(
293 footerType: FooterType.singleActionButton,
294 titleText: S.of(bottomSheetContext).transaction_sent,
295 contentImage: 'assets/images/birthday_cake.png',
296 content: S.of(bottomSheetContext).deuro_tx_commited_content,
297 singleActionButtonText: S.of(bottomSheetContext).close,
298 singleActionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'),
299 onSingleActionButtonPressed: () => Navigator.of(bottomSheetContext).pop(),
300 ),
301 );
302 });
303 }
304
305 if (state is NoEtherState) {
306 WidgetsBinding.instance.addPostFrameCallback((_) async => await _showNoEthTooltip(context));
307 }
308
309 if (state is FailureState) {
310 WidgetsBinding.instance.addPostFrameCallback((_) async {
311 if (!context.mounted) return;
312
313 await showPopUp<void>(
314 context: context,
315 builder: (BuildContext popupContext) => AlertWithOneAction(
316 alertTitle: S.of(popupContext).error,
317 alertContent: state.error,
318 buttonText: S.of(popupContext).ok,
319 buttonAction: () => Navigator.of(popupContext).pop(),
320 ),
321 );
322 });
323 }
324 });
325
326 _isReactionsSet = true;
327 }
328
329 void _onSavingsTooltipPressed(BuildContext context) => _showTooltip(
330 context,
331 title: S.of(context).deuro_savings_balance,
332 content: S.of(context).deuro_savings_balance_tooltip,
333 key: 'savings_tooltip',
334 onLearnMorePressed: () => launchUrlString("https://deuro.com/#faq"),
335 );
336
337 void _onInterestTooltipPressed(BuildContext context) => _showTooltip(
338 context,
339 title: S.of(context).deuro_savings_collect_interest,
340 content: S.of(context).deuro_savings_collect_interest_tooltip,
341 key: 'interest_tooltip',
342 onLearnMorePressed: () => launchUrlString("https://deuro.com/#faq"),
343 );
344
345 void _showTooltip(
346 BuildContext context, {
347 required String title,
348 required String content,
349 required String key,
350 required VoidCallback onLearnMorePressed,
351 }) {
352 if (!context.mounted) return;
353
354 showModalBottomSheet<void>(
355 context: context,
356 builder: (BuildContext bottomSheetContext) => TooltipSheet(
357 titleText: title,
358 titleIconPath: CryptoCurrency.deuro.iconPath,
359 tooltip: content,
360 footerType: FooterType.doubleActionButton,
361 doubleActionRightButtonText: S.of(context).close,
362 rightActionButtonKey: ValueKey('deuro_page_tooltip_dialog_${key}_ok_button_key'),
363 onRightActionButtonPressed: () => Navigator.of(bottomSheetContext).pop(),
364 doubleActionLeftButtonText: S.of(context).learn_more,
365 leftActionButtonKey: ValueKey('deuro_page_tooltip_dialog_${key}_learn_more_button_key'),
366 onLeftActionButtonPressed: onLearnMorePressed,
367 ),
368 );
369 }
370
371 Future<void> _showWelcomeTooltip(BuildContext context) async {
372 if (!context.mounted) return;
373
374 await showModalBottomSheet<void>(
375 context: context,
376 isScrollControlled: true,
377 builder: (BuildContext bottomSheetContext) => InfoBottomSheet(
378 height: 350,
379 titleText: "dEURO",
380 titleIconPath: CryptoCurrency.deuro.iconPath,
381 contentImage: 'assets/images/deuro_hero.png',
382 contentImageSize: 200,
383 content: S.of(context).deuro_savings_welcome_description,
384 footerType: FooterType.doubleActionButton,
385 doubleActionRightButtonText: S.of(context).close,
386 rightActionButtonKey: ValueKey('deuro_page_tooltip_dialog_welcome_ok_button_key'),
387 onRightActionButtonPressed: () => Navigator.of(bottomSheetContext).pop(),
388 doubleActionLeftButtonText: S.of(context).learn_more,
389 leftActionButtonKey: ValueKey('deuro_page_tooltip_dialog_welcome_learn_more_button_key'),
390 onLeftActionButtonPressed: () => launchUrlString("https://deuro.com/what-is-deuro.html"),
391 showDisclaimerText: _dEuroViewModel.isFistTime,
392 ),
393 );
394
395 if (_dEuroViewModel.isFistTime) _dEuroViewModel.acceptDisclaimer();
396 }
397
398 Future<void> _showNoEthTooltip(BuildContext context) async {
399 if (!context.mounted) return;
400
401 return showModalBottomSheet<void>(
402 context: context,
403 builder: (BuildContext bottomSheetContext) => InfoBottomSheet(
404 titleText: title,
405 titleIconPath: CryptoCurrency.deuro.iconPath,
406 contentImage: 'assets/images/deuro_not_enough_eth.png',
407 content: S.of(context).deuro_tooltip_no_eth,
408 singleActionButtonKey: ValueKey('deuro_page_tooltip_dialog_no_eth_ok_button_key'),
409 singleActionButtonText: S.of(context).close,
410 onSingleActionButtonPressed: () => Navigator.of(bottomSheetContext).pop(),
411 footerType: FooterType.singleActionButton,
412 ),
413 );
414 }
415
416 Future<String?> _showEditBottomSheet(BuildContext context, {bool isAdding = false}) async {
417 if (!context.mounted) return null;
418
419 return showModalBottomSheet<String?>(
420 context: context,
421 isScrollControlled: true,
422 builder: (BuildContext bottomSheetContext) => SavingsEditSheet(
423 titleText: isAdding ? S.of(context).deuro_savings_add : S.of(context).deuro_savings_remove,
424 titleIconPath: CryptoCurrency.deuro.iconPath,
425 balanceTitle: isAdding
426 ? S.of(context).deuro_savings_available_to_add
427 : S.of(context).deuro_savings_available_to_remove,
428 balance: isAdding ? _dEuroViewModel.accountBalance : _dEuroViewModel.savingsBalance,
429 footerType: FooterType.none,
430 maxHeight: MediaQuery.of(context).size.height * 0.8,
431 ),
432 );
433 }
434 }