TF-2830 Add recipient directly to account when click + Add recipients

This commit is contained in:
dab246
2024-05-07 12:36:12 +07:00
committed by Dat H. Pham
parent 045572a301
commit bd06d3174f
4 changed files with 148 additions and 84 deletions
@@ -1,3 +1,5 @@
class NotFoundForwardException implements Exception { class NotFoundForwardException implements Exception {}
NotFoundForwardException();
} class RecipientListIsEmptyException implements Exception {}
class RecipientListWithInvalidEmailsException implements Exception {}
@@ -15,6 +15,7 @@ import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/base/state/banner_state.dart'; import 'package:tmail_ui_user/features/base/state/banner_state.dart';
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart'; import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
import 'package:tmail_ui_user/features/manage_account/domain/exceptions/forward_exception.dart';
import 'package:tmail_ui_user/features/manage_account/domain/model/add_recipients_in_forwarding_request.dart'; import 'package:tmail_ui_user/features/manage_account/domain/model/add_recipients_in_forwarding_request.dart';
import 'package:tmail_ui_user/features/manage_account/domain/model/delete_recipient_in_forwarding_request.dart'; import 'package:tmail_ui_user/features/manage_account/domain/model/delete_recipient_in_forwarding_request.dart';
import 'package:tmail_ui_user/features/manage_account/domain/model/edit_local_copy_in_forwarding_request.dart'; import 'package:tmail_ui_user/features/manage_account/domain/model/edit_local_copy_in_forwarding_request.dart';
@@ -319,12 +320,12 @@ class ForwardController extends BaseController {
); );
} }
void handleExceptionCallback(BuildContext context, bool isListEmailEmpty) { void handleExceptionCallback(BuildContext context, Exception exception) {
if (isListEmailEmpty) { if (exception is RecipientListIsEmptyException) {
appToast.showToastErrorMessage( appToast.showToastErrorMessage(
context, context,
AppLocalizations.of(context).emptyListEmailForward); AppLocalizations.of(context).emptyListEmailForward);
} else { } else if (exception is RecipientListWithInvalidEmailsException) {
appToast.showToastErrorMessage( appToast.showToastErrorMessage(
context, context,
AppLocalizations.of(context).incorrectEmailFormat); AppLocalizations.of(context).incorrectEmailFormat);
@@ -133,8 +133,8 @@ class ForwardView extends GetWidget<ForwardController> with AppLoaderMixin {
onAddContactCallback: (listRecipientsSelected) { onAddContactCallback: (listRecipientsSelected) {
controller.addRecipientAction(context, listRecipientsSelected); controller.addRecipientAction(context, listRecipientsSelected);
}, },
onExceptionCallback: (isListEmailEmpty) { onExceptionCallback: (exception) {
controller.handleExceptionCallback(context, isListEmailEmpty); controller.handleExceptionCallback(context, exception);
}, },
); );
} }
@@ -4,6 +4,7 @@ import 'dart:async';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/keyboard_utils.dart';
import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart'; import 'package:core/utils/platform_info.dart';
@@ -19,6 +20,7 @@ import 'package:tmail_ui_user/features/composer/presentation/model/suggestion_em
import 'package:tmail_ui_user/features/contact/presentation/widgets/contact_input_tag_item.dart'; import 'package:tmail_ui_user/features/contact/presentation/widgets/contact_input_tag_item.dart';
import 'package:tmail_ui_user/features/contact/presentation/widgets/contact_suggestion_box_item.dart'; import 'package:tmail_ui_user/features/contact/presentation/widgets/contact_suggestion_box_item.dart';
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
import 'package:tmail_ui_user/features/manage_account/domain/exceptions/forward_exception.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/menu/settings_utils.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/menu/settings_utils.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/utils/app_config.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart';
@@ -26,7 +28,7 @@ import 'package:tmail_ui_user/main/utils/app_utils.dart';
typedef OnSuggestionContactCallbackAction = Future<List<EmailAddress>> Function(String query); typedef OnSuggestionContactCallbackAction = Future<List<EmailAddress>> Function(String query);
typedef OnAddListContactCallbackAction = Function(List<EmailAddress> listEmailAddress); typedef OnAddListContactCallbackAction = Function(List<EmailAddress> listEmailAddress);
typedef OnExceptionAddListContactCallbackAction = Function(bool isListEmpty); typedef OnExceptionAddListContactCallbackAction = Function(Exception exception);
class AutocompleteContactTextFieldWithTags extends StatefulWidget { class AutocompleteContactTextFieldWithTags extends StatefulWidget {
@@ -61,7 +63,6 @@ class _AutocompleteContactTextFieldWithTagsState extends State<AutocompleteConta
late List<EmailAddress> listEmailAddress; late List<EmailAddress> listEmailAddress;
Timer? _gapBetweenTagChangedAndFindSuggestion;
bool lastTagFocused = false; bool lastTagFocused = false;
@override @override
@@ -70,12 +71,6 @@ class _AutocompleteContactTextFieldWithTagsState extends State<AutocompleteConta
listEmailAddress = widget.listEmailAddress; listEmailAddress = widget.listEmailAddress;
} }
@override
void dispose() {
_gapBetweenTagChangedAndFindSuggestion?.cancel();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final itemTagEditor = TagEditor<SuggestionEmailAddress>( final itemTagEditor = TagEditor<SuggestionEmailAddress>(
@@ -118,7 +113,8 @@ class _AutocompleteContactTextFieldWithTagsState extends State<AutocompleteConta
), ),
onSubmitted: (value) => _addEmailAddressToInputFieldAction( onSubmitted: (value) => _addEmailAddressToInputFieldAction(
context: context, context: context,
emailAddress: EmailAddress(null, value) emailAddress: EmailAddress(null, value),
isClearInput: true
), ),
textStyle: const TextStyle( textStyle: const TextStyle(
color: Colors.black, color: Colors.black,
@@ -141,16 +137,7 @@ class _AutocompleteContactTextFieldWithTagsState extends State<AutocompleteConta
setState(() => listEmailAddress.remove(contact)); setState(() => listEmailAddress.remove(contact));
} }
), ),
onTagChanged: (value) { onTagChanged: (_) {},
_addEmailAddressToInputFieldAction(
context: context,
emailAddress: EmailAddress(null, value)
);
_gapBetweenTagChangedAndFindSuggestion = Timer(
const Duration(seconds: 1),
_handleGapBetweenTagChangedAndFindSuggestion
);
},
findSuggestions: _findSuggestions, findSuggestions: _findSuggestions,
suggestionBuilder: (context, tagEditorState, suggestionEmailAddress, index, length, highlight, suggestionValid) { suggestionBuilder: (context, tagEditorState, suggestionEmailAddress, index, length, highlight, suggestionValid) {
return Container( return Container(
@@ -162,7 +149,8 @@ class _AutocompleteContactTextFieldWithTagsState extends State<AutocompleteConta
selectedContactCallbackAction: (contact) { selectedContactCallbackAction: (contact) {
_addEmailAddressToInputFieldAction( _addEmailAddressToInputFieldAction(
context: context, context: context,
emailAddress: contact); emailAddress: contact
);
tagEditorState.closeSuggestionBox(); tagEditorState.closeSuggestionBox();
tagEditorState.resetTextField(); tagEditorState.resetTextField();
}, },
@@ -211,10 +199,6 @@ class _AutocompleteContactTextFieldWithTagsState extends State<AutocompleteConta
} }
FutureOr<List<SuggestionEmailAddress>> _findSuggestions(String query) async { FutureOr<List<SuggestionEmailAddress>> _findSuggestions(String query) async {
if (_gapBetweenTagChangedAndFindSuggestion?.isActive ?? false) {
return [];
}
final processedQuery = query.trim(); final processedQuery = query.trim();
if (processedQuery.isEmpty) { if (processedQuery.isEmpty) {
@@ -260,17 +244,7 @@ class _AutocompleteContactTextFieldWithTagsState extends State<AutocompleteConta
.map((emailAddress) => SuggestionEmailAddress(emailAddress, state: SuggestionEmailState.duplicated)); .map((emailAddress) => SuggestionEmailAddress(emailAddress, state: SuggestionEmailState.duplicated));
} }
void _handleGapBetweenTagChangedAndFindSuggestion() { bool _validateListEmailAddressIsValid(List<EmailAddress> listEmailAddress) => listEmailAddress.every(_validateEmailAddressIsValid);
log('_AutocompleteContactTextFieldWithTagsState::_handleGapBetweenTagChangedAndFindSuggestion(): Timeout');
}
bool _isValidAllEmailAddress(List<EmailAddress> addedEmailAddress) {
return addedEmailAddress.every((addedMail) => addedMail.emailAddress.isEmail || AppUtils.isEmailLocalhost(addedMail.emailAddress));
}
bool _inputFieldIsEmpty() {
return widget.controller?.text.isEmpty == true;
}
Widget _buildAddRecipientButton(BuildContext context, {double? maxWidth}) { Widget _buildAddRecipientButton(BuildContext context, {double? maxWidth}) {
return MaterialTextIconButton( return MaterialTextIconButton(
@@ -281,80 +255,167 @@ class _AutocompleteContactTextFieldWithTagsState extends State<AutocompleteConta
labelColor: Colors.white, labelColor: Colors.white,
iconColor: Colors.white, iconColor: Colors.white,
minimumSize: Size(maxWidth ?? 167, PlatformInfo.isMobile ? 44 : 54), minimumSize: Size(maxWidth ?? 167, PlatformInfo.isMobile ? 44 : 54),
onTap: _handleAddRecipientAction onTap: () => _handleAddRecipientAction(context)
); );
} }
void _handleAddRecipientAction() { void _handleAddRecipientAction(BuildContext context) {
_hideKeyboardForMobile(); KeyboardUtils.hideKeyboard(context);
if (widget.controller?.text.isNotEmpty == true) {
if (!_isDuplicatedRecipient(widget.controller?.text ?? '')) { final inputText = widget.controller?.text ?? '';
_addEmailAddressToInputFieldAction(
context: context, if (inputText.isNotEmpty) {
emailAddress: EmailAddress(null, widget.controller?.text) final emailAddress = EmailAddress(null, inputText);
);
if (!_validateEmailAddressIsValid(emailAddress)) {
widget.onExceptionCallback?.call(RecipientListWithInvalidEmailsException());
_resetInputText();
return;
} }
_validateEmailAddressSameDomain(
context: context,
emailAddress: emailAddress,
confirmAction: () {
final newListEmailAddress = List<EmailAddress>.from([...listEmailAddress, emailAddress]);
widget.onAddContactCallback?.call(newListEmailAddress);
_resetInputText();
if (listEmailAddress.isNotEmpty) {
setState(listEmailAddress.clear);
}
},
cancelAction: () {
if (listEmailAddress.isNotEmpty) {
widget.onAddContactCallback?.call(listEmailAddress);
setState(listEmailAddress.clear);
}
_resetInputText();
},
sameDomainAction: () {
final newListEmailAddress = List<EmailAddress>.from([...listEmailAddress, emailAddress]);
widget.onAddContactCallback?.call(newListEmailAddress);
_resetInputText();
if (listEmailAddress.isNotEmpty) {
setState(listEmailAddress.clear);
}
},
duplicatedRecipientAction: () {
if (listEmailAddress.isNotEmpty) {
widget.onAddContactCallback?.call(listEmailAddress);
setState(listEmailAddress.clear);
}
_resetInputText();
}
);
_closeSuggestionBox(); _closeSuggestionBox();
return; return;
} }
if (listEmailAddress.isEmpty) { if (listEmailAddress.isEmpty) {
widget.onExceptionCallback?.call(true); widget.onExceptionCallback?.call(RecipientListIsEmptyException());
return; return;
} }
if (_isValidAllEmailAddress(listEmailAddress) && _inputFieldIsEmpty()) { if (!_validateListEmailAddressIsValid(listEmailAddress)) {
widget.onAddContactCallback?.call(List.from(listEmailAddress)); widget.onExceptionCallback?.call(RecipientListWithInvalidEmailsException());
setState(() { return;
widget.controller?.clear();
listEmailAddress.clear();
});
} else {
widget.onExceptionCallback?.call(false);
} }
widget.onAddContactCallback?.call(List.from(listEmailAddress));
_resetInputText();
setState(listEmailAddress.clear);
} }
void _closeSuggestionBox() { bool _validateEmailAddressIsValid(EmailAddress emailAddress) {
keyToEmailTagEditor.currentState?.resetTextField(); return GetUtils.isEmail(emailAddress.emailAddress)
keyToEmailTagEditor.currentState?.closeSuggestionBox(); || AppUtils.isEmailLocalhost(emailAddress.emailAddress);
} }
void _hideKeyboardForMobile() { void _validateEmailAddressSameDomain({
if (!_responsiveUtils.isDesktop(context)) {
FocusScope.of(context).unfocus();
}
}
void _addEmailAddressToInputFieldAction({
required BuildContext context, required BuildContext context,
required EmailAddress emailAddress required EmailAddress emailAddress,
required VoidCallback? confirmAction,
required VoidCallback? cancelAction,
required VoidCallback? sameDomainAction,
required VoidCallback? duplicatedRecipientAction,
}) { }) {
if (_isDuplicatedRecipient(emailAddress.emailAddress)) { if (_isDuplicatedRecipient(emailAddress.emailAddress)) {
duplicatedRecipientAction?.call();
return; return;
} }
final validateSameDomain = EmailUtils.isSameDomain( bool isSameDomain = EmailUtils.isSameDomain(
emailAddress: emailAddress.emailAddress, emailAddress: emailAddress.emailAddress,
internalDomain: widget.internalDomain internalDomain: widget.internalDomain
); );
if (!validateSameDomain) { if (isSameDomain) {
sameDomainAction?.call();
} else {
_showWarningDialogWithExternalDomain( _showWarningDialogWithExternalDomain(
context: context, context: context,
confirmAction: () { confirmAction: confirmAction,
keyToEmailTagEditor.currentState?.resetTextField(); cancelAction: cancelAction
setState(() => listEmailAddress.add(emailAddress));
},
cancelAction: () {
keyToEmailTagEditor.currentState?.resetTextField();
}
); );
} else {
keyToEmailTagEditor.currentState?.resetTextField();
setState(() => listEmailAddress.add(emailAddress));
} }
} }
void _closeSuggestionBox() {
keyToEmailTagEditor.currentState?.closeSuggestionBox();
}
void _resetInputText() {
keyToEmailTagEditor.currentState?.resetTextField();
}
void _addEmailAddressToInputFieldAction({
required BuildContext context,
required EmailAddress emailAddress,
bool isClearInput = false
}) {
log('_AutocompleteContactTextFieldWithTagsState::_addEmailAddressToInputFieldAction:emailAddress = $emailAddress');
if (!_validateEmailAddressIsValid(emailAddress)) {
widget.onExceptionCallback?.call(RecipientListWithInvalidEmailsException());
if (isClearInput) {
_resetInputText();
}
return;
}
_validateEmailAddressSameDomain(
context: context,
emailAddress: emailAddress,
confirmAction: () {
if (isClearInput) {
_resetInputText();
}
setState(() => listEmailAddress.add(emailAddress));
},
cancelAction: () {
if (isClearInput) {
_resetInputText();
}
},
sameDomainAction: () {
if (isClearInput) {
_resetInputText();
}
setState(() => listEmailAddress.add(emailAddress));
},
duplicatedRecipientAction: () {
if (isClearInput) {
_resetInputText();
}
}
);
}
void _showWarningDialogWithExternalDomain({ void _showWarningDialogWithExternalDomain({
required BuildContext context, required BuildContext context,
VoidCallback? confirmAction, VoidCallback? confirmAction,