From 0e53c41375f0a2ea801ef3942fa1b9f3d092122e Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Wed, 19 Jul 2023 07:53:30 +0200 Subject: [PATCH 1/6] Allow decimal input in AddTransaction page. Fixes #95 Add custom formatter to fix various inconsistencies --- lib/pages/add_page/add_page.dart | 5 ++- lib/utils/decimal_text_input_formatter.dart | 44 +++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 lib/utils/decimal_text_input_formatter.dart diff --git a/lib/pages/add_page/add_page.dart b/lib/pages/add_page/add_page.dart index cb31433..23a0cbc 100644 --- a/lib/pages/add_page/add_page.dart +++ b/lib/pages/add_page/add_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:sossoldi/utils/decimal_text_input_formatter.dart'; import '../../model/transaction.dart'; import 'widgets/details_tile.dart'; import 'widgets/type_tab.dart'; @@ -337,9 +338,9 @@ class _AddPageState extends ConsumerState with Functions { color: typeToColor(selectedType), ), ), - keyboardType: TextInputType.number, + keyboardType: const TextInputType.numberWithOptions(decimal: true), inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'[0,0-9,9]')), + DecimalTextInputFormatter(decimalDigits: 2) ], autofocus: true, textAlign: TextAlign.center, diff --git a/lib/utils/decimal_text_input_formatter.dart b/lib/utils/decimal_text_input_formatter.dart new file mode 100644 index 0000000..be4307b --- /dev/null +++ b/lib/utils/decimal_text_input_formatter.dart @@ -0,0 +1,44 @@ +import 'dart:math' as math; +import 'package:flutter/services.dart'; + +class DecimalTextInputFormatter extends TextInputFormatter { + DecimalTextInputFormatter({this.decimalDigits}) + : assert(decimalDigits == null || decimalDigits > 0); + + final int? decimalDigits; + final String decimalSeparator = "."; + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, + TextEditingValue newValue, + ) { + TextSelection newSelection = newValue.selection; + String value = newValue.text; + + if (decimalSeparator.allMatches(value).length > 1) { + return oldValue; + } + + if (value == decimalSeparator) { + // Allow for .x decimal notation + value = "0$decimalSeparator"; + } + + if (decimalDigits != null && + value.contains(decimalSeparator) && value.substring(value.indexOf(decimalSeparator) + 1).length > decimalDigits!) { + value = oldValue.text; + } + + newSelection = newValue.selection.copyWith( + baseOffset: math.min(value.length, value.length + 1), + extentOffset: math.min(value.length, value.length + 1), + ); + + return TextEditingValue( + text: value, + selection: newSelection, + composing: TextRange.empty, + ); + } +} From 33b258e745b79f1c54f3a460cb8d0ba163ac81d7 Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Wed, 9 Aug 2023 15:24:27 +0200 Subject: [PATCH 2/6] Fix import statement --- lib/pages/add_page/add_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/add_page/add_page.dart b/lib/pages/add_page/add_page.dart index 23a0cbc..5d2dcf8 100644 --- a/lib/pages/add_page/add_page.dart +++ b/lib/pages/add_page/add_page.dart @@ -2,7 +2,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:sossoldi/utils/decimal_text_input_formatter.dart'; +import '../../utils/decimal_text_input_formatter.dart'; import '../../model/transaction.dart'; import 'widgets/details_tile.dart'; import 'widgets/type_tab.dart'; From 8272f837df627058bbe6e0a3557a473b4b761b0c Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Wed, 9 Aug 2023 17:57:17 +0200 Subject: [PATCH 3/6] Do not allow thousands separator input --- lib/utils/decimal_text_input_formatter.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/utils/decimal_text_input_formatter.dart b/lib/utils/decimal_text_input_formatter.dart index be4307b..965b722 100644 --- a/lib/utils/decimal_text_input_formatter.dart +++ b/lib/utils/decimal_text_input_formatter.dart @@ -7,6 +7,7 @@ class DecimalTextInputFormatter extends TextInputFormatter { final int? decimalDigits; final String decimalSeparator = "."; + final String thousandsSeparator = ","; @override TextEditingValue formatEditUpdate( @@ -16,7 +17,7 @@ class DecimalTextInputFormatter extends TextInputFormatter { TextSelection newSelection = newValue.selection; String value = newValue.text; - if (decimalSeparator.allMatches(value).length > 1) { + if (decimalSeparator.allMatches(value).length > 1 || value.contains(thousandsSeparator)) { return oldValue; } From dc986cc39427c4ba26bfc7643254c83946868311 Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Thu, 10 Aug 2023 07:52:22 +0200 Subject: [PATCH 4/6] Refactor some code and add some nullability checks --- .../widgets/categories_tab.dart | 50 ++++++++----------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/lib/pages/transactions_page/widgets/categories_tab.dart b/lib/pages/transactions_page/widgets/categories_tab.dart index 2a1ef9f..9c94d2e 100644 --- a/lib/pages/transactions_page/widgets/categories_tab.dart +++ b/lib/pages/transactions_page/widgets/categories_tab.dart @@ -38,46 +38,40 @@ class _CategoriesTabState extends ConsumerState with Functions { Map categoryToAmount = {}; double total = 0; - if (transactions.value != null) { - for (var t in transactions.value!) { - // add transaction to its category - int categoryId = t.idCategory!; + for (var transaction in transactions.value ?? []) { + print(transaction.idCategory); + final categoryId = transaction.idCategory; + if (categoryId != null) { + final updateValue = { + "account": transaction.idBankAccount.toString(), + "amount": transaction.amount, + "category": categoryId.toString(), + "title": transaction.note, + }; + if (categoryToTransactions.containsKey(categoryId)) { - categoryToTransactions[categoryId]?.add({ - "account": t.idBankAccount.toString(), - "amount": t.amount, - "category": categoryId.toString(), - "title": t.note, - }); + categoryToTransactions[categoryId]?.add(updateValue); } else { - categoryToTransactions.putIfAbsent( - categoryId, - () => [ - { - "account": t.idBankAccount.toString(), - "amount": t.amount, - "category": categoryId.toString(), - "title": t.note, - } - ], - ); + categoryToTransactions.putIfAbsent(categoryId, () => [updateValue]); } // update total amount for the category - total += t.amount; + total += transaction.amount; if (categoryToAmount.containsKey(categoryId)) { categoryToAmount[categoryId] = - categoryToAmount[categoryId]! + t.amount.toDouble(); + categoryToAmount[categoryId]! + transaction.amount.toDouble(); } else { - categoryToAmount.putIfAbsent(categoryId, () => t.amount.toDouble()); + categoryToAmount.putIfAbsent(categoryId, () => transaction.amount.toDouble()); } } } // Add missing catogories (with amount 0) // This will be removed when only categories with transactions are queried - for (var c in categories.value!) { - categoryToAmount.putIfAbsent(c.id!, () => 0); + for (var category in categories.value ?? []) { + if (category.id != null) { + categoryToAmount.putIfAbsent(category.id, () => 0); + } } return Container( @@ -137,8 +131,8 @@ class _CategoriesTabState extends ConsumerState with Functions { percent: (categoryToAmount[t.id] ?? 0) / total * 100, color: const Color(0xFFEBC35F), - icon: iconList[t.symbol] ?? - Icons.swap_horiz_rounded, + icon: + iconList[t.symbol] ?? Icons.swap_horiz_rounded, notifier: selectedCategory, index: index, ); From e284d0a12ed1a9c87ce163c8d0715560652e3143 Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Fri, 11 Aug 2023 07:40:13 +0200 Subject: [PATCH 5/6] Replace thousands separator (comma) with decimal separator --- lib/utils/decimal_text_input_formatter.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/utils/decimal_text_input_formatter.dart b/lib/utils/decimal_text_input_formatter.dart index 965b722..32aa542 100644 --- a/lib/utils/decimal_text_input_formatter.dart +++ b/lib/utils/decimal_text_input_formatter.dart @@ -17,6 +17,10 @@ class DecimalTextInputFormatter extends TextInputFormatter { TextSelection newSelection = newValue.selection; String value = newValue.text; + if (value.contains(thousandsSeparator)) { + value = value.replaceAll(thousandsSeparator, decimalSeparator); + } + if (decimalSeparator.allMatches(value).length > 1 || value.contains(thousandsSeparator)) { return oldValue; } From e47474a77159076dc8e89b66dd7e7e71dc14df01 Mon Sep 17 00:00:00 2001 From: Roberto Casula Date: Fri, 11 Aug 2023 08:12:24 +0200 Subject: [PATCH 6/6] Fix potential issue with external keyboard in android not allowing other chars than decimals and separators --- lib/utils/decimal_text_input_formatter.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/utils/decimal_text_input_formatter.dart b/lib/utils/decimal_text_input_formatter.dart index 32aa542..7e7cc68 100644 --- a/lib/utils/decimal_text_input_formatter.dart +++ b/lib/utils/decimal_text_input_formatter.dart @@ -17,6 +17,12 @@ class DecimalTextInputFormatter extends TextInputFormatter { TextSelection newSelection = newValue.selection; String value = newValue.text; + RegExp regex = RegExp(r'[\d\,\.]'); + + if (value.isNotEmpty && !regex.hasMatch(value[value.length -1])) { + return oldValue; + } + if (value.contains(thousandsSeparator)) { value = value.replaceAll(thousandsSeparator, decimalSeparator); }