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;