TF-3724 Thread Detail add unread indicator collapsed email
TF-3724 Thread Detail Optimize mark read/star call TF-3724 Thread Detail Organize markCollapsedEmailReadSuccess TF-3724 Thread Detail show toast on mark read fail TF-3724 Thread Detail Update sender, recipient and preview style TF-3724 Thread Detail Revert ThemeUtils changes
This commit is contained in:
@@ -135,6 +135,8 @@ extension EmailActionTypeExtension on EmailActionType {
|
||||
|
||||
String getIcon(ImagePaths imagePaths) {
|
||||
switch(this) {
|
||||
case EmailActionType.markAsRead:
|
||||
return imagePaths.icRead;
|
||||
case EmailActionType.markAsUnread:
|
||||
return imagePaths.icUnreadEmail;
|
||||
case EmailActionType.unSpam:
|
||||
@@ -177,6 +179,8 @@ extension EmailActionTypeExtension on EmailActionType {
|
||||
|
||||
String getTitle(AppLocalizations appLocalizations) {
|
||||
switch(this) {
|
||||
case EmailActionType.markAsRead:
|
||||
return AppLocalizations.of(context).mark_as_read;
|
||||
case EmailActionType.markAsUnread:
|
||||
return appLocalizations.mark_as_unread;
|
||||
case EmailActionType.unSpam:
|
||||
|
||||
@@ -132,6 +132,7 @@ import 'package:tmail_ui_user/features/manage_account/presentation/extensions/da
|
||||
import 'package:tmail_ui_user/features/search/email/presentation/search_email_controller.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/focus_thread_detail_expanded_email.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/thread_detail_controller.dart';
|
||||
import 'package:tmail_ui_user/main/error/capability_validator.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
@@ -276,7 +277,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
} else if (success is GetEmailContentFromCacheSuccess) {
|
||||
_getEmailContentOfflineSuccess(success);
|
||||
} else if (success is MarkAsEmailReadSuccess) {
|
||||
_handleMarkAsEmailReadCompleted(success.readActions);
|
||||
_handleMarkAsEmailReadCompleted(success);
|
||||
} else if (success is ExportAttachmentSuccess) {
|
||||
_exportAttachmentSuccessAction(success);
|
||||
} else if (success is ExportAllAttachmentsSuccess) {
|
||||
@@ -334,7 +335,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
void handleFailureViewState(Failure failure) {
|
||||
super.handleFailureViewState(failure);
|
||||
if (failure is MarkAsEmailReadFailure) {
|
||||
_handleMarkAsEmailReadCompleted(failure.readActions);
|
||||
_handleMarkAsEmailReadFailure(failure);
|
||||
} else if (failure is DownloadAttachmentsFailure) {
|
||||
_downloadAttachmentsFailure(failure);
|
||||
} else if (failure is ExportAttachmentFailure) {
|
||||
@@ -364,6 +365,15 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
}
|
||||
}
|
||||
|
||||
void _handleMarkAsEmailReadFailure(MarkAsEmailReadFailure failure) {
|
||||
if (currentContext != null && currentOverlayContext != null) {
|
||||
appToast.showToastErrorMessage(
|
||||
currentOverlayContext!,
|
||||
AppLocalizations.of(currentContext!).an_error_occurred,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleGetEmailContentFailure(GetEmailContentFailure failure) {
|
||||
emailLoadedViewState.value = Left<Failure, Success>(failure);
|
||||
showRetryToast(failure);
|
||||
@@ -790,16 +800,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
}
|
||||
}
|
||||
|
||||
void _handleMarkAsEmailReadCompleted(ReadActions readActions) {
|
||||
if (currentEmail?.id != null) {
|
||||
mailboxDashBoardController.updateEmailFlagByEmailIds(
|
||||
[currentEmail!.id!],
|
||||
readAction: readActions,
|
||||
);
|
||||
}
|
||||
if (readActions == ReadActions.markAsUnread) {
|
||||
closeEmailView(context: currentContext);
|
||||
}
|
||||
void _handleMarkAsEmailReadCompleted(MarkAsEmailReadSuccess success) {
|
||||
_threadDetailController?.markCollapsedEmailReadSuccess(success);
|
||||
}
|
||||
|
||||
void downloadAttachments(BuildContext context, List<Attachment> attachments) async {
|
||||
|
||||
@@ -97,6 +97,7 @@ class EmailView extends GetWidget<SingleEmailController> {
|
||||
username: controller.session?.username,
|
||||
handleEmailAction: (email, action) => controller.handleEmailAction(context, email, action),
|
||||
additionalActions: [],
|
||||
emailIsRead: presentationEmail.hasRead,
|
||||
),
|
||||
supportBackAction: !isInsideThreadDetailView,
|
||||
appBarDecoration: isInsideThreadDetailView
|
||||
@@ -299,6 +300,7 @@ class EmailView extends GetWidget<SingleEmailController> {
|
||||
EmailActionType.deletePermanently,
|
||||
],
|
||||
],
|
||||
emailIsRead: presentationEmail.hasRead,
|
||||
),
|
||||
onToggleThreadDetailCollapseExpand: onToggleThreadDetailCollapseExpand,
|
||||
onTapAvatarActionClick: onToggleThreadDetailCollapseExpand,
|
||||
|
||||
+5
-1
@@ -516,6 +516,7 @@ class EmailActionReactor with MessageDialogActionMixin, PopupContextMenuActionMi
|
||||
EmailActionType action,
|
||||
) handleEmailAction,
|
||||
required List<EmailActionType> additionalActions,
|
||||
required bool emailIsRead,
|
||||
}) {
|
||||
if (currentContext == null) return;
|
||||
|
||||
@@ -544,7 +545,10 @@ class EmailActionReactor with MessageDialogActionMixin, PopupContextMenuActionMi
|
||||
_canDeletePermanently(presentationEmail)
|
||||
? EmailActionType.deletePermanently
|
||||
: EmailActionType.moveToTrash,
|
||||
EmailActionType.markAsUnread,
|
||||
if (emailIsRead)
|
||||
EmailActionType.markAsUnread
|
||||
else
|
||||
EmailActionType.markAsRead,
|
||||
if (mailboxContain?.isChildOfTeamMailboxes == false)
|
||||
if (mailboxContain?.isSpam == true)
|
||||
EmailActionType.unSpam
|
||||
|
||||
@@ -54,7 +54,7 @@ class _EmailReceiverWidgetState extends State<EmailReceiverWidget> {
|
||||
if (PlatformInfo.isWeb) {
|
||||
if (_isDisplayAll) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Container(
|
||||
@@ -147,7 +147,7 @@ class _EmailReceiverWidgetState extends State<EmailReceiverWidget> {
|
||||
} else {
|
||||
if (_isDisplayAll) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Column(
|
||||
@@ -238,8 +238,11 @@ class _EmailReceiverWidgetState extends State<EmailReceiverWidget> {
|
||||
text: index == listEmailAddress.length - 1
|
||||
? emailAddress.asString()
|
||||
: '${emailAddress.asString()},',
|
||||
textStyle: ThemeUtils.textStyleBodyBody1(color: AppColor.steelGray400),
|
||||
padding: const EdgeInsetsDirectional.symmetric(vertical: 5, horizontal: 8),
|
||||
textStyle: ThemeUtils.textStyleHeadingHeadingSmall(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w400,
|
||||
).copyWith(height: 17),
|
||||
padding: const EdgeInsetsDirectional.symmetric(vertical: 2, horizontal: 8),
|
||||
backgroundColor: Colors.transparent,
|
||||
onTapActionCallback: () => widget.openEmailAddressDetailAction?.call(context, emailAddress),
|
||||
onLongPressActionCallback: () => AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress),
|
||||
|
||||
@@ -41,9 +41,10 @@ class EmailSenderBuilder extends StatelessWidget {
|
||||
},
|
||||
borderRadius: 8,
|
||||
padding: EdgeInsets.zero,
|
||||
customStyle: ThemeUtils.textStyleHeadingHeadingSmall(
|
||||
customStyle: ThemeUtils.textStyleHeadingH6(
|
||||
color: Colors.black,
|
||||
),
|
||||
fontWeight: FontWeight.w500,
|
||||
).copyWith(height: 20),
|
||||
overflow: CommonTextStyle.defaultTextOverFlow,
|
||||
softWrap: CommonTextStyle.defaultSoftWrap
|
||||
),
|
||||
|
||||
+29
-1
@@ -2,7 +2,6 @@ import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/resources/image_paths.dart';
|
||||
import 'package:core/presentation/utils/icon_utils.dart';
|
||||
import 'package:core/presentation/utils/responsive_utils.dart';
|
||||
import 'package:core/presentation/utils/theme_utils.dart';
|
||||
import 'package:core/presentation/views/button/tmail_button_widget.dart';
|
||||
import 'package:core/presentation/views/image/avatar_builder.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
@@ -41,6 +40,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
|
||||
final VoidCallback? onToggleThreadDetailCollapseExpand;
|
||||
final OnTapAvatarActionClick? onTapAvatarActionClick;
|
||||
final PresentationMailbox? mailboxContain;
|
||||
final bool showUnreadVisualization;
|
||||
|
||||
const InformationSenderAndReceiverBuilder({
|
||||
Key? key,
|
||||
@@ -59,6 +59,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
|
||||
this.onToggleThreadDetailCollapseExpand,
|
||||
this.onTapAvatarActionClick,
|
||||
this.mailboxContain,
|
||||
this.showUnreadVisualization = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -81,12 +82,25 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
if (showUnreadVisualization &&
|
||||
!emailSelected.hasRead &&
|
||||
responsiveUtils.isMobile(context))
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(end: 8),
|
||||
child: SvgPicture.asset(
|
||||
imagePaths.icUnreadStatus,
|
||||
width: 9,
|
||||
height: 9,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
if (emailSelected.from?.isNotEmpty == true)
|
||||
Flexible(
|
||||
child: EmailSenderBuilder(
|
||||
@@ -124,6 +138,19 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
|
||||
emailSelected: emailSelected,
|
||||
padding: const EdgeInsetsDirectional.only(start: 16, top: 2),
|
||||
),
|
||||
if (showUnreadVisualization &&
|
||||
!emailSelected.hasRead &&
|
||||
!responsiveUtils.isMobile(context))
|
||||
TMailButtonWidget.fromIcon(
|
||||
icon: imagePaths.icUnreadStatus,
|
||||
backgroundColor: Colors.transparent,
|
||||
iconSize: 9,
|
||||
margin: const EdgeInsetsDirectional.only(start: 8),
|
||||
onTapActionCallback: () => onEmailActionClick?.call(
|
||||
emailSelected,
|
||||
EmailActionType.markAsRead,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -160,6 +187,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (responsiveUtils.isMobile(context))
|
||||
ReceivedTimeBuilder(
|
||||
emailSelected: emailSelected,
|
||||
|
||||
@@ -12,12 +12,9 @@ class PrefixRecipientWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: Text(
|
||||
'${prefixEmailAddress.asName(context)}:',
|
||||
style: ThemeUtils.textStyleBodyBody1(color: AppColor.steelGray400),
|
||||
),
|
||||
return Text(
|
||||
'${prefixEmailAddress.asName(context)}:',
|
||||
style: ThemeUtils.textStyleBodyBody1(color: AppColor.steelGray400),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -92,6 +92,7 @@ extension GetThreadDetailEmailViews on ThreadDetailController {
|
||||
EmailActionType.deletePermanently,
|
||||
],
|
||||
],
|
||||
emailIsRead: presentationEmail.hasRead,
|
||||
),
|
||||
openEmailAddressDetailAction: (_, emailAddress) {
|
||||
openEmailAddressDetailAction(emailAddress);
|
||||
|
||||
+36
-1
@@ -1,3 +1,5 @@
|
||||
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
|
||||
import 'package:model/email/email_in_thread_status.dart';
|
||||
import 'package:model/email/read_actions.dart';
|
||||
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart';
|
||||
@@ -7,12 +9,45 @@ import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||
|
||||
extension MarkCollapsedEmailReadSuccess on ThreadDetailController {
|
||||
void markCollapsedEmailReadSuccess(MarkAsEmailReadSuccess success) {
|
||||
_updateEmailListInMailboxDashboardController(success);
|
||||
_updateEmailKeywordInThreadDetailController(success);
|
||||
_updateEmailCollapsedStatusInThreadDetailController(success);
|
||||
}
|
||||
|
||||
void _updateEmailListInMailboxDashboardController(
|
||||
MarkAsEmailReadSuccess success,
|
||||
) {
|
||||
mailboxDashBoardController.updateEmailFlagByEmailIds(
|
||||
[success.emailId],
|
||||
readAction: success.readActions,
|
||||
);
|
||||
if (success.readActions == ReadActions.markAsUnread) {
|
||||
}
|
||||
|
||||
void _updateEmailKeywordInThreadDetailController(
|
||||
MarkAsEmailReadSuccess success,
|
||||
) {
|
||||
if (success.readActions == ReadActions.markAsRead) {
|
||||
emailIdsPresentation[success.emailId]
|
||||
?.keywords
|
||||
?[KeyWordIdentifier.emailSeen] = true;
|
||||
} else {
|
||||
emailIdsPresentation[success.emailId]
|
||||
?.keywords?.remove(KeyWordIdentifier.emailSeen);
|
||||
}
|
||||
}
|
||||
|
||||
void _updateEmailCollapsedStatusInThreadDetailController(
|
||||
MarkAsEmailReadSuccess success,
|
||||
) {
|
||||
if (success.readActions == ReadActions.markAsRead) return;
|
||||
if (emailIdsPresentation.length == 1) {
|
||||
closeThreadDetailAction(currentContext);
|
||||
} else {
|
||||
emailIdsPresentation[success.emailId] = emailIdsPresentation
|
||||
[success.emailId]
|
||||
?.copyWith(
|
||||
emailInThreadStatus: EmailInThreadStatus.collapsed,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-3
@@ -83,7 +83,7 @@ extension ThreadDetailOnEmailActionClick on ThreadDetailController {
|
||||
}
|
||||
|
||||
void _markRead(PresentationEmail presentationEmail) {
|
||||
if (session == null || accountId == null) return;
|
||||
if (session == null || accountId == null || presentationEmail.hasRead) return;
|
||||
|
||||
consumeState(emailActionReactor.markAsEmailRead(
|
||||
session!,
|
||||
@@ -94,7 +94,7 @@ extension ThreadDetailOnEmailActionClick on ThreadDetailController {
|
||||
}
|
||||
|
||||
void _unRead(PresentationEmail presentationEmail) {
|
||||
if (session == null || accountId == null) return;
|
||||
if (session == null || accountId == null || !presentationEmail.hasRead) return;
|
||||
|
||||
consumeState(emailActionReactor.markAsEmailRead(
|
||||
session!,
|
||||
@@ -108,7 +108,10 @@ extension ThreadDetailOnEmailActionClick on ThreadDetailController {
|
||||
PresentationEmail presentationEmail,
|
||||
MarkStarAction markStarAction,
|
||||
) {
|
||||
if (session == null || accountId == null) return;
|
||||
if (session == null ||
|
||||
accountId == null ||
|
||||
(presentationEmail.hasStarred && markStarAction == MarkStarAction.markStar) ||
|
||||
(!presentationEmail.hasStarred && markStarAction == MarkStarAction.unMarkStar)) return;
|
||||
|
||||
consumeState(emailActionReactor.markAsStarEmail(
|
||||
session!,
|
||||
|
||||
+6
@@ -1,9 +1,11 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:model/email/email_action_type.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/bindings/email_bindings.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
|
||||
import 'package:model/email/email_in_thread_status.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/focus_thread_detail_expanded_email.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';
|
||||
|
||||
extension ToggleThreadDetailCollapeExpand on ThreadDetailController {
|
||||
@@ -27,6 +29,10 @@ extension ToggleThreadDetailCollapeExpand on ThreadDetailController {
|
||||
EmailBindings(currentEmailId: presentationEmail.id).dependencies();
|
||||
} else {
|
||||
focusExpandedEmail(emailId);
|
||||
threadDetailOnEmailActionClick(
|
||||
presentationEmail,
|
||||
EmailActionType.markAsRead,
|
||||
);
|
||||
}
|
||||
for (var key in emailIdsPresentation.keys) {
|
||||
if (emailIdsPresentation[key] == null) continue;
|
||||
|
||||
@@ -62,6 +62,7 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
|
||||
EmailActionType.printAll,
|
||||
EmailActionType.moveToMailbox,
|
||||
],
|
||||
emailIsRead: presentationEmail.hasRead,
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/resources/image_paths.dart';
|
||||
import 'package:core/presentation/utils/responsive_utils.dart';
|
||||
import 'package:core/presentation/utils/theme_utils.dart';
|
||||
import 'package:core/presentation/views/text/text_overflow_builder.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
@@ -73,6 +74,7 @@ class ThreadDetailCollapsedEmail extends StatelessWidget {
|
||||
showRecipients: false,
|
||||
onTapAvatarActionClick: onToggleThreadDetailCollapseExpand,
|
||||
mailboxContain: mailboxContain,
|
||||
showUnreadVisualization: true,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -80,7 +82,11 @@ class ThreadDetailCollapsedEmail extends StatelessWidget {
|
||||
preview,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
style: ThemeUtils.textStyleBodyBody1(
|
||||
fontWeight: presentationEmail.hasRead
|
||||
? FontWeight.normal
|
||||
: FontWeight.w600,
|
||||
).copyWith(height: 24),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user