From 6fc88f780cd7f8d246726ea5563c651185b88232 Mon Sep 17 00:00:00 2001 From: Dat Dang Date: Wed, 15 Apr 2026 14:25:37 +0700 Subject: [PATCH] TW-4308 Add labels for several messages part 2 (#4431) Add ChooseLabelModal, delegate, and update label UI components Wire add-labels action across Thread, Search, and Dashboard --- .../button/default_close_button_widget.dart | 5 +- .../add_list_label_to_list_email_state.dart | 21 +- ..._list_label_to_list_emails_interactor.dart | 31 ++- ...add_list_labels_to_list_emails_params.dart | 25 +++ ...dd_list_label_to_list_emails_delegate.dart | 93 ++++++++ .../widgets/choose_label_modal.dart | 199 ++++++++++++++++++ .../widgets/labels/label_list_item.dart | 52 ++++- .../email_action_interactor_bindings.dart | 4 +- .../bindings/mailbox_dashboard_bindings.dart | 6 + .../labels/handle_logic_label_extension.dart | 23 +- ...update_current_emails_flags_extension.dart | 18 +- .../mailbox_dashboard_view_web.dart | 2 + .../widgets/top_bar_thread_selection.dart | 17 ++ .../handle_press_email_selection_action.dart | 13 +- .../presentation/search_email_controller.dart | 10 + .../email/presentation/search_email_view.dart | 3 + .../handle_press_email_selection_action.dart | 8 +- .../presentation/thread_controller.dart | 107 ++++++---- .../thread/presentation/thread_view.dart | 4 + .../labels/add_label_to_thread_extension.dart | 2 +- 20 files changed, 560 insertions(+), 83 deletions(-) create mode 100644 lib/features/labels/domain/model/add_list_labels_to_list_emails_params.dart create mode 100644 lib/features/labels/presentation/delegates/add_list_label_to_list_emails_delegate.dart create mode 100644 lib/features/labels/presentation/widgets/choose_label_modal.dart diff --git a/core/lib/presentation/views/button/default_close_button_widget.dart b/core/lib/presentation/views/button/default_close_button_widget.dart index 8adb0f7e8..6813a85ef 100644 --- a/core/lib/presentation/views/button/default_close_button_widget.dart +++ b/core/lib/presentation/views/button/default_close_button_widget.dart @@ -6,18 +6,21 @@ import 'package:flutter/material.dart'; class DefaultCloseButtonWidget extends StatelessWidget { final String iconClose; final VoidCallback onTapActionCallback; + final bool isAlignTopEnd; const DefaultCloseButtonWidget({ super.key, required this.iconClose, required this.onTapActionCallback, + this.isAlignTopEnd = true, }); @override Widget build(BuildContext context) { return PositionedDirectional( top: 4, - end: 4, + end: isAlignTopEnd ? 4 : null, + start: isAlignTopEnd ? null : 4, child: TMailButtonWidget.fromIcon( icon: iconClose, iconSize: 24, diff --git a/lib/features/email/domain/state/labels/add_list_label_to_list_email_state.dart b/lib/features/email/domain/state/labels/add_list_label_to_list_email_state.dart index f3127b31b..d2edd040a 100644 --- a/lib/features/email/domain/state/labels/add_list_label_to_list_email_state.dart +++ b/lib/features/email/domain/state/labels/add_list_label_to_list_email_state.dart @@ -3,21 +3,29 @@ import 'package:core/presentation/state/success.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; +typedef OnSyncListLabelForListEmail = void Function( + List emailIds, + List labelKeywords, + {bool shouldRemove} +); + class AddingListLabelsToListEmails extends LoadingState {} class AddListLabelsToListEmailsSuccess extends UIState { final List emailIds; final List labelKeywords; final List labelDisplays; + final OnSyncListLabelForListEmail? onSync; AddListLabelsToListEmailsSuccess( this.emailIds, this.labelKeywords, - this.labelDisplays, - ); + this.labelDisplays, { + this.onSync, + }); @override - List get props => [emailIds, labelKeywords, labelDisplays]; + List get props => [emailIds, labelKeywords, labelDisplays]; } class AddListLabelsToListEmailsHasSomeFailure @@ -25,11 +33,12 @@ class AddListLabelsToListEmailsHasSomeFailure AddListLabelsToListEmailsHasSomeFailure( super.emailIds, super.labelKeywords, - super.labelDisplays, - ); + super.labelDisplays, { + super.onSync, + }); @override - List get props => [...super.props, 'hasSomeFailure']; + List get props => [...super.props, 'hasSomeFailure']; } class AddListLabelsToListEmailsFailure extends FeatureFailure { diff --git a/lib/features/email/domain/usecases/labels/add_list_label_to_list_emails_interactor.dart b/lib/features/email/domain/usecases/labels/add_list_label_to_list_emails_interactor.dart index c70218a5e..4c2840874 100644 --- a/lib/features/email/domain/usecases/labels/add_list_label_to_list_emails_interactor.dart +++ b/lib/features/email/domain/usecases/labels/add_list_label_to_list_emails_interactor.dart @@ -3,12 +3,11 @@ import 'package:core/presentation/state/success.dart'; import 'package:dartz/dartz.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; -import 'package:jmap_dart_client/jmap/mail/email/email.dart'; -import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:tmail_ui_user/features/composer/domain/exceptions/set_method_exception.dart'; import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart'; import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; import 'package:tmail_ui_user/features/email/domain/state/labels/add_list_label_to_list_email_state.dart'; +import 'package:tmail_ui_user/features/labels/domain/model/add_list_labels_to_list_emails_params.dart'; class AddListLabelToListEmailsInteractor { final EmailRepository _emailRepository; @@ -18,15 +17,13 @@ class AddListLabelToListEmailsInteractor { Stream> execute( Session session, AccountId accountId, - List emailIds, - List labelKeywords, - List labelDisplays, + AddListLabelsToListEmailsParams params, ) async* { try { - if (emailIds.isEmpty) { + if (params.emailIds.isEmpty) { yield Left(AddListLabelsToListEmailsFailure( exception: EmailIdsSuccessIsEmptyException(), - labelDisplays: labelDisplays, + labelDisplays: params.labelDisplays, )); return; } @@ -34,33 +31,35 @@ class AddListLabelToListEmailsInteractor { final result = await _emailRepository.addListLabelToListEmail( session, accountId, - emailIds, - labelKeywords, + params.emailIds, + params.labelKeywords, ); - if (emailIds.length == result.emailIdsSuccess.length) { + if (params.emailIds.length == result.emailIdsSuccess.length) { yield Right(AddListLabelsToListEmailsSuccess( result.emailIdsSuccess, - labelKeywords, - labelDisplays, + params.labelKeywords, + params.labelDisplays, + onSync: params.onSync, )); } else if (result.emailIdsSuccess.isEmpty) { yield Left(AddListLabelsToListEmailsFailure( exception: result.mapErrors.isNotEmpty ? SetMethodException(result.mapErrors) : EmailIdsSuccessIsEmptyException(), - labelDisplays: labelDisplays, + labelDisplays: params.labelDisplays, )); } else { yield Right(AddListLabelsToListEmailsHasSomeFailure( result.emailIdsSuccess, - labelKeywords, - labelDisplays, + params.labelKeywords, + params.labelDisplays, + onSync: params.onSync, )); } } catch (e) { yield Left(AddListLabelsToListEmailsFailure( exception: e, - labelDisplays: labelDisplays, + labelDisplays: params.labelDisplays, )); } } diff --git a/lib/features/labels/domain/model/add_list_labels_to_list_emails_params.dart b/lib/features/labels/domain/model/add_list_labels_to_list_emails_params.dart new file mode 100644 index 000000000..bc8f3df35 --- /dev/null +++ b/lib/features/labels/domain/model/add_list_labels_to_list_emails_params.dart @@ -0,0 +1,25 @@ +import 'package:equatable/equatable.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; +import 'package:labels/extensions/list_label_extension.dart'; +import 'package:labels/model/label.dart'; +import 'package:tmail_ui_user/features/email/domain/state/labels/add_list_label_to_list_email_state.dart'; + +class AddListLabelsToListEmailsParams extends Equatable { + final List