Skip to content

Commit 452f8a9

Browse files
committed
Formatters & Validators #8
1 parent 445073b commit 452f8a9

File tree

7 files changed

+228
-83
lines changed

7 files changed

+228
-83
lines changed

lib/src/api/extensions.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,22 @@ extension TextInputValidatorModelIterableExt<T extends TextInputValidatorModel>
9797
return model;
9898
}
9999
}
100+
101+
/// A helper extension that adds additional functionality to
102+
/// [TextInputValidatorModel] iterable.
103+
extension TextInputFormatterModelIterableExt<T extends TextInputFormatterModel>
104+
on Iterable<T> {
105+
/// Returns the first [TextInputFormatterModel] with the given [name].
106+
/// Returns null if no validator is found.
107+
T? byNameOrNull(String name) =>
108+
firstWhereOrNull((model) => model.name == name);
109+
110+
/// Returns the first [TextInputValidatorModel] with the given [name].
111+
T byName(String name) {
112+
final T? model = byNameOrNull(name);
113+
if (model == null) {
114+
throw ArgumentError('No validator found with the name: $name');
115+
}
116+
return model;
117+
}
118+
}
Lines changed: 143 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,195 @@
11
import 'package:codelessly_json_annotation/codelessly_json_annotation.dart';
22
import 'package:equatable/equatable.dart';
33

4-
import '../mixins.dart';
4+
import '../../../codelessly_api.dart';
55

66
part 'text_input_formatter_model.g.dart';
77

8+
/// Represents the type of formatter.
9+
enum TextInputFormatterType {
10+
/// Equivalent to no formatter.
11+
none,
12+
13+
/// A formatter that uses a regular expression to match the text.
14+
regex,
15+
}
16+
817
/// Text formatters that can be applied to the text field input to restrict the
918
/// input to a specific format.
19+
sealed class TextInputFormatterModel with EquatableMixin, SerializableMixin {
20+
/// The name of the formatter.
21+
final String name;
22+
23+
/// The type of formatter.
24+
@JsonKey(required: true, includeToJson: true)
25+
final TextInputFormatterType type;
26+
27+
/// Creates a new [TextInputFormatterModel] instance.
28+
const TextInputFormatterModel({required this.name, required this.type});
29+
30+
/// Creates a [TextInputFormatterModel] instance from a JSON object.
31+
factory TextInputFormatterModel.fromJson(Map<String, dynamic> json) {
32+
final TextInputFormatterType type =
33+
TextInputFormatterType.values.byName(json['type']);
34+
return switch (type) {
35+
TextInputFormatterType.none => NoneTextInputFormatter.fromJson(json),
36+
TextInputFormatterType.regex =>
37+
RegexTextInputFormatterModel.fromJson(json),
38+
};
39+
}
40+
41+
/// A list of all available text field formatters.
42+
static const List<TextInputFormatterModel> formatters = [
43+
NoneTextInputFormatter(),
44+
...RegexTextInputFormatterModel.formatters,
45+
];
46+
47+
/// A list of all available text field formatter names.
48+
static List<String> formatterNames = [
49+
for (final formatter in formatters) formatter.name,
50+
];
51+
52+
@override
53+
List<Object?> get props => [name, type];
54+
}
55+
56+
/// A formatter than does not restrict the input in any way.
1057
@JsonSerializable()
11-
class TextInputFormatterModel with EquatableMixin, SerializableMixin {
58+
class NoneTextInputFormatter extends TextInputFormatterModel {
59+
/// Creates a new [NoneTextInputFormatter] instance.
60+
const NoneTextInputFormatter()
61+
: super(name: 'None', type: TextInputFormatterType.none);
62+
63+
/// Creates a [TextInputFormatterModel] instance from a JSON object.
64+
factory NoneTextInputFormatter.fromJson(Map<String, dynamic> json) =>
65+
_$NoneTextInputFormatterFromJson(json);
66+
67+
@override
68+
Map toJson() => _$NoneTextInputFormatterToJson(this);
69+
}
70+
71+
/// Text formatters that can be applied to the text field input to restrict the
72+
/// input to a specific format.
73+
@JsonSerializable()
74+
class RegexTextInputFormatterModel extends TextInputFormatterModel {
1275
/// The regular expression to match the text.
1376
final String pattern;
1477

15-
/// The name of the formatter.
16-
final String name;
78+
/// Whether the regex pattern is case sensitive.
79+
final bool caseSensitive;
80+
81+
/// Whether the `.` pattern should match all characters, including
82+
/// line terminators.
83+
/// Defaults to `false`.
84+
final bool dotAll;
85+
86+
/// Whether the pattern should match across multiple lines.
87+
/// Defaults to `false`.
88+
final bool multiLine;
89+
90+
/// Whether the pattern should match unicode characters.
91+
/// Defaults to `false`.
92+
final bool unicode;
93+
94+
/// The string to replace the unmatched text with.
95+
final String replacementString;
96+
97+
/// Whether to allow or deny the pattern match.
98+
final bool allow;
1799

18100
/// Allow only digits in the text field.
19-
static const TextInputFormatterModel none = TextInputFormatterModel(
101+
static const RegexTextInputFormatterModel none = RegexTextInputFormatterModel(
20102
name: 'None',
21103
pattern: r'.*',
22104
);
23105

24106
/// Allow only digits in the text field.
25-
static const TextInputFormatterModel digitsOnly = TextInputFormatterModel(
107+
static const RegexTextInputFormatterModel digitsOnly =
108+
RegexTextInputFormatterModel(
26109
name: 'Digits Only',
27110
pattern: r'[0-9]',
28111
);
29112

30113
/// Allow only alphabets in the text field.
31-
static const TextInputFormatterModel alphabetsOnly =
32-
TextInputFormatterModel(name: 'Alphabets Only', pattern: r'[a-zA-Z]');
114+
static const RegexTextInputFormatterModel alphabetsOnly =
115+
RegexTextInputFormatterModel(
116+
name: 'Alphabets Only', pattern: r'[a-zA-Z]');
33117

34118
/// Allow only alpha-numeric characters in the text field.
35-
static const TextInputFormatterModel alphaNumeric =
36-
TextInputFormatterModel(name: 'Alpha-numeric', pattern: r'[a-zA-Z0-9]');
119+
static const RegexTextInputFormatterModel alphaNumeric =
120+
RegexTextInputFormatterModel(
121+
name: 'Alpha-numeric', pattern: r'[a-zA-Z0-9]');
37122

38123
/// Allow only phone number format in the text field.
39-
static const TextInputFormatterModel noSpaces =
40-
TextInputFormatterModel(name: 'No Spaces', pattern: r'^[^\s]*$');
41-
42-
/// Allow only email format in the text field.
43-
static const TextInputFormatterModel email =
44-
TextInputFormatterModel(name: 'Email', pattern: '^(|\\S)+\$');
124+
static const RegexTextInputFormatterModel noSpaces =
125+
RegexTextInputFormatterModel(name: 'No Spaces', pattern: r'^[^\s]*$');
45126

46127
/// Allow only phone number format in the text field.
47-
static const TextInputFormatterModel phoneNumber =
48-
TextInputFormatterModel(name: 'Phone Number', pattern: r'^+?[0-9]*$');
128+
static const RegexTextInputFormatterModel custom =
129+
RegexTextInputFormatterModel(name: 'Custom Regex', pattern: r'');
130+
131+
/// Whether the formatter is a custom formatter.
132+
bool get isCustom => name == custom.name;
49133

50134
/// List of all available text field formatters.
51-
static const List<TextInputFormatterModel> values = [
52-
none,
135+
static const List<RegexTextInputFormatterModel> formatters = [
53136
digitsOnly,
54137
alphabetsOnly,
55138
alphaNumeric,
56139
noSpaces,
57-
email,
58-
phoneNumber,
140+
custom,
59141
];
60142

61-
/// Returns true if the formatter is [none].
62-
bool get isNone => this == none;
63-
64-
/// Creates a new [TextInputFormatterModel] instance with the given pattern.
65-
const TextInputFormatterModel({required this.name, required this.pattern});
143+
/// Creates a new [RegexTextInputFormatterModel] instance with the given pattern.
144+
const RegexTextInputFormatterModel({
145+
required super.name,
146+
required this.pattern,
147+
this.allow = true,
148+
this.caseSensitive = true,
149+
this.dotAll = false,
150+
this.multiLine = false,
151+
this.unicode = false,
152+
this.replacementString = '',
153+
}) : super(type: TextInputFormatterType.regex);
66154

67155
/// copyWith
68-
TextInputFormatterModel copyWith({
69-
String? name,
156+
RegexTextInputFormatterModel copyWith({
70157
String? pattern,
158+
bool? caseSensitive,
159+
bool? dotAll,
160+
bool? multiLine,
161+
bool? unicode,
162+
bool? allow,
163+
String? replacementString,
71164
}) {
72-
return TextInputFormatterModel(
73-
name: name ?? this.name,
165+
return RegexTextInputFormatterModel(
166+
name: name,
74167
pattern: pattern ?? this.pattern,
168+
caseSensitive: caseSensitive ?? this.caseSensitive,
169+
dotAll: dotAll ?? this.dotAll,
170+
multiLine: multiLine ?? this.multiLine,
171+
unicode: unicode ?? this.unicode,
172+
allow: allow ?? this.allow,
173+
replacementString: replacementString ?? this.replacementString,
75174
);
76175
}
77176

78177
/// Creates a [TextInputFormatterModel] instance from a JSON object.
79-
factory TextInputFormatterModel.fromJson(Map json) =>
80-
_$TextInputFormatterModelFromJson(json);
178+
factory RegexTextInputFormatterModel.fromJson(Map json) =>
179+
_$RegexTextInputFormatterModelFromJson(json);
81180

82181
@override
83-
Map toJson() => _$TextInputFormatterModelToJson(this);
182+
Map toJson() => _$RegexTextInputFormatterModelToJson(this);
84183

85184
@override
86-
List<Object?> get props => [name];
185+
List<Object?> get props => [
186+
...super.props,
187+
pattern,
188+
caseSensitive,
189+
dotAll,
190+
multiLine,
191+
unicode,
192+
replacementString,
193+
allow,
194+
];
87195
}

lib/src/api/models/text_input_formatter_model.g.dart

Lines changed: 50 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)