TF-1118 Apply new latest design forwarding

This commit is contained in:
dab246
2022-11-08 15:45:49 +07:00
committed by Dat H. Pham
parent 2402371ca3
commit eaa47b9cd7
31 changed files with 897 additions and 714 deletions
+11 -5
View File
@@ -1,6 +1,12 @@
<svg width="65" height="64" viewBox="0 0 65 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" width="64" height="64" rx="16" fill="#FFE9DB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.4445 27.5015C28.8931 27.5322 28.4709 28.004 28.5015 28.5555L29.0015 37.5555C29.0322 38.1069 29.504 38.5291 30.0555 38.4985C30.6069 38.4678 31.0291 37.996 30.9985 37.4445L30.4985 28.4445C30.4678 27.8931 29.996 27.4709 29.4445 27.5015Z" fill="#E64646"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M35.5555 27.5015C36.1069 27.5322 36.5291 28.004 36.4985 28.5555L35.9985 37.5555C35.9678 38.1069 35.496 38.5291 34.9445 38.4985C34.3931 38.4678 33.9709 37.996 34.0015 37.4445L34.5015 28.4445C34.5322 27.8931 35.004 27.4709 35.5555 27.5015Z" fill="#E64646"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 23C21.4477 23 21 23.4477 21 24C21 24.5523 21.4477 25 22 25H23.0961L24.3936 37.8226C24.4674 38.5523 24.5286 39.157 24.6175 39.6489C24.7102 40.1618 24.845 40.629 25.1061 41.0628C25.5134 41.7394 26.1117 42.2803 26.8259 42.6173C27.2837 42.8334 27.7622 42.9205 28.2817 42.9611C28.7802 43 29.3879 43 30.1214 43H34.8786C35.6121 43 36.2198 43 36.7183 42.9611C37.2378 42.9205 37.7163 42.8334 38.1741 42.6173C38.8883 42.2803 39.4866 41.7394 39.8939 41.0628C40.155 40.629 40.2898 40.1618 40.3825 39.6489C40.4714 39.157 40.5326 38.5523 40.6064 37.8226L41.9039 25H43C43.5523 25 44 24.5523 44 24C44 23.4477 43.5523 23 43 23H36.8885C36.4338 20.996 34.6416 19.5 32.5 19.5C30.3584 19.5 28.5662 20.996 28.1115 23H22ZM26.3795 37.5826C26.4583 38.3607 26.5123 38.8873 26.5856 39.2932C26.6568 39.687 26.734 39.8891 26.8196 40.0314C27.0233 40.3697 27.3225 40.6401 27.6795 40.8087C27.8297 40.8796 28.0385 40.936 28.4375 40.9672C28.8488 40.9993 29.3781 41 30.1602 41H34.8398C35.6219 41 36.1512 40.9993 36.5625 40.9672C36.9615 40.936 37.1703 40.8796 37.3205 40.8087C37.6775 40.6401 37.9767 40.3697 38.1804 40.0314C38.266 39.8891 38.3432 39.687 38.4144 39.2932C38.4877 38.8873 38.5417 38.3607 38.6205 37.5826L39.8937 25H25.1063L26.3795 37.5826ZM32.5 21.5C31.4748 21.5 30.5938 22.117 30.208 23H34.792C34.4062 22.117 33.5252 21.5 32.5 21.5Z" fill="#E64646"/>
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="64" height="64" rx="16" fill="#F2F3F5" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M28.5083 26.8595C27.8781 26.8945 27.3956 27.4337 27.4306 28.064L28.002 38.3497C28.037 38.9799 28.5763 39.4624 29.2065 39.4274C29.8367 39.3924 30.3192 38.8531 30.2842 38.2229L29.7128 27.9372C29.6778 27.307 29.1385 26.8244 28.5083 26.8595Z"
fill="#E64646" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M35.4922 26.8595C36.1225 26.8945 36.605 27.4337 36.5699 28.064L35.9985 38.3497C35.9635 38.9799 35.4242 39.4624 34.794 39.4274C34.1638 39.3924 33.6813 38.8531 33.7163 38.2229L34.2878 27.9372C34.3228 27.307 34.862 26.8244 35.4922 26.8595Z"
fill="#E64646" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M20.0003 21.7148C19.3691 21.7148 18.8574 22.2265 18.8574 22.8577C18.8574 23.4889 19.3691 24.0006 20.0003 24.0006H21.2529L22.7358 38.655C22.8202 39.4889 22.8901 40.18 22.9917 40.7422C23.0977 41.3283 23.2517 41.8623 23.5501 42.3581C24.0155 43.1313 24.6994 43.7494 25.5155 44.1346C26.0388 44.3816 26.5856 44.4811 27.1794 44.5275C27.749 44.572 28.4436 44.572 29.2819 44.572H34.7187C35.5569 44.572 36.2515 44.572 36.8211 44.5275C37.4149 44.4811 37.9617 44.3816 38.485 44.1346C39.3012 43.7494 39.985 43.1313 40.4504 42.3581C40.7488 41.8623 40.9029 41.3283 41.0088 40.7422C41.1104 40.18 41.1803 39.4889 41.2647 38.655L42.7476 24.0006H44.0003C44.6315 24.0006 45.1431 23.4889 45.1431 22.8577C45.1431 22.2265 44.6315 21.7148 44.0003 21.7148H37.0157C36.496 19.4246 34.4478 17.7148 32.0003 17.7148C29.5527 17.7148 27.5045 19.4246 26.9849 21.7148H20.0003ZM25.0055 38.3806C25.0954 39.2699 25.1572 39.8717 25.241 40.3357C25.3224 40.7857 25.4106 41.0167 25.5084 41.1793C25.7412 41.5659 26.0831 41.875 26.4912 42.0676C26.6628 42.1486 26.9015 42.2131 27.3574 42.2487C27.8275 42.2855 28.4324 42.2863 29.3263 42.2863H34.6743C35.5681 42.2863 36.1731 42.2855 36.6431 42.2487C37.0991 42.2131 37.3378 42.1486 37.5094 42.0676C37.9175 41.875 38.2594 41.5659 38.4921 41.1793C38.59 41.0167 38.6782 40.7857 38.7595 40.3357C38.8434 39.8717 38.9051 39.2699 38.9951 38.3806L40.4502 24.0006H23.5503L25.0055 38.3806ZM32.0003 20.0006C30.8287 20.0006 29.8218 20.7058 29.3809 21.7148H34.6197C34.1788 20.7058 33.1719 20.0006 32.0003 20.0006Z"
fill="#E64646" />
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.0001 1.71484C17.6807 1.71484 22.2858 6.31991 22.2858 12.0006C22.2858 17.6812 17.6807 22.2863 12.0001 22.2863C6.31943 22.2863 1.71436 17.6812 1.71436 12.0006C1.71436 6.31991 6.31943 1.71484 12.0001 1.71484ZM12.0001 3.42913C7.2662 3.42913 3.42864 7.26669 3.42864 12.0006C3.42864 16.7344 7.2662 20.572 12.0001 20.572C16.7339 20.572 20.5715 16.7344 20.5715 12.0006C20.5715 7.26669 16.7339 3.42913 12.0001 3.42913ZM16.2858 11.1434C16.7592 11.1434 17.1429 11.5272 17.1429 12.0006C17.1429 12.4739 16.7592 12.8577 16.2858 12.8577H7.71436C7.24097 12.8577 6.85721 12.4739 6.85721 12.0006C6.85721 11.5272 7.24097 11.1434 7.71436 11.1434H16.2858Z"
fill="#E64646" />
</svg>

After

Width:  |  Height:  |  Size: 790 B

+8
View File
@@ -0,0 +1,8 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="20" fill="#007AFF" />
<path
d="M17 24.5858L11.7071 19.2929C11.3166 18.9024 10.6834 18.9024 10.2929 19.2929C9.90237 19.6834 9.90237 20.3166 10.2929 20.7071L16.2929 26.7071C16.6834 27.0976 17.3166 27.0976 17.7071 26.7071L29.7071 14.7071C30.0976 14.3166 30.0976 13.6834 29.7071 13.2929C29.3166 12.9024 28.6834 12.9024 28.2929 13.2929L17 24.5858Z"
fill="white" />
<rect x="0.10989" y="0.10989" width="39.7802" height="39.7802" rx="19.8901" stroke="#EBEDF0"
stroke-opacity="0.08" stroke-width="0.21978" stroke-linejoin="round" />
</svg>

After

Width:  |  Height:  |  Size: 696 B

@@ -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)
@@ -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;
@@ -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))
@@ -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
@@ -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<ContactController> {
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<ContactController> {
},
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<ContactController> {
);
}
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<EmailAddress> addedEmailAddresses) {
if (addedEmailAddresses.contains(item)) {
return SuggestionEmailAddress(item, state: SuggestionEmailState.duplicated);
} else {
return SuggestionEmailAddress(item);
}
}
}
@@ -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<List<EmailAddress>> Function(String query);
typedef OnAddListContactCallbackAction = Function(List<EmailAddress> listEmailAddress);
typedef OnExceptionAddListContactCallbackAction = Function();
class AutocompleteContactTextFieldWithTags extends StatefulWidget {
final List<EmailAddress> 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<AutocompleteContactTextFieldWithTags> createState() => _AutocompleteContactTextFieldWithTagsState();
}
class _AutocompleteContactTextFieldWithTagsState extends State<AutocompleteContactTextFieldWithTags> {
final _responsiveUtils = Get.find<ResponsiveUtils>();
final _imagePaths = Get.find<ImagePaths>();
late List<EmailAddress> 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<SuggestionEmailAddress>(
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<List<SuggestionEmailAddress>> _findSuggestions(String query) async {
if (_gapBetweenTagChangedAndFindSuggestion.isActive) {
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));
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));
}
bool _isValidAllEmailAddress(List<EmailAddress> 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();
}
}
@@ -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);
}
}
@@ -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<ImagePaths>();
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;
}
@@ -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<ImagePaths>();
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,
),
);
}
}
}
@@ -0,0 +1,44 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:flutter/material.dart';
class GradientColorAvatarIcon extends StatelessWidget {
final List<Color> 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)
)
);
}
}
@@ -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 '';
}
}
}
@@ -7,7 +7,7 @@ extension TMailForwardExtension on TMailForward {
List<RecipientForward> get listRecipientForward {
return forwards
.map((emailAddress) => RecipientForward(EmailAddress(emailAddress, emailAddress)))
.map((value) => RecipientForward(EmailAddress(value, value)))
.toList();
}
}
@@ -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<VerifyNameInteractor>()));
Get.lazyPut(() => ForwardController());
}
}
@@ -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<EmailAddress>();
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<EmailAddress> listEmailAddress) {
listRecipients.value = listEmailAddress;
}
void clearAll() {
inputEmailForwardController.clear();
_emailAddressSelected = null;
inputRecipientController.clear();
listRecipients.clear();
}
void _disposeWidget() {
inputEmailForwardController.dispose();
inputRecipientController.dispose();
}
}
@@ -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>(SelectMode.INACTIVE);
final listRecipientForward = RxList<RecipientForward>();
final currentForward = Rxn<TMailForward>();
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<RecipientForward> get listRecipientForwardSelected => listRecipientForward
.where((recipient) => recipient.selectMode == SelectMode.ACTIVE)
.toList();
List<RecipientForward> 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<String> listEmailAddress) {
@@ -233,21 +230,13 @@ class ForwardController extends BaseController {
.toList();
}
void addRecipientAction(BuildContext context) {
void addRecipientAction(BuildContext context, List<EmailAddress> 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
);
}
}
@@ -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<ForwardController> with AppLoaderMixin {
final _responsiveUtils = Get.find<ResponsiveUtils>();
final _imagePaths = Get.find<ImagePaths>();
final _appToast = Get.find<AppToast>();
ForwardView({Key? key}) : super(key: key);
@@ -76,7 +76,7 @@ class ForwardView extends GetWidget<ForwardController> 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<ForwardController> with AppLoaderMixin {
? _imagePaths.icSwitchOn
: _imagePaths.icSwitchOff,
fit: BoxFit.fill,
width: 24,
width: 36,
height: 24)
);
}),
@@ -108,7 +108,7 @@ class ForwardView extends GetWidget<ForwardController> 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<ForwardController> 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));
},
);
}
}
@@ -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<ImagePaths>();
final _emailForwardController = Get.find<ForwardController>();
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<ImagePaths>();
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();
}
}
@@ -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<EmailForwardItemWidget> createState() => _EmailForwardItemWidgetState();
}
class _EmailForwardItemWidgetState extends State<EmailForwardItemWidget> {
final _imagePaths = Get.find<ImagePaths>();
final _emailForwardController = Get.find<ForwardController>();
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();
}
}
}
@@ -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)),
]),
);
@@ -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<ForwardController> {
@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<ForwardController> {
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<ForwardController> {
));
}
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)
)
);
}
@@ -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);
}
}
@@ -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<EmailAddress>(
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,
);
}
}
@@ -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 {
@@ -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);
}
}
@@ -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]
);
}
}
@@ -31,6 +31,8 @@ extension EmailAddressExtension on EmailAddress {
String get displayName => name ?? '';
String get labelAvatar => asString().isNotEmpty ? asString()[0].toUpperCase() : '';
List<Color> get avatarColors {
return AppColor.mapGradientColor[_generateIndex()];
}
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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