Pay Anything RC Fixes (#2511)

* feat: Swap flow for Pay Anything. [WIP] * feat: Pay Anything Swap Flow [WIP] * feat: Pay anything swap flow [WIP] - Add fiat values for send and receive amounts to swap details confirmation bottomsheet * feat: Pay anything swap flow [WIP] - Switch exchangeviewmodel back to factory - Expand address box to fill width * Handle second swipe to swap cta * Add fiat for receive amount * Scope Observers for bottomsheets and add validator * fix: Repeated instances issue for exchangeVM * fix: Estimated fee and balance not correctly updating after wallet switching occurs * fix: Available balance not updating properly after wallet switching * fix: Available balance and fee rate not displaying after wallet is switched in pay anything flow * fixes: Pay anything related RC fixes - Recieve fiat amount showing as 0.0 - ID overlapping when there's enough space - receive amount changing even in fixed rate mode - Invalid double error for setReceiveFiatAmount

David Adegoke committed Sep 9, 2025 at 01:36 UTC c61215683c0cef52ee42e54a196fcfd6b4c851ca
4 files changed +23 -14
lib/src/widgets/bottom_sheet/swap_confirmation_bottom_sheet.dart
+6
@@ -180,6 +180,7 @@ class SwapConfirmationContentState extends State<SwapConfirmationContent> {
180 hintText: 'Amount (${detectedCurrencyName})',
181 focusNode: _amountFocus,
182 controller: _amountController,
183 + keyboardType: TextInputType.numberWithOptions(decimal: true),
184 validator: (value) {
185 return AmountValidator(
186 isAutovalidate: true,
@@ -252,6 +253,7 @@ class SwapConfirmationContentState extends State<SwapConfirmationContent> {
253 hintText: 'Amount (${widget.exchangeViewModel.fiat.title})',
254 focusNode: _amountFiatFocus,
255 controller: _amountFiatController,
256 + keyboardType: TextInputType.numberWithOptions(decimal: true),
257 ),
258 const SizedBox(height: 8),
259 SwapConfirmationTextfield(
@@ -418,6 +420,7 @@ class SwapConfirmationTextfield extends StatelessWidget {
420 this.isAddress = false,
421 this.maxLines = 1,
422 this.validator,
423 + this.keyboardType,
424 });
425
426 final FocusNode focusNode;
@@ -427,6 +430,8 @@ class SwapConfirmationTextfield extends StatelessWidget {
430 final bool isAddress;
431 final int maxLines;
432 final String? Function(String?)? validator;
433 + final TextInputType? keyboardType;
434 +
435 @override
436 Widget build(BuildContext context) {
437 return Container(
@@ -470,6 +475,7 @@ class SwapConfirmationTextfield extends StatelessWidget {
475 controller: controller,
476 maxLines: maxLines,
477 validator: validator,
478 + keyboardType: keyboardType,
479 ),
480 ],
481 ),
lib/src/widgets/bottom_sheet/swap_details_bottom_sheet.dart
+6 -7
@@ -10,7 +10,6 @@ import 'package:cake_wallet/src/widgets/standard_slide_button_widget.dart';
10 import 'package:cake_wallet/themes/core/material_base_theme.dart';
11 import 'package:cake_wallet/core/execution_state.dart';
12 import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
13 -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/alert_with_one_action.dart';
15 import 'package:cake_wallet/utils/show_pop_up.dart';
@@ -19,7 +18,6 @@ import 'package:cw_core/utils/print_verbose.dart';
18 import 'package:flutter/services.dart';
19 import 'package:mobx/mobx.dart';
20 import 'package:flutter_mobx/flutter_mobx.dart';
22 -import 'package:cake_wallet/reactions/wallet_connect.dart';
21
22 class SwapDetailsBottomSheet extends StatefulWidget {
23 SwapDetailsBottomSheet({
@@ -289,7 +287,8 @@ class _SwapDetailsContent extends StatelessWidget {
287 _SwapDetailsTile(
288 label: 'You Get',
289 value: '${trade.receiveAmount ?? '0'} ${trade.to?.title ?? ''}',
292 - valueFiatFormatted: exchangeTradeViewModel.receiveAmountFiatFormatted,
290 + valueFiatFormatted: exchangeTradeViewModel
291 + .getReceiveAmountFiatFormatted(trade.receiveAmount ?? '0.0'),
292 ),
293 const SizedBox(height: 8),
294 Container(
@@ -330,6 +329,7 @@ class _SwapDetailsContent extends StatelessWidget {
329 borderRadius: BorderRadius.circular(12),
330 ),
331 child: Row(
332 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
333 children: [
334 Row(
335 children: [
@@ -346,28 +346,27 @@ class _SwapDetailsContent extends StatelessWidget {
346 fontSize: 16,
347 ),
348 ),
349 + const SizedBox(width: 8),
350 ],
351 ),
351 - Spacer(),
352 Expanded(
353 - // flex: 4,
353 child: GestureDetector(
354 onTap: () {
355 Clipboard.setData(ClipboardData(text: trade.id));
356 showBar<void>(context, S.of(context).copied_to_clipboard);
357 },
358 child: Row(
359 + mainAxisAlignment: MainAxisAlignment.end,
360 children: [
361 Text(
362 'ID: ',
363 style: Theme.of(context).textTheme.bodyMedium!.copyWith(fontSize: 18),
364 ),
365 - Expanded(
365 + Flexible(
366 child: Text(
367 trade.id,
368 style:
369 Theme.of(context).textTheme.bodyMedium!.copyWith(fontSize: 18),
370 - softWrap: true,
370 ),
371 ),
372 ],
lib/view_model/exchange/exchange_trade_view_model.dart
+4 -4
@@ -127,16 +127,16 @@ abstract class ExchangeTradeViewModelBase with Store {
127 @computed
128 bool get isFiatDisabled => feesViewModel.isFiatDisabled;
129
130 - @computed
131 - String get receiveAmountFiatFormatted {
130 + @action
131 + String getReceiveAmountFiatFormatted(String receiveAmount) {
132 var amount = '0.00';
133 try {
134 - if (trade.receiveAmount?.isNotEmpty ?? false) {
134 + if (receiveAmount.isNotEmpty) {
135 if (fiatConversionStore.prices[trade.to] == null) return '';
136
137 amount = calculateFiatAmount(
138 price: fiatConversionStore.prices[trade.to]!,
139 - cryptoAmount: trade.receiveAmount,
139 + cryptoAmount: receiveAmount,
140 );
141 }
142 } catch (_) {
lib/view_model/exchange/exchange_view_model.dart
+7 -3
@@ -24,7 +24,6 @@ import 'package:cake_wallet/exchange/provider/chainflip_exchange_provider.dart';
24 import 'package:cake_wallet/exchange/provider/changenow_exchange_provider.dart';
25 import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
26 import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
27 -import 'package:cake_wallet/exchange/provider/letsexchange_exchange_provider.dart';
27 import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
28 import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
29 import 'package:cake_wallet/exchange/provider/swaptrade_exchange_provider.dart';
@@ -449,8 +448,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
448
449 @action
450 void setReceiveAmountFromFiat({required String fiatAmount}) {
452 - final crypto = double.parse(fiatAmount.replaceAll(',', '.')) /
453 - fiatConversionStore.prices[receiveCurrency]!;
451 + final _enteredAmount = double.tryParse(fiatAmount.replaceAll(',', '.')) ?? 0.0;
452 + final crypto = _enteredAmount / fiatConversionStore.prices[receiveCurrency]!;
453 final receiveAmountTmp = _cryptoNumberFormat.format(crypto);
454 if (receiveAmount != receiveAmountTmp) {
455 changeReceiveAmount(amount: receiveAmountTmp);
@@ -467,6 +466,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
466 return;
467 }
468
469 + /// For fixed-rate transactions, we don't want to recalculate receive amount
470 + /// as it should remain exactly what the user set
471 + if (isFixedRateMode) return;
472 +
473 final _enteredAmount = double.tryParse(amount.replaceAll(',', '.')) ?? 0;
474
475 /// in case the best rate was not calculated yet
@@ -475,6 +478,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
478
479 await calculateBestRate();
480 }
481 +
482 _cryptoNumberFormat.maximumFractionDigits = receiveMaxDigits;
483
484 receiveAmount = _cryptoNumberFormat