diff --git a/assets/images/ic_delete_dialog_identity.svg b/assets/images/ic_delete_dialog_identity.svg index f98ff95b7..1d7492eba 100644 --- a/assets/images/ic_delete_dialog_identity.svg +++ b/assets/images/ic_delete_dialog_identity.svg @@ -1,6 +1,12 @@ - - - - - + + + + + diff --git a/assets/images/ic_delete_recipient.svg b/assets/images/ic_delete_recipient.svg new file mode 100644 index 000000000..bcd90736d --- /dev/null +++ b/assets/images/ic_delete_recipient.svg @@ -0,0 +1,5 @@ + + + diff --git a/assets/images/ic_selected_recipient.svg b/assets/images/ic_selected_recipient.svg new file mode 100644 index 000000000..6915f0829 --- /dev/null +++ b/assets/images/ic_selected_recipient.svg @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index 4c038291a..9c3c51c01 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -159,7 +159,10 @@ extension AppColor on Color { static const colorItemSelected = Color(0xFFF2F3F5); static const colorBorderSettingContentWeb = Color(0xFFE7E8EC); static const colorDividerHeaderSetting = Color(0xFFE4E4E4); - static const colorSettingExplanation = Color(0xFF686E76); + static const colorSettingExplanation = Color(0xFF818C99); + static const colorBackgroundContactTagItem = Color(0xFFDCE0E5); + static const colorDeleteContactIcon = Color(0xFFAEB7C2); + static const colorItemRecipientSelected = Color(0xFFDFEEFF); static const mapGradientColor = [ [Color(0xFF21D4FD), Color(0xFFB721FF)], @@ -167,7 +170,11 @@ extension AppColor on Color { [Color(0xFF11E6F0), Color(0xFF4FACFE)], [Color(0xFFE88395), Color(0xFFEF9C8F)], [Color(0xFF8DDAD5), Color(0xFF00CDAC)], - [Color(0xFFDE9AF5), Color(0xFFD670EE)], + [Color(0xFFE4ABF0), Color(0xFFD96EED)], + [Color(0xFFF0FF00), Color(0xFF58CFFB)], + [Color(0xFFEFC0D7), Color(0xFF1AD5E4)], + [Color(0xFFFFD26F), Color(0xFF3677FF)], + [Color(0xFF87A6F8), Color(0xFF645FF6)], ]; String toHexTriplet() => '#${(value & 0xFFFFFF) diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index dc9674b21..b1b7c91b4 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -170,6 +170,8 @@ class ImagePaths { String get icOlder => _getImagePath('ic_older.svg'); String get icNewer => _getImagePath('ic_newer.svg'); String get icCancelSelection => _getImagePath('ic_cancel_selection.svg'); + String get icSelectedRecipient => _getImagePath('ic_selected_recipient.svg'); + String get icDeleteRecipient => _getImagePath('ic_delete_recipient.svg'); String _getImagePath(String imageName) { return AssetsPaths.images + imageName; diff --git a/lib/features/base/mixin/message_dialog_action_mixin.dart b/lib/features/base/mixin/message_dialog_action_mixin.dart index 943e85b92..9c5656a99 100644 --- a/lib/features/base/mixin/message_dialog_action_mixin.dart +++ b/lib/features/base/mixin/message_dialog_action_mixin.dart @@ -47,9 +47,9 @@ mixin MessageDialogActionMixin { ..widthDialog(_responsiveUtils.getSizeScreenWidth(context)) ..colorConfirmButton(actionButtonColor ?? AppColor.colorTextButton) ..colorCancelButton(cancelButtonColor ?? AppColor.colorCancelButton) - ..paddingTitle(icon != null ? const EdgeInsets.only(top: 34) : EdgeInsets.zero) + ..paddingTitle(icon != null ? const EdgeInsets.only(top: 24) : EdgeInsets.zero) ..marginIcon(EdgeInsets.zero) - ..paddingContent(const EdgeInsets.only(left: 44, right: 44, bottom: 24, top: 8)) + ..paddingContent(const EdgeInsets.only(left: 44, right: 44, bottom: 24, top: 12)) ..paddingButton(hasCancelButton ? null : const EdgeInsets.only(bottom: 16, left: 44, right: 44)) ..styleTitle(titleStyle ?? const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black)) ..styleContent(const TextStyle(fontSize: 14, fontWeight: FontWeight.normal, color: AppColor.colorContentEmail)) @@ -83,9 +83,9 @@ mixin MessageDialogActionMixin { ..addIcon(icon) ..colorConfirmButton(actionButtonColor ?? AppColor.colorTextButton) ..colorCancelButton(cancelButtonColor ?? AppColor.colorCancelButton) - ..paddingTitle(icon != null ? const EdgeInsets.only(top: 34) : EdgeInsets.zero) + ..paddingTitle(icon != null ? const EdgeInsets.only(top: 24) : EdgeInsets.zero) ..marginIcon(EdgeInsets.zero) - ..paddingContent(const EdgeInsets.only(left: 44, right: 44, bottom: 24, top: 8)) + ..paddingContent(const EdgeInsets.only(left: 44, right: 44, bottom: 24, top: 12)) ..paddingButton(hasCancelButton ? null : const EdgeInsets.only(bottom: 16, left: 44, right: 44)) ..styleTitle(titleStyle ?? const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black)) ..styleContent(const TextStyle(fontSize: 14, fontWeight: FontWeight.normal, color: AppColor.colorContentEmail)) diff --git a/lib/features/contact/presentation/contact_controller.dart b/lib/features/contact/presentation/contact_controller.dart index 3de76c3ac..039071953 100644 --- a/lib/features/contact/presentation/contact_controller.dart +++ b/lib/features/contact/presentation/contact_controller.dart @@ -15,11 +15,10 @@ import 'package:tmail_ui_user/features/composer/domain/state/get_autocomplete_st import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_with_device_contact_interactor.dart'; import 'package:tmail_ui_user/features/contact/presentation/model/contact_arguments.dart'; +import 'package:tmail_ui_user/features/contact/presentation/widgets/contact_suggestion_box_item.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; -typedef OnSelectedContactCallback = Function(EmailAddress emailAddress); - class ContactController extends BaseController { TextEditingController? textInputSearchController; @@ -38,7 +37,7 @@ class ContactController extends BaseController { ContactArguments? arguments; EmailAddress? contactSelected; - OnSelectedContactCallback? onSelectedContactCallback; + SelectedContactCallbackAction? onSelectedContactCallback; VoidCallback? onDismissContactView; @override diff --git a/lib/features/contact/presentation/contact_view.dart b/lib/features/contact/presentation/contact_view.dart index 5b2df0dc6..16867f87b 100644 --- a/lib/features/contact/presentation/contact_view.dart +++ b/lib/features/contact/presentation/contact_view.dart @@ -2,16 +2,16 @@ 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/presentation/utils/style_utils.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/extensions/email_address_extension.dart'; +import 'package:tmail_ui_user/features/composer/presentation/widgets/suggestion_email_address.dart'; import 'package:tmail_ui_user/features/contact/presentation/contact_controller.dart'; import 'package:tmail_ui_user/features/contact/presentation/model/contact_arguments.dart'; import 'package:tmail_ui_user/features/contact/presentation/utils/contact_utils.dart'; import 'package:tmail_ui_user/features/contact/presentation/widgets/app_bar_contact_widget.dart'; +import 'package:tmail_ui_user/features/contact/presentation/widgets/contact_suggestion_box_item.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/search_app_bar_widget.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -30,7 +30,7 @@ class ContactView extends GetWidget { ContactView.fromArguments( ContactArguments arguments, { Key? key, - OnSelectedContactCallback? onSelectedContactCallback, + SelectedContactCallbackAction? onSelectedContactCallback, VoidCallback? onDismissCallback }) : super(key: key) { controller.arguments = arguments; @@ -117,7 +117,17 @@ class ContactView extends GetWidget { }, itemBuilder: (context, index) { final emailAddress = controller.listContactSearched[index]; - return _buildItemSearchContact(context, emailAddress); + final suggestionEmailAddress = _toSuggestionEmailAddress( + emailAddress, + controller.contactSelected != null + ? [controller.contactSelected!] + : [] + ); + return ContactSuggestionBoxItem( + suggestionEmailAddress, + padding: ContactUtils.getPaddingSearchResultList(context, _responsiveUtils), + selectedContactCallbackAction: (contact) => controller.selectContact(context, contact), + ); } ) ); @@ -134,68 +144,11 @@ class ContactView extends GetWidget { ); } - Widget _buildItemSearchContact(BuildContext context, EmailAddress emailAddress) { - return Material( - color: Colors.transparent, - child: ListTile( - onTap: () => controller.selectContact(context, emailAddress), - contentPadding: ContactUtils.getPaddingSearchInputForm(context, _responsiveUtils), - leading: Container( - width: 44, - height: 44, - alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(44) * 0.5, - border: Border.all(color: Colors.transparent), - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - stops: const [0.0, 1.0], - colors: emailAddress.avatarColors), - color: AppColor.avatarColor - ), - child: Text( - emailAddress.asString().isNotEmpty - ? emailAddress.asString()[0].toUpperCase() - : '', - style: const TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.w600))), - title: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - emailAddress.asString(), - maxLines: 1, - overflow: CommonTextStyle.defaultTextOverFlow, - softWrap: CommonTextStyle.defaultSoftWrap, - style: const TextStyle( - color: Colors.black, - fontSize: 15, - fontWeight: FontWeight.normal)), - if (emailAddress.displayName.isNotEmpty && emailAddress.emailAddress.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 4), - child: Text( - emailAddress.emailAddress, - maxLines: 1, - overflow: CommonTextStyle.defaultTextOverFlow, - softWrap: CommonTextStyle.defaultSoftWrap, - style: const TextStyle( - color: AppColor.colorContentEmail, - fontSize: 13, - fontWeight: FontWeight.normal))) - ] - ), - trailing: controller.contactSelected?.email == emailAddress.email - ? SvgPicture.asset(_imagePaths.icFilterSelected, - width: 24, - height: 24, - fit: BoxFit.fill) - : null, - ), - ); + SuggestionEmailAddress _toSuggestionEmailAddress(EmailAddress item, List addedEmailAddresses) { + if (addedEmailAddresses.contains(item)) { + return SuggestionEmailAddress(item, state: SuggestionEmailState.duplicated); + } else { + return SuggestionEmailAddress(item); + } } } \ No newline at end of file diff --git a/lib/features/contact/presentation/widgets/autocomplete_contact_text_field_with_tags.dart b/lib/features/contact/presentation/widgets/autocomplete_contact_text_field_with_tags.dart new file mode 100644 index 000000000..cc20368c0 --- /dev/null +++ b/lib/features/contact/presentation/widgets/autocomplete_contact_text_field_with_tags.dart @@ -0,0 +1,258 @@ + +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/presentation/views/button/button_builder.dart'; +import 'package:flutter/material.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/composer/presentation/widgets/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/main/localizations/app_localizations.dart'; + +typedef OnSuggestionContactCallbackAction = Future> Function(String query); +typedef OnAddListContactCallbackAction = Function(List listEmailAddress); +typedef OnExceptionAddListContactCallbackAction = Function(); + +class AutocompleteContactTextFieldWithTags extends StatefulWidget { + + final List listEmailAddress; + final TextEditingController? controller; + final bool? hasAddContactButton; + final OnSuggestionContactCallbackAction? onSuggestionCallback; + final OnAddListContactCallbackAction? onAddContactCallback; + final OnExceptionAddListContactCallbackAction? onExceptionCallback; + + const AutocompleteContactTextFieldWithTags(this.listEmailAddress, { + Key? key, + this.controller, + this.hasAddContactButton = false, + this.onSuggestionCallback, + this.onAddContactCallback, + this.onExceptionCallback + }) : super(key: key); + + @override + State createState() => _AutocompleteContactTextFieldWithTagsState(); +} + +class _AutocompleteContactTextFieldWithTagsState extends State { + + final _responsiveUtils = Get.find(); + final _imagePaths = Get.find(); + + late List listEmailAddress; + late Timer _gapBetweenTagChangedAndFindSuggestion; + + @override + void initState() { + super.initState(); + listEmailAddress = widget.listEmailAddress; + _gapBetweenTagChangedAndFindSuggestion = Timer( + const Duration(microseconds: 500), + () {} + ); + } + + @override + void dispose() { + _gapBetweenTagChangedAndFindSuggestion.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final itemTagEditor = TagEditor( + 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, + hasAddButton: false, + tagSpacing: 12, + delimiters: const [' '], + autofocus: false, + minTextFieldWidth: 20, + resetTextOnSubmitted: true, + suggestionsBoxBackgroundColor: Colors.white, + suggestionsBoxRadius: 16, + suggestionsBoxMaxHeight: 350, + onSubmitted: (value) { + if (!_isDuplicatedRecipient(value)) { + setState(() => listEmailAddress.add(EmailAddress(null, value))); + } + }, + textStyle: const TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.w500), + inputDecoration: const InputDecoration(border: InputBorder.none), + tagBuilder: (context, index) => ContactInputTagItem( + listEmailAddress[index], + deleteContactCallbackAction: (contact) { + setState(() => listEmailAddress.remove(contact)); + } + ), + onTagChanged: (value) { + if (!_isDuplicatedRecipient(value)) { + setState(() => listEmailAddress.add(EmailAddress(null, value))); + } + }, + findSuggestions: _findSuggestions, + suggestionBuilder: (context, tagEditorState, suggestionEmailAddress) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: ContactSuggestionBoxItem( + suggestionEmailAddress, + shapeBorder: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12))), + selectedContactCallbackAction: (contact) { + setState(() => listEmailAddress.add(contact)); + tagEditorState.selectSuggestion(suggestionEmailAddress); + }, + ), + ); + }, + ); + + if (widget.hasAddContactButton == true) { + if (_responsiveUtils.isScreenWithShortestSide(context)) { + return Container( + color: Colors.transparent, + width: double.infinity, + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + itemTagEditor, + const SizedBox(height: 16), + _buildAddRecipientButton(context, maxWidth: double.infinity) + ], + ), + ); + } else { + return Container( + color: Colors.transparent, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + child: Row( + children: [ + Expanded(child: itemTagEditor), + const SizedBox(width: 12), + _buildAddRecipientButton(context) + ], + ), + ); + } + } else { + return itemTagEditor; + } + } + + bool _isDuplicatedRecipient(String inputEmail) { + if (inputEmail.isEmpty) { + return false; + } + return listEmailAddress + .map((emailAddress) => emailAddress.email) + .whereNotNull() + .contains(inputEmail); + } + + FutureOr> _findSuggestions(String query) async { + if (_gapBetweenTagChangedAndFindSuggestion.isActive) { + return []; + } + + final processedQuery = query.trim(); + + if (processedQuery.isEmpty) { + return []; + } + + final teamMailSuggestion = List.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)); + + return teamMailSuggestion.toSet().toList(); + } + + SuggestionEmailAddress _toSuggestionEmailAddress( + EmailAddress item, + List addedEmailAddresses + ) { + if (addedEmailAddresses.contains(item)) { + return SuggestionEmailAddress(item, state: SuggestionEmailState.duplicated); + } else { + return SuggestionEmailAddress(item); + } + } + + Iterable _matchedSuggestionEmailAddress( + String query, + List addedEmailAddress + ) { + return addedEmailAddress + .where((addedMail) => addedMail.emailAddress.contains(query)) + .map((emailAddress) => SuggestionEmailAddress(emailAddress, state: SuggestionEmailState.duplicated)); + } + + bool _isValidAllEmailAddress(List addedEmailAddress) { + return addedEmailAddress.every((addedMail) => addedMail.emailAddress.isEmail); + } + + bool _inputFieldIsEmpty() { + return widget.controller?.text.isEmpty == true; + } + + Widget _buildAddRecipientButton(BuildContext context, {double? maxWidth}) { + return (ButtonBuilder(_imagePaths.icAddIdentity) + ..key(const Key('button_add_recipient')) + ..decoration(BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: AppColor.colorTextButton)) + ..paddingIcon(const EdgeInsets.only(right: 8)) + ..iconColor(Colors.white) + ..maxWidth(maxWidth ?? 167) + ..size(20) + ..radiusSplash(12) + ..padding(const EdgeInsets.symmetric(vertical: 12)) + ..textStyle(const TextStyle( + fontSize: 16, + color: Colors.white, + fontWeight: FontWeight.w500)) + ..onPressActionClick(() { + if (_isValidAllEmailAddress(listEmailAddress) && _inputFieldIsEmpty()) { + widget.onAddContactCallback?.call(List.from(listEmailAddress)); + setState(() { + widget.controller?.clear(); + listEmailAddress.clear(); + }); + } else { + widget.onExceptionCallback?.call(); + } + }) + ..text(AppLocalizations.of(context).addRecipientButton, isVertical: false) + ).build(); + } +} \ No newline at end of file diff --git a/lib/features/contact/presentation/widgets/contact_input_decoration_builder.dart b/lib/features/contact/presentation/widgets/contact_input_decoration_builder.dart new file mode 100644 index 000000000..6083822dc --- /dev/null +++ b/lib/features/contact/presentation/widgets/contact_input_decoration_builder.dart @@ -0,0 +1,57 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/views/text/input_decoration_builder.dart'; +import 'package:core/utils/build_utils.dart'; +import 'package:flutter/material.dart'; + +class ContactInputDecorationBuilder extends InputDecorationBuilder { + + @override + InputDecoration build() { + return InputDecoration( + enabledBorder: enabledBorder ?? const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide( + width: 0.5, + color: AppColor.colorInputBorderCreateMailbox)), + focusedBorder: enabledBorder ?? const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide( + width: 1, + color: AppColor.colorTextButton)), + errorBorder: errorBorder ?? const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide( + width: 1, + color: AppColor.colorInputBorderErrorVerifyName)), + focusedErrorBorder: errorBorder ?? const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide( + width: 0.5, + color: AppColor.colorInputBorderErrorVerifyName)), + prefixText: prefixText, + labelText: labelText, + floatingLabelBehavior: FloatingLabelBehavior.never, + labelStyle: labelStyle ?? const TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.w500), + hintText: hintText, + isDense: true, + hintStyle: hintStyle ?? const TextStyle( + color: AppColor.colorEmailAddressFull, + fontWeight: FontWeight.w500, + fontSize: 16), + contentPadding: const EdgeInsets.symmetric( + vertical: BuildUtils.isWeb ? 16 : 12, + horizontal: 12), + errorText: errorText, + errorStyle: errorTextStyle ?? const TextStyle( + color: AppColor.colorInputBorderErrorVerifyName, + fontSize: 13), + filled: true, + fillColor: errorText?.isNotEmpty == true + ? AppColor.colorInputBackgroundErrorVerifyName + : AppColor.colorInputBackgroundCreateMailbox); + } +} \ No newline at end of file diff --git a/lib/features/contact/presentation/widgets/contact_input_tag_item.dart b/lib/features/contact/presentation/widgets/contact_input_tag_item.dart new file mode 100644 index 000000000..c8982e0fd --- /dev/null +++ b/lib/features/contact/presentation/widgets/contact_input_tag_item.dart @@ -0,0 +1,82 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/style_utils.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/extensions/email_address_extension.dart'; +import 'package:tmail_ui_user/features/contact/presentation/widgets/gradient_color_avatar_icon.dart'; + +typedef DeleteContactCallbackAction = Function(EmailAddress contactDeleted); + +class ContactInputTagItem extends StatelessWidget { + + final EmailAddress contact; + final DeleteContactCallbackAction? deleteContactCallbackAction; + + const ContactInputTagItem( + this.contact, + { + Key? key, + this.deleteContactCallbackAction + } + ) : super(key: key); + + @override + Widget build(BuildContext context) { + final imagePaths = Get.find(); + + final itemChild = Chip( + avatar: contact.displayName.isNotEmpty + ? GradientColorAvatarIcon( + contact.avatarColors, + iconSize: 20, + labelFontSize: 11, + label: contact.labelAvatar) + : null, + labelPadding: const EdgeInsets.symmetric(horizontal: 8), + padding: const EdgeInsets.all(6), + label: Text( + contact.asString(), + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Colors.black) + ), + deleteIcon: deleteContactCallbackAction != null + ? SvgPicture.asset( + imagePaths.icClose, + width: 20, + height: 20, + color: AppColor.colorDeleteContactIcon, + fit: BoxFit.fill) + : null, + labelStyle: const TextStyle(color: Colors.black, fontSize: 14, fontWeight: FontWeight.normal), + backgroundColor: _isValidEmailAddress(contact.emailAddress) + ? AppColor.colorBackgroundContactTagItem + : Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + side: BorderSide( + width: _isValidEmailAddress(contact.emailAddress) ? 0 : 1, + color: _isValidEmailAddress(contact.emailAddress) + ? AppColor.colorBackgroundContactTagItem + : AppColor.colorBorderEmailAddressInvalid + ) + ), + onDeleted: () => deleteContactCallbackAction?.call(contact), + ); + + return Padding( + padding: const EdgeInsets.only(top: 10), + child: itemChild + ); + } + + bool _isValidEmailAddress(String value) => value.isEmail; +} \ No newline at end of file diff --git a/lib/features/contact/presentation/widgets/contact_suggestion_box_item.dart b/lib/features/contact/presentation/widgets/contact_suggestion_box_item.dart new file mode 100644 index 000000000..c0f177f2f --- /dev/null +++ b/lib/features/contact/presentation/widgets/contact_suggestion_box_item.dart @@ -0,0 +1,101 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/style_utils.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/extensions/email_address_extension.dart'; +import 'package:tmail_ui_user/features/composer/presentation/widgets/suggestion_email_address.dart'; +import 'package:tmail_ui_user/features/contact/presentation/widgets/gradient_color_avatar_icon.dart'; + +typedef SelectedContactCallbackAction = Function(EmailAddress contactSelected); + +class ContactSuggestionBoxItem extends StatelessWidget { + + final SuggestionEmailAddress suggestionEmailAddress; + final SelectedContactCallbackAction? selectedContactCallbackAction; + final EdgeInsets? padding; + final ShapeBorder? shapeBorder; + + const ContactSuggestionBoxItem(this.suggestionEmailAddress, { + Key? key, + this.padding, + this.shapeBorder, + this.selectedContactCallbackAction + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final imagePaths = Get.find(); + + final itemChild = Row(children: [ + GradientColorAvatarIcon( + suggestionEmailAddress.emailAddress.avatarColors, + label: suggestionEmailAddress.emailAddress.labelAvatar, + ), + const SizedBox(width: 12), + Expanded(child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + suggestionEmailAddress.emailAddress.asString(), + maxLines: 1, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + style: const TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.w500) + ), + if (suggestionEmailAddress.emailAddress.displayName.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 2), + child: Text( + suggestionEmailAddress.emailAddress.emailAddress, + maxLines: 1, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + style: const TextStyle( + color: AppColor.colorEmailAddressFull, + fontSize: 14, + fontWeight: FontWeight.normal) + ) + ) + ] + )), + if (suggestionEmailAddress.state == SuggestionEmailState.duplicated) + Padding( + padding: const EdgeInsets.only(left: 12), + child: SvgPicture.asset(imagePaths.icFilterSelected, + width: 24, + height: 24, + fit: BoxFit.fill), + ) + ]); + + if (selectedContactCallbackAction != null && suggestionEmailAddress.state == SuggestionEmailState.valid) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () => selectedContactCallbackAction?.call(suggestionEmailAddress.emailAddress), + customBorder: shapeBorder, + child: Padding( + padding: padding ?? const EdgeInsets.all(12), + child: itemChild, + ), + ), + ); + } else { + return Material( + color: Colors.transparent, + child: Padding( + padding: padding ?? const EdgeInsets.all(12), + child: itemChild, + ), + ); + } + } +} \ No newline at end of file diff --git a/lib/features/contact/presentation/widgets/gradient_color_avatar_icon.dart b/lib/features/contact/presentation/widgets/gradient_color_avatar_icon.dart new file mode 100644 index 000000000..14a3d5263 --- /dev/null +++ b/lib/features/contact/presentation/widgets/gradient_color_avatar_icon.dart @@ -0,0 +1,44 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/material.dart'; + +class GradientColorAvatarIcon extends StatelessWidget { + + final List colors; + final double iconSize; + final double labelFontSize; + final String label; + + const GradientColorAvatarIcon(this.colors, { + Key? key, + this.iconSize = 40, + this.label = '', + this.labelFontSize = 24.0, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: iconSize, + height: iconSize, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(iconSize * 0.5), + border: Border.all(color: Colors.transparent), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: const [0.0, 1.0], + colors: colors), + color: AppColor.avatarColor + ), + child: Text( + label, + style: TextStyle( + color: Colors.white, + fontSize: labelFontSize, + fontWeight: FontWeight.w600) + ) + ); + } +} \ No newline at end of file diff --git a/lib/features/mailbox_creator/presentation/extensions/validator_failure_extension.dart b/lib/features/mailbox_creator/presentation/extensions/validator_failure_extension.dart index e6483697a..f916e449c 100644 --- a/lib/features/mailbox_creator/presentation/extensions/validator_failure_extension.dart +++ b/lib/features/mailbox_creator/presentation/extensions/validator_failure_extension.dart @@ -50,14 +50,4 @@ extension ValicatorFailureExtension on VerifyNameFailure { return ''; } } - - String getMessageForwarding(BuildContext context) { - if (exception is EmptyNameException) { - return AppLocalizations.of(context).this_field_cannot_be_blank; - } else if (exception is EmailAddressInvalidException) { - return AppLocalizations.of(context).incorrectEmailFormat; - } else { - return ''; - } - } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/extensions/tmail_forward_extension.dart b/lib/features/manage_account/presentation/extensions/tmail_forward_extension.dart index 4beac2de5..cc9d3e85e 100644 --- a/lib/features/manage_account/presentation/extensions/tmail_forward_extension.dart +++ b/lib/features/manage_account/presentation/extensions/tmail_forward_extension.dart @@ -7,7 +7,7 @@ extension TMailForwardExtension on TMailForward { List get listRecipientForward { return forwards - .map((emailAddress) => RecipientForward(EmailAddress(emailAddress, emailAddress))) + .map((value) => RecipientForward(EmailAddress(value, value))) .toList(); } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/forward/bindings/forward_bindings.dart b/lib/features/manage_account/presentation/forward/bindings/forward_bindings.dart index 63a7c7406..c110c80d0 100644 --- a/lib/features/manage_account/presentation/forward/bindings/forward_bindings.dart +++ b/lib/features/manage_account/presentation/forward/bindings/forward_bindings.dart @@ -1,12 +1,10 @@ import 'package:get/get.dart'; -import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/forward/forward_controller.dart'; class ForwardBindings extends Bindings { @override void dependencies() { - Get.lazyPut(() => VerifyNameInteractor()); - Get.lazyPut(() => ForwardController(Get.find())); + Get.lazyPut(() => ForwardController()); } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/forward/controller/forward_recipient_controller.dart b/lib/features/manage_account/presentation/forward/controller/forward_recipient_controller.dart index 07ba4c15a..8dc3dd7b7 100644 --- a/lib/features/manage_account/presentation/forward/controller/forward_recipient_controller.dart +++ b/lib/features/manage_account/presentation/forward/controller/forward_recipient_controller.dart @@ -18,13 +18,14 @@ import 'package:tmail_ui_user/main/error/capability_validator.dart'; class ForwardRecipientController { final ContactSuggestionSource _contactSuggestionSource = ContactSuggestionSource.tMailContact; - final TextEditingController inputEmailForwardController = TextEditingController(); + final TextEditingController inputRecipientController = TextEditingController(); GetAutoCompleteWithDeviceContactInteractor? _getAutoCompleteWithDeviceContactInteractor; GetAutoCompleteInteractor? _getAutoCompleteInteractor; AccountId? _accountId; Session? _session; - EmailAddress? _emailAddressSelected; + + final listRecipients = RxList(); ForwardRecipientController({AccountId? accountId, Session? session}) { _accountId = accountId; @@ -32,8 +33,6 @@ class ForwardRecipientController { injectAutoCompleteBindings(_session, _accountId); } - EmailAddress? get emailAddressSelected => _emailAddressSelected; - void onClose() { clearAll(); _disposeWidget(); @@ -82,16 +81,16 @@ class ForwardRecipientController { } } - void selectEmailAddress(EmailAddress? emailAddress) { - _emailAddressSelected = emailAddress; + void updateListRecipient(List listEmailAddress) { + listRecipients.value = listEmailAddress; } void clearAll() { - inputEmailForwardController.clear(); - _emailAddressSelected = null; + inputRecipientController.clear(); + listRecipients.clear(); } void _disposeWidget() { - inputEmailForwardController.dispose(); + inputRecipientController.dispose(); } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/forward/forward_controller.dart b/lib/features/manage_account/presentation/forward/forward_controller.dart index aa16d41a2..facec7b04 100644 --- a/lib/features/manage_account/presentation/forward/forward_controller.dart +++ b/lib/features/manage_account/presentation/forward/forward_controller.dart @@ -10,11 +10,6 @@ import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:model/extensions/email_address_extension.dart'; import 'package:model/mailbox/select_mode.dart'; import 'package:tmail_ui_user/features/base/base_controller.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'; -import 'package:tmail_ui_user/features/mailbox_creator/presentation/extensions/validator_failure_extension.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/edit_local_copy_in_forwarding_request.dart'; @@ -36,8 +31,6 @@ import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class ForwardController extends BaseController { - final VerifyNameInteractor _verifyNameInteractor; - GetForwardInteractor? _getForwardInteractor; DeleteRecipientInForwardingInteractor? _deleteRecipientInForwardingInteractor; AddRecipientsInForwardingInteractor? _addRecipientsInForwardingInteractor; @@ -50,13 +43,12 @@ class ForwardController extends BaseController { final selectionMode = Rx(SelectMode.INACTIVE); final listRecipientForward = RxList(); final currentForward = Rxn(); - final errorRecipientInputText = RxnString(); bool get currentForwardLocalCopyState => currentForward.value?.localCopy ?? false; late ForwardRecipientController recipientController; - ForwardController(this._verifyNameInteractor) { + ForwardController() { recipientController = ForwardRecipientController( accountId: accountDashBoardController.accountId.value, session: accountDashBoardController.sessionCurrent.value); @@ -84,23 +76,23 @@ class ForwardController extends BaseController { @override void onDone() { viewState.value.fold( - (failure) { - if (failure is DeleteRecipientInForwardingFailure) { - cancelSelectionMode(); - } - }, - (success) { - if (success is GetForwardSuccess) { - currentForward.value = success.forward; - listRecipientForward.value = currentForward.value!.listRecipientForward; - } else if (success is DeleteRecipientInForwardingSuccess) { - _handleDeleteRecipientSuccess(success); - } else if (success is AddRecipientsInForwardingSuccess) { - _handleAddRecipientsSuccess(success); - } else if (success is EditLocalCopyInForwardingSuccess) { - _handleEditLocalCopySuccess(success); - } - }); + (failure) { + if (failure is DeleteRecipientInForwardingFailure) { + cancelSelectionMode(); + } + }, + (success) { + if (success is GetForwardSuccess) { + currentForward.value = success.forward; + listRecipientForward.value = currentForward.value!.listRecipientForward; + } else if (success is DeleteRecipientInForwardingSuccess) { + _handleDeleteRecipientSuccess(success); + } else if (success is AddRecipientsInForwardingSuccess) { + _handleAddRecipientsSuccess(success); + } else if (success is EditLocalCopyInForwardingSuccess) { + _handleEditLocalCopySuccess(success); + } + }); } @override @@ -116,10 +108,10 @@ class ForwardController extends BaseController { } void deleteRecipients(BuildContext context, String emailAddress) { - showConfirmDialogAction(currentContext!, + showConfirmDialogAction(context, title: AppLocalizations.of(context).deleteRecipient, AppLocalizations.of(context).messageConfirmationDialogDeleteRecipientForward(emailAddress), - AppLocalizations.of(currentContext!).remove, + AppLocalizations.of(context).remove, onConfirmAction: () => _handleDeleteRecipientAction({emailAddress}), showAsBottomSheet: true, icon: SvgPicture.asset(_imagePaths.icDeleteDialogIdentity, fit: BoxFit.fill), @@ -167,22 +159,27 @@ class ForwardController extends BaseController { selectionMode.value = SelectMode.INACTIVE; } - List get listRecipientForwardSelected => listRecipientForward - .where((recipient) => recipient.selectMode == SelectMode.ACTIVE) - .toList(); + List get listRecipientForwardSelected => + listRecipientForward + .where((recipient) => recipient.selectMode == SelectMode.ACTIVE) + .toList(); bool get isAllUnSelected => - listRecipientForward.every((recipient) => recipient.selectMode == SelectMode.INACTIVE); + listRecipientForward.every((recipient) => recipient.selectMode == SelectMode.INACTIVE); + + bool get isAllSelected => + listRecipientForward.every((recipient) => recipient.selectMode == SelectMode.ACTIVE); void selectRecipientForward(RecipientForward recipientForward) { if (selectionMode.value == SelectMode.INACTIVE) { selectionMode.value = SelectMode.ACTIVE; } - final matchRecipientForward = listRecipientForward.indexOf(recipientForward); - if (matchRecipientForward >= 0) { + final matchedRecipientForward = listRecipientForward.indexOf(recipientForward); + log('ForwardController::selectRecipientForward(): matchedRecipientForward: $matchedRecipientForward'); + if (matchedRecipientForward >= 0) { final newRecipientForward = recipientForward.toggleSelection(); - listRecipientForward[matchRecipientForward] = newRecipientForward; + listRecipientForward[matchedRecipientForward] = newRecipientForward; listRecipientForward.refresh(); } @@ -194,8 +191,8 @@ class ForwardController extends BaseController { void cancelSelectionMode() { selectionMode.value = SelectMode.INACTIVE; listRecipientForward.value = listRecipientForward - .map((recipient) => recipient.cancelSelection()) - .toList(); + .map((recipient) => recipient.cancelSelection()) + .toList(); } void deleteMultipleRecipients(BuildContext context, Set listEmailAddress) { @@ -233,21 +230,13 @@ class ForwardController extends BaseController { .toList(); } - void addRecipientAction(BuildContext context) { + void addRecipientAction(BuildContext context, List listRecipientsSelected) { + log('ForwardController::addRecipientAction():listRecipientsSelected: $listRecipientsSelected'); FocusScope.of(context).unfocus(); final accountId = accountDashBoardController.accountId.value; - final emailAddressSelected = recipientController.emailAddressSelected; - - updateErrorRecipientInputText(context, emailAddressSelected?.email); - - if (errorRecipientInputText.value != null) { - return; - } - - log('ForwardController::addRecipientAction():emailAddressSelected: $emailAddressSelected'); - if (emailAddressSelected != null && accountId != null) { - _handleAddRecipients(accountId, [emailAddressSelected]); + if (accountId != null) { + _handleAddRecipients(accountId, listRecipientsSelected); } } @@ -304,24 +293,4 @@ class ForwardController extends BaseController { currentForward.value = success.forward; listRecipientForward.value = currentForward.value!.listRecipientForward; } - - void updateErrorRecipientInputText(BuildContext context, String? emailAddress) { - errorRecipientInputText.value = _getErrorRecipientInputString(context, emailAddress); - } - - String? _getErrorRecipientInputString(BuildContext context, String? emailAddress) { - return _verifyNameInteractor.execute( - emailAddress, - [EmptyNameValidator(), EmailAddressValidator()] - ).fold( - (failure) { - if (failure is VerifyNameFailure) { - return failure.getMessageForwarding(context); - } else { - return null; - } - }, - (success) => null - ); - } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/forward/forward_view.dart b/lib/features/manage_account/presentation/forward/forward_view.dart index 2bfe946b8..9adf4bdfe 100644 --- a/lib/features/manage_account/presentation/forward/forward_view.dart +++ b/lib/features/manage_account/presentation/forward/forward_view.dart @@ -1,24 +1,24 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/state/success.dart'; +import 'package:core/presentation/utils/app_toast.dart'; import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/utils/style_utils.dart'; -import 'package:core/presentation/views/button/button_builder.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:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; +import 'package:tmail_ui_user/features/contact/presentation/widgets/autocomplete_contact_text_field_with_tags.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/forward/forward_controller.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/forward/widgets/forward_header_widget.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/forward/widgets/list_email_forward_widget.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/forward/widgets/recipient_input_field_builder.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/menu/settings_utils.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class ForwardView extends GetWidget with AppLoaderMixin { final _responsiveUtils = Get.find(); final _imagePaths = Get.find(); + final _appToast = Get.find(); ForwardView({Key? key}) : super(key: key); @@ -76,7 +76,7 @@ class ForwardView extends GetWidget with AppLoaderMixin { AppLocalizations.of(context).forwardingSettingExplanation, style: const TextStyle( fontSize: 16, - fontWeight: FontWeight.normal, + fontWeight: FontWeight.w500, color: AppColor.colorSettingExplanation ) ), @@ -96,7 +96,7 @@ class ForwardView extends GetWidget with AppLoaderMixin { ? _imagePaths.icSwitchOn : _imagePaths.icSwitchOff, fit: BoxFit.fill, - width: 24, + width: 36, height: 24) ); }), @@ -108,7 +108,7 @@ class ForwardView extends GetWidget with AppLoaderMixin { softWrap: CommonTextStyle.defaultSoftWrap, style: const TextStyle( fontSize: 16, - fontWeight: FontWeight.normal, + fontWeight: FontWeight.w500, color: Colors.black) ), ) @@ -127,84 +127,30 @@ class ForwardView extends GetWidget with AppLoaderMixin { )); } - Widget _buildAddRecipientInputField() { - return Obx(() => RecipientInputFieldBuilder( - editingController: controller.recipientController.inputEmailForwardController, - onSelectedSuggestionAction: (newEmailAddress) { - controller.recipientController.selectEmailAddress(newEmailAddress); - controller.recipientController.inputEmailForwardController.text = newEmailAddress.email ?? ''; - }, - onRecipientInputTextChange: (pattern) { - if (pattern.trim().isNotEmpty) { - controller.recipientController.selectEmailAddress(EmailAddress(null, pattern.trim())); - } else { - controller.recipientController.selectEmailAddress(null); - } - controller.errorRecipientInputText.value = null; - }, - onSummitedCallbackAction: (pattern) { - if (pattern.trim().isNotEmpty) { - controller.recipientController.selectEmailAddress(EmailAddress(null, pattern.trim())); - } else { - controller.recipientController.selectEmailAddress(null); - } - }, - onSuggestionCallbackAction: controller.recipientController.getAutoCompleteSuggestion, - errorText: controller.errorRecipientInputText.value, - )); - } - - Widget _buildAddRecipientButton(BuildContext context, {double? maxWidth}) { - return (ButtonBuilder(_imagePaths.icAddIdentity) - ..key(const Key('button_add_recipient')) - ..decoration(BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: AppColor.colorTextButton)) - ..paddingIcon(const EdgeInsets.only(right: 8)) - ..iconColor(Colors.white) - ..maxWidth(maxWidth ?? 170) - ..size(20) - ..radiusSplash(10) - ..padding(const EdgeInsets.symmetric(vertical: 12)) - ..textStyle(const TextStyle( - fontSize: 17, - color: Colors.white, - fontWeight: FontWeight.w500)) - ..onPressActionClick(() => controller.addRecipientAction(context)) - ..text(AppLocalizations.of(context).addRecipientButton, isVertical: false) - ).build(); - } - Widget _buildAddRecipientsFormWidget(BuildContext context) { - if (_responsiveUtils.isPortraitMobile(context)) { - return Container( - color: Colors.transparent, - width: double.infinity, - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildAddRecipientInputField(), - const SizedBox(height: 16), - _buildAddRecipientButton(context, maxWidth: double.infinity) - ], - ), - ); - } else { - return Container( - color: Colors.transparent, - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded(child: _buildAddRecipientInputField()), - Padding( - padding: const EdgeInsets.only(left: 24, right: 12), - child: _buildAddRecipientButton(context)) - ], - ), - ); - } + return AutocompleteContactTextFieldWithTags( + controller.recipientController.listRecipients, + controller: controller.recipientController.inputRecipientController, + onSuggestionCallback: controller.recipientController.getAutoCompleteSuggestion, + hasAddContactButton: true, + onAddContactCallback: (listRecipientsSelected) { + controller.addRecipientAction(context, listRecipientsSelected); + }, + onExceptionCallback: () { + _appToast.showBottomToast( + context, + AppLocalizations.of(context).incorrectEmailFormat, + leadingIcon: SvgPicture.asset( + _imagePaths.icNotConnection, + width: 24, + height: 24, + color: Colors.white, + fit: BoxFit.fill), + backgroundColor: AppColor.toastErrorBackgroundColor, + textColor: Colors.white, + textActionColor: Colors.white, + maxWidth: _responsiveUtils.getMaxWidthToast(context)); + }, + ); } } diff --git a/lib/features/manage_account/presentation/forward/widgets/email_forward_item_widget.dart b/lib/features/manage_account/presentation/forward/widgets/email_forward_item_widget.dart index 159a8ea82..6cabccde3 100644 --- a/lib/features/manage_account/presentation/forward/widgets/email_forward_item_widget.dart +++ b/lib/features/manage_account/presentation/forward/widgets/email_forward_item_widget.dart @@ -1,66 +1,61 @@ -import 'package:core/core.dart'; +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/extensions/string_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/style_utils.dart'; +import 'package:core/presentation/views/button/icon_button_web.dart'; +import 'package:core/presentation/views/image/avatar_builder.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:model/extensions/email_address_extension.dart'; import 'package:model/mailbox/select_mode.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/forward/forward_controller.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/model/recipient_forward.dart'; +typedef OnSelectRecipientCallbackAction = Function(RecipientForward recipientForward); +typedef OnDeleteRecipientCallbackAction = Function(RecipientForward recipientForward); + class EmailForwardItemWidget extends StatelessWidget { - final _imagePaths = Get.find(); - final _emailForwardController = Get.find(); - final RecipientForward recipientForward; final SelectMode selectionMode; - final bool isLast; + final OnSelectRecipientCallbackAction? onSelectRecipientCallback; + final OnDeleteRecipientCallbackAction? onDeleteRecipientCallback; - EmailForwardItemWidget({ + const EmailForwardItemWidget(this.recipientForward, { Key? key, - required this.recipientForward, - this.isLast = false, this.selectionMode = SelectMode.INACTIVE, + this.onSelectRecipientCallback, + this.onDeleteRecipientCallback, }) : super(key: key); @override Widget build(BuildContext context) { - return GestureDetector( - onLongPress: () => _emailForwardController.selectRecipientForward(recipientForward), - onTap: () { - if (selectionMode == SelectMode.ACTIVE) { - _emailForwardController.selectRecipientForward(recipientForward); - } - }, - child: Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: recipientForward.selectMode == SelectMode.ACTIVE - ? AppColor.colorItemEmailSelectedDesktop - : Colors.white, - borderRadius: BorderRadius.only( - bottomLeft: isLast ? const Radius.circular(12) : Radius.zero, - bottomRight: isLast ? const Radius.circular(12) : Radius.zero, - ) - ), - child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ - Container( - width: 54, - height: 54, - color: Colors.transparent, - alignment: Alignment.center, - child: _buildAvatarIcon() - ), - const SizedBox(width: 12), - Expanded(child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (recipientForward.emailAddress.displayName.isNotEmpty) - Padding( - padding: const EdgeInsets.only(bottom: 4), - child: Text( - recipientForward.emailAddress.displayName, + final _imagePaths = Get.find(); + + return Padding( + padding: const EdgeInsets.only(top: 4), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () {}, + customBorder: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12))), + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: _getBackgroundColor(), + borderRadius: BorderRadius.all(Radius.circular( + recipientForward.selectMode == SelectMode.ACTIVE ? 12 : 0)) + ), + child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ + _buildAvatarIcon(_imagePaths), + const SizedBox(width: 12), + Expanded(child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + recipientForward.emailAddress.asString(), overflow: CommonTextStyle.defaultTextOverFlow, softWrap: CommonTextStyle.defaultSoftWrap, maxLines: 1, @@ -70,59 +65,74 @@ class EmailForwardItemWidget extends StatelessWidget { color: Colors.black ) ), + if (recipientForward.emailAddress.displayName.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + recipientForward.emailAddress.email ?? '', + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + maxLines: 1, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: AppColor.colorContentEmail + ) + ), + ) + ], + )), + const SizedBox(width: 12), + if (selectionMode == SelectMode.INACTIVE) + buildIconWeb( + iconSize: 34, + splashRadius: 25, + icon: SvgPicture.asset(_imagePaths.icDeleteRecipient), + onTap: () => onDeleteRecipientCallback?.call(recipientForward) ), - Text( - recipientForward.emailAddress.email ?? '', - overflow: CommonTextStyle.defaultTextOverFlow, - softWrap: CommonTextStyle.defaultSoftWrap, - maxLines: 1, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: AppColor.colorContentEmail - ) - ) - ], - )), - const SizedBox(width: 12), - buildIconWeb( - icon: SvgPicture.asset( - _imagePaths.icDeleteEmailForward, - fit: BoxFit.fill, - width: 18, - height: 18, - ), - onTap: () => _emailForwardController.deleteRecipients( - context, - recipientForward.emailAddress.email ?? '' - ) + ]), ), - ]), + ), ), ); } - Widget _buildAvatarIcon() { - if (selectionMode == SelectMode.ACTIVE) { - return Container( - alignment: Alignment.center, - child: SvgPicture.asset( - recipientForward.selectMode == SelectMode.ACTIVE - ? _imagePaths.icSelected - : _imagePaths.icUnSelected, - width: 24, - height: 24 - ) + Color _getBackgroundColor() { + if (recipientForward.selectMode == SelectMode.ACTIVE) { + return AppColor.colorItemRecipientSelected; + } else { + return Colors.transparent; + } + } + + Widget _buildAvatarIcon(ImagePaths imagePaths) { + if (recipientForward.selectMode == SelectMode.ACTIVE) { + return InkWell( + customBorder: const CircleBorder(), + onTap: () => onSelectRecipientCallback?.call(recipientForward), + child: Container( + width: 40, + height: 40, + color: Colors.transparent, + alignment: Alignment.center, + child: SvgPicture.asset( + imagePaths.icSelectedRecipient, + width: 40, + height: 40, + fit: BoxFit.fill, + ), + ), ); } else { return (AvatarBuilder() ..text(recipientForward.emailAddress.asString().firstLetterToUpperCase) - ..size(54) + ..size(40) ..addTextStyle(const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 20, + fontWeight: FontWeight.w500, + fontSize: 16, color: Colors.white)) ..avatarColor(recipientForward.emailAddress.avatarColors) + ..addOnTapActionClick(() => onSelectRecipientCallback?.call(recipientForward)) ).build(); } } diff --git a/lib/features/manage_account/presentation/forward/widgets/email_forward_item_widget_for_web.dart b/lib/features/manage_account/presentation/forward/widgets/email_forward_item_widget_for_web.dart deleted file mode 100644 index 87d1277dd..000000000 --- a/lib/features/manage_account/presentation/forward/widgets/email_forward_item_widget_for_web.dart +++ /dev/null @@ -1,155 +0,0 @@ -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/extensions/string_extension.dart'; -import 'package:core/presentation/resources/image_paths.dart'; -import 'package:core/presentation/utils/style_utils.dart'; -import 'package:core/presentation/views/button/icon_button_web.dart'; -import 'package:core/presentation/views/image/avatar_builder.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:get/get.dart'; -import 'package:html_editor_enhanced/utils/utils.dart'; -import 'package:model/extensions/email_address_extension.dart'; -import 'package:model/mailbox/select_mode.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/forward/forward_controller.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/model/recipient_forward.dart'; - -class EmailForwardItemWidget extends StatefulWidget { - - final RecipientForward recipientForward; - final SelectMode selectionMode; - final bool isLast; - - const EmailForwardItemWidget({ - Key? key, - required this.recipientForward, - this.isLast = false, - this.selectionMode = SelectMode.INACTIVE, - }) : super(key: key); - - @override - State createState() => _EmailForwardItemWidgetState(); -} - -class _EmailForwardItemWidgetState extends State { - final _imagePaths = Get.find(); - - final _emailForwardController = Get.find(); - - bool isHoverIcon = false; - bool isHoverItem = false; - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: () => {}, - onHover: (value) { - setState(mounted, this.setState, () { - isHoverItem = value; - }); - }, - child: Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: widget.recipientForward.selectMode == SelectMode.ACTIVE - ? AppColor.colorItemEmailSelectedDesktop - : Colors.white, - borderRadius: BorderRadius.only( - bottomLeft: widget.isLast ? const Radius.circular(12) : Radius.zero, - bottomRight: widget.isLast ? const Radius.circular(12) : Radius.zero, - ) - ), - child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ - InkWell( - onTap: () { - if (isHoverIcon) { - _emailForwardController.selectRecipientForward(widget.recipientForward); - } - }, - onHover: (value) { - setState(mounted, this.setState, () { - isHoverIcon = value; - }); - }, - child: Container( - width: 54, - height: 54, - color: Colors.transparent, - alignment: Alignment.center, - child: _buildAvatarIcon() - ) - ), - const SizedBox(width: 12), - Expanded(child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (widget.recipientForward.emailAddress.displayName.isNotEmpty) - Padding( - padding: const EdgeInsets.only(bottom: 4), - child: Text( - widget.recipientForward.emailAddress.displayName, - overflow: CommonTextStyle.defaultTextOverFlow, - softWrap: CommonTextStyle.defaultSoftWrap, - maxLines: 1, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.normal, - color: Colors.black - ) - ), - ), - Text( - widget.recipientForward.emailAddress.email ?? '', - overflow: CommonTextStyle.defaultTextOverFlow, - softWrap: CommonTextStyle.defaultSoftWrap, - maxLines: 1, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: AppColor.colorContentEmail - ) - ) - ], - )), - const SizedBox(width: 12), - if (isHoverItem) - buildIconWeb( - icon: SvgPicture.asset( - _imagePaths.icDeleteEmailForward, - fit: BoxFit.fill, - width: 18, - height: 18, - ), - onTap: () => _emailForwardController.deleteRecipients( - context, - widget.recipientForward.emailAddress.email ?? '' - ) - ), - ]), - ), - ); - } - - Widget _buildAvatarIcon() { - if (isHoverIcon || widget.recipientForward.selectMode == SelectMode.ACTIVE) { - return Container( - alignment: Alignment.center, - child: SvgPicture.asset( - widget.recipientForward.selectMode == SelectMode.ACTIVE - ? _imagePaths.icSelected - : _imagePaths.icUnSelected, - width: 24, - height: 24)); - } else { - return (AvatarBuilder() - ..text(widget.recipientForward.emailAddress.asString().firstLetterToUpperCase) - ..size(54) - ..addTextStyle(const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 20, - color: Colors.white)) - ..avatarColor(widget.recipientForward.emailAddress.avatarColors) - ).build(); - } - } -} diff --git a/lib/features/manage_account/presentation/forward/widgets/forward_header_widget.dart b/lib/features/manage_account/presentation/forward/widgets/forward_header_widget.dart index 84b8f8ae2..6632996b4 100644 --- a/lib/features/manage_account/presentation/forward/widgets/forward_header_widget.dart +++ b/lib/features/manage_account/presentation/forward/widgets/forward_header_widget.dart @@ -24,14 +24,14 @@ class ForwardHeaderWidget extends StatelessWidget { AppLocalizations.of(context).forwarding, style: const TextStyle( fontSize: 20, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.bold, color: Colors.black)), const SizedBox(height: 8), Text( AppLocalizations.of(context).forwardingSettingExplanation, style: const TextStyle( fontSize: 16, - fontWeight: FontWeight.normal, + fontWeight: FontWeight.w500, color: AppColor.colorSettingExplanation)), ]), ); diff --git a/lib/features/manage_account/presentation/forward/widgets/list_email_forward_widget.dart b/lib/features/manage_account/presentation/forward/widgets/list_email_forward_widget.dart index 217132126..08860fd21 100644 --- a/lib/features/manage_account/presentation/forward/widgets/list_email_forward_widget.dart +++ b/lib/features/manage_account/presentation/forward/widgets/list_email_forward_widget.dart @@ -1,11 +1,14 @@ -import 'package:core/core.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/presentation/views/button/icon_button_web.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; +import 'package:model/extensions/email_address_extension.dart'; import 'package:model/mailbox/select_mode.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/forward/forward_controller.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/forward/widgets/email_forward_item_widget.dart' - if (dart.library.html) 'package:tmail_ui_user/features/manage_account/presentation/forward/widgets/email_forward_item_widget_for_web.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/forward/widgets/email_forward_item_widget.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/menu/settings_utils.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/model/recipient_forward.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -20,46 +23,31 @@ class ListEmailForwardsWidget extends GetWidget { @override Widget build(BuildContext context) { return Container( - decoration: SettingsUtils.getBoxDecorationForListRecipient(context, _responsiveUtils), + color: Colors.white, margin: SettingsUtils.getPaddingListRecipientForwarding(context, _responsiveUtils), - child: ClipRRect( - borderRadius: BorderRadius.circular(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - _buildTitleHeader(context), - const Divider( - color: AppColor.lineItemListColor, - height: 1, - thickness: 0.2, - ), - Obx(() { - return ListView.separated( - shrinkWrap: true, - primary: false, - itemCount: controller.listRecipientForward.length, - itemBuilder: (context, index) { - final recipientForward = controller.listRecipientForward[index]; - return EmailForwardItemWidget( - recipientForward: recipientForward, - isLast: index == controller.listRecipientForward.length - 1, - selectionMode: controller.selectionMode.value); - }, - separatorBuilder: (context, index) { - if (index != controller.listRecipientForward.length - 1) { - return const Divider( - color: AppColor.lineItemListColor, - height: 1, - thickness: 0.2, - ); - } else { - return const SizedBox.shrink(); - } + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + _buildTitleHeader(context), + Obx(() { + return ListView.builder( + shrinkWrap: true, + primary: false, + itemCount: controller.listRecipientForward.length, + itemBuilder: (context, index) { + return EmailForwardItemWidget( + controller.listRecipientForward[index], + selectionMode: controller.selectionMode.value, + onSelectRecipientCallback: controller.selectRecipientForward, + onDeleteRecipientCallback: (recipientForward) { + controller.deleteRecipients(context, recipientForward.emailAddress.emailAddress); }, ); - }), - ]), + } + ); + }), + ] ), ); } @@ -67,27 +55,15 @@ class ListEmailForwardsWidget extends GetWidget { Widget _buildTitleHeader(BuildContext context) { return Obx(() => Container( width: double.infinity, - decoration: const BoxDecoration( - color: AppColor.colorBackgroundHeaderListForwards, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16) - ), - ), - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 12), + color: Colors.transparent, + height: 44, + padding: const EdgeInsets.only(right: 12), child: Row(children: [ - _buildSelectAllButton(), - const SizedBox(width: 12), - Expanded(child: Text( - AppLocalizations.of(context).headerRecipients, - overflow: CommonTextStyle.defaultTextOverFlow, - softWrap: CommonTextStyle.defaultSoftWrap, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: AppColor.colorTextButtonHeaderThread - ) - )), + if (controller.selectionMode.value == SelectMode.ACTIVE) + _buildCancelSectionButton(context), + if (!controller.isAllSelected) + _buildSelectAllButton(context), + const Spacer(), const SizedBox(width: 12), if (controller.listRecipientForwardSelected.isNotEmpty) _buildDeleteAllButton(context) @@ -95,44 +71,75 @@ class ListEmailForwardsWidget extends GetWidget { )); } - Widget _buildSelectAllButton() { + Widget _buildSelectAllButton(BuildContext context) { return Material( + color: Colors.transparent, child: InkWell( - customBorder: const CircleBorder(), + customBorder: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5))), child: Padding( - padding: const EdgeInsets.all(16), - child: SvgPicture.asset( - controller.selectionMode.value == SelectMode.ACTIVE - ? _imagePaths.icCancelSelection - : _imagePaths.icUnSelected, - width: 24, - height: 24 + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + child: Text( + AppLocalizations.of(context).select_all, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.normal, + color: AppColor.colorTextButton + ) ), ), - onTap: () { - if (controller.selectionMode.value == SelectMode.ACTIVE) { - controller.cancelSelectionMode(); - } else { - controller.selectAllRecipientForward(); - } - }, + onTap: controller.selectAllRecipientForward, + ) + ); + } + + Widget _buildCancelSectionButton(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(right: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + buildIconWeb( + icon: SvgPicture.asset( + _imagePaths.icCloseComposer, + color: AppColor.colorTextButton, + fit: BoxFit.fill), + tooltip: AppLocalizations.of(context).cancel, + onTap: controller.cancelSelectionMode + ), + Text( + controller.isAllSelected + ? AppLocalizations.of(context).totalEmailSelected(controller.listRecipientForwardSelected.length) + : AppLocalizations.of(context).count_email_selected(controller.listRecipientForwardSelected.length), + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.normal, + color: AppColor.colorTextButton + ) + ) + ], ), - color: Colors.transparent, ); } Widget _buildDeleteAllButton(BuildContext context) { - return buildIconWeb( - icon: SvgPicture.asset( - _imagePaths.icDeleteEmailForward, - fit: BoxFit.fill, - width: 18, - height: 18, - ), - tooltip: AppLocalizations.of(context).delete_all, - onTap: () => controller.deleteMultipleRecipients( - context, - controller.listRecipientForwardSelected.listEmailAddress + return Material( + color: Colors.transparent, + child: InkWell( + customBorder: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5))), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + child: Text( + AppLocalizations.of(context).remove, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.normal, + color: AppColor.colorDeletePermanentlyButton + ) + ), + ), + onTap: () => controller.deleteMultipleRecipients( + context, + controller.listRecipientForwardSelected.listEmailAddress) ) ); } diff --git a/lib/features/manage_account/presentation/forward/widgets/recipient_input_decoration_builder.dart b/lib/features/manage_account/presentation/forward/widgets/recipient_input_decoration_builder.dart deleted file mode 100644 index 5547ede7a..000000000 --- a/lib/features/manage_account/presentation/forward/widgets/recipient_input_decoration_builder.dart +++ /dev/null @@ -1,41 +0,0 @@ - -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/views/text/input_decoration_builder.dart'; -import 'package:core/utils/build_utils.dart'; -import 'package:flutter/material.dart'; - -class RecipientInputDecorationBuilder extends InputDecorationBuilder { - - @override - InputDecoration build() { - return InputDecoration( - enabledBorder: enabledBorder ?? const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(10)), - borderSide: BorderSide(width: 0.5, color: AppColor.colorInputBorderCreateMailbox)), - focusedBorder: enabledBorder ?? const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(10)), - borderSide: BorderSide(width: 0.5, color: AppColor.colorTextButton)), - errorBorder: errorBorder ?? const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(10)), - borderSide: BorderSide(width: 0.5, color: AppColor.colorInputBorderErrorVerifyName)), - focusedErrorBorder: errorBorder ?? const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(10)), - borderSide: BorderSide(width: 0.5, color: AppColor.colorInputBorderErrorVerifyName)), - prefixText: prefixText, - labelText: labelText, - floatingLabelBehavior: FloatingLabelBehavior.never, - labelStyle: labelStyle ?? const TextStyle(color: Colors.black, fontSize: 16), - hintText: hintText, - isDense: true, - hintStyle: hintStyle ?? const TextStyle(color: AppColor.colorEmailAddressFull, fontSize: 16), - contentPadding: const EdgeInsets.symmetric( - vertical: BuildUtils.isWeb ? 16 : 12, - horizontal: 12), - errorText: errorText, - errorStyle: errorTextStyle ?? const TextStyle(color: AppColor.colorInputBorderErrorVerifyName, fontSize: 13), - filled: true, - fillColor: errorText?.isNotEmpty == true - ? AppColor.colorInputBackgroundErrorVerifyName - : AppColor.colorInputBackgroundCreateMailbox); - } -} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/forward/widgets/recipient_input_field_builder.dart b/lib/features/manage_account/presentation/forward/widgets/recipient_input_field_builder.dart deleted file mode 100644 index 912bbecd1..000000000 --- a/lib/features/manage_account/presentation/forward/widgets/recipient_input_field_builder.dart +++ /dev/null @@ -1,81 +0,0 @@ - -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/manage_account/presentation/forward/widgets/recipient_input_decoration_builder.dart'; -import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; - -typedef OnSelectedSuggestionAction = Function(EmailAddress emailAddress); -typedef OnSuggestionCallbackAction = Function(String pattern); -typedef OnSummitedCallbackAction = Function(String pattern); -typedef OnRecipientInputTextChange = Function(String pattern); - -class RecipientInputFieldBuilder extends StatelessWidget { - - final TextEditingController? editingController; - final OnSelectedSuggestionAction? onSelectedSuggestionAction; - final OnSuggestionCallbackAction? onSuggestionCallbackAction; - final OnSummitedCallbackAction? onSummitedCallbackAction; - final OnRecipientInputTextChange? onRecipientInputTextChange; - final String? errorText; - - const RecipientInputFieldBuilder({ - Key? key, - this.editingController, - this.onSelectedSuggestionAction, - this.onSuggestionCallbackAction, - this.onSummitedCallbackAction, - this.onRecipientInputTextChange, - this.errorText - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return TypeAheadFormField( - textFieldConfiguration: TextFieldConfiguration( - controller: editingController, - onSubmitted: (pattern) { - if (onSummitedCallbackAction != null) { - onSummitedCallbackAction!(pattern); - } - }, - onChanged: onRecipientInputTextChange, - textInputAction: TextInputAction.done, - decoration: (RecipientInputDecorationBuilder() - ..setHintText(AppLocalizations.of(context).recipientsLabel) - ..setErrorText(errorText) - ).build() - ), - debounceDuration: const Duration(milliseconds: 500), - suggestionsCallback: (pattern) async { - 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, - ); - } -} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/manage_account_dashboard_controller.dart b/lib/features/manage_account/presentation/manage_account_dashboard_controller.dart index 613d1a9da..f6e810fbb 100644 --- a/lib/features/manage_account/presentation/manage_account_dashboard_controller.dart +++ b/lib/features/manage_account/presentation/manage_account_dashboard_controller.dart @@ -63,6 +63,7 @@ class ManageAccountDashBoardController extends ReloadableController { void onReady() { _getArguments(); _getAppVersion(); + _initialPageLevel(); super.onReady(); } @@ -116,6 +117,14 @@ class ManageAccountDashBoardController extends ReloadableController { } } + void _initialPageLevel() { + if (currentContext != null && _responsiveUtils.isWebDesktop(currentContext!)) { + settingsPageLevel.value = SettingsPageLevel.level1; + } else { + settingsPageLevel.value = SettingsPageLevel.universal; + } + } + @override void injectVacationBindings(Session? session, AccountId? accountId) { try { diff --git a/lib/features/manage_account/presentation/menu/settings_utils.dart b/lib/features/manage_account/presentation/menu/settings_utils.dart index c15bc4402..aa0ff3039 100644 --- a/lib/features/manage_account/presentation/menu/settings_utils.dart +++ b/lib/features/manage_account/presentation/menu/settings_utils.dart @@ -80,9 +80,9 @@ class SettingsUtils { static EdgeInsets getPaddingListRecipientForwarding(BuildContext context, ResponsiveUtils responsiveUtils) { if (responsiveUtils.isPortraitMobile(context)) { - return const EdgeInsets.all(12); + return const EdgeInsets.only(left: 12, right: 12, bottom: 12); } else { - return const EdgeInsets.symmetric(horizontal: 24, vertical: 12); + return const EdgeInsets.only(left: 24, right: 24, bottom: 24); } } diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index d4379a2c7..41fc5d304 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -2107,7 +2107,7 @@ class AppLocalizations { ); } - String get recipientsLabel { + String get hintInputAutocompleteContact { return Intl.message( 'Enter name or email address', name: 'recipientsLabel', @@ -2511,4 +2511,12 @@ class AppLocalizations { 'Remove', name: 'remove'); } + + String totalEmailSelected(int count) { + return Intl.message( + 'Deselect all ($count)', + name: 'totalEmailSelected', + args: [count] + ); + } } \ No newline at end of file diff --git a/model/lib/extensions/email_address_extension.dart b/model/lib/extensions/email_address_extension.dart index b81104223..2a0b31d9c 100644 --- a/model/lib/extensions/email_address_extension.dart +++ b/model/lib/extensions/email_address_extension.dart @@ -31,6 +31,8 @@ extension EmailAddressExtension on EmailAddress { String get displayName => name ?? ''; + String get labelAvatar => asString().isNotEmpty ? asString()[0].toUpperCase() : ''; + List get avatarColors { return AppColor.mapGradientColor[_generateIndex()]; } diff --git a/pubspec.lock b/pubspec.lock index f7900ede4..bb332d3db 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1266,7 +1266,7 @@ packages: name: super_tag_editor url: "https://pub.dartlang.org" source: hosted - version: "0.0.1" + version: "0.0.3+2" syncfusion_flutter_core: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 848d9654b..99526229f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -96,7 +96,7 @@ dependencies: filesize: 2.0.1 # super_tag_editor - super_tag_editor: 0.0.1 + super_tag_editor: 0.0.3+2 # uuid uuid: 3.0.4