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
@@ -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,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.form,
_buildSuggestionFilterField(
listTagSelected: controller.listTagFromSelected,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.form,
listTagInitial: controller.searchEmailFilter.from.toList()
),
_buildFilterField(
textEditingController: controller.toFilterInputController,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.to,
_buildSuggestionFilterField(
listTagSelected: controller.listTagToSelected,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.to,
listTagInitial: controller.searchEmailFilter.to.toList()
),
_buildFilterField(
textEditingController: controller.subjectFilterInputController,
@@ -50,14 +53,13 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
advancedSearchFilterField: AdvancedSearchFilterField.notKeyword,
),
_buildFilterField(
textEditingController: controller.mailBoxFilterInputController,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.mailBox,
isSelectFormList: true,
onTap: () async {
await controller.selectedMailBox();
}
),
textEditingController: controller.mailBoxFilterInputController,
context: context,
advancedSearchFilterField: AdvancedSearchFilterField.mailBox,
isSelectFormList: true,
onTap: () async {
await controller.selectedMailBox();
}),
_buildFilterField(
textEditingController: controller.dateFilterInputController,
context: context,
@@ -70,7 +72,7 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
);
},
),
const AdvancedSearchFilterFormBottomView()
const AdvancedSearchFilterFormBottomView()
],
),
);
@@ -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,7 +22,12 @@ class AdvancedSearchFilterOverlay extends StatelessWidget {
spreadRadius: 1, blurRadius: 1, offset: Offset(0, 0.5)),
]
),
child: AdvancedSearchInputForm(),
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();
}
}