TF-3731 Thread Detail Cache EmailLoaded in memory for web
This commit is contained in:
@@ -27,6 +27,15 @@ class GetEmailContentSuccess extends UIState {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GetEmailContentFromThreadCacheSuccess extends GetEmailContentSuccess {
|
||||||
|
GetEmailContentFromThreadCacheSuccess({
|
||||||
|
required super.htmlEmailContent,
|
||||||
|
super.attachments,
|
||||||
|
super.inlineImages,
|
||||||
|
super.emailCurrent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class GetEmailContentFromCacheSuccess extends UIState {
|
class GetEmailContentFromCacheSuccess extends UIState {
|
||||||
final String htmlEmailContent;
|
final String htmlEmailContent;
|
||||||
final List<Attachment>? attachments;
|
final List<Attachment>? attachments;
|
||||||
|
|||||||
@@ -78,4 +78,13 @@ class UnsubscribeFromThreadAction extends EmailUIAction {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [emailId, listUnsubscribe];
|
List<Object?> get props => [emailId, listUnsubscribe];
|
||||||
|
}
|
||||||
|
|
||||||
|
class CollapseEmailInThreadDetailAction extends EmailUIAction {
|
||||||
|
CollapseEmailInThreadDetailAction(this.emailId);
|
||||||
|
|
||||||
|
final EmailId emailId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [emailId];
|
||||||
}
|
}
|
||||||
@@ -127,6 +127,7 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_
|
|||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/focus_thread_detail_expanded_email.dart';
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/focus_thread_detail_expanded_email.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/mark_collapsed_email_unread_success.dart';
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/mark_collapsed_email_unread_success.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/update_cached_list_email_loaded.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.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/error/capability_validator.dart';
|
||||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||||
@@ -414,6 +415,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
} else if (action is UnsubscribeFromThreadAction) {
|
} else if (action is UnsubscribeFromThreadAction) {
|
||||||
if (_currentEmailId == null || action.emailId != _currentEmailId) return;
|
if (_currentEmailId == null || action.emailId != _currentEmailId) return;
|
||||||
_handleUnsubscribe(action.listUnsubscribe);
|
_handleUnsubscribe(action.listUnsubscribe);
|
||||||
|
} else if (action is CollapseEmailInThreadDetailAction) {
|
||||||
|
if (_currentEmailId == null || action.emailId != _currentEmailId) return;
|
||||||
|
for (var worker in obxListeners) {
|
||||||
|
worker.dispose();
|
||||||
|
}
|
||||||
|
Get.delete<SingleEmailController>(tag: _currentEmailId!.id.value);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -425,16 +432,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
if (_threadDetailController != null) {
|
|
||||||
obxListeners.add(ever(
|
|
||||||
_threadDetailController!.currentExpandedEmailId,
|
|
||||||
(emailId) {
|
|
||||||
if (emailId == null || emailId != _currentEmailId) return;
|
|
||||||
|
|
||||||
_threadDetailController!.currentEmailLoaded.value = currentEmailLoaded.value;
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleOpenEmailDetailedView() {
|
void _handleOpenEmailDetailedView() {
|
||||||
@@ -600,6 +597,17 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _getEmailContentAction(EmailId emailId) {
|
void _getEmailContentAction(EmailId emailId) {
|
||||||
|
if (_currentEmailId != null && _threadDetailController?.cachedEmailLoaded[_currentEmailId!] != null) {
|
||||||
|
final emailLoaded = _threadDetailController!.cachedEmailLoaded[_currentEmailId!]!;
|
||||||
|
consumeState(Stream.value(Right(GetEmailContentFromThreadCacheSuccess(
|
||||||
|
htmlEmailContent: emailLoaded.htmlContent,
|
||||||
|
emailCurrent: emailLoaded.emailCurrent,
|
||||||
|
attachments: emailLoaded.attachments,
|
||||||
|
inlineImages: emailLoaded.inlineImages,
|
||||||
|
))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (session != null && accountId != null) {
|
if (session != null && accountId != null) {
|
||||||
try {
|
try {
|
||||||
final baseDownloadUrl = session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl);
|
final baseDownloadUrl = session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl);
|
||||||
@@ -728,6 +736,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
accountId,
|
accountId,
|
||||||
detailedEmail
|
detailedEmail
|
||||||
);
|
);
|
||||||
|
} else if (currentEmail?.id != null && currentEmailLoaded.value != null) {
|
||||||
|
_threadDetailController?.cacheEmailLoaded(
|
||||||
|
currentEmail!.id!,
|
||||||
|
currentEmailLoaded.value!,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final isShowMessageReadReceipt = success.emailCurrent
|
final isShowMessageReadReceipt = success.emailCurrent
|
||||||
@@ -746,7 +759,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
_jumpScrollViewToTopOfEmail();
|
_jumpScrollViewToTopOfEmail();
|
||||||
}
|
}
|
||||||
if (currentEmail?.threadId != null &&
|
if (currentEmail?.threadId != null &&
|
||||||
currentEmail?.id == mailboxDashBoardController.selectedEmail.value?.id) {
|
currentEmail?.id == mailboxDashBoardController.selectedEmail.value?.id &&
|
||||||
|
success is! GetEmailContentFromThreadCacheSuccess) {
|
||||||
mailboxDashBoardController.dispatchThreadDetailUIAction(
|
mailboxDashBoardController.dispatchThreadDetailUIAction(
|
||||||
LoadThreadDetailAfterSelectedEmailAction(
|
LoadThreadDetailAfterSelectedEmailAction(
|
||||||
currentEmail!.threadId!,
|
currentEmail!.threadId!,
|
||||||
|
|||||||
+8
-17
@@ -1,11 +1,7 @@
|
|||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:model/email/email_action_type.dart';
|
|
||||||
import 'package:model/email/presentation_email.dart';
|
import 'package:model/email/presentation_email.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/bindings/email_bindings.dart';
|
import 'package:tmail_ui_user/features/email/presentation/bindings/email_bindings.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
|
|
||||||
import 'package:model/email/email_in_thread_status.dart';
|
import 'package:model/email/email_in_thread_status.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/focus_thread_detail_expanded_email.dart';
|
|
||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_on_email_action_click.dart';
|
|
||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
|
||||||
extension ToggleThreadDetailCollapeExpand on ThreadDetailController {
|
extension ToggleThreadDetailCollapeExpand on ThreadDetailController {
|
||||||
@@ -18,22 +14,14 @@ extension ToggleThreadDetailCollapeExpand on ThreadDetailController {
|
|||||||
emailIdsPresentation[emailId] = presentationEmail.copyWith(
|
emailIdsPresentation[emailId] = presentationEmail.copyWith(
|
||||||
emailInThreadStatus: EmailInThreadStatus.collapsed,
|
emailInThreadStatus: EmailInThreadStatus.collapsed,
|
||||||
);
|
);
|
||||||
|
mailboxDashBoardController.dispatchEmailUIAction(
|
||||||
|
CollapseEmailInThreadDetailAction(emailId),
|
||||||
|
);
|
||||||
currentExpandedEmailId.value = null;
|
currentExpandedEmailId.value = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final isInitialized = Get.isRegistered<SingleEmailController>(
|
EmailBindings(currentEmailId: presentationEmail.id).dependencies();
|
||||||
tag: emailId.id.value,
|
|
||||||
);
|
|
||||||
if (!isInitialized) {
|
|
||||||
EmailBindings(currentEmailId: presentationEmail.id).dependencies();
|
|
||||||
} else {
|
|
||||||
focusExpandedEmail(emailId);
|
|
||||||
threadDetailOnEmailActionClick(
|
|
||||||
presentationEmail,
|
|
||||||
EmailActionType.markAsRead,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (var key in emailIdsPresentation.keys) {
|
for (var key in emailIdsPresentation.keys) {
|
||||||
if (emailIdsPresentation[key] == null) continue;
|
if (emailIdsPresentation[key] == null) continue;
|
||||||
|
|
||||||
@@ -49,6 +37,9 @@ extension ToggleThreadDetailCollapeExpand on ThreadDetailController {
|
|||||||
emailIdsPresentation[key] = emailIdsPresentation[key]?.copyWith(
|
emailIdsPresentation[key] = emailIdsPresentation[key]?.copyWith(
|
||||||
emailInThreadStatus: EmailInThreadStatus.collapsed,
|
emailInThreadStatus: EmailInThreadStatus.collapsed,
|
||||||
);
|
);
|
||||||
|
mailboxDashBoardController.dispatchEmailUIAction(
|
||||||
|
CollapseEmailInThreadDetailAction(key),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
|
||||||
|
extension UpdateCachedEmailLoaded on ThreadDetailController {
|
||||||
|
void cacheEmailLoaded(EmailId emailId, EmailLoaded emailLoaded) {
|
||||||
|
const cacheLimit = 20;
|
||||||
|
|
||||||
|
while (cachedEmailLoaded[emailId] == null &&
|
||||||
|
cachedEmailLoaded.length >= cacheLimit) {
|
||||||
|
cachedEmailLoaded.remove(cachedEmailLoaded.keys.first);
|
||||||
|
}
|
||||||
|
cachedEmailLoaded[emailId] = emailLoaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,6 +89,7 @@ class ThreadDetailController extends BaseController {
|
|||||||
IndividualHeaderIdentifier.listPostHeader.value,
|
IndividualHeaderIdentifier.listPostHeader.value,
|
||||||
IndividualHeaderIdentifier.listUnsubscribeHeader.value,
|
IndividualHeaderIdentifier.listUnsubscribeHeader.value,
|
||||||
});
|
});
|
||||||
|
final cachedEmailLoaded = <EmailId, EmailLoaded>{};
|
||||||
|
|
||||||
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
|
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
|
||||||
final searchEmailController = Get.find<SearchEmailController>();
|
final searchEmailController = Get.find<SearchEmailController>();
|
||||||
@@ -221,6 +222,7 @@ class ThreadDetailController extends BaseController {
|
|||||||
scrollController = null;
|
scrollController = null;
|
||||||
currentExpandedEmailId.value = null;
|
currentExpandedEmailId.value = null;
|
||||||
currentEmailLoaded.value = null;
|
currentEmailLoaded.value = null;
|
||||||
|
cachedEmailLoaded.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||||
|
import 'package:mockito/annotations.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/update_cached_list_email_loaded.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
|
||||||
|
import 'update_cached_list_email_loaded_test.mocks.dart';
|
||||||
|
|
||||||
|
@GenerateNiceMocks([MockSpec<ThreadDetailController>()])
|
||||||
|
void main() {
|
||||||
|
late MockThreadDetailController threadDetailController;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
threadDetailController = MockThreadDetailController();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('update cached list email loaded test:', () {
|
||||||
|
test(
|
||||||
|
'should update cachedEmailLoaded as expected '
|
||||||
|
'when cachedEmailLoaded contains less than limit (20)',
|
||||||
|
() {
|
||||||
|
// arrange
|
||||||
|
when(threadDetailController.cachedEmailLoaded).thenReturn({
|
||||||
|
for (int i = 0; i < 18; i++) EmailId(Id('$i')): EmailLoaded(
|
||||||
|
htmlContent: '',
|
||||||
|
attachments: [],
|
||||||
|
inlineImages: [],
|
||||||
|
emailCurrent: Email(id: EmailId(Id('$i'))),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
final expectedEmailLoaded = EmailLoaded(
|
||||||
|
htmlContent: '',
|
||||||
|
attachments: [],
|
||||||
|
inlineImages: [],
|
||||||
|
emailCurrent: Email(id: EmailId(Id('19'))),
|
||||||
|
);
|
||||||
|
|
||||||
|
// act
|
||||||
|
threadDetailController.cacheEmailLoaded(
|
||||||
|
expectedEmailLoaded.emailCurrent!.id!,
|
||||||
|
expectedEmailLoaded,
|
||||||
|
);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(
|
||||||
|
expectedEmailLoaded,
|
||||||
|
threadDetailController.cachedEmailLoaded.values.last,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'should update cachedEmailLoaded as expected '
|
||||||
|
'when cachedEmailLoaded contains more than limit (20)',
|
||||||
|
() {
|
||||||
|
when(threadDetailController.cachedEmailLoaded).thenReturn({});
|
||||||
|
for (var i = 0; i < 25; i++) {
|
||||||
|
final expectedEmailLoaded = EmailLoaded(
|
||||||
|
htmlContent: '',
|
||||||
|
attachments: [],
|
||||||
|
inlineImages: [],
|
||||||
|
emailCurrent: Email(id: EmailId(Id('$i'))),
|
||||||
|
);
|
||||||
|
threadDetailController.cacheEmailLoaded(
|
||||||
|
expectedEmailLoaded.emailCurrent!.id!,
|
||||||
|
expectedEmailLoaded,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (i >= 20) {
|
||||||
|
expect(
|
||||||
|
expectedEmailLoaded,
|
||||||
|
threadDetailController.cachedEmailLoaded.values.last,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
threadDetailController.cachedEmailLoaded[EmailId(Id('${i - 20}'))],
|
||||||
|
isNull,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user