| 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 | } |