TF-1844 Highlight autocomplete suggestion item when hovering in advanced search

(cherry picked from commit 80b7da0ea4195b4e777aad333db56779ccb20346)
This commit is contained in:
dab246
2023-08-23 18:38:23 +07:00
committed by Dat Vu
parent 67eab3e403
commit 9378805f56
10 changed files with 298 additions and 179 deletions
@@ -15,6 +15,7 @@ import 'package:super_tag_editor/widgets/rich_text_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/prefix_email_address_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/styles/composer_style.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/suggestion_email_address.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/avatar_suggestion_item_widget.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/utils/app_constants.dart';
@@ -361,7 +362,7 @@ class EmailAddressInputBuilder {
borderRadius: BorderRadius.all(Radius.circular(_suggestionBoxRadius))),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 8.0),
leading: _buildAvatarSuggestionItem(emailAddress),
leading: AvatarSuggestionItemWidget(emailAddress: emailAddress),
title: _buildTitleSuggestionItem(emailAddress, suggestionValid),
subtitle: _buildSubtitleSuggestionItem(emailAddress, suggestionValid),
trailing: SvgPicture.asset(_imagePaths.icFilterSelected,
@@ -389,7 +390,7 @@ class EmailAddressInputBuilder {
type: MaterialType.transparency,
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
leading: _buildAvatarSuggestionItem(emailAddress),
leading: AvatarSuggestionItemWidget(emailAddress: emailAddress),
title: _buildTitleSuggestionItem(emailAddress, suggestionValid),
subtitle: _buildSubtitleSuggestionItem(emailAddress, suggestionValid),
onTap: () {
@@ -403,32 +404,6 @@ class EmailAddressInputBuilder {
);
}
Widget _buildAvatarSuggestionItem(EmailAddress emailAddress) {
return 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
)
)
);
}
Widget _buildTitleSuggestionItem(EmailAddress emailAddress, String? suggestionValid) {
return RichTextWidget(
textOrigin: emailAddress.asString(),
@@ -0,0 +1,20 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:flutter/material.dart';
class AutocompleteSuggestionItemStyle {
static const double space = 10;
static const EdgeInsetsGeometry padding = EdgeInsets.symmetric(vertical: 10, horizontal: 12);
static const TextStyle displayNameTextStyle = TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.normal
);
static const TextStyle emailAddressNameTextStyle = TextStyle(
color: AppColor.colorHintSearchBar,
fontSize: 13,
fontWeight: FontWeight.normal
);
}
@@ -0,0 +1,19 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:flutter/material.dart';
class AutocompleteTagItemStyle {
static const double radius = 10;
static const double space = 4;
static const double deleteIconSize = 24;
static Color get backgroundColor => AppColor.colorBackgroundTagFilter.withOpacity(0.08);
static const EdgeInsetsGeometry margin = EdgeInsetsDirectional.only(end: 8);
static const EdgeInsetsGeometry padding = EdgeInsetsDirectional.only(start: 8, end: 4, top: 4, bottom: 4);
static const TextStyle labelTextStyle = TextStyle(
color: Colors.black,
fontSize: 17,
fontWeight: FontWeight.normal
);
}
@@ -0,0 +1,17 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:flutter/material.dart';
class AvatarSuggestionItemStyle {
static const double iconSize = 40;
static const double iconBorderSize = 1.0;
static const Color iconColor = AppColor.avatarColor;
static const Color iconBorderColor = AppColor.colorShadowBgContentEmail;
static const TextStyle labelTextStyle = TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w600
);
}
@@ -0,0 +1,15 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:flutter/material.dart';
class AvatarTagItemStyle {
static const double iconSize = 24;
static const double iconBorderSize = 0.21;
static const Color iconBorderColor = AppColor.colorShadowBgContentEmail;
static const TextStyle labelTextStyle = TextStyle(
color: Colors.white,
fontSize: 8.57,
fontWeight: FontWeight.w600
);
}
@@ -0,0 +1,64 @@
import 'package:core/presentation/utils/style_utils.dart';
import 'package:flutter/material.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/mailbox_dashboard/presentation/styles/autocomplete_suggestion_item_style.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/avatar_suggestion_item_widget.dart';
typedef OnSelectSuggestionItemCallback = Function(String emailAddress);
class AutocompleteSuggestionItemWidget extends StatelessWidget {
final EmailAddress emailAddress;
final OnSelectSuggestionItemCallback onSelectCallback;
const AutocompleteSuggestionItemWidget({
super.key,
required this.emailAddress,
required this.onSelectCallback,
});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: () => onSelectCallback(emailAddress.emailAddress),
child: Container(
padding: AutocompleteSuggestionItemStyle.padding,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AvatarSuggestionItemWidget(emailAddress: emailAddress),
const SizedBox(width: AutocompleteSuggestionItemStyle.space),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (emailAddress.displayName.isNotEmpty)
Text(
emailAddress.displayName,
maxLines: 1,
softWrap: CommonTextStyle.defaultSoftWrap,
overflow: CommonTextStyle.defaultTextOverFlow,
style: AutocompleteSuggestionItemStyle.displayNameTextStyle
),
if (emailAddress.emailAddress.isNotEmpty)
Text(
emailAddress.emailAddress,
maxLines: 1,
softWrap: CommonTextStyle.defaultSoftWrap,
overflow: CommonTextStyle.defaultTextOverFlow,
style: AutocompleteSuggestionItemStyle.emailAddressNameTextStyle
)
]
)
),
]
),
),
),
);
}
}
@@ -0,0 +1,59 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/style_utils.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/autocomplete_tag_item_style.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/avatar_tag_item_widget.dart';
typedef OnDeleteTagItemCallback = Function(String tagName);
class AutocompleteTagItemWidget extends StatelessWidget {
final String tagName;
final OnDeleteTagItemCallback onDeleteCallback;
const AutocompleteTagItemWidget({
super.key,
required this.tagName,
required this.onDeleteCallback,
});
@override
Widget build(BuildContext context) {
final imagePaths = Get.find<ImagePaths>();
return Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(AutocompleteTagItemStyle.radius),
),
color: AutocompleteTagItemStyle.backgroundColor,
),
margin: AutocompleteTagItemStyle.margin,
padding: AutocompleteTagItemStyle.padding,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
AvatarTagItemWidget(tagName: tagName),
const SizedBox(width: AutocompleteTagItemStyle.space),
Text(
tagName,
maxLines: 1,
overflow: CommonTextStyle.defaultTextOverFlow,
softWrap: CommonTextStyle.defaultSoftWrap,
style: AutocompleteTagItemStyle.labelTextStyle
),
const SizedBox(width: AutocompleteTagItemStyle.space),
TMailButtonWidget.fromIcon(
icon: imagePaths.icClose,
iconSize: AutocompleteTagItemStyle.deleteIconSize,
backgroundColor: Colors.transparent,
padding: EdgeInsets.zero,
onTapActionCallback: () => onDeleteCallback(tagName),
)
],
),
);
}
}
@@ -0,0 +1,34 @@
import 'package:flutter/material.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/mailbox_dashboard/presentation/styles/avatar_suggestion_item_style.dart';
class AvatarSuggestionItemWidget extends StatelessWidget {
final EmailAddress emailAddress;
const AvatarSuggestionItemWidget({super.key, required this.emailAddress});
@override
Widget build(BuildContext context) {
return Container(
width: AvatarSuggestionItemStyle.iconSize,
height: AvatarSuggestionItemStyle.iconSize,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AvatarSuggestionItemStyle.iconColor,
border: Border.all(
color: AvatarSuggestionItemStyle.iconBorderColor,
width: AvatarSuggestionItemStyle.iconBorderSize
)
),
child: Text(
emailAddress.asString().isNotEmpty
? emailAddress.asString()[0].toUpperCase()
: '',
style: AvatarSuggestionItemStyle.labelTextStyle
)
);
}
}
@@ -0,0 +1,47 @@
import 'package:collection/collection.dart';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/avatar_tag_item_style.dart';
class AvatarTagItemWidget extends StatelessWidget {
final String tagName;
const AvatarTagItemWidget({super.key, required this.tagName});
@override
Widget build(BuildContext context) {
return Container(
width: AvatarTagItemStyle.iconSize,
height: AvatarTagItemStyle.iconSize,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: AppColor.mapGradientColor[_generateIndex(tagName)],
),
border: Border.all(
color: AvatarTagItemStyle.iconBorderColor,
width: AvatarTagItemStyle.iconBorderSize
)
),
child: Text(
tagName.isNotEmpty ? tagName[0].toUpperCase() : '',
style: AvatarTagItemStyle.labelTextStyle
)
);
}
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;
}
}
@@ -1,21 +1,17 @@
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/utils/style_utils.dart';
import 'package:core/presentation/views/text/text_field_builder.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.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/features/mailbox_dashboard/presentation/controller/custom_tf_tag_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/autocomplete_suggestion_item_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/autocomplete_tag_item_widget.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class TextFieldAutoCompleteEmailAddress extends StatefulWidget {
@@ -47,7 +43,6 @@ class TextFieldAutoCompleteEmailAddress extends StatefulWidget {
class _TextFieldAutoCompleteEmailAddressState
extends State<TextFieldAutoCompleteEmailAddress> {
final double _distanceToField = 380;
final ImagePaths _imagePaths = Get.find<ImagePaths>();
final _responsiveUtils = Get.find<ResponsiveUtils>();
late CustomController _controller;
@@ -94,9 +89,9 @@ class _TextFieldAutoCompleteEmailAddressState
itemCount: listEmailAddress.length,
itemBuilder: (BuildContext context, int index) {
final emailAddress = listEmailAddress.elementAt(index);
return InkWell(
onTap: () {_controller.onSubmitted(emailAddress.emailAddress);},
child: _buildSuggestionItem(context, emailAddress),
return AutocompleteSuggestionItemWidget(
emailAddress: emailAddress,
onSelectCallback: _controller.onSubmitted,
);
},
),
@@ -133,10 +128,8 @@ class _TextFieldAutoCompleteEmailAddressState
return RawKeyboardListener(
focusNode: widget.currentFocusNode ?? FocusNode(),
onKey: (event) {
log('_TextFieldAutoCompleteEmailAddressState::inputfieldBuilder(): Event runtimeType is ${event.runtimeType}');
if (event is RawKeyDownEvent &&
event.logicalKey == LogicalKeyboardKey.tab) {
log('_TextFieldAutoCompleteEmailAddressState::inputfieldBuilder(): PRESS TAB');
widget.nextFocusNode?.requestFocus();
}
},
@@ -176,16 +169,21 @@ class _TextFieldAutoCompleteEmailAddressState
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,
prefixIcon: tags.isNotEmpty
? SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 10),
controller: sc,
scrollDirection: Axis.horizontal,
child: Row(
children: tags
.map((tagName) => AutocompleteTagItemWidget(
tagName: tagName,
onDeleteCallback: onTagDelete
))
.toList()
),
)
: null,
),
onTextChange: (value) {
if (value.trim().isNotEmpty) {
@@ -209,135 +207,6 @@ class _TextFieldAutoCompleteEmailAddressState
);
}
Widget _buildSuggestionItem(
BuildContext context,
EmailAddress emailAddress,
) {
return Container(
color: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
child: Row(mainAxisAlignment: MainAxisAlignment.center, 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))),
Expanded(child: Padding(
padding: const EdgeInsets.only(left: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
emailAddress.asString(),
maxLines: 1,
softWrap: CommonTextStyle.defaultSoftWrap,
overflow: CommonTextStyle.defaultTextOverFlow,
style: const TextStyle(
color: Colors.black,
fontSize: 16, fontWeight:
FontWeight.normal)),
if (emailAddress.displayName.isNotEmpty &&
emailAddress.emailAddress.isNotEmpty)
Text(
emailAddress.emailAddress,
maxLines: 1,
softWrap: CommonTextStyle.defaultSoftWrap,
overflow: CommonTextStyle.defaultTextOverFlow,
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),
InkWell(
onTap: () => onTagDelete.call(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;
}
double _getHeightSuggestionBox(int countItem, double heightItem) {
final maxHeightList = countItem * heightItem;