Fix cannot auto create tag when paste list email and switch focus
Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
@@ -6,7 +6,7 @@ import 'package:core/domain/exceptions/string_exception.dart';
|
|||||||
import 'package:http_parser/http_parser.dart';
|
import 'package:http_parser/http_parser.dart';
|
||||||
|
|
||||||
class StringConvert {
|
class StringConvert {
|
||||||
static const String separatorPattern = r'[ ,;]+';
|
static const String emailSeparatorPattern = r'[,;]+';
|
||||||
|
|
||||||
static String? writeEmptyToNull(String text) {
|
static String? writeEmptyToNull(String text) {
|
||||||
if (text.isEmpty) return null;
|
if (text.isEmpty) return null;
|
||||||
@@ -26,23 +26,28 @@ class StringConvert {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<String> extractStrings(String input) {
|
static List<String> extractStrings(String input, String separatorPattern) {
|
||||||
try {
|
try {
|
||||||
// Check if the input is URL encoded
|
// Check if the input is URL encoded
|
||||||
if (input.contains('%')) {
|
if (input.contains('%')) {
|
||||||
input = Uri.decodeComponent(input); // Decode URL encoding
|
input = Uri.decodeComponent(input); // Decode URL encoding
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the input is Base64 encoded
|
// Efficient Base64 validation: Check length and minimal regex match
|
||||||
if (input.length % 4 == 0 && RegExp(r'^[A-Za-z0-9+/=]+$').hasMatch(input)) {
|
if (input.length % 4 == 0 && input.contains(RegExp(r'^[A-Za-z0-9+/=]+$'))) {
|
||||||
input = utf8.decode(base64.decode(input)); // Decode Base64 encoding
|
try {
|
||||||
|
input = utf8.decode(base64.decode(input)); // Decode Base64 encoding
|
||||||
|
} catch (_) {
|
||||||
|
// Ignore if decoding fails
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final RegExp separator = RegExp(separatorPattern);
|
final RegExp separator = RegExp(separatorPattern);
|
||||||
final listStrings = input
|
final listStrings = input
|
||||||
.replaceAll('\n', ' ')
|
.replaceAll('\n', ' ')
|
||||||
.split(separator)
|
.split(separator)
|
||||||
.where((value) => value.trim().isNotEmpty)
|
.map((value) => value.trim())
|
||||||
|
.where((value) => value.isNotEmpty)
|
||||||
.toList();
|
.toList();
|
||||||
log('StringConvert::extractStrings:listStrings = $listStrings');
|
log('StringConvert::extractStrings:listStrings = $listStrings');
|
||||||
return listStrings;
|
return listStrings;
|
||||||
@@ -51,6 +56,9 @@ class StringConvert {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<String> extractEmailAddress(String input) =>
|
||||||
|
extractStrings(input, emailSeparatorPattern);
|
||||||
|
|
||||||
static String decodeFromBytes(
|
static String decodeFromBytes(
|
||||||
Uint8List bytes, {
|
Uint8List bytes, {
|
||||||
required String? charset,
|
required String? charset,
|
||||||
|
|||||||
@@ -41,24 +41,24 @@ void main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('StringConvert::extractStrings::', () {
|
group('StringConvert::extractEmailAddress::', () {
|
||||||
group('Basic Functionality', () {
|
group('Basic Functionality', () {
|
||||||
test('should extract strings separated by spaces', () {
|
test('should not extract strings separated by spaces', () {
|
||||||
const input = 'user1@example.com user2@example.com';
|
const input = 'user1@example.com user2@example.com';
|
||||||
final expected = ['user1@example.com', 'user2@example.com'];
|
final expected = ['user1@example.com user2@example.com'];
|
||||||
expect(StringConvert.extractStrings(input), equals(expected));
|
expect(StringConvert.extractEmailAddress(input), equals(expected));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should extract strings separated by commas', () {
|
test('should extract strings separated by commas', () {
|
||||||
const input = 'user1@example.com,user2@example.com';
|
const input = 'user1@example.com,user2@example.com';
|
||||||
final expected = ['user1@example.com', 'user2@example.com'];
|
final expected = ['user1@example.com', 'user2@example.com'];
|
||||||
expect(StringConvert.extractStrings(input), equals(expected));
|
expect(StringConvert.extractEmailAddress(input), equals(expected));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should extract strings separated by semicolons', () {
|
test('should extract strings separated by semicolons', () {
|
||||||
const input = 'user1@example.com;user2@example.com';
|
const input = 'user1@example.com;user2@example.com';
|
||||||
final expected = ['user1@example.com', 'user2@example.com'];
|
final expected = ['user1@example.com', 'user2@example.com'];
|
||||||
expect(StringConvert.extractStrings(input), equals(expected));
|
expect(StringConvert.extractEmailAddress(input), equals(expected));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should extract strings separated by mixed separators', () {
|
test('should extract strings separated by mixed separators', () {
|
||||||
@@ -68,7 +68,7 @@ void main() {
|
|||||||
'user2@example.com',
|
'user2@example.com',
|
||||||
'user3@example.com'
|
'user3@example.com'
|
||||||
];
|
];
|
||||||
expect(StringConvert.extractStrings(input), equals(expected));
|
expect(StringConvert.extractEmailAddress(input), equals(expected));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle multiple consecutive separators', () {
|
test('should handle multiple consecutive separators', () {
|
||||||
@@ -79,7 +79,7 @@ void main() {
|
|||||||
'user2@example.com',
|
'user2@example.com',
|
||||||
'user3@example.com'
|
'user3@example.com'
|
||||||
];
|
];
|
||||||
expect(StringConvert.extractStrings(input), equals(expected));
|
expect(StringConvert.extractEmailAddress(input), equals(expected));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -87,19 +87,19 @@ void main() {
|
|||||||
test('should handle empty input', () {
|
test('should handle empty input', () {
|
||||||
const input = '';
|
const input = '';
|
||||||
final expected = <String>[];
|
final expected = <String>[];
|
||||||
expect(StringConvert.extractStrings(input), equals(expected));
|
expect(StringConvert.extractEmailAddress(input), equals(expected));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle input with only separators', () {
|
test('should handle input with only separators', () {
|
||||||
const input = ',, ;; ';
|
const input = ',, ;; ';
|
||||||
final expected = <String>[];
|
final expected = <String>[];
|
||||||
expect(StringConvert.extractStrings(input), equals(expected));
|
expect(StringConvert.extractEmailAddress(input), equals(expected));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle trailing and leading separators', () {
|
test('should handle trailing and leading separators', () {
|
||||||
const input = ', user1@example.com; user2@example.com ,';
|
const input = ', user1@example.com; user2@example.com ,';
|
||||||
final expected = ['user1@example.com', 'user2@example.com'];
|
final expected = ['user1@example.com', 'user2@example.com'];
|
||||||
expect(StringConvert.extractStrings(input), equals(expected));
|
expect(StringConvert.extractEmailAddress(input), equals(expected));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle extra spaces between separators', () {
|
test('should handle extra spaces between separators', () {
|
||||||
@@ -109,7 +109,7 @@ void main() {
|
|||||||
'user2@example.com',
|
'user2@example.com',
|
||||||
'user3@example.com'
|
'user3@example.com'
|
||||||
];
|
];
|
||||||
expect(StringConvert.extractStrings(input), equals(expected));
|
expect(StringConvert.extractEmailAddress(input), equals(expected));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -118,11 +118,11 @@ void main() {
|
|||||||
final input = List.generate(
|
final input = List.generate(
|
||||||
1000,
|
1000,
|
||||||
(index) =>
|
(index) =>
|
||||||
'user$index@example.com${index % 3 == 0 ? ',' : index % 3 == 1 ? ';' : ' '}',
|
'user$index@example.com${index % 3 == 0 ? ',' : ';'}',
|
||||||
).join();
|
).join();
|
||||||
final expected =
|
final expected =
|
||||||
List.generate(1000, (index) => 'user$index@example.com');
|
List.generate(1000, (index) => 'user$index@example.com');
|
||||||
expect(StringConvert.extractStrings(input), equals(expected));
|
expect(StringConvert.extractEmailAddress(input), equals(expected));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -130,43 +130,37 @@ void main() {
|
|||||||
test('should handle URL encoded input', () {
|
test('should handle URL encoded input', () {
|
||||||
String input = 'user1%40example.com%20user2%40example.com%20user3%40example.com';
|
String input = 'user1%40example.com%20user2%40example.com%20user3%40example.com';
|
||||||
final expected = [
|
final expected = [
|
||||||
'user1@example.com',
|
'user1@example.com user2@example.com user3@example.com'
|
||||||
'user2@example.com',
|
|
||||||
'user3@example.com'
|
|
||||||
];
|
];
|
||||||
expect(StringConvert.extractStrings(input), equals(expected));
|
expect(StringConvert.extractEmailAddress(input), equals(expected));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle Base64 encoded input', () {
|
test('should handle Base64 encoded input', () {
|
||||||
String input = 'dXNlcjFAZXhhbXBsZS5jb20gdXNlcjJAZXhhbXBsZS5jb20gdXNlcjNAZXhhbXBsZS5jb20=';
|
String input = 'dXNlcjFAZXhhbXBsZS5jb20gdXNlcjJAZXhhbXBsZS5jb20gdXNlcjNAZXhhbXBsZS5jb20=';
|
||||||
final expected = [
|
final expected = [
|
||||||
'user1@example.com',
|
'user1@example.com user2@example.com user3@example.com'
|
||||||
'user2@example.com',
|
|
||||||
'user3@example.com'
|
|
||||||
];
|
];
|
||||||
expect(StringConvert.extractStrings(input), equals(expected));
|
expect(StringConvert.extractEmailAddress(input), equals(expected));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle input with both URL encoding and Base64 encoding', () {
|
test('should handle input with both URL encoding and Base64 encoding', () {
|
||||||
String input = Uri.encodeComponent(base64.encode(utf8.encode('user1@example.com user2@example.com user3@example.com')));
|
String input = Uri.encodeComponent(base64.encode(utf8.encode('user1@example.com user2@example.com user3@example.com')));
|
||||||
final expected = [
|
final expected = [
|
||||||
'user1@example.com',
|
'user1@example.com user2@example.com user3@example.com'
|
||||||
'user2@example.com',
|
|
||||||
'user3@example.com'
|
|
||||||
];
|
];
|
||||||
expect(StringConvert.extractStrings(input), equals(expected));
|
expect(StringConvert.extractEmailAddress(input), equals(expected));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Failing Cases', () {
|
group('Failing Cases', () {
|
||||||
test('should return empty list for empty input', () {
|
test('should return empty list for empty input', () {
|
||||||
String input = '';
|
String input = '';
|
||||||
expect(StringConvert.extractStrings(input), equals([]));
|
expect(StringConvert.extractEmailAddress(input), equals([]));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return empty list if input is only separators', () {
|
test('should return empty list if input is only separators', () {
|
||||||
String input = ' , ; ';
|
String input = ' , ; ';
|
||||||
expect(StringConvert.extractStrings(input), equals([]));
|
expect(StringConvert.extractEmailAddress(input), equals([]));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return correct result for input with invalid separators', () {
|
test('should return correct result for input with invalid separators', () {
|
||||||
@@ -176,23 +170,22 @@ void main() {
|
|||||||
'user2@example.com',
|
'user2@example.com',
|
||||||
'user3@example.com'
|
'user3@example.com'
|
||||||
];
|
];
|
||||||
expect(StringConvert.extractStrings(input), equals(expected));
|
expect(StringConvert.extractEmailAddress(input), equals(expected));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return empty list for input with only whitespace and separators', () {
|
test('should return empty list for input with only whitespace and separators', () {
|
||||||
String input = ' , ; \n';
|
String input = ' , ; \n';
|
||||||
expect(StringConvert.extractStrings(input), equals([]));
|
expect(StringConvert.extractEmailAddress(input), equals([]));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle input with newline characters', () {
|
test('should handle input with newline characters', () {
|
||||||
// Arrange
|
// Arrange
|
||||||
String input = 'user1@example.com\nuser2@example.com;user3@example.com';
|
String input = 'user1@example.com\nuser2@example.com;user3@example.com';
|
||||||
final expected = [
|
final expected = [
|
||||||
'user1@example.com',
|
'user1@example.com user2@example.com',
|
||||||
'user2@example.com',
|
|
||||||
'user3@example.com'
|
'user3@example.com'
|
||||||
];
|
];
|
||||||
expect(StringConvert.extractStrings(input), equals(expected));
|
expect(StringConvert.extractEmailAddress(input), equals(expected));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+22
-9
@@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
import 'package:core/utils/mail/mail_address.dart';
|
import 'package:core/utils/mail/mail_address.dart';
|
||||||
|
import 'package:core/utils/string_convert.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||||
import 'package:model/email/prefix_email_address.dart';
|
import 'package:model/email/prefix_email_address.dart';
|
||||||
@@ -39,21 +40,33 @@ extension AutoCreateTagForRecipientsExtension on ComposerController {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void autoCreateEmailTagForType(PrefixEmailAddress type, String input) {
|
void autoCreateEmailTagForType(PrefixEmailAddress type, String input) {
|
||||||
final emailAddressRecord = _generateEmailAddressFromString(input);
|
final addressSet = StringConvert.extractEmailAddress(input).toSet();
|
||||||
|
if (addressSet.isEmpty) return;
|
||||||
|
|
||||||
final emailList = _emailLists[type]!;
|
final emailList = _emailLists[type]!;
|
||||||
final keyEditor = _emailEditors[type]!;
|
final keyEditor = _emailEditors[type]!;
|
||||||
|
final listEmailAddressRecord = addressSet
|
||||||
|
.map((address) => _generateEmailAddressFromString(address))
|
||||||
|
.toList();
|
||||||
|
|
||||||
final emailSet = emailList.toSet();
|
final emailSet = emailList.map((email) => email.email).toSet();
|
||||||
if (emailSet.any((email) => email.email == emailAddressRecord.$1)) return;
|
|
||||||
|
|
||||||
emailSet.add(emailAddressRecord.$2);
|
final newEmails = addressSet.difference(emailSet);
|
||||||
emailList
|
|
||||||
..clear()
|
if (newEmails.isEmpty) return;
|
||||||
..addAll(emailSet);
|
|
||||||
|
final listEmailAddress = listEmailAddressRecord
|
||||||
|
.where((emailRecord) => newEmails.contains(emailRecord.$1))
|
||||||
|
.map((emailRecord) => emailRecord.$2)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
emailList.addAll(listEmailAddress);
|
||||||
|
|
||||||
|
if (!isInitialRecipient.value) {
|
||||||
|
isInitialRecipient.value = true;
|
||||||
|
isInitialRecipient.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
isInitialRecipient.value = true;
|
|
||||||
isInitialRecipient.refresh();
|
|
||||||
updateStatusEmailSendButton();
|
updateStatusEmailSendButton();
|
||||||
|
|
||||||
keyEditor.currentState?.resetTextField();
|
keyEditor.currentState?.resetTextField();
|
||||||
|
|||||||
@@ -531,7 +531,7 @@ class _RecipientComposerWidgetState extends State<RecipientComposerWidget> {
|
|||||||
) => _createMailTag(value, stateSetter);
|
) => _createMailTag(value, stateSetter);
|
||||||
|
|
||||||
void _createMailTag(String value, StateSetter stateSetter) {
|
void _createMailTag(String value, StateSetter stateSetter) {
|
||||||
final listString = StringConvert.extractStrings(value.trim()).toSet();
|
final listString = StringConvert.extractEmailAddress(value.trim()).toSet();
|
||||||
|
|
||||||
if (listString.isEmpty && !_isDuplicatedRecipient(value)) {
|
if (listString.isEmpty && !_isDuplicatedRecipient(value)) {
|
||||||
_onEmailAddressReceived(value, stateSetter);
|
_onEmailAddressReceived(value, stateSetter);
|
||||||
|
|||||||
Reference in New Issue
Block a user