TF-2613 Move AutocompleteContactTextFieldWithTags to forward folder
Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
-397
@@ -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
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user