Skip to content

Commit 7d165b8

Browse files
authored
Merge pull request #69 from SimoneBressan/feature/pos_input_formatter
Added PosInputFormatter feature to format a number similar a pos machine
2 parents c4dd9b2 + 8447590 commit 7d165b8

5 files changed

Lines changed: 358 additions & 0 deletions

File tree

example/lib/main.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:example/pages/credit_card_format_page.dart';
33
import 'package:example/pages/masked_formatter_page.dart';
44
import 'package:example/pages/money_format_page.dart';
55
import 'package:example/pages/phone_format_page.dart';
6+
import 'package:example/pages/pos_format_page.dart';
67
import 'package:flutter/cupertino.dart';
78
import 'package:flutter/material.dart';
89
import 'package:flutter_multi_formatter/flutter_multi_formatter.dart';
@@ -137,6 +138,12 @@ class _MyHomePageState extends State<MyHomePage> {
137138
label: 'Bitcoin Validator',
138139
pageBuilder: () => BitcoinValidatorPage(),
139140
),
141+
_buildButton(
142+
color: Colors.purple,
143+
iconData: Icons.money_off,
144+
label: 'Pos Formatter',
145+
pageBuilder: () => PosFormatPage(),
146+
),
140147
],
141148
),
142149
),
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_multi_formatter/flutter_multi_formatter.dart';
3+
4+
class PosFormatPage extends StatelessWidget {
5+
const PosFormatPage({Key? key}) : super(key: key);
6+
7+
@override
8+
Widget build(BuildContext context) {
9+
return Unfocuser(
10+
child: Scaffold(
11+
appBar: AppBar(
12+
centerTitle: true,
13+
title: Text('Pos Formatter Demo'),
14+
),
15+
body: SingleChildScrollView(
16+
child: Padding(
17+
padding: const EdgeInsets.all(30.0),
18+
child: Column(
19+
children: <Widget>[
20+
TextFormField(
21+
decoration: InputDecoration(
22+
border: const OutlineInputBorder(),
23+
hintText: 'Enter a numeric value',
24+
hintStyle: TextStyle(
25+
color: Colors.black.withOpacity(0.3),
26+
),
27+
errorStyle: TextStyle(
28+
color: Colors.red,
29+
),
30+
),
31+
keyboardType: TextInputType.number,
32+
inputFormatters: const [PosInputFormatter()],
33+
),
34+
],
35+
),
36+
),
37+
),
38+
),
39+
);
40+
}
41+
}

lib/flutter_multi_formatter.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export 'formatters/money_input_enums.dart';
3636
export 'formatters/money_input_formatter.dart';
3737
export 'formatters/phone_input_enums.dart';
3838
export 'formatters/phone_input_formatter.dart';
39+
export 'formatters/pos_input_formatter.dart';
3940
export 'utils/bitcoin_validator/bitcoin_validator.dart';
4041
export 'utils/bitcoin_validator/bitcoin_wallet_details.dart';
4142
export 'utils/enum_utils.dart';
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import 'package:flutter/services.dart';
2+
3+
class PosInputFormatter implements TextInputFormatter {
4+
final DecimalPosSeparator decimalSeparator;
5+
final ThousandsPosSeparator? thousandsSeparator;
6+
final int mantissaLength;
7+
8+
/// [decimalSeparator] specifies what symbol will be used to separate
9+
/// integer part between decimal part, e.g. [ThousandsPosSeparator.comma]
10+
/// will format ten point thirteen as 10.13
11+
/// [thousandsSeparator] specifies what symbol will be used to separate
12+
/// each block of 3 digits, e.g. [ThousandsPosSeparator.comma] will format
13+
/// million as 1,000,000
14+
/// [mantissaLength] specifies how many digits will be added after a period sign
15+
const PosInputFormatter({
16+
this.decimalSeparator = DecimalPosSeparator.dot,
17+
this.thousandsSeparator,
18+
this.mantissaLength = 2,
19+
});
20+
21+
String insertThousandSeparator(String text, String separator) {
22+
final textLength = text.length;
23+
final textBuffer = <String>[];
24+
25+
for (var i = 0; i < textLength; i++) {
26+
if (i % 3 == 0 && i != 0) {
27+
textBuffer.add(separator);
28+
}
29+
textBuffer.add(text[textLength - i - 1]);
30+
}
31+
32+
return textBuffer.reversed.join();
33+
}
34+
35+
@override
36+
TextEditingValue formatEditUpdate(
37+
TextEditingValue oldValue,
38+
TextEditingValue newValue,
39+
) {
40+
var text = newValue.text;
41+
42+
// Clean text
43+
text = text.replaceAll(RegExp(r"[^0-9]"), '');
44+
45+
// Remove initial zero
46+
text = text.replaceFirst(RegExp(r'0*'), '');
47+
48+
// Add the zeros until you get to the whole part
49+
if (text.length <= mantissaLength)
50+
text = text.padLeft(mantissaLength + 1, '0');
51+
52+
if (text.length > mantissaLength) {
53+
final separatorOffset = text.length - mantissaLength;
54+
55+
var integerPart = text.substring(0, separatorOffset);
56+
final decimalPart = text.substring(separatorOffset, text.length);
57+
58+
if (thousandsSeparator != null) {
59+
integerPart =
60+
insertThousandSeparator(integerPart, thousandsSeparator!.char);
61+
}
62+
63+
text = '${integerPart}${decimalSeparator.char}${decimalPart}';
64+
65+
return newValue.copyWith(
66+
selection: TextSelection.collapsed(offset: text.length),
67+
text: text,
68+
);
69+
}
70+
71+
return newValue.copyWith(
72+
selection: TextSelection.collapsed(offset: text.length),
73+
text: text,
74+
);
75+
}
76+
}
77+
78+
class DecimalPosSeparator {
79+
final String char;
80+
81+
const DecimalPosSeparator._(this.char);
82+
83+
factory DecimalPosSeparator.parse(String char) {
84+
switch (char) {
85+
case ',':
86+
return comma;
87+
case '.':
88+
return dot;
89+
}
90+
91+
throw FormatException("Invalid char. Valid characters: ${values}", char);
92+
}
93+
94+
/// [comma] means this format 1000000.00
95+
static const DecimalPosSeparator dot = DecimalPosSeparator._('.');
96+
97+
/// [comma] means this format 1000000,00
98+
static const DecimalPosSeparator comma = DecimalPosSeparator._(',');
99+
100+
/// All decimal pos separators
101+
static List<DecimalPosSeparator> get values => const [comma, dot];
102+
103+
@override
104+
String toString() => '$runtimeType.$char';
105+
}
106+
107+
class ThousandsPosSeparator {
108+
final String char;
109+
110+
const ThousandsPosSeparator._(this.char);
111+
112+
/// Parse [char] to thousands pos separator
113+
factory ThousandsPosSeparator.parse(String char) {
114+
switch (char) {
115+
case ',':
116+
return comma;
117+
case '.':
118+
return dot;
119+
case ' ':
120+
return space;
121+
case '\'':
122+
return quote;
123+
}
124+
125+
throw FormatException("Invalid char. Valid characters: ${values}", char);
126+
}
127+
128+
/// [dot] means this format 1.000.000,00
129+
static const ThousandsPosSeparator dot = ThousandsPosSeparator._('.');
130+
131+
/// [comma] means this format 1,000,000.00
132+
static const ThousandsPosSeparator comma = ThousandsPosSeparator._(',');
133+
134+
/// [space] means this format 1 000 000,00
135+
static const ThousandsPosSeparator space = ThousandsPosSeparator._(' ');
136+
137+
/// [space] means this format 1'000'000,00
138+
static const ThousandsPosSeparator quote = ThousandsPosSeparator._('\'');
139+
140+
/// All thousands pos separators
141+
static List<ThousandsPosSeparator> get values => const [comma, dot];
142+
143+
@override
144+
String toString() => '$runtimeType.$char';
145+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import 'package:flutter/services.dart';
2+
import 'package:flutter_multi_formatter/formatters/pos_input_formatter.dart';
3+
import 'package:flutter_test/flutter_test.dart';
4+
5+
void main() {
6+
const decimalSeparator = '.';
7+
const formatter = PosInputFormatter(
8+
decimalSeparator: DecimalPosSeparator.dot,
9+
thousandsSeparator: ThousandsPosSeparator.space,
10+
mantissaLength: 2,
11+
);
12+
13+
group('Test PosInputFormatter text input formatter', () {
14+
group(
15+
'Tests for adding zeroes at the beginning of the number and adding decimal separator',
16+
() {
17+
test('Add "0.0" at the beginning of the string', () {
18+
const oldValue = TextEditingValue();
19+
const newValue = TextEditingValue(
20+
text: '1',
21+
selection: TextSelection.collapsed(offset: 1),
22+
);
23+
const expectedValue = TextEditingValue(
24+
text: '0${decimalSeparator}01',
25+
selection: TextSelection.collapsed(offset: 4),
26+
);
27+
28+
expect(formatter.formatEditUpdate(oldValue, newValue), expectedValue);
29+
});
30+
test('Add "0." at the beginning of the string', () {
31+
const oldValue = TextEditingValue(
32+
text: '1',
33+
selection: TextSelection.collapsed(offset: 1),
34+
);
35+
const newValue = TextEditingValue(
36+
text: '12',
37+
selection: TextSelection.collapsed(offset: 2),
38+
);
39+
const expectedValue = TextEditingValue(
40+
text: '0${decimalSeparator}12',
41+
selection: TextSelection.collapsed(offset: 4),
42+
);
43+
44+
expect(formatter.formatEditUpdate(oldValue, newValue), expectedValue);
45+
});
46+
test('Add "." to the string', () {
47+
const oldValue = TextEditingValue(
48+
text: '12',
49+
selection: TextSelection.collapsed(offset: 2),
50+
);
51+
const newValue = TextEditingValue(
52+
text: '123',
53+
selection: TextSelection.collapsed(offset: 3),
54+
);
55+
const expectedValue = TextEditingValue(
56+
text: '1${decimalSeparator}23',
57+
selection: TextSelection.collapsed(offset: 4),
58+
);
59+
60+
expect(formatter.formatEditUpdate(oldValue, newValue), expectedValue);
61+
});
62+
test('Add "." between 4 digits', () {
63+
const oldValue = TextEditingValue(
64+
text: '1.23',
65+
selection: TextSelection.collapsed(offset: 4),
66+
);
67+
const newValue = TextEditingValue(
68+
text: '1.234',
69+
selection: TextSelection.collapsed(offset: 5),
70+
);
71+
const expectedValue = TextEditingValue(
72+
text: '12${decimalSeparator}34',
73+
selection: TextSelection.collapsed(offset: 5),
74+
);
75+
76+
expect(formatter.formatEditUpdate(oldValue, newValue), expectedValue);
77+
});
78+
});
79+
80+
group('Tests for adding the thousands separator', () {
81+
test('Not add space', () {
82+
const oldValue = TextEditingValue(
83+
text: '12.34',
84+
selection: TextSelection.collapsed(offset: 5),
85+
);
86+
const newValue = TextEditingValue(
87+
text: '12.345',
88+
selection: TextSelection.collapsed(offset: 6),
89+
);
90+
const expectedValue = TextEditingValue(
91+
text: '123${decimalSeparator}45',
92+
selection: TextSelection.collapsed(offset: 6),
93+
);
94+
95+
expect(formatter.formatEditUpdate(oldValue, newValue), expectedValue);
96+
});
97+
test('Add space to the string', () {
98+
const oldValue = TextEditingValue(
99+
text: '123.45',
100+
selection: TextSelection.collapsed(offset: 5),
101+
);
102+
const newValue = TextEditingValue(
103+
text: '123.456',
104+
selection: TextSelection.collapsed(offset: 7),
105+
);
106+
const expectedValue = TextEditingValue(
107+
text: '1 234${decimalSeparator}56',
108+
selection: TextSelection.collapsed(offset: 8),
109+
);
110+
111+
expect(formatter.formatEditUpdate(oldValue, newValue), expectedValue);
112+
});
113+
test('Add only one space to the string', () {
114+
const oldValue = TextEditingValue(
115+
text: '12 345.67',
116+
selection: TextSelection.collapsed(offset: 9),
117+
);
118+
const newValue = TextEditingValue(
119+
text: '123 45.678',
120+
selection: TextSelection.collapsed(offset: 10),
121+
);
122+
const expectedValue = TextEditingValue(
123+
text: '123 456${decimalSeparator}78',
124+
selection: TextSelection.collapsed(offset: 10),
125+
);
126+
127+
expect(formatter.formatEditUpdate(oldValue, newValue), expectedValue);
128+
});
129+
test('Add two space to the string', () {
130+
const oldValue = TextEditingValue(
131+
text: '123 456.78',
132+
selection: TextSelection.collapsed(offset: 10),
133+
);
134+
const newValue = TextEditingValue(
135+
text: '123 456.789',
136+
selection: TextSelection.collapsed(offset: 11),
137+
);
138+
const expectedValue = TextEditingValue(
139+
text: '1 234 567${decimalSeparator}89',
140+
selection: TextSelection.collapsed(offset: 12),
141+
);
142+
143+
expect(formatter.formatEditUpdate(oldValue, newValue), expectedValue);
144+
});
145+
});
146+
147+
test('Remove incorrect characters', () {
148+
const oldValue = TextEditingValue(
149+
text: '',
150+
selection: TextSelection.collapsed(offset: 0),
151+
);
152+
const newValue = TextEditingValue(
153+
text: '123d45a.6',
154+
selection: TextSelection.collapsed(offset: 9),
155+
);
156+
const expectedValue = TextEditingValue(
157+
text: '1 234${decimalSeparator}56',
158+
selection: TextSelection.collapsed(offset: 8),
159+
);
160+
161+
expect(formatter.formatEditUpdate(oldValue, newValue), expectedValue);
162+
});
163+
});
164+
}

0 commit comments

Comments
 (0)