TF-3759 Always show dot icon when open popup menu of more action in email item widget

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-06-16 10:48:22 +07:00
committed by Dat H. Pham
parent 9c3f6a9684
commit d39d09663d
7 changed files with 596 additions and 312 deletions
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart';
@@ -10,7 +10,7 @@ import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
extension HandleEmailMoreActionExtension on SearchEmailController {
void handleEmailMoreAction(
Future<void> handleEmailMoreAction(
BuildContext context,
PresentationEmail presentationEmail,
RelativeRect? position,
@@ -24,7 +24,7 @@ extension HandleEmailMoreActionExtension on SearchEmailController {
if (!isDrafts) EmailActionType.editAsNewEmail,
];
if (listEmailActions.isEmpty) return;
if (listEmailActions.isEmpty) return Future.value();
if (position == null) {
final contextMenuActions = listEmailActions
@@ -35,7 +35,7 @@ extension HandleEmailMoreActionExtension on SearchEmailController {
))
.toList();
openBottomSheetContextMenuAction(
return openBottomSheetContextMenuAction(
context: context,
itemActions: contextMenuActions,
onContextMenuActionClick: (menuAction) {
@@ -70,7 +70,7 @@ extension HandleEmailMoreActionExtension on SearchEmailController {
);
}).toList();
openPopupMenuAction(context, position, popupMenuItems);
return openPopupMenuAction(context, position, popupMenuItems);
}
}
}
@@ -1,48 +0,0 @@
import 'package:flutter/material.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart';
import 'package:tmail_ui_user/features/email/presentation/model/popup_menu_item_email_action.dart';
import 'package:tmail_ui_user/features/search/email/presentation/search_email_controller.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
extension HandleEmailActionExtension on SearchEmailController {
void handleEmailMoreActionClick(
BuildContext context,
RelativeRect position,
PresentationEmail presentationEmail,
) {
final mailboxContain = presentationEmail.mailboxContain;
final isDrafts = mailboxContain?.isDrafts ?? false;
final isSpam = mailboxContain?.isSpam ?? false;
final popupMenuItems = [
isSpam ? EmailActionType.unSpam : EmailActionType.moveToSpam,
if (isDrafts == false) EmailActionType.editAsNewEmail,
].map((actionType) {
return PopupMenuItem(
padding: EdgeInsets.zero,
child: PopupMenuItemActionWidget(
menuAction: PopupMenuItemEmailAction(
actionType,
AppLocalizations.of(context),
imagePaths,
),
menuActionClick: (menuAction) {
popBack();
pressEmailAction(
context,
menuAction.action,
presentationEmail,
mailboxContain: mailboxContain,
);
},
),
);
}).toList();
openPopupMenuAction(context, position, popupMenuItems);
}
}
@@ -22,7 +22,7 @@ import 'package:tmail_ui_user/features/thread/presentation/styles/item_email_til
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
typedef OnPressEmailActionClick = void Function(EmailActionType, PresentationEmail);
typedef OnMoreActionClick = void Function(PresentationEmail, RelativeRect?);
typedef OnMoreActionClick = Future<void> Function(PresentationEmail, RelativeRect?);
mixin BaseEmailItemTile {
@@ -609,7 +609,7 @@ class ThreadView extends GetWidget<ThreadController>
);
}
void _handleEmailContextMenuAction(
Future<void> _handleEmailContextMenuAction(
BuildContext context,
PresentationEmail presentationEmail,
RelativeRect? position
@@ -639,7 +639,7 @@ class ThreadView extends GetWidget<ThreadController>
))
.toList();
controller.openBottomSheetContextMenuAction(
return controller.openBottomSheetContextMenuAction(
context: context,
itemActions: contextMenuActions,
onContextMenuActionClick: (menuAction) {
@@ -672,7 +672,7 @@ class ThreadView extends GetWidget<ThreadController>
);
}).toList();
controller.openPopupMenuAction(context, position, popupMenuItems);
return controller.openPopupMenuAction(context, position, popupMenuItems);
}
}
@@ -0,0 +1,174 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/thread/presentation/mixin/base_email_item_tile.dart';
import 'package:tmail_ui_user/features/thread/presentation/styles/item_email_tile_styles.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class DesktopListEmailActionHoverWidget extends StatefulWidget {
final PresentationEmail presentationEmail;
final bool isHovered;
final bool canDeletePermanently;
final bool isSearchEmailRunning;
final PresentationMailbox? mailboxContain;
final OnPressEmailActionClick? emailActionClick;
final OnMoreActionClick? onMoreActionClick;
const DesktopListEmailActionHoverWidget({
super.key,
required this.presentationEmail,
required this.isHovered,
required this.canDeletePermanently,
required this.isSearchEmailRunning,
required this.mailboxContain,
required this.emailActionClick,
required this.onMoreActionClick,
});
@override
State<DesktopListEmailActionHoverWidget> createState() =>
_DesktopListEmailActionHoverWidgetState();
}
class _DesktopListEmailActionHoverWidgetState
extends State<DesktopListEmailActionHoverWidget> with BaseEmailItemTile {
final _imagePaths = Get.find<ImagePaths>();
final _responsiveUtils = Get.find<ResponsiveUtils>();
bool _popupMenuVisible = false;
@override
Widget build(BuildContext context) {
final listActionWidget = [
if (widget.isHovered) ...[
TMailButtonWidget.fromIcon(
icon: _imagePaths.icOpenInNewTab,
iconColor: ItemEmailTileStyles.actionIconColor,
iconSize: _getIconSize(),
padding: _getPaddingIcon(context),
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).openInNewTab,
onTapActionCallback: () => widget.emailActionClick?.call(
EmailActionType.openInNewTab,
widget.presentationEmail,
),
),
if (!widget.presentationEmail.isDraft)
TMailButtonWidget.fromIcon(
icon: widget.presentationEmail.hasRead
? _imagePaths.icUnread
: _imagePaths.icRead,
iconColor: ItemEmailTileStyles.actionIconColor,
iconSize: _getIconSize(),
padding: _getPaddingIcon(context),
backgroundColor: Colors.transparent,
tooltipMessage: widget.presentationEmail.hasRead
? AppLocalizations.of(context).mark_as_unread
: AppLocalizations.of(context).mark_as_read,
onTapActionCallback: () => widget.emailActionClick?.call(
widget.presentationEmail.hasRead
? EmailActionType.markAsUnread
: EmailActionType.markAsRead,
widget.presentationEmail,
),
),
if (widget.mailboxContain != null &&
widget.mailboxContain?.isDrafts == false) ...[
TMailButtonWidget.fromIcon(
icon: _imagePaths.icMove,
iconColor: ItemEmailTileStyles.actionIconColor,
iconSize: _getIconSize(),
padding: _getPaddingIcon(context),
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).move,
onTapActionCallback: () => widget.emailActionClick?.call(
EmailActionType.moveToMailbox,
widget.presentationEmail,
),
),
],
TMailButtonWidget.fromIcon(
icon: _imagePaths.icDeleteComposer,
iconColor: ItemEmailTileStyles.actionIconColor,
iconSize: 16,
padding: _getPaddingIcon(context),
backgroundColor: Colors.transparent,
tooltipMessage: widget.canDeletePermanently
? AppLocalizations.of(context).delete_permanently
: AppLocalizations.of(context).move_to_trash,
onTapActionCallback: () => widget.emailActionClick?.call(
widget.canDeletePermanently
? EmailActionType.deletePermanently
: EmailActionType.moveToTrash,
widget.presentationEmail,
),
),
],
if (_shouldShowPopupMenu) ...[
TMailButtonWidget.fromIcon(
icon: _imagePaths.icMoreVertical,
iconColor: ItemEmailTileStyles.actionIconColor,
iconSize: _getIconSize(),
padding: _getPaddingIcon(context),
backgroundColor: _popupMenuVisible
? Theme.of(context).colorScheme.outline.withOpacity(0.08)
: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).more,
onTapActionAtPositionCallback: (position) {
if (_responsiveUtils.isScreenWithShortestSide(context)) {
widget.onMoreActionClick?.call(widget.presentationEmail, null);
} else {
_onPopupMenuVisibleChange(true);
widget.onMoreActionClick
?.call(widget.presentationEmail, position)
.whenComplete(() => _onPopupMenuVisibleChange(false));
}
},
),
const SizedBox(width: 16),
] else ...[
buildMailboxContain(
context,
widget.isSearchEmailRunning,
widget.presentationEmail,
),
if (widget.presentationEmail.hasAttachment == true)
Padding(
padding: const EdgeInsetsDirectional.only(start: 8),
child: buildIconAttachment(),
),
Padding(
padding: const EdgeInsetsDirectional.only(end: 20, start: 8),
child: buildDateTime(context, widget.presentationEmail),
),
]
];
if (listActionWidget.isEmpty) return const SizedBox.shrink();
return Row(children: listActionWidget);
}
double _getIconSize() => 18;
EdgeInsets _getPaddingIcon(BuildContext context) {
return const EdgeInsets.all(5);
}
bool get _shouldShowPopupMenu => widget.isHovered || _popupMenuVisible;
void _onPopupMenuVisibleChange(bool visible) {
if (_popupMenuVisible != visible && mounted) {
setState(() {
_popupMenuVisible = visible;
});
}
}
}
@@ -1,6 +1,5 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/extensions/tap_down_details_extension.dart';
import 'package:core/presentation/views/button/icon_button_web.dart';
import 'package:core/presentation/views/responsive/responsive_widget.dart';
import 'package:flutter/material.dart';
@@ -12,7 +11,8 @@ import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:model/mailbox/select_mode.dart';
import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart';
import 'package:tmail_ui_user/features/thread/presentation/mixin/base_email_item_tile.dart';
import 'package:tmail_ui_user/features/thread/presentation/styles/item_email_tile_styles.dart';
import 'package:tmail_ui_user/features/thread/presentation/widgets/desktop_list_email_action_hover_widget.dart';
import 'package:tmail_ui_user/features/thread/presentation/widgets/web_tablet_body_email_item_widget.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class EmailTileBuilder extends StatefulWidget {
@@ -174,142 +174,19 @@ class _EmailTileBuilderState extends State<EmailTileBuilder> with BaseEmailItem
)
),
),
tablet: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: () => widget.emailActionClick?.call(
EmailActionType.preview,
widget.presentationEmail
),
hoverColor: Theme.of(context).colorScheme.outline.withOpacity(0.08),
onHover: (value) => _hoverNotifier.value = value,
child: Container(
padding: widget.padding ?? _getPaddingItem(context),
decoration: _getDecorationItem(),
alignment: Alignment.center,
child: Stack(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () => widget.emailActionClick?.call(
EmailActionType.selection,
widget.presentationEmail
),
child: Padding(
padding: const EdgeInsetsDirectional.only(top: 8, end: 12),
child: ValueListenableBuilder(
valueListenable: _hoverNotifier,
builder: (context, value, child) {
return _buildAvatarIcon(
context: context,
isHovered: value
);
}
)
)
),
),
Expanded(child: Column(children: [
Row(children: [
if (!widget.presentationEmail.hasRead)
Padding(
padding: const EdgeInsetsDirectional.only(end: 5),
child: buildIconUnreadStatus(),
),
Expanded(
child: buildInformationSender(
context,
widget.presentationEmail,
widget.mailboxContain,
widget.isSearchEmailRunning,
widget.searchQuery
)
),
ValueListenableBuilder(
valueListenable: _hoverNotifier,
builder: (context, value, child) {
if (value) {
return const SizedBox(width: 120);
} else {
return _buildDateTimeForMobileTabletScreen(context);
}
}
),
]),
const SizedBox(height: 2),
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.presentationEmail.hasCalendarEvent)
buildCalendarEventIcon(
context: context,
presentationEmail: widget.presentationEmail
),
if (widget.presentationEmail.isMarkAsImportant && widget.isSenderImportantFlagEnabled)
buildMarkAsImportantIcon(context),
Expanded(
child: buildEmailTitle(
context,
widget.presentationEmail,
widget.isSearchEmailRunning,
widget.searchQuery
)
),
buildMailboxContain(
context,
widget.isSearchEmailRunning,
widget.presentationEmail
),
if (widget.presentationEmail.hasStarred)
Padding(
padding: const EdgeInsetsDirectional.only(start: 8),
child: buildIconStar(),
)
],
),
const SizedBox(height: 2),
Row(children: [
Expanded(
child: buildEmailPartialContent(
context,
widget.presentationEmail,
widget.isSearchEmailRunning,
widget.searchQuery
)
),
]),
]))
]
),
ValueListenableBuilder(
valueListenable: _hoverNotifier,
builder: (context, value, child) {
if (value) {
return Positioned(
top: 0,
right: 0,
child: Transform(
transform: Matrix4.translationValues(
0.0,
widget.selectAllMode == SelectMode.INACTIVE ? -5.0 : 0.0,
0.0
),
child: _buildListActionButtonWhenHover(context)
)
);
} else {
return const SizedBox.shrink();
}
}
),
],
),
),
),
tablet: WebTabletBodyEmailItemWidget(
presentationEmail: widget.presentationEmail,
selectAllMode: widget.selectAllMode,
canDeletePermanently: canDeletePermanently,
isSearchEmailRunning: widget.isSearchEmailRunning,
isShowingEmailContent: widget.isShowingEmailContent,
isDrag: widget.isDrag,
isSenderImportantFlagEnabled: widget.isSenderImportantFlagEnabled,
padding: widget.padding,
searchQuery: widget.searchQuery,
mailboxContain: widget.mailboxContain,
emailActionClick: widget.emailActionClick,
onMoreActionClick: widget.onMoreActionClick,
),
desktop: Padding(
padding: const EdgeInsetsDirectional.only(
@@ -440,12 +317,16 @@ class _EmailTileBuilderState extends State<EmailTileBuilder> with BaseEmailItem
const SizedBox(width: 16),
ValueListenableBuilder(
valueListenable: _hoverNotifier,
builder: (context, value, child) {
if (value) {
return _buildListActionButtonWhenHover(context);
} else {
return _buildDateTimeForDesktopScreen(context);
}
builder: (_, value, __) {
return DesktopListEmailActionHoverWidget(
presentationEmail: widget.presentationEmail,
isHovered: value,
canDeletePermanently: canDeletePermanently,
isSearchEmailRunning: widget.isSearchEmailRunning,
mailboxContain: widget.mailboxContain,
emailActionClick: widget.emailActionClick,
onMoreActionClick: widget.onMoreActionClick,
);
}
),
]),
@@ -481,121 +362,10 @@ class _EmailTileBuilderState extends State<EmailTileBuilder> with BaseEmailItem
}
}
double _getIconSize(BuildContext context) {
return responsiveUtils.isDesktop(context) ? 18 : 16;
}
EdgeInsets _getPaddingIcon(BuildContext context) {
return const EdgeInsets.all(5);
}
Widget _buildListActionButtonWhenHover(BuildContext context) {
return Row(children: [
buildSVGIconButton(
icon: imagePaths.icOpenInNewTab,
iconColor: ItemEmailTileStyles.actionIconColor,
iconSize: _getIconSize(context),
padding: _getPaddingIcon(context),
tooltip: AppLocalizations.of(context).openInNewTab,
onTap: () => widget.emailActionClick?.call(
EmailActionType.openInNewTab,
widget.presentationEmail
)
),
if(!widget.presentationEmail.isDraft)
buildSVGIconButton(
icon: widget.presentationEmail.hasRead ? imagePaths.icUnread: imagePaths.icRead,
iconColor: ItemEmailTileStyles.actionIconColor,
iconSize: _getIconSize(context),
padding: _getPaddingIcon(context),
tooltip: widget.presentationEmail.hasRead
? AppLocalizations.of(context).mark_as_unread
: AppLocalizations.of(context).mark_as_read,
onTap: () => widget.emailActionClick?.call(
widget.presentationEmail.hasRead ? EmailActionType.markAsUnread : EmailActionType.markAsRead,
widget.presentationEmail
)
),
if (widget.mailboxContain != null && widget.mailboxContain?.isDrafts == false)
... [
buildSVGIconButton(
icon: imagePaths.icMove,
iconColor: ItemEmailTileStyles.actionIconColor,
iconSize: _getIconSize(context),
padding: _getPaddingIcon(context),
tooltip: AppLocalizations.of(context).move,
onTap: () => widget.emailActionClick?.call(
EmailActionType.moveToMailbox,
widget.presentationEmail
)
),
],
buildSVGIconButton(
icon: imagePaths.icDeleteComposer,
iconColor: ItemEmailTileStyles.actionIconColor,
iconSize: responsiveUtils.isDesktop(context) ? 16 : 14,
padding: _getPaddingIcon(context),
tooltip: canDeletePermanently
? AppLocalizations.of(context).delete_permanently
: AppLocalizations.of(context).move_to_trash,
onTap: () => widget.emailActionClick?.call(
canDeletePermanently ? EmailActionType.deletePermanently : EmailActionType.moveToTrash,
widget.presentationEmail
)
),
buildSVGIconButton(
icon: imagePaths.icMore,
iconColor: ItemEmailTileStyles.actionIconColor,
iconSize: _getIconSize(context),
padding: _getPaddingIcon(context),
tooltip: AppLocalizations.of(context).more,
onTapDown: (tapDetails) {
if (responsiveUtils.isScreenWithShortestSide(context)) {
widget.onMoreActionClick?.call(widget.presentationEmail, null);
} else {
widget.onMoreActionClick?.call(widget.presentationEmail, tapDetails.getPosition(context));
}
}
),
if (responsiveUtils.isDesktop(context)) const SizedBox(width: 16),
]);
}
bool get canDeletePermanently {
return widget.mailboxContain?.isTrash == true || widget.mailboxContain?.isDrafts == true || widget.mailboxContain?.isSpam == true;
}
Widget _buildDateTimeForDesktopScreen(BuildContext context) {
return Row(children: [
buildMailboxContain(
context,
widget.isSearchEmailRunning,
widget.presentationEmail
),
if (widget.presentationEmail.hasAttachment == true)
Padding(
padding: const EdgeInsetsDirectional.only(start: 8),
child: buildIconAttachment()),
Padding(
padding: const EdgeInsetsDirectional.only(end: 20, start: 8),
child: buildDateTime(context, widget.presentationEmail))
]);
}
Widget _buildDateTimeForMobileTabletScreen(BuildContext context) {
return Row(children: [
buildIconAnsweredOrForwarded(width: 16, height: 16, presentationEmail: widget.presentationEmail),
if (widget.presentationEmail.hasAttachment == true)
Padding(
padding: const EdgeInsetsDirectional.only(start: 8),
child: buildIconAttachment()),
Padding(
padding: const EdgeInsetsDirectional.only(end: 4, start: 8),
child: buildDateTime(context, widget.presentationEmail)),
buildIconChevron()
]);
}
Widget _buildSubjectAndContent() {
return LayoutBuilder(builder: (context, constraints) {
return Row(children: [
@@ -0,0 +1,388 @@
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/tmail_button_widget.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:model/mailbox/select_mode.dart';
import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart';
import 'package:tmail_ui_user/features/thread/presentation/mixin/base_email_item_tile.dart';
import 'package:tmail_ui_user/features/thread/presentation/styles/item_email_tile_styles.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class WebTabletBodyEmailItemWidget extends StatefulWidget {
final PresentationEmail presentationEmail;
final SelectMode selectAllMode;
final bool canDeletePermanently;
final bool isSearchEmailRunning;
final bool isShowingEmailContent;
final bool isShowDateTimeView;
final bool isDrag;
final bool isSenderImportantFlagEnabled;
final EdgeInsetsGeometry? padding;
final SearchQuery? searchQuery;
final PresentationMailbox? mailboxContain;
final OnPressEmailActionClick? emailActionClick;
final OnMoreActionClick? onMoreActionClick;
const WebTabletBodyEmailItemWidget({
super.key,
required this.presentationEmail,
required this.selectAllMode,
required this.canDeletePermanently,
required this.isSearchEmailRunning,
required this.isShowingEmailContent,
required this.isDrag,
required this.isSenderImportantFlagEnabled,
required this.padding,
required this.searchQuery,
required this.mailboxContain,
required this.emailActionClick,
required this.onMoreActionClick,
this.isShowDateTimeView = false,
});
@override
State<WebTabletBodyEmailItemWidget> createState() =>
_WebTabletBodyEmailItemWidgetState();
}
class _WebTabletBodyEmailItemWidgetState
extends State<WebTabletBodyEmailItemWidget> with BaseEmailItemTile {
final _imagePaths = Get.find<ImagePaths>();
final _responsiveUtils = Get.find<ResponsiveUtils>();
bool _isHover = false;
bool _popupMenuVisible = false;
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
child: InkWell(
onTap: () => widget.emailActionClick?.call(
EmailActionType.preview,
widget.presentationEmail,
),
hoverColor: Theme.of(context).colorScheme.outline.withOpacity(0.08),
onHover: (value) {
setState(() {
_isHover = value;
});
},
child: Container(
padding: widget.padding ?? _getPaddingItem(context),
decoration: _getDecorationItem(),
alignment: Alignment.center,
child: Stack(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () => widget.emailActionClick?.call(
EmailActionType.selection,
widget.presentationEmail),
child: Padding(
padding: const EdgeInsetsDirectional.only(
top: 8,
end: 12,
),
child: _buildAvatarIcon(
context: context,
isHovered: _isHover,
))),
),
Expanded(
child: Column(
children: [
Row(children: [
if (!widget.presentationEmail.hasRead)
Padding(
padding: const EdgeInsetsDirectional.only(end: 5),
child: buildIconUnreadStatus(),
),
Expanded(
child: buildInformationSender(
context,
widget.presentationEmail,
widget.mailboxContain,
widget.isSearchEmailRunning,
widget.searchQuery,
),
),
if (_shouldShowPopupMenu)
const SizedBox(width: 120)
else
_buildDateTimeForMobileTabletScreen(context),
]),
const SizedBox(height: 2),
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.presentationEmail.hasCalendarEvent)
buildCalendarEventIcon(
context: context,
presentationEmail: widget.presentationEmail,
),
if (widget.presentationEmail.isMarkAsImportant &&
widget.isSenderImportantFlagEnabled)
buildMarkAsImportantIcon(context),
Expanded(
child: buildEmailTitle(
context,
widget.presentationEmail,
widget.isSearchEmailRunning,
widget.searchQuery,
),
),
buildMailboxContain(
context,
widget.isSearchEmailRunning,
widget.presentationEmail,
),
if (widget.presentationEmail.hasStarred)
Padding(
padding: const EdgeInsetsDirectional.only(
start: 8,
),
child: buildIconStar(),
)
],
),
const SizedBox(height: 2),
Row(
children: [
Expanded(
child: buildEmailPartialContent(
context,
widget.presentationEmail,
widget.isSearchEmailRunning,
widget.searchQuery,
),
),
],
),
],
),
)
],
),
Positioned(
top: 0,
right: 0,
child: Transform(
transform: Matrix4.translationValues(
0.0,
widget.selectAllMode == SelectMode.INACTIVE ? -5.0 : 0.0,
0.0,
),
child: Row(
children: [
if (_isHover) ...[
TMailButtonWidget.fromIcon(
icon: _imagePaths.icOpenInNewTab,
iconColor: ItemEmailTileStyles.actionIconColor,
iconSize: _getIconSize(),
padding: _getPaddingIcon(context),
backgroundColor: Colors.transparent,
tooltipMessage:
AppLocalizations.of(context).openInNewTab,
onTapActionCallback: () =>
widget.emailActionClick?.call(
EmailActionType.openInNewTab,
widget.presentationEmail,
),
),
if (!widget.presentationEmail.isDraft)
TMailButtonWidget.fromIcon(
icon: widget.presentationEmail.hasRead
? _imagePaths.icUnread
: _imagePaths.icRead,
iconColor: ItemEmailTileStyles.actionIconColor,
iconSize: _getIconSize(),
padding: _getPaddingIcon(context),
backgroundColor: Colors.transparent,
tooltipMessage: widget.presentationEmail.hasRead
? AppLocalizations.of(context).mark_as_unread
: AppLocalizations.of(context).mark_as_read,
onTapActionCallback: () =>
widget.emailActionClick?.call(
widget.presentationEmail.hasRead
? EmailActionType.markAsUnread
: EmailActionType.markAsRead,
widget.presentationEmail,
),
),
if (widget.mailboxContain != null &&
widget.mailboxContain?.isDrafts == false) ...[
TMailButtonWidget.fromIcon(
icon: _imagePaths.icMove,
iconColor: ItemEmailTileStyles.actionIconColor,
iconSize: _getIconSize(),
padding: _getPaddingIcon(context),
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).move,
onTapActionCallback: () =>
widget.emailActionClick?.call(
EmailActionType.moveToMailbox,
widget.presentationEmail,
),
),
],
TMailButtonWidget.fromIcon(
icon: _imagePaths.icDeleteComposer,
iconColor: ItemEmailTileStyles.actionIconColor,
iconSize: 14,
padding: _getPaddingIcon(context),
backgroundColor: Colors.transparent,
tooltipMessage: widget.canDeletePermanently
? AppLocalizations.of(context).delete_permanently
: AppLocalizations.of(context).move_to_trash,
onTapActionCallback: () =>
widget.emailActionClick?.call(
widget.canDeletePermanently
? EmailActionType.deletePermanently
: EmailActionType.moveToTrash,
widget.presentationEmail,
),
),
],
if (_shouldShowPopupMenu)
TMailButtonWidget.fromIcon(
icon: _imagePaths.icMoreVertical,
iconColor: ItemEmailTileStyles.actionIconColor,
iconSize: _getIconSize(),
padding: _getPaddingIcon(context),
backgroundColor: _popupMenuVisible
? Theme.of(context)
.colorScheme
.outline
.withOpacity(0.08)
: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).more,
onTapActionAtPositionCallback: (position) {
if (_responsiveUtils
.isScreenWithShortestSide(context)) {
widget.onMoreActionClick
?.call(widget.presentationEmail, null);
} else {
_onPopupMenuVisibleChange(true);
widget.onMoreActionClick
?.call(widget.presentationEmail, position)
.whenComplete(
() => _onPopupMenuVisibleChange(false));
}
},
)
],
),
),
)
],
),
),
),
);
}
EdgeInsetsGeometry _getPaddingItem(BuildContext context) {
if (responsiveUtils.isDesktop(context)) {
return const EdgeInsets.symmetric(vertical: 4);
} else if (responsiveUtils.isTablet(context)) {
return const EdgeInsetsDirectional.symmetric(vertical: 8, horizontal: 24);
} else {
return const EdgeInsetsDirectional.symmetric(vertical: 8, horizontal: 12);
}
}
BoxDecoration? _getDecorationItem() {
if (((widget.selectAllMode == SelectMode.ACTIVE &&
widget.presentationEmail.selectMode == SelectMode.ACTIVE) ||
widget.isDrag) &&
responsiveUtils.isDesktop(context)) {
return const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8)),
color: AppColor.blue100,
);
} else if (widget.isShowingEmailContent &&
responsiveUtils.isTabletLarge(context)) {
return const BoxDecoration(color: AppColor.blue100);
} else {
return null;
}
}
Widget _buildAvatarIcon({
required BuildContext context,
bool isHovered = false,
double? iconSize,
TextStyle? textStyle,
}) {
if (widget.presentationEmail.isSelected ||
(responsiveUtils.isScreenWithShortestSide(context) &&
widget.selectAllMode == SelectMode.ACTIVE) ||
isHovered) {
return buildIconAvatarSelection(
context,
widget.presentationEmail,
iconSize: iconSize ?? 48,
textStyle: textStyle,
);
} else {
return buildIconAvatarText(
widget.presentationEmail,
iconSize: iconSize ?? 48,
textStyle: textStyle,
);
}
}
Widget _buildDateTimeForMobileTabletScreen(BuildContext context) {
return Row(
children: [
buildIconAnsweredOrForwarded(
width: 16,
height: 16,
presentationEmail: widget.presentationEmail,
),
if (widget.presentationEmail.hasAttachment == true)
Padding(
padding: const EdgeInsetsDirectional.only(start: 8),
child: buildIconAttachment(),
),
Padding(
padding: const EdgeInsetsDirectional.only(end: 4, start: 8),
child: buildDateTime(
context,
widget.presentationEmail,
),
),
buildIconChevron(),
],
);
}
double _getIconSize() => 16;
EdgeInsets _getPaddingIcon(BuildContext context) {
return const EdgeInsets.all(5);
}
bool get _shouldShowPopupMenu => _isHover || _popupMenuVisible;
void _onPopupMenuVisibleChange(bool visible) {
if (_popupMenuVisible != visible && mounted) {
setState(() {
_popupMenuVisible = visible;
});
}
}
}