TF-3941 Thread Detail Thread level mark read/star, archive and spam
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# 68. Thread level actions
|
||||
|
||||
Date: 2025-08-18
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
- When user send an email, a copy of that email is created in Sent mailbox
|
||||
- If user sent an email, and recipients contains own user, there will be 2 emails in the thread, one in Sent mailbox, and another in Inbox mailbox (or the other mailbox if there is email rule for it).
|
||||
- This cause the thread has 2 emails with the exact same content.
|
||||
- Current solution is filtering out the email in Sent mailbox when displaying thread.
|
||||
- However, the thread level actions are ignoring these filtered-out emails.
|
||||
|
||||
## Decision
|
||||
|
||||
- All thread level actions will be applied to all emails of thread.
|
||||
|
||||
## Consequences
|
||||
|
||||
- If user move emails of thread to other mailbox, there will be email duplication due to the filtering conditions are not met anymore.
|
||||
@@ -46,6 +46,7 @@ extension EmailActionTypeExtension on EmailActionType {
|
||||
switch(this) {
|
||||
case EmailActionType.archiveMessage:
|
||||
case EmailActionType.moveToMailbox:
|
||||
case EmailActionType.archiveMessage:
|
||||
return AppLocalizations.of(context).movedToFolder(destinationPath ?? '');
|
||||
case EmailActionType.moveToTrash:
|
||||
return AppLocalizations.of(context).moved_to_trash;
|
||||
|
||||
@@ -207,9 +207,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
: _threadDetailController?.emailIdsPresentation[_currentEmailId];
|
||||
}
|
||||
|
||||
bool get isOnlyEmailInThread =>
|
||||
_threadDetailController?.emailIdsPresentation.length == 1;
|
||||
|
||||
bool get calendarEventProcessing => viewState.value.fold(
|
||||
(failure) => false,
|
||||
(success) => success is CalendarEventReplying);
|
||||
|
||||
@@ -252,40 +252,37 @@ class EmailView extends GetWidget<SingleEmailController> {
|
||||
onEmailActionClick: (presentationEmail, actionType) => controller.handleEmailAction(context, presentationEmail, actionType),
|
||||
isInsideThreadDetailView: isInsideThreadDetailView,
|
||||
emailLoaded: controller.currentEmailLoaded.value,
|
||||
onMoreActionClick: controller.isOnlyEmailInThread
|
||||
? null
|
||||
: (presentationEmail, position) => controller.emailActionReactor.handleMoreEmailAction(
|
||||
mailboxContain: controller.getMailboxContain(presentationEmail),
|
||||
presentationEmail: presentationEmail,
|
||||
position: position,
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
imagePaths: controller.imagePaths,
|
||||
username: controller.session?.username,
|
||||
handleEmailAction: (email, action) => controller.handleEmailAction(context, email, action),
|
||||
additionalActions: [
|
||||
EmailActionType.forward,
|
||||
EmailActionType.replyAll,
|
||||
EmailActionType.replyToList,
|
||||
EmailActionType.printAll,
|
||||
if (controller.responsiveUtils.isMobile(context))
|
||||
EmailActionType.moveToMailbox,
|
||||
if (!controller.responsiveUtils.isDesktop(context)) ...[
|
||||
EmailActionType.markAsStarred,
|
||||
EmailActionType.unMarkAsStarred,
|
||||
EmailActionType.moveToTrash,
|
||||
EmailActionType.deletePermanently,
|
||||
],
|
||||
],
|
||||
emailIsRead: presentationEmail.hasRead,
|
||||
openBottomSheetContextMenu: controller.mailboxDashBoardController.openBottomSheetContextMenu,
|
||||
openPopupMenu: controller.mailboxDashBoardController.openPopupMenu,
|
||||
),
|
||||
onMoreActionClick: (presentationEmail, position) => controller.emailActionReactor.handleMoreEmailAction(
|
||||
mailboxContain: controller.getMailboxContain(presentationEmail),
|
||||
presentationEmail: presentationEmail,
|
||||
position: position,
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
imagePaths: controller.imagePaths,
|
||||
username: controller.session?.username,
|
||||
handleEmailAction: (email, action) => controller.handleEmailAction(context, email, action),
|
||||
additionalActions: [
|
||||
EmailActionType.forward,
|
||||
EmailActionType.replyAll,
|
||||
EmailActionType.replyToList,
|
||||
EmailActionType.printAll,
|
||||
if (controller.responsiveUtils.isMobile(context))
|
||||
EmailActionType.moveToMailbox,
|
||||
if (!controller.responsiveUtils.isDesktop(context)) ...[
|
||||
EmailActionType.markAsStarred,
|
||||
EmailActionType.unMarkAsStarred,
|
||||
EmailActionType.moveToTrash,
|
||||
EmailActionType.deletePermanently,
|
||||
],
|
||||
],
|
||||
emailIsRead: presentationEmail.hasRead,
|
||||
openBottomSheetContextMenu: controller.mailboxDashBoardController.openBottomSheetContextMenu,
|
||||
openPopupMenu: controller.mailboxDashBoardController.openPopupMenu,
|
||||
),
|
||||
onToggleThreadDetailCollapseExpand: onToggleThreadDetailCollapseExpand,
|
||||
onTapAvatarActionClick: onToggleThreadDetailCollapseExpand,
|
||||
mailboxContain: presentationEmail.findMailboxContain(
|
||||
controller.mailboxDashBoardController.mapMailboxById,
|
||||
),
|
||||
isOnlyEmailInThread: controller.isOnlyEmailInThread,
|
||||
)),
|
||||
if (!controller.responsiveUtils.isMobile(context))
|
||||
const SizedBox(height: 24),
|
||||
|
||||
@@ -23,6 +23,16 @@ extension EmailExtension on Email {
|
||||
}
|
||||
}
|
||||
|
||||
bool checkEmailValidForThreadDetail(
|
||||
MailboxId sentMailboxId,
|
||||
String ownEmailAddress) {
|
||||
return id != null && (
|
||||
!inSentMailbox(sentMailboxId)
|
||||
|| !fromMe(ownEmailAddress)
|
||||
|| !recipientsHasMe(ownEmailAddress)
|
||||
);
|
||||
}
|
||||
|
||||
bool inSentMailbox(MailboxId sentMailboxId) {
|
||||
return mailboxIds?[sentMailboxId] == true;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ class EmailViewAppBarWidget extends StatelessWidget {
|
||||
final EdgeInsetsGeometry? iconPadding;
|
||||
final EdgeInsetsGeometry? iconMargin;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final bool isOnlyEmailInThread;
|
||||
|
||||
EmailViewAppBarWidget({
|
||||
Key? key,
|
||||
@@ -53,7 +52,6 @@ class EmailViewAppBarWidget extends StatelessWidget {
|
||||
this.iconPadding,
|
||||
this.iconMargin,
|
||||
this.padding,
|
||||
this.isOnlyEmailInThread = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -266,8 +264,7 @@ class EmailViewAppBarWidget extends StatelessWidget {
|
||||
_getMarkStarButton(appLocalizations),
|
||||
_getDeleteButton(appLocalizations),
|
||||
],
|
||||
if (!isOnlyEmailInThread)
|
||||
_getMoreButton(appLocalizations, isScreenWithShortestSide),
|
||||
_getMoreButton(appLocalizations, isScreenWithShortestSide),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,6 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
|
||||
final OnTapAvatarActionClick? onTapAvatarActionClick;
|
||||
final PresentationMailbox? mailboxContain;
|
||||
final bool showUnreadVisualization;
|
||||
final bool isOnlyEmailInThread;
|
||||
|
||||
const InformationSenderAndReceiverBuilder({
|
||||
Key? key,
|
||||
@@ -64,7 +63,6 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
|
||||
this.onTapAvatarActionClick,
|
||||
this.mailboxContain,
|
||||
this.showUnreadVisualization = false,
|
||||
this.isOnlyEmailInThread = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -201,7 +199,6 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
|
||||
iconPadding: const EdgeInsets.all(8),
|
||||
iconMargin: EdgeInsets.zero,
|
||||
padding: EdgeInsets.zero,
|
||||
isOnlyEmailInThread: isOnlyEmailInThread,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
+50
@@ -22,6 +22,7 @@ import 'package:jmap_dart_client/jmap/core/unsigned_int.dart';
|
||||
import 'package:jmap_dart_client/jmap/identities/identity.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/vacation/vacation_response.dart';
|
||||
import 'package:model/model.dart';
|
||||
@@ -181,6 +182,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_e
|
||||
import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/model/email_in_thread_detail_info.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_detail_ui_action.dart';
|
||||
import 'package:tmail_ui_user/main/deep_links/deep_link_data.dart';
|
||||
import 'package:tmail_ui_user/main/deep_links/deep_links_manager.dart';
|
||||
@@ -1563,6 +1565,54 @@ class MailboxDashBoardController extends ReloadableController
|
||||
);
|
||||
}
|
||||
|
||||
void moveMultipleEmailInThreadDetail(
|
||||
List<EmailInThreadDetailInfo> emailsInThreadDetailInfo, {
|
||||
required MailboxId destinationMailboxId,
|
||||
required EmailActionType emailActionType,
|
||||
}) {
|
||||
if (sessionCurrent == null || accountId.value == null) {
|
||||
consumeState(Stream.value(
|
||||
Left(MoveMultipleEmailToMailboxFailure(
|
||||
emailActionType,
|
||||
MoveAction.moving,
|
||||
NotFoundSessionException()
|
||||
))
|
||||
));
|
||||
return;
|
||||
}
|
||||
final currentMailboxes = <MailboxId, List<EmailId>>{};
|
||||
for (final email in emailsInThreadDetailInfo) {
|
||||
final mailboxIdContain = email.mailboxIdContain;
|
||||
if (mailboxIdContain == null) continue;
|
||||
|
||||
currentMailboxes.putIfAbsent(mailboxIdContain, () => []).add(email.emailId);
|
||||
}
|
||||
final moveRequest = MoveToMailboxRequest(
|
||||
currentMailboxes,
|
||||
destinationMailboxId,
|
||||
MoveAction.moving,
|
||||
emailActionType,
|
||||
destinationPath: currentContext == null
|
||||
? mapMailboxById[destinationMailboxId]?.name?.name
|
||||
: mapMailboxById[destinationMailboxId]?.getDisplayName(currentContext!),
|
||||
);
|
||||
final emailIdsWithReadStatus = Map.fromEntries(
|
||||
emailsInThreadDetailInfo.map(
|
||||
(email) => MapEntry(
|
||||
email.emailId,
|
||||
email.keywords?[KeyWordIdentifier.emailSeen] == true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
_moveSelectedEmailMultipleToMailboxAction(
|
||||
sessionCurrent!,
|
||||
accountId.value!,
|
||||
moveRequest,
|
||||
emailIdsWithReadStatus,
|
||||
);
|
||||
}
|
||||
|
||||
void deleteSelectionEmailsPermanently(
|
||||
BuildContext context,
|
||||
DeleteActionType actionType,
|
||||
|
||||
@@ -3,11 +3,14 @@ import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/user_name.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||
import 'package:model/extensions/account_id_extensions.dart';
|
||||
import 'package:model/extensions/email_id_extensions.dart';
|
||||
import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/extensions/email_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread/data/extensions/email_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread/data/model/email_cache.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/model/email_in_thread_detail_info.dart';
|
||||
|
||||
extension ListEmailExtension on List<Email> {
|
||||
Map<String, EmailCache> toMapCache(AccountId accountId, UserName userName) {
|
||||
@@ -39,4 +42,23 @@ extension ListEmailExtension on List<Email> {
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
List<EmailInThreadDetailInfo> toEmailsInThreadDetailInfo({
|
||||
required MailboxId? sentMailboxId,
|
||||
required String? ownEmailAddress,
|
||||
}) {
|
||||
return map(
|
||||
(email) => EmailInThreadDetailInfo(
|
||||
emailId: email.id!,
|
||||
keywords: email.keywords,
|
||||
mailboxIds: email.mailboxIds,
|
||||
isValidToDisplay: sentMailboxId == null || ownEmailAddress == null
|
||||
? true
|
||||
: email.checkEmailValidForThreadDetail(
|
||||
sentMailboxId,
|
||||
ownEmailAddress,
|
||||
),
|
||||
),
|
||||
).toList();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,9 @@ import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:model/extensions/presentation_email_extension.dart';
|
||||
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/extensions/email_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/model/email_in_thread_detail_info.dart';
|
||||
import 'package:tmail_ui_user/main/routes/app_routes.dart';
|
||||
import 'package:tmail_ui_user/main/routes/navigation_router.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_utils.dart';
|
||||
@@ -56,4 +58,23 @@ extension ListPresentationEmailExtensions on List<PresentationEmail> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
List<EmailInThreadDetailInfo> toEmailsInThreadDetailInfo({
|
||||
required MailboxId? sentMailboxId,
|
||||
required String? ownEmailAddress,
|
||||
}) {
|
||||
return map(
|
||||
(email) => EmailInThreadDetailInfo(
|
||||
emailId: email.id!,
|
||||
keywords: email.keywords,
|
||||
mailboxIds: email.mailboxIds,
|
||||
isValidToDisplay: sentMailboxId == null || ownEmailAddress == null
|
||||
? true
|
||||
: email.toEmail().checkEmailValidForThreadDetail(
|
||||
sentMailboxId,
|
||||
ownEmailAddress,
|
||||
),
|
||||
),
|
||||
).toList();
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import 'package:model/extensions/list_email_extension.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/extensions/email_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_data_source.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/exceptions/empty_thread_detail_exception.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/model/email_in_thread_detail_info.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/repository/thread_detail_repository.dart';
|
||||
|
||||
class ThreadDetailRepositoryImpl implements ThreadDetailRepository {
|
||||
@@ -20,14 +21,13 @@ class ThreadDetailRepositoryImpl implements ThreadDetailRepository {
|
||||
final Map<DataSourceType, ThreadDetailDataSource> threadDetailDataSource;
|
||||
|
||||
@override
|
||||
Future<List<EmailId>> getThreadById(
|
||||
Future<List<EmailInThreadDetailInfo>> getThreadById(
|
||||
ThreadId threadId,
|
||||
Session session,
|
||||
AccountId accountId,
|
||||
MailboxId sentMailboxId,
|
||||
String ownEmailAddress, {
|
||||
required EmailId? selectedEmailId,
|
||||
}) async {
|
||||
String ownEmailAddress,
|
||||
) async {
|
||||
final originalEmailIds = await threadDetailDataSource[DataSourceType.network]!
|
||||
.getThreadById(threadId, accountId);
|
||||
|
||||
@@ -44,27 +44,24 @@ class ThreadDetailRepositoryImpl implements ThreadDetailRepository {
|
||||
emailIds,
|
||||
sentMailboxId,
|
||||
ownEmailAddress,
|
||||
selectedEmailId: selectedEmailId,
|
||||
))
|
||||
);
|
||||
|
||||
return filteredEmailIds
|
||||
.reduce((prev, curr) => prev + curr)
|
||||
.reduce((prev, curr) => prev..addAll(curr))
|
||||
.sortWithResult(EmailComparator(
|
||||
EmailComparatorProperty.receivedAt
|
||||
)..setIsAscending(true))
|
||||
.map((e) => e.id!)
|
||||
.toList();
|
||||
.toEmailsInThreadDetailInfo();
|
||||
}
|
||||
|
||||
Future<List<Email>> _filterBadEmails(
|
||||
Future<Map<Email, bool>> _filterBadEmails(
|
||||
Session session,
|
||||
AccountId accountId,
|
||||
List<EmailId> emailIds,
|
||||
MailboxId sentMailboxId,
|
||||
String ownEmailAddress, {
|
||||
required EmailId? selectedEmailId,
|
||||
}) async {
|
||||
String ownEmailAddress,
|
||||
) async {
|
||||
int retry = 3;
|
||||
while (retry > 0) {
|
||||
try {
|
||||
@@ -81,37 +78,23 @@ class ThreadDetailRepositoryImpl implements ThreadDetailRepository {
|
||||
EmailProperty.cc,
|
||||
EmailProperty.bcc,
|
||||
EmailProperty.receivedAt,
|
||||
EmailProperty.keywords,
|
||||
}),
|
||||
);
|
||||
return emails
|
||||
.where((email) => checkEmailValidForThreadDetail(
|
||||
email,
|
||||
sentMailboxId,
|
||||
ownEmailAddress,
|
||||
selectedEmailId: selectedEmailId,
|
||||
))
|
||||
.toList();
|
||||
return {
|
||||
for (final email in emails)
|
||||
email: email.checkEmailValidForThreadDetail(
|
||||
sentMailboxId,
|
||||
ownEmailAddress,
|
||||
),
|
||||
};
|
||||
} catch (e) {
|
||||
retry--;
|
||||
|
||||
if (retry <= 0) rethrow;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
bool checkEmailValidForThreadDetail(
|
||||
Email email,
|
||||
MailboxId sentMailboxId,
|
||||
String ownEmailAddress, {
|
||||
required EmailId? selectedEmailId
|
||||
}) {
|
||||
return email.id != null && (
|
||||
!email.inSentMailbox(sentMailboxId)
|
||||
|| !email.fromMe(ownEmailAddress)
|
||||
|| !email.recipientsHasMe(ownEmailAddress)
|
||||
|| email.id == selectedEmailId
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -130,4 +113,28 @@ class ThreadDetailRepositoryImpl implements ThreadDetailRepository {
|
||||
return threadDetailDataSource[DataSourceType.local]!
|
||||
.getThreadDetailStatus();
|
||||
}
|
||||
}
|
||||
|
||||
extension SortListEmailInThreadDetailInfo on Map<Email, bool> {
|
||||
Map<Email, bool> sortWithResult(
|
||||
EmailComparator comparator,
|
||||
) {
|
||||
final originalEmails = keys.toList();
|
||||
final sortedEmails = originalEmails.sortWithResult(comparator);
|
||||
return {
|
||||
for (final email in sortedEmails)
|
||||
email: this[email] ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
List<EmailInThreadDetailInfo> toEmailsInThreadDetailInfo() {
|
||||
return entries.map(
|
||||
(entry) => EmailInThreadDetailInfo(
|
||||
emailId: entry.key.id!,
|
||||
keywords: entry.key.keywords,
|
||||
mailboxIds: entry.key.mailboxIds,
|
||||
isValidToDisplay: entry.value,
|
||||
),
|
||||
).toList();
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/model/email_in_thread_detail_info.dart';
|
||||
|
||||
extension ListEmailInThreadDetailInfoExtension on List<EmailInThreadDetailInfo> {
|
||||
List<EmailId> get emailIdsToDisplay =>
|
||||
where((email) => email.isValidToDisplay)
|
||||
.map((email) => email.emailId)
|
||||
.toList();
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'package:collection/collection.dart';
|
||||
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:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||
|
||||
class EmailInThreadDetailInfo with EquatableMixin {
|
||||
const EmailInThreadDetailInfo({
|
||||
required this.emailId,
|
||||
required this.keywords,
|
||||
required this.mailboxIds,
|
||||
required this.isValidToDisplay,
|
||||
});
|
||||
|
||||
final EmailId emailId;
|
||||
final Map<KeyWordIdentifier, bool>? keywords;
|
||||
final Map<MailboxId, bool>? mailboxIds;
|
||||
final bool isValidToDisplay;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [emailId, keywords, mailboxIds, isValidToDisplay];
|
||||
|
||||
EmailInThreadDetailInfo copyWith({
|
||||
EmailId? emailId,
|
||||
Map<KeyWordIdentifier, bool>? keywords,
|
||||
Map<MailboxId, bool>? mailboxIds,
|
||||
bool? isValidToDisplay,
|
||||
}) {
|
||||
return EmailInThreadDetailInfo(
|
||||
emailId: emailId ?? this.emailId,
|
||||
keywords: keywords ?? this.keywords,
|
||||
mailboxIds: mailboxIds ?? this.mailboxIds,
|
||||
isValidToDisplay: isValidToDisplay ?? this.isValidToDisplay,
|
||||
);
|
||||
}
|
||||
|
||||
MailboxId? get mailboxIdContain {
|
||||
return mailboxIds?.entries
|
||||
.firstWhereOrNull((entry) => entry.value)
|
||||
?.key;
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,16 @@ import 'package:jmap_dart_client/jmap/core/properties/properties.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/mailbox/mailbox.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/model/email_in_thread_detail_info.dart';
|
||||
|
||||
abstract class ThreadDetailRepository {
|
||||
Future<List<EmailId>> getThreadById(
|
||||
Future<List<EmailInThreadDetailInfo>> getThreadById(
|
||||
ThreadId threadId,
|
||||
Session session,
|
||||
AccountId accountId,
|
||||
MailboxId sentMailboxId,
|
||||
String ownEmailAddress, {
|
||||
required EmailId? selectedEmailId,
|
||||
});
|
||||
String ownEmailAddress,
|
||||
);
|
||||
|
||||
Future<List<Email>> getEmailsByIds(
|
||||
Session session,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/model/email_in_thread_detail_info.dart';
|
||||
|
||||
class GettingThreadById extends LoadingState {
|
||||
final bool updateCurrentThreadDetail;
|
||||
@@ -16,25 +17,32 @@ class GetThreadByIdSuccess extends UIState {
|
||||
final ThreadId? threadId;
|
||||
final bool updateCurrentThreadDetail;
|
||||
final bool skipLoadingMetadata;
|
||||
final List<EmailInThreadDetailInfo> emailsInThreadDetailInfo;
|
||||
|
||||
GetThreadByIdSuccess(
|
||||
this.emailIds, {
|
||||
required this.threadId,
|
||||
this.updateCurrentThreadDetail = false,
|
||||
this.skipLoadingMetadata = false,
|
||||
this.emailsInThreadDetailInfo = const [],
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
emailIds,
|
||||
threadId,
|
||||
emailsInThreadDetailInfo,
|
||||
updateCurrentThreadDetail,
|
||||
skipLoadingMetadata,
|
||||
];
|
||||
}
|
||||
|
||||
class PreloadEmailIdsInThreadSuccess extends GetThreadByIdSuccess {
|
||||
PreloadEmailIdsInThreadSuccess(super.emailIds, {required super.threadId});
|
||||
PreloadEmailIdsInThreadSuccess(
|
||||
super.emailIds, {
|
||||
required super.threadId,
|
||||
super.emailsInThreadDetailInfo,
|
||||
});
|
||||
}
|
||||
|
||||
class GetThreadByIdFailure extends FeatureFailure {
|
||||
|
||||
@@ -6,6 +6,7 @@ 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/mailbox/mailbox.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/extensions/list_email_in_thread_detail_info_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/repository/thread_detail_repository.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||
|
||||
@@ -20,7 +21,6 @@ class GetThreadByIdInteractor {
|
||||
AccountId accountId,
|
||||
MailboxId sentMailboxId,
|
||||
String ownEmailAddress, {
|
||||
required EmailId? selectedEmailId,
|
||||
bool updateCurrentThreadDetail = false,
|
||||
}) async* {
|
||||
try {
|
||||
@@ -33,13 +33,13 @@ class GetThreadByIdInteractor {
|
||||
accountId,
|
||||
sentMailboxId,
|
||||
ownEmailAddress,
|
||||
selectedEmailId: selectedEmailId,
|
||||
);
|
||||
|
||||
yield Right(GetThreadByIdSuccess(
|
||||
result,
|
||||
result.emailIdsToDisplay,
|
||||
threadId: threadId,
|
||||
updateCurrentThreadDetail: updateCurrentThreadDetail,
|
||||
emailsInThreadDetailInfo: result,
|
||||
));
|
||||
} catch (e) {
|
||||
logError('GetEmailIdsByThreadIdInteractor::execute(): Exception: $e');
|
||||
@@ -51,7 +51,6 @@ class GetThreadByIdInteractor {
|
||||
accountId,
|
||||
sentMailboxId,
|
||||
ownEmailAddress,
|
||||
selectedEmailId: selectedEmailId,
|
||||
),
|
||||
updateCurrentThreadDetail: updateCurrentThreadDetail,
|
||||
));
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
|
||||
extension GetThreadDetailActionStatus on ThreadDetailController {
|
||||
bool get threadDetailIsStarred {
|
||||
return emailsInThreadDetailInfo.every(
|
||||
(email) {
|
||||
return email.keywords?.containsKey(KeyWordIdentifier.emailFlagged) ==
|
||||
true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
bool get threadDetailIsRead {
|
||||
return emailsInThreadDetailInfo.every(
|
||||
(email) {
|
||||
return email.keywords?.containsKey(KeyWordIdentifier.emailSeen) == true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
MailboxId? getMailboxIdByRole(Role role) {
|
||||
return mailboxDashBoardController.mapDefaultMailboxIdByRole[role];
|
||||
}
|
||||
|
||||
bool get threadDetailIsArchived {
|
||||
final archiveMailboxId = getMailboxIdByRole(
|
||||
PresentationMailbox.roleArchive,
|
||||
);
|
||||
return emailsInThreadDetailInfo.every(
|
||||
(email) {
|
||||
return email.mailboxIds?[archiveMailboxId] == true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
bool get threadDetailIsSpam {
|
||||
final spamMailboxId =
|
||||
getMailboxIdByRole(PresentationMailbox.roleJunk) ??
|
||||
getMailboxIdByRole(PresentationMailbox.roleSpam);
|
||||
return emailsInThreadDetailInfo.every(
|
||||
(email) {
|
||||
return email.mailboxIds?[spamMailboxId] == true;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:model/email/email_action_type.dart';
|
||||
import 'package:model/extensions/presentation_email_extension.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/email_view.dart';
|
||||
import 'package:model/email/email_in_thread_status.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_open_context_menu_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/get_thread_detail_email_mailbox_contains.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/load_more_thread_detail_emails.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_on_email_action_click.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_open_email_address_detail_action.dart';
|
||||
@@ -14,7 +11,6 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/extension/togg
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_collapsed_email.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_load_more_circle.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||
|
||||
extension GetThreadDetailEmailViews on ThreadDetailController {
|
||||
List<Widget> getThreadDetailEmailViews() {
|
||||
@@ -65,33 +61,6 @@ extension GetThreadDetailEmailViews on ThreadDetailController {
|
||||
),
|
||||
emailLoaded: null,
|
||||
onEmailActionClick: threadDetailOnEmailActionClick,
|
||||
onMoreActionClick: (presentationEmail, position) => emailActionReactor.handleMoreEmailAction(
|
||||
mailboxContain: getThreadDetailEmailMailboxContains(presentationEmail),
|
||||
presentationEmail: presentationEmail,
|
||||
position: position,
|
||||
responsiveUtils: responsiveUtils,
|
||||
imagePaths: imagePaths,
|
||||
username: session?.username,
|
||||
handleEmailAction: threadDetailOnEmailActionClick,
|
||||
additionalActions: [
|
||||
EmailActionType.forward,
|
||||
EmailActionType.replyAll,
|
||||
EmailActionType.replyToList,
|
||||
EmailActionType.printAll,
|
||||
if (currentContext != null &&
|
||||
responsiveUtils.isMobile(currentContext!))
|
||||
EmailActionType.moveToMailbox,
|
||||
if (!responsiveUtils.isDesktop(currentContext!)) ...[
|
||||
EmailActionType.markAsStarred,
|
||||
EmailActionType.unMarkAsStarred,
|
||||
EmailActionType.moveToTrash,
|
||||
EmailActionType.deletePermanently,
|
||||
],
|
||||
],
|
||||
emailIsRead: presentationEmail.hasRead,
|
||||
openBottomSheetContextMenu: mailboxDashBoardController.openBottomSheetContextMenu,
|
||||
openPopupMenu: mailboxDashBoardController.openPopupMenu,
|
||||
),
|
||||
openEmailAddressDetailAction: (_, emailAddress) {
|
||||
openEmailAddressDetailAction(emailAddress);
|
||||
},
|
||||
|
||||
+11
@@ -11,15 +11,22 @@ extension HandleGetEmailIdsByThreadIdSuccess on ThreadDetailController {
|
||||
}
|
||||
|
||||
final allEmailIds = success.emailIds;
|
||||
final allEmailsInThreadDetailInfo = success.emailsInThreadDetailInfo;
|
||||
if (success.updateCurrentThreadDetail) {
|
||||
final newEmailIds = allEmailIds.where(
|
||||
(emailId) => !emailIdsPresentation.keys.contains(emailId),
|
||||
);
|
||||
final newEmailsInThreadDetailInfo = allEmailsInThreadDetailInfo.where(
|
||||
(email) => !emailsInThreadDetailInfo.contains(email),
|
||||
);
|
||||
emailIdsPresentation
|
||||
..removeWhere((key, _) => !allEmailIds.contains(key))
|
||||
..addEntries(
|
||||
newEmailIds.map((emailId) => MapEntry(emailId, null)),
|
||||
);
|
||||
emailsInThreadDetailInfo
|
||||
..removeWhere((e) => !allEmailsInThreadDetailInfo.contains(e))
|
||||
..addAll(newEmailsInThreadDetailInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -31,5 +38,9 @@ extension HandleGetEmailIdsByThreadIdSuccess on ThreadDetailController {
|
||||
? emailIdsPresentation[id] ?? selectedEmail
|
||||
: null,
|
||||
};
|
||||
if ((isThreadDetailEnabled && success is! PreloadEmailIdsInThreadSuccess) ||
|
||||
(!isThreadDetailEnabled && success is PreloadEmailIdsInThreadSuccess)) {
|
||||
emailsInThreadDetailInfo.value = allEmailsInThreadDetailInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:model/email/read_actions.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/toggle_thread_detail_collape_expand.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||
|
||||
extension HandleMarkMultipleEmailsReadSuccess on ThreadDetailController {
|
||||
void handleMarkMultipleEmailsReadSuccess(
|
||||
UIState success,
|
||||
ReadActions readActions,
|
||||
List<EmailId> emailIds,
|
||||
) {
|
||||
mailboxDashBoardController.consumeState(Stream.value(Right(success)));
|
||||
if (readActions == ReadActions.markAsRead) return;
|
||||
|
||||
if (emailsInThreadDetailInfo.length == 1) {
|
||||
closeThreadDetailAction(currentContext);
|
||||
return;
|
||||
}
|
||||
|
||||
final expandedEmailId = currentExpandedEmailId.value;
|
||||
if (expandedEmailId == null) return;
|
||||
|
||||
final expandedEmail = emailIdsPresentation[expandedEmailId];
|
||||
if (emailIds.contains(expandedEmailId) && expandedEmail != null) {
|
||||
toggleThreadDetailCollapeExpand(expandedEmail);
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -7,6 +7,7 @@ import 'package:model/extensions/keyword_identifier_extension.dart';
|
||||
import 'package:model/extensions/list_email_extension.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/extensions/email_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread/data/extensions/list_email_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart';
|
||||
@@ -34,6 +35,10 @@ extension HandleRefreshThreadDetailAction on ThreadDetailController {
|
||||
[currentEmailId],
|
||||
updateCurrentThreadDetail: true,
|
||||
threadId: mailboxDashBoardController.selectedEmail.value?.threadId,
|
||||
emailsInThreadDetailInfo: action.emailChangeResponse.updated?.toEmailsInThreadDetailInfo(
|
||||
sentMailboxId: sentMailboxId,
|
||||
ownEmailAddress: ownEmailAddress,
|
||||
) ?? [],
|
||||
))));
|
||||
}
|
||||
|
||||
@@ -71,6 +76,14 @@ extension HandleRefreshThreadDetailAction on ThreadDetailController {
|
||||
updated: emailsUpdated,
|
||||
destroyed: emailIdsDestroyed.toList(),
|
||||
);
|
||||
final afterRefreshedThreadDetailInfo = ThreadDetailPresentationUtils.refreshThreadDetailInfo(
|
||||
original: emailsInThreadDetailInfo,
|
||||
created: emailsCreated,
|
||||
updated: emailsUpdated,
|
||||
destroyed: emailIdsDestroyed.toList(),
|
||||
sentMailboxId: sentMailboxId,
|
||||
ownEmailAddress: ownEmailAddress,
|
||||
);
|
||||
|
||||
if (afterRefreshedEmailIds.isEmpty) {
|
||||
closeThreadDetailAction(currentContext);
|
||||
@@ -81,6 +94,7 @@ extension HandleRefreshThreadDetailAction on ThreadDetailController {
|
||||
afterRefreshedEmailIds,
|
||||
threadId: currentThreadId,
|
||||
updateCurrentThreadDetail: true,
|
||||
emailsInThreadDetailInfo: afterRefreshedThreadDetailInfo,
|
||||
))));
|
||||
consumeState(Stream.value(Right(GetEmailsByIdsSuccess(
|
||||
afterRefreshedEmails,
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||
import 'package:model/email/email_action_type.dart';
|
||||
import 'package:model/email/mark_star_action.dart';
|
||||
import 'package:model/email/read_actions.dart';
|
||||
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||
import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/model/context_item_email_action.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/model/popup_menu_item_email_action.dart';
|
||||
import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_open_context_menu_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread/domain/state/mark_as_star_multiple_email_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/get_thread_detail_action_status.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||
|
||||
extension OnThreadDetailActionClick on ThreadDetailController {
|
||||
void onThreadDetailActionClick(EmailActionType threadDetailActionType) {
|
||||
switch (threadDetailActionType) {
|
||||
case EmailActionType.markAsRead:
|
||||
case EmailActionType.markAsUnread:
|
||||
if (session == null || accountId == null) {
|
||||
consumeState(Stream.value(Left(MarkAsMultipleEmailReadFailure(
|
||||
threadDetailActionType == EmailActionType.markAsRead
|
||||
? ReadActions.markAsRead
|
||||
: ReadActions.markAsUnread,
|
||||
NotFoundSessionException(),
|
||||
))));
|
||||
return;
|
||||
}
|
||||
consumeState(markAsMultipleEmailReadInteractor.execute(
|
||||
session!,
|
||||
accountId!,
|
||||
emailsInThreadDetailInfo.map((e) => e.emailId).toList(),
|
||||
threadDetailActionType == EmailActionType.markAsRead
|
||||
? ReadActions.markAsRead
|
||||
: ReadActions.markAsUnread,
|
||||
{},
|
||||
));
|
||||
break;
|
||||
case EmailActionType.markAsStarred:
|
||||
case EmailActionType.unMarkAsStarred:
|
||||
if (session == null || accountId == null) {
|
||||
consumeState(Stream.value(Left(MarkAsStarMultipleEmailFailure(
|
||||
threadDetailActionType == EmailActionType.markAsStarred
|
||||
? MarkStarAction.markStar
|
||||
: MarkStarAction.unMarkStar,
|
||||
NotFoundSessionException(),
|
||||
))));
|
||||
return;
|
||||
}
|
||||
consumeState(markAsStarMultipleEmailInteractor.execute(
|
||||
session!,
|
||||
accountId!,
|
||||
emailsInThreadDetailInfo.map((e) => e.emailId).toList(),
|
||||
threadDetailActionType == EmailActionType.markAsStarred
|
||||
? MarkStarAction.markStar
|
||||
: MarkStarAction.unMarkStar,
|
||||
));
|
||||
break;
|
||||
case EmailActionType.archiveMessage:
|
||||
final mailboxId = mailboxDashBoardController.getMailboxIdByRole(
|
||||
PresentationMailbox.roleArchive,
|
||||
);
|
||||
if (mailboxId == null) return;
|
||||
|
||||
_moveToMailbox(mailboxId, threadDetailActionType);
|
||||
break;
|
||||
case EmailActionType.moveToSpam:
|
||||
final mailboxId = mailboxDashBoardController.getMailboxIdByRole(
|
||||
PresentationMailbox.roleJunk,
|
||||
) ?? mailboxDashBoardController.getMailboxIdByRole(
|
||||
PresentationMailbox.roleSpam,
|
||||
);
|
||||
if (mailboxId == null) return;
|
||||
|
||||
_moveToMailbox(mailboxId, threadDetailActionType);
|
||||
break;
|
||||
case EmailActionType.unSpam:
|
||||
final mailboxId = mailboxDashBoardController.getMailboxIdByRole(
|
||||
PresentationMailbox.roleInbox,
|
||||
);
|
||||
if (mailboxId == null) return;
|
||||
|
||||
_moveToMailbox(mailboxId, threadDetailActionType);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void onThreadDetailMoreActionClick(RelativeRect? position) {
|
||||
if (currentContext == null) return;
|
||||
|
||||
final moreActions = [
|
||||
threadDetailIsRead
|
||||
? EmailActionType.markAsUnread
|
||||
: EmailActionType.markAsRead,
|
||||
threadDetailIsStarred
|
||||
? EmailActionType.unMarkAsStarred
|
||||
: EmailActionType.markAsStarred,
|
||||
if (!threadDetailIsArchived) EmailActionType.archiveMessage,
|
||||
threadDetailIsSpam ? EmailActionType.unSpam : EmailActionType.moveToSpam,
|
||||
];
|
||||
|
||||
if (position == null) {
|
||||
mailboxDashBoardController.openBottomSheetContextMenu(
|
||||
context: currentContext!,
|
||||
itemActions: moreActions
|
||||
.map(
|
||||
(action) => ContextItemEmailAction(
|
||||
action,
|
||||
AppLocalizations.of(currentContext!),
|
||||
imagePaths,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onContextMenuActionClick: (action) {
|
||||
popBack();
|
||||
onThreadDetailActionClick(action.action);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
mailboxDashBoardController.openPopupMenu(
|
||||
currentContext!,
|
||||
position,
|
||||
moreActions.map((action) {
|
||||
return PopupMenuItem(
|
||||
key: Key('${action.name}_action'),
|
||||
padding: EdgeInsets.zero,
|
||||
child: PopupMenuItemActionWidget(
|
||||
menuAction: PopupMenuItemEmailAction(
|
||||
action,
|
||||
AppLocalizations.of(currentContext!),
|
||||
imagePaths,
|
||||
),
|
||||
menuActionClick: (_) {
|
||||
popBack();
|
||||
onThreadDetailActionClick(action);
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _moveToMailbox(MailboxId mailboxId, EmailActionType emailActionType) {
|
||||
closeThreadDetailAction(currentContext);
|
||||
mailboxDashBoardController.moveMultipleEmailInThreadDetail(
|
||||
emailsInThreadDetailInfo,
|
||||
destinationMailboxId: mailboxId,
|
||||
emailActionType: emailActionType,
|
||||
);
|
||||
}
|
||||
}
|
||||
-322
@@ -1,18 +1,10 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
|
||||
import 'package:model/email/email_action_type.dart';
|
||||
import 'package:model/email/mark_star_action.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:model/email/read_actions.dart';
|
||||
import 'package:model/extensions/presentation_email_extension.dart';
|
||||
import 'package:model/extensions/session_extension.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||
|
||||
extension ThreadDetailOnEmailActionClick on ThreadDetailController {
|
||||
void threadDetailOnEmailActionClick(
|
||||
@@ -23,60 +15,9 @@ extension ThreadDetailOnEmailActionClick on ThreadDetailController {
|
||||
case EmailActionType.markAsRead:
|
||||
_markRead(presentationEmail);
|
||||
break;
|
||||
case EmailActionType.markAsUnread:
|
||||
_unRead(presentationEmail);
|
||||
break;
|
||||
case EmailActionType.markAsStarred:
|
||||
_markStar(presentationEmail, MarkStarAction.markStar);
|
||||
break;
|
||||
case EmailActionType.unMarkAsStarred:
|
||||
_markStar(presentationEmail, MarkStarAction.unMarkStar);
|
||||
break;
|
||||
case EmailActionType.moveToMailbox:
|
||||
_moveEmail(presentationEmail);
|
||||
break;
|
||||
case EmailActionType.moveToTrash:
|
||||
_trashEmail(presentationEmail);
|
||||
break;
|
||||
case EmailActionType.deletePermanently:
|
||||
_deleteEmailPermanently(presentationEmail);
|
||||
break;
|
||||
case EmailActionType.moveToSpam:
|
||||
_markEmailSpam(presentationEmail);
|
||||
break;
|
||||
case EmailActionType.unSpam:
|
||||
_unSpamEmail(presentationEmail);
|
||||
break;
|
||||
case EmailActionType.createRule:
|
||||
_quickCreateRule(presentationEmail);
|
||||
break;
|
||||
case EmailActionType.unsubscribe:
|
||||
_unsubscribeEmail(presentationEmail);
|
||||
break;
|
||||
case EmailActionType.archiveMessage:
|
||||
_archiveMessage(presentationEmail);
|
||||
break;
|
||||
case EmailActionType.printAll:
|
||||
_printEmail(presentationEmail);
|
||||
break;
|
||||
case EmailActionType.downloadMessageAsEML:
|
||||
_downloadMessageAsEML(presentationEmail);
|
||||
break;
|
||||
case EmailActionType.editAsNewEmail:
|
||||
_editAsNewEmail(presentationEmail);
|
||||
break;
|
||||
case EmailActionType.reply:
|
||||
_replyEmail(presentationEmail);
|
||||
break;
|
||||
case EmailActionType.replyAll:
|
||||
_replyAll(presentationEmail);
|
||||
break;
|
||||
case EmailActionType.replyToList:
|
||||
_replyToList(presentationEmail);
|
||||
break;
|
||||
case EmailActionType.forward:
|
||||
_forward(presentationEmail);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -93,212 +34,6 @@ extension ThreadDetailOnEmailActionClick on ThreadDetailController {
|
||||
));
|
||||
}
|
||||
|
||||
void _unRead(PresentationEmail presentationEmail) {
|
||||
if (session == null || accountId == null || !presentationEmail.hasRead) return;
|
||||
|
||||
consumeState(emailActionReactor.markAsEmailRead(
|
||||
session!,
|
||||
accountId!,
|
||||
presentationEmail,
|
||||
readAction: ReadActions.markAsUnread,
|
||||
));
|
||||
}
|
||||
|
||||
void _markStar(
|
||||
PresentationEmail presentationEmail,
|
||||
MarkStarAction markStarAction,
|
||||
) {
|
||||
if (session == null ||
|
||||
accountId == null ||
|
||||
(presentationEmail.hasStarred && markStarAction == MarkStarAction.markStar) ||
|
||||
(!presentationEmail.hasStarred && markStarAction == MarkStarAction.unMarkStar)) {
|
||||
return;
|
||||
}
|
||||
|
||||
consumeState(emailActionReactor.markAsStarEmail(
|
||||
session!,
|
||||
accountId!,
|
||||
presentationEmail,
|
||||
markStarAction: markStarAction,
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> _moveEmail(PresentationEmail presentationEmail) async {
|
||||
if (session == null || accountId == null) return;
|
||||
|
||||
final moveActionRequest = await emailActionReactor.moveToMailbox(
|
||||
session!,
|
||||
accountId!,
|
||||
presentationEmail,
|
||||
mapMailbox: mailboxDashBoardController.mapMailboxById,
|
||||
selectedMailbox: presentationEmail.findMailboxContain(
|
||||
mailboxDashBoardController.mapMailboxById,
|
||||
),
|
||||
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
|
||||
);
|
||||
|
||||
if (moveActionRequest == null) return;
|
||||
|
||||
mailboxDashBoardController.moveToMailbox(
|
||||
session!,
|
||||
accountId!,
|
||||
moveActionRequest.moveRequest,
|
||||
moveActionRequest.emailIdsWithReadStatus,
|
||||
);
|
||||
if (emailIdsPresentation.length == 1) closeThreadDetailAction(currentContext);
|
||||
}
|
||||
|
||||
void _trashEmail(PresentationEmail presentationEmail) {
|
||||
if (session == null || accountId == null) return;
|
||||
|
||||
final moveActionRequest = emailActionReactor.moveToTrash(
|
||||
presentationEmail,
|
||||
mapMailbox: mailboxDashBoardController.mapMailboxById,
|
||||
selectedMailbox: mailboxDashBoardController.selectedMailbox.value,
|
||||
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
|
||||
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
|
||||
);
|
||||
if (moveActionRequest == null) return;
|
||||
mailboxDashBoardController.moveToMailbox(
|
||||
session!,
|
||||
accountId!,
|
||||
moveActionRequest.moveRequest,
|
||||
moveActionRequest.emailIdsWithReadStatus,
|
||||
);
|
||||
if (emailIdsPresentation.length == 1) closeThreadDetailAction(currentContext);
|
||||
}
|
||||
|
||||
void _markEmailSpam(PresentationEmail presentationEmail) {
|
||||
if (session == null || accountId == null) return;
|
||||
|
||||
final moveActionRequest = emailActionReactor.moveToSpam(
|
||||
presentationEmail,
|
||||
mapMailbox: mailboxDashBoardController.mapMailboxById,
|
||||
selectedMailbox: mailboxDashBoardController.selectedMailbox.value,
|
||||
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
|
||||
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
|
||||
);
|
||||
if (moveActionRequest == null) return;
|
||||
mailboxDashBoardController.moveToMailbox(
|
||||
session!,
|
||||
accountId!,
|
||||
moveActionRequest.moveRequest,
|
||||
moveActionRequest.emailIdsWithReadStatus,
|
||||
);
|
||||
if (emailIdsPresentation.length == 1) closeThreadDetailAction(currentContext);
|
||||
}
|
||||
|
||||
void _unSpamEmail(PresentationEmail presentationEmail) {
|
||||
if (session == null || accountId == null) return;
|
||||
|
||||
final moveActionRequest = emailActionReactor.unSpam(
|
||||
presentationEmail,
|
||||
mapMailbox: mailboxDashBoardController.mapMailboxById,
|
||||
selectedMailbox: mailboxDashBoardController.selectedMailbox.value,
|
||||
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
|
||||
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
|
||||
);
|
||||
if (moveActionRequest == null) return;
|
||||
mailboxDashBoardController.moveToMailbox(
|
||||
session!,
|
||||
accountId!,
|
||||
moveActionRequest.moveRequest,
|
||||
moveActionRequest.emailIdsWithReadStatus,
|
||||
);
|
||||
if (emailIdsPresentation.length == 1) closeThreadDetailAction(currentContext);
|
||||
}
|
||||
|
||||
void _deleteEmailPermanently(PresentationEmail email) {
|
||||
emailActionReactor.deleteEmailPermanently(
|
||||
email,
|
||||
onDeleteEmailRequest: (email) {
|
||||
popBack();
|
||||
mailboxDashBoardController.deleteEmailPermanently(email);
|
||||
},
|
||||
responsiveUtils: responsiveUtils,
|
||||
imagePaths: imagePaths,
|
||||
);
|
||||
if (emailIdsPresentation.length == 1) closeThreadDetailAction(currentContext);
|
||||
}
|
||||
|
||||
void _quickCreateRule(PresentationEmail presentationEmail) {
|
||||
final emailAddress = presentationEmail.from?.first;
|
||||
if (session == null || accountId == null || emailAddress == null) return;
|
||||
|
||||
consumeState(emailActionReactor.quickCreateRule(
|
||||
session!,
|
||||
accountId!,
|
||||
emailAddress: emailAddress,
|
||||
));
|
||||
}
|
||||
|
||||
void _unsubscribeEmail(PresentationEmail presentationEmail) {
|
||||
emailActionReactor.unsubscribeEmail(
|
||||
presentationEmail,
|
||||
emailUnsubscribe: EmailUtils.parsingUnsubscribe(
|
||||
presentationEmail.listUnsubscribe ?? '',
|
||||
),
|
||||
onUnsubscribeByHttpsLink: mailboxDashBoardController.unsubscribeMail,
|
||||
onUnsubscribeByMailtoLink: (emailId, navigationRouter) {
|
||||
mailboxDashBoardController.openComposer(
|
||||
ComposerArguments.fromUnsubscribeMailtoLink(
|
||||
listEmailAddress: navigationRouter.listEmailAddress,
|
||||
subject: navigationRouter.subject,
|
||||
body: navigationRouter.body,
|
||||
previousEmailId: emailId,
|
||||
)
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _archiveMessage(PresentationEmail presentationEmail) {
|
||||
if (currentContext == null) return;
|
||||
|
||||
emailActionReactor.archiveMessage(
|
||||
presentationEmail,
|
||||
onArchiveEmailRequest: (presentationEmail) {
|
||||
mailboxDashBoardController.archiveMessage(currentContext!, presentationEmail);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _printEmail(PresentationEmail presentationEmail) {
|
||||
consumeState(emailActionReactor.printEmail(
|
||||
presentationEmail,
|
||||
ownEmailAddress: mailboxDashBoardController.ownEmailAddress.value,
|
||||
emailLoaded: null,
|
||||
session: session,
|
||||
accountId: accountId,
|
||||
baseDownloadUrl: session?.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl),
|
||||
transformConfiguration: TransformConfiguration.forPreviewEmailOnWeb(),
|
||||
));
|
||||
}
|
||||
|
||||
void _downloadMessageAsEML(PresentationEmail presentationEmail) {
|
||||
if (accountId == null || session == null) return;
|
||||
|
||||
consumeState(emailActionReactor.downloadMessageAsEML(
|
||||
session!,
|
||||
accountId!,
|
||||
presentationEmail,
|
||||
downloadProgressStateController: downloadProgressState,
|
||||
));
|
||||
}
|
||||
|
||||
void _editAsNewEmail(PresentationEmail presentationEmail) {
|
||||
if (accountId == null || session == null) return;
|
||||
|
||||
emailActionReactor.editAsNewEmail(
|
||||
presentationEmail,
|
||||
onEditAsEmailRequest: (presentationEmail) {
|
||||
mailboxDashBoardController.openComposer(
|
||||
ComposerArguments.editAsNewEmail(presentationEmail),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _replyEmail(PresentationEmail presentationEmail) {
|
||||
emailActionReactor.replyEmail(
|
||||
presentationEmail,
|
||||
@@ -318,61 +53,4 @@ extension ThreadDetailOnEmailActionClick on ThreadDetailController {
|
||||
emailLoaded: null,
|
||||
);
|
||||
}
|
||||
|
||||
void _replyAll(PresentationEmail presentationEmail) {
|
||||
emailActionReactor.replyAll(
|
||||
presentationEmail,
|
||||
onReplyAllRequest: (presentationEmail, emailLoaded) {
|
||||
mailboxDashBoardController.openComposer(
|
||||
ComposerArguments.replyAllEmail(
|
||||
presentationEmail: presentationEmail,
|
||||
content: emailLoaded?.htmlContent,
|
||||
inlineImages: emailLoaded?.inlineImages,
|
||||
mailboxRole: presentationEmail.mailboxContain?.role,
|
||||
messageId: presentationEmail.messageId,
|
||||
references: presentationEmail.references,
|
||||
listPost: presentationEmail.listPost,
|
||||
)
|
||||
);
|
||||
},
|
||||
emailLoaded: null,
|
||||
);
|
||||
}
|
||||
void _replyToList(PresentationEmail presentationEmail) {
|
||||
emailActionReactor.replyToList(
|
||||
presentationEmail,
|
||||
onReplyToListRequest: (presentationEmail, emailLoaded) {
|
||||
mailboxDashBoardController.openComposer(
|
||||
ComposerArguments.replyToListEmail(
|
||||
presentationEmail: presentationEmail,
|
||||
content: emailLoaded?.htmlContent,
|
||||
inlineImages: emailLoaded?.inlineImages,
|
||||
mailboxRole: presentationEmail.mailboxContain?.role,
|
||||
messageId: presentationEmail.messageId,
|
||||
references: presentationEmail.references,
|
||||
listPost: presentationEmail.listPost,
|
||||
)
|
||||
);
|
||||
},
|
||||
emailLoaded: null,
|
||||
);
|
||||
}
|
||||
void _forward(PresentationEmail presentationEmail) {
|
||||
emailActionReactor.forward(
|
||||
presentationEmail,
|
||||
onForwardRequest: (presentationEmail, emailLoaded) {
|
||||
mailboxDashBoardController.openComposer(
|
||||
ComposerArguments.forwardEmail(
|
||||
presentationEmail: presentationEmail,
|
||||
content: emailLoaded?.htmlContent,
|
||||
attachments: emailLoaded?.attachments,
|
||||
inlineImages: emailLoaded?.inlineImages,
|
||||
messageId: presentationEmail.messageId,
|
||||
references: presentationEmail.references,
|
||||
)
|
||||
);
|
||||
},
|
||||
emailLoaded: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
+5
@@ -2,6 +2,7 @@ import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread/presentation/extensions/list_presentation_email_extensions.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart';
|
||||
@@ -56,6 +57,10 @@ extension ThreadDetailOnSelectedEmailUpdated on ThreadDetailController {
|
||||
Right(PreloadEmailIdsInThreadSuccess(
|
||||
[selectedEmail.id!],
|
||||
threadId: selectedEmail.threadId,
|
||||
emailsInThreadDetailInfo: [selectedEmail].toEmailsInThreadDetailInfo(
|
||||
sentMailboxId: sentMailboxId,
|
||||
ownEmailAddress: ownEmailAddress,
|
||||
),
|
||||
)),
|
||||
Right(PreloadEmailsByIdsSuccess([selectedEmail])),
|
||||
]));
|
||||
|
||||
@@ -8,6 +8,8 @@ import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_
|
||||
import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/manage_account/data/local/local_setting_cache_manager.dart';
|
||||
import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_data_source.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_local_data_source_impl.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_remote_data_source_impl.dart';
|
||||
@@ -43,6 +45,8 @@ class ThreadDetailBindings extends BaseBindings {
|
||||
Get.find<PrintEmailInteractor>(),
|
||||
Get.find<GetEmailContentInteractor>(),
|
||||
Get.find<DownloadAttachmentForWebInteractor>(),
|
||||
Get.find<MarkAsStarMultipleEmailInteractor>(),
|
||||
Get.find<MarkAsMultipleEmailReadInteractor>(),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action
|
||||
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart';
|
||||
import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart';
|
||||
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart';
|
||||
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_star_state.dart';
|
||||
import 'package:tmail_ui_user/features/email/domain/usecases/download_attachment_for_web_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart';
|
||||
@@ -37,6 +36,11 @@ import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new
|
||||
import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart'
|
||||
if (dart.library.html) 'package:tmail_ui_user/features/network_connection/presentation/web_network_connection_controller.dart';
|
||||
import 'package:tmail_ui_user/features/search/email/presentation/search_email_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread/domain/state/mark_as_star_multiple_email_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/model/email_in_thread_detail_info.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart';
|
||||
@@ -46,6 +50,7 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/extension/hand
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_email_ids_by_thread_id_success.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_emails_by_ids_success.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_thread_by_id_failure.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_mark_multiple_emails_read_success.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_refresh_thread_detail_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_on_selected_email_updated.dart';
|
||||
@@ -53,7 +58,6 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_collapsed_email_download_states.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/mark_collapsed_email_star_success.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/mark_collapsed_email_unread_success.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/quick_create_rule_from_collapsed_email_success.dart';
|
||||
|
||||
@@ -65,6 +69,8 @@ class ThreadDetailController extends BaseController {
|
||||
final PrintEmailInteractor _printEmailInteractor;
|
||||
final GetEmailContentInteractor _getEmailContentInteractor;
|
||||
final DownloadAttachmentForWebInteractor _downloadAttachmentForWebInteractor;
|
||||
final MarkAsStarMultipleEmailInteractor markAsStarMultipleEmailInteractor;
|
||||
final MarkAsMultipleEmailReadInteractor markAsMultipleEmailReadInteractor;
|
||||
|
||||
ThreadDetailController(
|
||||
this._getEmailIdsByThreadIdInteractor,
|
||||
@@ -74,16 +80,18 @@ class ThreadDetailController extends BaseController {
|
||||
this._printEmailInteractor,
|
||||
this._getEmailContentInteractor,
|
||||
this._downloadAttachmentForWebInteractor,
|
||||
this.markAsStarMultipleEmailInteractor,
|
||||
this.markAsMultipleEmailReadInteractor,
|
||||
);
|
||||
|
||||
final emailIdsPresentation = <EmailId, PresentationEmail?>{}.obs;
|
||||
final currentExpandedEmailId = Rxn<EmailId>();
|
||||
final currentEmailLoaded = Rxn<EmailLoaded>();
|
||||
final emailsInThreadDetailInfo = RxList<EmailInThreadDetailInfo>();
|
||||
|
||||
late final EmailActionReactor emailActionReactor;
|
||||
final additionalProperties = Properties({
|
||||
IndividualHeaderIdentifier.listPostHeader.value,
|
||||
IndividualHeaderIdentifier.listUnsubscribeHeader.value,
|
||||
EmailProperty.references,
|
||||
EmailProperty.messageId,
|
||||
});
|
||||
@@ -100,7 +108,6 @@ class ThreadDetailController extends BaseController {
|
||||
accountId!,
|
||||
sentMailboxId!,
|
||||
ownEmailAddress!,
|
||||
selectedEmailId: mailboxDashBoardController.selectedEmail.value?.id,
|
||||
));
|
||||
}
|
||||
},
|
||||
@@ -227,8 +234,18 @@ class ThreadDetailController extends BaseController {
|
||||
handleGetEmailsByIdsSuccess(success);
|
||||
} else if (success is MarkAsEmailReadSuccess) {
|
||||
markCollapsedEmailReadSuccess(success);
|
||||
} else if (success is MarkAsStarEmailSuccess) {
|
||||
markCollapsedEmailStarSuccess(success);
|
||||
} else if (success is MarkAsMultipleEmailReadAllSuccess) {
|
||||
handleMarkMultipleEmailsReadSuccess(
|
||||
success,
|
||||
success.readActions,
|
||||
success.emailIds,
|
||||
);
|
||||
} else if (success is MarkAsMultipleEmailReadHasSomeEmailFailure) {
|
||||
handleMarkMultipleEmailsReadSuccess(
|
||||
success,
|
||||
success.readActions,
|
||||
success.successEmailIds,
|
||||
);
|
||||
} else if (success is CreateNewRuleFilterSuccess) {
|
||||
quickCreateRuleFromCollapsedEmailSuccess(success);
|
||||
} else if (success is DownloadAttachmentForWebSuccess) {
|
||||
@@ -261,6 +278,10 @@ class ThreadDetailController extends BaseController {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (failure is MarkAsMultipleEmailReadFailure ||
|
||||
failure is MarkAsStarMultipleEmailFailure) {
|
||||
toastManager.showMessageFailure(failure as FeatureFailure);
|
||||
}
|
||||
super.handleFailureViewState(failure);
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,10 @@ import 'package:core/utils/platform_info.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:get/get_instance/get_instance.dart';
|
||||
import 'package:get/state_manager.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/session/session.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/base/base_controller.dart';
|
||||
import 'package:tmail_ui_user/features/base/reloadable/reloadable_controller.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_detail_status_state.dart';
|
||||
@@ -16,7 +17,7 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/extension/init
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/refresh_thread_detail_on_setting_changed.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/model/thread_detail_setting_status.dart';
|
||||
|
||||
class ThreadDetailManager extends BaseController {
|
||||
class ThreadDetailManager extends ReloadableController {
|
||||
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
|
||||
|
||||
final GetThreadDetailStatusInteractor _getThreadDetailStatusInteractor;
|
||||
@@ -86,6 +87,12 @@ class ThreadDetailManager extends BaseController {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void handleReloaded(Session session) {
|
||||
super.handleReloaded(session);
|
||||
consumeState(_getThreadDetailStatusInteractor.execute());
|
||||
}
|
||||
|
||||
@override
|
||||
void handleSuccessViewState(Success success) {
|
||||
if (success is GetThreadDetailStatusSuccess) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
@@ -8,19 +7,17 @@ import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:model/extensions/session_extension.dart';
|
||||
import 'package:model/email/email_action_type.dart';
|
||||
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/styles/email_view_app_bar_widget_styles.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_bottom_bar_widget.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_open_context_menu_extension.dart';
|
||||
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/vacation_response_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/get_thread_detail_email_mailbox_contains.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/get_thread_detail_action_status.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/get_thread_details_email_views.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/on_thread_detail_action_click.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/on_thread_page_changed.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_on_email_action_click.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_app_bar.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
@@ -35,49 +32,18 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
|
||||
Widget bodyWidget = Column(
|
||||
children: [
|
||||
Obx(() {
|
||||
final isLoading = showLoadingView(controller.viewState.value);
|
||||
|
||||
return ThreadDetailAppBar(
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
imagePaths: controller.imagePaths,
|
||||
isSearchRunning: controller.isSearchRunning,
|
||||
closeThreadDetailAction: controller.closeThreadDetailAction,
|
||||
lastEmailOfThread: controller.emailIdsPresentation.values.lastOrNull,
|
||||
ownUserName: controller.session?.getOwnEmailAddress() ?? '',
|
||||
isThreadDetailEnabled: controller.isThreadDetailEnabled,
|
||||
mailboxContain: _getMailboxContain(),
|
||||
onEmailActionClick: isLoading
|
||||
? null
|
||||
: controller.threadDetailOnEmailActionClick,
|
||||
onMoreActionClick: (presentationEmail, position) => isLoading
|
||||
? null
|
||||
: controller.emailActionReactor.handleMoreEmailAction(
|
||||
mailboxContain: controller.getThreadDetailEmailMailboxContains(
|
||||
presentationEmail,
|
||||
),
|
||||
presentationEmail: presentationEmail,
|
||||
position: position,
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
imagePaths: controller.imagePaths,
|
||||
username: controller.session?.username,
|
||||
handleEmailAction: controller.threadDetailOnEmailActionClick,
|
||||
additionalActions: [
|
||||
if (controller.responsiveUtils.isMobile(context)) ...[
|
||||
EmailActionType.forward,
|
||||
EmailActionType.replyAll,
|
||||
EmailActionType.replyToList,
|
||||
],
|
||||
EmailActionType.markAsStarred,
|
||||
EmailActionType.unMarkAsStarred,
|
||||
EmailActionType.moveToTrash,
|
||||
EmailActionType.deletePermanently,
|
||||
EmailActionType.printAll,
|
||||
EmailActionType.moveToMailbox,
|
||||
],
|
||||
emailIsRead: presentationEmail.hasRead,
|
||||
openBottomSheetContextMenu: controller.mailboxDashBoardController.openBottomSheetContextMenu,
|
||||
openPopupMenu: controller.mailboxDashBoardController.openPopupMenu,
|
||||
),
|
||||
threadActionReady: controller.emailsInThreadDetailInfo.isNotEmpty &&
|
||||
controller.emailIdsPresentation.length > 1,
|
||||
threadDetailIsStarred: controller.threadDetailIsStarred,
|
||||
onThreadActionClick: controller.onThreadDetailActionClick,
|
||||
onThreadMoreActionClick: controller.onThreadDetailMoreActionClick,
|
||||
optionWidgets: [
|
||||
if (controller.previousAvailable)
|
||||
TMailButtonWidget.fromIcon(
|
||||
|
||||
@@ -3,7 +3,10 @@ import 'dart:math';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/thread/presentation/extensions/list_presentation_email_extensions.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/model/email_in_thread_detail_info.dart';
|
||||
|
||||
class ThreadDetailPresentationUtils {
|
||||
const ThreadDetailPresentationUtils._();
|
||||
@@ -90,4 +93,32 @@ class ThreadDetailPresentationUtils {
|
||||
...created,
|
||||
];
|
||||
}
|
||||
|
||||
static List<EmailInThreadDetailInfo> refreshThreadDetailInfo({
|
||||
required List<EmailInThreadDetailInfo> original,
|
||||
required List<PresentationEmail> created,
|
||||
required List<PresentationEmail> updated,
|
||||
required List<EmailId> destroyed,
|
||||
required MailboxId? sentMailboxId,
|
||||
required String? ownEmailAddress,
|
||||
}) {
|
||||
return [
|
||||
...original
|
||||
.whereNot((email) => destroyed.contains(email.emailId))
|
||||
.map((email) {
|
||||
final updatedInOriginal = updated.firstWhereOrNull(
|
||||
(updatedEmail) => updatedEmail.id == email.emailId,
|
||||
);
|
||||
|
||||
return email.copyWith(
|
||||
keywords: updatedInOriginal?.keywords,
|
||||
mailboxIds: updatedInOriginal?.mailboxIds,
|
||||
);
|
||||
}),
|
||||
...created.toEmailsInThreadDetailInfo(
|
||||
sentMailboxId: sentMailboxId,
|
||||
ownEmailAddress: ownEmailAddress,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,15 @@ import 'package:core/presentation/views/button/tmail_button_widget.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:model/email/email_action_type.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:model/extensions/presentation_email_extension.dart';
|
||||
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/styles/email_view_app_bar_widget_styles.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_app_bar_widget.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_back_button.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
import 'package:tmail_ui_user/main/utils/app_utils.dart';
|
||||
|
||||
typedef OnThreadActionClick = void Function(EmailActionType);
|
||||
typedef OnThreadMoreActionClick = void Function(RelativeRect?);
|
||||
|
||||
class ThreadDetailAppBar extends StatelessWidget {
|
||||
const ThreadDetailAppBar({
|
||||
super.key,
|
||||
@@ -21,33 +20,29 @@ class ThreadDetailAppBar extends StatelessWidget {
|
||||
required this.imagePaths,
|
||||
required this.isSearchRunning,
|
||||
required this.closeThreadDetailAction,
|
||||
required this.lastEmailOfThread,
|
||||
required this.ownUserName,
|
||||
required this.threadActionReady,
|
||||
required this.threadDetailIsStarred,
|
||||
required this.isThreadDetailEnabled,
|
||||
this.mailboxContain,
|
||||
this.optionWidgets = const [],
|
||||
this.onEmailActionClick,
|
||||
this.onMoreActionClick,
|
||||
this.onThreadActionClick,
|
||||
this.onThreadMoreActionClick,
|
||||
});
|
||||
|
||||
final ResponsiveUtils responsiveUtils;
|
||||
final ImagePaths imagePaths;
|
||||
final bool isSearchRunning;
|
||||
final void Function(BuildContext context) closeThreadDetailAction;
|
||||
final PresentationEmail? lastEmailOfThread;
|
||||
final String ownUserName;
|
||||
final bool threadActionReady;
|
||||
final bool threadDetailIsStarred;
|
||||
final bool isThreadDetailEnabled;
|
||||
final PresentationMailbox? mailboxContain;
|
||||
final List<Widget> optionWidgets;
|
||||
final OnEmailActionClick? onEmailActionClick;
|
||||
final OnMoreActionClick? onMoreActionClick;
|
||||
final OnThreadActionClick? onThreadActionClick;
|
||||
final OnThreadMoreActionClick? onThreadMoreActionClick;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isReplyToListEnabled = EmailUtils.isReplyToListEnabled(
|
||||
lastEmailOfThread?.listPost ?? '',
|
||||
);
|
||||
|
||||
final child = LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
Widget backButton = EmailViewBackButton(
|
||||
@@ -86,61 +81,31 @@ class ThreadDetailAppBar extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (_supportDisplayMailboxNameTitle(context)) backButton,
|
||||
if (lastEmailOfThread != null && isThreadDetailEnabled) ...[
|
||||
if (isThreadDetailEnabled && threadActionReady)
|
||||
_ThreadDetailAppBarButton(
|
||||
icon: imagePaths.icReply,
|
||||
tooltipMessage: AppLocalizations.of(context).reply,
|
||||
icon: threadDetailIsStarred
|
||||
? imagePaths.icStar
|
||||
: imagePaths.icUnStar,
|
||||
tooltipMessage: threadDetailIsStarred
|
||||
? AppLocalizations.of(context).not_starred
|
||||
: AppLocalizations.of(context).mark_as_starred,
|
||||
responsiveUtils: responsiveUtils,
|
||||
onTapActionCallback: (_) => onEmailActionClick?.call(
|
||||
lastEmailOfThread!,
|
||||
EmailActionType.reply,
|
||||
),
|
||||
iconColor: null,
|
||||
onTapActionCallback: threadDetailIsStarred
|
||||
? (_) => onThreadActionClick?.call(EmailActionType.unMarkAsStarred)
|
||||
: (_) => onThreadActionClick?.call(EmailActionType.markAsStarred),
|
||||
),
|
||||
if (!responsiveUtils.isMobile(context)) ...[
|
||||
if (lastEmailOfThread!.getCountMailAddressWithoutMe(ownUserName) > 1)
|
||||
_ThreadDetailAppBarButton(
|
||||
icon: imagePaths.icReplyAll,
|
||||
tooltipMessage: AppLocalizations.of(context).reply_all,
|
||||
responsiveUtils: responsiveUtils,
|
||||
onTapActionCallback: (_) => onEmailActionClick?.call(
|
||||
lastEmailOfThread!,
|
||||
EmailActionType.replyAll,
|
||||
),
|
||||
),
|
||||
if (isReplyToListEnabled)
|
||||
_ThreadDetailAppBarButton(
|
||||
icon: imagePaths.icReply,
|
||||
tooltipMessage: AppLocalizations.of(context).replyToList,
|
||||
responsiveUtils: responsiveUtils,
|
||||
onTapActionCallback: (_) => onEmailActionClick?.call(
|
||||
lastEmailOfThread!,
|
||||
EmailActionType.replyToList,
|
||||
),
|
||||
),
|
||||
_ThreadDetailAppBarButton(
|
||||
icon: imagePaths.icForward,
|
||||
tooltipMessage: AppLocalizations.of(context).forward,
|
||||
responsiveUtils: responsiveUtils,
|
||||
onTapActionCallback: (_) => onEmailActionClick?.call(
|
||||
lastEmailOfThread!,
|
||||
EmailActionType.forward,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (!responsiveUtils.isMobile(context)) const Spacer(),
|
||||
] else const Spacer(),
|
||||
if (!responsiveUtils.isMobile(context)) const Spacer(),
|
||||
if (AppUtils.getCurrentDirection(context) == TextDirection.rtl)
|
||||
...optionWidgets.reversed
|
||||
else
|
||||
...optionWidgets,
|
||||
if (lastEmailOfThread != null)
|
||||
if (isThreadDetailEnabled && threadActionReady)
|
||||
_ThreadDetailAppBarButton(
|
||||
icon: imagePaths.icMoreVertical,
|
||||
tooltipMessage: AppLocalizations.of(context).more,
|
||||
responsiveUtils: responsiveUtils,
|
||||
onTapActionCallback: lastEmailOfThread != null
|
||||
? (position) => onMoreActionClick?.call(lastEmailOfThread!, position)
|
||||
: null,
|
||||
onTapActionCallback: onThreadMoreActionClick,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -175,22 +140,25 @@ class _ThreadDetailAppBarButton extends StatelessWidget {
|
||||
required this.tooltipMessage,
|
||||
required this.onTapActionCallback,
|
||||
required this.responsiveUtils,
|
||||
this.iconColor = EmailViewAppBarWidgetStyles.iconColor,
|
||||
});
|
||||
|
||||
final String icon;
|
||||
final String tooltipMessage;
|
||||
final void Function(RelativeRect? position)? onTapActionCallback;
|
||||
final ResponsiveUtils responsiveUtils;
|
||||
final Color? iconColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final smallScreen = responsiveUtils.isScreenWithShortestSide(context);
|
||||
|
||||
return TMailButtonWidget.fromIcon(
|
||||
icon: icon,
|
||||
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
||||
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
||||
iconColor: iconColor,
|
||||
backgroundColor: Colors.transparent,
|
||||
tooltipMessage: AppLocalizations.of(context).more,
|
||||
tooltipMessage: tooltipMessage,
|
||||
onTapActionCallback:
|
||||
smallScreen ? () => onTapActionCallback?.call(null) : null,
|
||||
onTapActionAtPositionCallback: !smallScreen
|
||||
@@ -198,4 +166,4 @@ class _ThreadDetailAppBarButton extends StatelessWidget {
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ class ThreadDetailCollapsedEmail extends StatelessWidget {
|
||||
this.mailboxContain,
|
||||
this.emailLoaded,
|
||||
this.onEmailActionClick,
|
||||
this.onMoreActionClick,
|
||||
this.onToggleThreadDetailCollapseExpand,
|
||||
});
|
||||
|
||||
@@ -35,7 +34,6 @@ class ThreadDetailCollapsedEmail extends StatelessWidget {
|
||||
final PresentationMailbox? mailboxContain;
|
||||
final EmailLoaded? emailLoaded;
|
||||
final OnEmailActionClick? onEmailActionClick;
|
||||
final OnMoreActionClick? onMoreActionClick;
|
||||
final VoidCallback? onToggleThreadDetailCollapseExpand;
|
||||
|
||||
String get preview => presentationEmail.getPartialContent();
|
||||
@@ -69,7 +67,6 @@ class ThreadDetailCollapsedEmail extends StatelessWidget {
|
||||
emailLoaded: emailLoaded,
|
||||
isInsideThreadDetailView: true,
|
||||
onEmailActionClick: onEmailActionClick,
|
||||
onMoreActionClick: onMoreActionClick,
|
||||
openEmailAddressDetailAction: openEmailAddressDetailAction,
|
||||
showRecipients: false,
|
||||
onTapAvatarActionClick: onToggleThreadDetailCollapseExpand,
|
||||
|
||||
+3
@@ -7,6 +7,7 @@ import 'package:mockito/mockito.dart';
|
||||
import 'package:model/email/email_in_thread_status.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/model/email_in_thread_detail_info.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_email_ids_by_thread_id_success.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
@@ -62,6 +63,8 @@ void main() {
|
||||
};
|
||||
when(threadDetailController.emailIdsPresentation)
|
||||
.thenReturn(initialEmailIdsPresentation.obs);
|
||||
when(threadDetailController.emailsInThreadDetailInfo)
|
||||
.thenReturn(<EmailInThreadDetailInfo>[].obs);
|
||||
when(threadDetailController.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashBoardController);
|
||||
when(mailboxDashBoardController.selectedEmail)
|
||||
|
||||
+3
@@ -11,6 +11,7 @@ import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread/data/model/email_change_response.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/model/email_in_thread_detail_info.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart';
|
||||
@@ -98,6 +99,8 @@ void main() {
|
||||
.thenReturn(MailboxId(Id('sent-mailbox-id')));
|
||||
when(threadDetailController.ownEmailAddress)
|
||||
.thenReturn('9jEYK@example.com');
|
||||
when(threadDetailController.emailsInThreadDetailInfo)
|
||||
.thenReturn(<EmailInThreadDetailInfo>[].obs);
|
||||
|
||||
// act
|
||||
threadDetailController.handleRefreshThreadDetailAction(
|
||||
|
||||
+9
-1
@@ -8,6 +8,7 @@ import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread/presentation/extensions/list_presentation_email_extensions.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart';
|
||||
@@ -85,7 +86,14 @@ void main() {
|
||||
expect(
|
||||
streamsConsumed,
|
||||
emitsInOrder([
|
||||
Right(PreloadEmailIdsInThreadSuccess([selectedEmail.id!], threadId: ThreadId(Id('1')))),
|
||||
Right(PreloadEmailIdsInThreadSuccess(
|
||||
[selectedEmail.id!],
|
||||
threadId: ThreadId(Id('1')),
|
||||
emailsInThreadDetailInfo: [selectedEmail].toEmailsInThreadDetailInfo(
|
||||
sentMailboxId: null,
|
||||
ownEmailAddress: null,
|
||||
),
|
||||
)),
|
||||
Right(PreloadEmailsByIdsSuccess([selectedEmail])),
|
||||
]),
|
||||
);
|
||||
|
||||
+244
@@ -4,6 +4,7 @@ import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/model/email_in_thread_detail_info.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/utils/thread_detail_presentation_utils.dart';
|
||||
|
||||
void main() {
|
||||
@@ -492,5 +493,248 @@ void main() {
|
||||
expect(result, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('refreshThreadDetailInfo', () {
|
||||
final sentMailboxId = MailboxId(Id('sent'));
|
||||
const ownEmailAddress = 'user@example';
|
||||
test(
|
||||
'should return empty list when all inputs are empty',
|
||||
() {
|
||||
// arrange
|
||||
final original = <EmailInThreadDetailInfo>[];
|
||||
final created = <PresentationEmail>[];
|
||||
final updated = <PresentationEmail>[];
|
||||
final destroyed = <EmailId>[];
|
||||
|
||||
// act
|
||||
final result = ThreadDetailPresentationUtils.refreshThreadDetailInfo(
|
||||
original: original,
|
||||
created: created,
|
||||
updated: updated,
|
||||
destroyed: destroyed,
|
||||
sentMailboxId: sentMailboxId,
|
||||
ownEmailAddress: ownEmailAddress,
|
||||
);
|
||||
|
||||
// assert
|
||||
expect(result, isEmpty);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'should return original list when no changes are made',
|
||||
() {
|
||||
// arrange
|
||||
final id = Id('email-1');
|
||||
final emailId = EmailId(id);
|
||||
final original = [
|
||||
EmailInThreadDetailInfo(
|
||||
emailId: emailId,
|
||||
keywords: {},
|
||||
mailboxIds: {},
|
||||
isValidToDisplay: true,
|
||||
),
|
||||
];
|
||||
final created = <PresentationEmail>[];
|
||||
final updated = <PresentationEmail>[];
|
||||
final destroyed = <EmailId>[];
|
||||
|
||||
// act
|
||||
final result = ThreadDetailPresentationUtils.refreshThreadDetailInfo(
|
||||
original: original,
|
||||
created: created,
|
||||
updated: updated,
|
||||
destroyed: destroyed,
|
||||
sentMailboxId: sentMailboxId,
|
||||
ownEmailAddress: ownEmailAddress,
|
||||
);
|
||||
|
||||
// assert
|
||||
expect(result, equals(original));
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'should return created emails when no original emails are present',
|
||||
() {
|
||||
// arrange
|
||||
final id = Id('email-1');
|
||||
final emailId = EmailId(id);
|
||||
final created = [
|
||||
PresentationEmail(
|
||||
id: emailId,
|
||||
keywords: {},
|
||||
mailboxIds: {},
|
||||
),
|
||||
];
|
||||
final original = <EmailInThreadDetailInfo>[];
|
||||
final updated = <PresentationEmail>[];
|
||||
final destroyed = <EmailId>[];
|
||||
|
||||
// act
|
||||
final result = ThreadDetailPresentationUtils.refreshThreadDetailInfo(
|
||||
original: original,
|
||||
created: created,
|
||||
updated: updated,
|
||||
destroyed: destroyed,
|
||||
sentMailboxId: sentMailboxId,
|
||||
ownEmailAddress: ownEmailAddress,
|
||||
);
|
||||
|
||||
// assert
|
||||
expect(result, hasLength(1));
|
||||
expect(result.first.emailId, equals(emailId));
|
||||
expect(result.first.keywords, equals({}));
|
||||
expect(result.first.mailboxIds, equals({}));
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'should return updated emails when emails are updated',
|
||||
() {
|
||||
// arrange
|
||||
final id = Id('email-1');
|
||||
final emailId = EmailId(id);
|
||||
final original = [
|
||||
EmailInThreadDetailInfo(
|
||||
emailId: emailId,
|
||||
keywords: {},
|
||||
mailboxIds: {},
|
||||
isValidToDisplay: true,
|
||||
),
|
||||
];
|
||||
final updated = [
|
||||
PresentationEmail(
|
||||
id: emailId,
|
||||
keywords: {KeyWordIdentifier('test'): true},
|
||||
mailboxIds: {},
|
||||
),
|
||||
];
|
||||
final created = <PresentationEmail>[];
|
||||
final destroyed = <EmailId>[];
|
||||
|
||||
// act
|
||||
final result = ThreadDetailPresentationUtils.refreshThreadDetailInfo(
|
||||
original: original,
|
||||
created: created,
|
||||
updated: updated,
|
||||
destroyed: destroyed,
|
||||
sentMailboxId: sentMailboxId,
|
||||
ownEmailAddress: ownEmailAddress,
|
||||
);
|
||||
|
||||
// assert
|
||||
expect(result, hasLength(1));
|
||||
expect(result.first.emailId, equals(emailId));
|
||||
expect(result.first.keywords, equals({KeyWordIdentifier('test'): true}));
|
||||
expect(result.first.mailboxIds, equals({}));
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'should remove destroyed emails from the result',
|
||||
() {
|
||||
// arrange
|
||||
final id1 = Id('email-1');
|
||||
final emailId1 = EmailId(id1);
|
||||
final id2 = Id('email-2');
|
||||
final emailId2 = EmailId(id2);
|
||||
final original = [
|
||||
EmailInThreadDetailInfo(
|
||||
emailId: emailId1,
|
||||
keywords: {},
|
||||
mailboxIds: {},
|
||||
isValidToDisplay: true,
|
||||
),
|
||||
EmailInThreadDetailInfo(
|
||||
emailId: emailId2,
|
||||
keywords: {},
|
||||
mailboxIds: {},
|
||||
isValidToDisplay: true,
|
||||
),
|
||||
];
|
||||
final created = <PresentationEmail>[];
|
||||
final updated = <PresentationEmail>[];
|
||||
final destroyed = [emailId1];
|
||||
|
||||
// act
|
||||
final result = ThreadDetailPresentationUtils.refreshThreadDetailInfo(
|
||||
original: original,
|
||||
created: created,
|
||||
updated: updated,
|
||||
destroyed: destroyed,
|
||||
sentMailboxId: sentMailboxId,
|
||||
ownEmailAddress: ownEmailAddress,
|
||||
);
|
||||
|
||||
// assert
|
||||
expect(result, hasLength(1));
|
||||
expect(result.first.emailId, equals(emailId2));
|
||||
expect(result.first.keywords, equals({}));
|
||||
expect(result.first.mailboxIds, equals({}));
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'should apply all changes when multiple changes are present',
|
||||
() {
|
||||
// arrange
|
||||
final id1 = Id('email-1');
|
||||
final emailId1 = EmailId(id1);
|
||||
final id2 = Id('email-2');
|
||||
final emailId2 = EmailId(id2);
|
||||
final id3 = Id('email-3');
|
||||
final emailId3 = EmailId(id3);
|
||||
final original = [
|
||||
EmailInThreadDetailInfo(
|
||||
emailId: emailId1,
|
||||
keywords: {},
|
||||
mailboxIds: {},
|
||||
isValidToDisplay: true,
|
||||
),
|
||||
EmailInThreadDetailInfo(
|
||||
emailId: emailId2,
|
||||
keywords: {},
|
||||
mailboxIds: {},
|
||||
isValidToDisplay: true,
|
||||
),
|
||||
];
|
||||
final created = [
|
||||
PresentationEmail(
|
||||
id: emailId3,
|
||||
keywords: {KeyWordIdentifier('test'): true},
|
||||
mailboxIds: {},
|
||||
),
|
||||
];
|
||||
final updated = [
|
||||
PresentationEmail(
|
||||
id: emailId1,
|
||||
keywords: {KeyWordIdentifier('updated'): true},
|
||||
mailboxIds: {},
|
||||
),
|
||||
];
|
||||
final destroyed = [emailId2];
|
||||
|
||||
// act
|
||||
final result = ThreadDetailPresentationUtils.refreshThreadDetailInfo(
|
||||
original: original,
|
||||
created: created,
|
||||
updated: updated,
|
||||
destroyed: destroyed,
|
||||
sentMailboxId: sentMailboxId,
|
||||
ownEmailAddress: ownEmailAddress,
|
||||
);
|
||||
|
||||
// assert
|
||||
expect(result, hasLength(2));
|
||||
expect(result.first.emailId, equals(emailId1));
|
||||
expect(result.first.keywords, equals({KeyWordIdentifier('updated'): true}));
|
||||
expect(result.first.mailboxIds, equals({}));
|
||||
expect(result.last.emailId, equals(emailId3));
|
||||
expect(result.last.keywords, equals({KeyWordIdentifier('test'): true}));
|
||||
expect(result.last.mailboxIds, equals({}));
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user