diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index de885f90a..e8dcfcbcf 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -291,6 +291,7 @@ extension AppColor on Color { static const lightGrayF6FAFF = Color(0xFFF6FAFF); static const lightGrayF7F6F9 = Color(0xFFF7F6F9); static const blue25AEFE = Color(0xFF25AEFE); + static const aiActionTag = Color(0xFF7E57E3); static const mapGradientColor = [ [Color(0xFF21D4FD), Color(0xFFB721FF)], diff --git a/lib/features/base/widget/labels/ai_action_tag_widget.dart b/lib/features/base/widget/labels/ai_action_tag_widget.dart new file mode 100644 index 000000000..000dca5e1 --- /dev/null +++ b/lib/features/base/widget/labels/ai_action_tag_widget.dart @@ -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, + ); + } +} diff --git a/lib/features/base/widget/labels/tag_widget.dart b/lib/features/base/widget/labels/tag_widget.dart new file mode 100644 index 000000000..a85d93e53 --- /dev/null +++ b/lib/features/base/widget/labels/tag_widget.dart @@ -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; + } + } +} diff --git a/lib/features/mailbox_dashboard/presentation/extensions/handle_ai_action_extension.dart b/lib/features/mailbox_dashboard/presentation/extensions/handle_ai_action_extension.dart new file mode 100644 index 000000000..9c6b71da4 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/extensions/handle_ai_action_extension.dart @@ -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, + ); + } +} diff --git a/lib/features/search/email/presentation/search_email_view.dart b/lib/features/search/email/presentation/search_email_view.dart index 27517382b..0472ff952 100644 --- a/lib/features/search/email/presentation/search_email_view.dart +++ b/lib/features/search/email/presentation/search_email_view.dart @@ -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/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/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_sort_order_type.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart'; @@ -665,96 +666,96 @@ class SearchEmailView extends GetWidget } return false; }, - child: PlatformInfo.isMobile - ? 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, 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: (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( + child: ListView.separated( + controller: controller.resultSearchScrollController, + physics: const AlwaysScrollableScrollPhysics(), + key: const PageStorageKey('list_presentation_email_in_search_view'), + itemCount: listPresentationEmail.length, + itemBuilder: (context, index) => _buildEmailItem( + context, + listPresentationEmail[index], + ), + separatorBuilder: (context, index) { + final isMobile = PlatformInfo.isMobile; + final isLastItem = index == listPresentationEmail.length - 1; + final isInactive = + controller.selectionMode.value == SelectMode.INACTIVE; + + final showDivider = !isLastItem; + + final dividerColor = isMobile + ? null + : (showDivider && isInactive ? null : Colors.white); + + final padding = isMobile + ? SearchEmailUtils.getPaddingItemListMobile( context, - 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 + controller.responsiveUtils, ) - ); - }, - ) + : 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, + ), + ); + }, ); } diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 9f7d2320f..a6b2c692d 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -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/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/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/open_and_close_composer_extension.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 } Widget _buildEmailItemWhenDragging(BuildContext context, PresentationEmail presentationEmail) { - final isShowingEmailContent = controller.mailboxDashBoardController.selectedEmail.value?.id == presentationEmail.id; - final selectModeAll = controller.mailboxDashBoardController.currentSelectMode.value; - final isSenderImportantFlagEnabled = - controller.mailboxDashBoardController.isSenderImportantFlagEnabled.value; + final dashboardController = controller.mailboxDashBoardController; - return EmailTileBuilder( - key: Key('email_tile_builder_${presentationEmail.id?.asString}'), - presentationEmail: presentationEmail, - selectAllMode: selectModeAll, - isShowingEmailContent: isShowingEmailContent, - searchQuery: controller.searchQuery, - mailboxContain: presentationEmail.mailboxContain, - isSearchEmailRunning: controller.searchController.isSearchEmailRunning, - isDrag: true, - isSenderImportantFlagEnabled: isSenderImportantFlagEnabled, - ); + return Obx(() { + final selectAllMode = dashboardController.currentSelectMode.value; + + final isSenderImportantFlagEnabled = + dashboardController.isSenderImportantFlagEnabled.value; + + final isShowingEmailContent = + dashboardController.selectedEmail.value?.id == presentationEmail.id; + + 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) { - final isShowingEmailContent = controller.mailboxDashBoardController.selectedEmail.value?.id == presentationEmail.id; - final selectModeAll = controller.mailboxDashBoardController.currentSelectMode.value; - final isSenderImportantFlagEnabled = - controller.mailboxDashBoardController.isSenderImportantFlagEnabled.value; - - return Dismissible( - key: ValueKey(presentationEmail.id), - direction: controller.getSwipeDirection( - controller.responsiveUtils.isWebDesktop(context), - selectModeAll, - presentationEmail - ), - background: Container( - color: AppColor.colorItemRecipientSelected, - padding: const EdgeInsetsDirectional.only(start: 16), - alignment: AlignmentDirectional.centerStart, - child: Row( - children: [ - CircleAvatar( - backgroundColor: AppColor.colorSpamReportBannerBackground, - radius: 24, - child: !presentationEmail.hasRead - ? SvgPicture.asset( - 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 + final backgroundWidget = Container( + color: AppColor.colorItemRecipientSelected, + padding: const EdgeInsetsDirectional.only(start: 16), + alignment: AlignmentDirectional.centerStart, + child: Row( + children: [ + CircleAvatar( + backgroundColor: AppColor.colorSpamReportBannerBackground, + radius: 24, + child: !presentationEmail.hasRead + ? SvgPicture.asset( + 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_unread, - style: ThemeUtils.defaultTextStyleInterFont.copyWith( - fontSize: 15, - color: AppColor.primaryColor, - ), + style: ThemeUtils.defaultTextStyleInterFont.copyWith( + fontSize: 15, + color: AppColor.primaryColor, ), - ], - ), + ), + ], ), - secondaryBackground: controller.isInArchiveMailbox(presentationEmail) == false + ); + + final isInArchiveMailbox = controller.isInArchiveMailbox(presentationEmail); + final secondaryBackgroundWidget = !isInArchiveMailbox ? Container( color: AppColor.colorItemRecipientSelected, padding: const EdgeInsetsDirectional.only(end: 16), @@ -573,7 +578,7 @@ class ThreadView extends GetWidget child: SvgPicture.asset( controller.imagePaths.icMailboxArchived, fit: BoxFit.fill, - ) + ), ), const SizedBox(width: 11), Text( @@ -586,24 +591,54 @@ class ThreadView extends GetWidget ], ), ) - : null, - 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: controller.searchController.isSearchEmailRunning, - emailActionClick: _handleEmailActionClicked, - onMoreActionClick: (email, position) => _handleEmailContextMenuAction(context, email, position), - ), - ); + : null; + + final dashboardController = controller.mailboxDashBoardController; + + return Obx(() { + final selectModeAll = dashboardController.currentSelectMode.value; + + final isSenderImportantFlagEnabled = + dashboardController.isSenderImportantFlagEnabled.value; + + final isShowingEmailContent = + dashboardController.selectedEmail.value?.id == presentationEmail.id; + + final isSearchEmailRunning = + controller.searchController.isSearchEmailRunning; + + final isAiCapabilitySupported = + dashboardController.isAiCapabilitySupported; + + return Dismissible( + key: ValueKey(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( diff --git a/lib/features/thread/presentation/widgets/email_tile_builder.dart b/lib/features/thread/presentation/widgets/email_tile_builder.dart index cd6b7c136..d63f05928 100644 --- a/lib/features/thread/presentation/widgets/email_tile_builder.dart +++ b/lib/features/thread/presentation/widgets/email_tile_builder.dart @@ -4,6 +4,7 @@ import 'package:model/email/email_action_type.dart'; import 'package:model/email/presentation_email.dart'; import 'package:model/mailbox/presentation_mailbox.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/presentation/mixin/base_email_item_tile.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 isShowingEmailContent; final bool isSenderImportantFlagEnabled; + final bool isAIEnabled; final OnPressEmailActionClick? emailActionClick; final OnMoreActionClick? onMoreActionClick; @@ -30,6 +32,7 @@ class EmailTileBuilder extends StatelessWidget with BaseEmailItemTile { this.searchQuery, this.isSearchEmailRunning = false, this.isSenderImportantFlagEnabled = true, + this.isAIEnabled = true, this.mailboxContain, this.padding, this.isDrag = false, @@ -128,6 +131,7 @@ class EmailTileBuilder extends StatelessWidget with BaseEmailItemTile { presentationEmail, isSearchEmailRunning, searchQuery)), + if (_shouldShowAIAction) const AiActionTagWidget(), ]) ), ], @@ -135,4 +139,7 @@ class EmailTileBuilder extends StatelessWidget with BaseEmailItemTile { ), ); } + + bool get _shouldShowAIAction => + isAIEnabled && presentationEmail.hasNeedAction; } \ No newline at end of file diff --git a/lib/features/thread/presentation/widgets/email_tile_web_builder.dart b/lib/features/thread/presentation/widgets/email_tile_web_builder.dart index 9ac326666..b92f8acea 100644 --- a/lib/features/thread/presentation/widgets/email_tile_web_builder.dart +++ b/lib/features/thread/presentation/widgets/email_tile_web_builder.dart @@ -9,6 +9,7 @@ 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/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/presentation/mixin/base_email_item_tile.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 isShowingEmailContent; final bool isSenderImportantFlagEnabled; + final bool isAIEnabled; final OnPressEmailActionClick? emailActionClick; final OnMoreActionClick? onMoreActionClick; @@ -40,6 +42,7 @@ class EmailTileBuilder extends StatefulWidget { this.mailboxContain, this.padding, this.isDrag = false, + this.isAIEnabled = false, this.emailActionClick, this.onMoreActionClick, }); @@ -169,6 +172,7 @@ class _EmailTileBuilderState extends State with BaseEmailItem widget.searchQuery ) ), + if (_shouldShowAIAction) const AiActionTagWidget(), ]), ]), ) @@ -185,6 +189,7 @@ class _EmailTileBuilderState extends State with BaseEmailItem isShowingEmailContent: widget.isShowingEmailContent, isDrag: widget.isDrag, isSenderImportantFlagEnabled: widget.isSenderImportantFlagEnabled, + shouldShowAIAction: _shouldShowAIAction, padding: widget.padding, searchQuery: widget.searchQuery, mailboxContain: widget.mailboxContain, @@ -368,6 +373,8 @@ class _EmailTileBuilderState extends State with BaseEmailItem buildCalendarEventIcon(context: context, presentationEmail: widget.presentationEmail), if (widget.presentationEmail.isMarkAsImportant && widget.isSenderImportantFlagEnabled) buildMarkAsImportantIcon(context), + if (_shouldShowAIAction) + const AiActionTagWidget(margin: EdgeInsetsDirectional.only(end: 12)), if (widget.presentationEmail.getEmailTitle().isNotEmpty) Container( constraints: BoxConstraints(maxWidth: constraints.maxWidth / 2), @@ -415,6 +422,9 @@ class _EmailTileBuilderState extends State with BaseEmailItem } } + bool get _shouldShowAIAction => + widget.isAIEnabled && widget.presentationEmail.hasNeedAction; + @override void dispose() { _hoverNotifier.dispose(); diff --git a/lib/features/thread/presentation/widgets/web_tablet_body_email_item_widget.dart b/lib/features/thread/presentation/widgets/web_tablet_body_email_item_widget.dart index 8821affd7..7dabfd5b3 100644 --- a/lib/features/thread/presentation/widgets/web_tablet_body_email_item_widget.dart +++ b/lib/features/thread/presentation/widgets/web_tablet_body_email_item_widget.dart @@ -8,6 +8,7 @@ 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/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/presentation/mixin/base_email_item_tile.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 isDrag; final bool isSenderImportantFlagEnabled; + final bool shouldShowAIAction; final EdgeInsetsGeometry? padding; final SearchQuery? searchQuery; final PresentationMailbox? mailboxContain; @@ -43,6 +45,7 @@ class WebTabletBodyEmailItemWidget extends StatefulWidget { required this.emailActionClick, required this.onMoreActionClick, this.isShowDateTimeView = false, + this.shouldShowAIAction = false, }); @override @@ -165,6 +168,8 @@ class _WebTabletBodyEmailItemWidgetState widget.searchQuery, ), ), + if (widget.shouldShowAIAction) + const AiActionTagWidget(), ], ), ], diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 902ec63aa..cced4a5d6 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -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": { "type": "text", @@ -5135,5 +5135,11 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "actionRequired": "Action Required", + "@actionRequired": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 6dcd13726..65d7abba3 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -5457,4 +5457,11 @@ class AppLocalizations { name: 'startToAddFavoritesEmails', ); } + + String get actionRequired { + return Intl.message( + 'Action Required', + name: 'actionRequired', + ); + } } diff --git a/model/lib/email/presentation_email.dart b/model/lib/email/presentation_email.dart index 89a55d296..0d2c316d2 100644 --- a/model/lib/email/presentation_email.dart +++ b/model/lib/email/presentation_email.dart @@ -138,6 +138,9 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin { bool get isSubscribed => keywords?.containsKey(KeyWordIdentifierExtension.unsubscribeMail) == true; + bool get hasNeedAction => + keywords?.containsKey(KeyWordIdentifierExtension.needActionMail) == true; + bool get isAnsweredAndForwarded => isAnswered && isForwarded; bool get withAttachments => hasAttachment == true; diff --git a/model/lib/extensions/keyword_identifier_extension.dart b/model/lib/extensions/keyword_identifier_extension.dart index 4f0bcfeec..fc2daa1ab 100644 --- a/model/lib/extensions/keyword_identifier_extension.dart +++ b/model/lib/extensions/keyword_identifier_extension.dart @@ -5,6 +5,7 @@ import 'package:model/email/read_actions.dart'; extension KeyWordIdentifierExtension on KeyWordIdentifier { static final unsubscribeMail = KeyWordIdentifier('\$unsubscribe'); + static final needActionMail = KeyWordIdentifier('\$need-action'); String generatePath() { return '${PatchObject.keywordsProperty}/$value';