create suggest box email

This commit is contained in:
ManhNTX
2022-06-29 01:39:07 +07:00
committed by Dat H. Pham
parent 7ca8b83822
commit 0c3391f76c
20 changed files with 633 additions and 90 deletions
@@ -120,6 +120,7 @@ extension AppColor on Color {
static const colorBgMenuItemDropDownSelected = Color(0x80DEE2E7);
static const colorButtonCancelDialog = Color(0x0D000000);
static const colorShadowComposerButton = Color(0x99007AFF);
static const colorBackgroundTagFilter = Color(0x6D7885);
static const mapGradientColor = [
[Color(0xFF21D4FD), Color(0xFFB721FF)],
@@ -0,0 +1,22 @@
# 7. Fix show Dropdown can't not over overlayUI (#685)
Date: 2022-06-29
## Status
- Issue: DropDownButton menu shows behind Overlay
## Context
- Root cause: Add package flutter_portal to fix this issue
## Decision
- When add Dropdown button to overlayUI, it can't lying above overlayUI.
## Consequences
- The app Navigator has a list of routes (_history) and each route has its list of OverlayEntry's.
When you push a new route using Navigator.push() you are inserting the route overlays above the overlays of the last route.
So, when you add an independent Overlay with just Overlay.of(context).insert(myOverlay);
it will always be above any route/overlay managed by the app Navigator (if what you receive from the of() method is the Navigator Overlay).
@@ -0,0 +1,63 @@
import 'package:contact/contact_module.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/base_bindings.dart';
import 'package:tmail_ui_user/features/composer/data/datasource/contact_datasource.dart';
import 'package:tmail_ui_user/features/composer/data/datasource_impl/contact_datasource_impl.dart';
import 'package:tmail_ui_user/features/composer/data/repository/auto_complete_repository_impl.dart';
import 'package:tmail_ui_user/features/composer/data/repository/contact_repository_impl.dart';
import 'package:tmail_ui_user/features/composer/domain/repository/auto_complete_repository.dart';
import 'package:tmail_ui_user/features/composer/domain/repository/contact_repository.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_with_device_contact_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/get_device_contact_suggestions_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_interactor.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/session_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:jmap_dart_client/http/http_client.dart' as jmap_http_client;
class AdvancedFilterBindings extends BaseBindings {
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
final Set<AutoCompleteDataSource> dataSources = {};
@override
void bindingsDataSourceImpl() {
if (mailboxDashBoardController.sessionCurrent?.hasSupportAutoComplete == true) {
Get.lazyPut(() => ContactAPI(Get.find<jmap_http_client.HttpClient>()));
Get.lazyPut(() => TMailContactDataSourceImpl(Get.find<ContactAPI>()));
dataSources.add(Get.find<TMailContactDataSourceImpl>());
}
Get.lazyPut(() => ContactDataSourceImpl());
}
@override
void bindingsDataSource() {
Get.lazyPut<ContactDataSource>(() => Get.find<ContactDataSourceImpl>());
}
@override
void bindingsRepositoryImpl() {
Get.lazyPut(() => AutoCompleteRepositoryImpl(dataSources));
Get.lazyPut(() => ContactRepositoryImpl(Get.find<ContactDataSource>()));
}
@override
void bindingsRepository() {
Get.lazyPut<AutoCompleteRepository>(() => Get.find<AutoCompleteRepositoryImpl>());
Get.lazyPut<ContactRepository>(() => Get.find<ContactRepositoryImpl>());
}
@override
void bindingsInteractor() {
Get.lazyPut(() => GetAutoCompleteInteractor(Get.find<AutoCompleteRepository>()));
Get.lazyPut(() => GetDeviceContactSuggestionsInteractor(Get.find<ContactRepository>()));
Get.lazyPut(() => GetAutoCompleteWithDeviceContactInteractor(
Get.find<GetAutoCompleteInteractor>(),
Get.find<GetDeviceContactSuggestionsInteractor>()
));
}
@override
void bindingsController() {
}
}
@@ -3,6 +3,13 @@ import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/base_bindings.dart';
import 'package:tmail_ui_user/features/caching/recent_search_cache_client.dart';
import 'package:tmail_ui_user/features/caching/state_cache_client.dart';
import 'package:tmail_ui_user/features/composer/data/repository/auto_complete_repository_impl.dart';
import 'package:tmail_ui_user/features/composer/data/repository/contact_repository_impl.dart';
import 'package:tmail_ui_user/features/composer/domain/repository/auto_complete_repository.dart';
import 'package:tmail_ui_user/features/composer/domain/repository/contact_repository.dart';
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/composer/domain/usecases/get_device_contact_suggestions_interactor.dart';
import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart';
import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart';
import 'package:tmail_ui_user/features/email/data/datasource_impl/email_datasource_impl.dart';
@@ -129,6 +136,12 @@ class MailboxDashBoardBindings extends BaseBindings {
@override
void bindingsInteractor() {
Get.lazyPut(() => GetAutoCompleteInteractor(Get.find<AutoCompleteRepository>()));
Get.lazyPut(() => GetDeviceContactSuggestionsInteractor(Get.find<ContactRepository>()));
Get.lazyPut(() => GetAutoCompleteWithDeviceContactInteractor(
Get.find<GetAutoCompleteInteractor>(),
Get.find<GetDeviceContactSuggestionsInteractor>()
));
Get.lazyPut(() => GetUserProfileInteractor(Get.find<CredentialRepository>()));
Get.lazyPut(() => RemoveEmailDraftsInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => MoveToMailboxInteractor(Get.find<EmailRepository>()));
@@ -156,6 +169,8 @@ class MailboxDashBoardBindings extends BaseBindings {
@override
void bindingsRepository() {
Get.lazyPut<AutoCompleteRepository>(() => Get.find<AutoCompleteRepositoryImpl>());
Get.lazyPut<ContactRepository>(() => Get.find<ContactRepositoryImpl>());
Get.lazyPut<EmailRepository>(() => Get.find<EmailRepositoryImpl>());
Get.lazyPut<SearchRepository>(() => Get.find<SearchRepositoryImpl>());
Get.lazyPut<ThreadRepository>(() => Get.find<ThreadRepositoryImpl>());
@@ -2,10 +2,16 @@ import 'package:collection/collection.dart';
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:model/model.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:tmail_ui_user/features/composer/domain/model/contact_suggestion_source.dart';
import 'package:tmail_ui_user/features/composer/domain/state/get_autocomplete_state.dart';
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/destination_picker/presentation/model/destination_picker_arguments.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/bindings/advanced_filter_bindings.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart';
@@ -13,33 +19,59 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/sear
import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
class AdvancedFilterController extends BaseController {
class AdvancedFilterController extends GetxController {
late GetAutoCompleteWithDeviceContactInteractor
_getAutoCompleteWithDeviceContactInteractor;
late GetAutoCompleteInteractor _getAutoCompleteInteractor;
final dateFilterSelectedFormAdvancedSearch = EmailReceiveTimeType.allTime.obs;
final hasAttachment = false.obs;
final listEmailAddressInit = RxList<EmailAddress>();
final listTagFromSelected = RxList<String>();
final listTagToSelected = RxList<String>();
TextEditingController fromFilterInputController = TextEditingController();
TextEditingController toFilterInputController = TextEditingController();
TextEditingController subjectFilterInputController = TextEditingController();
TextEditingController hasKeyWordFilterInputController = TextEditingController();
TextEditingController notKeyWordFilterInputController = TextEditingController();
TextEditingController hasKeyWordFilterInputController =
TextEditingController();
TextEditingController notKeyWordFilterInputController =
TextEditingController();
TextEditingController dateFilterInputController = TextEditingController();
TextEditingController mailBoxFilterInputController = TextEditingController();
ContactSuggestionSource _contactSuggestionSource =
ContactSuggestionSource.tMailContact;
final SearchController _searchController = Get.find<SearchController>();
final MailboxDashBoardController _mailboxDashBoardController = Get.find<MailboxDashBoardController>();
final MailboxDashBoardController _mailboxDashBoardController =
Get.find<MailboxDashBoardController>();
SearchEmailFilter get _searchEmailFilter => _searchController.searchEmailFilter.value;
AdvancedFilterController() {
AdvancedFilterBindings().dependencies();
_getAutoCompleteWithDeviceContactInteractor =
Get.find<GetAutoCompleteWithDeviceContactInteractor>();
_getAutoCompleteInteractor = Get.find<GetAutoCompleteInteractor>();
}
selectOpenAdvanceSearch() {
SearchEmailFilter get searchEmailFilter =>
_searchController.searchEmailFilter.value;
@override
void onReady() async {
if (!BuildUtils.isWeb) {
Future.delayed(
const Duration(milliseconds: 500), () => _checkContactPermission());
}
super.onReady();
}
void selectOpenAdvanceSearch() {
_searchController.isAdvancedSearchViewOpen.toggle();
}
cleanSearchFilter(BuildContext context) {
void cleanSearchFilter(BuildContext context) {
_searchController.cleanSearchFilter();
dateFilterSelectedFormAdvancedSearch.value = EmailReceiveTimeType.allTime;
fromFilterInputController.text = '';
toFilterInputController.text = '';
listTagFromSelected.clear();
listTagToSelected.clear();
subjectFilterInputController.text = '';
hasKeyWordFilterInputController.text = '';
notKeyWordFilterInputController.text = '';
@@ -47,33 +79,39 @@ class AdvancedFilterController extends BaseController {
hasAttachment.value = false;
_searchController.isAdvancedSearchHasApply.value = false;
_searchController.isAdvancedSearchViewOpen.toggle();
_mailboxDashBoardController.searchEmail(context, StringConvert.writeNullToEmpty(_searchEmailFilter.text?.value));
_mailboxDashBoardController.searchEmail(
context, StringConvert.writeNullToEmpty(searchEmailFilter.text?.value));
}
void _updateFilterEmailFromAdvancedSearchView() {
if (fromFilterInputController.text.isNotEmpty) {
_searchController.updateFilterEmail(from: fromFilterInputController.text.split(',').toSet());
if (listTagFromSelected.isNotEmpty) {
_searchController.updateFilterEmail(
from: listTagFromSelected.toSet());
} else {
_searchController.updateFilterEmail(from: {});
}
if (toFilterInputController.text.isNotEmpty) {
_searchController.updateFilterEmail(to: toFilterInputController.text.split(',').toSet());
if (listTagToSelected.isNotEmpty) {
_searchController.updateFilterEmail(
to: listTagToSelected.toSet());
} else {
_searchController.updateFilterEmail(to: {});
}
if (hasKeyWordFilterInputController.text.isNotEmpty) {
_searchController.updateFilterEmail(hasKeyword: hasKeyWordFilterInputController.text.split(',').toSet());
_searchController.updateFilterEmail(
hasKeyword: hasKeyWordFilterInputController.text.split(',').toSet());
} else {
_searchController.updateFilterEmail(hasKeyword: {});
}
if (notKeyWordFilterInputController.text.isNotEmpty) {
_searchController.updateFilterEmail(notKeyword: notKeyWordFilterInputController.text.split(',').toSet());
_searchController.updateFilterEmail(
notKeyword: notKeyWordFilterInputController.text.split(',').toSet());
} else {
_searchController.updateFilterEmail(notKeyword: {});
}
_searchController.updateFilterEmail(
subject: StringConvert.writeEmptyToNull(subjectFilterInputController.text),
subject:
StringConvert.writeEmptyToNull(subjectFilterInputController.text),
emailReceiveTimeType: dateFilterSelectedFormAdvancedSearch.value,
hasAttachment: hasAttachment.value,
);
@@ -82,21 +120,61 @@ class AdvancedFilterController extends BaseController {
Future<void> selectedMailBox() async {
final PresentationMailbox destinationMailbox = await push(
AppRoutes.DESTINATION_PICKER,
arguments: DestinationPickerArguments(_mailboxDashBoardController.accountId.value!, MailboxActions.moveEmail));
arguments: DestinationPickerArguments(
_mailboxDashBoardController.accountId.value!,
MailboxActions.moveEmail));
_searchController.updateFilterEmail(mailbox: destinationMailbox);
mailBoxFilterInputController.text = StringConvert.writeNullToEmpty(destinationMailbox.name?.name);
mailBoxFilterInputController.text =
StringConvert.writeNullToEmpty(destinationMailbox.name?.name);
}
applyAdvancedSearchFilter(BuildContext context){
void applyAdvancedSearchFilter(BuildContext context) {
_updateFilterEmailFromAdvancedSearchView();
_mailboxDashBoardController.searchEmail(context, StringConvert.writeNullToEmpty(_searchEmailFilter.text?.value));
_mailboxDashBoardController.searchEmail(
context, StringConvert.writeNullToEmpty(searchEmailFilter.text?.value));
_searchController.isAdvancedSearchViewOpen.toggle();
_searchController.isAdvancedSearchHasApply.value = _checkAdvancedSearchHasApply();
_searchController.isAdvancedSearchHasApply.value =
_checkAdvancedSearchHasApply();
}
void _checkContactPermission() async {
final permissionStatus = await Permission.contacts.status;
if (permissionStatus.isGranted) {
_contactSuggestionSource = ContactSuggestionSource.all;
} else if (!permissionStatus.isPermanentlyDenied) {
final requestedPermission = await Permission.contacts.request();
_contactSuggestionSource = requestedPermission == PermissionStatus.granted
? ContactSuggestionSource.all
: _contactSuggestionSource;
}
}
Future<List<EmailAddress>> getAutoCompleteSuggestion(
{required String word}) async {
if (_contactSuggestionSource == ContactSuggestionSource.all) {
return await _getAutoCompleteWithDeviceContactInteractor
.execute(AutoCompletePattern(
word: word,
accountId: _mailboxDashBoardController.accountId.value))
.then((value) => value.fold(
(failure) => <EmailAddress>[],
(success) => success is GetAutoCompleteSuccess
? success.listEmailAddress
: <EmailAddress>[]));
}
return await _getAutoCompleteInteractor
.execute(AutoCompletePattern(
word: word, accountId: _mailboxDashBoardController.accountId.value))
.then((value) => value.fold(
(failure) => <EmailAddress>[],
(success) => success is GetAutoCompleteSuccess
? success.listEmailAddress
: <EmailAddress>[]));
}
bool _checkAdvancedSearchHasApply() {
return fromFilterInputController.text.isNotEmpty ||
toFilterInputController.text.isNotEmpty ||
return listTagFromSelected.isNotEmpty ||
listTagToSelected.isNotEmpty ||
subjectFilterInputController.text.isNotEmpty ||
hasKeyWordFilterInputController.text.isNotEmpty ||
notKeyWordFilterInputController.text.isNotEmpty ||
@@ -105,32 +183,33 @@ class AdvancedFilterController extends BaseController {
hasAttachment.isTrue;
}
initSearchFilterField(BuildContext context) {
_searchController.updateFilterEmail(mailbox: _mailboxDashBoardController.selectedMailbox.value);
fromFilterInputController.text = StringConvert.writeNullToEmpty(_searchEmailFilter.from.firstOrNull);
toFilterInputController.text = StringConvert.writeNullToEmpty(_searchEmailFilter.to.firstOrNull);
subjectFilterInputController.text = StringConvert.writeNullToEmpty(_searchEmailFilter.subject);
hasKeyWordFilterInputController.text = StringConvert.writeNullToEmpty(_searchEmailFilter.hasKeyword.firstOrNull);
notKeyWordFilterInputController.text = StringConvert.writeNullToEmpty(_searchEmailFilter.notKeyword.firstOrNull);
dateFilterInputController.text = StringConvert.writeNullToEmpty(_searchEmailFilter.emailReceiveTimeType.getTitle(context));
mailBoxFilterInputController.text = StringConvert.writeNullToEmpty(_searchEmailFilter.mailbox?.name?.name);
dateFilterSelectedFormAdvancedSearch.value = _searchEmailFilter.emailReceiveTimeType;
hasAttachment.value = _searchEmailFilter.hasAttachment;
void initSearchFilterField(BuildContext context) {
_searchController.updateFilterEmail(
mailbox: _mailboxDashBoardController.selectedMailbox.value);
subjectFilterInputController.text =
StringConvert.writeNullToEmpty(searchEmailFilter.subject);
hasKeyWordFilterInputController.text = StringConvert.writeNullToEmpty(
searchEmailFilter.hasKeyword.firstOrNull);
notKeyWordFilterInputController.text = StringConvert.writeNullToEmpty(
searchEmailFilter.notKeyword.firstOrNull);
dateFilterInputController.text = StringConvert.writeNullToEmpty(
searchEmailFilter.emailReceiveTimeType.getTitle(context));
mailBoxFilterInputController.text =
StringConvert.writeNullToEmpty(searchEmailFilter.mailbox?.name?.name);
dateFilterSelectedFormAdvancedSearch.value =
searchEmailFilter.emailReceiveTimeType;
hasAttachment.value = searchEmailFilter.hasAttachment;
}
@override
void onDone() {
}
void onDone() {}
@override
void onError(error) {
}
void onError(error) {}
@override
void onClose() {
fromFilterInputController.dispose();
subjectFilterInputController.dispose();
toFilterInputController.dispose();
hasKeyWordFilterInputController.dispose();
notKeyWordFilterInputController.dispose();
mailBoxFilterInputController.dispose();
@@ -241,7 +241,6 @@ class MailboxDashBoardController extends ReloadableController {
void setNewFirstSelectedMailbox(PresentationMailbox? newPresentationMailbox) {
selectedMailbox.firstRebuild = true;
selectedMailbox.value = newPresentationMailbox;
}
void setSelectedEmail(PresentationEmail? newPresentationEmail) {
@@ -92,7 +92,7 @@ class SearchController extends BaseController {
return;
case QuickSearchFilter.fromMe:
quickSearchFilterSelected
? searchEmailFilter.value.from.remove(userProfile.email)
? searchEmailFilter.value.from.removeWhere((e) => e == userProfile.email)
: searchEmailFilter.value.from.add(userProfile.email);
updateFilterEmail(from: searchEmailFilter.value.from);
return;
@@ -98,7 +98,7 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView {
tabletLarge: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(child: MailboxView(), width: responsiveUtils.defaultSizeDrawer),
SizedBox(child: MailboxView(), width: responsiveUtils.defaultSizeMenu),
Expanded(child: _wrapContainerForThreadAndEmail(context))
],
),
@@ -423,8 +423,8 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView {
target: Alignment.bottomLeft,
widthFactor: 1,
backup: Aligned(
follower: Alignment.bottomLeft,
target: Alignment.topLeft,
follower: Alignment.topLeft,
target: Alignment.bottomLeft,
widthFactor: 1,
),
),
@@ -8,6 +8,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/sear
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form_bottom_view.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/drop_down_button_filter_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/text_field_auto_complete_email_adress.dart';
class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
with PopupContextMenuActionMixin {
@@ -24,15 +25,17 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
color: Colors.white,
child: Column(
children: [
_buildFilterField(
textEditingController: controller.fromFilterInputController,
_buildSuggestionFilterField(
listTagSelected: controller.listTagFromSelected,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.form,
listTagInitial: controller.searchEmailFilter.from.toList()
),
_buildFilterField(
textEditingController: controller.toFilterInputController,
_buildSuggestionFilterField(
listTagSelected: controller.listTagToSelected,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.to,
listTagInitial: controller.searchEmailFilter.to.toList()
),
_buildFilterField(
textEditingController: controller.subjectFilterInputController,
@@ -56,8 +59,7 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
isSelectFormList: true,
onTap: () async {
await controller.selectedMailBox();
}
),
}),
_buildFilterField(
textEditingController: controller.dateFilterInputController,
context: context,
@@ -177,6 +179,57 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
}
}
Widget _buildSuggestionFilterField({
required AdvancedSearchFilterField advancedSearchFilterField,
required BuildContext context,
required List<String> listTagSelected,
required List<String> listTagInitial,
}) {
final child = [
SizedBox(
width: _responsiveUtils.isMobile(context) ? null : 112,
child: Text(
advancedSearchFilterField.getTitle(context),
style: const TextStyle(
fontSize: 14,
color: AppColor.colorContentEmail,
),
),
),
const Padding(padding: EdgeInsets.all(4)),
_responsiveUtils.isMobile(context)
? TextFieldAutoCompleteEmailAddress(
optionsBuilder: (word) async {
return controller.getAutoCompleteSuggestion(word: word);
},
advancedSearchFilterField: advancedSearchFilterField,
initialTags: listTagInitial,
listTagSelected: listTagSelected,
)
: Expanded(
child: TextFieldAutoCompleteEmailAddress(
optionsBuilder: (word) async {
return controller.getAutoCompleteSuggestion(word: word);
},
advancedSearchFilterField: advancedSearchFilterField,
initialTags: listTagInitial,
listTagSelected: listTagSelected,
),
)
];
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: _responsiveUtils.isMobile(context)
? Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: child,
)
: SizedBox(height: 44, child: Row(children: child)),
);
}
Widget _buildTextField({
required BuildContext context,
required AdvancedSearchFilterField advancedSearchFilterField,
@@ -195,6 +248,15 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
right: 8,
left: 12,
),
enabledBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
borderSide: BorderSide(
width: 0.5,
color: AppColor.colorInputBorderCreateMailbox,
),
),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
@@ -33,8 +33,8 @@ class AdvancedSearchFilterFormBottomView
controller.cleanSearchFilter(context);
popBack();
},
colorButton: AppColor.primaryColor.withOpacity(0.14),
colorText: AppColor.primaryColor,
colorButton: Colors.transparent,
colorText: AppColor.colorMessageConfirmDialog,
text: AppLocalizations.of(context).clearFilter,
),
const SizedBox(width: 12),
@@ -80,8 +80,8 @@ class AdvancedSearchFilterFormBottomView
onAction.call();
},
child: Container(
constraints: const BoxConstraints(maxWidth: 154),
padding: const EdgeInsets.symmetric(vertical: 16),
height: 44,
constraints: const BoxConstraints(maxWidth: 144),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10), color: colorButton),
@@ -11,7 +11,7 @@ class AdvancedSearchFilterOverlay extends StatelessWidget {
padding: const EdgeInsets.only(top: 8),
child: Container(
width: 660,
height: 558,
height: MediaQuery.of(context).size.height,
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: Colors.white,
@@ -22,8 +22,13 @@ class AdvancedSearchFilterOverlay extends StatelessWidget {
spreadRadius: 1, blurRadius: 1, offset: Offset(0, 0.5)),
]
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 12),
child: AdvancedSearchInputForm(),
),
),
),
);
}
}
@@ -0,0 +1,286 @@
import 'package:collection/collection.dart';
import 'package:core/core.dart';
import 'package:flutter/foundation.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:textfield_tags/textfield_tags.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/advanced_search_filter.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class TextFieldAutoCompleteEmailAddress extends StatefulWidget {
const TextFieldAutoCompleteEmailAddress({
Key? key,
required this.advancedSearchFilterField,
required this.listTagSelected,
required this.initialTags,
required this.optionsBuilder,
}) : super(key: key);
final AdvancedSearchFilterField advancedSearchFilterField;
final List<String> listTagSelected;
final List<String> initialTags;
final Future<List<EmailAddress>> Function(String) optionsBuilder;
@override
State<TextFieldAutoCompleteEmailAddress> createState() =>
_TextFieldAutoCompleteEmailAddressState();
}
class _TextFieldAutoCompleteEmailAddressState
extends State<TextFieldAutoCompleteEmailAddress> {
final double _distanceToField = 380;
final ImagePaths _imagePaths = Get.find<ImagePaths>();
late TextfieldTagsController _controller;
@override
void initState() {
_controller = TextfieldTagsController();
_controller.addListener(() {
if(_controller.hasTags){
widget.listTagSelected.addAll(_controller.getTags!);
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Autocomplete<EmailAddress>(
optionsViewBuilder: (context, onSelected, options) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 4.0),
child: Align(
alignment: Alignment.topLeft,
child: Material(
elevation: 4.0,
child: ConstrainedBox(
constraints:
const BoxConstraints(maxHeight: 200, maxWidth: 200),
child: ListView.builder(
shrinkWrap: true,
itemCount: options.length,
itemBuilder: (BuildContext context, int index) {
final dynamic option = options.elementAt(index);
return GestureDetector(
onTap: () {
onSelected(option);
},
child: _buildSuggestionItem(context, option),
);
},
),
),
),
),
);
},
optionsBuilder: (TextEditingValue textEditingValue) {
return widget.optionsBuilder.call(textEditingValue.text);
},
onSelected: (EmailAddress selectedTag) {
_controller.addTag = selectedTag.asString();
},
fieldViewBuilder: (context, ttec, tfn, onFieldSubmitted) {
return TextFieldTags(
initialTags: widget.initialTags,
textEditingController: ttec,
focusNode: tfn,
textfieldTagsController: _controller,
textSeparators: const [' ', ','],
letterCase: LetterCase.normal,
validator: (String tag) {
if (_controller.getTags!.contains(tag)) {
return AppLocalizations.of(context).messageDuplicateTagFilterMail;
}
return null;
},
inputfieldBuilder: (context, tec, fn, error, onChanged, onSubmitted) {
return ((context, sc, tags, onTagDelete) {
return TextField(
controller: tec,
focusNode: fn,
decoration: InputDecoration(
filled: true,
fillColor: AppColor.loginTextFieldBackgroundColor,
contentPadding: const EdgeInsets.only(
right: 8,
left: 12,
),
enabledBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
borderSide: BorderSide(
width: 0.5,
color: AppColor.colorInputBorderCreateMailbox,
),
),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
hintText:
widget.advancedSearchFilterField.getHintText(context),
hintStyle: const TextStyle(
fontSize: 14,
color: AppColor.colorHintSearchBar,
),
prefixIconConstraints:
BoxConstraints(maxWidth: _distanceToField * 0.74),
prefixIcon: tags.isNotEmpty
? SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 10),
controller: sc,
scrollDirection: Axis.horizontal,
child: Row(
children: tags.map((String tag) {
return _buildTagItem(context, tag, onTagDelete);
}).toList()),
)
: null,
),
onChanged: onChanged,
onSubmitted: (tag){
onSubmitted?.call(tag);
},
);
});
},
);
},
);
}
Widget _buildSuggestionItem(
BuildContext context,
EmailAddress emailAddress,
) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 40,
height: 40,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColor.avatarColor,
border: Border.all(
color: AppColor.colorShadowBgContentEmail, width: 1.0)),
child: Text(
emailAddress.asString().isNotEmpty
? emailAddress.asString()[0].toUpperCase()
: '',
style: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w600))),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(emailAddress.asString(),
maxLines: 1,
overflow: kIsWeb ? null : TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.normal)),
if (emailAddress.displayName.isNotEmpty &&
emailAddress.emailAddress.isNotEmpty)
Text(emailAddress.emailAddress,
maxLines: 1,
overflow: kIsWeb ? null : TextOverflow.ellipsis,
style: const TextStyle(
color: AppColor.colorHintSearchBar,
fontSize: 13,
fontWeight: FontWeight.normal))
],
)
],
);
}
Widget _buildTagItem(
BuildContext context,
String tag,
Function(String tag) onTagDelete,
) {
return Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(10),
),
color: AppColor.colorBackgroundTagFilter.withOpacity(0.08),
),
margin: const EdgeInsets.only(right: 8),
padding: const EdgeInsets.fromLTRB(8, 4, 4, 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 24,
height: 24,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: AppColor.mapGradientColor[_generateIndex(tag)],
),
border: Border.all(
color: AppColor.colorShadowBgContentEmail, width: 0.21)),
child: Text(tag[0].toUpperCase(),
style: const TextStyle(
color: Colors.white,
fontSize: 8.57,
fontWeight: FontWeight.w600))),
const SizedBox(width: 4),
Text(tag,
maxLines: 1,
overflow: kIsWeb ? null : TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.black,
fontSize: 17,
fontWeight: FontWeight.normal)),
const SizedBox(width: 4),
GestureDetector(
onTap: (){
onTagDelete.call(tag);
widget.listTagSelected.remove(tag);
},
child: SvgPicture.asset(
_imagePaths.icClose,
width: 28,
height: 28,
fit: BoxFit.fill,
),
)
],
),
);
}
int _generateIndex(String tag) {
if (tag.isNotEmpty) {
final codeUnits = tag.codeUnits;
if (codeUnits.isNotEmpty) {
final sumCodeUnits = codeUnits.sum;
final index = sumCodeUnits % AppColor.mapGradientColor.length;
return index;
}
}
return 0;
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
}
@@ -289,8 +289,8 @@ class ThreadController extends BaseController {
if (viewState is Either) {
viewState.map((success) {
if (success is SearchEmailNewQuery){
_searchEmail();
mailboxDashBoardController.clearState();
_searchEmail();
} else if (success is MarkAsEmailReadSuccess
|| success is MoveToMailboxSuccess
|| success is MarkAsStarEmailSuccess
@@ -190,8 +190,8 @@ class ThreadView extends GetWidget<ThreadController> with AppLoaderMixin,
target: Alignment.bottomLeft,
widthFactor: 1,
backup: Aligned(
follower: Alignment.bottomLeft,
target: Alignment.topLeft,
follower: Alignment.topLeft,
target: Alignment.bottomLeft,
widthFactor: 1,
),
),
+1 -1
View File
@@ -1,5 +1,5 @@
{
"@@last_modified": "2022-06-24T14:16:53.170960",
"@@last_modified": "2022-06-29T12:21:47.830137",
"initializing_data": "Initializing data...",
"@initializing_data": {
"type": "text",
@@ -1545,4 +1545,11 @@ class AppLocalizations {
name: 'selectMailbox',
);
}
String get messageDuplicateTagFilterMail {
return Intl.message(
'you already entered that',
name: 'selectMailbox',
);
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ import 'package:tmail_ui_user/features/login/presentation/login_view.dart'
if (dart.library.html) 'package:tmail_ui_user/features/login/presentation/login_view_web.dart' deferred as login;
import 'package:tmail_ui_user/features/mailbox_creator/presentation/mailbox_creator_bindings.dart';
import 'package:tmail_ui_user/features/mailbox_creator/presentation/mailbox_creator_view.dart' deferred as mailbox_creator;
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_bindings.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_view.dart'
if (dart.library.html) 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart' deferred as mailbox_dashboard;
import 'package:tmail_ui_user/features/manage_account/presentation/manage_account_dashboard_bindings.dart';
+2
View File
@@ -69,6 +69,8 @@ dependencies:
built_collection: 5.1.1
textfield_tags: ^2.0.1
# jmap_dart_client
jmap_dart_client:
git:
@@ -1,4 +1,4 @@
// Mocks generated by Mockito 5.0.17 from annotations
// Mocks generated by Mockito 5.2.0 from annotations
// in tmail_ui_user/test/features/thread/domain/usecases/get_emails_in_mailbox_interactor_test.dart.
// Do not manually edit this file.
@@ -24,6 +24,7 @@ import 'package:tmail_ui_user/features/thread/domain/model/get_email_request.dar
import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'
as _i2;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
@@ -1,4 +1,4 @@
// Mocks generated by Mockito 5.0.17 from annotations
// Mocks generated by Mockito 5.2.0 from annotations
// in tmail_ui_user/test/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor_test.dart.
// Do not manually edit this file.
@@ -24,6 +24,7 @@ import 'package:tmail_ui_user/features/thread/domain/model/get_email_request.dar
import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'
as _i2;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references