dev
dart 741 lines 32.1 KB
Raw
1 import "package:cake_wallet/core/amount_validator.dart";
2 import "package:cake_wallet/entities/qr_scanner.dart";
3 import "package:cake_wallet/generated/i18n.dart";
4 import "package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart";
5 import "package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_sheet.dart";
6 import "package:cake_wallet/new-ui/widgets/modern_button.dart";
7 import "package:cake_wallet/new-ui/widgets/send_page/fiat_amount_bar.dart";
8 import "package:cake_wallet/new-ui/widgets/send_page/send_memo_input.dart";
9 import "package:cake_wallet/new-ui/widgets/swap_page/refund_address_modal.dart";
10 import "package:cake_wallet/new-ui/widgets/swap_page/swap_address_selection_modal.dart";
11 import "package:cake_wallet/new-ui/widgets/swap_page/swap_source_selector.dart";
12 import "package:cake_wallet/src/widgets/cake_image_widget.dart";
13 import "package:cake_wallet/utils/decimal_input_formatter.dart";
14 import "package:cake_wallet/utils/permission_handler.dart";
15 import "package:cake_wallet/view_model/exchange/exchange_view_model.dart";
16 import "package:cake_wallet/view_model/wallet_switcher_view_model.dart";
17 import "package:cw_core/crypto_currency.dart";
18 import "package:cw_core/currencies_with_memo.dart";
19 import "package:cw_core/currency.dart";
20 import "package:cw_core/wallet_info.dart";
21 import "package:cw_core/wallet_type.dart";
22 import "package:flutter/material.dart";
23 import "package:flutter/services.dart";
24 import "package:flutter_mobx/flutter_mobx.dart";
25 import "package:mobx/mobx.dart" as mobx;
26 import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
27 import "package:permission_handler/permission_handler.dart";
28
29 class SwapAmountBox extends StatefulWidget {
30 SwapAmountBox({
31 required this.exchangeViewModel,
32 required this.isReceiverCard,
33 required this.walletSwitcherViewModel,
34 required this.currency,
35 required this.currencies,
36 required this.onCurrencySelected,
37 this.currencyValueValidator,
38 this.addressTextFieldValidator,
39 this.title = "",
40 this.hasRefundAddress = false,
41 this.hasAllAmount = false,
42 this.useBaseUnit = false,
43 this.allAmount,
44 this.onPushPasteButton,
45 this.onPushAddressBookButton,
46 this.filteredNetwork,
47 this.sourceSelectorMode = false,
48 this.walletName,
49 this.balanceByAsset,
50 this.useSingleNetworkLayout = false,
51 super.key,
52 });
53
54 final List<Currency> currencies;
55 final Function(Currency) onCurrencySelected;
56 final String title;
57 final bool isReceiverCard;
58 final Currency currency;
59 final bool useBaseUnit;
60 final bool hasRefundAddress;
61 final FormFieldValidator<String>? currencyValueValidator;
62 final FormFieldValidator<String>? addressTextFieldValidator;
63 final FormFieldValidator<String> allAmountValidator = AllAmountValidator();
64 final bool hasAllAmount;
65 final VoidCallback? allAmount;
66 final void Function(BuildContext context)? onPushPasteButton;
67 final void Function(BuildContext context)? onPushAddressBookButton;
68 final ExchangeViewModel exchangeViewModel;
69 final WalletSwitcherViewModel walletSwitcherViewModel;
70 final WalletType? filteredNetwork;
71 final bool sourceSelectorMode;
72 final String? walletName;
73 final Map<CryptoCurrency, CurrencyPickerBalance>? balanceByAsset;
74 final bool useSingleNetworkLayout;
75
76 @override
77 State<SwapAmountBox> createState() => SwapAmountBoxState();
78 }
79
80 class SwapAmountBoxState extends State<SwapAmountBox> {
81 final addressController = TextEditingController();
82 final amountController = TextEditingController();
83 final fiatAmountController = TextEditingController();
84 final amountFocusNode = FocusNode();
85 final memoController = TextEditingController();
86 final _amountFieldKey = GlobalKey<FormFieldState<String>>();
87 mobx.ReactionDisposer? _memoReactionDisposer;
88
89 @override
90 void initState() {
91 amountController
92 .addListener(() => _amountFieldKey.currentState?.didChange(amountController.text));
93
94 if (widget.isReceiverCard) {
95 memoController.text = widget.exchangeViewModel.receiveAddressExtraId;
96
97 memoController.addListener(() {
98 if (widget.exchangeViewModel.receiveAddressExtraId != memoController.text) {
99 widget.exchangeViewModel.receiveAddressExtraId = memoController.text;
100 }
101 });
102
103 _memoReactionDisposer =
104 mobx.reaction((_) => widget.exchangeViewModel.receiveAddressExtraId, (String value) {
105 if (memoController.text != value) {
106 memoController.text = value;
107 }
108 });
109 }
110
111 super.initState();
112 }
113
114 @override
115 void dispose() {
116 _memoReactionDisposer?.call();
117 memoController.dispose();
118 addressController.dispose();
119 amountController.dispose();
120 fiatAmountController.dispose();
121 amountFocusNode.dispose();
122 super.dispose();
123 }
124
125 bool _fiatInputMode = false;
126
127 /// Whether the amount field is currently taking fiat input, the page's
128 /// best-rate reaction needs this to know which controller holds the amount.
129 bool get fiatInputMode => _fiatInputMode;
130
131 CurrencyPickerBalance? _sourceBalance() {
132 final selected = widget.currency;
133 if (selected is! CryptoCurrency) {
134 return null;
135 }
136 return balanceForAsset(widget.balanceByAsset, selected);
137 }
138
139 @override
140 Widget build(BuildContext context) {
141 final currencyToShow = (widget.currency is CryptoCurrency)
142 ? widget.exchangeViewModel.amountParsingProxy
143 .getCryptoSymbol(widget.currency as CryptoCurrency)
144 : widget.currency.name.toUpperCase();
145
146 final chainIconPath = (widget.currency is CryptoCurrency)
147 ? _getCurrencyChainIconPath(widget.currency as CryptoCurrency)
148 : null;
149
150 final colors = Theme.of(context).colorScheme;
151
152 if (widget.sourceSelectorMode) {
153 return SwapSourceSelector(
154 currencyIconPath: widget.currency.iconPath ?? "",
155 currencyLabel: currencyToShow,
156 chainIconPath: chainIconPath,
157 walletName: widget.walletName,
158 availableBalance: _sourceBalance()?.amount.split(" ").first ?? "—",
159 onTap: _presentCurrencyPicker,
160 );
161 }
162
163 return Column(
164 spacing: 12,
165 crossAxisAlignment: CrossAxisAlignment.start,
166 children: [
167 if (widget.title.isNotEmpty)
168 Text(
169 widget.title,
170 style: TextStyle(
171 fontSize: 12,
172 fontWeight: FontWeight.w500,
173 color: Theme.of(context).colorScheme.onSurfaceVariant,
174 ),
175 ),
176 Container(
177 decoration: ShapeDecoration(
178 color: Theme.of(context).colorScheme.surfaceContainer,
179 shape: RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(24)),
180 ),
181 child: Padding(
182 padding: const EdgeInsets.all(16),
183 child: Column(
184 spacing: 12,
185 children: [
186 FormField<String>(
187 key: _amountFieldKey,
188 initialValue: amountController.text,
189 validator: _fiatInputMode ? null : widget.currencyValueValidator,
190 builder: (state) => Column(
191 crossAxisAlignment: CrossAxisAlignment.start,
192 children: [
193 GestureDetector(
194 behavior: HitTestBehavior.opaque,
195 onTap: amountFocusNode.requestFocus,
196 child: Row(
197 mainAxisAlignment: MainAxisAlignment.spaceBetween,
198 spacing: 4,
199 children: [
200 Expanded(
201 child: Row(
202 spacing: 4,
203 crossAxisAlignment: CrossAxisAlignment.center,
204 children: [
205 Flexible(
206 child: IntrinsicWidth(
207 child: Observer(
208 builder: (_) {
209 final showFetching = !widget.isReceiverCard &&
210 widget.exchangeViewModel.isFixedRateMode &&
211 widget.exchangeViewModel.receiveAmount.isNotEmpty &&
212 widget.exchangeViewModel.depositAmount.isEmpty;
213 return TextField(
214 keyboardType: TextInputType.numberWithOptions(
215 signed: false,
216 decimal: !widget.useBaseUnit,
217 ),
218 onChanged: state.didChange,
219 controller: _fiatInputMode
220 ? fiatAmountController
221 : amountController,
222 focusNode: amountFocusNode,
223 style: TextStyle(
224 fontSize: 28,
225 color: Theme.of(context).colorScheme.onSurface,
226 fontWeight: FontWeight.w400,
227 ),
228 decoration: InputDecoration(
229 contentPadding: EdgeInsets.zero,
230 isDense: true,
231 hintText: showFetching ? S.of(context).fetching : "0",
232 fillColor: Colors.transparent,
233 hoverColor: Colors.transparent,
234 focusedBorder: InputBorder.none,
235 enabledBorder: InputBorder.none,
236 ),
237 inputFormatters: <TextInputFormatter>[
238 DecimalInputFormatter(
239 maxDecimals: widget.useBaseUnit
240 ? 0
241 : widget.currency.decimals,
242 ),
243 ],
244 );
245 },
246 ),
247 ),
248 ),
249 if (_fiatInputMode)
250 Center(
251 child: Text(
252 widget.exchangeViewModel.fiat.title,
253 style: TextStyle(
254 fontSize: 16,
255 color: Theme.of(context).colorScheme.onSurfaceVariant,
256 ),
257 ),
258 ),
259 ],
260 ),
261 ),
262 GestureDetector(
263 onTap: _presentCurrencyPicker,
264 child: Container(
265 decoration: BoxDecoration(
266 color: Theme.of(context).colorScheme.surface,
267 borderRadius: BorderRadius.circular(999999),
268 ),
269 child: Padding(
270 padding:
271 const EdgeInsets.only(top: 4, bottom: 4, left: 4, right: 4),
272 child: Row(
273 mainAxisAlignment: MainAxisAlignment.center,
274 children: [
275 CakeImageWidget(
276 imageUrl: widget.currency.iconPath ?? "",
277 width: 28,
278 height: 28,
279 ),
280 const SizedBox(width: 10),
281 Text(
282 currencyToShow,
283 textAlign: TextAlign.center,
284 ),
285 if (chainIconPath != null && chainIconPath.isNotEmpty) ...[
286 const SizedBox(width: 4),
287 CakeImageWidget(
288 imageUrl: chainIconPath,
289 width: 12,
290 height: 12,
291 colorFilter: ColorFilter.mode(
292 Theme.of(context).colorScheme.onSurfaceVariant,
293 BlendMode.srcIn,
294 ),
295 ),
296 const SizedBox(width: 6),
297 ] else
298 const SizedBox(width: 10),
299 Container(
300 width: 16,
301 height: 16,
302 decoration: BoxDecoration(
303 borderRadius: BorderRadius.circular(9999999999),
304 color: Theme.of(context).colorScheme.surfaceContainerHigh,
305 ),
306 child: Padding(
307 padding: const EdgeInsets.all(4),
308 child: RotatedBox(
309 quarterTurns: 2,
310 child: CakeImageWidget(
311 imageUrl: "assets/new-ui/dropdown_arrow.svg",
312 width: 4,
313 height: 4,
314 colorFilter: ColorFilter.mode(
315 Theme.of(context).colorScheme.primary,
316 BlendMode.srcIn,
317 ),
318 ),
319 ),
320 ),
321 ),
322 const SizedBox(width: 4),
323 ],
324 ),
325 ),
326 ),
327 ),
328 ],
329 ),
330 ),
331 if (state.hasError)
332 Padding(
333 padding: const EdgeInsets.only(top: 6),
334 child: Semantics(
335 container: true,
336 liveRegion: true,
337 label: "${S.of(context).amount}${state.errorText!}",
338 excludeSemantics: true,
339 child: Text(
340 state.errorText!,
341 style: TextStyle(
342 fontSize: 12,
343 color: Theme.of(context).colorScheme.error,
344 ),
345 ),
346 ),
347 ),
348 ],
349 ),
350 ),
351 Observer(
352 builder: (_) {
353 final hasAllAmount = !(widget.isReceiverCard ||
354 widget.exchangeViewModel.isSendFromExternal ||
355 !widget.exchangeViewModel.hasAllAmount);
356
357 return FiatAmountBar(
358 foregroundElementColor: Theme.of(context).colorScheme.surfaceContainerHigh,
359 textColor: Theme.of(context).colorScheme.onSurfaceVariant,
360 allAmountColor: hasAllAmount ? null : Colors.transparent,
361 allAmountTextColor:
362 hasAllAmount ? null : Theme.of(context).colorScheme.onSurfaceVariant,
363 fiatInputMode: _fiatInputMode,
364 allAmount: !hasAllAmount
365 ? widget.isReceiverCard
366 ? null
367 : widget.exchangeViewModel.balanceDisplay
368 : widget.exchangeViewModel.depositAvailableAmount,
369 onSwitchButtonPressed: () {
370 setState(() => _fiatInputMode = !_fiatInputMode);
371 if (_fiatInputMode) {
372 updateFiatAmount();
373 } else {
374 amountController.text = widget.isReceiverCard
375 ? widget.exchangeViewModel.receiveAmount
376 : widget.exchangeViewModel.depositAmount;
377 }
378 },
379 onAllButtonPressed: () {
380 setState(() {
381 _fiatInputMode = false;
382 });
383 widget.allAmount?.call();
384 },
385 cryptoAmount: widget.isReceiverCard
386 ? widget.exchangeViewModel.roundedReceiveAmount(6)
387 : widget.exchangeViewModel.roundedDepositAmount(6),
388 fiatAmount: widget.isReceiverCard
389 ? widget.exchangeViewModel.roundedReceiveAmountFiat(6)
390 : widget.exchangeViewModel.roundedDepositAmountFiat(6),
391 cryptoCurrencySymbol: currencyToShow,
392 fiatCurrencySymbol: widget.exchangeViewModel.fiat.symbol,
393 );
394 },
395 ),
396 Observer(
397 builder: (_) {
398 final addressEmpty = (widget.isReceiverCard &&
399 widget.exchangeViewModel.receiveAddress.isEmpty) ||
400 (!widget.isReceiverCard && widget.exchangeViewModel.depositAddress.isEmpty);
401 final addressPickerText = widget.isReceiverCard
402 ? (addressEmpty ? S.of(context).select_receiver : S.of(context).to)
403 : S.of(context).from;
404 final addressDescription = widget.isReceiverCard
405 ? widget.exchangeViewModel.receiveAddressDisplayName ??
406 _middleTruncate(widget.exchangeViewModel.receiveAddress, 8, 8)
407 : widget.exchangeViewModel.isSendFromExternal
408 ? S.of(context).external
409 : widget.exchangeViewModel.wallet.name;
410 return Row(
411 spacing: 8,
412 children: [
413 Flexible(
414 child: GestureDetector(
415 onTap: _presentWalletPicker,
416 child: Container(
417 height: 36,
418 decoration: BoxDecoration(
419 color: (addressEmpty && widget.isReceiverCard)
420 ? Theme.of(context).colorScheme.primary
421 : Theme.of(context).colorScheme.surfaceContainerHigh,
422 borderRadius: BorderRadius.circular(9999),
423 ),
424 child: Padding(
425 padding: const EdgeInsets.symmetric(horizontal: 12),
426 child: Observer(
427 builder: (_) => Row(
428 mainAxisAlignment: MainAxisAlignment.spaceBetween,
429 children: [
430 Row(
431 spacing: 8,
432 mainAxisSize: MainAxisSize.max,
433 children: [
434 Text(
435 addressPickerText,
436 style: TextStyle(
437 fontSize: 12,
438 fontWeight: FontWeight.w500,
439 color: (addressEmpty && widget.isReceiverCard)
440 ? Theme.of(context).colorScheme.onPrimary
441 : Theme.of(context).colorScheme.onSurface,
442 ),
443 ),
444 Text(
445 addressDescription,
446 style: TextStyle(
447 fontSize: 12,
448 fontWeight: FontWeight.w500,
449 color: Theme.of(context).colorScheme.primary,
450 ),
451 ),
452 ],
453 ),
454 RotatedBox(
455 quarterTurns: 2,
456 child: CakeImageWidget(
457 imageUrl: "assets/new-ui/dropdown_arrow.svg",
458 colorFilter: ColorFilter.mode(
459 (addressEmpty && widget.isReceiverCard)
460 ? Theme.of(context).colorScheme.onPrimary
461 : Theme.of(context).colorScheme.onSurface,
462 BlendMode.srcIn,
463 ),
464 ),
465 ),
466 ],
467 ),
468 ),
469 ),
470 ),
471 ),
472 ),
473 if (!widget.isReceiverCard &&
474 widget.exchangeViewModel.isSendFromExternal &&
475 widget.exchangeViewModel.depositAddress.isEmpty)
476 ModernButton.svg(
477 svgPath: "assets/new-ui/refund_address.svg",
478 onPressed: askForRefundAddress,
479 size: 36,
480 iconSize: 18,
481 backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
482 semanticLabel: S.of(context).refund_address,
483 ),
484 if (widget.isReceiverCard &&
485 widget.exchangeViewModel.receiveAddress.isEmpty) ...[
486 ModernButton.svg(
487 svgPath: "assets/new-ui/paste.svg",
488 onPressed: () => widget.onPushPasteButton?.call(context),
489 size: 36,
490 iconSize: 20,
491 backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
492 semanticLabel: S.of(context).paste,
493 ),
494 ModernButton.svg(
495 svgPath: "assets/new-ui/scan.svg",
496 onPressed: () => _presentQRScanner(context),
497 size: 36,
498 iconSize: 20,
499 backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
500 semanticLabel: S.of(context).scan,
501 ),
502 ],
503 ],
504 );
505 },
506 ),
507 if (widget.isReceiverCard)
508 Observer(
509 builder: (_) {
510 final selected = widget.exchangeViewModel.receiveCurrency;
511 final labelType = memoLabelTypeFor(selected);
512 if (labelType == null) {
513 return const SizedBox.shrink();
514 }
515
516 final isDestinationTag = labelType == MemoLabelType.destinationTag;
517 final hint = isDestinationTag
518 ? S.of(context).destination_tag_optional
519 : S.of(context).memo_optional;
520 final disclaimer = isDestinationTag
521 ? S.of(context).destination_tag_swap_disclaimer
522 : S.of(context).memo_swap_disclaimer;
523
524 return NewSendMemoInput(
525 memoController: memoController,
526 maxMemoLength: isDestinationTag ? 20 : 256,
527 memoLength: memoController.text.length,
528 hintText: hint,
529 disclaimerText: disclaimer,
530 );
531 },
532 ),
533 ],
534 ),
535 ),
536 ),
537 ],
538 );
539 }
540
541 void changeAddress({required String address}) {
542 setState(() => addressController.text = _normalizeAddressFormat(address));
543 }
544
545 void changeAmount({required String amount}) {
546 setState(() => amountController.text = amount);
547 }
548
549 String _normalizeAddressFormat(String address) {
550 if (address.startsWith("bitcoincash:")) {
551 address = address.substring(12);
552 }
553 return address;
554 }
555
556 void _presentCurrencyPicker() {
557 final rawCurrencies = widget.isReceiverCard
558 ? widget.exchangeViewModel.receiveCurrencies
559 : widget.exchangeViewModel.depositCurrencies;
560 final currencies = rawCurrencies.whereType<CryptoCurrency>().toList();
561 final restrictToCurrentBalances = !widget.isReceiverCard &&
562 widget.balanceByAsset != null &&
563 widget.balanceByAsset!.isNotEmpty;
564 if (!restrictToCurrentBalances) {
565 appendEvmDefaultTokens(currencies);
566 }
567 if (widget.exchangeViewModel.wallet.type == WalletType.bitcoin) {
568 currencies.sort((a, b) {
569 if (a == CryptoCurrency.btcln) {
570 return -1;
571 }
572 if (b == CryptoCurrency.btcln) {
573 return 1;
574 }
575 return 0;
576 });
577 }
578
579 List<CryptoCurrency> items = currencies;
580 if (restrictToCurrentBalances) {
581 final allowed = {
582 for (final asset in widget.balanceByAsset!.keys) asset.title.toUpperCase(),
583 };
584 items = currencies.where((c) => allowed.contains(c.title.toUpperCase())).toList();
585 }
586
587 if (widget.isReceiverCard && widget.filteredNetwork != null) {
588 final network = widget.filteredNetwork!;
589 items = items.where((c) => cryptoCurrencyOrTokenToWalletType(c) == network).toList();
590 }
591
592 if (items.length <= 1) {
593 return;
594 }
595
596 final selected = widget.isReceiverCard
597 ? widget.exchangeViewModel.receiveCurrency
598 : widget.exchangeViewModel.depositCurrency;
599
600 CurrencyPickerSheet.show(
601 context: context,
602 args: CurrencyPickerArgs(
603 items: items,
604 selected: selected,
605 filterByNetwork: widget.filteredNetwork,
606 balanceByAsset: widget.balanceByAsset,
607 useSingleNetworkLayout: widget.useSingleNetworkLayout,
608 recentsSource: RecentsSource.trades,
609 onSelected: widget.onCurrencySelected,
610 symbolResolver: widget.exchangeViewModel.amountParsingProxy.getCryptoSymbol,
611 ),
612 );
613 }
614
615 Future<void> _presentQRScanner(BuildContext context) async {
616 final isCameraPermissionGranted =
617 await PermissionHandler.checkPermission(Permission.camera, context);
618 if (!isCameraPermissionGranted) {
619 return;
620 }
621 final code = await presentQRScanner(context);
622 if (code == null) {
623 return;
624 }
625 if (code.isEmpty) {
626 return;
627 }
628
629 try {
630 final uri = Uri.parse(code);
631 widget.exchangeViewModel.receiveAddress = uri.path;
632 } catch (_) {}
633 }
634
635 void _presentWalletPicker() async {
636 final res = await showMaterialModalBottomSheet(
637 context: context,
638 backgroundColor: Colors.transparent,
639 builder: (context) => ConstrainedBox(
640 constraints: BoxConstraints(
641 maxHeight: MediaQuery.of(context).size.height * 0.6,
642 ),
643 child: SwapAddressSelectionModal(
644 isSelectingReceiver: widget.isReceiverCard,
645 exchangeViewModel: widget.exchangeViewModel,
646 ),
647 ),
648 );
649 if (res != null && res is SwapAddressSelectionResult) {
650 if (widget.isReceiverCard) {
651 widget.exchangeViewModel.selectedAddressBookWallet = res.walletInfo;
652 widget.exchangeViewModel.receiveAddress = res.address!;
653 if (res.walletInfo?.name != null) {
654 if (res.accountName != null) {
655 widget.exchangeViewModel.receiveAddressDisplayName =
656 "${res.walletInfo!.name}${res.accountName}";
657 } else {
658 widget.exchangeViewModel.receiveAddressDisplayName = res.walletInfo!.name;
659 }
660 }
661 } else if (res.address == null || res.address!.isEmpty) {
662 widget.exchangeViewModel.isSendFromExternal = true;
663 askForRefundAddress();
664 } else {
665 widget.exchangeViewModel.isSendFromExternal = false;
666 switchToDepositWallet(res.walletInfo!.name);
667 }
668 }
669 }
670
671 void switchToDepositWallet(String walletName) async {
672 final walletType = cryptoCurrencyOrTokenToWalletType(widget.exchangeViewModel.depositCurrency);
673 if (walletType == null) {
674 return;
675 }
676 final wallet = await WalletInfo.get(walletName, walletType);
677 if (wallet == null) {
678 return;
679 }
680 widget.exchangeViewModel.depositAddress = wallet.address;
681 addressController.text = _normalizeAddressFormat(wallet.address);
682 widget.walletSwitcherViewModel.selectWallet(wallet);
683 await widget.walletSwitcherViewModel.switchToSelectedWallet();
684 }
685
686 void askForRefundAddress() async {
687 final refundAddress = await showModalBottomSheet(
688 context: context,
689 isScrollControlled: true,
690 backgroundColor: Colors.transparent,
691 builder: (context) => RefundAddressModal(
692 selectedCurrency: widget.exchangeViewModel.depositCurrency,
693 isFromWalletSelection: true,
694 ),
695 );
696 if (refundAddress != null && refundAddress is String) {
697 widget.exchangeViewModel.depositAddress = refundAddress;
698 } else {
699 widget.exchangeViewModel.depositAddress = "";
700 }
701 }
702
703 void updateFiatAmount() {
704 final newText = widget.isReceiverCard
705 ? widget.exchangeViewModel.receiveAmountFiat
706 : widget.exchangeViewModel.depositAmountFiat;
707
708 if (double.tryParse(fiatAmountController.text) != double.tryParse(newText)) {
709 if (newText == "0.00") {
710 fiatAmountController.text = "";
711 } else {
712 fiatAmountController.text =
713 newText.replaceAll(RegExp(r"(?<=\.\d*)0+$"), "").replaceAll(RegExp(r"\.$"), "");
714 }
715 }
716 }
717
718 String _middleTruncate(String s, int head, int tail) {
719 if (s.length <= head + tail + 3) {
720 return s;
721 }
722 return s.substring(0, head) + "..." + s.substring(s.length - tail);
723 }
724
725 String? _getCurrencyChainIconPath(CryptoCurrency curr) {
726 try {
727 if (curr.chainIconPath != null) {
728 return curr.chainIconPath!;
729 }
730
731 if (curr.tag != null) {
732 final currencyFromTag = CryptoCurrency.fromString(curr.tag!);
733
734 if (currencyFromTag.chainIconPath != null) {
735 return currencyFromTag.chainIconPath!;
736 }
737 }
738 } catch (_) {}
739 return null;
740 }
741 }