TF-2901 Implement caching composer on log out

This commit is contained in:
DatDang
2024-06-18 15:24:02 +07:00
committed by Dat H. Pham
parent c4bffd6626
commit b2c92388f2
18 changed files with 507 additions and 185 deletions
@@ -1,10 +1,31 @@
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/user_name.dart';
import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/data/model/composer_cache.dart';
abstract class SessionStorageComposerDatasource {
void saveComposerCacheOnWeb(Email email);
Future<void> saveComposerCacheOnWeb(
Email email,
{
required AccountId accountId,
required UserName userName,
required ScreenDisplayMode displayMode,
Identity? identity,
bool? readReceipentEnabled,
}
);
ComposerCache getComposerCacheOnWeb();
Future<ComposerCache> getComposerCacheOnWeb(
AccountId accountId,
UserName userName);
void removeComposerCacheOnWeb();
Future<void> removeComposerCacheOnWeb();
Future<String> restoreEmailInlineImages(
String htmlContent,
TransformConfiguration transformConfiguration,
Map<String, String> mapUrlDownloadCID);
}
@@ -1,47 +1,94 @@
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:core/core.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/user_name.dart';
import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/data/model/composer_cache.dart';
import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource/session_storage_composer_datasource.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/data/model/composer_cache.dart';
import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart';
import 'package:universal_html/html.dart' as html;
class SessionStorageComposerDatasourceImpl
extends SessionStorageComposerDatasource {
SessionStorageComposerDatasourceImpl(this._htmlTransform, this._exceptionThrower);
final HtmlTransform _htmlTransform;
final ExceptionThrower _exceptionThrower;
@override
ComposerCache getComposerCacheOnWeb() {
try {
final result = html.window.sessionStorage.entries.firstWhereOrNull((e) => e.key == EmailActionType.reopenComposerBrowser.name);
Future<ComposerCache> getComposerCacheOnWeb(
AccountId accountId,
UserName userName
) async {
return Future.sync(() async {
final keyWithIdentity = TupleKey(
EmailActionType.reopenComposerBrowser.name,
accountId.asString,
userName.value).toString();
final result = html.window.sessionStorage.entries.firstWhereOrNull(
(entry) => entry.key == keyWithIdentity);
if (result != null) {
final emailCache = ComposerCache.fromJson(jsonDecode(result.value));
return emailCache;
return ComposerCache.fromJson(jsonDecode(result.value));
} else {
throw NotFoundInWebSessionException();
}
} catch (e) {
throw NotFoundInWebSessionException(errorMessage: e.toString());
}
}).catchError(_exceptionThrower.throwException);
}
@override
void removeComposerCacheOnWeb() {
try {
html.window.sessionStorage.removeWhere((key, value) => key == EmailActionType.reopenComposerBrowser.name);
} catch (e) {
throw NotFoundInWebSessionException(errorMessage: e.toString());
}
Future<void> removeComposerCacheOnWeb() async {
return Future.sync(() {
html.window.sessionStorage.removeWhere(
(key, value) => key.startsWith(EmailActionType.reopenComposerBrowser.name));
}).catchError(_exceptionThrower.throwException);
}
@override
void saveComposerCacheOnWeb(Email email) {
try {
Future<void> saveComposerCacheOnWeb(
Email email,
{
required AccountId accountId,
required UserName userName,
required ScreenDisplayMode displayMode,
Identity? identity,
bool? readReceipentEnabled
}
) async {
return Future.sync(() {
final composerCacheKey = TupleKey(
EmailActionType.reopenComposerBrowser.name,
accountId.asString,
userName.value).toString();
Map<String, String> entries = {
EmailActionType.reopenComposerBrowser.name: email.asString()
composerCacheKey: jsonEncode(
ComposerCache(
displayMode: displayMode,
email: email,
identity: identity,
readReceipentEnabled: readReceipentEnabled,
).toJson()
)
};
html.window.sessionStorage.addAll(entries);
} catch (e) {
throw SaveToWebSessionFailException(errorMessage: e.toString());
}
}).catchError(_exceptionThrower.throwException);
}
@override
Future<String> restoreEmailInlineImages(
String htmlContent,
TransformConfiguration transformConfiguration,
Map<String, String> mapUrlDownloadCID) {
return Future.sync(() async {
return await _htmlTransform.transformToHtml(
htmlContent: htmlContent,
transformConfiguration: transformConfiguration,
mapCidImageDownloadUrl: mapUrlDownloadCID);
}).catchError(_exceptionThrower.throwException);
}
}
@@ -1,81 +1,44 @@
import 'package:equatable/equatable.dart';
import 'package:jmap_dart_client/http/converter/email/email_body_value_converter.dart';
import 'package:jmap_dart_client/http/converter/email/email_mailbox_ids_converter.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_body_value.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/email/email_content.dart';
import 'package:model/extensions/media_type_nullable_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart';
class ComposerCache with EquatableMixin {
final EmailId? id;
final Map<MailboxId, bool>? mailboxIds;
final String? subject;
final Set<EmailAddress>? from;
final Set<EmailAddress>? to;
final Set<EmailAddress>? cc;
final Set<EmailAddress>? bcc;
final Set<EmailAddress>? replyTo;
final Set<EmailBodyPart>? textBody;
final Set<EmailBodyPart>? htmlBody;
final Map<PartId, EmailBodyValue>? bodyValues;
final Email? email;
final Identity? identity;
final bool? readReceipentEnabled;
final ScreenDisplayMode displayMode;
ComposerCache({
this.id,
this.mailboxIds,
this.subject,
this.from,
this.to,
this.cc,
this.bcc,
this.replyTo,
this.textBody,
this.htmlBody,
this.bodyValues,
required this.displayMode,
this.email,
this.identity,
this.readReceipentEnabled,
});
factory ComposerCache.fromJson(Map<String, dynamic> json) {
return ComposerCache(
id: json['id'] != null ? EmailId(Id(json['id'])) : null,
mailboxIds: (json['mailboxIds'] as Map<String, dynamic>?)?.map((key, value) => EmailMailboxIdsConverter().parseEntry(key, value)),
subject: json['subject'] as String?,
from: (json['from'] as List<dynamic>?)?.map((json) => EmailAddress.fromJson(json)).toSet(),
to: (json['to'] as List<dynamic>?)?.map((json) => EmailAddress.fromJson(json)).toSet(),
cc: (json['cc'] as List<dynamic>?)?.map((json) => EmailAddress.fromJson(json)).toSet(),
bcc: (json['bcc'] as List<dynamic>?)?.map((json) => EmailAddress.fromJson(json)).toSet(),
bodyValues: (json['bodyValues'] as Map<String, dynamic>?)?.map((key, value) => EmailBodyValueConverter().parseEntry(key, value)),
replyTo: (json['replyTo'] as List<dynamic>?)?.map((json) => EmailAddress.fromJson(json)).toSet(),
textBody: (json['textBody'] as List<dynamic>?)?.map((json) => EmailBodyPart.fromJson(json)).toSet(),
htmlBody: (json['htmlBody'] as List<dynamic>?)?.map((json) => EmailBodyPart.fromJson(json)).toSet(),
);
}
@override
List<Object?> get props => [
id,
subject,
from,
to,
cc,
bcc,
replyTo,
email,
identity,
readReceipentEnabled
];
List<EmailContent> get emailContentList {
final newHtmlBody = htmlBody
?.where((emailBody) => emailBody.partId != null && emailBody.type != null)
.toList() ?? <EmailBodyPart>[];
final mapHtmlBody = { for (var emailBody in newHtmlBody) emailBody.partId! : emailBody.type! };
final emailContents = bodyValues?.entries
.map((entries) => EmailContent(mapHtmlBody[entries.key].toEmailContentType(), entries.value.value))
.toList();
return emailContents ?? [];
Map<String, dynamic> toJson() {
return <String, dynamic>{
'email': email?.toJson(),
'identity': identity?.toJson(),
'readReceipentEnabled': readReceipentEnabled,
'displayMode': displayMode.toJson()
};
}
}
factory ComposerCache.fromJson(Map<String, dynamic> map) {
return ComposerCache(
displayMode: ScreenDisplayMode.fromJson(map['displayMode'] ?? ''),
email: map['email'] != null ? Email.fromJson(map['email']) : null,
identity: map['identity'] != null ? Identity.fromJson(map['identity']) : null,
readReceipentEnabled: map['readReceipentEnabled'] as bool?
);
}
}
@@ -1,4 +1,9 @@
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/user_name.dart';
import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource/session_storage_composer_datasource.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/data/model/composer_cache.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/composer_cache_repository.dart';
@@ -10,17 +15,46 @@ class ComposerCacheRepositoryImpl extends ComposerCacheRepository {
ComposerCacheRepositoryImpl(this.composerCacheDataSource);
@override
ComposerCache getComposerCacheOnWeb() {
return composerCacheDataSource.getComposerCacheOnWeb();
Future<ComposerCache> getComposerCacheOnWeb(
AccountId accountId,
UserName userName
) {
return composerCacheDataSource.getComposerCacheOnWeb(accountId, userName);
}
@override
void removeComposerCacheOnWeb() {
Future<void> removeComposerCacheOnWeb() {
return composerCacheDataSource.removeComposerCacheOnWeb();
}
@override
void saveComposerCacheOnWeb(Email email) {
return composerCacheDataSource.saveComposerCacheOnWeb(email);
Future<void> saveComposerCacheOnWeb(
Email email,
{
required AccountId accountId,
required UserName userName,
required ScreenDisplayMode displayMode,
Identity? identity,
bool? readReceipentEnabled
}
) {
return composerCacheDataSource.saveComposerCacheOnWeb(
email,
accountId: accountId,
userName: userName,
displayMode: displayMode,
identity: identity,
readReceipentEnabled: readReceipentEnabled);
}
@override
Future<String> restoreEmailInlineImages(
String htmlContent,
TransformConfiguration transformConfiguration,
Map<String, String> mapUrlDownloadCID) {
return composerCacheDataSource.restoreEmailInlineImages(
htmlContent,
transformConfiguration,
mapUrlDownloadCID);
}
}