TF-585 Fix identity restrict BCC field

This commit is contained in:
dab246
2022-05-26 12:09:14 +07:00
committed by Dat H. Pham
parent b4fda5e9aa
commit cd02444941
9 changed files with 259 additions and 25 deletions
@@ -12,6 +12,7 @@ import 'package:model/model.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/model/identity_creator_arguments.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/model/signature_type.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/email_address_validator.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/empty_name_validator.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/state/verify_name_view_state.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart';
@@ -31,6 +32,7 @@ class IdentityCreatorController extends BaseController {
final listEmailAddressDefault = <EmailAddress>[].obs;
final listEmailAddressOfReplyTo = <EmailAddress>[].obs;
final errorNameIdentity = Rxn<String>();
final errorBccIdentity = Rxn<String>();
final emailOfIdentity = Rxn<EmailAddress>();
final replyToOfIdentity = Rxn<EmailAddress>();
final bccOfIdentity = Rxn<EmailAddress>();
@@ -39,6 +41,7 @@ class IdentityCreatorController extends BaseController {
final HtmlEditorController signatureHtmlEditorController = HtmlEditorController(processNewLineAsBr: true);
final TextEditingController signaturePlainEditorController = TextEditingController();
final TextEditingController inputNameIdentityController = TextEditingController();
final TextEditingController inputBccIdentityController = TextEditingController();
final FocusNode inputNameIdentityFocusNode = FocusNode();
HtmlEditorApi? signatureHtmlEditorMobileController;
@@ -77,6 +80,7 @@ class IdentityCreatorController extends BaseController {
signaturePlainEditorController.dispose();
inputNameIdentityFocusNode.dispose();
inputNameIdentityController.dispose();
inputBccIdentityController.dispose();
super.onClose();
}
@@ -141,6 +145,8 @@ class IdentityCreatorController extends BaseController {
listEmailAddressOfReplyTo.add(noneEmailAddress);
listEmailAddressOfReplyTo.addAll(listEmailAddressDefault);
log('IdentityCreatorController::_getALlIdentitiesSuccess(): listEmailAddressOfReplyTo: ${listEmailAddressOfReplyTo.toJson()}');
_setUpAllFieldEmailAddress();
} else {
_setDefaultEmailAddressList();
@@ -165,24 +171,48 @@ class IdentityCreatorController extends BaseController {
void _setUpAllFieldEmailAddress() {
if (actionType.value == IdentityActionType.edit && identity != null) {
replyToOfIdentity.value = identity?.replyTo?.isNotEmpty == true
? identity!.replyTo!.first
: noneEmailAddress;
if (identity?.replyTo?.isNotEmpty == true) {
try {
replyToOfIdentity.value = listEmailAddressOfReplyTo
.firstWhere((emailAddress) => emailAddress == identity?.replyTo!.first);
} catch(e) {
replyToOfIdentity.value = null;
}
} else {
replyToOfIdentity.value = null;
}
log('IdentityCreatorController::_setUpAllFieldEmailAddress(): replyToOfIdentity: ${replyToOfIdentity.value}');
bccOfIdentity.value = identity?.bcc?.isNotEmpty == true
? identity!.bcc!.first
: noneEmailAddress;
if (identity?.bcc?.isNotEmpty == true) {
try {
bccOfIdentity.value = listEmailAddressOfReplyTo
.firstWhere((emailAddress) => emailAddress == identity?.bcc!.first);
inputBccIdentityController.text = bccOfIdentity.value?.email ?? '';
} catch(e) {
bccOfIdentity.value = null;
}
} else {
bccOfIdentity.value = null;
}
log('IdentityCreatorController::_setUpAllFieldEmailAddress(): bccOfIdentity: ${bccOfIdentity.value}');
if (identity?.email?.isNotEmpty == true) {
emailOfIdentity.value = EmailAddress(null, identity?.email!);
try {
emailOfIdentity.value = listEmailAddressDefault
.firstWhere((emailAddress) => emailAddress.email == identity?.email);
} catch(e) {
emailOfIdentity.value = null;
}
} else {
emailOfIdentity.value = listEmailAddressDefault.isNotEmpty
? listEmailAddressDefault.first
: null;
}
} else {
replyToOfIdentity.value = noneEmailAddress;
bccOfIdentity.value = noneEmailAddress;
replyToOfIdentity.value = null;
bccOfIdentity.value = null;
emailOfIdentity.value = listEmailAddressDefault.isNotEmpty
? listEmailAddressDefault.first
: null;
@@ -220,6 +250,8 @@ class IdentityCreatorController extends BaseController {
}
void createNewIdentity(BuildContext context) async {
clearFocusEditor(context);
final error = _getErrorInputNameString(context);
if (error?.isNotEmpty == true) {
log('IdentityCreatorController::createNewIdentity(): error: $error');
@@ -227,6 +259,13 @@ class IdentityCreatorController extends BaseController {
return;
}
final errorBcc = _getErrorInputAddressString(context);
if (errorBcc?.isNotEmpty == true) {
log('IdentityCreatorController::createNewIdentity(): errorBcc: $errorBcc');
errorBccIdentity.value = errorBcc;
return;
}
final signatureHtmlText = await _getSignatureHtmlText();
log('IdentityCreatorController::createNewIdentity(): signatureHtmlText: $signatureHtmlText');
@@ -246,7 +285,6 @@ class IdentityCreatorController extends BaseController {
log('IdentityCreatorController::createNewIdentity(): $newIdentity');
_clearAll();
clearFocusEditor(context);
popBack(result: newIdentity);
}
@@ -266,9 +304,47 @@ class IdentityCreatorController extends BaseController {
);
}
String? _getErrorInputAddressString(BuildContext context, {String? value}) {
final emailAddress = value ?? inputBccIdentityController.text;
if (emailAddress.trim().isEmpty) {
return null;
}
return _verifyNameInteractor.execute(
emailAddress,
[EmailAddressValidator()]
).fold(
(failure) {
if (failure is VerifyNameFailure) {
return failure.getMessageIdentity(context);
} else {
return null;
}
},
(success) => null
);
}
void validateInputBccAddress(BuildContext context, String? value) {
final errorBcc = _getErrorInputAddressString(context, value: value);
if (errorBccIdentity.value?.isNotEmpty == true
&& (errorBcc == null || errorBcc.isEmpty)) {
errorBccIdentity.value = null;
}
}
void _clearAll() {
signaturePlainEditorController.clear();
inputNameIdentityController.clear();
inputBccIdentityController.clear();
}
List<EmailAddress> getSuggestionEmailAddress(String? pattern) {
if (pattern == null || pattern.isEmpty) {
return List.empty();
}
return listEmailAddressOfReplyTo
.where((emailAddress) => emailAddress.email?.contains(pattern) == true)
.toList();
}
void clearFocusEditor(BuildContext context) {
@@ -4,11 +4,13 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:html_editor_enhanced/html_editor.dart' as html_editor_browser;
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/identity_creator_controller.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/model/signature_type.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_drop_list_field_builder.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_field_no_editable_builder.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_input_field_builder.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_input_with_drop_list_field_builder.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/model/identity_action_type.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
@@ -175,14 +177,22 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController> {
controller.updaterReplyToOfIdentity(emailAddress)))
.build())),
const SizedBox(width: 24),
Expanded(child: Obx(() => (IdentityDropListFieldBuilder(
_imagePaths,
Expanded(child: Obx(() => (IdentityInputWithDropListFieldBuilder(
AppLocalizations.of(context).bcc_to_address,
controller.bccOfIdentity.value,
controller.listEmailAddressOfReplyTo)
..addOnSelectEmailAddressDropListAction((emailAddress) =>
controller.updateBccOfIdentity(emailAddress)))
.build())),
controller.errorBccIdentity.value,
controller.inputBccIdentityController)
..addOnSelectedSuggestionAction((newEmailAddress) {
controller.inputBccIdentityController.text = newEmailAddress?.email ?? '';
controller.updateBccOfIdentity(newEmailAddress);
})
..addOnChangeInputSuggestionAction((pattern) {
controller.validateInputBccAddress(context, pattern);
controller.updateBccOfIdentity(EmailAddress(null, pattern));
})
..addOnSuggestionCallbackAction((pattern) =>
controller.getSuggestionEmailAddress(pattern)))
.build()
)),
]),
const SizedBox(height: 32),
Row(children: [
@@ -308,14 +318,22 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController> {
controller.updaterReplyToOfIdentity(newEmailAddress)))
.build()),
const SizedBox(height: 24),
Obx(() => (IdentityDropListFieldBuilder(
_imagePaths,
Obx(() => (IdentityInputWithDropListFieldBuilder(
AppLocalizations.of(context).bcc_to_address,
controller.bccOfIdentity.value,
controller.listEmailAddressOfReplyTo)
..addOnSelectEmailAddressDropListAction((newEmailAddress) =>
controller.updateBccOfIdentity(newEmailAddress)))
.build()),
controller.errorBccIdentity.value,
controller.inputBccIdentityController)
..addOnSelectedSuggestionAction((newEmailAddress) {
controller.inputBccIdentityController.text = newEmailAddress?.email ?? '';
controller.updateBccOfIdentity(newEmailAddress);
})
..addOnChangeInputSuggestionAction((pattern) {
controller.validateInputBccAddress(context, pattern);
controller.updateBccOfIdentity(EmailAddress(null, pattern));
})
..addOnSuggestionCallbackAction((pattern) =>
controller.getSuggestionEmailAddress(pattern)))
.build()
),
const SizedBox(height: 32),
Text(AppLocalizations.of(context).signature,
style: const TextStyle(
@@ -0,0 +1,94 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_input_decoration_builder.dart';
typedef OnSelectedSuggestionAction = Function(EmailAddress? emailAddress);
typedef OnSuggestionCallbackAction = Function(String? pattern);
typedef OnChangeInputSuggestionAction = Function(String? pattern);
class IdentityInputWithDropListFieldBuilder {
final String _label;
final String? _error;
final TextEditingController editingController;
OnSelectedSuggestionAction? _onSelectedSuggestionAction;
OnSuggestionCallbackAction? _onSuggestionCallbackAction;
OnChangeInputSuggestionAction? _onChangeInputSuggestionAction;
IdentityInputWithDropListFieldBuilder(
this._label,
this._error,
this.editingController,
);
void addOnSelectedSuggestionAction(OnSelectedSuggestionAction action) {
_onSelectedSuggestionAction = action;
}
void addOnSuggestionCallbackAction(OnSuggestionCallbackAction action) {
_onSuggestionCallbackAction = action;
}
void addOnChangeInputSuggestionAction(OnChangeInputSuggestionAction action) {
_onChangeInputSuggestionAction = action;
}
Widget build() {
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(_label, style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: AppColor.colorContentEmail)),
const SizedBox(height: 8),
TypeAheadFormField<EmailAddress>(
textFieldConfiguration: TextFieldConfiguration(
controller: editingController,
textInputAction: TextInputAction.done,
decoration: (IdentityInputDecorationBuilder()
..setContentPadding(const EdgeInsets.symmetric(
vertical: BuildUtils.isWeb ? 16 : 12,
horizontal: 12))
..setErrorText(_error))
.build()
),
debounceDuration: const Duration(milliseconds: 500),
suggestionsCallback: (pattern) async {
if (_onChangeInputSuggestionAction != null) {
_onChangeInputSuggestionAction!(pattern);
}
if (_onSuggestionCallbackAction != null) {
return _onSuggestionCallbackAction!(pattern);
} else {
return [];
}
},
itemBuilder: (BuildContext context, emailAddress) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Text(emailAddress.email ?? '',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: Colors.black)),
);
},
onSuggestionSelected: (emailSelected) {
if (_onSelectedSuggestionAction != null) {
_onSelectedSuggestionAction!(emailSelected);
}
},
suggestionsBoxDecoration: SuggestionsBoxDecoration(
borderRadius: BorderRadius.circular(14)
),
noItemsFoundBuilder: (context) => const SizedBox(),
hideOnEmpty: true,
hideOnError: true,
hideOnLoading: true,
)
]);
}
}
@@ -5,6 +5,7 @@ abstract class VerifyNameException extends Equatable implements Exception {
static const emptyName = 'The name cannot be empty!';
static const duplicatedName = 'The name already exists!';
static const nameContainSpecialCharacter = 'The name cannot contain special characters';
static const emailAddressInvalid = 'The email address invalid';
final String? message;
@@ -28,6 +29,13 @@ class DuplicatedNameException extends VerifyNameException {
class SpecialCharacterException extends VerifyNameException {
const SpecialCharacterException() : super(VerifyNameException.nameContainSpecialCharacter);
@override
List<Object> get props => [];
}
class EmailAddressInvalidException extends VerifyNameException {
const EmailAddressInvalidException() : super(VerifyNameException.emailAddressInvalid);
@override
List<Object> get props => [];
}
@@ -0,0 +1,20 @@
import 'package:core/core.dart';
import 'package:dartz/dartz.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/exceptions/verify_name_exception.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/new_name_request.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/validator.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/state/verify_name_view_state.dart';
class EmailAddressValidator extends Validator<NewNameRequest> {
@override
Either<Failure, Success> validate(NewNameRequest newNameRequest) {
if (newNameRequest.value != null && GetUtils.isEmail(newNameRequest.value!)) {
return Right<Failure, Success>(VerifyNameViewState());
} else {
return Left<Failure, Success>(VerifyNameFailure(const EmailAddressInvalidException()));
}
}
}
@@ -28,6 +28,8 @@ extension ValicatorFailureExtension on VerifyNameFailure {
String getMessageIdentity(BuildContext context) {
if (exception is EmptyNameException) {
return AppLocalizations.of(context).this_field_cannot_be_blank;
} else if (exception is EmailAddressInvalidException) {
return AppLocalizations.of(context).thisEmailAddressInvalid;
} else {
return '';
}
+7 -1
View File
@@ -1,5 +1,5 @@
{
"@@last_modified": "2022-05-24T09:43:00.761124",
"@@last_modified": "2022-05-26T12:07:26.941919",
"initializing_data": "Initializing data...",
"@initializing_data": {
"type": "text",
@@ -1347,5 +1347,11 @@
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"thisEmailAddressInvalid": "This email address invalid",
"@thisEmailAddressInvalid": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
}
}
@@ -1380,4 +1380,11 @@ class AppLocalizations {
'Last years',
name: 'lastYears');
}
String get thisEmailAddressInvalid {
return Intl.message(
'This email address invalid',
name: 'thisEmailAddressInvalid',
);
}
}
+3
View File
@@ -64,6 +64,9 @@ dependencies:
# flutter_dotenv
flutter_dotenv: 5.0.2
# flutter_typeahead
flutter_typeahead: 3.2.7
built_collection: 5.1.1
# jmap_dart_client