1
-import 'dart:developer';
2
-
1
import 'package:cake_wallet/core/amount_validator.dart';
2
import 'package:cake_wallet/core/auth_service.dart';
3
import 'package:cake_wallet/di.dart';
9
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
10
import 'package:cake_wallet/utils/address_formatter.dart';
11
import 'package:cake_wallet/utils/debounce.dart';
12
+import 'package:cw_core/crypto_amount_format.dart';
13
14
import 'package:flutter/material.dart';
15
+import 'package:flutter/services.dart';
16
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
17
import 'package:cake_wallet/src/widgets/primary_button.dart';
18
import 'package:cw_core/wallet_type.dart';
76
late TextEditingController _amountController;
77
late TextEditingController _amountFiatController;
78
late TextEditingController _addressController;
79
- late TextEditingController _noteController;
79
80
final _receiveAmountDebounce = Debounce(Duration(milliseconds: 500));
81
final _receiveAmountFiatDebounce = Debounce(Duration(milliseconds: 500));
82
final FocusNode _amountFocus = FocusNode();
83
final FocusNode _amountFiatFocus = FocusNode();
84
final FocusNode _addressFocus = FocusNode();
86
- final FocusNode _noteFocus = FocusNode();
85
final _formKey = GlobalKey<FormState>();
86
87
ReactionDisposer? _receiveAmountReaction;
92
93
bool _showingFailureDialog = false;
94
bool _showingSwapDetailsDialog = false;
95
+ bool _isUserTypingFiat = false;
96
97
@override
98
void initState() {
105
: '0.00');
106
_amountFiatController =
107
TextEditingController(text: widget.exchangeViewModel.receiveAmountFiatFormatted);
109
- _noteController =
110
- TextEditingController(text: widget.paymentFlowResult.addressDetectionResult?.note ?? '');
108
109
WidgetsBinding.instance.addPostFrameCallback(
110
(_) => _setUpReactions(
120
_amountController.dispose();
121
_amountFiatController.dispose();
122
_addressController.dispose();
126
- _noteController.dispose();
123
_amountFocus.dispose();
124
_amountFiatFocus.dispose();
125
_addressFocus.dispose();
130
- _noteFocus.dispose();
126
_receiveAmountReaction?.call();
127
_receiveAddressReaction?.call();
128
_tradeStateReaction?.call();
171
hintText: 'Amount (${detectedCurrencyName})',
172
focusNode: _amountFocus,
173
controller: _amountController,
179
- keyboardType: TextInputType.numberWithOptions(decimal: true),
174
+ keyboardType: TextInputType.numberWithOptions(decimal: true, signed: false),
175
+ inputFormatters: [
176
+ FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]')),
177
+ ],
178
+ onChanged: (value) {
179
+ final sanitized = value
180
+ .replaceAll(',', '.')
181
+ .withMaxDecimals(widget.exchangeViewModel.receiveCurrency.decimals);
182
+ if (sanitized != _amountController.text) {
183
+ // Update text while preserving a sane cursor position to avoid auto-selection
184
+ _amountController.value = _amountController.value.copyWith(
185
+ text: sanitized,
186
+ selection: TextSelection.collapsed(offset: sanitized.length),
187
+ composing: TextRange.empty,
188
+ );
189
+ }
190
+ },
191
validator: (value) {
192
return AmountValidator(
193
isAutovalidate: true,
271
focusNode: _addressFocus,
272
controller: _addressController,
273
),
263
- const SizedBox(height: 8),
264
- SwapConfirmationTextfield(
265
- maxLines: 1,
266
- key: ValueKey('swap_confirmation_bottomsheet_note_textfield_key'),
267
- hintText: 'Transaction Note',
268
- focusNode: _noteFocus,
269
- controller: _noteController,
270
- ),
274
SizedBox(height: 8),
275
Center(
276
child: Text(
306
307
_receiveAmountFiatReaction =
308
reaction((_) => exchangeViewModel.receiveAmountFiatFormatted, (String amount) {
306
- if (_amountFiatController.text != amount) {
309
+ if (!_isUserTypingFiat && _amountFiatController.text != amount) {
310
_amountFiatController.text = amount;
311
}
312
});
384
385
_amountFiatController.addListener(() {
386
if (_amountFiatController.text != exchangeViewModel.receiveAmountFiatFormatted) {
387
+ _isUserTypingFiat = true;
388
_receiveAmountFiatDebounce.run(() {
389
exchangeViewModel.loadLimits();
390
exchangeViewModel.setReceiveAmountFromFiat(fiatAmount: _amountFiatController.text);
391
+ // Reset the flag after the debounced operation completes
392
+ Future.delayed(Duration(milliseconds: 100), () {
393
+ _isUserTypingFiat = false;
394
+ });
395
});
396
}
397
});
402
}
403
});
404
405
+ _amountFiatFocus.addListener(() {
406
+ if (_amountFiatFocus.hasFocus) {
407
+ _isUserTypingFiat = true;
408
+ } else {
409
+ // Reset the flag when user stops focusing on the field
410
+ Future.delayed(Duration(milliseconds: 200), () {
411
+ _isUserTypingFiat = false;
412
+ });
413
+ }
414
+ });
415
+
416
exchangeViewModel.receiveCurrency = walletTypeToCryptoCurrency(paymentFlowResult.walletType!);
417
await exchangeViewModel.fetchFiatPrice(exchangeViewModel.receiveCurrency);
418
436
this.maxLines = 1,
437
this.validator,
438
this.keyboardType,
439
+ this.onChanged,
440
+ this.inputFormatters,
441
});
442
443
final FocusNode focusNode;
448
final int maxLines;
449
final String? Function(String?)? validator;
450
final TextInputType? keyboardType;
430
-
451
+ final void Function(String)? onChanged;
452
+ final List<TextInputFormatter>? inputFormatters;
453
@override
454
Widget build(BuildContext context) {
455
return Container(
494
maxLines: maxLines,
495
validator: validator,
496
keyboardType: keyboardType,
497
+ onChanged: onChanged,
498
+ inputFormatters: inputFormatters,
499
),
500
],
501
),