Skip to content

Conversation

@dab246
Copy link
Member

@dab246 dab246 commented Dec 12, 2025

Issue

#4195

Resolved

  • Web:
Screen.Recording.2025-12-12.at.10.online-video-cutter.com.mp4
  • Mobile:
demo-mobile.webm

Summary by CodeRabbit

Release Notes

  • New Features
    • Added "Label as" action to assign custom labels to emails
    • New label selection modal with intuitive interface for managing email tags
    • Enhanced popup menus with improved hover effects and submenu support
    • Added success and failure notifications for label operations

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 12, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This PR implements email labeling functionality and refactors dialog routing patterns. Key changes include converting the Label model's keyword field from String? to KeyWordIdentifier?, introducing label-related UI components and state management, adding an addLabelToEmail method chain across data layers, and establishing popup submenu support via PopupSubmenuController. DialogRouter is refactored from static methods to a singleton factory pattern with instance invocations throughout the codebase. The KeywordIdentifier extension gains a generateLabelActionPath method, and the map keywords extension becomes nullable-aware with new mutation helpers. Localization strings for label operations are added alongside a new EmailActionType.labelAs action.

Possibly related PRs

Suggested reviewers

  • tddang-linagora
  • hoangdat

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'TF-4195 Add a label to an email' directly summarizes the main change in the PR: implementing functionality to add labels to emails.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

This PR has been deployed to https://linagora.github.io/tmail-flutter/4204.

@chibenwa
Copy link
Member

@coderabbitai please review this work.

@coderabbitai
Copy link

coderabbitai bot commented Dec 12, 2025

@chibenwa: I'll review the changes in this PR. Let me analyze the implementation of the "Add a label to an email" feature.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
labels/test/method/get/get_label_method_test.dart (1)

209-230: Test description/type mismatch (“DioException” vs DioError).
The test name says “should throw DioException” but the assertion expects DioError. Either rename the test or align the expectation with the project’s Dio error type conventions (avoiding a piecemeal migration). Based on learnings, don’t switch the whole file to DioException unless there’s a repo-wide decision.

lib/features/base/widget/popup_menu/popup_menu_action_group_widget.dart (1)

7-52: Ensure submenu overlay is always dismissed (not just on “hover other item”)
Right now the submenu is hidden on onHoverOtherItem, but it may remain visible if the popup menu closes via click selection or outside-tap/escape. Consider always hiding in a finally around openPopupMenuAction(...), and also before Navigator.pop(...).

 class PopupMenuActionGroupWidget with PopupContextMenuActionMixin {
@@
   Future<void> show(
     BuildContext context,
     RelativeRect position,
   ) async {
@@
-    return openPopupMenuAction(context, position, popupMenuItems);
+    try {
+      await openPopupMenuAction(context, position, popupMenuItems);
+    } finally {
+      submenuController?.hide();
+    }
   }
@@
                 child: PopupMenuItemActionWidget(
                   menuAction: menuAction,
                   menuActionClick: (menuAction) {
+                    submenuController?.hide();
                     Navigator.pop(context);
                     onActionSelected(menuAction);
                   },

Also applies to: 66-83

lib/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart (1)

11-25: Potential no-op when adding a label if presentationEmail.keywords is null. The _updateKeyword() method uses safe navigation (presentationEmail.keywords?[keyword] = true), which silently fails if keywords is null. Additionally, isLabelAdded defaults to false, making it easy to accidentally remove a label instead of adding it when passing labelKeyword without explicitly setting isLabelAdded=true.

Consider ensuring keywords is initialized before mutation, or adopt the copyWith pattern already used elsewhere in the codebase (see presentation_email_extension.dart line 162) to replace the email with updated keywords rather than mutating in-place.

lib/main/routes/dialog_router.dart (1)

26-49: Gate isRuleFilterDialogOpened consistently (avoid stale true on non-web).
_bindingDI() sets isRuleFilterDialogOpened to true for AppRoutes.rulesFilterCreator, but pushGeneralDialog() only resets it on web. If any non-web code reads this flag, it can remain stuck true.

   case AppRoutes.rulesFilterCreator:
-    isRuleFilterDialogOpened.value = true;
+    if (PlatformInfo.isWeb) {
+      isRuleFilterDialogOpened.value = true;
+    }
     RulesFilterCreatorBindings().dependencies();
     break;

Also applies to: 56-79

lib/features/email/presentation/controller/single_email_controller.dart (1)

222-274: Success/failure routing for label state looks fine; logging may be noisy.
If these logs are expected in production, OK; otherwise consider reducing to debug-only.

🧹 Nitpick comments (20)
model/lib/extensions/keyword_identifier_extension.dart (4)

9-14: Nice consolidation; clarify the “null means remove” contract for keyword patches

generatePath() + _boolPatch() removes a lot of duplication. One thing to make explicit (doc comment) is that passing null is intended to unset/remove the keyword in JMAP patch semantics (vs writing false). That assumption is load-bearing for generateReadActionPath / generateMarkStarActionPath.


16-19: Simplify boolean-to-null logic for readability

return _boolPatch(isRead ? true : null); can be reduced to return _boolPatch(isRead ? true : null)return _boolPatch(isRead ? true : null) is already short, but you can also avoid the ternary by inlining: return _boolPatch(isRead ? true : null);return _boolPatch(isRead ? true : null); (or return _boolPatch(isRead ? true : null);). Concretely, I’d suggest return _boolPatch(isRead ? true : null);return _boolPatch(isRead ? true : null); is fine; if you want an actual improvement: return _boolPatch(isRead ? true : null);return _boolPatch(isRead ? true : null); isn’t one. Better: just inline the comparison: return _boolPatch(action == ReadActions.markAsRead ? true : null);.


21-24: Same simplification opportunity as read-action path

Consider inlining isStar similarly to keep these generators uniform and compact.


11-14: Proposed tiny cleanup diff (optional)

 PatchObject generateReadActionPath(ReadActions action) {
-  final isRead = action == ReadActions.markAsRead;
-  return _boolPatch(isRead ? true : null);
+  return _boolPatch(action == ReadActions.markAsRead ? true : null);
 }

 PatchObject generateMarkStarActionPath(MarkStarAction action) {
-  final isStar = action == MarkStarAction.markStar;
-  return _boolPatch(isStar ? true : null);
+  return _boolPatch(action == MarkStarAction.markStar ? true : null);
 }

Also applies to: 16-24

lib/features/labels/domain/exceptions/label_exceptions.dart (1)

1-1: Add a message/toString for debuggability.
Right now logs will just show Instance of 'LabelKeywordIsNull'. Consider @override String toString() => 'LabelKeywordIsNull: ...';.

labels/lib/extensions/label_extension.dart (1)

30-41: copyWith can’t explicitly clear keyword to null.
If “unset keyword” is a valid operation, consider a wrapper (e.g., Optional<KeyWordIdentifier?>) or a bool clearKeyword = false flag; current keyword ?? this.keyword only supports “keep existing” vs “set non-null”.

lib/features/email/presentation/bindings/email_interactor_bindings.dart (1)

19-19: Confirm DI registration order for EmailRepository before AddALabelToAnEmailInteractor.
AddALabelToAnEmailInteractor is created with Get.find<EmailRepository>(); if bindingsInteractor() can run before bindingsRepository(), this will throw at runtime. If the base InteractorsBindings.dependencies() guarantees repository bindings first, then this is fine.

Optional: consider typing the registration for consistency/readability:

-    Get.lazyPut(
-      () => AddALabelToAnEmailInteractor(Get.find<EmailRepository>()),
-    );
+    Get.lazyPut<AddALabelToAnEmailInteractor>(
+      () => AddALabelToAnEmailInteractor(Get.find<EmailRepository>()),
+    );

Also applies to: 121-123

lib/features/mailbox/presentation/model/popup_menu_item_mailbox_action.dart (1)

29-32: hoverIcon override: verify UI semantics + base contract.
Returning icThumbsUp for all mailbox actions on hover might be surprising UX (vs “same icon”, highlight, etc.). Also ensure PopupMenuItemActionRequiredIcon expects/uses hoverIcon (otherwise dead code).

lib/features/composer/presentation/composer_view_web.dart (1)

56-62: Cache DialogRouter() once inside the Obx to avoid double instantiation/read skew.

Even if it’s a factory singleton, prefer a single local reference for clarity and to guarantee consistent state reads:

 final iframeOverlay = Obx(() {
+  final dialogRouter = DialogRouter();
   bool isOverlayEnabled = controller.mailboxDashBoardController.isDisplayedOverlayViewOnIFrame ||
       MessageDialogActionManager().isDialogOpened ||
       EmailActionReactor.isDialogOpened ||
       ColorDialogPicker().isOpened.isTrue ||
-      DialogRouter().isRuleFilterDialogOpened.isTrue ||
-      DialogRouter().isDialogOpened;
+      dialogRouter.isRuleFilterDialogOpened.isTrue ||
+      dialogRouter.isDialogOpened;
lib/features/manage_account/presentation/identities/widgets/signature_builder.dart (1)

31-35: Same here: cache DialogRouter() once in Obx.

 final iframeOverlay = Obx(() {
-  if (MessageDialogActionManager().isDialogOpened ||
-      DialogRouter().isDialogOpened ||
-      DialogRouter().isRuleFilterDialogOpened.isTrue) {
+  final dialogRouter = DialogRouter();
+  if (MessageDialogActionManager().isDialogOpened ||
+      dialogRouter.isDialogOpened ||
+      dialogRouter.isRuleFilterDialogOpened.isTrue) {
lib/features/email/data/datasource/email_datasource.dart (1)

206-213: Consider returning a result (e.g., success + SetErrors) instead of void for label mutation.

Most other mutating email ops return { emailIdsSuccess, mapErrors }; having this return void makes partial failures harder to surface consistently. If the underlying JMAP call can return per-id errors, consider aligning the signature.

lib/features/email/data/repository/email_repository_impl.dart (1)

484-495: Consider updating the cache layer for consistency.

Unlike other email operations (markAsRead, markAsStar, moveToMailbox), this method only updates the network datasource without propagating the change to the hiveCache datasource. This could lead to stale cached data. Consider whether the cache should also be updated to reflect the label change, similar to the pattern used in lines 115-134 for markAsRead.

If cache updates are needed, apply a pattern similar to:

 @override
 Future<void> addLabelToEmail(
   AccountId accountId,
   EmailId emailId,
   KeyWordIdentifier labelKeyword,
 ) {
-   return emailDataSource[DataSourceType.network]!.addLabelToEmail(
+   return Future.value().then((_) async {
+     await emailDataSource[DataSourceType.network]!.addLabelToEmail(
       accountId,
       emailId,
       labelKeyword,
     );
+     try {
+       await emailDataSource[DataSourceType.hiveCache]!.addLabelToEmail(
+         accountId,
+         emailId,
+         labelKeyword,
+       );
+     } catch (e) {
+       logError('EmailRepositoryImpl::addLabelToEmail:exception $e');
+     }
+   });
 }
lib/features/labels/presentation/widgets/label_item_context_menu.dart (1)

1-62: Make text color/theme adaptive (avoid hard-coded Colors.black)
Using Colors.black will likely break dark theme or themed popups. Prefer Theme.of(context).colorScheme.onSurface (or a ThemeUtils variant that derives from theme).

-                  style: ThemeUtils.textStyleBodyBody3(color: Colors.black),
+                  style: ThemeUtils.textStyleBodyBody3(
+                    color: Theme.of(context).colorScheme.onSurface,
+                  ),
lib/features/email/domain/usecases/add_a_label_to_an_email_interactor.dart (1)

15-38: Capture stack trace on failures to preserve debuggability
Catching only e loses stack context; most of your failure types benefit from retaining it (even if you just log it).

-    } catch (e) {
+    } catch (e, s) {
       yield Left(AddALabelToAnEmailFailure(
         exception: e,
         labelDisplay: labelDisplay,
       ));
+      // Optional: logError('AddALabelToAnEmailInteractor::execute', e, s);
     }
lib/features/email/presentation/extensions/email_loaded_extension.dart (1)

8-18: Prefer non-null local var to avoid nullable chaining after the guard
After emailCurrent == null is ruled out, emailCurrent?.… makes the code harder to reason about and can mask unexpected nulls.

   EmailLoaded addEmailKeyword({
@@
-    final newKeyword = emailCurrent?.keywords?.withKeyword(keyword);
-    final updatedEmail = emailCurrent?.copyWith(keywords: newKeyword);
+    final current = emailCurrent!;
+    final newKeyword = current.keywords?.withKeyword(keyword);
+    final updatedEmail = current.copyWith(keywords: newKeyword);
     return copyWith(emailCurrent: updatedEmail);
   }
@@
-    final newKeyword = emailCurrent?.keywords?.withoutKeyword(keyword);
-    final updatedEmail = emailCurrent?.copyWith(keywords: newKeyword);
+    final current = emailCurrent!;
+    final newKeyword = current.keywords?.withoutKeyword(keyword);
+    final updatedEmail = current.copyWith(keywords: newKeyword);
     return copyWith(emailCurrent: updatedEmail);
   }

Also applies to: 20-30

lib/features/labels/presentation/widgets/add_label_to_email_modal.dart (1)

15-19: Tighten callback typing + ensure GetX dependency availability for ImagePaths. Prefer typedef OnAddLabelToEmailCallback = void Function(EmailId emailId, Label label, bool isSelected); (type-safety) and please confirm ImagePaths is always registered before this modal is built (since Get.find<ImagePaths>() will throw).

Proposed diff:

-typedef OnAddLabelToEmailCallback = Function(
+typedef OnAddLabelToEmailCallback = void Function(
   EmailId emailId,
   Label label,
   bool isSelected,
 );

Also applies to: 40-41, 134-142

lib/features/thread/domain/extensions/presentation_email_map_extension.dart (1)

6-29: LGTM! Clean extension implementation.

The immutable pattern returning a new map is appropriate. The logic correctly handles null emails and non-matching IDs.

One optional optimization consideration: iterating the entire map with .map() is O(n) when you only need to update a single key. If performance becomes a concern with large email maps, you could optimize by directly copying and updating:

Map<EmailId, PresentationEmail?> addEmailKeywordById({
  required EmailId emailId,
  required KeyWordIdentifier keyword,
}) {
  final newMap = Map<EmailId, PresentationEmail?>.from(this);
  final email = newMap[emailId];
  if (email != null) {
    newMap[emailId] = email.addKeyword(keyword);
  }
  return newMap;
}

This is optional given typical email list sizes.

lib/features/email/presentation/controller/single_email_controller.dart (1)

821-907: Guard labelAs action by feature availability (defensive).
Right now labelAs will try to open the modal even if labels are disabled (or mis-triggered via shortcut/context).

       case EmailActionType.labelAs:
-        openAddLabelToEmailDialogModal(presentationEmail);
+        if (!isLabelFeatureEnabled) return;
+        openAddLabelToEmailDialogModal(presentationEmail);
         break;
lib/features/email/presentation/extensions/handle_label_for_email_extension.dart (1)

24-40: Nice: feature gating + DialogRouter modal usage are straightforward; add a defensive gate too.
openAddLabelToEmailDialogModal should probably early-return when !isLabelFeatureEnabled (in case the action is invoked unexpectedly).

   Future<void> openAddLabelToEmailDialogModal(PresentationEmail email) async {
+    if (!isLabelFeatureEnabled) return;
     final labels = mailboxDashBoardController.labelController.labels;

Also applies to: 150-167

lib/features/base/widget/popup_menu/popup_menu_item_action_widget.dart (1)

29-41: Consider allocating _itemKey/_hoverItemNotifier only for the submenu item.
Right now every PopupMenuItemEmailAction pays the cost, even if it never shows a submenu.

   void initState() {
     super.initState();
-    if (widget.menuAction is PopupMenuItemEmailAction) {
+    if (widget.menuAction is PopupMenuItemEmailAction &&
+        widget.menuAction.action == EmailActionType.labelAs) {
       _itemKey = GlobalKey();
       _hoverItemNotifier = ValueNotifier(false);
     }
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 896f349 and 06441ad.

⛔ Files ignored due to path filters (1)
  • assets/images/ic_thumbs_up.svg is excluded by !**/*.svg
📒 Files selected for processing (66)
  • core/lib/presentation/resources/image_paths.dart (1 hunks)
  • labels/lib/converter/keyword_identifier_nullable_converter.dart (1 hunks)
  • labels/lib/extensions/label_extension.dart (2 hunks)
  • labels/lib/model/label.dart (2 hunks)
  • labels/test/method/get/get_label_method_test.dart (2 hunks)
  • lib/features/base/base_mailbox_controller.dart (2 hunks)
  • lib/features/base/model/popup_menu_item_action.dart (5 hunks)
  • lib/features/base/widget/popup_menu/popup_menu_action_group_widget.dart (3 hunks)
  • lib/features/base/widget/popup_menu/popup_menu_item_action_widget.dart (6 hunks)
  • lib/features/base/widget/popup_menu/popup_submenu_controller.dart (1 hunks)
  • lib/features/composer/presentation/composer_view_web.dart (1 hunks)
  • lib/features/composer/presentation/extensions/email_action_type_extension.dart (2 hunks)
  • lib/features/email/data/datasource/email_datasource.dart (2 hunks)
  • lib/features/email/data/datasource_impl/email_datasource_impl.dart (2 hunks)
  • lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart (1 hunks)
  • lib/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart (2 hunks)
  • lib/features/email/data/datasource_impl/email_session_storage_datasource_impl.dart (2 hunks)
  • lib/features/email/data/network/email_api.dart (1 hunks)
  • lib/features/email/data/repository/email_repository_impl.dart (2 hunks)
  • lib/features/email/domain/repository/email_repository.dart (2 hunks)
  • lib/features/email/domain/state/add_a_label_to_an_email_state.dart (1 hunks)
  • lib/features/email/domain/usecases/add_a_label_to_an_email_interactor.dart (1 hunks)
  • lib/features/email/presentation/bindings/email_bindings.dart (2 hunks)
  • lib/features/email/presentation/bindings/email_interactor_bindings.dart (2 hunks)
  • lib/features/email/presentation/controller/single_email_controller.dart (9 hunks)
  • lib/features/email/presentation/email_view.dart (4 hunks)
  • lib/features/email/presentation/extensions/email_loaded_extension.dart (1 hunks)
  • lib/features/email/presentation/extensions/handle_label_for_email_extension.dart (1 hunks)
  • lib/features/email/presentation/extensions/presentation_email_extension.dart (2 hunks)
  • lib/features/email/presentation/model/email_loaded.dart (1 hunks)
  • lib/features/email/presentation/model/popup_menu_item_email_action.dart (2 hunks)
  • lib/features/email/presentation/utils/email_action_reactor/email_action_reactor.dart (8 hunks)
  • lib/features/labels/domain/exceptions/label_exceptions.dart (1 hunks)
  • lib/features/labels/presentation/label_controller.dart (2 hunks)
  • lib/features/labels/presentation/widgets/add_label_to_email_modal.dart (1 hunks)
  • lib/features/labels/presentation/widgets/label_item_context_menu.dart (1 hunks)
  • lib/features/labels/presentation/widgets/label_list_context_menu.dart (1 hunks)
  • lib/features/mailbox/presentation/mailbox_controller.dart (1 hunks)
  • lib/features/mailbox/presentation/model/popup_menu_item_mailbox_action.dart (1 hunks)
  • lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart (1 hunks)
  • lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart (5 hunks)
  • lib/features/mailbox_dashboard/presentation/extensions/handle_create_new_rule_filter.dart (1 hunks)
  • lib/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart (2 hunks)
  • lib/features/mailbox_dashboard/presentation/extensions/verify_display_overlay_view_on_iframe_extension.dart (1 hunks)
  • lib/features/mailbox_dashboard/presentation/model/profile_setting/popup_menu_item_profile_setting_type_action.dart (1 hunks)
  • lib/features/manage_account/presentation/email_rules/email_rules_controller.dart (2 hunks)
  • lib/features/manage_account/presentation/identities/identities_controller.dart (3 hunks)
  • lib/features/manage_account/presentation/identities/widgets/signature_builder.dart (1 hunks)
  • lib/features/rules_filter_creator/presentation/rules_filter_creator_controller.dart (1 hunks)
  • lib/features/search/email/presentation/search_email_controller.dart (1 hunks)
  • lib/features/search/mailbox/presentation/search_mailbox_controller.dart (1 hunks)
  • lib/features/thread/data/extensions/map_keywords_extension.dart (1 hunks)
  • lib/features/thread/domain/extensions/presentation_email_map_extension.dart (1 hunks)
  • lib/features/thread/presentation/mixin/email_action_controller.dart (1 hunks)
  • lib/features/thread_detail/domain/extensions/list_email_in_thread_detail_info_extension.dart (2 hunks)
  • lib/features/thread_detail/presentation/extension/on_thread_detail_action_click.dart (1 hunks)
  • lib/l10n/intl_messages.arb (2 hunks)
  • lib/main/localizations/app_localizations.dart (1 hunks)
  • lib/main/routes/dialog_router.dart (5 hunks)
  • lib/main/utils/toast_manager.dart (4 hunks)
  • model/lib/email/email_action_type.dart (1 hunks)
  • model/lib/extensions/keyword_identifier_extension.dart (1 hunks)
  • test/features/composer/presentation/composer_controller_test.dart (0 hunks)
  • test/features/email/presentation/controller/single_email_controller_test.dart (4 hunks)
  • test/features/thread/presentation/extensions/map_keywords_extension_test.dart (1 hunks)
  • test/model/lib/extensions/get_label_list_in_keyword_email_test.dart (5 hunks)
💤 Files with no reviewable changes (1)
  • test/features/composer/presentation/composer_controller_test.dart
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-09T09:36:45.349Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4194
File: lib/features/manage_account/presentation/manage_account_dashboard_controller.dart:174-176
Timestamp: 2025-12-09T09:36:45.349Z
Learning: In Dart/Flutter projects using GetX, do not wrap getBinding<T>() calls in try/catch since they return null when not found. Only wrap Get.find<T>() calls in try/catch because they throw if a dependency is unavailable. When a binding is optional, consider checking Get.isRegistered<T>() or handling the null/exception path gracefully instead of blindly catching, and document the expectation for failure modes where a dependency may not be registered.

Applied to files:

  • lib/features/mailbox_dashboard/presentation/model/profile_setting/popup_menu_item_profile_setting_type_action.dart
  • lib/features/email/data/datasource/email_datasource.dart
  • lib/features/composer/presentation/extensions/email_action_type_extension.dart
  • lib/features/email/data/network/email_api.dart
  • labels/lib/extensions/label_extension.dart
  • model/lib/email/email_action_type.dart
  • lib/features/labels/domain/exceptions/label_exceptions.dart
  • lib/features/email/data/datasource_impl/email_session_storage_datasource_impl.dart
  • core/lib/presentation/resources/image_paths.dart
  • lib/features/email/presentation/extensions/email_loaded_extension.dart
  • lib/features/email/presentation/bindings/email_bindings.dart
  • lib/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart
  • lib/features/rules_filter_creator/presentation/rules_filter_creator_controller.dart
  • labels/lib/converter/keyword_identifier_nullable_converter.dart
  • lib/features/mailbox/presentation/mailbox_controller.dart
  • lib/features/base/widget/popup_menu/popup_menu_action_group_widget.dart
  • test/features/thread/presentation/extensions/map_keywords_extension_test.dart
  • lib/features/base/widget/popup_menu/popup_submenu_controller.dart
  • lib/features/labels/presentation/widgets/label_list_context_menu.dart
  • lib/main/localizations/app_localizations.dart
  • lib/features/mailbox/presentation/model/popup_menu_item_mailbox_action.dart
  • lib/features/thread/data/extensions/map_keywords_extension.dart
  • lib/features/search/mailbox/presentation/search_mailbox_controller.dart
  • lib/features/email/data/repository/email_repository_impl.dart
  • lib/features/manage_account/presentation/email_rules/email_rules_controller.dart
  • lib/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart
  • lib/features/labels/presentation/widgets/add_label_to_email_modal.dart
  • lib/features/email/data/datasource_impl/email_datasource_impl.dart
  • lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart
  • lib/features/thread_detail/domain/extensions/list_email_in_thread_detail_info_extension.dart
  • lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart
  • lib/features/email/domain/state/add_a_label_to_an_email_state.dart
  • lib/features/composer/presentation/composer_view_web.dart
  • lib/features/email/domain/usecases/add_a_label_to_an_email_interactor.dart
  • lib/features/thread_detail/presentation/extension/on_thread_detail_action_click.dart
  • lib/features/base/base_mailbox_controller.dart
  • lib/features/email/domain/repository/email_repository.dart
  • labels/test/method/get/get_label_method_test.dart
  • lib/features/labels/presentation/widgets/label_item_context_menu.dart
  • lib/features/email/presentation/model/popup_menu_item_email_action.dart
  • lib/features/mailbox_dashboard/presentation/extensions/verify_display_overlay_view_on_iframe_extension.dart
  • model/lib/extensions/keyword_identifier_extension.dart
  • lib/features/thread/presentation/mixin/email_action_controller.dart
  • test/model/lib/extensions/get_label_list_in_keyword_email_test.dart
  • lib/features/labels/presentation/label_controller.dart
  • lib/features/thread/domain/extensions/presentation_email_map_extension.dart
  • lib/features/email/presentation/bindings/email_interactor_bindings.dart
  • lib/features/search/email/presentation/search_email_controller.dart
  • lib/features/manage_account/presentation/identities/widgets/signature_builder.dart
  • lib/features/email/presentation/utils/email_action_reactor/email_action_reactor.dart
  • lib/features/base/widget/popup_menu/popup_menu_item_action_widget.dart
  • test/features/email/presentation/controller/single_email_controller_test.dart
  • lib/features/mailbox_dashboard/presentation/extensions/handle_create_new_rule_filter.dart
  • lib/features/base/model/popup_menu_item_action.dart
  • lib/features/email/presentation/model/email_loaded.dart
  • labels/lib/model/label.dart
  • lib/features/email/presentation/extensions/handle_label_for_email_extension.dart
  • lib/features/manage_account/presentation/identities/identities_controller.dart
  • lib/features/email/presentation/controller/single_email_controller.dart
  • lib/features/email/presentation/extensions/presentation_email_extension.dart
  • lib/main/routes/dialog_router.dart
  • lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart
  • lib/main/utils/toast_manager.dart
  • lib/features/email/presentation/email_view.dart
📚 Learning: 2025-12-09T12:47:45.861Z
Learnt from: zatteo
Repo: linagora/tmail-flutter PR: 4196
File: scribe/lib/scribe/ai/data/datasource_impl/ai_datasource_impl.dart:34-35
Timestamp: 2025-12-09T12:47:45.861Z
Learning: In the tmail-flutter repository, avoid suggesting replacing DioError with DioException unless there is a codebase-wide migration. This pattern applies to all Dart files; when reviewing, only propose a DioError-safe change (e.g., compatible error handling or conversion) unless a global migration is in place. Ensure consistency of DioError usage across modules and flag any deviations for a repo-wide decision.

Applied to files:

  • lib/features/mailbox_dashboard/presentation/model/profile_setting/popup_menu_item_profile_setting_type_action.dart
  • lib/features/email/data/datasource/email_datasource.dart
  • lib/features/composer/presentation/extensions/email_action_type_extension.dart
  • lib/features/email/data/network/email_api.dart
  • labels/lib/extensions/label_extension.dart
  • model/lib/email/email_action_type.dart
  • lib/features/labels/domain/exceptions/label_exceptions.dart
  • lib/features/email/data/datasource_impl/email_session_storage_datasource_impl.dart
  • core/lib/presentation/resources/image_paths.dart
  • lib/features/email/presentation/extensions/email_loaded_extension.dart
  • lib/features/email/presentation/bindings/email_bindings.dart
  • lib/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart
  • lib/features/rules_filter_creator/presentation/rules_filter_creator_controller.dart
  • labels/lib/converter/keyword_identifier_nullable_converter.dart
  • lib/features/mailbox/presentation/mailbox_controller.dart
  • lib/features/base/widget/popup_menu/popup_menu_action_group_widget.dart
  • test/features/thread/presentation/extensions/map_keywords_extension_test.dart
  • lib/features/base/widget/popup_menu/popup_submenu_controller.dart
  • lib/features/labels/presentation/widgets/label_list_context_menu.dart
  • lib/main/localizations/app_localizations.dart
  • lib/features/mailbox/presentation/model/popup_menu_item_mailbox_action.dart
  • lib/features/thread/data/extensions/map_keywords_extension.dart
  • lib/features/search/mailbox/presentation/search_mailbox_controller.dart
  • lib/features/email/data/repository/email_repository_impl.dart
  • lib/features/manage_account/presentation/email_rules/email_rules_controller.dart
  • lib/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart
  • lib/features/labels/presentation/widgets/add_label_to_email_modal.dart
  • lib/features/email/data/datasource_impl/email_datasource_impl.dart
  • lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart
  • lib/features/thread_detail/domain/extensions/list_email_in_thread_detail_info_extension.dart
  • lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart
  • lib/features/email/domain/state/add_a_label_to_an_email_state.dart
  • lib/features/composer/presentation/composer_view_web.dart
  • lib/features/email/domain/usecases/add_a_label_to_an_email_interactor.dart
  • lib/features/thread_detail/presentation/extension/on_thread_detail_action_click.dart
  • lib/features/base/base_mailbox_controller.dart
  • lib/features/email/domain/repository/email_repository.dart
  • labels/test/method/get/get_label_method_test.dart
  • lib/features/labels/presentation/widgets/label_item_context_menu.dart
  • lib/features/email/presentation/model/popup_menu_item_email_action.dart
  • lib/features/mailbox_dashboard/presentation/extensions/verify_display_overlay_view_on_iframe_extension.dart
  • model/lib/extensions/keyword_identifier_extension.dart
  • lib/features/thread/presentation/mixin/email_action_controller.dart
  • test/model/lib/extensions/get_label_list_in_keyword_email_test.dart
  • lib/features/labels/presentation/label_controller.dart
  • lib/features/thread/domain/extensions/presentation_email_map_extension.dart
  • lib/features/email/presentation/bindings/email_interactor_bindings.dart
  • lib/features/search/email/presentation/search_email_controller.dart
  • lib/features/manage_account/presentation/identities/widgets/signature_builder.dart
  • lib/features/email/presentation/utils/email_action_reactor/email_action_reactor.dart
  • lib/features/base/widget/popup_menu/popup_menu_item_action_widget.dart
  • test/features/email/presentation/controller/single_email_controller_test.dart
  • lib/features/mailbox_dashboard/presentation/extensions/handle_create_new_rule_filter.dart
  • lib/features/base/model/popup_menu_item_action.dart
  • lib/features/email/presentation/model/email_loaded.dart
  • labels/lib/model/label.dart
  • lib/features/email/presentation/extensions/handle_label_for_email_extension.dart
  • lib/features/manage_account/presentation/identities/identities_controller.dart
  • lib/features/email/presentation/controller/single_email_controller.dart
  • lib/features/email/presentation/extensions/presentation_email_extension.dart
  • lib/main/routes/dialog_router.dart
  • lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart
  • lib/main/utils/toast_manager.dart
  • lib/features/email/presentation/email_view.dart
📚 Learning: 2025-12-12T04:54:05.432Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4191
File: lib/features/email/presentation/extensions/handle_email_action_extension.dart:37-80
Timestamp: 2025-12-12T04:54:05.432Z
Learning: In lib/features/email/presentation/extensions/handle_email_action_extension.dart, the mailboxDashBoardController.selectedEmail should only be synchronized when isMobileThreadDisabled is true. This is intentional behavior and should not be changed to update selectedEmail in non-mobile or thread-enabled contexts.

Applied to files:

  • lib/features/composer/presentation/extensions/email_action_type_extension.dart
  • lib/features/email/presentation/extensions/email_loaded_extension.dart
  • lib/features/email/presentation/bindings/email_bindings.dart
  • lib/features/mailbox/presentation/mailbox_controller.dart
  • lib/features/labels/presentation/widgets/label_list_context_menu.dart
  • lib/features/mailbox/presentation/model/popup_menu_item_mailbox_action.dart
  • lib/features/search/mailbox/presentation/search_mailbox_controller.dart
  • lib/features/manage_account/presentation/email_rules/email_rules_controller.dart
  • lib/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart
  • lib/features/labels/presentation/widgets/add_label_to_email_modal.dart
  • lib/features/thread_detail/domain/extensions/list_email_in_thread_detail_info_extension.dart
  • lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart
  • lib/features/thread_detail/presentation/extension/on_thread_detail_action_click.dart
  • lib/features/email/presentation/model/popup_menu_item_email_action.dart
  • lib/features/mailbox_dashboard/presentation/extensions/verify_display_overlay_view_on_iframe_extension.dart
  • lib/features/thread/presentation/mixin/email_action_controller.dart
  • lib/features/thread/domain/extensions/presentation_email_map_extension.dart
  • lib/features/email/presentation/bindings/email_interactor_bindings.dart
  • lib/features/search/email/presentation/search_email_controller.dart
  • lib/features/email/presentation/utils/email_action_reactor/email_action_reactor.dart
  • test/features/email/presentation/controller/single_email_controller_test.dart
  • lib/features/mailbox_dashboard/presentation/extensions/handle_create_new_rule_filter.dart
  • lib/features/email/presentation/model/email_loaded.dart
  • lib/features/email/presentation/extensions/handle_label_for_email_extension.dart
  • lib/features/email/presentation/controller/single_email_controller.dart
  • lib/features/email/presentation/extensions/presentation_email_extension.dart
  • lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart
  • lib/main/utils/toast_manager.dart
  • lib/features/email/presentation/email_view.dart
📚 Learning: 2025-12-08T08:11:08.985Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4189
File: lib/features/login/presentation/extensions/handle_company_server_login_info_extension.dart:50-56
Timestamp: 2025-12-08T08:11:08.985Z
Learning: In the tmail-flutter codebase, prefer descriptive variable names that include full context rather than abbreviated names. For example, `removeLoginInfoInteractor` is preferred over shorter alternatives like `removeInteractor` or `interactor`, as it clearly conveys both the action (remove) and the specific data being operated on (loginInfo).

Applied to files:

  • lib/features/email/presentation/bindings/email_bindings.dart
  • lib/features/email/presentation/bindings/email_interactor_bindings.dart
  • test/features/email/presentation/controller/single_email_controller_test.dart
  • lib/features/email/presentation/controller/single_email_controller.dart
🪛 RuboCop (1.81.7)
lib/l10n/intl_messages.arb

[convention] 5249-5249: Put a comma after the last item of a multiline array.

(Style/TrailingCommaInArrayLiteral)


[convention] 5259-5259: Put a comma after the last item of a multiline array.

(Style/TrailingCommaInArrayLiteral)

🔇 Additional comments (54)
model/lib/extensions/keyword_identifier_extension.dart (1)

26-32: Action helpers look consistent; confirm “remove label” is handled elsewhere

The => _boolPatch(true) helpers (answered/forwarded/unsubscribe/label) look consistent. For labeling specifically: if the UI supports unlabeling, make sure there’s a corresponding path generator that unsets the keyword (likely via _boolPatch(null)) and that callers don’t misuse false for removal.

lib/features/mailbox_dashboard/presentation/model/profile_setting/popup_menu_item_profile_setting_type_action.dart (1)

29-33: hoverIcon override is consistent with the new hover-icon pattern.
Only note: double-check this icon choice matches profile-setting UX (a generic thumbs-up hover icon can be surprising outside “label” actions).

lib/features/mailbox_dashboard/presentation/extensions/handle_create_new_rule_filter.dart (1)

46-54: DialogRouter instance call looks correct; please smoke-test web route stacking.
The migration to DialogRouter().pushGeneralDialog(...) is fine, but it’s worth verifying the dialog still anchors to the expected navigator/overlay in web builds.

lib/features/email/domain/state/add_a_label_to_an_email_state.dart (1)

6-29: State objects look coherent and sufficiently descriptive for UI + error messaging.

lib/features/thread_detail/presentation/extension/on_thread_detail_action_click.dart (1)

213-219: DialogRouter instance migration is consistent; keep an eye on web return types.
The PresentationMailbox type-guard is good—please confirm the web dialog path still returns that object (not a serialized/map payload) after the router refactor.

core/lib/presentation/resources/image_paths.dart (1)

266-266: No action needed. The asset ic_thumbs_up.svg is properly included in the asset bundle via the assets: - assets/images/ configuration in pubspec.yaml, and the file exists at the expected location.

lib/features/mailbox_dashboard/presentation/extensions/verify_display_overlay_view_on_iframe_extension.dart (1)

6-13: The property labelController.isCreateNewLabelModalVisible does not exist in the codebase and was never part of the overlay contract.

The create-label modal is managed through DialogRouter().openDialogModal(), which tracks modal visibility via DialogRouter.isDialogOpened. However, the isDisplayedOverlayViewOnIFrame extension method does not include checks for DialogRouter().isDialogOpened. This is consistent with the codebase architecture: views like email_view.dart and composer_view_web.dart supplement the extension method with additional dialog checks (DialogRouter().isDialogOpened, MessageDialogActionManager().isDialogOpened, etc.) rather than including them in the extension itself. If the create-label modal should block iframe interactions in views using this extension, those views are responsible for adding DialogRouter().isDialogOpened to their iframe overlay conditions.

Likely an incorrect or invalid review comment.

lib/features/mailbox/presentation/mailbox_controller.dart (1)

874-881: DialogRouter instance call: ensure behavior parity with previous static usage on web.
If DialogRouter() is a factory/singleton, this is likely equivalent; just make sure no per-instance state was relied upon (e.g., navigator keys, stacked dialogs).

labels/test/method/get/get_label_method_test.dart (1)

6-7: Label keyword migration in tests looks consistent.
Switching Label.keyword to KeyWordIdentifier(...) aligns expectations with the new model type.

Also applies to: 43-54

lib/features/email/domain/repository/email_repository.dart (1)

14-15: addLabelToEmail signature: confirm why it doesn’t require Session.
Most repository network operations here accept Session; if labeling is also a network call, double-check this won’t constrain multi-session/multi-account flows or force hidden globals in lower layers.

Also applies to: 164-169

lib/features/manage_account/presentation/identities/identities_controller.dart (1)

232-235: DialogRouter instance call migration is consistent.
Looks like a mechanical refactor; just ensure DialogRouter’s factory/instance behavior matches the previous static usage across web dialogs.

Also applies to: 384-387, 482-485

lib/features/email/presentation/bindings/email_bindings.dart (1)

1-4: DI wiring for label interactor into SingleEmailController looks correct.
Since EmailInteractorBindings().dependencies() is called before Get.put(SingleEmailController(...)), the new Get.find<AddALabelToAnEmailInteractor>() should be available (assuming it’s registered there as added).

Also applies to: 20-31

lib/features/composer/presentation/extensions/email_action_type_extension.dart (1)

137-183: labelAs support in icon/title mapping is straightforward.
Assuming imagePaths.icTag and appLocalizations.labelAs are present everywhere, this looks good.

Also applies to: 185-232

lib/features/search/mailbox/presentation/search_mailbox_controller.dart (1)

825-828: DialogRouter instance migration looks safe here (web-only).

Web branch now uses DialogRouter().pushGeneralDialog(...); flow/typing checks remain intact.

lib/features/thread/presentation/mixin/email_action_controller.dart (1)

162-165: Web destination picker now uses instance-based DialogRouter; flow unchanged.

lib/features/base/base_mailbox_controller.dart (2)

391-394: DialogRouter instance migration (move mailbox) looks good.


660-666: DialogRouter instance migration (move folder content) looks good.

lib/features/email/data/datasource/email_datasource.dart (1)

14-15: Confirm keyword_identifier.dart / KeyWordIdentifier is stable in your pinned jmap_dart_client.

This is a public API surface change relying on an external package type; worth double-checking the exact type name/export in the repo’s dependency lock.

model/lib/email/email_action_type.dart (1)

2-9: No action required. The labelAs enum value is properly handled throughout the codebase in all appropriate contexts. All switches have default cases, and the value is explicitly managed where needed (individual email actions) while being intentionally omitted from thread-level and search-level handlers where it's not applicable.

lib/features/manage_account/presentation/email_rules/email_rules_controller.dart (1)

100-102: LGTM! DialogRouter refactoring applied consistently.

The migration from static to instance-based DialogRouter invocation is correct and aligns with the broader refactoring pattern across the codebase.

lib/features/email/data/datasource_impl/email_session_storage_datasource_impl.dart (1)

269-272: LGTM! Stub implementation is appropriate.

The UnimplementedError for addLabelToEmail in the session storage datasource is consistent with the pattern used for other unimplemented methods in this class, which delegates operations to other datasource implementations.

lib/features/email/presentation/model/email_loaded.dart (1)

20-32: LGTM! Standard copyWith implementation.

The copyWith method follows the typical Dart pattern for immutable data classes and will support the label-related updates mentioned in the PR summary.

lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart (1)

223-227: LGTM! DialogRouter refactoring applied correctly.

The instance-based DialogRouter invocation is consistent with the codebase-wide migration pattern.

lib/features/search/email/presentation/search_email_controller.dart (1)

707-709: LGTM! DialogRouter refactoring is consistent.

The change maintains functionality while adopting the instance-based pattern.

lib/features/rules_filter_creator/presentation/rules_filter_creator_controller.dart (1)

443-445: LGTM! DialogRouter migration complete.

The instance-based invocation is correctly applied.

test/features/email/presentation/controller/single_email_controller_test.dart (1)

30-30: LGTM! Test infrastructure updated correctly.

The new AddALabelToAnEmailInteractor dependency is properly mocked and wired into the test setup, supporting the label feature addition.

Also applies to: 77-77, 112-112, 180-180

lib/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart (2)

24-24: LGTM!

The KeyWordIdentifier import is correctly added to support the new labeling functionality.


355-358: LGTM!

The UnimplementedError is appropriate for the local storage datasource, as label operations require network interaction and are not persisted locally.

lib/features/email/presentation/model/popup_menu_item_email_action.dart (1)

19-19: LGTM!

The submenu parameter correctly extends the constructor to support the new popup submenu functionality.

lib/features/email/data/datasource_impl/email_datasource_impl.dart (2)

25-25: LGTM!

The KeyWordIdentifier import is correctly added to support the new label functionality.


557-566: LGTM!

The implementation correctly follows the established pattern for delegation to the EmailAPI layer with consistent error handling via the exception thrower.

labels/lib/converter/keyword_identifier_nullable_converter.dart (1)

1-14: LGTM!

The converter correctly handles bidirectional conversion between KeyWordIdentifier? and String? for JSON serialization, with proper null handling in both directions.

lib/features/email/data/repository/email_repository_impl.dart (1)

16-16: LGTM!

The KeyWordIdentifier import is correctly added.

test/model/lib/extensions/get_label_list_in_keyword_email_test.dart (1)

11-96: LGTM!

The test updates correctly reflect the API change from String to KeyWordIdentifier type, maintaining comprehensive test coverage while adapting to the new type-safe keyword handling.

test/features/thread/presentation/extensions/map_keywords_extension_test.dart (1)

1-109: LGTM!

Comprehensive test coverage for the MapKeywordsExtension methods. The tests correctly verify immutability, edge cases (empty maps, non-existing keywords), and core functionality for both withKeyword() and withoutKeyword() methods.

lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart (1)

1316-1318: Dialog routing invocation update looks consistent
Swapping to DialogRouter().pushGeneralDialog(...) in the web paths keeps the existing control-flow intact and aligns with the refactor described in the PR summary.

Also applies to: 2174-2177, 2197-2200, 2221-2228, 3248-3254

lib/main/utils/toast_manager.dart (1)

25-25: Label add success/failure toast wiring looks consistent with existing patterns.

Also applies to: 164-208, 275-279

lib/features/email/presentation/extensions/presentation_email_extension.dart (1)

2-2: Good migration to KeyWordIdentifier-based label detection + helper methods. Consider double-checking keywords.withKeyword(...) / keywords.withoutKeyword(...) semantics when keywords is null (and whether they mutate vs copy), since these helpers will likely become widely used.

Also applies to: 160-171, 173-183

lib/features/thread_detail/domain/extensions/list_email_in_thread_detail_info_extension.dart (1)

2-3: Nice immutable-style list update helpers (single-element copyWith) for thread detail keywords.

Also applies to: 13-37

lib/features/labels/presentation/widgets/label_list_context_menu.dart (1)

22-40: LGTM: selection computed from presentationEmail.getLabelList(labelList) keeps identity consistent for contains.

lib/main/localizations/app_localizations.dart (1)

5560-5588: New localization keys are straightforward and match the new label flow messaging needs.

lib/features/thread/data/extensions/map_keywords_extension.dart (2)

20-22: Mutating method silently no-ops on null receiver.

The addKeyword method mutates this in place, but if the receiver is null, it silently does nothing. This could mask bugs where callers expect the keyword to be added but the map was never initialized.

Consider either:

  1. Making callers responsible for null-checking before calling
  2. Throwing an exception if this is null
  3. Documenting this behavior clearly

If this silent behavior is intentional (e.g., the caller handles the null case separately), please add a brief doc comment to clarify.


12-18: Immutable keyword helpers look good.

withKeyword and withoutKeyword correctly create new maps from a nullable receiver, defaulting to an empty map when null. This is a safe and predictable pattern.

lib/features/base/model/popup_menu_item_action.dart (2)

6-22: Well-structured submenu support.

The addition of submenu to PopupMenuItemAction and its propagation through all derived classes is clean. Including submenu in props ensures proper equality comparisons for state management.


50-66: Good use of mixin for hover icon properties.

The OptionalPopupHoverIcon mixin follows the existing pattern of OptionalPopupIcon and OptionalPopupSelectedIcon, providing consistent default values. Mixing it into PopupMenuItemActionRequiredIcon is the right approach.

lib/features/email/presentation/utils/email_action_reactor/email_action_reactor.dart (3)

568-606: Label submenu integration looks good.

The PopupSubmenuController is properly scoped to the popup lifecycle and cleaned up via whenComplete. The pattern of skipping labelAs in onActionSelected while delegating to the submenu is correct.


610-627: Helper method is clean and focused.

_getEmailActionSubmenu correctly returns null for non-label actions and handles the empty labels case. The null-check labels?.isNotEmpty == true is idiomatic Dart.


299-303: DialogRouter refactored to instance-based invocation.

The change from static DialogRouter.pushGeneralDialog to DialogRouter().pushGeneralDialog correctly implements the singleton factory pattern. The refactoring is consistently applied across the codebase—all usages of DialogRouter (29+ instances found) use the instance-based invocation pattern DialogRouter(). No remaining static method calls exist. The implementation at lines 299-303 and 743-744 aligns with this consistent pattern.

lib/l10n/intl_messages.arb (1)

5239-5270: Localization entries are well-structured.

The new label-related localization strings follow the existing ARB file patterns correctly. Placeholders for labelName are properly defined with matching metadata entries.

Note: The static analysis hints about trailing commas in placeholders_order arrays (lines 5249, 5259) are style suggestions from RuboCop (which is a Ruby linter incorrectly applied to JSON/ARB). These can be safely ignored as ARB files don't require trailing commas.

lib/main/routes/dialog_router.dart (1)

19-55: Singleton refactor looks coherent (instance Rx state + factory).
The factory singleton + instance members reads cleanly and keeps call-sites simple (DialogRouter()....).

lib/features/base/widget/popup_menu/popup_menu_item_action_widget.dart (1)

246-253: Notifier disposal looks correct.
Good cleanup for the added ValueNotifier.

lib/features/email/presentation/email_view.dart (2)

598-615: Overlay gating via DialogRouter() is reasonable; ensure all dialog entry points toggle the flags.
Now that overlay depends on DialogRouter().isDialogOpened, make sure any dialog that should block iframe interactions goes through DialogRouter (or also toggles MessageDialogActionManager / other existing flags).


25-26: Avoid presentationEmail.id! in label callbacks (defensive null-safety).
If an email ever reaches this path without an id (draft/placeholder), this will throw.

- onSelectLabelAction: (label, isSelected) =>
-     controller.toggleLabelToEmail(
-       presentationEmail.id!,
-       label,
-       isSelected,
-     ),
+ onSelectLabelAction: (label, isSelected) {
+   final id = presentationEmail.id;
+   if (id == null) return;
+   controller.toggleLabelToEmail(id, label, isSelected);
+ },

Also applies to: 106-116, 319-329

⛔ Skipped due to learnings
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4191
File: lib/features/email/presentation/extensions/handle_email_action_extension.dart:37-80
Timestamp: 2025-12-12T04:54:05.432Z
Learning: In lib/features/email/presentation/extensions/handle_email_action_extension.dart, the mailboxDashBoardController.selectedEmail should only be synchronized when isMobileThreadDisabled is true. This is intentional behavior and should not be changed to update selectedEmail in non-mobile or thread-enabled contexts.
lib/features/email/presentation/controller/single_email_controller.dart (1)

38-53: The constructor change for SingleEmailController has already been properly handled. The AddALabelToAnEmailInteractor parameter is present in the constructor signature (line 191) and is correctly provided in all instantiation points:

  • email_bindings.dart (line 28): Get.find<AddALabelToAnEmailInteractor>() is passed
  • single_email_controller_test.dart (line 180): addALabelToAnEmailInteractor is passed

No binding locations are missing this parameter.

Likely an incorrect or invalid review comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants