TF-4141 Add the AI needs-action option into the user settings.

This commit is contained in:
dab246
2025-12-23 01:13:17 +07:00
committed by Dat H. Pham
parent cb0471ecb7
commit 1661210e04
19 changed files with 171 additions and 35 deletions
@@ -35,4 +35,8 @@ class OpenMailboxAction extends MailboxUIAction {
class SystemBackToInboxAction extends MailboxUIAction {}
class RefreshAllMailboxAction extends MailboxUIAction {}
class RefreshAllMailboxAction extends MailboxUIAction {}
class AutoCreateActionRequiredFolderMailboxAction extends MailboxUIAction {}
class AutoRemoveActionRequiredFolderMailboxAction extends MailboxUIAction {}
@@ -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<MailboxNode>.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);
}
}
@@ -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();
}
}
@@ -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<DraggableAppState>();
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) {
@@ -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());
}
}
}
@@ -16,4 +16,7 @@ extension HandleAiActionExtension on MailboxDashBoardController {
currentAccountId,
);
}
bool get isAINeedsActionEnabled =>
isAINeedsActionSettingEnabled.value && isAiCapabilitySupported;
}
@@ -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;
}
}
}
@@ -50,7 +50,7 @@ class PreferencesController extends BaseController {
final _manageAccountDashBoardController = Get.find<ManageAccountDashBoardController>();
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;
}
@@ -68,17 +68,20 @@ class PreferencesView extends GetWidget<PreferencesController> 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(
@@ -724,8 +724,7 @@ class SearchEmailView extends GetWidget<SearchEmailController>
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<SearchEmailController>
isShowingEmailContent: isShowingEmailContent,
isSenderImportantFlagEnabled: isSenderImportantFlagEnabled,
isSearchEmailRunning: true,
isAIEnabled: isAiCapabilitySupported,
isAINeedsActionEnabled: isAINeedsActionEnabled,
padding: SearchEmailViewStyle.getPaddingSearchResultList(
context,
controller.responsiveUtils,
@@ -902,7 +902,7 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa
void autoCreateVirtualFolder() {
addFavoriteFolderToMailboxList();
if (dashboardController.isAiCapabilitySupported) {
if (dashboardController.isAINeedsActionEnabled) {
addActionRequiredFolder();
}
}
@@ -11,6 +11,7 @@ extension TmailServerSettingsExtension on TMailServerSettings {
settings: TMailServerSettingOptions(
alwaysReadReceipts: settings?.alwaysReadReceipts,
displaySenderPriority: settings?.displaySenderPriority,
aiNeedsActionEnabled: settings?.aiNeedsActionEnabled,
),
);
}
@@ -510,8 +510,7 @@ class ThreadView extends GetWidget<ThreadController>
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<ThreadController>
isSearchEmailRunning: isSearchEmailRunning,
isDrag: true,
isSenderImportantFlagEnabled: isSenderImportantFlagEnabled,
isAIEnabled: isAiCapabilitySupported,
isAINeedsActionEnabled: isAINeedsActionEnabled,
);
});
}
@@ -607,8 +606,7 @@ class ThreadView extends GetWidget<ThreadController>
final isSearchEmailRunning =
controller.searchController.isSearchEmailRunning;
final isAiCapabilitySupported =
dashboardController.isAiCapabilitySupported;
final isAINeedsActionEnabled = dashboardController.isAINeedsActionEnabled;
return Dismissible(
key: ValueKey<EmailId?>(presentationEmail.id),
@@ -632,7 +630,7 @@ class ThreadView extends GetWidget<ThreadController>
searchQuery: controller.searchQuery,
mailboxContain: presentationEmail.mailboxContain,
isSearchEmailRunning: isSearchEmailRunning,
isAIEnabled: isAiCapabilitySupported,
isAINeedsActionEnabled: isAINeedsActionEnabled,
emailActionClick: _handleEmailActionClicked,
onMoreActionClick: (email, position) =>
_handleEmailContextMenuAction(context, email, position),
@@ -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;
}
@@ -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<EmailTileBuilder> with BaseEmailItem
}
bool get _shouldShowAIAction =>
widget.isAIEnabled && widget.presentationEmail.hasNeedAction;
widget.isAINeedsActionEnabled && widget.presentationEmail.hasNeedAction;
@override
void dispose() {
+37 -7
View File
@@ -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": {}
}
}
@@ -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',
);
}
}
@@ -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<String, dynamic> 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,
];
}
@@ -5,4 +5,6 @@ extension TmailServerSettingsExtension on TMailServerSettingOptions {
bool get isDisplaySenderPriority => displaySenderPriority ?? true;
bool get isAlwaysReadReceipts => alwaysReadReceipts ?? false;
bool get isAINeedsActionEnabled => aiNeedsActionEnabled ?? false;
}