Fix fiat rate on receive screen. (#1754)

* Fix fiat rate on receive screen. * Code cleanup * Update Receive QR on currency change

Konstantin Ullrich committed Oct 17, 2024 at 14:52 UTC 6c9de67599fb0612ee95a87ab644e8b83ffb858e
1 file changed +133 -98
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+133 -98
@@ -1,14 +1,15 @@
1 -import 'dart:math';
1 +import 'dart:developer' as dev;
2
3 import 'package:cake_wallet/bitcoin/bitcoin.dart';
4 +import 'package:cake_wallet/core/fiat_conversion_service.dart';
5 import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
6 import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
7 +import 'package:cake_wallet/entities/fiat_api_mode.dart';
8 import 'package:cake_wallet/entities/fiat_currency.dart';
9 import 'package:cake_wallet/ethereum/ethereum.dart';
10 import 'package:cake_wallet/generated/i18n.dart';
11 import 'package:cake_wallet/haven/haven.dart';
12 import 'package:cake_wallet/monero/monero.dart';
11 -import 'package:cake_wallet/nano/nano.dart';
13 import 'package:cake_wallet/polygon/polygon.dart';
14 import 'package:cake_wallet/solana/solana.dart';
15 import 'package:cake_wallet/store/app_store.dart';
@@ -24,16 +25,14 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i
25 import 'package:cake_wallet/wownero/wownero.dart';
26 import 'package:cw_core/amount_converter.dart';
27 import 'package:cw_core/currency.dart';
27 -import 'package:cw_core/wallet_info.dart';
28 import 'package:cw_core/wallet_type.dart';
29 -import 'package:cw_monero/api/wallet.dart';
30 -import 'package:flutter/foundation.dart';
29 import 'package:intl/intl.dart';
30 import 'package:mobx/mobx.dart';
31
32 part 'wallet_address_list_view_model.g.dart';
33
36 -class WalletAddressListViewModel = WalletAddressListViewModelBase with _$WalletAddressListViewModel;
34 +class WalletAddressListViewModel = WalletAddressListViewModelBase
35 + with _$WalletAddressListViewModel;
36
37 abstract class PaymentURI {
38 PaymentURI({required this.amount, required this.address});
@@ -43,12 +42,11 @@ abstract class PaymentURI {
42 }
43
44 class MoneroURI extends PaymentURI {
46 - MoneroURI({required String amount, required String address})
47 - : super(amount: amount, address: address);
45 + MoneroURI({required super.amount, required super.address});
46
47 @override
48 String toString() {
51 - var base = 'monero:' + address;
49 + var base = 'monero:$address';
50
51 if (amount.isNotEmpty) {
52 base += '?tx_amount=${amount.replaceAll(',', '.')}';
@@ -59,12 +57,11 @@ class MoneroURI extends PaymentURI {
57 }
58
59 class HavenURI extends PaymentURI {
62 - HavenURI({required String amount, required String address})
63 - : super(amount: amount, address: address);
60 + HavenURI({required super.amount, required super.address});
61
62 @override
63 String toString() {
67 - var base = 'haven:' + address;
64 + var base = 'haven:$address';
65
66 if (amount.isNotEmpty) {
67 base += '?tx_amount=${amount.replaceAll(',', '.')}';
@@ -75,12 +72,11 @@ class HavenURI extends PaymentURI {
72 }
73
74 class BitcoinURI extends PaymentURI {
78 - BitcoinURI({required String amount, required String address})
79 - : super(amount: amount, address: address);
75 + BitcoinURI({required super.amount, required super.address});
76
77 @override
78 String toString() {
83 - var base = 'bitcoin:' + address;
79 + var base = 'bitcoin:$address';
80
81 if (amount.isNotEmpty) {
82 base += '?amount=${amount.replaceAll(',', '.')}';
@@ -91,12 +87,11 @@ class BitcoinURI extends PaymentURI {
87 }
88
89 class LitecoinURI extends PaymentURI {
94 - LitecoinURI({required String amount, required String address})
95 - : super(amount: amount, address: address);
90 + LitecoinURI({required super.amount, required super.address});
91
92 @override
93 String toString() {
99 - var base = 'litecoin:' + address;
94 + var base = 'litecoin:$address';
95
96 if (amount.isNotEmpty) {
97 base += '?amount=${amount.replaceAll(',', '.')}';
@@ -107,12 +102,11 @@ class LitecoinURI extends PaymentURI {
102 }
103
104 class EthereumURI extends PaymentURI {
110 - EthereumURI({required String amount, required String address})
111 - : super(amount: amount, address: address);
105 + EthereumURI({required super.amount, required super.address});
106
107 @override
108 String toString() {
115 - var base = 'ethereum:' + address;
109 + var base = 'ethereum:$address';
110
111 if (amount.isNotEmpty) {
112 base += '?amount=${amount.replaceAll(',', '.')}';
@@ -123,8 +117,7 @@ class EthereumURI extends PaymentURI {
117 }
118
119 class BitcoinCashURI extends PaymentURI {
126 - BitcoinCashURI({required String amount, required String address})
127 - : super(amount: amount, address: address);
120 + BitcoinCashURI({required super.amount, required super.address});
121
122 @override
123 String toString() {
@@ -139,12 +132,11 @@ class BitcoinCashURI extends PaymentURI {
132 }
133
134 class NanoURI extends PaymentURI {
142 - NanoURI({required String amount, required String address})
143 - : super(amount: amount, address: address);
135 + NanoURI({required super.amount, required super.address});
136
137 @override
138 String toString() {
147 - var base = 'nano:' + address;
139 + var base = 'nano:$address';
140 if (amount.isNotEmpty) {
141 base += '?amount=${amount.replaceAll(',', '.')}';
142 }
@@ -154,12 +146,11 @@ class NanoURI extends PaymentURI {
146 }
147
148 class PolygonURI extends PaymentURI {
157 - PolygonURI({required String amount, required String address})
158 - : super(amount: amount, address: address);
149 + PolygonURI({required super.amount, required super.address});
150
151 @override
152 String toString() {
162 - var base = 'polygon:' + address;
153 + var base = 'polygon:$address';
154
155 if (amount.isNotEmpty) {
156 base += '?amount=${amount.replaceAll(',', '.')}';
@@ -170,12 +161,12 @@ class PolygonURI extends PaymentURI {
161 }
162
163 class SolanaURI extends PaymentURI {
173 - SolanaURI({required String amount, required String address})
174 - : super(amount: amount, address: address);
164 + SolanaURI({required super.amount, required super.address});
165
166 @override
167 String toString() {
178 - var base = 'solana:' + address;
168 + var base = 'solana:$address';
169 +
170 if (amount.isNotEmpty) {
171 base += '?amount=${amount.replaceAll(',', '.')}';
172 }
@@ -185,12 +176,12 @@ class SolanaURI extends PaymentURI {
176 }
177
178 class TronURI extends PaymentURI {
188 - TronURI({required String amount, required String address})
189 - : super(amount: amount, address: address);
179 + TronURI({required super.amount, required super.address});
180
181 @override
182 String toString() {
193 - var base = 'tron:' + address;
183 + var base = 'tron:$address';
184 +
185 if (amount.isNotEmpty) {
186 base += '?amount=${amount.replaceAll(',', '.')}';
187 }
@@ -200,12 +191,11 @@ class TronURI extends PaymentURI {
191 }
192
193 class WowneroURI extends PaymentURI {
203 - WowneroURI({required String amount, required String address})
204 - : super(amount: amount, address: address);
194 + WowneroURI({required super.amount, required super.address});
195
196 @override
197 String toString() {
208 - var base = 'wownero:' + address;
198 + var base = 'wownero:$address';
199
200 if (amount.isNotEmpty) {
201 base += '?tx_amount=${amount.replaceAll(',', '.')}';
@@ -215,7 +205,8 @@ class WowneroURI extends PaymentURI {
205 }
206 }
207
218 -abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
208 +abstract class WalletAddressListViewModelBase
209 + extends WalletChangeListenerViewModel with Store {
210 WalletAddressListViewModelBase({
211 required AppStore appStore,
212 required this.yatStore,
@@ -223,9 +214,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
214 }) : _baseItems = <ListItem>[],
215 selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type),
216 _cryptoNumberFormat = NumberFormat(_cryptoNumberPattern),
226 - hasAccounts = appStore.wallet!.type == WalletType.monero ||
227 - appStore.wallet!.type == WalletType.wownero ||
228 - appStore.wallet!.type == WalletType.haven,
217 + hasAccounts = [WalletType.monero, WalletType.wownero, WalletType.haven]
218 + .contains(appStore.wallet!.type),
219 amount = '',
220 _settingsStore = appStore.settingsStore,
221 super(appStore: appStore) {
@@ -237,9 +227,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
227 _init();
228
229 selectedCurrency = walletTypeToCryptoCurrency(wallet.type);
240 - hasAccounts = wallet.type == WalletType.monero ||
241 - wallet.type == WalletType.wownero ||
242 - wallet.type == WalletType.haven;
230 + hasAccounts = [WalletType.monero, WalletType.wownero, WalletType.haven]
231 + .contains(wallet.type);
232 }
233
234 static const String _cryptoNumberPattern = '0.00000000';
@@ -249,7 +238,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
238 final FiatConversionStore fiatConversionStore;
239 final SettingsStore _settingsStore;
240
252 - List<Currency> get currencies => [walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all];
241 + double? _fiatRate;
242 + String _rawAmount = '';
243 +
244 + List<Currency> get currencies =>
245 + [walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all];
246
247 String get buttonTitle {
248 if (isElectrumWallet) {
@@ -275,9 +268,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
268 WalletType get type => wallet.type;
269
270 @computed
278 - WalletAddressListItem get address {
279 - return WalletAddressListItem(address: wallet.walletAddresses.address, isPrimary: false);
280 - }
271 + WalletAddressListItem get address => WalletAddressListItem(
272 + address: wallet.walletAddresses.address, isPrimary: false);
273
274 @computed
275 PaymentURI get uri {
@@ -321,8 +313,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
313 final addressList = ObservableList<ListItem>();
314
315 if (wallet.type == WalletType.monero) {
324 - final primaryAddress = monero!.getSubaddressList(wallet).subaddresses.first;
325 - final addressItems = monero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
316 + final primaryAddress =
317 + monero!.getSubaddressList(wallet).subaddresses.first;
318 + final addressItems =
319 + monero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
320 final isPrimary = subaddress == primaryAddress;
321
322 return WalletAddressListItem(
@@ -338,8 +332,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
332 }
333
334 if (wallet.type == WalletType.wownero) {
341 - final primaryAddress = wownero!.getSubaddressList(wallet).subaddresses.first;
342 - final addressItems = wownero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
335 + final primaryAddress =
336 + wownero!.getSubaddressList(wallet).subaddresses.first;
337 + final addressItems =
338 + wownero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
339 final isPrimary = subaddress == primaryAddress;
340
341 return WalletAddressListItem(
@@ -352,8 +348,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
348 }
349
350 if (wallet.type == WalletType.haven) {
355 - final primaryAddress = haven!.getSubaddressList(wallet).subaddresses.first;
356 - final addressItems = haven!.getSubaddressList(wallet).subaddresses.map((subaddress) {
351 + final primaryAddress =
352 + haven!.getSubaddressList(wallet).subaddresses.first;
353 + final addressItems =
354 + haven!.getSubaddressList(wallet).subaddresses.map((subaddress) {
355 final isPrimary = subaddress == primaryAddress;
356
357 return WalletAddressListItem(
@@ -367,7 +365,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
365
366 if (isElectrumWallet) {
367 if (bitcoin!.hasSelectedSilentPayments(wallet)) {
370 - final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
368 + final addressItems =
369 + bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
370 final isPrimary = address.id == 0;
371
372 return WalletAddressListItem(
@@ -418,7 +417,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
417 if (wallet.type == WalletType.litecoin && addressItems.length >= 1000) {
418 // find the index of the last item with a txCount > 0
419 final addressItemsList = addressItems.toList();
421 - int index = addressItemsList.lastIndexWhere((item) => (item.txCount ?? 0) > 0);
420 + int index = addressItemsList
421 + .lastIndexWhere((item) => (item.txCount ?? 0) > 0);
422 if (index == -1) {
423 index = 0;
424 }
@@ -432,19 +432,22 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
432 if (wallet.type == WalletType.ethereum) {
433 final primaryAddress = ethereum!.getAddress(wallet);
434
435 - addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
435 + addressList.add(WalletAddressListItem(
436 + isPrimary: true, name: null, address: primaryAddress));
437 }
438
439 if (wallet.type == WalletType.polygon) {
440 final primaryAddress = polygon!.getAddress(wallet);
441
441 - addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
442 + addressList.add(WalletAddressListItem(
443 + isPrimary: true, name: null, address: primaryAddress));
444 }
445
446 if (wallet.type == WalletType.solana) {
447 final primaryAddress = solana!.getAddress(wallet);
448
447 - addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
449 + addressList.add(WalletAddressListItem(
450 + isPrimary: true, name: null, address: primaryAddress));
451 }
452
453 if (wallet.type == WalletType.nano) {
@@ -458,18 +461,21 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
461 if (wallet.type == WalletType.tron) {
462 final primaryAddress = tron!.getAddress(wallet);
463
461 - addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
464 + addressList.add(WalletAddressListItem(
465 + isPrimary: true, name: null, address: primaryAddress));
466 }
467
468 for (var i = 0; i < addressList.length; i++) {
469 if (!(addressList[i] is WalletAddressListItem)) continue;
466 - (addressList[i] as WalletAddressListItem).isHidden = wallet.walletAddresses.hiddenAddresses
470 + (addressList[i] as WalletAddressListItem).isHidden = wallet
471 + .walletAddresses.hiddenAddresses
472 .contains((addressList[i] as WalletAddressListItem).address);
473 }
474
475 for (var i = 0; i < addressList.length; i++) {
476 if (!(addressList[i] is WalletAddressListItem)) continue;
472 - (addressList[i] as WalletAddressListItem).isManual = wallet.walletAddresses.manualAddresses
477 + (addressList[i] as WalletAddressListItem).isManual = wallet
478 + .walletAddresses.manualAddresses
479 .contains((addressList[i] as WalletAddressListItem).address);
480 }
481
@@ -487,7 +493,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
493
494 Future<void> toggleHideAddress(WalletAddressListItem item) async {
495 if (item.isHidden) {
490 - wallet.walletAddresses.hiddenAddresses.removeWhere((element) => element == item.address);
496 + wallet.walletAddresses.hiddenAddresses
497 + .removeWhere((element) => element == item.address);
498 } else {
499 wallet.walletAddresses.hiddenAddresses.add(item.address);
500 }
@@ -512,57 +519,58 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
519
520 @computed
521 String get accountLabel {
515 - if (wallet.type == WalletType.monero) {
516 - return monero!.getCurrentAccount(wallet).label;
517 - }
518 -
519 - if (wallet.type == WalletType.wownero) {
520 - return wownero!.getCurrentAccount(wallet).label;
521 - }
522 -
523 - if (wallet.type == WalletType.haven) {
524 - return haven!.getCurrentAccount(wallet).label;
522 + switch (wallet.type) {
523 + case WalletType.monero:
524 + return monero!.getCurrentAccount(wallet).label;
525 + case WalletType.wownero:
526 + wownero!.getCurrentAccount(wallet).label;
527 + case WalletType.haven:
528 + return haven!.getCurrentAccount(wallet).label;
529 + default:
530 + return '';
531 }
526 -
532 return '';
533 }
534
535 @computed
531 - bool get hasAddressList =>
532 - wallet.type == WalletType.monero ||
533 - wallet.type == WalletType.wownero ||
534 - wallet.type == WalletType.haven ||
535 - wallet.type == WalletType.bitcoinCash ||
536 - wallet.type == WalletType.bitcoin ||
537 - wallet.type == WalletType.litecoin;
536 + bool get hasAddressList => [
537 + WalletType.monero,
538 + WalletType.wownero,
539 + WalletType.haven,
540 + WalletType.bitcoinCash,
541 + WalletType.bitcoin,
542 + WalletType.litecoin
543 + ].contains(wallet.type);
544
545 @computed
540 - bool get isElectrumWallet =>
541 - wallet.type == WalletType.bitcoin ||
542 - wallet.type == WalletType.litecoin ||
543 - wallet.type == WalletType.bitcoinCash;
546 + bool get isElectrumWallet => [
547 + WalletType.bitcoin,
548 + WalletType.litecoin,
549 + WalletType.bitcoinCash
550 + ].contains(wallet.type);
551
552 @computed
553 bool get isBalanceAvailable => isElectrumWallet;
554
555 @computed
556 bool get isReceivedAvailable =>
550 - wallet.type == WalletType.monero || wallet.type == WalletType.wownero;
557 + [WalletType.monero, WalletType.wownero].contains(wallet.type);
558
559 @computed
560 bool get isSilentPayments =>
554 - wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
561 + wallet.type == WalletType.bitcoin &&
562 + bitcoin!.hasSelectedSilentPayments(wallet);
563
564 @computed
565 bool get isAutoGenerateSubaddressEnabled =>
558 - _settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled &&
566 + _settingsStore.autoGenerateSubaddressStatus !=
567 + AutoGenerateSubaddressStatus.disabled &&
568 !isSilentPayments;
569
570 @computed
571 bool get showAddManualAddresses =>
572 !isAutoGenerateSubaddressEnabled ||
564 - wallet.type == WalletType.monero ||
565 - wallet.type == WalletType.wownero;
573 + [WalletType.monero, WalletType.wownero].contains(wallet.type);
574
575 List<ListItem> _baseItems;
576
@@ -574,7 +582,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
582
583 @action
584 Future<void> setAddressType(dynamic option) async {
577 - if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) {
585 + if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type)) {
586 await bitcoin!.setAddressType(wallet, option);
587 }
588 }
@@ -586,13 +594,15 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
594 _baseItems.add(WalletAddressHiddenListHeader());
595 }
596
589 - if (wallet.type == WalletType.monero ||
590 - wallet.type == WalletType.wownero ||
591 - wallet.type == WalletType.haven) {
597 + if ([
598 + WalletType.monero,
599 + WalletType.wownero,
600 + WalletType.haven,
601 + ].contains(wallet.type)) {
602 _baseItems.add(WalletAccountListHeader());
603 }
604
595 - if (wallet.type != WalletType.nano && wallet.type != WalletType.banano) {
605 + if (![WalletType.nano, WalletType.banano].contains(wallet.type)) {
606 _baseItems.add(WalletAddressListHeader());
607 }
608 if (wallet.isEnabledAutoGenerateSubaddress) {
@@ -603,11 +613,27 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
613 @action
614 void selectCurrency(Currency currency) {
615 selectedCurrency = currency;
616 +
617 + if (currency is FiatCurrency && _settingsStore.fiatCurrency != currency) {
618 + final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type);
619 +
620 + dev.log("Requesting Fiat rate for $cryptoCurrency-$currency");
621 + FiatConversionService.fetchPrice(
622 + crypto: cryptoCurrency,
623 + fiat: currency,
624 + torOnly: _settingsStore.fiatApiMode == FiatApiMode.torOnly,
625 + ).then((value) {
626 + dev.log("Received Fiat rate 1 $cryptoCurrency = $value $currency");
627 + _fiatRate = value;
628 + _convertAmountToCrypto();
629 + });
630 + }
631 }
632
633 @action
634 void changeAmount(String amount) {
635 this.amount = amount;
636 + this._rawAmount = amount;
637 if (selectedCurrency is FiatCurrency) {
638 _convertAmountToCrypto();
639 }
@@ -618,11 +644,20 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
644 searchText = text;
645 }
646
647 + @action
648 void _convertAmountToCrypto() {
649 final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type);
650 + final fiatRate =
651 + _fiatRate ?? (fiatConversionStore.prices[cryptoCurrency] ?? 0.0);
652 +
653 + if (fiatRate <= 0.0) {
654 + dev.log("Invalid Fiat Rate $fiatRate");
655 + amount = '';
656 + return;
657 + }
658 +
659 try {
624 - final crypto =
625 - double.parse(amount.replaceAll(',', '.')) / fiatConversionStore.prices[cryptoCurrency]!;
660 + final crypto = double.parse(_rawAmount.replaceAll(',', '.')) / fiatRate;
661 final cryptoAmountTmp = _cryptoNumberFormat.format(crypto);
662 if (amount != cryptoAmountTmp) {
663 amount = cryptoAmountTmp;