insert and handle localised group and decimal separators in textfield amounts

This commit is contained in:
julian 2023-06-16 16:47:03 -06:00
parent f88d7f200c
commit c0eb85ce5a
8 changed files with 213 additions and 76 deletions

View file

@ -40,6 +40,7 @@ import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_input_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_unit.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
@ -128,8 +129,6 @@ class _SendViewState extends ConsumerState<SendView> {
if (!_cryptoAmountChangeLock) {
final cryptoAmount = ref.read(pAmountFormatter(coin)).tryParse(
cryptoAmountController.text,
locale: ref.read(localeServiceChangeNotifierProvider).locale,
coin: coin,
);
if (cryptoAmount != null) {
_amountToSend = cryptoAmount;
@ -1538,13 +1537,20 @@ class _SendViewState extends ConsumerState<SendView> {
),
textAlign: TextAlign.right,
inputFormatters: [
AmountInputFormatter(
decimals: coin.decimals,
locale: locale,
),
// regex to validate a crypto amount with 8 decimal places
TextInputFormatter.withFunction((oldValue,
newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
// TextInputFormatter.withFunction((oldValue,
// newValue) =>
// // RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
// // RegExp(r'^\d{1,3}([,\.]\d+)?|[,\.\d]+$')
// getAmountRegex(locale, coin.decimals)
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(
@ -1599,13 +1605,18 @@ class _SendViewState extends ConsumerState<SendView> {
),
textAlign: TextAlign.right,
inputFormatters: [
AmountInputFormatter(
decimals: 2,
locale: locale,
),
// regex to validate a fiat amount with 2 decimal places
TextInputFormatter.withFunction((oldValue,
newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
// TextInputFormatter.withFunction((oldValue,
// newValue) =>
// // RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
// getAmountRegex(locale, 2)
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
onChanged: (baseAmountString) {
final baseAmount = Amount.tryParseFiatString(

View file

@ -31,6 +31,7 @@ import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_input_formatter.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
@ -269,8 +270,6 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
if (!_cryptoAmountChangeLock) {
final cryptoAmount = ref.read(pAmountFormatter(coin)).tryParse(
cryptoAmountController.text,
locale: ref.read(localeServiceChangeNotifierProvider).locale,
coin: coin,
ethContract: tokenContract,
);
if (cryptoAmount != null) {
@ -937,13 +936,17 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
),
textAlign: TextAlign.right,
inputFormatters: [
// regex to validate a crypto amount with 8 decimal places
TextInputFormatter.withFunction((oldValue,
newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
AmountInputFormatter(
decimals: tokenContract.decimals,
locale: locale,
),
// // regex to validate a crypto amount with 8 decimal places
// TextInputFormatter.withFunction((oldValue,
// newValue) =>
// RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(
@ -996,13 +999,17 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
),
textAlign: TextAlign.right,
inputFormatters: [
// regex to validate a fiat amount with 2 decimal places
TextInputFormatter.withFunction((oldValue,
newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
AmountInputFormatter(
decimals: 2,
locale: locale,
),
// // regex to validate a fiat amount with 2 decimal places
// TextInputFormatter.withFunction((oldValue,
// newValue) =>
// RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
onChanged: _onFiatAmountFieldChanged,
decoration: InputDecoration(

View file

@ -10,16 +10,17 @@
import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_rounded_date_picker/flutter_rounded_date_picker.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/transaction_filter.dart';
import 'package:stackwallet/providers/global/locale_provider.dart';
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/themes/theme_providers.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_input_formatter.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -757,12 +758,19 @@ class _TransactionSearchViewState
decimal: true,
),
inputFormatters: [
// regex to validate a crypto amount with 8 decimal places
TextInputFormatter.withFunction((oldValue, newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
AmountInputFormatter(
decimals: widget.coin.decimals,
locale: ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
),
),
// // regex to validate a crypto amount with 8 decimal places
// TextInputFormatter.withFunction((oldValue, newValue) =>
// RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
style: isDesktop
? STextStyles.desktopTextExtraSmall(context).copyWith(

View file

@ -39,6 +39,7 @@ import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_input_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_unit.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
@ -1040,12 +1041,16 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
),
textAlign: TextAlign.right,
inputFormatters: [
// regex to validate a crypto amount with 8 decimal places
TextInputFormatter.withFunction((oldValue, newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
AmountInputFormatter(
decimals: coin.decimals,
locale: locale,
),
// // regex to validate a crypto amount with 8 decimal places
// TextInputFormatter.withFunction((oldValue, newValue) =>
// RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
onChanged: (newValue) {},
decoration: InputDecoration(
@ -1097,12 +1102,16 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
),
textAlign: TextAlign.right,
inputFormatters: [
// regex to validate a fiat amount with 2 decimal places
TextInputFormatter.withFunction((oldValue, newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
AmountInputFormatter(
decimals: 2,
locale: locale,
),
// // regex to validate a fiat amount with 2 decimal places
// TextInputFormatter.withFunction((oldValue, newValue) =>
// RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
onChanged: fiatTextFieldOnChanged,
decoration: InputDecoration(

View file

@ -31,6 +31,7 @@ import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_input_formatter.dart';
import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/constants.dart';
@ -50,7 +51,7 @@ import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
const _kCryptoAmountRegex = r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$';
// const _kCryptoAmountRegex = r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$';
class DesktopTokenSend extends ConsumerStatefulWidget {
const DesktopTokenSend({
@ -717,15 +718,22 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
),
textAlign: TextAlign.right,
inputFormatters: [
AmountInputFormatter(
decimals: tokenContract.decimals,
locale: ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
),
),
// regex to validate a crypto amount with 8 decimal places
TextInputFormatter.withFunction((oldValue, newValue) => RegExp(
_kCryptoAmountRegex.replaceAll(
"0,8",
"0,${tokenContract.decimals}",
),
).hasMatch(newValue.text)
? newValue
: oldValue),
// TextInputFormatter.withFunction((oldValue, newValue) => RegExp(
// _kCryptoAmountRegex.replaceAll(
// "0,8",
// "0,${tokenContract.decimals}",
// ),
// ).hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
onChanged: (newValue) {},
decoration: InputDecoration(
@ -777,12 +785,19 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
),
textAlign: TextAlign.right,
inputFormatters: [
// regex to validate a fiat amount with 2 decimal places
TextInputFormatter.withFunction((oldValue, newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
AmountInputFormatter(
decimals: 2,
locale: ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
),
),
// // regex to validate a fiat amount with 2 decimal places
// TextInputFormatter.withFunction((oldValue, newValue) =>
// RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
onChanged: fiatTextFieldOnChanged,
decoration: InputDecoration(

View file

@ -66,8 +66,6 @@ class AmountFormatter {
Amount? tryParse(
String string, {
required String locale,
required Coin coin,
EthContract? ethContract,
}) {
return unit.tryParse(string, locale: locale, coin: coin);

View file

@ -0,0 +1,82 @@
import 'package:flutter/services.dart';
import 'package:intl/number_symbols.dart';
import 'package:intl/number_symbols_data.dart';
class AmountInputFormatter extends TextInputFormatter {
final int decimals;
final String locale;
AmountInputFormatter({required this.decimals, required this.locale});
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
// get number symbols for decimal place and group separator
final numberSymbols = numberFormatSymbols[locale] as NumberSymbols? ??
numberFormatSymbols[locale.substring(0, 2)] as NumberSymbols?;
final decimalSeparator = numberSymbols?.DECIMAL_SEP ?? ".";
final groupSeparator = numberSymbols?.GROUP_SEP ?? ",";
String newText = newValue.text.replaceAll(groupSeparator, "");
final selectionIndexFromTheRight =
newValue.text.length - newValue.selection.end;
String? fraction;
if (newText.contains(decimalSeparator)) {
final parts = newText.split(decimalSeparator);
if (parts.length > 2) {
return oldValue;
}
if (newText.startsWith(decimalSeparator)) {
return TextEditingValue(
text: newText,
selection: TextSelection.collapsed(
offset: newText.length - selectionIndexFromTheRight,
),
);
}
newText = parts.first;
if (parts.length == 2) {
fraction = parts.last;
} else {
fraction = "";
}
if (fraction.length > decimals) {
return oldValue;
}
}
if (newText.trim() == '' || newText.trim() == '0') {
return newValue.copyWith(text: '');
} else if (BigInt.parse(newText) < BigInt.one) {
return newValue.copyWith(text: '');
}
// insert group separator
final regex = RegExp(r'\B(?=(\d{3})+(?!\d))');
String newString = newText.replaceAllMapped(
regex,
(m) => "${m.group(0)}${numberSymbols?.GROUP_SEP ?? ","}",
);
if (fraction != null) {
newString += decimalSeparator;
if (fraction.isNotEmpty) {
newString += fraction;
}
}
return TextEditingValue(
text: newString,
selection: TextSelection.collapsed(
offset: newString.length - selectionIndexFromTheRight,
),
);
}
}

View file

@ -9,17 +9,19 @@
*/
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/exchange/aggregate_currency.dart';
import 'package:stackwallet/pages/buy_view/sub_widgets/crypto_selection_view.dart';
import 'package:stackwallet/providers/global/locale_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/amount/amount_input_formatter.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
class ExchangeTextField extends StatefulWidget {
class ExchangeTextField extends ConsumerStatefulWidget {
const ExchangeTextField({
Key? key,
this.borderRadius = 0,
@ -55,10 +57,10 @@ class ExchangeTextField extends StatefulWidget {
final AggregateCurrency? currency;
@override
State<ExchangeTextField> createState() => _ExchangeTextFieldState();
ConsumerState<ExchangeTextField> createState() => _ExchangeTextFieldState();
}
class _ExchangeTextFieldState extends State<ExchangeTextField> {
class _ExchangeTextFieldState extends ConsumerState<ExchangeTextField> {
late final TextEditingController controller;
late final FocusNode focusNode;
late final TextStyle textStyle;
@ -130,12 +132,17 @@ class _ExchangeTextFieldState extends State<ExchangeTextField> {
),
),
inputFormatters: [
// regex to validate a crypto amount with 8 decimal places
TextInputFormatter.withFunction((oldValue, newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
AmountInputFormatter(
decimals: 8, // todo change this
locale: ref.watch(localeServiceChangeNotifierProvider
.select((value) => value.locale)),
),
// // regex to validate a crypto amount with 8 decimal places
// TextInputFormatter.withFunction((oldValue, newValue) =>
// RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
),
),