CW-1186-Show-the-fiat-amount-for-UTXOs-in-coin-control (#2595)

* Add fiat amount display to unspent coins list * refactor fiat amount handling for unspent coins * Update lib/view_model/unspent_coins/unspent_coins_list_view_model.dart [skip ci] --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

Serhii committed Nov 22, 2025 at 00:05 UTC ccfe424b28a39abb4dd5f8e84789da5a3b5d98b8
4 files changed +82 -23
lib/di.dart
+2
@@ -1428,6 +1428,8 @@ Future<void> setup({
1428 return UnspentCoinsListViewModel(
1429 wallet: wallet!,
1430 unspentCoinsInfo: _unspentCoinsInfoSource,
1431 + fiatConversationStore: getIt.get<FiatConversionStore>(),
1432 + settingsStore: getIt.get<SettingsStore>(),
1433 coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.any,
1434 );
1435 });
lib/src/screens/unspent_coins/unspent_coins_list_page.dart
+26 -22
@@ -178,28 +178,32 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
178 itemBuilder: (_, int index) {
179 final item = unspentCoinsListViewModel.items[index];
180 return Observer(
181 - builder: (_) => GestureDetector(
182 - onTap: () => Navigator.of(context).pushNamed(
183 - Routes.unspentCoinsDetails,
184 - arguments: [item, unspentCoinsListViewModel],
185 - ),
186 - child: UnspentCoinsListItem(
187 - note: item.note,
188 - amount: item.amount,
189 - address: item.address,
190 - isSending: item.isSending,
191 - isFrozen: item.isFrozen,
192 - isChange: item.isChange,
193 - isSilentPayment: item.isSilentPayment,
194 - onCheckBoxTap: item.isFrozen
195 - ? null
196 - : () async {
197 - item.isSending = !item.isSending;
198 - await unspentCoinsListViewModel
199 - .saveUnspentCoinInfo(item);
200 - },
201 - ),
202 - ),
181 + builder: (_) {
182 + final fiatAmount = unspentCoinsListViewModel.fiatAmounts[item.hash] ?? '';
183 + return GestureDetector(
184 + onTap: () => Navigator.of(context).pushNamed(
185 + Routes.unspentCoinsDetails,
186 + arguments: [item, unspentCoinsListViewModel],
187 + ),
188 + child: UnspentCoinsListItem(
189 + note: item.note,
190 + amount: item.amount,
191 + fiatAmount: fiatAmount,
192 + address: item.address,
193 + isSending: item.isSending,
194 + isFrozen: item.isFrozen,
195 + isChange: item.isChange,
196 + isSilentPayment: item.isSilentPayment,
197 + onCheckBoxTap: item.isFrozen
198 + ? null
199 + : () async {
200 + item.isSending = !item.isSending;
201 + await unspentCoinsListViewModel
202 + .saveUnspentCoinInfo(item);
203 + },
204 + ),
205 + );
206 + }
207 );
208 },
209 ),
lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart
+18
@@ -7,6 +7,7 @@ class UnspentCoinsListItem extends StatelessWidget {
7 UnspentCoinsListItem({
8 required this.note,
9 required this.amount,
10 + required this.fiatAmount,
11 required this.address,
12 required this.isSending,
13 required this.isFrozen,
@@ -17,6 +18,7 @@ class UnspentCoinsListItem extends StatelessWidget {
18
19 final String note;
20 final String amount;
21 + final String fiatAmount;
22 final String address;
23 final bool isSending;
24 final bool isFrozen;
@@ -108,6 +110,22 @@ class UnspentCoinsListItem extends StatelessWidget {
110 ),
111 ],
112 ),
113 + if (fiatAmount.isNotEmpty)
114 + Row(
115 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
116 + crossAxisAlignment: CrossAxisAlignment.start,
117 + children: [
118 + AutoSizeText(
119 + fiatAmount,
120 + style: Theme.of(context).textTheme.bodyMedium!.copyWith(
121 + color: amountColor,
122 + fontSize: 1,
123 + fontWeight: FontWeight.w600,
124 + ),
125 + maxLines: 1,
126 + ),
127 + ],
128 + ),
129 Expanded(
130 child: Row(
131 crossAxisAlignment: CrossAxisAlignment.center,
lib/view_model/unspent_coins/unspent_coins_list_view_model.dart
+36 -1
@@ -1,5 +1,10 @@
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 +import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
3 +import 'package:cake_wallet/entities/fiat_api_mode.dart';
4 +import 'package:cake_wallet/entities/fiat_currency.dart';
5 import 'package:cake_wallet/monero/monero.dart';
6 +import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
7 +import 'package:cake_wallet/store/settings_store.dart';
8 import 'package:cake_wallet/utils/exception_handler.dart';
9 import 'package:cake_wallet/decred/decred.dart';
10 import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
@@ -27,13 +32,19 @@ abstract class UnspentCoinsListViewModelBase with Store {
32 required this.wallet,
33 required Box<UnspentCoinsInfo> unspentCoinsInfo,
34 this.coinTypeToSpendFrom = UnspentCoinType.any,
35 + required FiatConversionStore fiatConversationStore,
36 + required SettingsStore settingsStore,
37 }) : _unspentCoinsInfo = unspentCoinsInfo,
38 + _fiatConversationStore = fiatConversationStore,
39 + _settingsStore = settingsStore,
40 items = ObservableList<UnspentCoinsItem>(),
41 _originalState = {};
42
43 @observable
44 WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
45 final Box<UnspentCoinsInfo> _unspentCoinsInfo;
46 + final FiatConversionStore _fiatConversationStore;
47 + final SettingsStore _settingsStore;
48 final UnspentCoinType coinTypeToSpendFrom;
49
50 @observable
@@ -47,6 +58,30 @@ abstract class UnspentCoinsListViewModelBase with Store {
58 @computed
59 bool get isAllSelected => items.every((element) => element.isFrozen || element.isSending);
60
61 + @computed
62 + FiatCurrency get fiatCurrency => _settingsStore.fiatCurrency;
63 +
64 + @computed
65 + bool get isFiatDisabled => _settingsStore.fiatApiMode == FiatApiMode.disabled;
66 +
67 + @computed
68 + Map<String, String> get fiatAmounts {
69 +
70 + final currency = wallet.currency;
71 + final price = _fiatConversationStore.prices[currency];
72 + if (price == null || price == 0.0 || isFiatDisabled) return {};
73 +
74 + final result = <String, String>{};
75 + for (final item in items) {
76 + final formatted = formatAmountToString(item.value);
77 + final cryptoAmount = double.tryParse(formatted.replaceAll(',', '')) ?? 0.0;
78 + final fiatValue = price * cryptoAmount;
79 + result[item.hash] = fiatCurrency.title + ' ' + fiatValue.toStringAsFixed(2);
80 + }
81 +
82 + return result;
83 + }
84 +
85 Future<void> initialSetup() async {
86 await _updateUnspents();
87 _storeOriginalState();
@@ -176,7 +211,7 @@ abstract class UnspentCoinsListViewModelBase with Store {
211 .map((elem) {
212 try {
213 final existingItem = _unspentCoinsInfo.values
179 - .firstWhereOrNull((item) => item.walletId == wallet.id && item == elem);
214 + .firstWhereOrNull((item) => item.walletId == wallet.id && item == elem);;
215
216 if (existingItem == null) return null;
217