Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 67 additions & 13 deletions lib/pages/buy_view/buy_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ import '../../services/buy/buy_response.dart';
import '../../services/buy/simplex/simplex_api.dart';
import '../../themes/stack_colors.dart';
import '../../utilities/address_utils.dart';
import '../../utilities/amount/amount.dart';
import '../../utilities/amount/amount_formatter.dart';
import '../../utilities/amount/amount_input_formatter.dart';
import '../../utilities/assets.dart';
import '../../utilities/barcode_scanner_interface.dart';
import '../../utilities/clipboard_interface.dart';
Expand Down Expand Up @@ -80,8 +83,6 @@ class BuyForm extends ConsumerStatefulWidget {
}

class _BuyFormState extends ConsumerState<BuyForm> {
late final CryptoCurrency? coin;

late final ClipboardInterface clipboard;

late final TextEditingController _receiveAddressController;
Expand Down Expand Up @@ -124,6 +125,45 @@ class _BuyFormState extends ConsumerState<BuyForm> {
// static Decimal maxCrypto = Decimal.parse((10000.00000000).toString());
// static String boundedCryptoTicker = '';

/// The currency a crypto amount is denominated in: the crypto being bought,
/// which need not be the wallet's coin and is the only source on desktop,
/// where [BuyForm] is always built without one.
CryptoCurrency? get _selectedCryptoCurrency {
final ticker = selectedCrypto?.ticker ?? widget.coin?.ticker;
return ticker == null ? null : AppConfig.getCryptoCurrencyForTicker(ticker);
}

/// Parse a buy amount string using the active locale's decimal and group
/// separators. Fiat amounts are parsed via [Amount.tryParseFiatString];
/// crypto amounts go through [pAmountFormatter] so that full crypto
/// precision is preserved.
Decimal? _tryParseBuyAmount(String value) {
if (buyWithFiat) {
return Amount.tryParseFiatString(
value,
locale: ref.read(localeServiceChangeNotifierProvider).locale,
)?.decimal;
}

final cc = _selectedCryptoCurrency;
if (cc == null) {
// Fail closed: the fiat parser truncates to 2 decimals, which would
// silently zero a small crypto amount.
return null;
}

return ref.read(pAmountFormatter(cc)).tryParse(value)?.decimal;
}

AmountInputFormatter _buyAmountInputFormatter() {
final cc = buyWithFiat ? null : _selectedCryptoCurrency;
return AmountInputFormatter(
decimals: buyWithFiat ? 2 : cc?.fractionDigits ?? 8,
locale: ref.read(localeServiceChangeNotifierProvider).locale,
unit: cc == null ? null : ref.read(pAmountUnit(cc)),
);
}

String _amountOutOfRangeErrorString = "";
void validateAmount() {
if (_buyAmountController.text.isEmpty) {
Expand All @@ -133,7 +173,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
return;
}

final value = Decimal.tryParse(_buyAmountController.text);
final value = _tryParseBuyAmount(_buyAmountController.text);
if (value == null) {
setState(() {
_amountOutOfRangeErrorString = "Invalid amount";
Expand Down Expand Up @@ -396,6 +436,12 @@ class _BuyFormState extends ConsumerState<BuyForm> {
// }

Future<void> previewQuote(SimplexQuote quote) async {
final amount = _tryParseBuyAmount(_buyAmountController.text);
if (amount == null) {
validateAmount();
return;
}

bool shouldPop = false;
unawaited(
showDialog(
Expand All @@ -414,11 +460,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
crypto: selectedCrypto!,
fiat: selectedFiat!,
youPayFiatPrice: buyWithFiat
? Decimal.parse(_buyAmountController.text)
? amount
: Decimal.parse("100"), // dummy value
youReceiveCryptoAmount: buyWithFiat
? Decimal.parse("0.000420282") // dummy value
: Decimal.parse(_buyAmountController.text), // Ternary for this
: amount,
id: "id", // anything; we get an ID back
receivingAddress: _receiveAddressController.text,
buyWithFiat: buyWithFiat,
Expand Down Expand Up @@ -1013,7 +1059,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
decimal: true,
),
textAlign: TextAlign.left,
// inputFormatters: [NumericalRangeFormatter()],
inputFormatters: [_buyAmountInputFormatter()],
onChanged: (_) {
validateAmount();
},
Expand Down Expand Up @@ -1123,13 +1169,21 @@ class _BuyFormState extends ConsumerState<BuyForm> {
final ClipboardData? data = await clipboard
.getData(Clipboard.kTextPlain);

final amountString = Decimal.tryParse(
data?.text ?? "",
);
if (amountString != null) {
_buyAmountController.text = amountString
.toString();

final pasted = data?.text ?? "";
final formatted = _buyAmountInputFormatter()
.formatEditUpdate(
TextEditingValue.empty,
TextEditingValue(
text: pasted,
selection: TextSelection.collapsed(
offset: pasted.length,
),
),
);
if (formatted.text.isNotEmpty &&
_tryParseBuyAmount(formatted.text) !=
null) {
_buyAmountController.value = formatted;
validateAmount();
}
},
Expand Down
9 changes: 5 additions & 4 deletions lib/pages/send_view/send_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1277,10 +1277,11 @@ class _SendViewState extends ConsumerState<SendView> {
builder: (_) => TransactionFeeSelectionSheet(
walletId: walletId,
amount:
(Decimal.tryParse(cryptoAmountController.text) ??
ref.watch(pSendAmount)?.decimal ??
Decimal.zero)
.toAmount(fractionDigits: coin.fractionDigits),
ref
.read(pAmountFormatter(coin))
.tryParse(cryptoAmountController.text) ??
ref.watch(pSendAmount) ??
Amount.zeroWith(fractionDigits: coin.fractionDigits),
updateChosen: (String fee) {
if (fee == "custom") {
if (!isCustomFee.value) {
Expand Down
33 changes: 24 additions & 9 deletions lib/pages/send_view/sol_token_send_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,11 @@ class _SolTokenSendViewState extends ConsumerState<SolTokenSendView> {
final tokenWallet = ref.read(pCurrentSolanaTokenWallet);
if (tokenWallet == null) return;

final cryptoAmount = Decimal.tryParse(
cryptoAmountController.text,
)?.toAmount(fractionDigits: tokenWallet.tokenDecimals);
final cryptoAmount = ref
.read(pAmountFormatter(Solana(CryptoCurrencyNetwork.main)))
.tryParse(cryptoAmountController.text)
?.decimal
.toAmount(fractionDigits: tokenWallet.tokenDecimals);
if (cryptoAmount != null) {
_amountToSend = cryptoAmount;
if (_cachedAmountToSend != null &&
Expand Down Expand Up @@ -1258,16 +1260,29 @@ class _SolTokenSendViewState extends ConsumerState<SolTokenSendView> {
walletId: walletId,
isToken: true,
amount:
(Decimal.tryParse(
cryptoAmountController
.text,
) ??
Decimal.zero)
ref
.read(
pAmountFormatter(
Solana(
CryptoCurrencyNetwork
.main,
),
),
)
.tryParse(
cryptoAmountController
.text,
)
?.decimal
.toAmount(
fractionDigits:
tokenWallet
.tokenDecimals,
),
) ??
Amount.zeroWith(
fractionDigits: tokenWallet
.tokenDecimals,
),
updateChosen: (String fee) {
setState(() {
_calculateFeesFuture = Future(
Expand Down
24 changes: 14 additions & 10 deletions lib/pages/send_view/token_send_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1200,16 +1200,20 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
walletId: walletId,
isToken: true,
amount:
(Decimal.tryParse(
cryptoAmountController
.text,
) ??
Decimal.zero)
.toAmount(
fractionDigits:
tokenContract
.decimals,
),
ref
.read(
pAmountFormatter(coin),
)
.tryParse(
cryptoAmountController
.text,
tokenContract:
tokenContract,
) ??
Amount.zeroWith(
fractionDigits:
tokenContract.decimals,
),
updateChosen: (String fee) {
if (fee == "custom") {
if (!isCustomFee.value) {
Expand Down
Loading
Loading