Cw 262 better handle user exchange amount below minimum or maximum trade size (#868)

* CW-262-Better-handle-user-exchange-amount-below-minimum-or-maximum-trade-size * fix: App should compute conversion even if it's not within the limits

Adegoke David committed Apr 20, 2023 at 02:13 UTC 7b91b0e938c6f5c3707ecf86b33dd3ecd3b1e4d5
26 files changed +185 -12
lib/core/amount_validator.dart
+98 -2
@@ -3,17 +3,45 @@ import 'package:cake_wallet/generated/i18n.dart';
3 import 'package:cw_core/crypto_currency.dart';
4
5 class AmountValidator extends TextValidator {
6 - AmountValidator({required CryptoCurrency currency, bool isAutovalidate = false}) {
6 + AmountValidator({
7 + required CryptoCurrency currency,
8 + bool isAutovalidate = false,
9 + String? minValue,
10 + String? maxValue,
11 + }) {
12 symbolsAmountValidator =
13 SymbolsAmountValidator(isAutovalidate: isAutovalidate);
14 decimalAmountValidator = DecimalAmountValidator(currency: currency,isAutovalidate: isAutovalidate);
15 +
16 + amountMinValidator = AmountMinValidator(
17 + minValue: minValue,
18 + isAutovalidate: isAutovalidate,
19 + );
20 +
21 + amountMaxValidator = AmountMaxValidator(
22 + maxValue: maxValue,
23 + isAutovalidate: isAutovalidate,
24 + );
25 }
26
27 + late final AmountMinValidator amountMinValidator;
28 +
29 + late final AmountMaxValidator amountMaxValidator;
30 +
31 late final SymbolsAmountValidator symbolsAmountValidator;
32
33 late final DecimalAmountValidator decimalAmountValidator;
34
16 - String? call(String? value) => symbolsAmountValidator(value) ?? decimalAmountValidator(value);
35 + String? call(String? value) {
36 + //* Validate for Text(length, symbols, decimals etc)
37 +
38 + final textValidation = symbolsAmountValidator(value) ?? decimalAmountValidator(value);
39 +
40 + //* Validate for Comparison(Value greater than min and less than )
41 + final comparisonValidation = amountMinValidator(value) ?? amountMaxValidator(value);
42 +
43 + return textValidation ?? comparisonValidation;
44 + }
45 }
46
47 class SymbolsAmountValidator extends TextValidator {
@@ -57,3 +85,71 @@ class AllAmountValidator extends TextValidator {
85 minLength: 0,
86 maxLength: 0);
87 }
88 +
89 +class AmountMinValidator extends Validator<String> {
90 + final String? minValue;
91 + final bool isAutovalidate;
92 +
93 + AmountMinValidator({
94 + this.minValue,
95 + required this.isAutovalidate,
96 + }) : super(errorMessage: S.current.error_text_input_below_minimum_limit);
97 +
98 + @override
99 + bool isValid(String? value) {
100 + if (value == null || value.isEmpty) {
101 + return isAutovalidate ? true : false;
102 + }
103 +
104 + if (minValue == null || minValue == "null") {
105 + return true;
106 + }
107 +
108 + final valueInDouble = parseToDouble(value);
109 + final minInDouble = parseToDouble(minValue ?? '');
110 +
111 + if (valueInDouble == null || minInDouble == null) {
112 + return false;
113 + }
114 +
115 + return valueInDouble > minInDouble;
116 + }
117 +
118 + double? parseToDouble(String value) {
119 + final data = double.tryParse(value.replaceAll(',', '.'));
120 + return data;
121 + }
122 +}
123 +
124 +class AmountMaxValidator extends Validator<String> {
125 + final String? maxValue;
126 + final bool isAutovalidate;
127 +
128 + AmountMaxValidator({
129 + this.maxValue,
130 + required this.isAutovalidate,
131 + }) : super(errorMessage: S.current.error_text_input_above_maximum_limit);
132 +
133 + @override
134 + bool isValid(String? value) {
135 + if (value == null || value.isEmpty) {
136 + return isAutovalidate ? true : false;
137 + }
138 +
139 + if (maxValue == null || maxValue == "null") {
140 + return true;
141 + }
142 +
143 + final valueInDouble = parseToDouble(value);
144 + final maxInDouble = parseToDouble(maxValue ?? '');
145 +
146 + if (valueInDouble == null || maxInDouble == null) {
147 + return false;
148 + }
149 + return valueInDouble < maxInDouble;
150 + }
151 +
152 + double? parseToDouble(String value) {
153 + return double.tryParse(value.replaceAll(',', '.'));
154 + }
155 +}
lib/src/screens/exchange/exchange_page.dart
+22 -8
@@ -456,8 +456,7 @@ class ExchangePage extends BasePage {
456 depositAmountController.addListener(() {
457 if (depositAmountController.text != exchangeViewModel.depositAmount) {
458 _depositAmountDebounce.run(() {
459 - exchangeViewModel.changeDepositAmount(
460 - amount: depositAmountController.text);
459 + exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
460 exchangeViewModel.isReceiveAmountEntered = false;
461 });
462 }
@@ -469,8 +468,7 @@ class ExchangePage extends BasePage {
468 receiveAmountController.addListener(() {
469 if (receiveAmountController.text != exchangeViewModel.receiveAmount) {
470 _receiveAmountDebounce.run(() {
472 - exchangeViewModel.changeReceiveAmount(
473 - amount: receiveAmountController.text);
471 + exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text);
472 exchangeViewModel.isReceiveAmountEntered = true;
473 });
474 }
@@ -626,8 +624,16 @@ class ExchangePage extends BasePage {
624 currencyButtonColor: Colors.transparent,
625 addressButtonsColor: Theme.of(context).focusColor!,
626 borderColor: Theme.of(context).primaryTextTheme!.bodyText1!.color!,
629 - currencyValueValidator:
630 - AmountValidator(currency: exchangeViewModel.depositCurrency),
627 + currencyValueValidator: (value) {
628 + return !exchangeViewModel.isFixedRateMode
629 + ? AmountValidator(
630 + isAutovalidate: true,
631 + currency: exchangeViewModel.depositCurrency,
632 + minValue: exchangeViewModel.limits.min.toString(),
633 + maxValue: exchangeViewModel.limits.max.toString(),
634 + ).call(value)
635 + : null;
636 + },
637 addressTextFieldValidator:
638 AddressValidator(type: exchangeViewModel.depositCurrency),
639 onPushPasteButton: (context) async {
@@ -668,8 +674,16 @@ class ExchangePage extends BasePage {
674 addressButtonsColor: Theme.of(context).focusColor!,
675 borderColor:
676 Theme.of(context).primaryTextTheme!.bodyText1!.decorationColor!,
671 - currencyValueValidator:
672 - AmountValidator(currency: exchangeViewModel.receiveCurrency),
677 + currencyValueValidator: (value) {
678 + return exchangeViewModel.isFixedRateMode
679 + ? AmountValidator(
680 + isAutovalidate: true,
681 + currency: exchangeViewModel.receiveCurrency,
682 + minValue: exchangeViewModel.limits.min.toString(),
683 + maxValue: exchangeViewModel.limits.max.toString(),
684 + ).call(value)
685 + : null;
686 + },
687 addressTextFieldValidator:
688 AddressValidator(type: exchangeViewModel.receiveCurrency),
689 onPushPasteButton: (context) async {
lib/view_model/exchange/exchange_view_model.dart
+19 -2
@@ -198,6 +198,9 @@ abstract class ExchangeViewModelBase with Store {
198 @observable
199 bool isFixedRateMode;
200
201 + @observable
202 + Limits limits;
203 +
204 @computed
205 SyncStatus get status => wallet.syncStatus;
206
@@ -241,8 +244,6 @@ abstract class ExchangeViewModelBase with Store {
244
245 List<CryptoCurrency> depositCurrencies;
246
244 - Limits limits;
245 -
247 NumberFormat _cryptoNumberFormat;
248
249 final SettingsStore _settingsStore;
@@ -320,6 +321,22 @@ abstract class ExchangeViewModelBase with Store {
321 .replaceAll(RegExp('\\,'), '');
322 }
323
324 + bool checkIfInputMeetsMinOrMaxCondition(String input) {
325 + final _enteredAmount = double.tryParse(input.replaceAll(',', '.')) ?? 0;
326 + double minLimit = limits.min ?? 0;
327 + double? maxLimit = limits.max;
328 +
329 + if (_enteredAmount < minLimit) {
330 + return false;
331 + }
332 +
333 + if (maxLimit != null && _enteredAmount > maxLimit) {
334 + return false;
335 + }
336 +
337 + return true;
338 + }
339 +
340 Future<void> _calculateBestRate() async {
341 final amount = double.tryParse(isFixedRateMode ? receiveAmount : depositAmount) ?? 1;
342
res/values/strings_ar.arb
+2
@@ -697,5 +697,7 @@
697 "onion_link": "رابط البصل",
698 "settings": "إعدادات",
699 "sell_monero_com_alert_content": "بيع Monero غير مدعوم حتى الآن",
700 + "error_text_input_below_minimum_limit":" المبلغ أقل من الحد الأدنى",
701 + "error_text_input_above_maximum_limit":"المبلغ أكبر من الحد الأقصى",
702 "show_market_place": "إظهار السوق"
703 }
res/values/strings_bg.arb
+2
@@ -698,5 +698,7 @@
698 "clearnet_link": "Clearnet връзка",
699 "onion_link": "Лукова връзка",
700 "sell_monero_com_alert_content": "Продажбата на Monero все още не се поддържа",
701 + "error_text_input_below_minimum_limit" : "Сумата е по-малко от минималната",
702 + "error_text_input_above_maximum_limit" : "Сумата надвишава максималната",
703 "show_market_place":"Покажи пазар"
704 }
res/values/strings_cs.arb
+2
@@ -698,5 +698,7 @@
698 "clearnet_link": "Odkaz na Clearnet",
699 "onion_link": "Cibulový odkaz",
700 "sell_monero_com_alert_content": "Prodej Monero zatím není podporován",
701 + "error_text_input_below_minimum_limit" : "Částka je menší než minimální hodnota",
702 + "error_text_input_above_maximum_limit" : "Částka je větší než maximální hodnota",
703 "show_market_place": "Zobrazit trh"
704 }
res/values/strings_de.arb
+2
@@ -699,5 +699,7 @@
699 "onion_link": "Zwiebel-Link",
700 "settings": "Einstellungen",
701 "sell_monero_com_alert_content": "Der Verkauf von Monero wird noch nicht unterstützt",
702 + "error_text_input_below_minimum_limit" : "Menge ist unter dem Minimum",
703 + "error_text_input_above_maximum_limit" : "Menge ist über dem Maximum",
704 "show_market_place": "Marktplatz anzeigen"
705 }
res/values/strings_en.arb
+2
@@ -699,5 +699,7 @@
699 "edit_node": "Edit Node",
700 "settings": "Settings",
701 "sell_monero_com_alert_content": "Selling Monero is not supported yet",
702 + "error_text_input_below_minimum_limit" : "Amount is less than the minimum",
703 + "error_text_input_above_maximum_limit" : "Amount is more than the maximum",
704 "show_market_place" :"Show Marketplace"
705 }
res/values/strings_es.arb
+2
@@ -699,5 +699,7 @@
699 "onion_link": "Enlace de cebolla",
700 "settings": "Configuraciones",
701 "sell_monero_com_alert_content": "Aún no se admite la venta de Monero",
702 + "error_text_input_below_minimum_limit" : "La cantidad es menos que mínima",
703 + "error_text_input_above_maximum_limit" : "La cantidad es más que el máximo",
704 "show_market_place": "Mostrar mercado"
705 }
res/values/strings_fr.arb
+2
@@ -699,5 +699,7 @@
699 "settings": "Paramètres",
700 "onion_link": "Lien .onion",
701 "sell_monero_com_alert_content": "La vente de Monero n'est pas encore prise en charge",
702 + "error_text_input_below_minimum_limit" : "Le montant est inférieur au minimum",
703 + "error_text_input_above_maximum_limit" : "Le montant est supérieur au maximum",
704 "show_market_place" :"Afficher la place de marché"
705 }
res/values/strings_hi.arb
+2
@@ -699,5 +699,7 @@
699 "onion_link": "प्याज का लिंक",
700 "settings": "समायोजन",
701 "sell_monero_com_alert_content": "मोनेरो बेचना अभी तक समर्थित नहीं है",
702 + "error_text_input_below_minimum_limit" : "राशि न्यूनतम से कम है",
703 + "error_text_input_above_maximum_limit" : "राशि अधिकतम से अधिक है",
704 "show_market_place":"बाज़ार दिखाएँ"
705 }
res/values/strings_hr.arb
+2
@@ -699,5 +699,7 @@
699 "onion_link": "Poveznica luka",
700 "settings": "Postavke",
701 "sell_monero_com_alert_content": "Prodaja Monera još nije podržana",
702 + "error_text_input_below_minimum_limit" : "Iznos je manji od minimalnog",
703 + "error_text_input_above_maximum_limit" : "Iznos je veći od maskimalnog",
704 "show_market_place" : "Prikaži tržište"
705 }
res/values/strings_id.arb
+2
@@ -680,5 +680,7 @@
680 "clearnet_link": "Tautan clearnet",
681 "onion_link": "Tautan bawang",
682 "sell_monero_com_alert_content": "Menjual Monero belum didukung",
683 + "error_text_input_below_minimum_limit" : "Jumlah kurang dari minimal",
684 + "error_text_input_above_maximum_limit" : "Jumlah lebih dari maksimal",
685 "show_market_place": "Tampilkan Pasar"
686 }
res/values/strings_it.arb
+2
@@ -699,5 +699,7 @@
699 "onion_link": "Collegamento a cipolla",
700 "settings": "Impostazioni",
701 "sell_monero_com_alert_content": "La vendita di Monero non è ancora supportata",
702 + "error_text_input_below_minimum_limit" : "L'ammontare è inferiore al minimo",
703 + "error_text_input_above_maximum_limit" : "L'ammontare è superiore al massimo",
704 "show_market_place":"Mostra mercato"
705 }
res/values/strings_ja.arb
+2
@@ -699,5 +699,7 @@
699 "onion_link": "オニオンリンク",
700 "settings": "設定",
701 "sell_monero_com_alert_content": "モネロの販売はまだサポートされていません",
702 + "error_text_input_below_minimum_limit" : "金額は最小額より少ない",
703 + "error_text_input_above_maximum_limit" : "金額は最大値を超えています",
704 "show_market_place":"マーケットプレイスを表示"
705 }
res/values/strings_ko.arb
+2
@@ -699,5 +699,7 @@
699 "onion_link": "양파 링크",
700 "settings": "설정",
701 "sell_monero_com_alert_content": "지원되지 않습니다.",
702 + "error_text_input_below_minimum_limit" : "금액이 최소보다 적습니다.",
703 + "error_text_input_above_maximum_limit" : "금액이 최대 값보다 많습니다.",
704 "show_market_place":"마켓플레이스 표시"
705 }
res/values/strings_my.arb
+2
@@ -699,5 +699,7 @@
699 "onion_link": "ကြက်သွန်လင့်",
700 "settings": "ဆက်တင်များ",
701 "sell_monero_com_alert_content": "Monero ရောင်းချခြင်းကို မပံ့ပိုးရသေးပါ။",
702 + "error_text_input_below_minimum_limit" : "ပမာဏသည် အနိမ့်ဆုံးထက်နည်းသည်။",
703 + "error_text_input_above_maximum_limit" : "ပမာဏသည် အများဆုံးထက် ပိုများသည်။",
704 "show_market_place":"စျေးကွက်ကိုပြသပါ။"
705 }
res/values/strings_nl.arb
+2
@@ -699,5 +699,7 @@
699 "onion_link": "Ui koppeling",
700 "settings": "Instellingen",
701 "sell_monero_com_alert_content": "Het verkopen van Monero wordt nog niet ondersteund",
702 + "error_text_input_below_minimum_limit" : "Bedrag is minder dan minimaal",
703 + "error_text_input_above_maximum_limit" : "Bedrag is meer dan maximaal",
704 "show_market_place":"Toon Marktplaats"
705 }
res/values/strings_pl.arb
+2
@@ -699,5 +699,7 @@
699 "onion_link": "Łącznik cebulowy",
700 "settings": "Ustawienia",
701 "sell_monero_com_alert_content": "Sprzedaż Monero nie jest jeszcze obsługiwana",
702 + "error_text_input_below_minimum_limit" : "Kwota jest mniejsza niż minimalna",
703 + "error_text_input_above_maximum_limit" : "Kwota jest większa niż maksymalna",
704 "show_market_place" : "Pokaż rynek"
705 }
res/values/strings_pt.arb
+2
@@ -698,5 +698,7 @@
698 "onion_link": "ligação de cebola",
699 "settings": "Configurações",
700 "sell_monero_com_alert_content": "A venda de Monero ainda não é suportada",
701 + "error_text_input_below_minimum_limit" : "O valor é menor que o mínimo",
702 + "error_text_input_above_maximum_limit" : "O valor é superior ao máximo",
703 "show_market_place":"Mostrar mercado"
704 }
res/values/strings_ru.arb
+2
@@ -699,5 +699,7 @@
699 "onion_link": "Луковая ссылка",
700 "settings": "Настройки",
701 "sell_monero_com_alert_content": "Продажа Monero пока не поддерживается",
702 + "error_text_input_below_minimum_limit" : "Сумма меньше минимальной",
703 + "error_text_input_above_maximum_limit" : "Сумма больше максимальной",
704 "show_market_place":"Показать торговую площадку"
705 }
res/values/strings_th.arb
+2
@@ -697,5 +697,7 @@
697 "onion_link": "ลิงค์หัวหอม",
698 "settings": "การตั้งค่า",
699 "sell_monero_com_alert_content": "ยังไม่รองรับการขาย Monero",
700 + "error_text_input_below_minimum_limit" : "จำนวนเงินน้อยกว่าขั้นต่ำ",
701 + "error_text_input_above_maximum_limit" : "จำนวนเงินสูงกว่าค่าสูงสุด",
702 "show_market_place":"แสดงตลาดกลาง"
703 }
res/values/strings_tr.arb
+2
@@ -699,5 +699,7 @@
699 "onion_link": "soğan bağlantısı",
700 "settings": "ayarlar",
701 "sell_monero_com_alert_content": "Monero satışı henüz desteklenmiyor",
702 + "error_text_input_below_minimum_limit" : "Miktar minimumdan daha azdır",
703 + "error_text_input_above_maximum_limit" : "Miktar maksimumdan daha fazla",
704 "show_market_place":"Pazar Yerini Göster"
705 }
res/values/strings_uk.arb
+2
@@ -698,5 +698,7 @@
698 "onion_link": "Посилання на цибулю",
699 "settings": "Налаштування",
700 "sell_monero_com_alert_content": "Продаж Monero ще не підтримується",
701 + "error_text_input_below_minimum_limit" : "Сума менша мінімальної",
702 + "error_text_input_above_maximum_limit" : "Сума більше максимальної",
703 "show_market_place":"Шоу Ринок"
704 }
res/values/strings_ur.arb
+2
@@ -699,5 +699,7 @@
699 "clearnet_link": "کلیرنیٹ لنک",
700 "onion_link": "پیاز کا لنک",
701 "sell_monero_com_alert_content": "Monero فروخت کرنا ابھی تک تعاون یافتہ نہیں ہے۔",
702 + "error_text_input_below_minimum_limit" : "رقم کم از کم سے کم ہے۔",
703 + "error_text_input_above_maximum_limit" : "رقم زیادہ سے زیادہ سے زیادہ ہے۔",
704 "show_market_place":"بازار دکھائیں۔"
705 }
res/values/strings_zh.arb
+2
@@ -698,5 +698,7 @@
698 "onion_link": "洋葱链接",
699 "settings": "设置",
700 "sell_monero_com_alert_content": "尚不支持出售门罗币",
701 + "error_text_input_below_minimum_limit" : "金额小于最小值",
702 + "error_text_input_above_maximum_limit" : "金额大于最大值",
703 "show_market_place" :"显示市场"
704 }