TF-2613 Move AutocompleteContactTextFieldWithTags to forward folder

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2024-02-29 10:34:47 +07:00
committed by Dat H. Pham
parent 3480d5e235
commit ba89f5a710
2 changed files with 1 additions and 1 deletions
@@ -1,397 +0,0 @@
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:model/model.dart';
import 'package:super_tag_editor/tag_editor.dart';
import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_mixin.dart';
import 'package:tmail_ui_user/features/base/widget/material_text_icon_button.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/suggestion_email_address.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/email/presentation/utils/email_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/utils/app_utils.dart';
typedef OnSuggestionContactCallbackAction = Future<List<EmailAddress>> Function(String query);
typedef OnAddListContactCallbackAction = Function(List<EmailAddress> listEmailAddress);
typedef OnExceptionAddListContactCallbackAction = Function(bool isListEmpty);
class AutocompleteContactTextFieldWithTags extends StatefulWidget {
final List<EmailAddress> listEmailAddress;
final TextEditingController? controller;
final bool? hasAddContactButton;
final OnSuggestionContactCallbackAction? onSuggestionCallback;
final OnAddListContactCallbackAction? onAddContactCallback;
final OnExceptionAddListContactCallbackAction? onExceptionCallback;
final String serverDomain;
const AutocompleteContactTextFieldWithTags({
Key? key,
required this.listEmailAddress,
required this.serverDomain,
this.controller,
this.hasAddContactButton = false,
this.onSuggestionCallback,
this.onAddContactCallback,
this.onExceptionCallback,
}) : super(key: key);
@override
State<AutocompleteContactTextFieldWithTags> createState() => _AutocompleteContactTextFieldWithTagsState();
}
class _AutocompleteContactTextFieldWithTagsState extends State<AutocompleteContactTextFieldWithTags> with MessageDialogActionMixin {
final _responsiveUtils = Get.find<ResponsiveUtils>();
final _imagePaths = Get.find<ImagePaths>();
final GlobalKey<TagsEditorState> keyToEmailTagEditor = GlobalKey<TagsEditorState>();
late List<EmailAddress> listEmailAddress;
Timer? _gapBetweenTagChangedAndFindSuggestion;
bool lastTagFocused = false;
@override
void initState() {
super.initState();
listEmailAddress = widget.listEmailAddress;
}
@override
void dispose() {
_gapBetweenTagChangedAndFindSuggestion?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final itemTagEditor = TagEditor<SuggestionEmailAddress>(
key: keyToEmailTagEditor,
length: listEmailAddress.length,
controller: widget.controller,
borderRadius: 12,
backgroundColor: AppColor.colorInputBackgroundCreateMailbox,
focusedBorderColor: AppColor.colorTextButton,
enableBorderColor: AppColor.colorInputBorderCreateMailbox,
cursorColor: AppColor.primaryColor,
padding: const EdgeInsets.symmetric(horizontal: 12),
suggestionPadding: const EdgeInsets.symmetric(vertical: 12),
suggestionMargin: const EdgeInsets.symmetric(vertical: 4),
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.done,
tagSpacing: PlatformInfo.isWeb ? 12 : 8,
autofocus: false,
minTextFieldWidth: 20,
debounceDuration: const Duration(milliseconds: 150),
autoScrollToInput: false,
suggestionsBoxBackgroundColor: Colors.white,
suggestionsBoxRadius: 16,
suggestionsBoxMaxHeight: 350,
onFocusTagAction: (focused) {
setState(() {
lastTagFocused = focused;
});
},
onDeleteTagAction: () {
if (listEmailAddress.isNotEmpty) {
setState(() {
listEmailAddress.removeLast();
});
}
},
onSelectOptionAction: (item) => _addEmailAddressToInputFieldAction(
context: context,
emailAddress: item.emailAddress
),
onSubmitted: (value) => _addEmailAddressToInputFieldAction(
context: context,
emailAddress: EmailAddress(null, value)
),
textStyle: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w500),
inputDecoration: InputDecoration(
border: InputBorder.none,
hintText: AppLocalizations.of(context).hintInputAutocompleteContact,
hintStyle: const TextStyle(
fontWeight: FontWeight.w500,
color: AppColor.colorSettingExplanation,
fontSize: 16
)
),
tagBuilder: (context, index) => ContactInputTagItem(
listEmailAddress[index],
isLastContact: index == listEmailAddress.length - 1,
lastTagFocused: lastTagFocused,
deleteContactCallbackAction: (contact) {
setState(() => listEmailAddress.remove(contact));
}
),
onTagChanged: (value) {
_addEmailAddressToInputFieldAction(
context: context,
emailAddress: EmailAddress(null, value)
);
_gapBetweenTagChangedAndFindSuggestion = Timer(
const Duration(seconds: 1),
_handleGapBetweenTagChangedAndFindSuggestion
);
},
findSuggestions: _findSuggestions,
suggestionBuilder: (context, tagEditorState, suggestionEmailAddress, index, length, highlight, suggestionValid) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: ContactSuggestionBoxItem(
suggestionEmailAddress,
shapeBorder: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12))),
selectedContactCallbackAction: (contact) {
_addEmailAddressToInputFieldAction(
context: context,
emailAddress: contact);
tagEditorState.closeSuggestionBox();
tagEditorState.resetTextField();
},
),
);
},
);
if (widget.hasAddContactButton == true) {
return Container(
color: Colors.transparent,
width: double.infinity,
padding: SettingsUtils.getPaddingInputRecipientForwarding(context, _responsiveUtils),
child: _responsiveUtils.isScreenWithShortestSide(context)
? Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
itemTagEditor,
const SizedBox(height: 16),
_buildAddRecipientButton(context, maxWidth: double.infinity)
],
)
: Row(
children: [
Expanded(child: itemTagEditor),
const SizedBox(width: 12),
_buildAddRecipientButton(context)
],
)
);
} else {
return itemTagEditor;
}
}
bool _isDuplicatedRecipient(String inputEmail) {
log('_AutocompleteContactTextFieldWithTagsState::_isDuplicatedRecipient: inputEmail = $inputEmail');
if (inputEmail.isEmpty) {
return false;
}
return listEmailAddress
.map((emailAddress) => emailAddress.email)
.whereNotNull()
.contains(inputEmail);
}
FutureOr<List<SuggestionEmailAddress>> _findSuggestions(String query) async {
if (_gapBetweenTagChangedAndFindSuggestion?.isActive ?? false) {
return [];
}
final processedQuery = query.trim();
if (processedQuery.isEmpty) {
return [];
}
final teamMailSuggestion = List<SuggestionEmailAddress>.empty(growable: true);
if (processedQuery.isNotEmpty && widget.onSuggestionCallback != null) {
final addedEmailAddresses = await widget.onSuggestionCallback!(processedQuery);
final listSuggestionEmailAddress = addedEmailAddresses
.map((emailAddress) => _toSuggestionEmailAddress(emailAddress, listEmailAddress))
.toList();
teamMailSuggestion.addAll(listSuggestionEmailAddress);
}
teamMailSuggestion.addAll(_matchedSuggestionEmailAddress(processedQuery, listEmailAddress));
final currentTextOnTextField = widget.controller?.text ?? '';
if (currentTextOnTextField.isEmpty) {
return [];
}
return teamMailSuggestion.toSet().toList();
}
SuggestionEmailAddress _toSuggestionEmailAddress(
EmailAddress item,
List<EmailAddress> addedEmailAddresses
) {
if (addedEmailAddresses.contains(item)) {
return SuggestionEmailAddress(item, state: SuggestionEmailState.duplicated);
} else {
return SuggestionEmailAddress(item);
}
}
Iterable<SuggestionEmailAddress> _matchedSuggestionEmailAddress(
String query,
List<EmailAddress> addedEmailAddress
) {
return addedEmailAddress
.where((addedMail) => addedMail.emailAddress.contains(query))
.map((emailAddress) => SuggestionEmailAddress(emailAddress, state: SuggestionEmailState.duplicated));
}
void _handleGapBetweenTagChangedAndFindSuggestion() {
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}) {
return MaterialTextIconButton(
key: const Key('button_add_recipient'),
label: AppLocalizations.of(context).addRecipientButton,
icon: _imagePaths.icAddIdentity,
backgroundColor: AppColor.colorTextButton,
labelColor: Colors.white,
iconColor: Colors.white,
minimumSize: Size(maxWidth ?? 167, PlatformInfo.isMobile ? 44 : 54),
onTap: _handleAddRecipientAction
);
}
void _handleAddRecipientAction() {
_hideKeyboardForMobile();
if (widget.controller?.text.isNotEmpty == true) {
if (!_isDuplicatedRecipient(widget.controller?.text ?? '')) {
_addEmailAddressToInputFieldAction(
context: context,
emailAddress: EmailAddress(null, widget.controller?.text)
);
_closeSuggestionBox();
} else {
_closeSuggestionBox();
return;
}
}
if (listEmailAddress.isEmpty) {
widget.onExceptionCallback?.call(true);
return;
}
if (_isValidAllEmailAddress(listEmailAddress) && _inputFieldIsEmpty()) {
widget.onAddContactCallback?.call(List.from(listEmailAddress));
setState(() {
widget.controller?.clear();
listEmailAddress.clear();
});
} else {
widget.onExceptionCallback?.call(false);
}
}
void _closeSuggestionBox() {
keyToEmailTagEditor.currentState?.resetTextField();
keyToEmailTagEditor.currentState?.closeSuggestionBox();
}
void _hideKeyboardForMobile() {
if (!_responsiveUtils.isDesktop(context)) {
FocusScope.of(context).unfocus();
}
}
void _addEmailAddressToInputFieldAction({
required BuildContext context,
required EmailAddress emailAddress
}) {
if (_isDuplicatedRecipient(emailAddress.emailAddress)) {
return;
}
final validateSameDomain = EmailUtils.isSameDomain(
emailAddress: emailAddress.emailAddress,
serverDomain: widget.serverDomain
);
if (!validateSameDomain) {
_showWarningDialogWithExternalDomain(
context: context,
confirmAction: () {
keyToEmailTagEditor.currentState?.resetTextField();
setState(() => listEmailAddress.add(emailAddress));
},
cancelAction: () {
keyToEmailTagEditor.currentState?.resetTextField();
}
);
} else {
keyToEmailTagEditor.currentState?.resetTextField();
setState(() => listEmailAddress.add(emailAddress));
}
}
void _showWarningDialogWithExternalDomain({
required BuildContext context,
VoidCallback? confirmAction,
VoidCallback? cancelAction
}) async {
await showConfirmDialogAction(
context,
title: AppLocalizations.of(context).messageWarningDialogForForwardsToOtherDomains,
AppLocalizations.of(context).doYouWantToProceed,
AppLocalizations.of(context).yes,
cancelTitle: AppLocalizations.of(context).no,
alignCenter: true,
onConfirmAction: confirmAction,
onCancelAction: cancelAction,
icon: SvgPicture.asset(
_imagePaths.icQuotasWarning,
width: 40,
height: 40,
colorFilter: AppColor.colorQuotaError.asFilter(),
),
messageStyle: Theme.of(context).textTheme.labelMedium?.copyWith(
fontSize: 17,
color: Colors.black
),
titleStyle: Theme.of(context).textTheme.labelSmall?.copyWith(
fontSize: 14,
color: AppColor.colorMessageConfirmDialog
),
actionStyle: Theme.of(context).textTheme.labelMedium?.copyWith(
fontSize: 17,
color: Colors.white
),
cancelStyle: Theme.of(context).textTheme.labelMedium?.copyWith(
fontSize: 17,
color: Colors.black
),
);
}
}