TF-4141 Show AI action tag in list emails

This commit is contained in:
dab246
2025-12-16 12:53:38 +07:00
committed by Dat H. Pham
parent bbbb04a70e
commit 61b8c95123
13 changed files with 348 additions and 165 deletions
@@ -291,6 +291,7 @@ extension AppColor on Color {
static const lightGrayF6FAFF = Color(0xFFF6FAFF); static const lightGrayF6FAFF = Color(0xFFF6FAFF);
static const lightGrayF7F6F9 = Color(0xFFF7F6F9); static const lightGrayF7F6F9 = Color(0xFFF7F6F9);
static const blue25AEFE = Color(0xFF25AEFE); static const blue25AEFE = Color(0xFF25AEFE);
static const aiActionTag = Color(0xFF7E57E3);
static const mapGradientColor = [ static const mapGradientColor = [
[Color(0xFF21D4FD), Color(0xFFB721FF)], [Color(0xFF21D4FD), Color(0xFFB721FF)],
@@ -0,0 +1,23 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/base/widget/labels/tag_widget.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class AiActionTagWidget extends StatelessWidget {
final EdgeInsetsGeometry? margin;
const AiActionTagWidget({super.key, this.margin});
@override
Widget build(BuildContext context) {
return TagWidget(
text: AppLocalizations.of(context).actionRequired,
backgroundColor: AppColor.aiActionTag,
textColor: Colors.white,
isTruncateText: true,
showTooltip: PlatformInfo.isWeb,
margin: margin,
);
}
}
@@ -0,0 +1,65 @@
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:flutter/material.dart';
class TagWidget extends StatelessWidget {
final String text;
final double horizontalPadding;
final Color? backgroundColor;
final Color? textColor;
final double? maxWidth;
final bool isTruncateText;
final bool showTooltip;
final EdgeInsetsGeometry? margin;
const TagWidget({
super.key,
required this.text,
this.horizontalPadding = 4,
this.isTruncateText = false,
this.showTooltip = false,
this.backgroundColor,
this.textColor,
this.maxWidth,
this.margin,
});
@override
Widget build(BuildContext context) {
Widget labelText = Text(
isTruncateText ? _truncateName(text) : text,
style: ThemeUtils.textStyleContentCaption().copyWith(
color: textColor,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
if (showTooltip) {
labelText = Tooltip(
message: text,
child: labelText,
);
}
return Container(
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: const BorderRadius.all(Radius.circular(4)),
),
constraints:
maxWidth != null ? BoxConstraints(maxWidth: maxWidth!) : null,
padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
margin: margin,
child: labelText,
);
}
String _truncateName(String name, {int maxLength = 16}) {
try {
if (name.length <= maxLength) return name;
return '${name.substring(0, maxLength - 1)}...';
} catch (_) {
return name;
}
}
}
@@ -0,0 +1,19 @@
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/main/error/capability_validator.dart';
extension HandleAiActionExtension on MailboxDashBoardController {
bool get isAiCapabilitySupported {
final currentAccountId = accountId.value;
final currentSession = sessionCurrent;
if (currentAccountId == null || currentSession == null) {
return false;
}
return SessionExtensions.linagoraAICapability.isSupported(
currentSession,
currentAccountId,
);
}
}
@@ -18,6 +18,7 @@ import 'package:tmail_ui_user/features/base/widget/keyboard/keyboard_handler_wra
import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart'; import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart';
import 'package:tmail_ui_user/features/base/widget/scrollbar_list_view.dart'; import 'package:tmail_ui_user/features/base/widget/scrollbar_list_view.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/recent_search.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/recent_search.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_ai_action_extension.dart';
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/model/search/email_receive_time_type.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart';
@@ -665,96 +666,96 @@ class SearchEmailView extends GetWidget<SearchEmailController>
} }
return false; return false;
}, },
child: PlatformInfo.isMobile child: ListView.separated(
? ListView.separated( controller: controller.resultSearchScrollController,
controller: controller.resultSearchScrollController, physics: const AlwaysScrollableScrollPhysics(),
physics: const AlwaysScrollableScrollPhysics(), key: const PageStorageKey('list_presentation_email_in_search_view'),
key: const PageStorageKey('list_presentation_email_in_search_view'), itemCount: listPresentationEmail.length,
itemCount: listPresentationEmail.length, itemBuilder: (context, index) => _buildEmailItem(
itemBuilder: (context, index) { context,
final currentPresentationEmail = listPresentationEmail[index]; listPresentationEmail[index],
return Obx(() => EmailTileBuilder( ),
presentationEmail: currentPresentationEmail, separatorBuilder: (context, index) {
selectAllMode: controller.selectionMode.value, final isMobile = PlatformInfo.isMobile;
searchQuery: controller.searchQuery, final isLastItem = index == listPresentationEmail.length - 1;
isShowingEmailContent: controller.mailboxDashBoardController.selectedEmail.value?.id == currentPresentationEmail.id, final isInactive =
isSenderImportantFlagEnabled: controller.mailboxDashBoardController.isSenderImportantFlagEnabled.value, controller.selectionMode.value == SelectMode.INACTIVE;
isSearchEmailRunning: true,
padding: SearchEmailViewStyle.getPaddingSearchResultList(context, controller.responsiveUtils), final showDivider = !isLastItem;
mailboxContain: currentPresentationEmail.mailboxContain,
emailActionClick: (action, email) { final dividerColor = isMobile
controller.pressEmailAction( ? null
context, : (showDivider && isInactive ? null : Colors.white);
action,
email, final padding = isMobile
mailboxContain: currentPresentationEmail.mailboxContain ? SearchEmailUtils.getPaddingItemListMobile(
);
},
onMoreActionClick: (email, position) =>
controller.handleEmailMoreAction(
context,
email,
position,
),
));
},
separatorBuilder: (BuildContext context, int index) {
if (index < listPresentationEmail.length - 1) {
return Padding(
padding: SearchEmailUtils.getPaddingItemListMobile(context, controller.responsiveUtils),
child: const Divider());
} else {
return const SizedBox.shrink();
}
},
)
: ListView.separated(
controller: controller.resultSearchScrollController,
physics: const AlwaysScrollableScrollPhysics(),
key: const PageStorageKey('list_presentation_email_in_search_view'),
itemCount: listPresentationEmail.length,
itemBuilder: (context, index) {
final currentPresentationEmail = listPresentationEmail[index];
return Obx(() => EmailTileBuilder(
presentationEmail: currentPresentationEmail,
selectAllMode: controller.selectionMode.value,
searchQuery: controller.searchQuery,
isShowingEmailContent: controller.mailboxDashBoardController.selectedEmail.value?.id == currentPresentationEmail.id,
isSenderImportantFlagEnabled: controller.mailboxDashBoardController.isSenderImportantFlagEnabled.value,
isSearchEmailRunning: true,
padding: SearchEmailViewStyle.getPaddingSearchResultList(
context, context,
controller.responsiveUtils controller.responsiveUtils,
),
mailboxContain: currentPresentationEmail.mailboxContain,
emailActionClick: (action, email) {
controller.pressEmailAction(
context,
action,
email,
mailboxContain: currentPresentationEmail.mailboxContain
);
},
onMoreActionClick: (email, position) =>
controller.handleEmailMoreAction(
context,
email,
position,
),
));
},
separatorBuilder: (context, index) {
return Padding(
padding: ItemEmailTileStyles.getPaddingDividerWeb(context, controller.responsiveUtils),
child: Divider(
color: index < listPresentationEmail.length - 1 &&
controller.selectionMode.value == SelectMode.INACTIVE
? null
: Colors.white
) )
); : ItemEmailTileStyles.getPaddingDividerWeb(
}, context,
) controller.responsiveUtils,
);
if (!showDivider) {
return const SizedBox.shrink();
}
return Padding(
padding: padding,
child: Divider(color: dividerColor),
);
},
)
);
}
Widget _buildEmailItem(
BuildContext context,
PresentationEmail presentationEmail,
) {
final dashboardController = controller.mailboxDashBoardController;
return Obx(
() {
final isShowingEmailContent =
dashboardController.selectedEmail.value?.id == presentationEmail.id;
final isSenderImportantFlagEnabled =
dashboardController.isSenderImportantFlagEnabled.value;
final isAiCapabilitySupported =
dashboardController.isAiCapabilitySupported;
return EmailTileBuilder(
presentationEmail: presentationEmail,
selectAllMode: controller.selectionMode.value,
searchQuery: controller.searchQuery,
isShowingEmailContent: isShowingEmailContent,
isSenderImportantFlagEnabled: isSenderImportantFlagEnabled,
isSearchEmailRunning: true,
isAIEnabled: isAiCapabilitySupported,
padding: SearchEmailViewStyle.getPaddingSearchResultList(
context,
controller.responsiveUtils,
),
mailboxContain: presentationEmail.mailboxContain,
emailActionClick: (action, email) {
controller.pressEmailAction(
context,
action,
email,
mailboxContain: presentationEmail.mailboxContain,
);
},
onMoreActionClick: (email, position) =>
controller.handleEmailMoreAction(
context,
email,
position,
),
);
},
); );
} }
+111 -76
View File
@@ -20,6 +20,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/state/clear_mailbox_state.
import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart';
import 'package:tmail_ui_user/features/mailbox/domain/state/move_folder_content_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/move_folder_content_state.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/spam_report_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/spam_report_state.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_ai_action_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_open_context_menu_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_open_context_menu_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/recover_deleted_message_loading_banner_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/recover_deleted_message_loading_banner_widget.dart';
@@ -495,71 +496,75 @@ class ThreadView extends GetWidget<ThreadController>
} }
Widget _buildEmailItemWhenDragging(BuildContext context, PresentationEmail presentationEmail) { Widget _buildEmailItemWhenDragging(BuildContext context, PresentationEmail presentationEmail) {
final isShowingEmailContent = controller.mailboxDashBoardController.selectedEmail.value?.id == presentationEmail.id; final dashboardController = controller.mailboxDashBoardController;
final selectModeAll = controller.mailboxDashBoardController.currentSelectMode.value;
final isSenderImportantFlagEnabled =
controller.mailboxDashBoardController.isSenderImportantFlagEnabled.value;
return EmailTileBuilder( return Obx(() {
key: Key('email_tile_builder_${presentationEmail.id?.asString}'), final selectAllMode = dashboardController.currentSelectMode.value;
presentationEmail: presentationEmail,
selectAllMode: selectModeAll, final isSenderImportantFlagEnabled =
isShowingEmailContent: isShowingEmailContent, dashboardController.isSenderImportantFlagEnabled.value;
searchQuery: controller.searchQuery,
mailboxContain: presentationEmail.mailboxContain, final isShowingEmailContent =
isSearchEmailRunning: controller.searchController.isSearchEmailRunning, dashboardController.selectedEmail.value?.id == presentationEmail.id;
isDrag: true,
isSenderImportantFlagEnabled: isSenderImportantFlagEnabled, final isSearchEmailRunning =
); controller.searchController.isSearchEmailRunning;
final isAiCapabilitySupported =
dashboardController.isAiCapabilitySupported;
return EmailTileBuilder(
key: Key('email_tile_builder_${presentationEmail.id?.asString}'),
presentationEmail: presentationEmail,
selectAllMode: selectAllMode,
isShowingEmailContent: isShowingEmailContent,
searchQuery: controller.searchQuery,
mailboxContain: presentationEmail.mailboxContain,
isSearchEmailRunning: isSearchEmailRunning,
isDrag: true,
isSenderImportantFlagEnabled: isSenderImportantFlagEnabled,
isAIEnabled: isAiCapabilitySupported,
);
});
} }
Widget _buildEmailItemNotDraggable(BuildContext context, PresentationEmail presentationEmail) { Widget _buildEmailItemNotDraggable(BuildContext context, PresentationEmail presentationEmail) {
final isShowingEmailContent = controller.mailboxDashBoardController.selectedEmail.value?.id == presentationEmail.id; final backgroundWidget = Container(
final selectModeAll = controller.mailboxDashBoardController.currentSelectMode.value; color: AppColor.colorItemRecipientSelected,
final isSenderImportantFlagEnabled = padding: const EdgeInsetsDirectional.only(start: 16),
controller.mailboxDashBoardController.isSenderImportantFlagEnabled.value; alignment: AlignmentDirectional.centerStart,
child: Row(
return Dismissible( children: [
key: ValueKey<EmailId?>(presentationEmail.id), CircleAvatar(
direction: controller.getSwipeDirection( backgroundColor: AppColor.colorSpamReportBannerBackground,
controller.responsiveUtils.isWebDesktop(context), radius: 24,
selectModeAll, child: !presentationEmail.hasRead
presentationEmail ? SvgPicture.asset(
), controller.imagePaths.icMarkAsRead,
background: Container( fit: BoxFit.fill,
color: AppColor.colorItemRecipientSelected, )
padding: const EdgeInsetsDirectional.only(start: 16), : SvgPicture.asset(
alignment: AlignmentDirectional.centerStart, controller.imagePaths.icUnreadEmail,
child: Row( fit: BoxFit.fill,
children: [ colorFilter: AppColor.primaryColor.asFilter(),
CircleAvatar( ),
backgroundColor: AppColor.colorSpamReportBannerBackground, ),
radius: 24, const SizedBox(width: 11),
child: !presentationEmail.hasRead Text(
? SvgPicture.asset( !presentationEmail.hasRead
controller.imagePaths.icMarkAsRead,
fit: BoxFit.fill,
)
: SvgPicture.asset(
controller.imagePaths.icUnreadEmail,
fit: BoxFit.fill,
colorFilter: AppColor.primaryColor.asFilter(),
),
),
const SizedBox(width: 11),
Text(
!presentationEmail.hasRead
? AppLocalizations.of(context).mark_as_read ? AppLocalizations.of(context).mark_as_read
: AppLocalizations.of(context).mark_as_unread, : AppLocalizations.of(context).mark_as_unread,
style: ThemeUtils.defaultTextStyleInterFont.copyWith( style: ThemeUtils.defaultTextStyleInterFont.copyWith(
fontSize: 15, fontSize: 15,
color: AppColor.primaryColor, color: AppColor.primaryColor,
),
), ),
], ),
), ],
), ),
secondaryBackground: controller.isInArchiveMailbox(presentationEmail) == false );
final isInArchiveMailbox = controller.isInArchiveMailbox(presentationEmail);
final secondaryBackgroundWidget = !isInArchiveMailbox
? Container( ? Container(
color: AppColor.colorItemRecipientSelected, color: AppColor.colorItemRecipientSelected,
padding: const EdgeInsetsDirectional.only(end: 16), padding: const EdgeInsetsDirectional.only(end: 16),
@@ -573,7 +578,7 @@ class ThreadView extends GetWidget<ThreadController>
child: SvgPicture.asset( child: SvgPicture.asset(
controller.imagePaths.icMailboxArchived, controller.imagePaths.icMailboxArchived,
fit: BoxFit.fill, fit: BoxFit.fill,
) ),
), ),
const SizedBox(width: 11), const SizedBox(width: 11),
Text( Text(
@@ -586,24 +591,54 @@ class ThreadView extends GetWidget<ThreadController>
], ],
), ),
) )
: null, : null;
confirmDismiss: (direction) => controller.swipeEmailAction(
presentationEmail, final dashboardController = controller.mailboxDashBoardController;
direction,
), return Obx(() {
child: EmailTileBuilder( final selectModeAll = dashboardController.currentSelectMode.value;
key: Key('email_tile_builder_${presentationEmail.id?.asString}'),
presentationEmail: presentationEmail, final isSenderImportantFlagEnabled =
selectAllMode: selectModeAll, dashboardController.isSenderImportantFlagEnabled.value;
isShowingEmailContent: isShowingEmailContent,
isSenderImportantFlagEnabled: isSenderImportantFlagEnabled, final isShowingEmailContent =
searchQuery: controller.searchQuery, dashboardController.selectedEmail.value?.id == presentationEmail.id;
mailboxContain: presentationEmail.mailboxContain,
isSearchEmailRunning: controller.searchController.isSearchEmailRunning, final isSearchEmailRunning =
emailActionClick: _handleEmailActionClicked, controller.searchController.isSearchEmailRunning;
onMoreActionClick: (email, position) => _handleEmailContextMenuAction(context, email, position),
), final isAiCapabilitySupported =
); dashboardController.isAiCapabilitySupported;
return Dismissible(
key: ValueKey<EmailId?>(presentationEmail.id),
direction: controller.getSwipeDirection(
controller.responsiveUtils.isWebDesktop(context),
selectModeAll,
presentationEmail
),
background: backgroundWidget,
secondaryBackground: secondaryBackgroundWidget,
confirmDismiss: (direction) => controller.swipeEmailAction(
presentationEmail,
direction,
),
child: EmailTileBuilder(
key: Key('email_tile_builder_${presentationEmail.id?.asString}'),
presentationEmail: presentationEmail,
selectAllMode: selectModeAll,
isShowingEmailContent: isShowingEmailContent,
isSenderImportantFlagEnabled: isSenderImportantFlagEnabled,
searchQuery: controller.searchQuery,
mailboxContain: presentationEmail.mailboxContain,
isSearchEmailRunning: isSearchEmailRunning,
isAIEnabled: isAiCapabilitySupported,
emailActionClick: _handleEmailActionClicked,
onMoreActionClick: (email, position) =>
_handleEmailContextMenuAction(context, email, position),
),
);
});
} }
void _handleEmailActionClicked( void _handleEmailActionClicked(
@@ -4,6 +4,7 @@ import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart'; import 'package:model/email/presentation_email.dart';
import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:model/mailbox/select_mode.dart'; import 'package:model/mailbox/select_mode.dart';
import 'package:tmail_ui_user/features/base/widget/labels/ai_action_tag_widget.dart';
import 'package:tmail_ui_user/features/thread/domain/model/search_query.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/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/styles/item_email_tile_styles.dart';
@@ -19,6 +20,7 @@ class EmailTileBuilder extends StatelessWidget with BaseEmailItemTile {
final bool isDrag; final bool isDrag;
final bool isShowingEmailContent; final bool isShowingEmailContent;
final bool isSenderImportantFlagEnabled; final bool isSenderImportantFlagEnabled;
final bool isAIEnabled;
final OnPressEmailActionClick? emailActionClick; final OnPressEmailActionClick? emailActionClick;
final OnMoreActionClick? onMoreActionClick; final OnMoreActionClick? onMoreActionClick;
@@ -30,6 +32,7 @@ class EmailTileBuilder extends StatelessWidget with BaseEmailItemTile {
this.searchQuery, this.searchQuery,
this.isSearchEmailRunning = false, this.isSearchEmailRunning = false,
this.isSenderImportantFlagEnabled = true, this.isSenderImportantFlagEnabled = true,
this.isAIEnabled = true,
this.mailboxContain, this.mailboxContain,
this.padding, this.padding,
this.isDrag = false, this.isDrag = false,
@@ -128,6 +131,7 @@ class EmailTileBuilder extends StatelessWidget with BaseEmailItemTile {
presentationEmail, presentationEmail,
isSearchEmailRunning, isSearchEmailRunning,
searchQuery)), searchQuery)),
if (_shouldShowAIAction) const AiActionTagWidget(),
]) ])
), ),
], ],
@@ -135,4 +139,7 @@ class EmailTileBuilder extends StatelessWidget with BaseEmailItemTile {
), ),
); );
} }
bool get _shouldShowAIAction =>
isAIEnabled && presentationEmail.hasNeedAction;
} }
@@ -9,6 +9,7 @@ import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/presentation_mailbox_extension.dart'; import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:model/mailbox/select_mode.dart'; import 'package:model/mailbox/select_mode.dart';
import 'package:tmail_ui_user/features/base/widget/labels/ai_action_tag_widget.dart';
import 'package:tmail_ui_user/features/thread/domain/model/search_query.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/mixin/base_email_item_tile.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/desktop_list_email_action_hover_widget.dart';
@@ -26,6 +27,7 @@ class EmailTileBuilder extends StatefulWidget {
final bool isDrag; final bool isDrag;
final bool isShowingEmailContent; final bool isShowingEmailContent;
final bool isSenderImportantFlagEnabled; final bool isSenderImportantFlagEnabled;
final bool isAIEnabled;
final OnPressEmailActionClick? emailActionClick; final OnPressEmailActionClick? emailActionClick;
final OnMoreActionClick? onMoreActionClick; final OnMoreActionClick? onMoreActionClick;
@@ -40,6 +42,7 @@ class EmailTileBuilder extends StatefulWidget {
this.mailboxContain, this.mailboxContain,
this.padding, this.padding,
this.isDrag = false, this.isDrag = false,
this.isAIEnabled = false,
this.emailActionClick, this.emailActionClick,
this.onMoreActionClick, this.onMoreActionClick,
}); });
@@ -169,6 +172,7 @@ class _EmailTileBuilderState extends State<EmailTileBuilder> with BaseEmailItem
widget.searchQuery widget.searchQuery
) )
), ),
if (_shouldShowAIAction) const AiActionTagWidget(),
]), ]),
]), ]),
) )
@@ -185,6 +189,7 @@ class _EmailTileBuilderState extends State<EmailTileBuilder> with BaseEmailItem
isShowingEmailContent: widget.isShowingEmailContent, isShowingEmailContent: widget.isShowingEmailContent,
isDrag: widget.isDrag, isDrag: widget.isDrag,
isSenderImportantFlagEnabled: widget.isSenderImportantFlagEnabled, isSenderImportantFlagEnabled: widget.isSenderImportantFlagEnabled,
shouldShowAIAction: _shouldShowAIAction,
padding: widget.padding, padding: widget.padding,
searchQuery: widget.searchQuery, searchQuery: widget.searchQuery,
mailboxContain: widget.mailboxContain, mailboxContain: widget.mailboxContain,
@@ -368,6 +373,8 @@ class _EmailTileBuilderState extends State<EmailTileBuilder> with BaseEmailItem
buildCalendarEventIcon(context: context, presentationEmail: widget.presentationEmail), buildCalendarEventIcon(context: context, presentationEmail: widget.presentationEmail),
if (widget.presentationEmail.isMarkAsImportant && widget.isSenderImportantFlagEnabled) if (widget.presentationEmail.isMarkAsImportant && widget.isSenderImportantFlagEnabled)
buildMarkAsImportantIcon(context), buildMarkAsImportantIcon(context),
if (_shouldShowAIAction)
const AiActionTagWidget(margin: EdgeInsetsDirectional.only(end: 12)),
if (widget.presentationEmail.getEmailTitle().isNotEmpty) if (widget.presentationEmail.getEmailTitle().isNotEmpty)
Container( Container(
constraints: BoxConstraints(maxWidth: constraints.maxWidth / 2), constraints: BoxConstraints(maxWidth: constraints.maxWidth / 2),
@@ -415,6 +422,9 @@ class _EmailTileBuilderState extends State<EmailTileBuilder> with BaseEmailItem
} }
} }
bool get _shouldShowAIAction =>
widget.isAIEnabled && widget.presentationEmail.hasNeedAction;
@override @override
void dispose() { void dispose() {
_hoverNotifier.dispose(); _hoverNotifier.dispose();
@@ -8,6 +8,7 @@ import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/presentation_mailbox_extension.dart'; import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:model/mailbox/select_mode.dart'; import 'package:model/mailbox/select_mode.dart';
import 'package:tmail_ui_user/features/base/widget/labels/ai_action_tag_widget.dart';
import 'package:tmail_ui_user/features/thread/domain/model/search_query.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/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/styles/item_email_tile_styles.dart';
@@ -22,6 +23,7 @@ class WebTabletBodyEmailItemWidget extends StatefulWidget {
final bool isShowDateTimeView; final bool isShowDateTimeView;
final bool isDrag; final bool isDrag;
final bool isSenderImportantFlagEnabled; final bool isSenderImportantFlagEnabled;
final bool shouldShowAIAction;
final EdgeInsetsGeometry? padding; final EdgeInsetsGeometry? padding;
final SearchQuery? searchQuery; final SearchQuery? searchQuery;
final PresentationMailbox? mailboxContain; final PresentationMailbox? mailboxContain;
@@ -43,6 +45,7 @@ class WebTabletBodyEmailItemWidget extends StatefulWidget {
required this.emailActionClick, required this.emailActionClick,
required this.onMoreActionClick, required this.onMoreActionClick,
this.isShowDateTimeView = false, this.isShowDateTimeView = false,
this.shouldShowAIAction = false,
}); });
@override @override
@@ -165,6 +168,8 @@ class _WebTabletBodyEmailItemWidgetState
widget.searchQuery, widget.searchQuery,
), ),
), ),
if (widget.shouldShowAIAction)
const AiActionTagWidget(),
], ],
), ),
], ],
+7 -1
View File
@@ -1,5 +1,5 @@
{ {
"@@last_modified": "2025-12-15T14:32:52.563522", "@@last_modified": "2025-12-16T11:52:06.406969",
"initializing_data": "Initializing data...", "initializing_data": "Initializing data...",
"@initializing_data": { "@initializing_data": {
"type": "text", "type": "text",
@@ -5135,5 +5135,11 @@
"type": "text", "type": "text",
"placeholders_order": [], "placeholders_order": [],
"placeholders": {} "placeholders": {}
},
"actionRequired": "Action Required",
"@actionRequired": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
} }
} }
@@ -5457,4 +5457,11 @@ class AppLocalizations {
name: 'startToAddFavoritesEmails', name: 'startToAddFavoritesEmails',
); );
} }
String get actionRequired {
return Intl.message(
'Action Required',
name: 'actionRequired',
);
}
} }
+3
View File
@@ -138,6 +138,9 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
bool get isSubscribed => keywords?.containsKey(KeyWordIdentifierExtension.unsubscribeMail) == true; bool get isSubscribed => keywords?.containsKey(KeyWordIdentifierExtension.unsubscribeMail) == true;
bool get hasNeedAction =>
keywords?.containsKey(KeyWordIdentifierExtension.needActionMail) == true;
bool get isAnsweredAndForwarded => isAnswered && isForwarded; bool get isAnsweredAndForwarded => isAnswered && isForwarded;
bool get withAttachments => hasAttachment == true; bool get withAttachments => hasAttachment == true;
@@ -5,6 +5,7 @@ import 'package:model/email/read_actions.dart';
extension KeyWordIdentifierExtension on KeyWordIdentifier { extension KeyWordIdentifierExtension on KeyWordIdentifier {
static final unsubscribeMail = KeyWordIdentifier('\$unsubscribe'); static final unsubscribeMail = KeyWordIdentifier('\$unsubscribe');
static final needActionMail = KeyWordIdentifier('\$need-action');
String generatePath() { String generatePath() {
return '${PatchObject.keywordsProperty}/$value'; return '${PatchObject.keywordsProperty}/$value';