changed amount validation and (hopefully) fixed preview quote button

This commit is contained in:
julian 2023-01-30 13:08:44 -06:00
parent 1e5f624c8b
commit 47fb446b2e

View file

@ -108,6 +108,37 @@ class _BuyFormState extends ConsumerState<BuyForm> {
static Decimal maxCrypto = Decimal.parse((10000.00000000).toString()); static Decimal maxCrypto = Decimal.parse((10000.00000000).toString());
static String boundedCryptoTicker = ''; static String boundedCryptoTicker = '';
String _amountOutOfRangeErrorString = "";
void validateAmount() {
if (_buyAmountController.text.isEmpty) {
setState(() {
_amountOutOfRangeErrorString = "";
});
return;
}
final value = Decimal.tryParse(_buyAmountController.text);
if (value == null) {
setState(() {
_amountOutOfRangeErrorString = "Invalid amount";
});
} else if (value > maxFiat) {
setState(() {
_amountOutOfRangeErrorString =
"Maximum amount: ${maxFiat.toStringAsFixed(2)}";
});
} else if (value < minFiat) {
setState(() {
_amountOutOfRangeErrorString =
"Minimum amount: ${minFiat.toStringAsFixed(2)}";
});
} else {
setState(() {
_amountOutOfRangeErrorString = "";
});
}
}
void selectCrypto() async { void selectCrypto() async {
if (ref.read(simplexProvider).supportedCryptos.isEmpty) { if (ref.read(simplexProvider).supportedCryptos.isEmpty) {
bool shouldPop = false; bool shouldPop = false;
@ -355,11 +386,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
} }
} }
String? _fetchIconUrlFromTicker(String? ticker) { // String? _fetchIconUrlFromTicker(String? ticker) {
if (ticker == null) return null; // if (ticker == null) return null;
//
return null; // return null;
} // }
bool isStackCoin(String? ticker) { bool isStackCoin(String? ticker) {
if (ticker == null) return false; if (ticker == null) return false;
@ -373,13 +404,15 @@ class _BuyFormState extends ConsumerState<BuyForm> {
} }
Widget? getIconForTicker(String ticker) { Widget? getIconForTicker(String ticker) {
String? iconAsset = /*isStackCoin(ticker) String iconAsset = /*isStackCoin(ticker)
?*/ ?*/
Assets.svg.iconFor(coin: coinFromTickerCaseInsensitive(ticker)); Assets.svg.iconFor(coin: coinFromTickerCaseInsensitive(ticker));
// : Assets.svg.buyIconFor(ticker); // : Assets.svg.buyIconFor(ticker);
return (iconAsset != null) // return (iconAsset != null)
? SvgPicture.asset(iconAsset, height: 20, width: 20) // ? SvgPicture.asset(iconAsset, height: 20, width: 20)
: null; // : null;
return SvgPicture.asset(iconAsset, height: 20, width: 20);
} }
Future<void> previewQuote(SimplexQuote quote) async { Future<void> previewQuote(SimplexQuote quote) async {
@ -500,7 +533,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
String errorMessage = "${quoteResponse.exception?.errorMessage}"; String errorMessage = "${quoteResponse.exception?.errorMessage}";
if (errorMessage.contains('must be between')) { if (errorMessage.contains('must be between')) {
errorMessage = errorMessage.substring( errorMessage = errorMessage.substring(
(errorMessage.indexOf('getQuote exception: ') ?? 19) + 20, errorMessage.indexOf('getQuote exception: ') + 20,
errorMessage.indexOf(", value: null")); errorMessage.indexOf(", value: null"));
_BuyFormState.boundedCryptoTicker = errorMessage.substring( _BuyFormState.boundedCryptoTicker = errorMessage.substring(
errorMessage.indexOf('The ') + 4, errorMessage.indexOf('The ') + 4,
@ -848,7 +881,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
.textFieldDefaultBG, .textFieldDefaultBG,
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 12.0, top: 12.0, right: 12.0, bottom: 12.0), left: 12.0,
top: 12.0,
right: 12.0,
bottom: 12.0,
),
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Container( Container(
@ -874,7 +911,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
width: 8, width: 8,
), ),
Text( Text(
"${selectedFiat?.ticker ?? 'ERR'}", selectedFiat?.ticker ?? 'ERR',
style: STextStyles.largeMedium14(context), style: STextStyles.largeMedium14(context),
), ),
const SizedBox( const SizedBox(
@ -882,7 +919,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
), ),
Expanded( Expanded(
child: Text( child: Text(
"${selectedFiat?.name ?? 'Error'}", selectedFiat?.name ?? 'Error',
style: STextStyles.largeMedium14(context), style: STextStyles.largeMedium14(context),
), ),
), ),
@ -934,10 +971,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
color: Theme.of(context).extension<StackColors>()!.textDark, color: Theme.of(context).extension<StackColors>()!.textDark,
), ),
key: const Key("buyAmountInputFieldTextFieldKey"), key: const Key("buyAmountInputFieldTextFieldKey"),
controller: _buyAmountController controller: _buyAmountController,
..text = _BuyFormState.buyWithFiat // note: setting the text value here will set it every time this widget rebuilds
? _BuyFormState.minFiat.toStringAsFixed(2) ?? '50.00' // ..text = _BuyFormState.buyWithFiat
: _BuyFormState.minCrypto.toStringAsFixed(8), // ? _BuyFormState.minFiat.toStringAsFixed(2) ?? '50.00'
// : _BuyFormState.minCrypto.toStringAsFixed(8),
focusNode: _buyAmountFocusNode, focusNode: _buyAmountFocusNode,
keyboardType: Util.isDesktop keyboardType: Util.isDesktop
? null ? null
@ -946,7 +984,10 @@ class _BuyFormState extends ConsumerState<BuyForm> {
decimal: true, decimal: true,
), ),
textAlign: TextAlign.left, textAlign: TextAlign.left,
inputFormatters: [NumericalRangeFormatter()], // inputFormatters: [NumericalRangeFormatter()],
onChanged: (_) {
validateAmount();
},
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: const EdgeInsets.only( contentPadding: const EdgeInsets.only(
// top: 22, // top: 22,
@ -981,8 +1022,8 @@ class _BuyFormState extends ConsumerState<BuyForm> {
), ),
child: Text( child: Text(
format.simpleCurrencySymbol( format.simpleCurrencySymbol(
selectedFiat?.ticker ?? selectedFiat?.ticker.toUpperCase() ??
"ERR".toUpperCase()), "ERR"),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: STextStyles.smallMed12(context).copyWith( style: STextStyles.smallMed12(context).copyWith(
color: Theme.of(context) color: Theme.of(context)
@ -1019,18 +1060,20 @@ class _BuyFormState extends ConsumerState<BuyForm> {
key: const Key( key: const Key(
"buyViewClearAmountFieldButtonKey"), "buyViewClearAmountFieldButtonKey"),
onTap: () { onTap: () {
if (_BuyFormState.buyWithFiat) { // if (_BuyFormState.buyWithFiat) {
_buyAmountController.text = _BuyFormState // _buyAmountController.text = _BuyFormState
.minFiat // .minFiat
.toStringAsFixed(2); // .toStringAsFixed(2);
} else { // } else {
if (selectedCrypto?.ticker == // if (selectedCrypto?.ticker ==
_BuyFormState.boundedCryptoTicker) { // _BuyFormState.boundedCryptoTicker) {
_buyAmountController.text = _BuyFormState // _buyAmountController.text = _BuyFormState
.minCrypto // .minCrypto
.toStringAsFixed(8); // .toStringAsFixed(8);
} // }
} // }
_buyAmountController.text = "";
validateAmount();
}, },
child: const XIcon(), child: const XIcon(),
) )
@ -1047,7 +1090,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
_buyAmountController.text = _buyAmountController.text =
amountString.toString(); amountString.toString();
setState(() {}); validateAmount();
} }
}, },
child: _buyAmountController.text.isEmpty child: _buyAmountController.text.isEmpty
@ -1060,6 +1103,14 @@ class _BuyFormState extends ConsumerState<BuyForm> {
), ),
), ),
), ),
SizedBox(
height: isDesktop ? 10 : 4,
),
if (_amountOutOfRangeErrorString.isNotEmpty)
Text(
_amountOutOfRangeErrorString,
style: STextStyles.errorSmall(context),
),
SizedBox( SizedBox(
height: isDesktop ? 20 : 12, height: isDesktop ? 20 : 12,
), ),
@ -1167,7 +1218,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
_receiveAddressController.text = ""; _receiveAddressController.text = "";
_address = ""; _address = "";
setState(() { setState(() {
_addressToggleFlag = true; _addressToggleFlag = false;
}); });
}, },
child: const XIcon(), child: const XIcon(),
@ -1338,27 +1389,15 @@ class _BuyFormState extends ConsumerState<BuyForm> {
SizedBox( SizedBox(
height: isDesktop ? 20 : 12, height: isDesktop ? 20 : 12,
), ),
MouseRegion( PrimaryButton(
cursor: SystemMouseCursors.click, buttonHeight: isDesktop ? ButtonHeight.l : null,
child: GestureDetector( enabled: _addressToggleFlag &&
onTap: () { _amountOutOfRangeErrorString.isEmpty &&
if (_receiveAddressController.text.isNotEmpty && _buyAmountController.text.isNotEmpty,
_buyAmountController.text.isNotEmpty) { onPressed: () {
previewQuote(quote); previewQuote(quote);
} },
}, label: "Preview quote",
child: PrimaryButton(
buttonHeight: isDesktop ? ButtonHeight.l : null,
enabled: _receiveAddressController.text.isNotEmpty &&
_buyAmountController.text.isNotEmpty,
onPressed: () {
if (_receiveAddressController.text.isNotEmpty &&
_buyAmountController.text.isNotEmpty) {
previewQuote(quote);
}
},
label: "Preview quote",
)),
), ),
], ],
), ),
@ -1367,51 +1406,53 @@ class _BuyFormState extends ConsumerState<BuyForm> {
} }
} }
// See https://stackoverflow.com/a/68072967 // might need this again in the future
class NumericalRangeFormatter extends TextInputFormatter {
NumericalRangeFormatter();
@override // // See https://stackoverflow.com/a/68072967
TextEditingValue formatEditUpdate( // class NumericalRangeFormatter extends TextInputFormatter {
TextEditingValue oldValue, // NumericalRangeFormatter();
TextEditingValue newValue, //
) { // @override
TextSelection newSelection = newValue.selection; // TextEditingValue formatEditUpdate(
String newVal = _BuyFormState.buyWithFiat // TextEditingValue oldValue,
? Decimal.parse(newValue.text).toStringAsFixed(2) // TextEditingValue newValue,
: Decimal.parse(newValue.text).toStringAsFixed(8); // ) {
if (newValue.text == '') { // TextSelection newSelection = newValue.selection;
return newValue; // String newVal = _BuyFormState.buyWithFiat
} else { // ? Decimal.parse(newValue.text).toStringAsFixed(2)
if (_BuyFormState.buyWithFiat) { // : Decimal.parse(newValue.text).toStringAsFixed(8);
if (Decimal.parse(newValue.text) < _BuyFormState.minFiat) { // if (newValue.text == '') {
newVal = _BuyFormState.minFiat.toStringAsFixed(2); // return newValue;
// _BuyFormState._buyAmountController.selection = // } else {
// TextSelection.collapsed( // if (_BuyFormState.buyWithFiat) {
// offset: _BuyFormState.buyWithFiat // if (Decimal.parse(newValue.text) < _BuyFormState.minFiat) {
// ? _BuyFormState._buyAmountController.text.length - 2 // newVal = _BuyFormState.minFiat.toStringAsFixed(2);
// : _BuyFormState._buyAmountController.text.length - 8); // // _BuyFormState._buyAmountController.selection =
} else if (Decimal.parse(newValue.text) > _BuyFormState.maxFiat) { // // TextSelection.collapsed(
newVal = _BuyFormState.maxFiat.toStringAsFixed(2); // // offset: _BuyFormState.buyWithFiat
} // // ? _BuyFormState._buyAmountController.text.length - 2
} else if (!_BuyFormState.buyWithFiat && // // : _BuyFormState._buyAmountController.text.length - 8);
_BuyFormState.selectedCrypto?.ticker == // } else if (Decimal.parse(newValue.text) > _BuyFormState.maxFiat) {
_BuyFormState.boundedCryptoTicker) { // newVal = _BuyFormState.maxFiat.toStringAsFixed(2);
if (Decimal.parse(newValue.text) < _BuyFormState.minCrypto) { // }
newVal = _BuyFormState.minCrypto.toStringAsFixed(8); // } else if (!_BuyFormState.buyWithFiat &&
} else if (Decimal.parse(newValue.text) > _BuyFormState.maxCrypto) { // _BuyFormState.selectedCrypto?.ticker ==
newVal = _BuyFormState.maxCrypto.toStringAsFixed(8); // _BuyFormState.boundedCryptoTicker) {
} // if (Decimal.parse(newValue.text) < _BuyFormState.minCrypto) {
} // newVal = _BuyFormState.minCrypto.toStringAsFixed(8);
} // } else if (Decimal.parse(newValue.text) > _BuyFormState.maxCrypto) {
// newVal = _BuyFormState.maxCrypto.toStringAsFixed(8);
final regexString = _BuyFormState.buyWithFiat // }
? r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$' // }
: r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$'; // }
//
// return RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') // final regexString = _BuyFormState.buyWithFiat
return RegExp(regexString).hasMatch(newVal) // ? r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$'
? TextEditingValue(text: newVal, selection: newSelection) // : r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$';
: oldValue; //
} // // return RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
} // return RegExp(regexString).hasMatch(newVal)
// ? TextEditingValue(text: newVal, selection: newSelection)
// : oldValue;
// }
// }