TF-3643 Thread Detail get emails meta data method

TF-3643 Thread Detail cache email with thread id

TF-3643 Thread Detail view thread detail

TF-3643 Thread Detail [Part 2 fixup] remove ThreadDetailController dependency from MailboxDashboardController

TF-3643 Thread Detail [Part 2 fixup] remove EmailSupervisorController dependency from ThreadDetailController

TF-3643 Thread Detail [Part 2 fixup] move isSearchRunning check into ThreadDetailController

TF-3643 Thread Detail [Part 2 fixup] ensure GetEmailsByIdsInteractor performance

TF-3643 Thread Detail [Part 2 fixup] unit test loadMoreThreadDetailEmails

TF-3643 Thread Detail [Part 2 fixup] unit test getEmailIdsToLoad

TF-3643 Thread Detail [Part 2 fixup] optimize ThreadDetailBinding bindingsDataSource

TF-3643 Thread Detail [Part 2 fixup] optimize getThreadDetailLoadingView

TF-3643 Thread Detail [Part 2 fixup] readd unique ValueKey for HtmlContentViewerOnWebWidget

TF-3643 Thread Detail [Part 2 fixup] unit test initializeThreadDetailEmails

TF-3643 Thread Detail [Part 3 fixup] Update private action button methods

TF-3643 Thread Detail [Part 4 fixup] Collapsed email preview 65 characters max

TF-3643 Thread Detail [Part 4 fixup] Remove personal scripts

TF-3643 Thread Detail move EmailInThreadStatus into PresentationEmail

TF-3643 Thread Detail refactor toggle collapse expand

TF-3643 Thread Detail EmailViewAppBarWidget required parameters

TF-3643 Thread Detail tap avatar toggle expansion

TF-3643 Thread Detail fix cannot open single email

TF-3643 Thread Detail fix open thread first time blank view

TF-3643 Thread Detail fix thread detail UI

TF-3643 Thread Detail fix round bottom view

TF-3643 Thread Detail fix round bottom view

TF-3643 Thread Detail bottom actions depend on current expanded email

TF-3643 Thread Detail hide next previous actions

TF-3643 Thread Detail fix load more show initial loading view

TF-3643 Thread Detail fix cannot view single email

TF-3643 Thread Detail fix bottom place holder not aligned

TF-3643 Thread Detail fix back to search view

TF-3643 Thread Detail fix display 65 character preview collapsed email

TF-3643 Thread Detail fix search not work

TF-3643 Thread Detail fix bottom place holder UI

TF-3643 Thread Detail fix expand view

TF-3643 Thread Detail jump to top of last email view

TF-3643 Thread Detail remove toMe condition of filtering thread

TF-3643 Thread Detail fix sort thread detail

TF-3643 Thread Detail fix assert not work in release

TF-3643 Thread Detail add load more test case <20

TF-3643 Thread Detail remove the use of currentThreadId

TF-3643 Thread Detail remove the use of selectedEmail in SingleEmailController

TF-3643 Thread Detail fix unit test SingleEmailController calendar

TF-3643 Thread Detail disable 2 way scroll control

TF-3643 Thread Detail allow thread to load sent email incase thread only contains 1 email

TF-3643 Thread Detail disable single email page view

TF-3643 Thread Detail update emailsNotLoadedCount name

TF-3643 Thread Detail remove collapsed email char limit

TF-3643 Thread Detail fix CI

TF-3643 Thread Detail unit test handleGetEmailIdsByThreadIdSuccess

TF-3643 Thread Detail make _currentEmailId private

TF-3643 Thread Detail fix search click users

TF-3643 Thread Detail fix scroll jump

TF-3643 Thread Detail fix scroll jump

TF-3643 Thread Detail fix web back action

TF-3643 Thread Detail round bottom actions

TF-3643 Thread Detail fix tablet large empty email

TF-3643 Thread Detail fix email subject selectable

TF-3643 Thread Detail fix email single view with top divider

TF-3643 Thread Detail fix email cache threadId missing

TF-3643 Thread Detail fix filter sent email in thread details

TF-3643 Thread Detail fix select text

TF-3643 Thread Detail fix subject alignment

TF-3643 Thread Detail fix subject alignment

TF-3643 Thread Detail fix tablet large empty email

TF-3643 Thread Detail add nullcheck for _jumpScrollViewToTopOfEmail

TF-3643 Thread Detail decouple ThreadDetailController from SingleEmailController

TF-3643 Thread Detail combine emailIds to emailIdsPresentation

TF-3643 Thread Detail remove currentEmailLoaded obx listener of SingleEmailController

TF-3643 Thread Detail replace threadAction with an email ui action

TF-3643 Thread Detail fix top and bottom bar delay displaying

TF-3643 Thread Detail update ReplyForwardBottomBarAction to PerformEmailActionInThreadDetailAction

TF-3643 Thread Detail use getOwnEmailAddress in ThreadDetailAppBar

TF-3643 Thread Detail add TextOverflow.ellipsis to EmailSubjectWidget

TF-3643 Thread Detail fix round border threadDetailLoadingView
This commit is contained in:
DatDang
2025-05-12 15:22:29 +07:00
committed by Dat H. Pham
parent 7c3af832e4
commit 58a576adeb
84 changed files with 2845 additions and 558 deletions
@@ -1,5 +1,8 @@
import 'package:jmap_dart_client/jmap/core/state.dart' as jmap;
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart';
import 'package:tmail_ui_user/features/base/action/ui_action.dart';
class EmailUIAction extends UIAction {
@@ -28,4 +31,28 @@ class HideEmailContentViewAction extends EmailUIAction {}
class ShowEmailContentViewAction extends EmailUIAction {}
class RefreshAllEmailAction extends EmailUIAction {}
class RefreshAllEmailAction extends EmailUIAction {}
class CloseEmailInThreadDetailAction extends EmailUIAction {
final EmailId emailId;
CloseEmailInThreadDetailAction(this.emailId);
@override
List<Object?> get props => [emailId];
}
class PerformEmailActionInThreadDetailAction extends EmailUIAction {
PerformEmailActionInThreadDetailAction({
required this.emailActionType,
required this.presentationEmail,
});
final EmailActionType emailActionType;
final PresentationEmail presentationEmail;
@override
List<Object?> get props => [
emailActionType,
presentationEmail,
];
}
@@ -1,5 +1,6 @@
import 'package:core/core.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:tmail_ui_user/features/base/base_bindings.dart';
import 'package:tmail_ui_user/features/caching/utils/local_storage_manager.dart';
import 'package:tmail_ui_user/features/caching/utils/session_storage_manager.dart';
@@ -32,7 +33,6 @@ import 'package:tmail_ui_user/features/email/domain/usecases/preview_email_from_
import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/get_html_content_from_attachment_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart';
import 'package:tmail_ui_user/features/email/presentation/controller/email_supervisor_controller.dart';
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
import 'package:tmail_ui_user/features/login/data/network/interceptors/authorization_interceptors.dart';
import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart';
@@ -62,10 +62,14 @@ import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart';
import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart';
class EmailBindings extends BaseBindings {
final EmailId? currentEmailId;
EmailBindings({this.currentEmailId});
String? get tag => currentEmailId?.id.value;
@override
void bindingsController() {
Get.put(EmailSupervisorController());
Get.put(SingleEmailController(
Get.find<GetEmailContentInteractor>(),
Get.find<MarkAsEmailReadInteractor>(),
@@ -83,7 +87,8 @@ class EmailBindings extends BaseBindings {
Get.find<GetHtmlContentFromAttachmentInteractor>(),
Get.find<DownloadAllAttachmentsForWebInteractor>(),
Get.find<ExportAllAttachmentsInteractor>(),
));
currentEmailId: currentEmailId,
), tag: tag);
}
@override
@@ -101,7 +101,6 @@ import 'package:tmail_ui_user/features/email/domain/usecases/send_receipt_to_sen
import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart';
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
import 'package:tmail_ui_user/features/email/presentation/bindings/calendar_event_interactor_bindings.dart';
import 'package:tmail_ui_user/features/email/presentation/controller/email_supervisor_controller.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_attendee_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_organizer_extension.dart';
@@ -135,8 +134,10 @@ import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart';
import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/rules_filter_creator_arguments.dart';
import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart';
import 'package:tmail_ui_user/features/search/email/presentation/search_email_controller.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.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/thread_detail_controller.dart';
import 'package:tmail_ui_user/main/error/capability_validator.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/localizations/localization_service.dart';
@@ -156,7 +157,6 @@ import 'package:twake_previewer_flutter/twake_plain_text_previewer/twake_plain_t
class SingleEmailController extends BaseController with AppLoaderMixin {
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
final emailSupervisorController = Get.find<EmailSupervisorController>();
final _downloadManager = Get.find<DownloadManager>();
final _printUtils = Get.find<PrintUtils>();
final _attachmentListScrollController = ScrollController();
@@ -177,6 +177,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
final GetHtmlContentFromAttachmentInteractor _getHtmlContentFromAttachmentInteractor;
final DownloadAllAttachmentsForWebInteractor _downloadAllAttachmentsForWebInteractor;
final ExportAllAttachmentsInteractor _exportAllAttachmentsInteractor;
final EmailId? _currentEmailId;
CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor;
SendReceiptToSenderInteractor? _sendReceiptToSenderInteractor;
@@ -185,6 +186,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
MaybeCalendarEventInteractor? _maybeCalendarEventInteractor;
RejectCalendarEventInteractor? _rejectCalendarEventInteractor;
AcceptCounterCalendarEventInteractor? _acceptCounterCalendarEventInteractor;
ThreadDetailController? _threadDetailController;
final emailContents = RxnString();
final attachments = <Attachment>[].obs;
@@ -198,15 +200,19 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
final attendanceStatus = Rxn<AttendanceStatus>();
final htmlContentViewKey = GlobalKey<HtmlContentViewState>();
EmailId? _currentEmailId;
Identity? _identitySelected;
ButtonState? _printEmailButtonState;
final obxListeners = <Worker>[];
final StreamController<Either<Failure, Success>> _downloadProgressStateController =
StreamController<Either<Failure, Success>>.broadcast();
Stream<Either<Failure, Success>> get downloadProgressState => _downloadProgressStateController.stream;
PresentationEmail? get currentEmail => mailboxDashBoardController.selectedEmail.value;
PresentationEmail? get currentEmail {
return _currentEmailId == null
? null
: _threadDetailController?.emailIdsPresentation[_currentEmailId];
}
bool get calendarEventProcessing => viewState.value.fold(
(failure) => false,
@@ -238,11 +244,14 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
this._previewEmailFromEmlFileInteractor,
this._getHtmlContentFromAttachmentInteractor,
this._downloadAllAttachmentsForWebInteractor,
this._exportAllAttachmentsInteractor,
);
this._exportAllAttachmentsInteractor, {
EmailId? currentEmailId,
}) : _currentEmailId = currentEmailId;
@override
void onInit() {
_threadDetailController = getBinding<ThreadDetailController>();
_injectCalendarEventBindings(session, accountId);
_registerObxStreamListener();
_listenDownloadAttachmentProgressState();
super.onInit();
@@ -250,6 +259,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
@override
void onClose() {
_threadDetailController = null;
_downloadProgressStateController.close();
_attachmentListScrollController.dispose();
super.onClose();
@@ -357,28 +367,15 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
void _registerObxStreamListener() {
ever(mailboxDashBoardController.accountId, (accountId) {
if (accountId is AccountId) {
_injectAndGetInteractorBindings(
session,
accountId
);
}
if (accountId != null) {
_injectAndGetInteractorBindings(session, accountId!);
}
WidgetsBinding.instance.addPostFrameCallback((_) {
_handleOpenEmailDetailedView();
});
ever<PresentationEmail?>(
mailboxDashBoardController.selectedEmail,
_handleOpenEmailDetailedView
);
ever(mailboxDashBoardController.emailUIAction, (action) {
obxListeners.add(ever(mailboxDashBoardController.emailUIAction, (action) {
if (action is CloseEmailDetailedViewToRedirectToTheInboxAction) {
if (emailSupervisorController.supportedPageView.isTrue) {
emailSupervisorController.popEmailQueue(_currentEmailId);
emailSupervisorController.setCurrentEmailIndex(-1);
emailSupervisorController.disposePageViewController();
}
_updateCurrentEmailId(null);
_resetToOriginalValue(isEmailClosing: true);
mailboxDashBoardController.clearSelectedEmail();
mailboxDashBoardController.dispatchRoute(DashboardRoutes.thread);
@@ -392,42 +389,54 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
} else if (action is ShowEmailContentViewAction) {
isEmailContentHidden.value = false;
mailboxDashBoardController.clearEmailUIAction();
} else if (action is PerformEmailActionInThreadDetailAction) {
if (action.presentationEmail.id != _currentEmailId) return;
pressEmailAction(
action.emailActionType,
action.presentationEmail,
);
} else if (action is CloseEmailInThreadDetailAction) {
if (action.emailId != _currentEmailId) return;
closeEmailView(context: currentContext);
for (var worker in obxListeners) {
worker.dispose();
}
Get.delete<SingleEmailController>(tag: _currentEmailId?.id.value);
}
});
}));
ever(mailboxDashBoardController.viewState, (viewState) {
obxListeners.add(ever(mailboxDashBoardController.viewState, (viewState) {
viewState.map((success) {
if (success is UnsubscribeEmailSuccess) {
emailUnsubscribe.value = null;
}
});
});
}));
if (_threadDetailController != null) {
obxListeners.add(ever(
_threadDetailController!.currentExpandedEmailId,
(emailId) {
if (emailId == null || emailId != _currentEmailId) return;
_threadDetailController!.currentEmailLoaded.value = currentEmailLoaded.value;
},
));
}
}
bool isListEmailContainSelectedEmail(PresentationEmail selectedEmail) {
return emailSupervisorController.currentListEmail.isNotEmpty
&& emailSupervisorController.currentListEmail.listEmailIds.contains(selectedEmail.id);
}
void _handleOpenEmailDetailedView(PresentationEmail? selectedEmail) {
if (selectedEmail == null || _currentEmailId == selectedEmail.id) {
void _handleOpenEmailDetailedView() {
if (currentEmail == null) {
log('SingleEmailController::_handleOpenEmailDetailedView(): email unselected');
return;
}
emailLoadedViewState.value = Right<Failure, Success>(GetEmailContentLoading());
emailSupervisorController.updateNewCurrentListEmail();
_updateCurrentEmailId(selectedEmail.id);
_resetToOriginalValue();
if (isListEmailContainSelectedEmail(selectedEmail)) {
_createMultipleEmailViewAsPageView(selectedEmail.id!);
} else {
_createSingleEmailView(selectedEmail.id!);
}
_createSingleEmailView(currentEmail!.id!);
if (!selectedEmail.hasRead) {
markAsEmailRead(selectedEmail, ReadActions.markAsRead, MarkReadAction.tap);
if (!currentEmail!.hasRead) {
markAsEmailRead(currentEmail!, ReadActions.markAsRead, MarkReadAction.tap);
}
if (mailboxDashBoardController.listIdentities.isEmpty) {
@@ -437,20 +446,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
}
void _updateCurrentEmailId(EmailId? emailId) {
_currentEmailId = emailId;
}
void _createMultipleEmailViewAsPageView(EmailId emailId) {
log('SingleEmailController::_createMultipleEmailViewAsPageView():');
emailSupervisorController.supportedPageView.value = true;
emailSupervisorController.createPageControllerAndJumpToEmailById(emailId);
_getEmailContentAction(emailId);
}
void _createSingleEmailView(EmailId emailId) {
log('SingleEmailController::_createSingleEmailView():');
emailSupervisorController.supportedPageView.value = false;
_getEmailContentAction(emailId);
}
@@ -461,7 +458,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
(failure) => null,
(success) {
if (success is StartDownloadAttachmentForWeb && !success.previewerSupported) {
emailSupervisorController.mailboxDashBoardController.addDownloadTask(
mailboxDashBoardController.addDownloadTask(
DownloadTaskState(
taskId: success.taskId,
attachment: success.attachment,
@@ -480,7 +477,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
final percent = success.progress.round();
log('SingleEmailController::DownloadingAttachmentForWeb(): $percent%');
emailSupervisorController.mailboxDashBoardController.updateDownloadTask(
mailboxDashBoardController.updateDownloadTask(
success.taskId,
(currentTask) {
final newTask = currentTask.copyWith(
@@ -491,7 +488,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
return newTask;
});
} else if (success is StartDownloadAllAttachmentsForWeb) {
emailSupervisorController.mailboxDashBoardController.addDownloadTask(
mailboxDashBoardController.addDownloadTask(
DownloadTaskState(
taskId: success.taskId,
attachment: success.attachment,
@@ -510,7 +507,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
final percent = success.progress.round();
log('SingleEmailController::DownloadingAttachmentForWeb(): $percent%');
emailSupervisorController.mailboxDashBoardController.updateDownloadTask(
mailboxDashBoardController.updateDownloadTask(
success.taskId,
(currentTask) {
final newTask = currentTask.copyWith(
@@ -590,59 +587,42 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
void _getEmailContentAction(EmailId emailId) {
final emailLoaded = emailSupervisorController.getEmailInQueueByEmailId(emailId);
if (session != null && accountId != null) {
try {
final baseDownloadUrl = session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl);
TransformConfiguration transformConfiguration = PlatformInfo.isWeb
? TransformConfiguration.forPreviewEmailOnWeb()
: TransformConfiguration.forPreviewEmail();
if (emailLoaded != null) {
consumeState(Stream.value(Right<Failure, Success>(
GetEmailContentSuccess(
htmlEmailContent: emailLoaded.htmlContent,
attachments: emailLoaded.attachments,
inlineImages: emailLoaded.inlineImages,
emailCurrent: emailLoaded.emailCurrent
)
)));
} else {
if (session != null && accountId != null) {
try {
final baseDownloadUrl = session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl);
TransformConfiguration transformConfiguration = PlatformInfo.isWeb
? TransformConfiguration.forPreviewEmailOnWeb()
: TransformConfiguration.forPreviewEmail();
consumeState(_getEmailContentInteractor.execute(
session!,
accountId!,
emailId,
baseDownloadUrl,
transformConfiguration
));
} catch (e) {
logError('SingleEmailController::_getEmailContentAction(): $e');
consumeState(Stream.value(Left(GetEmailContentFailure(
e,
onRetry: e is UnknownUriException
? null
: _getEmailContentInteractor.execute(
session!,
accountId!,
emailId,
session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl),
PlatformInfo.isWeb
? TransformConfiguration.forPreviewEmailOnWeb()
: TransformConfiguration.forPreviewEmail(),
),
))));
}
consumeState(_getEmailContentInteractor.execute(
session!,
accountId!,
emailId,
baseDownloadUrl,
transformConfiguration
));
} catch (e) {
logError('SingleEmailController::_getEmailContentAction(): $e');
consumeState(Stream.value(Left(GetEmailContentFailure(
e,
onRetry: e is UnknownUriException
? null
: _getEmailContentInteractor.execute(
session!,
accountId!,
emailId,
session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl),
PlatformInfo.isWeb
? TransformConfiguration.forPreviewEmailOnWeb()
: TransformConfiguration.forPreviewEmail(),
),
))));
}
}
}
void _getEmailContentOfflineSuccess(GetEmailContentFromCacheSuccess success) {
emailLoadedViewState.value = Right<Failure, Success>(success);
if (emailSupervisorController.presentationEmailsLoaded.length > ThreadConstants.defaultLimit.value.toInt()) {
emailSupervisorController.popFirstEmailQueue();
}
emailSupervisorController.popEmailQueue(success.emailCurrent.id);
currentEmailLoaded.value = EmailLoaded(
htmlContent: success.htmlEmailContent,
@@ -650,7 +630,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
inlineImages: List.of(success.inlineImages ?? []),
emailCurrent: success.emailCurrent,
);
emailSupervisorController.pushEmailQueue(currentEmailLoaded.value!);
_threadDetailController?.currentEmailLoaded.value = currentEmailLoaded.value;
if (success.emailCurrent.id == currentEmail?.id) {
attachments.value = success.attachments ?? [];
@@ -683,10 +663,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
void _getEmailContentSuccess(GetEmailContentSuccess success) {
emailLoadedViewState.value = Right<Failure, Success>(success);
if (emailSupervisorController.presentationEmailsLoaded.length > ThreadConstants.defaultLimit.value.toInt()) {
emailSupervisorController.popFirstEmailQueue();
}
emailSupervisorController.popEmailQueue(success.emailCurrent?.id);
currentEmailLoaded.value = EmailLoaded(
htmlContent: success.htmlEmailContent,
@@ -694,7 +670,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
inlineImages: List.of(success.inlineImages ?? []),
emailCurrent: success.emailCurrent,
);
emailSupervisorController.pushEmailQueue(currentEmailLoaded.value!);
_threadDetailController?.currentEmailLoaded.value = currentEmailLoaded.value;
if (success.emailCurrent?.id == currentEmail?.id) {
attachments.value = success.attachments ?? [];
@@ -745,6 +721,14 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
emailUnsubscribe.value = null;
}
}
if ((_threadDetailController?.emailIdsPresentation.keys.length ?? 0) > 1 == true) {
_jumpScrollViewToTopOfEmail();
}
}
void _jumpScrollViewToTopOfEmail() {
if (_currentEmailId == null) return;
_threadDetailController?.focusExpandedEmail(_currentEmailId!);
}
void _handleUnsubscribe(String listUnsubscribe) {
@@ -809,9 +793,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
void _handleMarkAsEmailReadCompleted(ReadActions readActions) {
if (_currentEmailId != null) {
if (currentEmail?.id != null) {
mailboxDashBoardController.updateEmailFlagByEmailIds(
[_currentEmailId!],
[currentEmail!.id!],
readAction: readActions,
);
}
@@ -952,7 +936,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
final downloadAllSupported = session!.isDownloadAllSupported(accountId);
if (!downloadAllSupported || _currentEmailId == null) {
if (!downloadAllSupported) {
consumeState(Stream.value(Left(ExportAllAttachmentsFailure())));
return;
}
@@ -960,7 +944,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
final baseDownloadAllUrl = session!.getDownloadAllCapability(accountId)!.endpoint!;
consumeState(_exportAllAttachmentsInteractor.execute(
accountId!,
_currentEmailId!,
currentEmail!.id!,
baseDownloadAllUrl,
outputFileName,
cancelToken: cancelToken,
@@ -1071,7 +1055,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
final downloadAllSupported = session.isDownloadAllSupported(accountId);
final emailId = _currentEmailId;
final emailId = currentEmail?.id;
if (!downloadAllSupported || emailId == null) {
consumeState(Stream.value(Left(DownloadAllAttachmentsForWebFailure(
@@ -1454,10 +1438,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
final newEmail = currentEmail?.updateKeywords({
KeyWordIdentifier.emailFlagged: success.markStarAction == MarkStarAction.markStar,
});
mailboxDashBoardController.setSelectedEmail(newEmail);
final emailId = newEmail?.id;
if (emailId == null) return;
_threadDetailController?.emailIdsPresentation[emailId] = newEmail;
mailboxDashBoardController.updateEmailFlagByEmailIds(
[emailId],
markStarAction: success.markStarAction,
@@ -1508,6 +1492,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
case EmailActionType.editAsNewEmail:
_editAsNewEmail(presentationEmail);
break;
case EmailActionType.reply:
_replyEmail(presentationEmail);
break;
default:
break;
}
@@ -1639,7 +1626,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
return;
}
if (currentEmail == null || _currentEmailId == null) {
if (currentEmail == null) {
appToast.showToastErrorMessage(
currentOverlayContext!,
AppLocalizations.of(context).toastMessageCannotFoundEmailIdWhenSendReceipt);
@@ -1700,16 +1687,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
void closeEmailView({BuildContext? context}) {
if (emailSupervisorController.supportedPageView.isTrue) {
emailSupervisorController.popEmailQueue(_currentEmailId);
emailSupervisorController.setCurrentEmailIndex(-1);
emailSupervisorController.disposePageViewController();
}
mailboxDashBoardController.clearSelectedEmail();
_updateCurrentEmailId(null);
_resetToOriginalValue(isEmailClosing: true);
_replaceBrowserHistory();
if (mailboxDashBoardController.searchController.isSearchEmailRunning) {
if (mailboxDashBoardController.searchController.isSearchEmailRunning
|| getBinding<SearchEmailController>()?.searchIsRunning.value == true
) {
if (context != null && responsiveUtils.isWebDesktop(context)) {
mailboxDashBoardController.dispatchRoute(DashboardRoutes.thread);
} else {
@@ -1723,8 +1706,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
}
bool get isOpenEmailNotMailboxFromRoute => emailSupervisorController.supportedPageView.isFalse
&& mailboxDashBoardController.selectedMailbox.value == null;
bool get isOpenEmailNotMailboxFromRoute => mailboxDashBoardController.selectedMailbox.value == null;
void _replaceBrowserHistory() {
if (PlatformInfo.isWeb) {
@@ -1761,17 +1743,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
mailboxDashBoardController.openComposer(ComposerArguments());
break;
case EmailActionType.reply:
mailboxDashBoardController.openComposer(
ComposerArguments.replyEmail(
presentationEmail: presentationEmail,
content: currentEmailLoaded.value?.htmlContent ?? '',
inlineImages: currentEmailLoaded.value?.inlineImages ?? [],
mailboxRole: presentationEmail.mailboxContain?.role,
messageId: currentEmailLoaded.value?.emailCurrent?.messageId,
references: currentEmailLoaded.value?.emailCurrent?.references,
listPost: currentEmailLoaded.value?.emailCurrent?.listPost,
)
);
_replyEmail(presentationEmail);
break;
case EmailActionType.replyToList:
log('SingleEmailController::pressEmailAction:replyToList');
@@ -1860,15 +1832,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
}
void toggleScrollPhysicsPagerView(bool leftDirection) {
log('SingleEmailController::toggleScrollPhysicsPagerView():leftDirection: $leftDirection');
if (leftDirection) {
emailSupervisorController.moveToNextEmail();
} else {
emailSupervisorController.backToPreviousEmail();
}
}
void _storeOpenedEmailAction(Session? session, AccountId? accountId, DetailedEmail detailedEmail) async {
if (session != null && accountId != null) {
consumeState(_storeOpenedEmailInteractor.execute(session, accountId, detailedEmail));
@@ -1913,9 +1876,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
void _handleParseCalendarEventSuccess(ParseCalendarEventSuccess success) {
emailLoadedViewState.value = Right<Failure, Success>(success);
blobCalendarEvent.value = success.blobCalendarEventList.firstOrNull;
if (PlatformInfo.isMobile) {
_enableScrollPageView();
}
updateAttendanceStatus(success);
}
@@ -1924,10 +1884,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
emailContents.value = currentEmailLoaded.value?.htmlContent;
}
void _enableScrollPageView() {
emailSupervisorController.scrollPhysicsPageView.value = null;
}
void openNewTabAction(String link) {
AppUtils.launchLink(link);
}
@@ -1938,8 +1894,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
void openAttachmentList(BuildContext context, List<Attachment> attachments) {
final tag = _currentEmailId?.id.value;
if (responsiveUtils.isMobile(context)) {
(AttachmentListBottomSheetBuilder(context, attachments, imagePaths, _attachmentListScrollController)
(AttachmentListBottomSheetBuilder(context, attachments, imagePaths, _attachmentListScrollController, tag)
..onCloseButtonAction(() => popBack())
..onDownloadAttachmentFileAction((attachment) => handleDownloadAttachmentAction(context, attachment))
..onViewAttachmentFileAction((attachment) => handleViewAttachmentAction(context, attachment))
@@ -1963,6 +1920,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
onDownloadAllButtonAction: isDownloadAllSupported()
? () => downloadAllAttachmentsForWeb('TwakeMail-${DateTime.now()}')
: null,
singleEmailControllerTag: tag,
)
),
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
@@ -2240,6 +2198,20 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
);
}
void _replyEmail(PresentationEmail presentationEmail) {
mailboxDashBoardController.openComposer(
ComposerArguments.replyEmail(
presentationEmail: presentationEmail,
content: currentEmailLoaded.value?.htmlContent ?? '',
inlineImages: currentEmailLoaded.value?.inlineImages ?? [],
mailboxRole: presentationEmail.mailboxContain?.role,
messageId: currentEmailLoaded.value?.emailCurrent?.messageId,
references: currentEmailLoaded.value?.emailCurrent?.references,
listPost: currentEmailLoaded.value?.emailCurrent?.listPost,
)
);
}
void handleDownloadAttachmentAction(
BuildContext context,
Attachment attachment,
+183 -194
View File
@@ -1,26 +1,32 @@
import 'dart:math';
import 'package:core/presentation/constants/constants_ui.dart';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart';
import 'package:core/presentation/views/html_viewer/html_content_viewer_widget.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/mail/calendar/calendar_event.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/email_extension.dart';
import 'package:model/extensions/list_email_address_extension.dart';
import 'package:model/extensions/presentation_email_extension.dart';
import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/base/widget/optional_expanded.dart';
import 'package:tmail_ui_user/features/base/widget/optional_scroll.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/controller/single_email_controller.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_event_extension.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/email/presentation/model/email_loaded.dart';
import 'package:tmail_ui_user/features/email/presentation/styles/email_view_styles.dart';
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/calendar_event_action_banner_widget.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/calendar_event_detail_widget.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/calendar_event_information_widget.dart';
@@ -43,56 +49,45 @@ import 'package:tmail_ui_user/main/utils/app_utils.dart';
class EmailView extends GetWidget<SingleEmailController> {
const EmailView({Key? key}) : super(key: key);
const EmailView({
super.key,
this.isInsideThreadDetailView = false,
this.emailId,
this.isFirstEmailInThreadDetail = false,
this.threadSubject,
this.onToggleThreadDetailCollapseExpand,
});
final bool isInsideThreadDetailView;
final EmailId? emailId;
final bool isFirstEmailInThreadDetail;
final String? threadSubject;
final VoidCallback? onToggleThreadDetailCollapseExpand;
@override
String? get tag => emailId?.id.value;
@override
Widget build(BuildContext context) {
return SelectionArea(
child: Scaffold(
backgroundColor: controller.responsiveUtils.isWebDesktop(context)
child: ColoredBox(
color: controller.responsiveUtils.isWebDesktop(context)
? AppColor.colorBgDesktop
: Colors.white,
appBar: PlatformInfo.isIOS
? PreferredSize(
preferredSize: const Size(double.infinity, 100),
child: Obx(() {
if (controller.currentEmail != null) {
return SafeArea(
top: false,
bottom: false,
child: EmailViewAppBarWidget(
key: const Key('email_view_app_bar_widget'),
presentationEmail: controller.currentEmail!,
mailboxContain: _getMailboxContain(controller.currentEmail!),
isSearchActivated: controller.mailboxDashBoardController.searchController.isSearchEmailRunning,
onBackAction: () => controller.closeEmailView(context: context),
onEmailActionClick: (email, action) => controller.handleEmailAction(context, email, action),
onMoreActionClick: (presentationEmail, position) => _handleMoreEmailAction(context: context, presentationEmail: presentationEmail, position: position)
),
);
} else {
return const SizedBox.shrink();
}
})
)
: null,
body: SafeArea(
child: SafeArea(
right: controller.responsiveUtils.isLandscapeMobile(context),
left: controller.responsiveUtils.isLandscapeMobile(context),
bottom: !PlatformInfo.isIOS,
child: Container(
clipBehavior: Clip.antiAlias,
decoration: controller.responsiveUtils.isWebDesktop(context)
? const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: Colors.white)
: const BoxDecoration(color: Colors.white),
decoration: _getDecorationEmailView(context),
margin: _getMarginEmailView(context),
child: Obx(() {
final currentEmail = controller.currentEmail;
final currentEmailListener = Rxn(controller.currentEmail);
final currentEmail = currentEmailListener.value;
if (currentEmail != null) {
return Column(children: [
if (!PlatformInfo.isIOS)
if (!isInsideThreadDetailView)
Obx(() => EmailViewAppBarWidget(
key: const Key('email_view_app_bar_widget'),
presentationEmail: currentEmail,
@@ -100,10 +95,20 @@ class EmailView extends GetWidget<SingleEmailController> {
isSearchActivated: controller.mailboxDashBoardController.searchController.isSearchEmailRunning,
onBackAction: () => controller.closeEmailView(context: context),
onEmailActionClick: (email, action) => controller.handleEmailAction(context, email, action),
onMoreActionClick: (presentationEmail, position) => _handleMoreEmailAction(context: context, presentationEmail: presentationEmail, position: position),
optionsWidget: PlatformInfo.isWeb && controller.emailSupervisorController.supportedPageView.isTrue
? _buildNavigatorPageViewWidgets(context)
onMoreActionClick: (presentationEmail, position) => _handleMoreEmailAction(
context: context,
presentationEmail: presentationEmail,
position: position,
emailLoaded: controller.currentEmailLoaded.value,
),
supportBackAction: !isInsideThreadDetailView,
appBarDecoration: isInsideThreadDetailView
? const BoxDecoration(border: Border(bottom: BorderSide(
color: AppColor.colorDividerEmailView,
)))
: null,
emailLoaded: controller.currentEmailLoaded.value,
isInsideThreadDetailView: isInsideThreadDetailView,
)),
Obx(() {
final vacation = controller.mailboxDashBoardController.vacationResponse.value;
@@ -124,123 +129,63 @@ class EmailView extends GetWidget<SingleEmailController> {
return const SizedBox.shrink();
}
}),
Expanded(
OptionalExpanded(
expandedEnabled: !isInsideThreadDetailView,
child: LayoutBuilder(builder: (context, constraints) {
return Obx(() {
bool supportedPageView = controller.emailSupervisorController.supportedPageView.isTrue && PlatformInfo.isMobile;
final currentListEmail = controller.emailSupervisorController.currentListEmail;
if (supportedPageView) {
return PageView.builder(
physics: controller.emailSupervisorController.scrollPhysicsPageView.value,
itemCount: currentListEmail.length,
allowImplicitScrolling: true,
controller: controller.emailSupervisorController.pageController,
onPageChanged: controller.emailSupervisorController.onPageChanged,
itemBuilder: (context, index) {
final currentEmail = currentListEmail[index];
if (PlatformInfo.isMobile) {
return SingleChildScrollView(
physics : const ClampingScrollPhysics(),
child: Container(
width: double.infinity,
alignment: Alignment.center,
color: Colors.white,
child: Obx(() => _buildEmailMessage(
context: context,
presentationEmail: currentEmail,
calendarEvent: controller.calendarEvent,
maxBodyHeight: constraints.maxHeight
))
if (PlatformInfo.isMobile) {
return OptionalScroll(
scrollEnabled: !isInsideThreadDetailView,
child: Container(
width: double.infinity,
alignment: Alignment.center,
color: Colors.white,
child: Obx(() => _buildEmailMessage(
context: context,
presentationEmail: currentEmail,
calendarEvent: controller.calendarEvent,
maxBodyHeight: constraints.maxHeight
))
)
);
} else {
return Obx(() {
final calendarEvent = controller.calendarEvent;
if (currentEmail.hasCalendarEvent && calendarEvent != null) {
return Padding(
padding: const EdgeInsetsDirectional.symmetric(horizontal: 4),
child: OptionalScroll(
scrollEnabled: !isInsideThreadDetailView,
child: Container(
width: double.infinity,
alignment: Alignment.center,
color: Colors.white,
child: _buildEmailMessage(
context: context,
presentationEmail: currentEmail,
calendarEvent: calendarEvent,
emailAddressSender: currentEmail.listEmailAddressSender.getListAddress(),
maxBodyHeight: constraints.maxHeight
)
);
} else {
return Obx(() {
final calendarEvent = controller.calendarEvent;
if (currentEmail.hasCalendarEvent && calendarEvent != null) {
return Padding(
padding: const EdgeInsetsDirectional.symmetric(horizontal: 4),
child: SingleChildScrollView(
physics : const ClampingScrollPhysics(),
child: Container(
width: double.infinity,
alignment: Alignment.center,
color: Colors.white,
child: _buildEmailMessage(
context: context,
presentationEmail: currentEmail,
calendarEvent: calendarEvent,
emailAddressSender: currentEmail.listEmailAddressSender.getListAddress(),
)
)
),
);
} else {
return _buildEmailMessage(
context: context,
presentationEmail: currentEmail,
maxBodyHeight: constraints.maxHeight
);
}
});
}
}
);
} else {
if (PlatformInfo.isMobile) {
return SingleChildScrollView(
physics : const ClampingScrollPhysics(),
child: Container(
width: double.infinity,
alignment: Alignment.center,
color: Colors.white,
child: Obx(() => _buildEmailMessage(
context: context,
presentationEmail: currentEmail,
calendarEvent: controller.calendarEvent,
maxBodyHeight: constraints.maxHeight
))
)
)
),
);
} else {
return Obx(() {
final calendarEvent = controller.calendarEvent;
if (currentEmail.hasCalendarEvent && calendarEvent != null) {
return Padding(
padding: const EdgeInsetsDirectional.symmetric(horizontal: 4),
child: SingleChildScrollView(
physics : const ClampingScrollPhysics(),
child: Container(
width: double.infinity,
alignment: Alignment.center,
color: Colors.white,
child: _buildEmailMessage(
context: context,
presentationEmail: currentEmail,
calendarEvent: calendarEvent,
emailAddressSender: currentEmail.listEmailAddressSender.getListAddress(),
maxBodyHeight: constraints.maxHeight
)
)
),
);
} else {
return _buildEmailMessage(
context: context,
presentationEmail: currentEmail,
maxBodyHeight: constraints.maxHeight
);
}
});
return _buildEmailMessage(
context: context,
presentationEmail: currentEmail,
maxBodyHeight: constraints.maxHeight
);
}
}
});
});
}
}),
),
Obx(() {
final emailLoaded = controller.currentEmailLoaded.value;
if (emailLoaded == null) return const SizedBox.shrink();
if (emailLoaded == null || isInsideThreadDetailView) {
return const SizedBox.shrink();
}
return EmailViewBottomBarWidget(
key: const Key('email_view_button_bar'),
@@ -263,51 +208,47 @@ class EmailView extends GetWidget<SingleEmailController> {
);
}
EdgeInsetsGeometry? _getMarginEmailView(BuildContext context) {
if (PlatformInfo.isWeb) {
if (controller.responsiveUtils.isDesktop(context)) {
return const EdgeInsetsDirectional.only(
end: 16,
bottom: 16
);
} else {
return const EdgeInsets.symmetric(vertical: 16);
}
} else {
return null;
BoxDecoration _getDecorationEmailView(BuildContext context) {
if (controller.responsiveUtils.isWebDesktop(context) && !isInsideThreadDetailView) {
return const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: Colors.white,
);
}
if (controller.currentEmail == null || !isInsideThreadDetailView) {
return const BoxDecoration(color: Colors.white);
}
return const BoxDecoration(
color: Colors.white,
border: Border(top: BorderSide(
color: AppColor.colorDividerEmailView,
width: 0.5,
)),
);
}
EdgeInsetsGeometry? _getMarginEmailView(BuildContext context) {
if (PlatformInfo.isMobile) return null;
if (isInsideThreadDetailView) {
return EdgeInsets.zero;
}
if (!controller.responsiveUtils.isDesktop(context)) {
return EdgeInsets.zero;
}
return const EdgeInsetsDirectional.only(
end: 16,
bottom: 16
);
}
PresentationMailbox? _getMailboxContain(PresentationEmail currentEmail) {
return currentEmail.findMailboxContain(controller.mailboxDashBoardController.mapMailboxById);
}
List<Widget> _buildNavigatorPageViewWidgets(BuildContext context) {
return [
if (controller.emailSupervisorController.nextEmailActivated)
TMailButtonWidget.fromIcon(
icon: DirectionUtils.isDirectionRTLByLanguage(context)
? controller.imagePaths.icOlder
: controller.imagePaths.icNewer,
iconColor: EmailViewStyles.iconColor,
iconSize: EmailViewStyles.pageViewIconSize,
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).newer,
onTapActionCallback: controller.emailSupervisorController.moveToNextEmail
),
if (controller.emailSupervisorController.previousEmailActivated)
TMailButtonWidget.fromIcon(
icon: DirectionUtils.isDirectionRTLByLanguage(context)
? controller.imagePaths.icNewer
: controller.imagePaths.icOlder,
iconColor: EmailViewStyles.iconColor,
iconSize: EmailViewStyles.pageViewIconSize,
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).older,
onTapActionCallback: controller.emailSupervisorController.backToPreviousEmail
),
];
}
Widget _buildEmailMessage({
required BuildContext context,
@@ -318,8 +259,16 @@ class EmailView extends GetWidget<SingleEmailController> {
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
EmailSubjectWidget(presentationEmail: presentationEmail),
if (!isInsideThreadDetailView || isFirstEmailInThreadDetail)
EmailSubjectWidget(
presentationEmail: presentationEmail.copyWith(
subject: threadSubject,
),
)
else
const SizedBox(height: 16),
Obx(() => InformationSenderAndReceiverBuilder(
emailSelected: presentationEmail,
imagePaths: controller.imagePaths,
@@ -329,6 +278,16 @@ class EmailView extends GetWidget<SingleEmailController> {
maxBodyHeight: maxBodyHeight,
openEmailAddressDetailAction: controller.openEmailAddressDialog,
onEmailActionClick: (presentationEmail, actionType) => controller.handleEmailAction(context, presentationEmail, actionType),
isInsideThreadDetailView: isInsideThreadDetailView,
emailLoaded: controller.currentEmailLoaded.value,
onMoreActionClick: (email, position) => _handleMoreEmailAction(
context: context,
presentationEmail: email,
position: position,
emailLoaded: controller.currentEmailLoaded.value,
),
onToggleThreadDetailCollapseExpand: onToggleThreadDetailCollapseExpand,
onTapAvatarActionClick: onToggleThreadDetailCollapseExpand,
)),
Obx(() => MailUnsubscribedBanner(
presentationEmail: controller.currentEmail,
@@ -349,6 +308,7 @@ class EmailView extends GetWidget<SingleEmailController> {
onTapShowAllAttachmentFile: () => controller.openAttachmentList(context, controller.attachments),
showDownloadAllAttachmentsButton: controller.downloadAllButtonIsEnabled(),
onTapDownloadAllButton: () => controller.handleDownloadAllAttachmentsAction(context, 'TwakeMail-${DateTime.now()}'),
singleEmailControllerTag: tag,
);
} else {
return const SizedBox.shrink();
@@ -408,7 +368,8 @@ class EmailView extends GetWidget<SingleEmailController> {
final allEmailContents = controller.emailContents.value ?? '';
if (PlatformInfo.isWeb) {
return Expanded(
return OptionalExpanded(
expandedEnabled: !isInsideThreadDetailView,
child: Padding(
padding: EmailViewStyles.emailContentPadding,
child: LayoutBuilder(builder: (context, constraints) {
@@ -416,8 +377,12 @@ class EmailView extends GetWidget<SingleEmailController> {
return Stack(
children: [
HtmlContentViewerOnWeb(
key: ValueKey(tag),
widthContent: constraints.maxWidth,
heightContent: constraints.maxHeight,
heightContent: min(
constraints.maxHeight,
EmailViewStyles.initialHtmlViewHeight,
),
contentHtml: allEmailContents,
mailtoDelegate: controller.openMailToLink,
direction: AppUtils.getCurrentDirection(context),
@@ -462,9 +427,8 @@ class EmailView extends GetWidget<SingleEmailController> {
useDefaultFont: true,
maxHtmlContentHeight: ConstantsUI.htmlContentMaxHeight,
onMailtoDelegateAction: controller.openMailToLink,
onScrollHorizontalEnd: controller.toggleScrollPhysicsPagerView,
onLoadWidthHtmlViewer: controller.emailSupervisorController.updateScrollPhysicPageView,
onHtmlContentClippedAction: controller.onHtmlContentClippedAction,
keepAlive: isInsideThreadDetailView,
);
}),
),
@@ -503,8 +467,7 @@ class EmailView extends GetWidget<SingleEmailController> {
contentPadding: 0,
useDefaultFont: true,
onMailtoDelegateAction: controller.openMailToLink,
onScrollHorizontalEnd: controller.toggleScrollPhysicsPagerView,
onLoadWidthHtmlViewer: controller.emailSupervisorController.updateScrollPhysicPageView,
keepAlive: isInsideThreadDetailView,
);
})
);
@@ -531,10 +494,17 @@ class EmailView extends GetWidget<SingleEmailController> {
return usernameEvent.isNotEmpty && titleEvent.isNotEmpty;
}
bool canDeletePermanently(PresentationEmail email) {
return email.mailboxContain?.isTrash
?? email.mailboxContain?.isSpam
?? false;
}
void _handleMoreEmailAction({
required BuildContext context,
required PresentationEmail presentationEmail,
RelativeRect? position
RelativeRect? position,
EmailLoaded? emailLoaded,
}) {
final mailboxContain = controller.getMailboxContain(presentationEmail);
final isChildOfTeamMailboxes = mailboxContain?.isChildOfTeamMailboxes ?? false;
@@ -548,6 +518,25 @@ class EmailView extends GetWidget<SingleEmailController> {
PlatformInfo.isWeb && PlatformInfo.isCanvasKit;
final emailActions = [
if (isInsideThreadDetailView) ...[
EmailActionType.forward,
if (presentationEmail.getCountMailAddressWithoutMe(controller.session?.username.value ?? '') > 1)
EmailActionType.replyAll,
if (EmailUtils.isReplyToListEnabled(emailLoaded?.emailCurrent?.listPost ?? ''))
EmailActionType.replyToList,
if (PlatformInfo.isWeb && PlatformInfo.isCanvasKit)
EmailActionType.printAll,
if (controller.responsiveUtils.isMobile(context))
EmailActionType.moveToMailbox,
if (!controller.responsiveUtils.isDesktop(context)) ...[
presentationEmail.hasStarred
? EmailActionType.unMarkAsStarred
: EmailActionType.markAsStarred,
canDeletePermanently(presentationEmail)
? EmailActionType.deletePermanently
: EmailActionType.moveToTrash,
],
],
EmailActionType.markAsUnread,
if (!isChildOfTeamMailboxes)
isSpam ? EmailActionType.unSpam : EmailActionType.moveToSpam,
@@ -1,6 +1,7 @@
import 'package:core/utils/app_logger.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/email_extension.dart';
import 'package:model/extensions/list_email_header_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/model/smime_signature_status.dart';
@@ -21,4 +22,25 @@ extension EmailExtension on Email {
return SMimeSignatureStatus.notSigned;
}
}
bool inSentMailbox(MailboxId sentMailboxId) {
return mailboxIds?[sentMailboxId] == true;
}
bool fromMe(String ownEmailAddress) {
return from?.any(
(emailAdress) => emailAdress.email == ownEmailAddress
) == true;
}
bool recipientsHasMe(String ownEmailAddress) {
final recipients = {
...to ?? {},
...cc ?? {},
...bcc ?? {},
};
return recipients.any(
(emailAdress) => emailAdress.email == ownEmailAddress
) == true;
}
}
@@ -17,7 +17,7 @@ class EmailViewAppBarWidgetStyles {
return null;
}
}
static const Color bottomBorderColor = AppColor.colorDividerHorizontal;
static const Color bottomBorderColor = AppColor.colorDividerEmailView;
static const Color backgroundColor = Colors.white;
static const Color iconColor = AppColor.steelGrayA540;
@@ -27,7 +27,7 @@ class EmailViewAppBarWidgetStyles {
} else if (responsiveUtils.isPortraitTablet(context) || responsiveUtils.isLandscapeTablet(context)) {
return const EdgeInsetsDirectional.only(top: 40, start: 16, end: 16, bottom: 4);
} else {
return const EdgeInsetsDirectional.only(top: 60, start: 16, end: 16, bottom: 4);
return const EdgeInsetsDirectional.only(start: 16, end: 16, bottom: 4);
}
}
static const EdgeInsetsGeometry padding = EdgeInsetsDirectional.symmetric(horizontal: 16);
@@ -7,6 +7,7 @@ class EmailViewStyles {
static const double mobileContentHorizontalMargin = 16;
static const double mobileContentVerticalMargin = 12;
static double pageViewIconSize = IconUtils.defaultIconSize;
static const double initialHtmlViewHeight = 200;
static const Color iconColor = AppColor.steelGrayA540;
@@ -21,6 +21,7 @@ class AttachmentItemWidget extends StatelessWidget {
final Attachment attachment;
final OnDownloadAttachmentFileAction? downloadAttachmentAction;
final OnViewAttachmentFileAction? viewAttachmentAction;
final String? singleEmailControllerTag;
final _imagePaths = Get.find<ImagePaths>();
final _responsiveUtils = Get.find<ResponsiveUtils>();
@@ -30,13 +31,14 @@ class AttachmentItemWidget extends StatelessWidget {
required this.attachment,
this.downloadAttachmentAction,
this.viewAttachmentAction,
this.singleEmailControllerTag,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Obx(
() {
final controller = Get.find<SingleEmailController>();
final controller = Get.find<SingleEmailController>(tag: singleEmailControllerTag);
final attachmentsViewState = controller.attachmentsViewState;
bool isLoading = false;
if (attachment.blobId != null) {
@@ -21,6 +21,7 @@ class AttachmentListBottomSheetBodyBuilder extends StatelessWidget {
final OnViewAttachmentFileAction? onViewAttachmentFileAction;
final OnCancelButtonAction? onCancelButtonAction;
final OnCloseButtonAction? onCloseButtonAction;
final String? singleEmailControllerTag;
const AttachmentListBottomSheetBodyBuilder({
super.key,
@@ -33,6 +34,7 @@ class AttachmentListBottomSheetBodyBuilder extends StatelessWidget {
this.onViewAttachmentFileAction,
this.onCancelButtonAction,
this.onCloseButtonAction,
this.singleEmailControllerTag,
});
@override
@@ -105,6 +107,7 @@ class AttachmentListBottomSheetBodyBuilder extends StatelessWidget {
attachment: attachments[index],
downloadAttachmentAction: onDownloadAttachmentFileAction,
viewAttachmentAction: onViewAttachmentFileAction,
singleEmailControllerTag: singleEmailControllerTag,
);
},
separatorBuilder: (context, index) {
@@ -15,6 +15,7 @@ class AttachmentListBottomSheetBuilder {
final List<Attachment> _attachments;
final ImagePaths _imagePaths;
final ScrollController _scrollController;
final String? _singleEmailControllerTag;
late double _statusBarHeight;
@@ -29,6 +30,7 @@ class AttachmentListBottomSheetBuilder {
this._attachments,
this._imagePaths,
this._scrollController,
this._singleEmailControllerTag,
) {
_statusBarHeight = Get.statusBarHeight / MediaQuery.of(_context).devicePixelRatio;
}
@@ -70,6 +72,7 @@ class AttachmentListBottomSheetBuilder {
onViewAttachmentFileAction: _onViewAttachmentFileAction,
onCancelButtonAction: _onCancelButtonAction,
onCloseButtonAction: _onCloseButtonAction,
singleEmailControllerTag: _singleEmailControllerTag,
),
);
}
@@ -22,6 +22,7 @@ class AttachmentListDialogBodyBuilder extends StatelessWidget {
final OnViewAttachmentFileAction? onViewAttachmentFileAction;
final OnCancelButtonAction? onCancelButtonAction;
final OnCloseButtonAction? onCloseButtonAction;
final String? singleEmailControllerTag;
const AttachmentListDialogBodyBuilder({
super.key,
@@ -35,7 +36,8 @@ class AttachmentListDialogBodyBuilder extends StatelessWidget {
this.onDownloadAttachmentFileAction,
this.onViewAttachmentFileAction,
this.onCancelButtonAction,
this.onCloseButtonAction
this.onCloseButtonAction,
this.singleEmailControllerTag,
});
@override
@@ -113,6 +115,7 @@ class AttachmentListDialogBodyBuilder extends StatelessWidget {
attachment: attachments[index],
downloadAttachmentAction: onDownloadAttachmentFileAction,
viewAttachmentAction: onViewAttachmentFileAction,
singleEmailControllerTag: singleEmailControllerTag,
);
},
separatorBuilder: (context, index) {
@@ -22,6 +22,7 @@ class AttachmentListDialogBuilder extends StatelessWidget {
final OnViewAttachmentFileAction? onViewAttachmentFileAction;
final OnCancelButtonAction? onCancelButtonAction;
final OnCloseButtonAction? onCloseButtonAction;
final String? singleEmailControllerTag;
const AttachmentListDialogBuilder({
Key? key,
@@ -37,6 +38,7 @@ class AttachmentListDialogBuilder extends StatelessWidget {
this.onViewAttachmentFileAction,
this.onCancelButtonAction,
this.onCloseButtonAction,
this.singleEmailControllerTag,
}) : super(key: key);
@override
@@ -61,6 +63,7 @@ class AttachmentListDialogBuilder extends StatelessWidget {
onViewAttachmentFileAction: onViewAttachmentFileAction,
onCancelButtonAction: onCancelButtonAction,
onCloseButtonAction: onCloseButtonAction,
singleEmailControllerTag: singleEmailControllerTag,
),
);
}
@@ -19,6 +19,7 @@ class AttachmentListItemWidget extends StatelessWidget {
final Attachment attachment;
final OnDownloadAttachmentFileAction? downloadAttachmentAction;
final OnViewAttachmentFileAction? viewAttachmentAction;
final String? singleEmailControllerTag;
final _imagePaths = Get.find<ImagePaths>();
@@ -27,13 +28,14 @@ class AttachmentListItemWidget extends StatelessWidget {
required this.attachment,
this.downloadAttachmentAction,
this.viewAttachmentAction,
this.singleEmailControllerTag,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Obx(
() {
final controller = Get.find<SingleEmailController>();
final controller = Get.find<SingleEmailController>(tag: singleEmailControllerTag);
final attachmentsViewState = controller.attachmentsViewState;
bool isLoading = false;
if (attachment.blobId != null) {
@@ -1,3 +1,4 @@
import 'dart:math';
import 'package:core/presentation/constants/constants_ui.dart';
import 'package:core/presentation/extensions/color_extension.dart';
@@ -9,6 +10,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/email/presentation/styles/email_view_styles.dart';
import 'package:tmail_ui_user/features/email/presentation/styles/event_description_detail_widget_styles.dart';
import 'package:tmail_ui_user/main/utils/app_utils.dart';
@@ -44,15 +46,17 @@ class EventBodyContentWidget extends StatelessWidget {
child: Stack(
children: [
if (PlatformInfo.isWeb)
Container(
constraints: const BoxConstraints(maxHeight: EventDescriptionDetailWidgetStyles.maxHeight),
Padding(
padding: const EdgeInsetsDirectional.only(end: EventDescriptionDetailWidgetStyles.webContentPadding),
child: LayoutBuilder(builder: (context, constraints) {
return Stack(
children: [
HtmlContentViewerOnWeb(
widthContent: constraints.maxWidth,
heightContent: constraints.maxHeight,
heightContent: min(
constraints.maxHeight,
EmailViewStyles.initialHtmlViewHeight,
),
contentHtml: content,
mailtoDelegate: onMailtoDelegateAction,
direction: AppUtils.getCurrentDirection(context),
@@ -14,6 +14,7 @@ class DraggableAttachmentItemWidget extends StatelessWidget{
final OnDragAttachmentEnd? onDragEnd;
final OnDownloadAttachmentFileAction? downloadAttachmentAction;
final OnViewAttachmentFileAction? viewAttachmentAction;
final String? singleEmailControllerTag;
const DraggableAttachmentItemWidget({
Key? key,
@@ -22,6 +23,7 @@ class DraggableAttachmentItemWidget extends StatelessWidget{
this.onDragEnd,
this.downloadAttachmentAction,
this.viewAttachmentAction,
this.singleEmailControllerTag,
}) : super(key: key);
@override
@@ -35,6 +37,7 @@ class DraggableAttachmentItemWidget extends StatelessWidget{
attachment: attachment,
downloadAttachmentAction: downloadAttachmentAction,
viewAttachmentAction: viewAttachmentAction,
singleEmailControllerTag: singleEmailControllerTag,
),
);
}
@@ -27,6 +27,7 @@ class EmailAttachmentsWidget extends StatelessWidget {
final OnTapActionCallback? onTapShowAllAttachmentFile;
final bool showDownloadAllAttachmentsButton;
final OnTapActionCallback? onTapDownloadAllButton;
final String? singleEmailControllerTag;
const EmailAttachmentsWidget({
super.key,
@@ -40,6 +41,7 @@ class EmailAttachmentsWidget extends StatelessWidget {
this.onTapShowAllAttachmentFile,
this.showDownloadAllAttachmentsButton = false,
this.onTapDownloadAllButton,
this.singleEmailControllerTag,
});
Widget _buildMoreAttachmentButton(
@@ -108,17 +110,19 @@ class EmailAttachmentsWidget extends StatelessWidget {
children: attachmentDisplayed.map((attachment) {
if (PlatformInfo.isWeb) {
return DraggableAttachmentItemWidget(
attachment: attachment,
onDragStarted: onDragStarted,
onDragEnd: onDragEnd,
downloadAttachmentAction: downloadAttachmentAction,
viewAttachmentAction: viewAttachmentAction,
attachment: attachment,
onDragStarted: onDragStarted,
onDragEnd: onDragEnd,
downloadAttachmentAction: downloadAttachmentAction,
viewAttachmentAction: viewAttachmentAction,
singleEmailControllerTag: singleEmailControllerTag,
);
} else {
return AttachmentItemWidget(
attachment: attachment,
downloadAttachmentAction: downloadAttachmentAction,
viewAttachmentAction: viewAttachmentAction,
attachment: attachment,
downloadAttachmentAction: downloadAttachmentAction,
viewAttachmentAction: viewAttachmentAction,
singleEmailControllerTag: singleEmailControllerTag,
);
}
}).toList(),
@@ -56,7 +56,7 @@ class _EmailReceiverWidgetState extends State<EmailReceiverWidget> {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
Flexible(
child: Container(
constraints: BoxConstraints(maxHeight: _maxHeight),
child: ListView(
@@ -149,7 +149,7 @@ class _EmailReceiverWidgetState extends State<EmailReceiverWidget> {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
@@ -14,12 +14,14 @@ class EmailSenderBuilder extends StatelessWidget {
final EmailAddress emailAddress;
final OnOpenEmailAddressDetailAction? openEmailAddressDetailAction;
final bool showSenderEmail;
const EmailSenderBuilder({
Key? key,
super.key,
required this.emailAddress,
this.openEmailAddressDetailAction,
}) : super(key: key);
this.showSenderEmail = true,
});
@override
Widget build(BuildContext context) {
@@ -27,7 +29,7 @@ class EmailSenderBuilder extends StatelessWidget {
scrollDirection: Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
if (emailAddress.displayName.isNotEmpty)
@@ -38,32 +40,30 @@ class EmailSenderBuilder extends StatelessWidget {
AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress);
},
borderRadius: 8,
padding: const EdgeInsets.all(3),
padding: EdgeInsets.zero,
customStyle: ThemeUtils.textStyleHeadingHeadingSmall(
color: Colors.black,
),
overflow: CommonTextStyle.defaultTextOverFlow,
softWrap: CommonTextStyle.defaultSoftWrap
),
Padding(
padding: const EdgeInsets.only(top: 2),
child: MaterialTextButton(
if (showSenderEmail)
MaterialTextButton(
label: '<${emailAddress.emailAddress}>',
onTap: () => openEmailAddressDetailAction?.call(context, emailAddress),
onLongPress: () {
AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress);
},
borderRadius: 8,
padding: const EdgeInsets.all(3),
padding: EdgeInsets.zero,
customStyle: ThemeUtils.textStyleBodyBody1(
color: AppColor.steelGray400,
),
overflow: CommonTextStyle.defaultTextOverFlow,
softWrap: CommonTextStyle.defaultSoftWrap
),
)
)
]
)
),
);
}
}
@@ -20,7 +20,9 @@ class EmailSubjectWidget extends StatelessWidget {
child: Text(
presentationEmail.getEmailTitle(),
maxLines: EmailSubjectStyles.maxLines,
style: ThemeUtils.textStyleHeadingH5(color: Colors.black)
style: ThemeUtils.textStyleHeadingH5(color: Colors.black).copyWith(
overflow: TextOverflow.ellipsis,
),
)
);
}
@@ -8,7 +8,7 @@ import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.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_back_button.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
@@ -19,7 +19,6 @@ typedef OnMoreActionClick = void Function(PresentationEmail, RelativeRect?);
class EmailViewAppBarWidget extends StatelessWidget {
final _imagePaths = Get.find<ImagePaths>();
final _responsiveUtils = Get.find<ResponsiveUtils>();
final _singleEmailController = Get.find<SingleEmailController>();
final PresentationEmail presentationEmail;
final List<Widget>? optionsWidget;
@@ -28,29 +27,45 @@ class EmailViewAppBarWidget extends StatelessWidget {
final VoidCallback onBackAction;
final OnEmailActionClick? onEmailActionClick;
final OnMoreActionClick? onMoreActionClick;
final bool supportBackAction;
final BoxDecoration? appBarDecoration;
final EmailLoaded? emailLoaded;
final bool isInsideThreadDetailView;
final double? height;
final EdgeInsetsGeometry? iconPadding;
final EdgeInsetsGeometry? iconMargin;
final EdgeInsetsGeometry? padding;
EmailViewAppBarWidget({
Key? key,
required this.presentationEmail,
required this.onBackAction,
required this.isSearchActivated,
required this.emailLoaded,
this.mailboxContain,
this.onEmailActionClick,
this.onMoreActionClick,
this.optionsWidget,
this.supportBackAction = true,
this.appBarDecoration,
this.isInsideThreadDetailView = false,
this.height,
this.iconPadding,
this.iconMargin,
this.padding,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final child = LayoutBuilder(builder: (context, constraints) {
return Container(
height: PlatformInfo.isIOS
height: height ?? (PlatformInfo.isIOS
? EmailViewAppBarWidgetStyles.heightIOS(context, _responsiveUtils)
: EmailViewAppBarWidgetStyles.height,
padding: PlatformInfo.isIOS
: EmailViewAppBarWidgetStyles.height),
padding: padding ?? (PlatformInfo.isIOS
? EmailViewAppBarWidgetStyles.paddingIOS(context, _responsiveUtils)
: EmailViewAppBarWidgetStyles.padding,
decoration: const BoxDecoration(
: EmailViewAppBarWidgetStyles.padding),
decoration: appBarDecoration ?? const BoxDecoration(
border: Border(
bottom: BorderSide(
color: EmailViewAppBarWidgetStyles.bottomBorderColor,
@@ -63,89 +78,30 @@ class EmailViewAppBarWidget extends StatelessWidget {
),
color: EmailViewAppBarWidgetStyles.backgroundColor,
),
child: Row(children: [
if (_supportDisplayMailboxNameTitle(context))
EmailViewBackButton(
imagePaths: _imagePaths,
onBackAction: onBackAction,
mailboxContain: mailboxContain,
isSearchActivated: isSearchActivated,
maxWidth: constraints.maxWidth,
child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [
if (_supportDisplayMailboxNameTitle(context) && supportBackAction)
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: EmailViewBackButton(
imagePaths: _imagePaths,
onBackAction: onBackAction,
mailboxContain: mailboxContain,
isSearchActivated: isSearchActivated,
maxWidth: constraints.maxWidth,
),
),
),
const Spacer(),
Row(
children: [
if (optionsWidget != null) ... optionsWidget!,
TMailButtonWidget.fromIcon(
icon: _imagePaths.icMoveEmail,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
tooltipMessage: AppLocalizations.of(context).move_message,
backgroundColor: Colors.transparent,
onTapActionCallback: () => onEmailActionClick?.call(presentationEmail, EmailActionType.moveToMailbox)
),
TMailButtonWidget.fromIcon(
icon: presentationEmail.hasStarred
? _imagePaths.icStar
: _imagePaths.icUnStar,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: presentationEmail.hasStarred
? null
: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: presentationEmail.hasStarred
? AppLocalizations.of(context).not_starred
: AppLocalizations.of(context).mark_as_starred,
onTapActionCallback: () => onEmailActionClick?.call(
presentationEmail,
presentationEmail.hasStarred ? EmailActionType.unMarkAsStarred : EmailActionType.markAsStarred
)
),
if (PlatformInfo.isWeb && PlatformInfo.isCanvasKit)
Obx(() => AbsorbPointer(
absorbing: _singleEmailController.currentEmailLoaded.value == null,
child: TMailButtonWidget.fromIcon(
icon: _imagePaths.icPrinter,
iconSize: EmailViewAppBarWidgetStyles.deleteButtonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).printAll,
onTapActionCallback: () => onEmailActionClick?.call(
presentationEmail,
EmailActionType.printAll,
),
),
)),
TMailButtonWidget.fromIcon(
icon: _imagePaths.icDeleteComposer,
iconSize: EmailViewAppBarWidgetStyles.deleteButtonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: canDeletePermanently
? AppLocalizations.of(context).delete_permanently
: AppLocalizations.of(context).move_to_trash,
onTapActionCallback: () {
if (canDeletePermanently) {
onEmailActionClick?.call(presentationEmail, EmailActionType.deletePermanently);
} else {
onEmailActionClick?.call(presentationEmail, EmailActionType.moveToTrash);
}
}
),
TMailButtonWidget.fromIcon(
icon: _imagePaths.icMoreVertical,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).more,
onTapActionCallback: _responsiveUtils.isScreenWithShortestSide(context)
? () => onMoreActionClick?.call(presentationEmail, null)
: null,
onTapActionAtPositionCallback: !_responsiveUtils.isScreenWithShortestSide(context)
? (position) => onMoreActionClick?.call(presentationEmail, position)
: null
),
]
if (optionsWidget != null) ... optionsWidget!,
..._buildActionButtons(
appLocalizations: AppLocalizations.of(context),
isScreenWithShortestSide: _responsiveUtils.isScreenWithShortestSide(context),
isResponsiveMobile: _responsiveUtils.isMobile(context),
isResponsiveDesktop: _responsiveUtils.isDesktop(context),
),
]
),
])
);
@@ -154,7 +110,7 @@ class EmailViewAppBarWidget extends StatelessWidget {
return PopScope(
canPop: false,
onPopInvokedWithResult: (_, __) {
if (!PlatformInfo.isAndroid) return;
if (PlatformInfo.isAndroid) return;
onBackAction();
},
child: child,
@@ -178,4 +134,137 @@ class EmailViewAppBarWidget extends StatelessWidget {
bool get canDeletePermanently {
return mailboxContain?.isTrash == true || mailboxContain?.isSpam == true;
}
Widget _getReplyButton(AppLocalizations appLocalizations) => TMailButtonWidget.fromIcon(
icon: _imagePaths.icReply,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
tooltipMessage: appLocalizations.reply,
backgroundColor: Colors.transparent,
onTapActionCallback: () => onEmailActionClick?.call(
presentationEmail,
EmailActionType.reply,
),
padding: iconPadding,
margin: iconMargin,
);
Widget _getPrintButton(AppLocalizations appLocalizations) => AbsorbPointer(
absorbing: emailLoaded == null,
child: TMailButtonWidget.fromIcon(
icon: _imagePaths.icPrinter,
iconSize: EmailViewAppBarWidgetStyles.deleteButtonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: appLocalizations.printAll,
onTapActionCallback: () => onEmailActionClick?.call(
presentationEmail,
EmailActionType.printAll,
),
padding: iconPadding,
margin: iconMargin,
),
);
Widget _getMoveEmailButton(AppLocalizations appLocalizations) => TMailButtonWidget.fromIcon(
icon: _imagePaths.icMoveEmail,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
tooltipMessage: appLocalizations.move_message,
backgroundColor: Colors.transparent,
onTapActionCallback: () => onEmailActionClick?.call(
presentationEmail,
EmailActionType.moveToMailbox,
),
padding: iconPadding,
margin: iconMargin,
);
Widget _getMarkStarButton(AppLocalizations applocalizations) => TMailButtonWidget.fromIcon(
icon: presentationEmail.hasStarred
? _imagePaths.icStar
: _imagePaths.icUnStar,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: presentationEmail.hasStarred
? null
: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: presentationEmail.hasStarred
? applocalizations.not_starred
: applocalizations.mark_as_starred,
onTapActionCallback: () => onEmailActionClick?.call(
presentationEmail,
presentationEmail.hasStarred
? EmailActionType.unMarkAsStarred
: EmailActionType.markAsStarred
),
padding: iconPadding,
margin: iconMargin,
);
Widget _getDeleteButton(AppLocalizations applocalizations) => TMailButtonWidget.fromIcon(
icon: _imagePaths.icDeleteComposer,
iconSize: EmailViewAppBarWidgetStyles.deleteButtonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: canDeletePermanently
? applocalizations.delete_permanently
: applocalizations.move_to_trash,
onTapActionCallback: () {
if (canDeletePermanently) {
onEmailActionClick?.call(presentationEmail, EmailActionType.deletePermanently);
} else {
onEmailActionClick?.call(presentationEmail, EmailActionType.moveToTrash);
}
},
padding: iconPadding,
margin: iconMargin,
);
Widget _getMoreButton(
AppLocalizations applocalizations,
bool isScreenWithShortestSide,
) => TMailButtonWidget.fromIcon(
icon: _imagePaths.icMoreVertical,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: applocalizations.more,
onTapActionCallback: isScreenWithShortestSide
? () => onMoreActionClick?.call(presentationEmail, null)
: null,
onTapActionAtPositionCallback: !isScreenWithShortestSide
? (position) => onMoreActionClick?.call(presentationEmail, position)
: null,
padding: iconPadding,
margin: iconMargin,
);
List<Widget> _buildActionButtons({
required AppLocalizations appLocalizations,
required bool isScreenWithShortestSide,
required bool isResponsiveMobile,
required bool isResponsiveDesktop,
}) {
if (!isInsideThreadDetailView) {
return [
_getPrintButton(appLocalizations),
_getMoveEmailButton(appLocalizations),
_getMarkStarButton(appLocalizations),
_getDeleteButton(appLocalizations),
_getMoreButton(appLocalizations, isScreenWithShortestSide),
];
}
return [
_getReplyButton(appLocalizations),
if (!isResponsiveMobile)
_getMoveEmailButton(appLocalizations),
if (isResponsiveDesktop) ...[
_getMarkStarButton(appLocalizations),
_getDeleteButton(appLocalizations),
],
_getMoreButton(appLocalizations, isScreenWithShortestSide),
];
}
}
@@ -21,6 +21,8 @@ class EmailViewBottomBarWidget extends StatelessWidget {
final EmailLoaded emailLoaded;
final String userName;
final OnEmailActionCallback emailActionCallback;
final BoxDecoration? bottomBarDecoration;
final EdgeInsetsGeometry? padding;
const EmailViewBottomBarWidget({
Key? key,
@@ -30,6 +32,8 @@ class EmailViewBottomBarWidget extends StatelessWidget {
required this.emailLoaded,
required this.userName,
required this.emailActionCallback,
this.bottomBarDecoration,
this.padding,
}) : super(key: key);
@override
@@ -40,7 +44,7 @@ class EmailViewBottomBarWidget extends StatelessWidget {
);
return Container(
decoration: const BoxDecoration(
decoration: bottomBarDecoration ?? const BoxDecoration(
border: Border(
top: BorderSide(
color: EmailViewBottomBarWidgetStyles.topBorderColor,
@@ -53,7 +57,7 @@ class EmailViewBottomBarWidget extends StatelessWidget {
),
color: EmailViewBottomBarWidgetStyles.backgroundColor
),
padding: EmailViewBottomBarWidgetStyles.padding,
padding: padding ?? EmailViewBottomBarWidgetStyles.padding,
child: IntrinsicHeight(
child: Row(
children: [
@@ -1,14 +1,17 @@
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:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.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:tmail_ui_user/features/base/widget/email_avatar_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.dart';
import 'package:tmail_ui_user/features/email/presentation/model/smime_signature_status.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_receiver_widget.dart';
@@ -27,6 +30,12 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
final OnEmailActionClick? onEmailActionClick;
final double? maxBodyHeight;
final SMimeSignatureStatus? sMimeStatus;
final bool isInsideThreadDetailView;
final EmailLoaded? emailLoaded;
final OnMoreActionClick? onMoreActionClick;
final bool showRecipients;
final VoidCallback? onToggleThreadDetailCollapseExpand;
final OnTapAvatarActionClick? onTapAvatarActionClick;
const InformationSenderAndReceiverBuilder({
Key? key,
@@ -38,66 +47,110 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
this.maxBodyHeight,
this.openEmailAddressDetailAction,
this.onEmailActionClick,
this.isInsideThreadDetailView = false,
this.emailLoaded,
this.onMoreActionClick,
this.showRecipients = true,
this.onToggleThreadDetailCollapseExpand,
this.onTapAvatarActionClick,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsetsDirectional.only(start: 16, end: 16, top: 16),
padding: const EdgeInsetsDirectional.symmetric(horizontal: 16),
child: Row(
crossAxisAlignment: emailSelected.countRecipients > 0
crossAxisAlignment: emailSelected.countRecipients > 0 && (showRecipients || responsiveUtils.isMobile(context))
? CrossAxisAlignment.start
: CrossAxisAlignment.center,
children: [
EmailAvatarBuilder(emailSelected: emailSelected),
EmailAvatarBuilder(
emailSelected: emailSelected,
onTapAvatarActionClick: onTapAvatarActionClick,
),
const SizedBox(width: 16),
Expanded(child: LayoutBuilder(builder: (context, constraints) {
return Transform(
transform: Matrix4.translationValues(0.0, -5.0, 0.0),
return InkWell(
onTap: onToggleThreadDetailCollapseExpand,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (emailSelected.from?.isNotEmpty == true)
Row(children: [
Expanded(child: Row(
children: [
Flexible(child: Transform(
transform: Matrix4.translationValues(-5.0, 0.0, 0.0),
child: EmailSenderBuilder(
emailAddress: emailSelected.from!.first,
openEmailAddressDetailAction: openEmailAddressDetailAction,
)
)),
if (sMimeStatus != null && sMimeStatus != SMimeSignatureStatus.notSigned)
Tooltip(
key: const Key('smime_signature_status_icon'),
message: sMimeStatus!.getTooltipMessage(context),
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: SvgPicture.asset(
sMimeStatus!.getIcon(imagePaths),
fit: BoxFit.fill,
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Row(
children: [
if (emailSelected.from?.isNotEmpty == true)
Flexible(
child: EmailSenderBuilder(
emailAddress: emailSelected.from!.first,
openEmailAddressDetailAction: openEmailAddressDetailAction,
),
),
),
if (!emailSelected.isSubscribed && emailUnsubscribe != null && !responsiveUtils.isPortraitMobile(context))
TMailButtonWidget.fromText(
text: AppLocalizations.of(context).unsubscribe,
textStyle: ThemeUtils.defaultTextStyleInterFont.copyWith(
fontWeight: FontWeight.normal,
fontSize: 14,
color: AppColor.colorTextBody,
decoration: TextDecoration.underline,
if (sMimeStatus != null && sMimeStatus != SMimeSignatureStatus.notSigned)
Tooltip(
key: const Key('smime_signature_status_icon'),
message: sMimeStatus!.getTooltipMessage(context),
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: SvgPicture.asset(
sMimeStatus!.getIcon(imagePaths),
fit: BoxFit.fill,
),
),
),
padding: const EdgeInsetsDirectional.symmetric(vertical: 5, horizontal: 8),
backgroundColor: Colors.transparent,
onTapActionCallback: () => onEmailActionClick?.call(emailSelected, EmailActionType.unsubscribe),
),
]
)),
ReceivedTimeBuilder(emailSelected: emailSelected),
]),
if (emailSelected.countRecipients > 0)
if (!emailSelected.isSubscribed && emailUnsubscribe != null && !responsiveUtils.isPortraitMobile(context))
TMailButtonWidget.fromText(
text: AppLocalizations.of(context).unsubscribe,
textStyle: ThemeUtils.defaultTextStyleInterFont.copyWith(
fontWeight: FontWeight.normal,
fontSize: 14,
color: AppColor.colorTextBody,
decoration: TextDecoration.underline,
),
padding: const EdgeInsetsDirectional.symmetric(vertical: 5, horizontal: 8),
backgroundColor: Colors.transparent,
onTapActionCallback: () => onEmailActionClick?.call(emailSelected, EmailActionType.unsubscribe),
),
if (isInsideThreadDetailView && !responsiveUtils.isMobile(context))
ReceivedTimeBuilder(
emailSelected: emailSelected,
padding: const EdgeInsetsDirectional.only(start: 16, top: 2),
),
],
),
),
if (!isInsideThreadDetailView && !responsiveUtils.isMobile(context))
ReceivedTimeBuilder(
emailSelected: emailSelected,
padding: const EdgeInsetsDirectional.only(start: 16, top: 2),
),
if (isInsideThreadDetailView)
EmailViewAppBarWidget(
key: const Key('email_view_app_bar_widget'),
presentationEmail: emailSelected,
isSearchActivated: false,
onBackAction: () {},
onEmailActionClick: onEmailActionClick,
onMoreActionClick: onMoreActionClick,
supportBackAction: false,
appBarDecoration: const BoxDecoration(),
emailLoaded: emailLoaded,
isInsideThreadDetailView: isInsideThreadDetailView,
height: IconUtils.defaultIconSize,
iconPadding: EdgeInsets.zero,
iconMargin: const EdgeInsetsDirectional.only(start: 16),
padding: const EdgeInsets.symmetric(horizontal: 8),
),
],
),
if (responsiveUtils.isMobile(context))
ReceivedTimeBuilder(
emailSelected: emailSelected,
padding: const EdgeInsetsDirectional.only(top: 5),
),
if (emailSelected.countRecipients > 0 && showRecipients)
EmailReceiverWidget(
emailSelected: emailSelected,
maxWidth: constraints.maxWidth,
@@ -10,16 +10,18 @@ import 'package:model/extensions/presentation_email_extension.dart';
class ReceivedTimeBuilder extends StatelessWidget {
final PresentationEmail emailSelected;
final EdgeInsetsGeometry? padding;
const ReceivedTimeBuilder({
Key? key,
required this.emailSelected,
this.padding,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsetsDirectional.only(start: 16),
padding: padding ?? const EdgeInsetsDirectional.only(start: 16),
child: Text(
emailSelected.getReceivedAt(
Localizations.localeOf(context).toLanguageTag(),