diff --git a/lib/features/mailbox/presentation/action/mailbox_ui_action.dart b/lib/features/mailbox/presentation/action/mailbox_ui_action.dart index fe02896e6..fa0ef1787 100644 --- a/lib/features/mailbox/presentation/action/mailbox_ui_action.dart +++ b/lib/features/mailbox/presentation/action/mailbox_ui_action.dart @@ -35,4 +35,8 @@ class OpenMailboxAction extends MailboxUIAction { class SystemBackToInboxAction extends MailboxUIAction {} -class RefreshAllMailboxAction extends MailboxUIAction {} \ No newline at end of file +class RefreshAllMailboxAction extends MailboxUIAction {} + +class AutoCreateActionRequiredFolderMailboxAction extends MailboxUIAction {} + +class AutoRemoveActionRequiredFolderMailboxAction extends MailboxUIAction {} \ No newline at end of file diff --git a/lib/features/mailbox/presentation/extensions/handle_action_required_tab_extension.dart b/lib/features/mailbox/presentation/extensions/handle_action_required_tab_extension.dart index 80b3225fc..08b42fb8b 100644 --- a/lib/features/mailbox/presentation/extensions/handle_action_required_tab_extension.dart +++ b/lib/features/mailbox/presentation/extensions/handle_action_required_tab_extension.dart @@ -42,4 +42,24 @@ extension HandleActionRequiredTabExtension on BaseMailboxController { bool _allMailboxesContains(MailboxId id) { return allMailboxes.any((mailbox) => mailbox.id == id); } + + void removeActionRequiredFolder() { + final folder = PresentationMailbox.actionRequiredFolder; + _removeFromDefaultMailboxTree(folder.id); + _removeFromAllMailboxes(folder.id); + } + + void _removeFromDefaultMailboxTree(MailboxId folderId) { + final root = defaultMailboxTree.value.root; + final children = List.from(root.childrenItems ?? []) + ..removeWhere((node) => node.item.id == folderId); + + defaultMailboxTree.value = MailboxTree( + root.copyWith(children: children), + ); + } + + void _removeFromAllMailboxes(MailboxId folderId) { + allMailboxes.removeWhere((mailbox) => mailbox.id == folderId); + } } diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index 26300b6f8..387971d07 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -319,6 +319,15 @@ class MailboxController extends BaseMailboxController } else if (action is RefreshAllMailboxAction) { refreshAllMailbox(); mailboxDashBoardController.clearMailboxUIAction(); + } else if (action is AutoCreateActionRequiredFolderMailboxAction) { + addActionRequiredFolder(); + mailboxDashBoardController.clearMailboxUIAction(); + } else if (action is AutoRemoveActionRequiredFolderMailboxAction) { + removeActionRequiredFolder(); + mailboxDashBoardController.clearMailboxUIAction(); + if (selectedMailbox?.isActionRequired == true) { + _switchBackToMailboxDefault(); + } } }); @@ -1564,7 +1573,7 @@ class MailboxController extends BaseMailboxController void autoCreateVirtualFolder() { addFavoriteFolderToMailboxList(); - if (mailboxDashBoardController.isAiCapabilitySupported) { + if (mailboxDashBoardController.isAINeedsActionEnabled) { addActionRequiredFolder(); } } diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index 1d24d1a4b..82ddb7271 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -120,6 +120,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dow import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart' as search; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/ai_scribe/setup_ai_needs_action_setting_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/ai_scribe/setup_cached_ai_scribe_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/cleanup_recent_search_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/delete_emails_in_mailbox_extension.dart'; @@ -303,6 +304,7 @@ class MailboxDashBoardController extends ReloadableController final isRecoveringDeletedMessage = RxBool(false); final localFileDraggableAppState = Rxn(); final isSenderImportantFlagEnabled = RxBool(true); + final isAINeedsActionSettingEnabled = RxBool(false); final isAppGridDialogDisplayed = RxBool(false); final isDrawerOpened = RxBool(false); final isContextMenuOpened = RxBool(false); @@ -512,6 +514,7 @@ class MailboxDashBoardController extends ReloadableController ); } else if (success is GetServerSettingSuccess) { isSenderImportantFlagEnabled.value = success.settingOption.isDisplaySenderPriority; + setupAINeedsActionSetting(options: success.settingOption); initializeAppLanguage(success); } else if (success is ClearMailboxSuccess) { clearMailboxSuccess(success); @@ -568,6 +571,7 @@ class MailboxDashBoardController extends ReloadableController _handleIdentityCache(); } else if (failure is GetServerSettingFailure) { isSenderImportantFlagEnabled.value = true; + setupAINeedsActionSetting(); } else if (failure is GetAllIdentitiesFailure) { _handleGetAllIdentitiesFailure(); } else if (failure is ClearMailboxFailure) { diff --git a/lib/features/mailbox_dashboard/presentation/extensions/ai_scribe/setup_ai_needs_action_setting_extension.dart b/lib/features/mailbox_dashboard/presentation/extensions/ai_scribe/setup_ai_needs_action_setting_extension.dart new file mode 100644 index 000000000..e5eaa961b --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/extensions/ai_scribe/setup_ai_needs_action_setting_extension.dart @@ -0,0 +1,21 @@ +import 'package:get/get.dart'; +import 'package:server_settings/server_settings/tmail_server_settings.dart'; +import 'package:server_settings/server_settings/tmail_server_settings_extension.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/action/mailbox_ui_action.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; + +extension SetupAiNeedsActionSettingExtension on MailboxDashBoardController { + void setupAINeedsActionSetting({TMailServerSettingOptions? options}) { + if (options != null) { + isAINeedsActionSettingEnabled.value = options.isAINeedsActionEnabled; + } else { + isAINeedsActionSettingEnabled.value = false; + } + + if (isAINeedsActionSettingEnabled.isTrue) { + dispatchMailboxUIAction(AutoCreateActionRequiredFolderMailboxAction()); + } else { + dispatchMailboxUIAction(AutoRemoveActionRequiredFolderMailboxAction()); + } + } +} 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 index 9c6b71da4..bada1a4e6 100644 --- a/lib/features/mailbox_dashboard/presentation/extensions/handle_ai_action_extension.dart +++ b/lib/features/mailbox_dashboard/presentation/extensions/handle_ai_action_extension.dart @@ -16,4 +16,7 @@ extension HandleAiActionExtension on MailboxDashBoardController { currentAccountId, ); } + + bool get isAINeedsActionEnabled => + isAINeedsActionSettingEnabled.value && isAiCapabilitySupported; } diff --git a/lib/features/manage_account/presentation/model/preferences_option_type.dart b/lib/features/manage_account/presentation/model/preferences_option_type.dart index adb29ff12..bd0273ed9 100644 --- a/lib/features/manage_account/presentation/model/preferences_option_type.dart +++ b/lib/features/manage_account/presentation/model/preferences_option_type.dart @@ -9,7 +9,8 @@ enum PreferencesOptionType { senderPriority(isLocal: false), thread(isLocal: true), spamReport(isLocal: true), - aiScribe(isLocal: true); + aiScribe(isLocal: true), + aiNeedsAction(isLocal: false); final bool isLocal; @@ -27,6 +28,8 @@ enum PreferencesOptionType { return appLocalizations.spamReports; case PreferencesOptionType.aiScribe: return appLocalizations.aiScribe; + case PreferencesOptionType.aiNeedsAction: + return appLocalizations.aiNeedsAction; } } @@ -42,6 +45,8 @@ enum PreferencesOptionType { return appLocalizations.spamReportsSettingExplanation; case PreferencesOptionType.aiScribe: return appLocalizations.aiScribeSettingExplanation; + case PreferencesOptionType.aiNeedsAction: + return appLocalizations.aiNeedsActionSettingExplanation; } } @@ -57,6 +62,8 @@ enum PreferencesOptionType { return appLocalizations.spamReportToggleDescription; case PreferencesOptionType.aiScribe: return appLocalizations.aiScribeToggleDescription; + case PreferencesOptionType.aiNeedsAction: + return appLocalizations.aiNeedsActionToggleDescription; } } @@ -75,6 +82,8 @@ enum PreferencesOptionType { return preferencesSetting.spamReportConfig.isEnabled; case PreferencesOptionType.aiScribe: return preferencesSetting.aiScribeConfig.isEnabled; + case PreferencesOptionType.aiNeedsAction: + return settingOption?.isAINeedsActionEnabled ?? false; } } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/preferences/preferences_controller.dart b/lib/features/manage_account/presentation/preferences/preferences_controller.dart index c12cb03ae..ae870fe62 100644 --- a/lib/features/manage_account/presentation/preferences/preferences_controller.dart +++ b/lib/features/manage_account/presentation/preferences/preferences_controller.dart @@ -50,7 +50,7 @@ class PreferencesController extends BaseController { final _manageAccountDashBoardController = Get.find(); - bool get isAIScribeAvailable { + bool get isAICapabilitySupported { return _manageAccountDashBoardController.isAICapabilitySupported; } @@ -183,6 +183,11 @@ class PreferencesController extends BaseController { displaySenderPriority: !isEnabled, ); break; + case PreferencesOptionType.aiNeedsAction: + newSettingOption = settingOption.value?.copyWith( + aiNeedsActionEnabled: !isEnabled, + ); + break; default: break; } diff --git a/lib/features/manage_account/presentation/preferences/preferences_view.dart b/lib/features/manage_account/presentation/preferences/preferences_view.dart index 26fb2d0c7..be0fa5567 100644 --- a/lib/features/manage_account/presentation/preferences/preferences_view.dart +++ b/lib/features/manage_account/presentation/preferences/preferences_view.dart @@ -68,17 +68,20 @@ class PreferencesView extends GetWidget with AppLoaderMix final availableSettingOptions = [ if (settingOption != null) ...PreferencesOptionType.values.where( - (optionType) => !optionType.isLocal, + (type) => + !type.isLocal && + type != PreferencesOptionType.aiNeedsAction, ), if (localSettingOption.configs.isNotEmpty) ...PreferencesOptionType.values.where( - (optionType) => optionType.isLocal, - ).where((optionType) { - if (optionType == PreferencesOptionType.aiScribe) { - return controller.isAIScribeAvailable && !PlatformInfo.isMobile; - } - return true; - }), + (type) => + type.isLocal && + (type != PreferencesOptionType.aiScribe || + controller.isAICapabilitySupported), + ), + if (settingOption != null && + controller.isAICapabilitySupported) + PreferencesOptionType.aiNeedsAction, ]; return Expanded( diff --git a/lib/features/search/email/presentation/search_email_view.dart b/lib/features/search/email/presentation/search_email_view.dart index 0472ff952..6fe7aefbd 100644 --- a/lib/features/search/email/presentation/search_email_view.dart +++ b/lib/features/search/email/presentation/search_email_view.dart @@ -724,8 +724,7 @@ class SearchEmailView extends GetWidget final isSenderImportantFlagEnabled = dashboardController.isSenderImportantFlagEnabled.value; - final isAiCapabilitySupported = - dashboardController.isAiCapabilitySupported; + final isAINeedsActionEnabled = dashboardController.isAINeedsActionEnabled; return EmailTileBuilder( presentationEmail: presentationEmail, @@ -734,7 +733,7 @@ class SearchEmailView extends GetWidget isShowingEmailContent: isShowingEmailContent, isSenderImportantFlagEnabled: isSenderImportantFlagEnabled, isSearchEmailRunning: true, - isAIEnabled: isAiCapabilitySupported, + isAINeedsActionEnabled: isAINeedsActionEnabled, padding: SearchEmailViewStyle.getPaddingSearchResultList( context, controller.responsiveUtils, diff --git a/lib/features/search/mailbox/presentation/search_mailbox_controller.dart b/lib/features/search/mailbox/presentation/search_mailbox_controller.dart index be5e1fae2..38c4c933c 100644 --- a/lib/features/search/mailbox/presentation/search_mailbox_controller.dart +++ b/lib/features/search/mailbox/presentation/search_mailbox_controller.dart @@ -902,7 +902,7 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa void autoCreateVirtualFolder() { addFavoriteFolderToMailboxList(); - if (dashboardController.isAiCapabilitySupported) { + if (dashboardController.isAINeedsActionEnabled) { addActionRequiredFolder(); } } diff --git a/lib/features/server_settings/domain/extensions/tmail_server_settings_extension.dart b/lib/features/server_settings/domain/extensions/tmail_server_settings_extension.dart index 8e99b2836..9e5457477 100644 --- a/lib/features/server_settings/domain/extensions/tmail_server_settings_extension.dart +++ b/lib/features/server_settings/domain/extensions/tmail_server_settings_extension.dart @@ -11,6 +11,7 @@ extension TmailServerSettingsExtension on TMailServerSettings { settings: TMailServerSettingOptions( alwaysReadReceipts: settings?.alwaysReadReceipts, displaySenderPriority: settings?.displaySenderPriority, + aiNeedsActionEnabled: settings?.aiNeedsActionEnabled, ), ); } diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 816741fd0..804286768 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -510,8 +510,7 @@ class ThreadView extends GetWidget final isSearchEmailRunning = controller.searchController.isSearchEmailRunning; - final isAiCapabilitySupported = - dashboardController.isAiCapabilitySupported; + final isAINeedsActionEnabled = dashboardController.isAINeedsActionEnabled; return EmailTileBuilder( key: Key('email_tile_builder_${presentationEmail.id?.asString}'), @@ -523,7 +522,7 @@ class ThreadView extends GetWidget isSearchEmailRunning: isSearchEmailRunning, isDrag: true, isSenderImportantFlagEnabled: isSenderImportantFlagEnabled, - isAIEnabled: isAiCapabilitySupported, + isAINeedsActionEnabled: isAINeedsActionEnabled, ); }); } @@ -607,8 +606,7 @@ class ThreadView extends GetWidget final isSearchEmailRunning = controller.searchController.isSearchEmailRunning; - final isAiCapabilitySupported = - dashboardController.isAiCapabilitySupported; + final isAINeedsActionEnabled = dashboardController.isAINeedsActionEnabled; return Dismissible( key: ValueKey(presentationEmail.id), @@ -632,7 +630,7 @@ class ThreadView extends GetWidget searchQuery: controller.searchQuery, mailboxContain: presentationEmail.mailboxContain, isSearchEmailRunning: isSearchEmailRunning, - isAIEnabled: isAiCapabilitySupported, + isAINeedsActionEnabled: isAINeedsActionEnabled, emailActionClick: _handleEmailActionClicked, onMoreActionClick: (email, position) => _handleEmailContextMenuAction(context, email, position), diff --git a/lib/features/thread/presentation/widgets/email_tile_builder.dart b/lib/features/thread/presentation/widgets/email_tile_builder.dart index d63f05928..8d1a068f8 100644 --- a/lib/features/thread/presentation/widgets/email_tile_builder.dart +++ b/lib/features/thread/presentation/widgets/email_tile_builder.dart @@ -20,7 +20,7 @@ class EmailTileBuilder extends StatelessWidget with BaseEmailItemTile { final bool isDrag; final bool isShowingEmailContent; final bool isSenderImportantFlagEnabled; - final bool isAIEnabled; + final bool isAINeedsActionEnabled; final OnPressEmailActionClick? emailActionClick; final OnMoreActionClick? onMoreActionClick; @@ -32,7 +32,7 @@ class EmailTileBuilder extends StatelessWidget with BaseEmailItemTile { this.searchQuery, this.isSearchEmailRunning = false, this.isSenderImportantFlagEnabled = true, - this.isAIEnabled = true, + this.isAINeedsActionEnabled = true, this.mailboxContain, this.padding, this.isDrag = false, @@ -141,5 +141,5 @@ class EmailTileBuilder extends StatelessWidget with BaseEmailItemTile { } bool get _shouldShowAIAction => - isAIEnabled && presentationEmail.hasNeedAction; + isAINeedsActionEnabled && 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 b92f8acea..97f48e6b3 100644 --- a/lib/features/thread/presentation/widgets/email_tile_web_builder.dart +++ b/lib/features/thread/presentation/widgets/email_tile_web_builder.dart @@ -27,7 +27,7 @@ class EmailTileBuilder extends StatefulWidget { final bool isDrag; final bool isShowingEmailContent; final bool isSenderImportantFlagEnabled; - final bool isAIEnabled; + final bool isAINeedsActionEnabled; final OnPressEmailActionClick? emailActionClick; final OnMoreActionClick? onMoreActionClick; @@ -42,7 +42,7 @@ class EmailTileBuilder extends StatefulWidget { this.mailboxContain, this.padding, this.isDrag = false, - this.isAIEnabled = false, + this.isAINeedsActionEnabled = false, this.emailActionClick, this.onMoreActionClick, }); @@ -423,7 +423,7 @@ class _EmailTileBuilderState extends State with BaseEmailItem } bool get _shouldShowAIAction => - widget.isAIEnabled && widget.presentationEmail.hasNeedAction; + widget.isAINeedsActionEnabled && widget.presentationEmail.hasNeedAction; @override void dispose() { diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index bc29c9021..754d2c872 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2025-12-17T00:04:15.124514", + "@@last_modified": "2025-12-22T16:54:22.593873", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -4862,6 +4862,24 @@ "placeholders_order": [], "placeholders": {} }, + "aiScribe": "AI Scribe", + "@aiScribe": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "aiScribeSettingExplanation": "Use AI to help write and improve your emails", + "@aiScribeSettingExplanation": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "aiScribeToggleDescription": "Enable AI Scribe", + "@aiScribeToggleDescription": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, "showMoreAttachmentButton": "Show +{count} more", "@showMoreAttachmentButton": { "type": "text", @@ -5130,12 +5148,6 @@ "placeholders_order": [], "placeholders": {} }, - "aiAssistant": "AI assistant", - "@aiAssistant": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, "actionRequired": "Action Required", "@actionRequired": { "type": "text", @@ -5153,5 +5165,23 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "aiNeedsAction": "AI needs-action", + "@aiNeedsAction": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "aiNeedsActionToggleDescription": "Enable AI needs-action", + "@aiNeedsActionToggleDescription": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "aiNeedsActionSettingExplanation": "Detect emails that need your attention using AI. When turned off, AI will not process any emails.", + "@aiNeedsActionSettingExplanation": { + "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 20fff982b..cb93c9630 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -5478,4 +5478,25 @@ class AppLocalizations { name: 'youDoNotHaveAnyActionRequiredEmails', ); } + + String get aiNeedsAction { + return Intl.message( + 'AI needs-action', + name: 'aiNeedsAction', + ); + } + + String get aiNeedsActionToggleDescription { + return Intl.message( + 'Enable AI needs-action', + name: 'aiNeedsActionToggleDescription', + ); + } + + String get aiNeedsActionSettingExplanation { + return Intl.message( + 'Detect emails that need your attention using AI. When turned off, AI will not process any emails.', + name: 'aiNeedsActionSettingExplanation', + ); + } } diff --git a/server_settings/lib/server_settings/tmail_server_settings.dart b/server_settings/lib/server_settings/tmail_server_settings.dart index fe339de39..d9c0a9e43 100644 --- a/server_settings/lib/server_settings/tmail_server_settings.dart +++ b/server_settings/lib/server_settings/tmail_server_settings.dart @@ -40,10 +40,14 @@ class TMailServerSettingOptions with EquatableMixin { @JsonKey(name: 'language') final String? language; + @JsonKey(name: 'ai.needs-action.enabled') + final bool? aiNeedsActionEnabled; + TMailServerSettingOptions({ this.alwaysReadReceipts, this.displaySenderPriority, this.language, + this.aiNeedsActionEnabled, }); factory TMailServerSettingOptions.fromJson(Map json) => @@ -55,11 +59,13 @@ class TMailServerSettingOptions with EquatableMixin { bool? alwaysReadReceipts, bool? displaySenderPriority, String? language, + bool? aiNeedsActionEnabled, }) { return TMailServerSettingOptions( alwaysReadReceipts: alwaysReadReceipts ?? this.alwaysReadReceipts, displaySenderPriority: displaySenderPriority ?? this.displaySenderPriority, language: language ?? this.language, + aiNeedsActionEnabled: aiNeedsActionEnabled ?? this.aiNeedsActionEnabled, ); } @@ -68,5 +74,6 @@ class TMailServerSettingOptions with EquatableMixin { alwaysReadReceipts, displaySenderPriority, language, + aiNeedsActionEnabled, ]; } diff --git a/server_settings/lib/server_settings/tmail_server_settings_extension.dart b/server_settings/lib/server_settings/tmail_server_settings_extension.dart index 8e53d9957..c89cddb8b 100644 --- a/server_settings/lib/server_settings/tmail_server_settings_extension.dart +++ b/server_settings/lib/server_settings/tmail_server_settings_extension.dart @@ -5,4 +5,6 @@ extension TmailServerSettingsExtension on TMailServerSettingOptions { bool get isDisplaySenderPriority => displaySenderPriority ?? true; bool get isAlwaysReadReceipts => alwaysReadReceipts ?? false; + + bool get isAINeedsActionEnabled => aiNeedsActionEnabled ?? false; } \ No newline at end of file