-
Notifications
You must be signed in to change notification settings - Fork 110
TF-3358 Save draft locally periodically #3598
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
TF-3358 Save draft locally periodically #3598
Conversation
|
This PR has been deployed to https://linagora.github.io/tmail-flutter/3598. |
..._dashboard/presentation/widgets/local_email_draft/local_email_draft_list_dialog_builder.dart
Show resolved
Hide resolved
lib/features/mailbox_dashboard/domain/state/remove_composer_cache_state.dart
Outdated
Show resolved
Hide resolved
cc29684 to
1582106
Compare
|
please rebase it, we will merge it next sprint |
1582106 to
02a1b0f
Compare
Done |
Signed-off-by: dab246 <tdvu@linagora.com>
02a1b0f to
4ba978c
Compare
WalkthroughThis PR replaces the previous web/composer-cache flow with a local email draft system. It adds LocalEmailDraft data model, Hive adapter, caching client, manager, worker queue, datasource/repository implementations, domain interactors (get/save/remove), presentation models and extensions, UI list/dialog widgets, ComposerTimer and composer persistence logic, DialogBuilderManager, composer and mailbox bindings/controller updates, EmailAvatarBuilder API change, EmailActionType rename to composeFromLocalEmailDraft, localization entries, and corresponding test adjustments. Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart (1)
493-513: DuplicateMailboxRepositoryImplbinding.Lines 493-499 and lines 507-513 both register
MailboxRepositoryImplwith identical configuration. This duplicate should be removed.🔎 Proposed fix
Get.lazyPut(() => MailboxRepositoryImpl( { DataSourceType.network: Get.find<MailboxDataSource>(), DataSourceType.local: Get.find<MailboxCacheDataSourceImpl>() }, Get.find<StateDataSource>(), )); Get.lazyPut(() => LocalEmailDraftRepositoryImpl(Get.find<LocalEmailDraftDatasource>())); Get.lazyPut(() => SpamReportRepositoryImpl( { DataSourceType.local: Get.find<LocalSpamReportDataSourceImpl>(), DataSourceType.hiveCache: Get.find<HiveSpamReportDataSourceImpl>() }, )); - Get.lazyPut(() => MailboxRepositoryImpl( - { - DataSourceType.network: Get.find<MailboxDataSource>(), - DataSourceType.local: Get.find<MailboxCacheDataSourceImpl>() - }, - Get.find<StateDataSource>(), - )); Get.lazyPut(() => ServerSettingsRepositoryImpl(Get.find<ServerSettingsDataSource>()));
🧹 Nitpick comments (9)
lib/features/mailbox_dashboard/domain/exceptions/local_email_draft_exception.dart (1)
1-1: Consider adding a message field for better debugging.The exception class is minimal. Adding a message field or
toString()method would improve error diagnostics when drafts are not found.🔎 Proposed enhancement
-class NotFoundLocalEmailDraftException implements Exception {} +class NotFoundLocalEmailDraftException implements Exception { + final String? message; + + NotFoundLocalEmailDraftException([this.message]); + + @override + String toString() => message ?? 'Local email draft not found'; +}lib/features/composer/presentation/manager/composer_timer.dart (1)
16-21: Consider wrapping onTick in error handling.If
onTick()throws an exception, the timer will stop. Adding a try-catch would make the timer more resilient to callback failures.🔎 Proposed enhancement
void start() { stop(); _timer = Timer.periodic(interval, (timer) { - onTick(); + try { + onTick(); + } catch (e) { + // Log error but continue timer + } }); }lib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dart (1)
167-179: Consider removing trailing margin from the last button.The Discard button has
margin: const EdgeInsetsDirectional.only(end: 8), adding extra space on the right. Typically, the last button in a row doesn't need trailing margin, which could cause slight alignment inconsistency.🔎 Proposed fix
Flexible( child: TMailButtonWidget( text: AppLocalizations.of(context).discard, textStyle: Theme.of(context) .textTheme .bodyMedium ?.copyWith(color: AppColor.colorActionDeleteConfirmDialog, fontSize: 12), maxLines: 1, padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 8), - margin: const EdgeInsetsDirectional.only(end: 8), onTapActionCallback: () => onDiscardLocalEmailDraftAction?.call(draftLocal), ), ),lib/features/composer/domain/usecases/save_local_email_draft_interactor.dart (1)
21-43: LGTM! Interactor correctly orchestrates draft saving.The implementation properly:
- Generates the email with appropriate draft flags
- Converts the result to a local draft
- Persists via the repository
- Handles errors with proper state types
Consider adding validation before saving (e.g., checking if email content is substantive) to avoid cluttering local storage with empty drafts. However, this can be deferred based on user feedback.
lib/features/mailbox_dashboard/presentation/extensions/local_email_draft_extension.dart (1)
18-18: Consider handling JSON parsing failures.
jsonDecode(email!)andEmail.fromJson()can throw exceptions if the stored email string is malformed or corrupted. An unhandled exception here would prevent displaying any drafts. Consider wrapping in try-catch with a fallback or logging.🔎 Proposed defensive parsing
- email: email != null ? Email.fromJson(jsonDecode(email!)) : null, + email: _parseEmail(email),Add a helper method:
Email? _parseEmail(String? emailJson) { if (emailJson == null) return null; try { return Email.fromJson(jsonDecode(emailJson)); } catch (e) { // Log error if needed return null; } }lib/features/mailbox_dashboard/presentation/extensions/remove_local_email_draft_extension.dart (1)
26-36: Consider extracting common key generation logic.The
TupleKey(composerId, accountId.asString, userName.value).encodeKeypattern is duplicated inCreateEmailRequestExtension.generateLocalEmailDraftFromEmail. For maintainability, consider extracting this to a shared utility inLocalEmailDraftor a dedicated helper.lib/features/mailbox_dashboard/presentation/model/presentation_local_email_draft.dart (1)
34-58: copyWith method cannot reset nullable fields to null.The current
copyWithimplementation uses??operator, which means you cannot intentionally set a nullable field (e.g.,composerIndex) back tonullonce it has a value. If this is needed, consider using a sentinel pattern or wrapper.If resetting nullable fields to
nullis required, use a wrapper pattern:🔎 Proposed fix
// Add a private sentinel class at top of file class _Absent { const _Absent(); } const _absent = _Absent(); // Then in copyWith, use Object? type and check for sentinel: PresentationLocalEmailDraft copyWith({ String? id, String? composerId, DateTime? savedTime, Object? email = _absent, // ... other nullable fields with = _absent }) => PresentationLocalEmailDraft( id: id ?? this.id, composerId: composerId ?? this.composerId, savedTime: savedTime ?? this.savedTime, email: email == _absent ? this.email : email as Email?, // ... );lib/features/mailbox_dashboard/data/local/local_email_draft_manager.dart (1)
16-25: Throwing exception for empty list may be unconventional.Returning an empty list when no drafts exist is often more idiomatic than throwing an exception. Exceptions are typically reserved for error conditions, not expected empty states. This forces callers to handle the exception even when an empty result is valid.
Consider returning an empty list and letting the caller decide how to handle it:
🔎 Proposed fix
Future<List<LocalEmailDraft>> getAllLocalEmailDraft(AccountId accountId, UserName userName) async { final nestedKey = TupleKey(accountId.asString, userName.value).encodeKey; final listLocalEmailDraft = await _localEmailDraftClient.getListByNestedKey(nestedKey); - - if (listLocalEmailDraft.isEmpty) { - throw NotFoundLocalEmailDraftException(); - } - return listLocalEmailDraft; }lib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_list_dialog_builder.dart (1)
369-371: Consider passing the exception directly toCancelToken.cancelYou currently call
cancelToken?.cancel([SavingEmailToDraftsCanceledException()]);, i.e., the cancel reason is aListcontaining the exception. Unless downstream logic explicitly expects a list, it’s more conventional (and easier to pattern-match) to pass the exception itself:Possible refinement
- void _handleCancelSavingMessageToDrafts({CancelToken? cancelToken}) { - cancelToken?.cancel([SavingEmailToDraftsCanceledException()]); - } + void _handleCancelSavingMessageToDrafts({CancelToken? cancelToken}) { + cancelToken?.cancel(SavingEmailToDraftsCanceledException()); + }Please double-check how the cancel reason is consumed elsewhere to ensure this aligns with your existing Dio usage.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (75)
lib/features/base/widget/dialog_builder/dialog_builder_manager.dartlib/features/base/widget/email_avatar_builder.dartlib/features/caching/clients/local_email_draft_client.dartlib/features/caching/config/hive_cache_config.dartlib/features/caching/utils/caching_constants.dartlib/features/composer/domain/state/save_local_email_draft_state.dartlib/features/composer/domain/usecases/create_new_and_save_email_to_drafts_interactor.dartlib/features/composer/domain/usecases/restore_email_inline_images_interactor.dartlib/features/composer/domain/usecases/save_composer_cache_on_web_interactor.dartlib/features/composer/domain/usecases/save_local_email_draft_interactor.dartlib/features/composer/presentation/composer_bindings.dartlib/features/composer/presentation/composer_controller.dartlib/features/composer/presentation/composer_view_web.dartlib/features/composer/presentation/extensions/create_email_request_extension.dartlib/features/composer/presentation/extensions/email_action_type_extension.dartlib/features/composer/presentation/extensions/handle_local_email_draft_extension.dartlib/features/composer/presentation/extensions/setup_email_attachments_extension.dartlib/features/composer/presentation/extensions/setup_email_content_extension.dartlib/features/composer/presentation/extensions/setup_email_important_flag_extension.dartlib/features/composer/presentation/extensions/setup_email_other_components_extension.dartlib/features/composer/presentation/extensions/setup_email_recipients_extension.dartlib/features/composer/presentation/extensions/setup_email_request_read_receipt_flag_extension.dartlib/features/composer/presentation/extensions/setup_email_subject_extension.dartlib/features/composer/presentation/extensions/setup_email_template_id_extension.dartlib/features/composer/presentation/extensions/setup_selected_identity_extension.dartlib/features/composer/presentation/manager/composer_manager.dartlib/features/composer/presentation/manager/composer_timer.dartlib/features/composer/presentation/mixin/handle_message_failure_mixin.dartlib/features/composer/presentation/model/create_email_request.dartlib/features/composer/presentation/view/mobile/mobile_editor_view.dartlib/features/composer/presentation/view/web/web_editor_view.dartlib/features/email/presentation/email_view.dartlib/features/email/presentation/model/composer_arguments.dartlib/features/email/presentation/widgets/information_sender_and_receiver_builder.dartlib/features/mailbox_dashboard/data/datasource/local_email_draft_datasource.dartlib/features/mailbox_dashboard/data/datasource_impl/local_email_draft_datasource_impl.dartlib/features/mailbox_dashboard/data/datasource_impl/session_storage_composer_datasoure_impl.dartlib/features/mailbox_dashboard/data/local/local_email_draft_manager.dartlib/features/mailbox_dashboard/data/local/local_email_draft_worker_queue.dartlib/features/mailbox_dashboard/data/model/composer_cache.dartlib/features/mailbox_dashboard/data/model/local_email_draft.dartlib/features/mailbox_dashboard/data/repository/composer_cache_repository_impl.dartlib/features/mailbox_dashboard/data/repository/local_email_draft_repository_impl.dartlib/features/mailbox_dashboard/domain/exceptions/local_email_draft_exception.dartlib/features/mailbox_dashboard/domain/repository/local_email_draft_repository.dartlib/features/mailbox_dashboard/domain/state/get_all_local_email_draft_state.dartlib/features/mailbox_dashboard/domain/state/get_composer_cache_state.dartlib/features/mailbox_dashboard/domain/state/remove_all_local_email_draft_state.dartlib/features/mailbox_dashboard/domain/state/remove_composer_cache_state.dartlib/features/mailbox_dashboard/domain/state/remove_local_email_draft_state.dartlib/features/mailbox_dashboard/domain/state/save_composer_cache_state.dartlib/features/mailbox_dashboard/domain/usecases/get_all_local_email_draft_interactor.dartlib/features/mailbox_dashboard/domain/usecases/get_composer_cache_on_web_interactor.dartlib/features/mailbox_dashboard/domain/usecases/remove_all_local_email_drafts_interactor.dartlib/features/mailbox_dashboard/domain/usecases/remove_composer_cache_by_id_on_web_interactor.dartlib/features/mailbox_dashboard/domain/usecases/remove_local_email_draft_interactor.dartlib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dartlib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dartlib/features/mailbox_dashboard/presentation/extensions/local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dartlib/features/mailbox_dashboard/presentation/extensions/presentation_local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/extensions/remove_local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/extensions/reopen_composer_cache_extension.dartlib/features/mailbox_dashboard/presentation/extensions/restore_local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/model/presentation_local_email_draft.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_list_dialog_builder.dartlib/l10n/intl_messages.arblib/main/bindings/local/local_bindings.dartlib/main/localizations/app_localizations.dartmodel/lib/email/email_action_type.darttest/features/composer/presentation/composer_controller_test.darttest/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.darttest/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.darttest/features/search/verify_before_time_in_search_email_filter_test.dart
💤 Files with no reviewable changes (15)
- lib/features/mailbox_dashboard/data/model/composer_cache.dart
- lib/features/mailbox_dashboard/data/repository/composer_cache_repository_impl.dart
- lib/features/mailbox_dashboard/domain/state/get_composer_cache_state.dart
- lib/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart
- lib/features/mailbox_dashboard/domain/state/remove_composer_cache_state.dart
- lib/features/mailbox_dashboard/domain/usecases/remove_composer_cache_by_id_on_web_interactor.dart
- lib/features/mailbox_dashboard/presentation/extensions/reopen_composer_cache_extension.dart
- lib/features/mailbox_dashboard/domain/state/save_composer_cache_state.dart
- test/features/composer/presentation/composer_controller_test.dart
- test/features/search/verify_before_time_in_search_email_filter_test.dart
- test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart
- lib/features/mailbox_dashboard/data/datasource_impl/session_storage_composer_datasoure_impl.dart
- test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart
- lib/features/mailbox_dashboard/domain/usecases/get_composer_cache_on_web_interactor.dart
- lib/features/composer/domain/usecases/save_composer_cache_on_web_interactor.dart
🧰 Additional context used
🧠 Learnings (14)
📚 Learning: 2025-12-12T04:54:11.121Z
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:11.121Z
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/view/mobile/mobile_editor_view.dartlib/features/composer/presentation/extensions/setup_selected_identity_extension.dartlib/features/composer/domain/usecases/create_new_and_save_email_to_drafts_interactor.dartlib/features/composer/presentation/extensions/setup_email_content_extension.dartlib/features/composer/presentation/extensions/setup_email_recipients_extension.dartlib/features/mailbox_dashboard/domain/exceptions/local_email_draft_exception.dartlib/features/composer/presentation/extensions/setup_email_template_id_extension.dartlib/features/caching/config/hive_cache_config.dartlib/features/composer/presentation/extensions/create_email_request_extension.dartlib/features/composer/presentation/extensions/setup_email_other_components_extension.dartmodel/lib/email/email_action_type.dartlib/main/bindings/local/local_bindings.dartlib/features/mailbox_dashboard/data/local/local_email_draft_worker_queue.dartlib/features/composer/presentation/extensions/handle_local_email_draft_extension.dartlib/features/composer/presentation/view/web/web_editor_view.dartlib/features/mailbox_dashboard/domain/repository/local_email_draft_repository.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_list_dialog_builder.dartlib/features/mailbox_dashboard/domain/usecases/remove_local_email_draft_interactor.dartlib/features/composer/presentation/model/create_email_request.dartlib/features/mailbox_dashboard/presentation/extensions/presentation_local_email_draft_extension.dartlib/features/mailbox_dashboard/domain/usecases/remove_all_local_email_drafts_interactor.dartlib/features/mailbox_dashboard/data/repository/local_email_draft_repository_impl.dartlib/features/mailbox_dashboard/presentation/extensions/remove_local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dartlib/features/composer/presentation/manager/composer_manager.dartlib/features/email/presentation/model/composer_arguments.dartlib/features/mailbox_dashboard/presentation/model/presentation_local_email_draft.dartlib/features/mailbox_dashboard/presentation/extensions/local_email_draft_extension.dartlib/features/email/presentation/email_view.dartlib/features/composer/presentation/extensions/setup_email_request_read_receipt_flag_extension.dartlib/features/mailbox_dashboard/data/datasource_impl/local_email_draft_datasource_impl.dartlib/features/composer/presentation/extensions/email_action_type_extension.dartlib/features/email/presentation/widgets/information_sender_and_receiver_builder.dartlib/features/mailbox_dashboard/data/local/local_email_draft_manager.dartlib/features/mailbox_dashboard/data/datasource/local_email_draft_datasource.dartlib/features/composer/presentation/mixin/handle_message_failure_mixin.dartlib/features/mailbox_dashboard/data/model/local_email_draft.dartlib/features/composer/presentation/extensions/setup_email_attachments_extension.dartlib/features/mailbox_dashboard/domain/state/remove_local_email_draft_state.dartlib/features/composer/presentation/extensions/setup_email_important_flag_extension.dartlib/features/composer/presentation/composer_bindings.dartlib/features/mailbox_dashboard/presentation/extensions/restore_local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dartlib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dartlib/features/base/widget/email_avatar_builder.dartlib/features/composer/presentation/composer_controller.dartlib/features/composer/presentation/extensions/setup_email_subject_extension.dart
📚 Learning: 2025-12-23T05:27:10.887Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4210
File: model/lib/mailbox/presentation_mailbox.dart:22-34
Timestamp: 2025-12-23T05:27:10.887Z
Learning: In the tmail-flutter codebase, the Action Required folder (actionRequiredFolder) is a virtual folder that is AI-curated and contains emails with the keyword "needs-action". Unlike the Favorites folder, users should NOT be able to drag emails into the Action Required folder. The UI correctly prevents this by not wrapping the Action Required folder item in a DragTarget (see mailbox_item_widget.dart).
Applied to files:
lib/features/composer/presentation/view/mobile/mobile_editor_view.dartlib/features/caching/config/hive_cache_config.dartlib/features/composer/presentation/extensions/create_email_request_extension.dartmodel/lib/email/email_action_type.dartlib/main/bindings/local/local_bindings.dartlib/features/composer/presentation/model/create_email_request.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dartlib/features/email/presentation/model/composer_arguments.dartlib/features/email/presentation/email_view.dartlib/features/composer/presentation/extensions/setup_email_important_flag_extension.dartlib/features/composer/presentation/composer_bindings.dartlib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dartlib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dartlib/features/composer/presentation/composer_controller.dart
📚 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/composer/presentation/view/mobile/mobile_editor_view.dartlib/features/caching/clients/local_email_draft_client.dartlib/features/composer/presentation/extensions/setup_selected_identity_extension.dartlib/features/composer/domain/usecases/create_new_and_save_email_to_drafts_interactor.dartlib/features/composer/presentation/extensions/setup_email_content_extension.dartlib/features/composer/presentation/extensions/setup_email_recipients_extension.dartlib/features/caching/utils/caching_constants.dartlib/features/mailbox_dashboard/domain/exceptions/local_email_draft_exception.dartlib/features/composer/presentation/extensions/setup_email_template_id_extension.dartlib/features/caching/config/hive_cache_config.dartlib/features/composer/presentation/extensions/create_email_request_extension.dartlib/features/mailbox_dashboard/domain/usecases/get_all_local_email_draft_interactor.dartlib/features/composer/presentation/extensions/setup_email_other_components_extension.dartmodel/lib/email/email_action_type.dartlib/main/bindings/local/local_bindings.dartlib/features/mailbox_dashboard/domain/state/get_all_local_email_draft_state.dartlib/features/mailbox_dashboard/data/local/local_email_draft_worker_queue.dartlib/features/composer/presentation/extensions/handle_local_email_draft_extension.dartlib/features/composer/presentation/view/web/web_editor_view.dartlib/features/composer/presentation/composer_view_web.dartlib/features/composer/domain/usecases/save_local_email_draft_interactor.dartlib/features/mailbox_dashboard/domain/repository/local_email_draft_repository.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_list_dialog_builder.dartlib/features/mailbox_dashboard/domain/usecases/remove_local_email_draft_interactor.dartlib/features/composer/presentation/model/create_email_request.dartlib/features/mailbox_dashboard/presentation/extensions/presentation_local_email_draft_extension.dartlib/features/mailbox_dashboard/domain/usecases/remove_all_local_email_drafts_interactor.dartlib/features/composer/domain/state/save_local_email_draft_state.dartlib/features/composer/presentation/manager/composer_timer.dartlib/features/mailbox_dashboard/data/repository/local_email_draft_repository_impl.dartlib/features/composer/domain/usecases/restore_email_inline_images_interactor.dartlib/features/mailbox_dashboard/presentation/extensions/remove_local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dartlib/features/composer/presentation/manager/composer_manager.dartlib/features/email/presentation/model/composer_arguments.dartlib/main/localizations/app_localizations.dartlib/features/mailbox_dashboard/presentation/model/presentation_local_email_draft.dartlib/features/mailbox_dashboard/presentation/extensions/local_email_draft_extension.dartlib/features/email/presentation/email_view.dartlib/features/composer/presentation/extensions/setup_email_request_read_receipt_flag_extension.dartlib/features/base/widget/dialog_builder/dialog_builder_manager.dartlib/features/mailbox_dashboard/data/datasource_impl/local_email_draft_datasource_impl.dartlib/features/composer/presentation/extensions/email_action_type_extension.dartlib/features/email/presentation/widgets/information_sender_and_receiver_builder.dartlib/features/mailbox_dashboard/data/local/local_email_draft_manager.dartlib/features/mailbox_dashboard/data/datasource/local_email_draft_datasource.dartlib/features/composer/presentation/mixin/handle_message_failure_mixin.dartlib/features/mailbox_dashboard/data/model/local_email_draft.dartlib/features/composer/presentation/extensions/setup_email_attachments_extension.dartlib/features/mailbox_dashboard/domain/state/remove_local_email_draft_state.dartlib/features/composer/presentation/extensions/setup_email_important_flag_extension.dartlib/features/composer/presentation/composer_bindings.dartlib/features/mailbox_dashboard/presentation/extensions/restore_local_email_draft_extension.dartlib/features/mailbox_dashboard/domain/state/remove_all_local_email_draft_state.dartlib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dartlib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dartlib/features/base/widget/email_avatar_builder.dartlib/features/composer/presentation/composer_controller.dartlib/features/composer/presentation/extensions/setup_email_subject_extension.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/composer/presentation/view/mobile/mobile_editor_view.dartlib/features/caching/clients/local_email_draft_client.dartlib/features/composer/presentation/extensions/setup_selected_identity_extension.dartlib/features/composer/domain/usecases/create_new_and_save_email_to_drafts_interactor.dartlib/features/composer/presentation/extensions/setup_email_content_extension.dartlib/features/composer/presentation/extensions/setup_email_recipients_extension.dartlib/features/caching/utils/caching_constants.dartlib/features/mailbox_dashboard/domain/exceptions/local_email_draft_exception.dartlib/features/composer/presentation/extensions/setup_email_template_id_extension.dartlib/features/caching/config/hive_cache_config.dartlib/features/composer/presentation/extensions/create_email_request_extension.dartlib/features/mailbox_dashboard/domain/usecases/get_all_local_email_draft_interactor.dartlib/features/composer/presentation/extensions/setup_email_other_components_extension.dartmodel/lib/email/email_action_type.dartlib/main/bindings/local/local_bindings.dartlib/features/mailbox_dashboard/domain/state/get_all_local_email_draft_state.dartlib/features/mailbox_dashboard/data/local/local_email_draft_worker_queue.dartlib/features/composer/presentation/extensions/handle_local_email_draft_extension.dartlib/features/composer/presentation/view/web/web_editor_view.dartlib/features/composer/presentation/composer_view_web.dartlib/features/composer/domain/usecases/save_local_email_draft_interactor.dartlib/features/mailbox_dashboard/domain/repository/local_email_draft_repository.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_list_dialog_builder.dartlib/features/mailbox_dashboard/domain/usecases/remove_local_email_draft_interactor.dartlib/features/composer/presentation/model/create_email_request.dartlib/features/mailbox_dashboard/presentation/extensions/presentation_local_email_draft_extension.dartlib/features/mailbox_dashboard/domain/usecases/remove_all_local_email_drafts_interactor.dartlib/features/composer/domain/state/save_local_email_draft_state.dartlib/features/composer/presentation/manager/composer_timer.dartlib/features/mailbox_dashboard/data/repository/local_email_draft_repository_impl.dartlib/features/composer/domain/usecases/restore_email_inline_images_interactor.dartlib/features/mailbox_dashboard/presentation/extensions/remove_local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dartlib/features/composer/presentation/manager/composer_manager.dartlib/features/email/presentation/model/composer_arguments.dartlib/main/localizations/app_localizations.dartlib/features/mailbox_dashboard/presentation/model/presentation_local_email_draft.dartlib/features/mailbox_dashboard/presentation/extensions/local_email_draft_extension.dartlib/features/email/presentation/email_view.dartlib/features/composer/presentation/extensions/setup_email_request_read_receipt_flag_extension.dartlib/features/base/widget/dialog_builder/dialog_builder_manager.dartlib/features/mailbox_dashboard/data/datasource_impl/local_email_draft_datasource_impl.dartlib/features/composer/presentation/extensions/email_action_type_extension.dartlib/features/email/presentation/widgets/information_sender_and_receiver_builder.dartlib/features/mailbox_dashboard/data/local/local_email_draft_manager.dartlib/features/mailbox_dashboard/data/datasource/local_email_draft_datasource.dartlib/features/composer/presentation/mixin/handle_message_failure_mixin.dartlib/features/mailbox_dashboard/data/model/local_email_draft.dartlib/features/composer/presentation/extensions/setup_email_attachments_extension.dartlib/features/mailbox_dashboard/domain/state/remove_local_email_draft_state.dartlib/features/composer/presentation/extensions/setup_email_important_flag_extension.dartlib/features/composer/presentation/composer_bindings.dartlib/features/mailbox_dashboard/presentation/extensions/restore_local_email_draft_extension.dartlib/features/mailbox_dashboard/domain/state/remove_all_local_email_draft_state.dartlib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dartlib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dartlib/features/base/widget/email_avatar_builder.dartlib/features/composer/presentation/composer_controller.dartlib/features/composer/presentation/extensions/setup_email_subject_extension.dart
📚 Learning: 2025-12-12T07:56:31.877Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4204
File: lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart:579-582
Timestamp: 2025-12-12T07:56:31.877Z
Learning: In lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart, the addLabelToEmail method intentionally throws UnimplementedError because label synchronization with the email cache is handled separately via extension methods (e.g., _autoSyncLabelToSelectedEmailOnMemory in handle_label_for_email_extension.dart). Implementing it in the cache datasource would cause data conflicts. This differs from other keyword operations like markAsRead, markAsStar, markAsAnswered, and markAsForwarded, which are implemented directly in the cache datasource.
Applied to files:
lib/features/caching/clients/local_email_draft_client.dartlib/features/caching/utils/caching_constants.dartlib/features/caching/config/hive_cache_config.dartlib/features/composer/presentation/extensions/create_email_request_extension.dartlib/main/bindings/local/local_bindings.dartlib/features/mailbox_dashboard/data/local/local_email_draft_worker_queue.dartlib/features/mailbox_dashboard/domain/repository/local_email_draft_repository.dartlib/features/mailbox_dashboard/domain/usecases/remove_all_local_email_drafts_interactor.dartlib/features/mailbox_dashboard/data/repository/local_email_draft_repository_impl.dartlib/features/mailbox_dashboard/presentation/extensions/remove_local_email_draft_extension.dartlib/features/mailbox_dashboard/data/datasource_impl/local_email_draft_datasource_impl.dartlib/features/mailbox_dashboard/data/datasource/local_email_draft_datasource.dartlib/features/mailbox_dashboard/data/model/local_email_draft.dartlib/features/composer/presentation/extensions/setup_email_attachments_extension.dartlib/features/composer/presentation/extensions/setup_email_important_flag_extension.dartlib/features/composer/presentation/composer_bindings.dartlib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dartlib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dartlib/features/composer/presentation/composer_controller.dart
📚 Learning: 2025-12-12T07:43:26.643Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4204
File: lib/features/email/presentation/extensions/handle_label_for_email_extension.dart:94-148
Timestamp: 2025-12-12T07:43:26.643Z
Learning: In lib/features/email/presentation/extensions/handle_label_for_email_extension.dart and similar keyword synchronization code, the addKeyword() function is the preferred method for adding keywords to emails. The resyncKeywords() pattern is being phased out and will be replaced in favor of addKeyword().
Applied to files:
lib/features/composer/presentation/extensions/setup_email_recipients_extension.dartlib/features/composer/presentation/extensions/setup_email_template_id_extension.dartlib/features/composer/presentation/extensions/create_email_request_extension.dartlib/features/composer/presentation/extensions/handle_local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/extensions/presentation_local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/extensions/remove_local_email_draft_extension.dartlib/features/composer/presentation/manager/composer_manager.dartlib/features/mailbox_dashboard/presentation/extensions/local_email_draft_extension.dartlib/features/composer/presentation/extensions/email_action_type_extension.dartlib/features/composer/presentation/extensions/setup_email_attachments_extension.dartlib/features/composer/presentation/extensions/setup_email_important_flag_extension.dartlib/features/mailbox_dashboard/presentation/extensions/restore_local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dartlib/features/composer/presentation/composer_controller.dartlib/features/composer/presentation/extensions/setup_email_subject_extension.dart
📚 Learning: 2025-12-15T06:24:50.369Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4137
File: lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart:32-32
Timestamp: 2025-12-15T06:24:50.369Z
Learning: In lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart and lib/features/manage_account/presentation/manage_account_dashboard_controller.dart, SentryManager.instance.setUser() intentionally sends PII (account ID, display name, username, email) to Sentry for error tracking. The team plans to document this in the app's privacy policy for transparency and compliance.
Applied to files:
lib/features/caching/config/hive_cache_config.dartlib/features/composer/presentation/extensions/create_email_request_extension.dartlib/main/bindings/local/local_bindings.dartlib/features/mailbox_dashboard/domain/repository/local_email_draft_repository.dartlib/features/mailbox_dashboard/presentation/extensions/remove_local_email_draft_extension.dartlib/features/email/presentation/model/composer_arguments.dartlib/features/mailbox_dashboard/presentation/model/presentation_local_email_draft.dartlib/features/mailbox_dashboard/data/local/local_email_draft_manager.dartlib/features/mailbox_dashboard/data/datasource/local_email_draft_datasource.dartlib/features/composer/presentation/composer_bindings.dartlib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dartlib/features/composer/presentation/composer_controller.dart
📚 Learning: 2025-12-18T09:19:32.437Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4203
File: scribe/pubspec.yaml:37-37
Timestamp: 2025-12-18T09:19:32.437Z
Learning: In the tmail-flutter repository, json_annotation version 4.8.0 is the standardized version across modules and should not be upgraded without considering the standardization practice.
Applied to files:
lib/features/composer/presentation/extensions/create_email_request_extension.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/mailbox_dashboard/domain/usecases/remove_local_email_draft_interactor.dartlib/features/mailbox_dashboard/domain/usecases/remove_all_local_email_drafts_interactor.dartlib/features/composer/presentation/composer_bindings.dartlib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dartlib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dartlib/features/composer/presentation/composer_controller.dart
📚 Learning: 2025-12-12T09:03:05.962Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4204
File: lib/features/email/presentation/model/popup_menu_item_email_action.dart:34-35
Timestamp: 2025-12-12T09:03:05.962Z
Learning: In the tmail-flutter codebase, popup menu items intentionally use a consistent hoverIcon (imagePaths.icThumbsUp) across all popup item types (email actions, mailbox actions, profile settings, etc.), while actionIcon varies per action type. This is a deliberate UI/UX design choice.
Applied to files:
lib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dartlib/features/email/presentation/email_view.dartlib/features/email/presentation/widgets/information_sender_and_receiver_builder.dartlib/features/base/widget/email_avatar_builder.dart
📚 Learning: 2025-12-23T05:27:40.476Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4210
File: lib/l10n/intl_messages.arb:5151-5182
Timestamp: 2025-12-23T05:27:40.476Z
Learning: In lib/l10n, translations for locale .arb files are updated automatically by an external workflow when new English keys are added to intl_messages.arb. Do not manually modify translated locale .arb files in pull requests; rely on the automated workflow to update translations. If manual edits are necessary, clearly reference the automation workflow and ensure translations remain consistent with the source English keys.
Applied to files:
lib/l10n/intl_messages.arb
📚 Learning: 2025-12-19T08:04:06.064Z
Learnt from: hoangdat
Repo: linagora/tmail-flutter PR: 4208
File: lib/l10n/intl_fr.arb:5209-5226
Timestamp: 2025-12-19T08:04:06.064Z
Learning: In all ARB localization files under lib/l10n, AI Scribe translation keys should only be present for the supported locales: en (default), fr, vi, and ru. Do not add AI Scribe keys to other locale files. Ensure fr, vi, and ru ARB files include the same AI Scribe keys as applicable, with en as the source/default. This pattern applies to any ARB file in the lib/l10n directory.
Applied to files:
lib/l10n/intl_messages.arb
📚 Learning: 2025-12-23T05:27:47.287Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4210
File: lib/l10n/intl_messages.arb:5151-5182
Timestamp: 2025-12-23T05:27:47.287Z
Learning: In the tmail-flutter repository, localization (l10n) updates for locale files (intl_fr.arb, intl_vi.arb, intl_ru.arb, etc.) are managed automatically through a third-party website/platform. When new English keys are added to intl_messages.arb, translations to other locales are handled by this automated workflow rather than being manually added in pull requests.
Applied to files:
lib/main/localizations/app_localizations.dart
📚 Learning: 2025-12-15T06:24:38.823Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4137
File: lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart:32-32
Timestamp: 2025-12-15T06:24:38.823Z
Learning: In lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart and lib/features/manage_account/presentation/manage_account_dashboard_controller.dart, the code calls SentryManager.instance.setUser() with PII fields (account ID, display name, username, email). This is privacy-sensitive. Review and ensure PII handling complies with policy: avoid sending unnecessary PII to Sentry, redact or hash sensitive fields if possible, or document explicit privacy policy and user consent for such data. If PII must be sent, ensure minimal data, secure handling, and add notes to the privacy policy. Consider adding tests or a lint rule to flag setUser calls that include PII.
Applied to files:
lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: analyze-test (contact)
- GitHub Check: analyze-test (server_settings)
- GitHub Check: analyze-test (email_recovery)
- GitHub Check: analyze-test (core)
- GitHub Check: analyze-test (forward)
- GitHub Check: analyze-test (fcm)
- GitHub Check: analyze-test (default)
- GitHub Check: analyze-test (model)
- GitHub Check: analyze-test (rule_filter)
- GitHub Check: Build web version and deploy
lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart
Show resolved
Hide resolved
lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart
Outdated
Show resolved
Hide resolved
lib/features/mailbox_dashboard/presentation/extensions/restore_local_email_draft_extension.dart
Show resolved
Hide resolved
..._dashboard/presentation/widgets/local_email_draft/local_email_draft_list_dialog_builder.dart
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (5)
lib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dart (4)
72-76: HardcodedColors.blackmay break dark theme support.Using
Colors.blackdirectly instead of a theme-aware color (likeAppColorconstants used elsewhere in this file) could cause visibility issues if dark mode is supported.🔎 Suggested fix
style: Theme.of(context) .textTheme .bodyMedium - ?.copyWith(color: Colors.black), + ?.copyWith(color: AppColor.colorNameEmail),Alternatively, use
Theme.of(context).textTheme.bodyMediumwithout color override if the theme already defines the appropriate color.
157-161: Consistent theme color concern.Same as the recipient name,
Colors.blackis hardcoded here for the "Save as Draft" button text. Consider using a theme-aware color for consistency with the rest of the widget.
181-184: Consider renamingisOldestfor clarity.The name
isOldestimplies temporal ordering, but it's used to control whether this is the last item (hiding the bottom divider). A name likeisLastItemmight be clearer, though this is a minor naming suggestion.
44-45: Consider adding semantic label for accessibility.The
InkWellwrapper makes the entire item tappable, but screen readers won't have context about what this action does. Consider wrapping withSemanticsor usingInkWell'ssemanticLabelproperty.🔎 Suggested improvement
child: InkWell( + semanticLabel: 'Open draft to ${draftLocal.firstRecipientName}', onTap: () => onSelectLocalEmailDraftAction?.call(draftLocal),lib/features/composer/presentation/manager/composer_timer.dart (1)
6-35: LGTM!The
ComposerTimerclass cleanly encapsulates periodic timer logic with proper lifecycle management. The 15-second default interval aligns with the PR requirements, and the try-catch in the timer callback provides error isolation.Consider capturing the stack trace in the catch block for richer debugging:
} catch (e) { - logError('ComposerTimer::start:Exception: $e'); + logError('ComposerTimer::start:Exception: $e', e, StackTrace.current); }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
lib/features/caching/config/hive_cache_config.dartlib/features/composer/presentation/composer_controller.dartlib/features/composer/presentation/extensions/create_email_request_extension.dartlib/features/composer/presentation/extensions/handle_local_email_draft_extension.dartlib/features/composer/presentation/manager/composer_manager.dartlib/features/composer/presentation/manager/composer_timer.dartlib/features/composer/presentation/utils/local_email_draft_helper.dartlib/features/download/presentation/extensions/download_attachment_download_controller_extension.dartlib/features/mailbox_dashboard/data/local/local_email_draft_manager.dartlib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dartlib/features/mailbox_dashboard/presentation/extensions/local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/extensions/remove_local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/extensions/restore_local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_list_dialog_builder.dart
🚧 Files skipped from review as they are similar to previous changes (5)
- lib/features/caching/config/hive_cache_config.dart
- lib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_list_dialog_builder.dart
- lib/features/composer/presentation/extensions/create_email_request_extension.dart
- lib/features/mailbox_dashboard/data/local/local_email_draft_manager.dart
- lib/features/mailbox_dashboard/presentation/extensions/local_email_draft_extension.dart
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-12-12T07:56:31.877Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4204
File: lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart:579-582
Timestamp: 2025-12-12T07:56:31.877Z
Learning: In lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart, the addLabelToEmail method intentionally throws UnimplementedError because label synchronization with the email cache is handled separately via extension methods (e.g., _autoSyncLabelToSelectedEmailOnMemory in handle_label_for_email_extension.dart). Implementing it in the cache datasource would cause data conflicts. This differs from other keyword operations like markAsRead, markAsStar, markAsAnswered, and markAsForwarded, which are implemented directly in the cache datasource.
Applied to files:
lib/features/composer/presentation/utils/local_email_draft_helper.dartlib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dartlib/features/composer/presentation/composer_controller.dart
📚 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/composer/presentation/utils/local_email_draft_helper.dartlib/features/composer/presentation/extensions/handle_local_email_draft_extension.dartlib/features/composer/presentation/manager/composer_manager.dartlib/features/composer/presentation/manager/composer_timer.dartlib/features/download/presentation/extensions/download_attachment_download_controller_extension.dartlib/features/mailbox_dashboard/presentation/extensions/restore_local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dartlib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dartlib/features/mailbox_dashboard/presentation/extensions/remove_local_email_draft_extension.dartlib/features/composer/presentation/composer_controller.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/composer/presentation/utils/local_email_draft_helper.dartlib/features/composer/presentation/extensions/handle_local_email_draft_extension.dartlib/features/composer/presentation/manager/composer_manager.dartlib/features/composer/presentation/manager/composer_timer.dartlib/features/download/presentation/extensions/download_attachment_download_controller_extension.dartlib/features/mailbox_dashboard/presentation/extensions/restore_local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dartlib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dartlib/features/mailbox_dashboard/presentation/extensions/remove_local_email_draft_extension.dartlib/features/composer/presentation/composer_controller.dart
📚 Learning: 2025-12-12T04:54:11.121Z
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:11.121Z
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/handle_local_email_draft_extension.dartlib/features/composer/presentation/manager/composer_manager.dartlib/features/mailbox_dashboard/presentation/extensions/restore_local_email_draft_extension.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dartlib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dartlib/features/mailbox_dashboard/presentation/extensions/remove_local_email_draft_extension.dartlib/features/composer/presentation/composer_controller.dart
📚 Learning: 2025-12-12T07:43:26.643Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4204
File: lib/features/email/presentation/extensions/handle_label_for_email_extension.dart:94-148
Timestamp: 2025-12-12T07:43:26.643Z
Learning: In lib/features/email/presentation/extensions/handle_label_for_email_extension.dart and similar keyword synchronization code, the addKeyword() function is the preferred method for adding keywords to emails. The resyncKeywords() pattern is being phased out and will be replaced in favor of addKeyword().
Applied to files:
lib/features/composer/presentation/extensions/handle_local_email_draft_extension.dartlib/features/composer/presentation/manager/composer_manager.dartlib/features/mailbox_dashboard/presentation/extensions/restore_local_email_draft_extension.dartlib/features/composer/presentation/composer_controller.dart
📚 Learning: 2025-12-23T05:27:10.887Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4210
File: model/lib/mailbox/presentation_mailbox.dart:22-34
Timestamp: 2025-12-23T05:27:10.887Z
Learning: In the tmail-flutter codebase, the Action Required folder (actionRequiredFolder) is a virtual folder that is AI-curated and contains emails with the keyword "needs-action". Unlike the Favorites folder, users should NOT be able to drag emails into the Action Required folder. The UI correctly prevents this by not wrapping the Action Required folder item in a DragTarget (see mailbox_item_widget.dart).
Applied to files:
lib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dartlib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dartlib/features/composer/presentation/composer_controller.dart
📚 Learning: 2025-12-15T06:24:50.369Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4137
File: lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart:32-32
Timestamp: 2025-12-15T06:24:50.369Z
Learning: In lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart and lib/features/manage_account/presentation/manage_account_dashboard_controller.dart, SentryManager.instance.setUser() intentionally sends PII (account ID, display name, username, email) to Sentry for error tracking. The team plans to document this in the app's privacy policy for transparency and compliance.
Applied to files:
lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dartlib/features/composer/presentation/composer_controller.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/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dartlib/features/composer/presentation/composer_controller.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: analyze-test (server_settings)
- GitHub Check: analyze-test (fcm)
- GitHub Check: analyze-test (email_recovery)
- GitHub Check: analyze-test (core)
- GitHub Check: analyze-test (contact)
- GitHub Check: analyze-test (forward)
- GitHub Check: analyze-test (rule_filter)
- GitHub Check: analyze-test (model)
- GitHub Check: analyze-test (default)
- GitHub Check: Build web version and deploy
🔇 Additional comments (29)
lib/features/download/presentation/extensions/download_attachment_download_controller_extension.dart (1)
167-169: LGTM! Correct API usage for CancelToken.The fix correctly passes a single string argument to
cancelToken.cancel()instead of the erroneous list argument. The DioCancelToken.cancel([dynamic reason])API accepts an optional reason parameter, and using the localized cancellation message is appropriate.lib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dart (2)
20-38: Well-structured widget API.The constructor clearly separates required dependencies from optional callbacks, making the widget flexible for different use cases while enforcing essential data requirements.
40-193: Overall implementation is clean and well-structured.The widget follows Flutter best practices with proper use of
constconstructor, clear separation of layout concerns, and appropriate use of callbacks for actions. The conditional rendering logic for subject, content, and divider is correctly implemented.lib/features/composer/presentation/utils/local_email_draft_helper.dart (1)
6-19: LGTM!The utility class is well-designed with a private constructor and a clear static method for generating composite draft IDs. The use of
TupleKeyensures consistent key generation across the codebase.lib/features/composer/presentation/extensions/handle_local_email_draft_extension.dart (2)
8-58: LGTM!The
generateCreateEmailRequest()method has proper null-safety checks and comprehensively constructs theCreateEmailRequestwith all necessary fields for local draft persistence.
60-84: LGTM!The
saveLocalEmailDraftAction()method correctly validates prerequisites, auto-creates email tags before saving, and awaits the interactor execution. Error handling is appropriately deferred to the caller (ComposerManager's_handleOnTick).lib/features/mailbox_dashboard/presentation/extensions/remove_local_email_draft_extension.dart (1)
4-19: LGTM!The extension correctly mirrors the ID generation logic used in save operations, ensuring consistent key management. The null-safety checks and async handling are properly implemented.
lib/features/composer/presentation/manager/composer_manager.dart (5)
25-43: LGTM!The
isSynchronousparameter provides good flexibility for batch operations vs single additions. The lazy timer initialization pattern is appropriate.
45-52: LGTM!Using
isSynchronous: falsefor batch additions and deferring timer initialization until after all composers are added prevents redundant timer restarts.
54-67: LGTM!The timer cleanup logic correctly stops the timer when all composers are removed.
323-347: LGTM!The timer lifecycle methods are well-implemented:
- Lazy initialization prevents duplicate timers.
- Error handling in
_handleOnTickisolates failures per composer, ensuring one failing draft save doesn't affect others.- Errors are logged with composer ID for debugging visibility.
349-354: LGTM!Proper cleanup in
onCloseensures the timer is stopped when the manager is disposed.lib/features/mailbox_dashboard/presentation/extensions/restore_local_email_draft_extension.dart (4)
17-30: LGTM!The method correctly uses
getBinding<>()pattern (which returns null when not found, per learnings) and has proper null checks before consuming state.
32-46: LGTM!The conversion and sorting logic is clean. The in-place sort on
listPresentationLocalEmailDraftis fine since it's a locally-created list from.toList().
48-64: LGTM!The dialog display and single draft editing flow are correctly implemented with proper navigation handling.
66-84: LGTM!The spread operator copy (
[...localDrafts]) correctly prevents mutation of the input parameter before sorting. The toast feedback provides good UX confirmation.lib/features/composer/presentation/composer_controller.dart (11)
134-141: LGTM!The addition of
HandleMessageFailureMixinfollows proper Dart mixin patterns and correctly replaces the previous extension-based approach for message failure handling.
187-188: LGTM!Making
saveLocalEmailDraftInteractornullable and binding it conditionally for web platform only is appropriate since local draft persistence is a web-specific feature.
247-248: LGTM!Changing visibility from private (
_savedEmailDraftHash) to package-private (savedEmailDraftHash) correctly enables access from the extension methods while maintaining encapsulation.
425-427: LGTM!Making
onBeforeUnloadBrowserListenerasync and awaitingsaveLocalEmailDraftAction()ensures the draft is persisted before the browser unloads.
479-486: LGTM!Renaming to
getUploadUriFromSession(public) enables the extension to access this method while maintaining the same error handling logic.
858-865: LGTM!Correctly removes the local email draft after a successful send operation. The null check on
composerIdis appropriate.
1150-1158: LGTM!The
EmailActionType.composeFromLocalEmailDraftcorrectly replaces the previousreopenComposerBrowseraction type, aligning with the new local draft flow.
1186-1195: LGTM!The draft hash initialization logic correctly handles the new
composeFromLocalEmailDraftaction type, preserving the original hash for change detection.
1632-1638: LGTM!The local draft is correctly removed when the user explicitly deletes the composer.
2026-2031: LGTM!The local draft is correctly removed after successfully saving to the server drafts, preventing duplicate data.
2237-2240: LGTM!Using
saveLocalEmailDraftAction()inonBeforeReconnectensures drafts are persisted before network reconnection events, providing resilience against data loss.lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart (2)
257-520: Overall binding structure looks good.The integration of the local email draft system follows the correct GetX dependency injection pattern:
- Datasource interfaces and implementations are properly wired
- Repositories depend on registered datasources
- Interactors depend on registered repositories
- The dependency chain is logical and maintainable
The previous duplicate binding issues flagged in past reviews have been successfully addressed.
315-319: All flagged dependencies are properly registered in the codebase.LocalEmailDraftManagerandLocalEmailDraftWorkerQueueare registered inlib/main/bindings/local/local_bindings.dart,UuidandApplicationManagerare inlib/main/bindings/core/core_bindings.dart,FileUploaderis inlib/main/bindings/network/network_isolate_binding.dart, andDownloadClientis inlib/main/bindings/network/network_bindings.dart. No registration issues exist.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
core/lib/presentation/extensions/color_extension.dartlib/features/composer/presentation/manager/composer_timer.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_list_dialog_builder.dart
🚧 Files skipped from review as they are similar to previous changes (3)
- lib/features/composer/presentation/manager/composer_timer.dart
- lib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dart
- lib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_list_dialog_builder.dart
🧰 Additional context used
🧠 Learnings (2)
📚 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:
core/lib/presentation/extensions/color_extension.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:
core/lib/presentation/extensions/color_extension.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: analyze-test (forward)
- GitHub Check: analyze-test (fcm)
- GitHub Check: analyze-test (email_recovery)
- GitHub Check: analyze-test (default)
- GitHub Check: analyze-test (rule_filter)
- GitHub Check: analyze-test (contact)
- GitHub Check: analyze-test (server_settings)
- GitHub Check: analyze-test (model)
- GitHub Check: analyze-test (core)
…—theme-aware color selection is needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
core/lib/presentation/extensions/app_text_colors_extension.dart (1)
4-4: Consider singular naming for consistency.The extension is named
AppTextColors(plural) but currently provides only one getter. Consider renaming toAppTextColor(singular) for consistency, unless additional text color getters are planned for future addition.lib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dart (1)
140-181: Consider adding semantic labels for better accessibility.For improved screen reader support, consider wrapping the action buttons with
Semanticswidgets or adding semantic labels to clarify the action context (e.g., "Edit draft", "Save draft to server", "Discard draft").Example enhancement
Semantics( label: 'Edit draft', button: true, child: TMailButtonWidget( text: AppLocalizations.of(context).edit, // ... rest of the properties ), )
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
core/lib/core.dartcore/lib/presentation/extensions/app_text_colors_extension.dartcore/lib/presentation/extensions/color_extension.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dart
🚧 Files skipped from review as they are similar to previous changes (1)
- core/lib/presentation/extensions/color_extension.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:
core/lib/presentation/extensions/app_text_colors_extension.dartcore/lib/core.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.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:
core/lib/presentation/extensions/app_text_colors_extension.dartcore/lib/core.dartlib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dart
📚 Learning: 2025-12-12T04:54:11.121Z
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:11.121Z
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/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dart
📚 Learning: 2025-12-23T05:27:10.887Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4210
File: model/lib/mailbox/presentation_mailbox.dart:22-34
Timestamp: 2025-12-23T05:27:10.887Z
Learning: In the tmail-flutter codebase, the Action Required folder (actionRequiredFolder) is a virtual folder that is AI-curated and contains emails with the keyword "needs-action". Unlike the Favorites folder, users should NOT be able to drag emails into the Action Required folder. The UI correctly prevents this by not wrapping the Action Required folder item in a DragTarget (see mailbox_item_widget.dart).
Applied to files:
lib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: analyze-test (server_settings)
- GitHub Check: analyze-test (email_recovery)
- GitHub Check: analyze-test (fcm)
- GitHub Check: analyze-test (model)
- GitHub Check: analyze-test (default)
- GitHub Check: analyze-test (rule_filter)
- GitHub Check: analyze-test (core)
- GitHub Check: analyze-test (forward)
- GitHub Check: analyze-test (contact)
🔇 Additional comments (9)
core/lib/core.dart (1)
21-21: LGTM!The export addition is appropriately placed in the Extensions section and correctly exposes the new
AppTextColorsextension to consumers of the core library.core/lib/presentation/extensions/app_text_colors_extension.dart (2)
1-2: LGTM!The imports are appropriate and necessary for the extension implementation.
5-10: Implementation is correct.The
primaryTextColorgetter properly implements theme-aware text color selection usingTheme.of(this).brightness. Both requiredAppColorconstants (primaryDarkThemeTextColorandprimaryLightThemeTextColor) are defined incolor_extension.dart, so there are no compilation concerns.lib/features/mailbox_dashboard/presentation/widgets/local_email_draft/local_email_draft_item_widget.dart (6)
1-19: LGTM! Clean imports and well-defined callback types.The imports are appropriate and the typedef declarations provide clear type safety for the action callbacks.
21-39: LGTM! Well-structured widget with appropriate field declarations.The class structure is clean with proper separation of required data and optional callbacks.
42-194: LGTM! Solid widget structure with proper tap handling and layout.The widget properly handles user interaction with the
InkWellwrapper and conditionally renders the divider based on item position. The overall layout and spacing are well-structured.
144-144: All localization keys are properly defined:edit,saveAsDraft, anddiscardexist inintl_messages.arband are accessible via the generatedAppLocalizationsclass.
57-60: The EmailAvatarBuilder API usage is correct. The constructor acceptsavatarText(required) andavatarColors(optional), and the code at lines 57-60 properly passes both parameters matching the current API contract.Likely an incorrect or invalid review comment.
70-70: All extension methods are properly implemented and handle edge cases correctly.Verification confirms:
firstRecipientNamegracefully handles empty recipients by returningsenderNameas fallbackAppColor.steelGray200.asFilter()extension method exists incolor_extension.dartand properly implementsColorFilter?with defaultBlendMode.srcIngetSavedTime(String locale)is properly implemented with locale support viaDateFormat
Issue
#3358
Resolved
Screen.Recording.2025-03-26.at.09.02.02.mov
Screen.Recording.2025-03-26.at.09.16.31.mov
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.