TF-1809: Handle sync data from cache and network when get email contents

(cherry picked from commit 896059cd5b29c62c2cf34621fe35d7b23a54acf4)
This commit is contained in:
HuyNguyen
2023-05-18 11:56:24 +07:00
committed by Dat Vu
parent b17e4e4728
commit 48f5a8b51f
19 changed files with 205 additions and 135 deletions
@@ -124,7 +124,7 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource {
final fileSaved = await _fileUtils.saveToFile(
nameFile: detailedEmail.emailId.asString,
content: detailedEmail.htmlEmailContent ?? '',
folderPath: detailedEmail.folderPath
folderPath: detailedEmail.newEmailFolderPath
);
log('EmailHiveCacheDataSourceImpl::storeDetailedEmailToCache():fileSavedPath: ${fileSaved.path}');
final detailedEmailSaved = detailedEmail.fromEmailContentPath(fileSaved.path);
@@ -163,7 +163,7 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource {
final task = HiveTask(
runnable: () async {
final detailedEmailExisted = await _openedEmailCacheManager.isOpenedDetailEmailCached(accountId, session.username, detailedEmail);
final detailedEmailExisted = await _openedEmailCacheManager.isOpenedDetailEmailCached(accountId, session.username, detailedEmail.emailId);
log('EmailHiveCacheDataSourceImpl::storeOpenedEmail():detailedEmailExisted: $detailedEmailExisted');
if (detailedEmailExisted) {
@@ -173,7 +173,7 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource {
final fileSaved = await _fileUtils.saveToFile(
nameFile: detailedEmail.emailId.asString,
content: detailedEmail.htmlEmailContent ?? '',
folderPath: detailedEmail.folderPath
folderPath: detailedEmail.openedEmailFolderPath
);
log('EmailHiveCacheDataSourceImpl::storeOpenedEmail():fileSavedPath: ${fileSaved.path}');
@@ -196,7 +196,7 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource {
final emailContent = await _fileUtils.getContentFromFile(
nameFile: emailId.asString,
folderPath: CachingConstants.emailContentFolderName
folderPath: CachingConstants.openedEmailContentFolderNamee
);
return detailedEmailHiveCache?.toDetailedEmailWithContent(emailContent);
@@ -205,7 +205,7 @@ class EmailRepositoryImpl extends EmailRepository {
}
@override
Future<DetailedEmail> getOpenedEmail(Session session, AccountId accountId, EmailId emailId) {
throw UnimplementedError();
Future<DetailedEmail?> getOpenedEmail(Session session, AccountId accountId, EmailId emailId) {
return emailDataSource[DataSourceType.hiveCache]!.getOpenedEmail(session, accountId, emailId);
}
}
@@ -8,13 +8,13 @@ import 'package:tmail_ui_user/features/offline_mode/model/attachment_hive_cache.
extension AttachmentExtension on AttachmentHiveCache {
Attachment toAttachment() {
return Attachment(
partId: PartId(partId!),
blobId: Id(blobId!),
size: UnsignedInt(size!),
name: name,
type: MediaType.parse(type!),
cid: cid,
disposition: disposition?.toContentDisposition(),
partId: partId != null ? PartId(partId!) : null,
blobId: blobId != null ? Id(blobId!) : null,
size: size != null ? UnsignedInt(size!) : null,
name: name,
type: type != null ? MediaType.parse(type!) : null,
cid: cid,
disposition: disposition?.toContentDisposition(),
);
}
}
@@ -17,7 +17,9 @@ extension DetailedEmailExtension on DetailedEmail {
);
}
String get folderPath => CachingConstants.emailContentFolderName;
String get newEmailFolderPath => CachingConstants.newEmailContentFolderName;
String get openedEmailFolderPath => CachingConstants.openedEmailContentFolderNamee;
DetailedEmail fromEmailContentPath(String path) {
return DetailedEmail(
@@ -96,5 +96,5 @@ abstract class EmailRepository {
Future<void> storeOpenedEmail(Session session, AccountId accountId, DetailedEmail detailedEmail);
Future<DetailedEmail> getOpenedEmail(Session session, AccountId accountId, EmailId emailId);
Future<DetailedEmail?> getOpenedEmail(Session session, AccountId accountId, EmailId emailId);
}
@@ -26,6 +26,22 @@ class GetEmailContentSuccess extends UIState {
];
}
class GetEmailContentFromCacheSuccess extends UIState {
final String emailContentString;
final List<Attachment> attachments;
GetEmailContentFromCacheSuccess(
this.emailContentString,
this.attachments,
);
@override
List<Object?> get props => [
emailContentString,
attachments,
];
}
class GetEmailContentFailure extends FeatureFailure {
final dynamic exception;
@@ -1,22 +0,0 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart';
class GetOpenedEmailLoading extends UIState {}
class GetOpenedEmailSuccess extends UIState {
final DetailedEmail detailedEmail;
GetOpenedEmailSuccess(this.detailedEmail);
@override
List<Object?> get props => [detailedEmail];
}
class GetOpenedEmailFailure extends FeatureFailure {
GetOpenedEmailFailure(dynamic exception) : super(exception: exception);
@override
List<Object?> get props => [exception];
}
@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/utils/app_logger.dart';
@@ -28,37 +30,85 @@ class GetEmailContentInteractor {
) async* {
try {
yield Right<Failure, Success>(GetEmailContentLoading());
final email = await emailRepository.getEmailContent(session, accountId, emailId);
if (email.emailContentList.isNotEmpty) {
final newEmailContents = await emailRepository.transformEmailContent(
email.emailContentList,
email.allAttachments.listAttachmentsDisplayedInContent,
baseDownloadUrl,
accountId,
draftsEmail: draftsEmail
);
final newEmailContentsDisplayed = BuildUtils.isWeb && !composeEmail
? await emailRepository.addTooltipWhenHoverOnLink(newEmailContents)
: newEmailContents;
yield Right<Failure, Success>(GetEmailContentSuccess(
newEmailContents,
newEmailContentsDisplayed,
email.allAttachments,
email
));
} else if (email.allAttachments.isNotEmpty) {
yield Right<Failure, Success>(GetEmailContentSuccess([], [], email.allAttachments, email));
} else if (email.headers?.isNotEmpty == true) {
yield Right<Failure, Success>(GetEmailContentSuccess([], [], [], email));
if (!BuildUtils.isWeb) {
yield* _tryToGetOpenedEmailCache(session, accountId, emailId, baseDownloadUrl, composeEmail: composeEmail, draftsEmail: draftsEmail);
} else {
yield Left(GetEmailContentFailure(null));
yield* _getContentEmailFromServer(session, accountId, emailId, baseDownloadUrl, composeEmail: composeEmail, draftsEmail: draftsEmail);
}
} catch (e) {
log('GetEmailContentInteractor::execute(): exception = $e');
yield Left(GetEmailContentFailure(e));
}
}
Stream<Either<Failure, Success>> _getContentEmailFromServer(
Session session,
AccountId accountId,
EmailId emailId,
String? baseDownloadUrl,
{
bool composeEmail = false,
bool draftsEmail = false
}
) async* {
final email = await emailRepository.getEmailContent(session, accountId, emailId);
if (email.emailContentList.isNotEmpty) {
final newEmailContents = await emailRepository.transformEmailContent(
email.emailContentList,
email.allAttachments.listAttachmentsDisplayedInContent,
baseDownloadUrl,
accountId,
draftsEmail: draftsEmail
);
final newEmailContentsDisplayed = BuildUtils.isWeb && !composeEmail
? await emailRepository.addTooltipWhenHoverOnLink(newEmailContents)
: newEmailContents;
yield Right<Failure, Success>(GetEmailContentSuccess(
newEmailContents,
newEmailContentsDisplayed,
email.allAttachments,
email
));
} else if (email.allAttachments.isNotEmpty) {
yield Right<Failure, Success>(GetEmailContentSuccess([], [], email.allAttachments, email));
} else if (email.headers?.isNotEmpty == true) {
yield Right<Failure, Success>(GetEmailContentSuccess([], [], [], email));
} else {
yield Left(GetEmailContentFailure(null));
}
}
Stream<Either<Failure, Success>> _tryToGetOpenedEmailCache(
Session session,
AccountId accountId,
EmailId emailId,
String? baseDownloadUrl,
{
bool composeEmail = false,
bool draftsEmail = false
}
) async* {
log('GetEmailContentInteractor::_getOpenedEmailCache():');
try {
final detailedEmail = await emailRepository.getOpenedEmail(session, accountId, emailId);
if (detailedEmail != null) {
yield Right<Failure, Success>(GetEmailContentFromCacheSuccess(
detailedEmail.htmlEmailContent ?? "",
detailedEmail.attachments ?? [],
));
} else {
yield* _getContentEmailFromServer(session, accountId, emailId, baseDownloadUrl, composeEmail: composeEmail, draftsEmail: draftsEmail);
}
} catch (e) {
if (e is PathNotFoundException) {
yield* _getContentEmailFromServer(session, accountId, emailId, baseDownloadUrl, composeEmail: composeEmail, draftsEmail: draftsEmail);
} else {
yield Left<Failure, Success>(GetEmailContentFailure(e));
}
}
}
}
@@ -1,25 +0,0 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:dartz/dartz.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart';
import 'package:tmail_ui_user/features/email/domain/state/get_opened_email_from_cache_state.dart';
class GetOpenedEmailFromCacheInteractor {
final EmailRepository _emailRepository;
GetOpenedEmailFromCacheInteractor(this._emailRepository);
Stream<Either<Failure, Success>> execute(Session session, AccountId accountId, EmailId emailId) async* {
try {
yield Right<Failure, Success>(GetOpenedEmailLoading());
final detailedEmail = await _emailRepository.getOpenedEmail(session, accountId, emailId);
yield Right<Failure, Success>(GetOpenedEmailSuccess(detailedEmail));
} catch (e) {
yield Left<Failure, Success>(GetOpenedEmailFailure(e));
}
}
}
@@ -100,11 +100,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
final emailAddressExpandMode = ExpandMode.COLLAPSE.obs;
final attachmentsExpandMode = ExpandMode.COLLAPSE.obs;
final emailContents = <EmailContent>[].obs;
final emailContents = RxnString();
final attachments = <Attachment>[].obs;
EmailId? _currentEmailId;
Identity? _identitySelected;
List<EmailContent>? initialEmailContents;
String? initialEmailContents;
final ScrollController scrollControllerAttachment = ScrollController();
@@ -150,6 +150,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
super.handleSuccessViewState(success);
if (success is GetEmailContentSuccess) {
_getEmailContentSuccess(success);
} else if (success is GetEmailContentFromCacheSuccess) {
_getEmailContentOffLineSuccess(success);
} else if (success is MarkAsEmailReadSuccess) {
_markAsEmailReadSuccess(success);
} else if (success is ExportAttachmentSuccess) {
@@ -356,6 +358,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
}
void _getEmailContentOffLineSuccess(GetEmailContentFromCacheSuccess success) {
emailContents.value = success.emailContentString;
attachments.value = success.attachments;
initialEmailContents = success.emailContentString;
}
void _getEmailContentSuccess(GetEmailContentSuccess success) {
if(emailSupervisorController.presentationEmailsLoaded.length > ThreadConstants.defaultLimit.value.toInt()) {
emailSupervisorController.popFirstEmailQueue();
@@ -370,8 +378,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
));
if (success.emailCurrent?.id == currentEmail?.id) {
emailContents.value = success.emailContentsDisplayed;
initialEmailContents = success.emailContents;
emailContents.value = success.emailContentsDisplayed.asHtmlString;
initialEmailContents = success.emailContents.asHtmlString;
attachments.value = success.attachments;
if (!BuildUtils.isWeb) {
@@ -379,7 +387,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
emailId: currentEmail!.id!,
attachments: attachments,
headers: currentEmail?.emailHeader,
htmlEmailContent: emailContents.asHtmlString
htmlEmailContent: emailContents.value
);
_storeOpenedEmailToCache(
@@ -414,8 +422,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
void _resetToOriginalValue() {
attachmentsExpandMode.value = ExpandMode.COLLAPSE;
emailAddressExpandMode.value = ExpandMode.COLLAPSE;
emailContents.clear();
initialEmailContents?.clear();
emailContents.value = null;
initialEmailContents = null;
attachments.clear();
}
@@ -1133,7 +1141,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
presentationEmail: mailboxDashBoardController.selectedEmail.value!,
emailContents: initialEmailContents,
attachments: emailActionType == EmailActionType.forward ? attachments : null,
mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role);
mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role
);
mailboxDashBoardController.goToComposer(arguments);
}
@@ -18,7 +18,6 @@ import 'package:model/email/attachment.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/list_attachment_extension.dart';
import 'package:model/extensions/list_email_content_extension.dart';
import 'package:model/extensions/presentation_email_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/base/widget/custom_scroll_behavior.dart';
@@ -441,20 +440,20 @@ class EmailView extends GetWidget<SingleEmailController> {
return const SizedBox.shrink();
}
return Obx(() {
if (controller.emailContents.isNotEmpty) {
final allEmailContents = controller.emailContents.asHtmlString;
if (controller.emailContents.value != null) {
final allEmailContents = controller.emailContents.value;
if (BuildUtils.isWeb) {
return HtmlContentViewerOnWeb(
widthContent: constraints.maxWidth,
heightContent: responsiveUtils.getSizeScreenHeight(context),
contentHtml: allEmailContents,
contentHtml: allEmailContents ?? "",
controller: HtmlViewerControllerForWeb(),
mailtoDelegate: (uri) => controller.openMailToLink(uri));
} else {
return HtmlContentViewer(
heightContent: responsiveUtils.getSizeScreenHeight(context),
contentHtml: allEmailContents,
contentHtml: allEmailContents ?? "",
mailtoDelegate: (uri) async => controller.openMailToLink(uri),
onScrollHorizontalEnd: controller.toggleScrollPhysicsPagerView,
onWebViewLoaded: (isScrollPageViewActivated) {
@@ -7,7 +7,7 @@ import 'package:tmail_ui_user/main/routes/router_arguments.dart';
class ComposerArguments extends RouterArguments {
final EmailActionType emailActionType;
final PresentationEmail? presentationEmail;
final List<EmailContent>? emailContents;
final String? emailContents;
final List<SharedMediaFile>? listSharedMediaFile;
final EmailAddress? emailAddress;
final List<Attachment>? attachments;