TF-3334 Use queue to handle multiple refresh changes mailbox from incoming websocket
This commit is contained in:
@@ -16,6 +16,7 @@ export 'presentation/extensions/string_extension.dart';
|
|||||||
export 'presentation/extensions/tap_down_details_extension.dart';
|
export 'presentation/extensions/tap_down_details_extension.dart';
|
||||||
export 'domain/extensions/media_type_extension.dart';
|
export 'domain/extensions/media_type_extension.dart';
|
||||||
export 'presentation/extensions/map_extensions.dart';
|
export 'presentation/extensions/map_extensions.dart';
|
||||||
|
export 'presentation/extensions/either_view_state_extension.dart';
|
||||||
|
|
||||||
// Exceptions
|
// Exceptions
|
||||||
export 'domain/exceptions/download_file_exception.dart';
|
export 'domain/exceptions/download_file_exception.dart';
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import 'package:core/presentation/state/failure.dart';
|
||||||
|
import 'package:core/presentation/state/success.dart';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
|
||||||
|
extension EitherViewStateExtension on Either<Failure, Success> {
|
||||||
|
dynamic foldSuccessWithResult<T>() {
|
||||||
|
return fold(
|
||||||
|
(failure) => failure,
|
||||||
|
(success) => success is T ? success as T : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -143,20 +143,7 @@ abstract class BaseController extends GetxController
|
|||||||
|
|
||||||
void onData(Either<Failure, Success> newState) {
|
void onData(Either<Failure, Success> newState) {
|
||||||
viewState.value = newState;
|
viewState.value = newState;
|
||||||
viewState.value.fold(
|
viewState.value.fold(onDataFailureViewState, handleSuccessViewState);
|
||||||
(failure) {
|
|
||||||
if (failure is FeatureFailure) {
|
|
||||||
final isUrgentException = validateUrgentException(failure.exception);
|
|
||||||
if (isUrgentException) {
|
|
||||||
handleUrgentException(failure: failure, exception: failure.exception);
|
|
||||||
} else {
|
|
||||||
handleFailureViewState(failure);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
handleFailureViewState(failure);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleSuccessViewState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onError(dynamic error, StackTrace stackTrace) {
|
void onError(dynamic error, StackTrace stackTrace) {
|
||||||
@@ -272,6 +259,19 @@ abstract class BaseController extends GetxController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onDataFailureViewState(Failure failure) {
|
||||||
|
if (failure is FeatureFailure) {
|
||||||
|
final isUrgentException = validateUrgentException(failure.exception);
|
||||||
|
if (isUrgentException) {
|
||||||
|
handleUrgentException(failure: failure, exception: failure.exception);
|
||||||
|
} else {
|
||||||
|
handleFailureViewState(failure);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleFailureViewState(failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void handleFailureViewState(Failure failure) async {
|
void handleFailureViewState(Failure failure) async {
|
||||||
logError('$runtimeType::handleFailureViewState():Failure = $failure');
|
logError('$runtimeType::handleFailureViewState():Failure = $failure');
|
||||||
if (failure is LogoutOidcFailure) {
|
if (failure is LogoutOidcFailure) {
|
||||||
|
|||||||
@@ -104,8 +104,7 @@ abstract class BaseMailboxController extends BaseController {
|
|||||||
teamMailboxesTree.value = tupleTree.value3;
|
teamMailboxesTree.value = tupleTree.value3;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> syncAllMailboxWithDisplayName(BuildContext context) async {
|
void syncAllMailboxWithDisplayName(BuildContext context) {
|
||||||
log("BaseMailboxController::syncAllMailboxWithDisplayName");
|
|
||||||
final syncedMailbox = allMailboxes
|
final syncedMailbox = allMailboxes
|
||||||
.map((mailbox) => mailbox.withDisplayName(mailbox.getDisplayName(context)))
|
.map((mailbox) => mailbox.withDisplayName(mailbox.getDisplayName(context)))
|
||||||
.toList();
|
.toList();
|
||||||
|
|||||||
@@ -112,12 +112,12 @@ class DestinationPickerController extends BaseMailboxController {
|
|||||||
await buildTree(success.mailboxList.listSubscribedMailboxesAndDefaultMailboxes);
|
await buildTree(success.mailboxList.listSubscribedMailboxesAndDefaultMailboxes);
|
||||||
}
|
}
|
||||||
if (currentContext != null) {
|
if (currentContext != null) {
|
||||||
await syncAllMailboxWithDisplayName(currentContext!);
|
syncAllMailboxWithDisplayName(currentContext!);
|
||||||
}
|
}
|
||||||
} else if (success is RefreshChangesAllMailboxSuccess) {
|
} else if (success is RefreshChangesAllMailboxSuccess) {
|
||||||
await refreshTree(success.mailboxList.listSubscribedMailboxesAndDefaultMailboxes);
|
await refreshTree(success.mailboxList.listSubscribedMailboxesAndDefaultMailboxes);
|
||||||
if (currentContext != null) {
|
if (currentContext != null) {
|
||||||
await syncAllMailboxWithDisplayName(currentContext!);
|
syncAllMailboxWithDisplayName(currentContext!);
|
||||||
}
|
}
|
||||||
} else if (success is SearchMailboxSuccess) {
|
} else if (success is SearchMailboxSuccess) {
|
||||||
_searchMailboxSuccess(success);
|
_searchMailboxSuccess(success);
|
||||||
|
|||||||
@@ -1711,57 +1711,54 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
void _rejectCalendarEventAction(EmailId emailId) {
|
void _rejectCalendarEventAction(EmailId emailId) {
|
||||||
if (_rejectCalendarEventInteractor == null
|
if (_rejectCalendarEventInteractor == null
|
||||||
|| _displayingEventBlobId == null
|
|| _displayingEventBlobId == null
|
||||||
|| mailboxDashBoardController.accountId.value == null
|
|| accountId == null
|
||||||
|| mailboxDashBoardController.sessionCurrent == null
|
|| session == null
|
||||||
|| mailboxDashBoardController.sessionCurrent
|
|| session!.validateCalendarEventCapability(accountId!).isAvailable == false
|
||||||
!.validateCalendarEventCapability(mailboxDashBoardController.accountId.value!)
|
|
||||||
.isAvailable == false
|
|
||||||
) {
|
) {
|
||||||
consumeState(Stream.value(Left(CalendarEventRejectFailure())));
|
consumeState(Stream.value(Left(CalendarEventRejectFailure())));
|
||||||
} else {
|
} else {
|
||||||
consumeState(_rejectCalendarEventInteractor!.execute(
|
consumeState(_rejectCalendarEventInteractor!.execute(
|
||||||
mailboxDashBoardController.accountId.value!,
|
accountId!,
|
||||||
{_displayingEventBlobId!},
|
{_displayingEventBlobId!},
|
||||||
emailId,
|
emailId,
|
||||||
mailboxDashBoardController.sessionCurrent!.getLanguageForCalendarEvent(
|
session!.getLanguageForCalendarEvent(
|
||||||
LocalizationService.getLocaleFromLanguage(),
|
LocalizationService.getLocaleFromLanguage(),
|
||||||
mailboxDashBoardController.accountId.value!)));
|
accountId!,
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _maybeCalendarEventAction(EmailId emailId) {
|
void _maybeCalendarEventAction(EmailId emailId) {
|
||||||
if (_maybeCalendarEventInteractor == null
|
if (_maybeCalendarEventInteractor == null
|
||||||
|| _displayingEventBlobId == null
|
|| _displayingEventBlobId == null
|
||||||
|| mailboxDashBoardController.accountId.value == null
|
|| accountId == null
|
||||||
|| mailboxDashBoardController.sessionCurrent == null
|
|| session == null
|
||||||
|| mailboxDashBoardController.sessionCurrent
|
|| session!.validateCalendarEventCapability(accountId!).isAvailable == false
|
||||||
!.validateCalendarEventCapability(mailboxDashBoardController.accountId.value!)
|
|
||||||
.isAvailable == false
|
|
||||||
) {
|
) {
|
||||||
consumeState(Stream.value(Left(CalendarEventMaybeFailure())));
|
consumeState(Stream.value(Left(CalendarEventMaybeFailure())));
|
||||||
} else {
|
} else {
|
||||||
consumeState(_maybeCalendarEventInteractor!.execute(
|
consumeState(_maybeCalendarEventInteractor!.execute(
|
||||||
mailboxDashBoardController.accountId.value!,
|
accountId!,
|
||||||
{_displayingEventBlobId!},
|
{_displayingEventBlobId!},
|
||||||
emailId,
|
emailId,
|
||||||
mailboxDashBoardController.sessionCurrent!.getLanguageForCalendarEvent(
|
session!.getLanguageForCalendarEvent(
|
||||||
LocalizationService.getLocaleFromLanguage(),
|
LocalizationService.getLocaleFromLanguage(),
|
||||||
mailboxDashBoardController.accountId.value!)));
|
accountId!,
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void calendarEventSuccess(CalendarEventReplySuccess success) {
|
void calendarEventSuccess(CalendarEventReplySuccess success) {
|
||||||
final session = mailboxDashBoardController.sessionCurrent;
|
|
||||||
final accountId = mailboxDashBoardController.accountId.value;
|
|
||||||
|
|
||||||
if (session == null || accountId == null) {
|
if (session == null || accountId == null) {
|
||||||
consumeState(Stream.value(Left(StoreEventAttendanceStatusFailure(exception: NotFoundSessionException()))));
|
consumeState(Stream.value(Left(StoreEventAttendanceStatusFailure(exception: NotFoundSessionException()))));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
consumeState(_storeEventAttendanceStatusInteractor.execute(
|
consumeState(_storeEventAttendanceStatusInteractor.execute(
|
||||||
session,
|
session!,
|
||||||
accountId,
|
accountId!,
|
||||||
success.emailId,
|
success.emailId,
|
||||||
success.getEventActionType()
|
success.getEventActionType()
|
||||||
));
|
));
|
||||||
@@ -1839,16 +1836,15 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> previewPDFFileAction(BuildContext context, Attachment attachment) async {
|
Future<void> previewPDFFileAction(BuildContext context, Attachment attachment) async {
|
||||||
final accountId = mailboxDashBoardController.accountId.value;
|
if (accountId == null || session == null) {
|
||||||
final downloadUrl = mailboxDashBoardController.sessionCurrent
|
|
||||||
?.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl);
|
|
||||||
|
|
||||||
if (accountId == null || downloadUrl == null) {
|
|
||||||
appToast.showToastErrorMessage(
|
appToast.showToastErrorMessage(
|
||||||
context,
|
context,
|
||||||
AppLocalizations.of(context).noPreviewAvailable);
|
AppLocalizations.of(context).noPreviewAvailable);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
final downloadUrl = session!.getDownloadUrl(
|
||||||
|
jmapUrl: dynamicUrlInterceptors.jmapUrl,
|
||||||
|
);
|
||||||
|
|
||||||
await Get.generalDialog(
|
await Get.generalDialog(
|
||||||
barrierColor: Colors.black.withOpacity(0.8),
|
barrierColor: Colors.black.withOpacity(0.8),
|
||||||
@@ -1856,7 +1852,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
return PointerInterceptor(
|
return PointerInterceptor(
|
||||||
child: PDFViewer(
|
child: PDFViewer(
|
||||||
attachment: attachment,
|
attachment: attachment,
|
||||||
accountId: accountId,
|
accountId: accountId!,
|
||||||
downloadUrl: downloadUrl,
|
downloadUrl: downloadUrl,
|
||||||
downloadAction: _downloadPDFFile,
|
downloadAction: _downloadPDFFile,
|
||||||
printAction: _printPDFFile,
|
printAction: _printPDFFile,
|
||||||
@@ -1876,7 +1872,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final listEmailAddressMailTo = listEmailAddressAttendees
|
final listEmailAddressMailTo = listEmailAddressAttendees
|
||||||
.where((emailAddress) => emailAddress.emailAddress.isNotEmpty && emailAddress.emailAddress != mailboxDashBoardController.sessionCurrent?.username.value)
|
.where((emailAddress) {
|
||||||
|
return emailAddress.emailAddress.isNotEmpty &&
|
||||||
|
emailAddress.emailAddress != session?.username.value;
|
||||||
|
})
|
||||||
.toSet()
|
.toSet()
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ import 'package:tmail_ui_user/features/mailbox_creator/presentation/model/new_ma
|
|||||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart';
|
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart';
|
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart';
|
||||||
|
import 'package:tmail_ui_user/features/push_notification/presentation/websocket/web_socket_message.dart';
|
||||||
|
import 'package:tmail_ui_user/features/push_notification/presentation/websocket/web_socket_queue_handler.dart';
|
||||||
import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_bindings.dart';
|
import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_bindings.dart';
|
||||||
import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart';
|
import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart';
|
||||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||||
@@ -94,6 +96,7 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM
|
|||||||
|
|
||||||
MailboxId? _newFolderId;
|
MailboxId? _newFolderId;
|
||||||
NavigationRouter? _navigationRouter;
|
NavigationRouter? _navigationRouter;
|
||||||
|
WebSocketQueueHandler? _webSocketQueueHandler;
|
||||||
|
|
||||||
final _openMailboxEventController = StreamController<OpenMailboxViewEvent>();
|
final _openMailboxEventController = StreamController<OpenMailboxViewEvent>();
|
||||||
final mailboxListScrollController = ScrollController();
|
final mailboxListScrollController = ScrollController();
|
||||||
@@ -128,6 +131,7 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM
|
|||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
_registerObxStreamListener();
|
_registerObxStreamListener();
|
||||||
|
_initWebSocketQueueHandler();
|
||||||
super.onInit();
|
super.onInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +149,7 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM
|
|||||||
void onClose() {
|
void onClose() {
|
||||||
_openMailboxEventController.close();
|
_openMailboxEventController.close();
|
||||||
mailboxListScrollController.dispose();
|
mailboxListScrollController.dispose();
|
||||||
|
_webSocketQueueHandler?.dispose();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,8 +158,6 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM
|
|||||||
super.handleSuccessViewState(success);
|
super.handleSuccessViewState(success);
|
||||||
if (success is GetAllMailboxSuccess) {
|
if (success is GetAllMailboxSuccess) {
|
||||||
_handleGetAllMailboxSuccess(success);
|
_handleGetAllMailboxSuccess(success);
|
||||||
} else if (success is RefreshChangesAllMailboxSuccess) {
|
|
||||||
_handleRefreshChangesAllMailboxSuccess(success);
|
|
||||||
} else if (success is CreateNewMailboxSuccess) {
|
} else if (success is CreateNewMailboxSuccess) {
|
||||||
_createNewMailboxSuccess(success);
|
_createNewMailboxSuccess(success);
|
||||||
} else if (success is DeleteMultipleMailboxAllSuccess) {
|
} else if (success is DeleteMultipleMailboxAllSuccess) {
|
||||||
@@ -181,8 +184,6 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM
|
|||||||
_renameMailboxFailure(failure);
|
_renameMailboxFailure(failure);
|
||||||
} else if (failure is DeleteMultipleMailboxFailure) {
|
} else if (failure is DeleteMultipleMailboxFailure) {
|
||||||
_deleteMailboxFailure(failure);
|
_deleteMailboxFailure(failure);
|
||||||
} else if (failure is RefreshChangesAllMailboxFailure) {
|
|
||||||
_clearNewFolderId();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,13 +205,6 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM
|
|||||||
if (PlatformInfo.isIOS) {
|
if (PlatformInfo.isIOS) {
|
||||||
_updateMailboxIdsBlockNotificationToKeychain(success.mailboxList);
|
_updateMailboxIdsBlockNotificationToKeychain(success.mailboxList);
|
||||||
}
|
}
|
||||||
} else if (success is RefreshChangesAllMailboxSuccess) {
|
|
||||||
_selectSelectedMailboxDefault();
|
|
||||||
mailboxDashBoardController.refreshSpamReportBanner();
|
|
||||||
|
|
||||||
if (_newFolderId != null) {
|
|
||||||
_redirectToNewFolder();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -258,6 +252,13 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _initWebSocketQueueHandler() {
|
||||||
|
_webSocketQueueHandler = WebSocketQueueHandler(
|
||||||
|
processMessageCallback: _handleWebSocketMessage,
|
||||||
|
onErrorCallback: onError,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _initCollapseMailboxCategories() {
|
void _initCollapseMailboxCategories() {
|
||||||
if (kIsWeb && currentContext != null
|
if (kIsWeb && currentContext != null
|
||||||
&& (responsiveUtils.isMobile(currentContext!) || responsiveUtils.isTablet(currentContext!))) {
|
&& (responsiveUtils.isMobile(currentContext!) || responsiveUtils.isTablet(currentContext!))) {
|
||||||
@@ -283,17 +284,66 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM
|
|||||||
if (accountId == null ||
|
if (accountId == null ||
|
||||||
session == null ||
|
session == null ||
|
||||||
currentMailboxState == null ||
|
currentMailboxState == null ||
|
||||||
newState == currentMailboxState) {
|
newState == null) {
|
||||||
_newFolderId = null;
|
_newFolderId = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshMailboxChanges(
|
_webSocketQueueHandler?.enqueue(WebSocketMessage(newState: newState));
|
||||||
session!,
|
}
|
||||||
accountId!,
|
|
||||||
currentMailboxState!,
|
Future<void> _handleWebSocketMessage(WebSocketMessage message) async {
|
||||||
properties: MailboxConstants.propertiesDefault,
|
try {
|
||||||
);
|
if (currentMailboxState == message.newState) {
|
||||||
|
log('MailboxController::_handleWebSocketMessage:Skipping redundant state: ${message.newState}');
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
final refreshViewState = await refreshAllMailboxInteractor!.execute(
|
||||||
|
session!,
|
||||||
|
accountId!,
|
||||||
|
currentMailboxState!,
|
||||||
|
properties: MailboxConstants.propertiesDefault,
|
||||||
|
).last;
|
||||||
|
|
||||||
|
final refreshState = refreshViewState
|
||||||
|
.foldSuccessWithResult<RefreshChangesAllMailboxSuccess>();
|
||||||
|
|
||||||
|
if (refreshState is RefreshChangesAllMailboxSuccess) {
|
||||||
|
await _handleRefreshChangeMailboxSuccess(refreshState);
|
||||||
|
} else {
|
||||||
|
_clearNewFolderId();
|
||||||
|
onDataFailureViewState(refreshState);
|
||||||
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
logError('MailboxController::_processMailboxStateQueue:Error processing state: $e');
|
||||||
|
onError(e, stackTrace);
|
||||||
|
}
|
||||||
|
if (currentMailboxState != null) {
|
||||||
|
_webSocketQueueHandler?.removeMessagesUpToCurrent(currentMailboxState!.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleRefreshChangeMailboxSuccess(RefreshChangesAllMailboxSuccess success) async {
|
||||||
|
currentMailboxState = success.currentMailboxState;
|
||||||
|
log('MailboxController::_handleRefreshChangeMailboxSuccess:currentMailboxState: $currentMailboxState');
|
||||||
|
final listMailboxDisplayed = success
|
||||||
|
.mailboxList
|
||||||
|
.listSubscribedMailboxesAndDefaultMailboxes;
|
||||||
|
|
||||||
|
await refreshTree(listMailboxDisplayed);
|
||||||
|
|
||||||
|
if (currentContext != null) {
|
||||||
|
syncAllMailboxWithDisplayName(currentContext!);
|
||||||
|
}
|
||||||
|
_setMapMailbox();
|
||||||
|
_setOutboxMailbox();
|
||||||
|
_selectSelectedMailboxDefault();
|
||||||
|
mailboxDashBoardController.refreshSpamReportBanner();
|
||||||
|
|
||||||
|
if (_newFolderId != null) {
|
||||||
|
_redirectToNewFolder();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setMapMailbox() {
|
void _setMapMailbox() {
|
||||||
@@ -1078,7 +1128,7 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM
|
|||||||
final listMailboxDisplayed = success.mailboxList.listSubscribedMailboxesAndDefaultMailboxes;
|
final listMailboxDisplayed = success.mailboxList.listSubscribedMailboxesAndDefaultMailboxes;
|
||||||
await buildTree(listMailboxDisplayed);
|
await buildTree(listMailboxDisplayed);
|
||||||
if (currentContext != null) {
|
if (currentContext != null) {
|
||||||
await syncAllMailboxWithDisplayName(currentContext!);
|
syncAllMailboxWithDisplayName(currentContext!);
|
||||||
}
|
}
|
||||||
_setMapMailbox();
|
_setMapMailbox();
|
||||||
_setOutboxMailbox();
|
_setOutboxMailbox();
|
||||||
@@ -1105,18 +1155,6 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM
|
|||||||
mailboxIds: mailboxIdsBlockNotification);
|
mailboxIds: mailboxIdsBlockNotification);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleRefreshChangesAllMailboxSuccess(RefreshChangesAllMailboxSuccess success) async {
|
|
||||||
currentMailboxState = success.currentMailboxState;
|
|
||||||
log('MailboxController::_handleRefreshChangesAllMailboxSuccess:currentMailboxState: $currentMailboxState');
|
|
||||||
final listMailboxDisplayed = success.mailboxList.listSubscribedMailboxesAndDefaultMailboxes;
|
|
||||||
await refreshTree(listMailboxDisplayed);
|
|
||||||
if (currentContext != null) {
|
|
||||||
await syncAllMailboxWithDisplayName(currentContext!);
|
|
||||||
}
|
|
||||||
_setMapMailbox();
|
|
||||||
_setOutboxMailbox();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _unsubscribeMailboxAction(MailboxId mailboxId) {
|
void _unsubscribeMailboxAction(MailboxId mailboxId) {
|
||||||
if (session != null && accountId != null) {
|
if (session != null && accountId != null) {
|
||||||
final subscribeRequest = generateSubscribeRequest(
|
final subscribeRequest = generateSubscribeRequest(
|
||||||
|
|||||||
+8
@@ -243,6 +243,7 @@ class MailboxDashBoardController extends ReloadableController with UserSettingPo
|
|||||||
PresentationMailbox? outboxMailbox;
|
PresentationMailbox? outboxMailbox;
|
||||||
ComposerArguments? composerArguments;
|
ComposerArguments? composerArguments;
|
||||||
List<Identity>? _identities;
|
List<Identity>? _identities;
|
||||||
|
jmap.State? _currentEmailState;
|
||||||
ScrollController? listSearchFilterScrollController;
|
ScrollController? listSearchFilterScrollController;
|
||||||
StreamSubscription? _pendingSharedFileInfoSubscription;
|
StreamSubscription? _pendingSharedFileInfoSubscription;
|
||||||
StreamSubscription? _receivingFileSharingStreamSubscription;
|
StreamSubscription? _receivingFileSharingStreamSubscription;
|
||||||
@@ -2930,6 +2931,12 @@ class MailboxDashBoardController extends ReloadableController with UserSettingPo
|
|||||||
accountId: accountId.value!);
|
accountId: accountId.value!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setCurrentEmailState(jmap.State? newState) {
|
||||||
|
_currentEmailState = newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
jmap.State? get currentEmailState => _currentEmailState;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
if (PlatformInfo.isWeb) {
|
if (PlatformInfo.isWeb) {
|
||||||
@@ -2957,6 +2964,7 @@ class MailboxDashBoardController extends ReloadableController with UserSettingPo
|
|||||||
mapMailboxById = {};
|
mapMailboxById = {};
|
||||||
mapDefaultMailboxIdByRole = {};
|
mapDefaultMailboxIdByRole = {};
|
||||||
WebSocketController.instance.onClose();
|
WebSocketController.instance.onClose();
|
||||||
|
_currentEmailState = null;
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -73,7 +73,7 @@ class MailboxVisibilityController extends BaseMailboxController {
|
|||||||
currentMailboxState = success.currentMailboxState;
|
currentMailboxState = success.currentMailboxState;
|
||||||
await refreshTree(success.mailboxList);
|
await refreshTree(success.mailboxList);
|
||||||
if (currentContext != null) {
|
if (currentContext != null) {
|
||||||
await syncAllMailboxWithDisplayName(currentContext!);
|
syncAllMailboxWithDisplayName(currentContext!);
|
||||||
}
|
}
|
||||||
} else if (success is SubscribeMailboxSuccess) {
|
} else if (success is SubscribeMailboxSuccess) {
|
||||||
_subscribeMailboxSuccess(success);
|
_subscribeMailboxSuccess(success);
|
||||||
@@ -99,7 +99,7 @@ class MailboxVisibilityController extends BaseMailboxController {
|
|||||||
await buildTree(mailboxList);
|
await buildTree(mailboxList);
|
||||||
dispatchState(Right(BuildTreeMailboxVisibilitySuccess()));
|
dispatchState(Right(BuildTreeMailboxVisibilitySuccess()));
|
||||||
if (currentContext != null) {
|
if (currentContext != null) {
|
||||||
await syncAllMailboxWithDisplayName(currentContext!);
|
syncAllMailboxWithDisplayName(currentContext!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:core/presentation/state/failure.dart';
|
|||||||
import 'package:core/presentation/state/success.dart';
|
import 'package:core/presentation/state/success.dart';
|
||||||
import 'package:core/utils/app_logger.dart';
|
import 'package:core/utils/app_logger.dart';
|
||||||
import 'package:core/utils/platform_info.dart';
|
import 'package:core/utils/platform_info.dart';
|
||||||
|
import 'package:debounce_throttle/debounce_throttle.dart';
|
||||||
import 'package:fcm/model/type_name.dart';
|
import 'package:fcm/model/type_name.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:jmap_dart_client/jmap/account_id.dart';
|
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||||
@@ -18,6 +19,7 @@ import 'package:tmail_ui_user/features/push_notification/presentation/controller
|
|||||||
import 'package:tmail_ui_user/features/push_notification/presentation/extensions/state_change_extension.dart';
|
import 'package:tmail_ui_user/features/push_notification/presentation/extensions/state_change_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/push_notification/presentation/listener/email_change_listener.dart';
|
import 'package:tmail_ui_user/features/push_notification/presentation/listener/email_change_listener.dart';
|
||||||
import 'package:tmail_ui_user/features/push_notification/presentation/listener/mailbox_change_listener.dart';
|
import 'package:tmail_ui_user/features/push_notification/presentation/listener/mailbox_change_listener.dart';
|
||||||
|
import 'package:tmail_ui_user/features/push_notification/presentation/utils/fcm_utils.dart';
|
||||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||||
import 'package:tmail_ui_user/main/utils/app_config.dart';
|
import 'package:tmail_ui_user/main/utils/app_config.dart';
|
||||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||||
@@ -36,6 +38,7 @@ class WebSocketController extends PushBaseController {
|
|||||||
Timer? _webSocketPingTimer;
|
Timer? _webSocketPingTimer;
|
||||||
StreamSubscription? _webSocketSubscription;
|
StreamSubscription? _webSocketSubscription;
|
||||||
AppLifecycleListener? _appLifecycleListener;
|
AppLifecycleListener? _appLifecycleListener;
|
||||||
|
Debouncer<StateChange?>? _stateChangeDebouncer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void handleFailureViewState(Failure failure) {
|
void handleFailureViewState(Failure failure) {
|
||||||
@@ -105,8 +108,9 @@ class WebSocketController extends PushBaseController {
|
|||||||
_webSocketSubscription?.cancel();
|
_webSocketSubscription?.cancel();
|
||||||
_webSocketChannel = null;
|
_webSocketChannel = null;
|
||||||
_webSocketPingTimer?.cancel();
|
_webSocketPingTimer?.cancel();
|
||||||
|
_stateChangeDebouncer?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleWebSocketConnectionSuccess(WebSocketConnectionSuccess success) {
|
void _handleWebSocketConnectionSuccess(WebSocketConnectionSuccess success) {
|
||||||
log('WebSocketController::_handleWebSocketConnectionSuccess(): $success');
|
log('WebSocketController::_handleWebSocketConnectionSuccess(): $success');
|
||||||
_cleanUpWebSocketResources();
|
_cleanUpWebSocketResources();
|
||||||
@@ -117,6 +121,7 @@ class WebSocketController extends PushBaseController {
|
|||||||
_pingWebSocket();
|
_pingWebSocket();
|
||||||
}
|
}
|
||||||
_listenToWebSocket();
|
_listenToWebSocket();
|
||||||
|
_initStateChangeDeouncerTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleWebSocketConnectionRetry() {
|
void _handleWebSocketConnectionRetry() {
|
||||||
@@ -150,14 +155,7 @@ class WebSocketController extends PushBaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final stateChange = StateChange.fromJson(data);
|
final stateChange = StateChange.fromJson(data);
|
||||||
final mapTypeState = stateChange.getMapTypeState(accountId!);
|
_stateChangeDebouncer?.value = stateChange;
|
||||||
mappingTypeStateToAction(
|
|
||||||
mapTypeState,
|
|
||||||
accountId!,
|
|
||||||
emailChangeListener: EmailChangeListener.instance,
|
|
||||||
mailboxChangeListener: MailboxChangeListener.instance,
|
|
||||||
session!.username,
|
|
||||||
session: session);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError('WebSocketController::_listenToWebSocket(): Data is not StateChange');
|
logError('WebSocketController::_listenToWebSocket(): Data is not StateChange');
|
||||||
}
|
}
|
||||||
@@ -173,4 +171,31 @@ class WebSocketController extends PushBaseController {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _initStateChangeDeouncerTimer() {
|
||||||
|
_stateChangeDebouncer = Debouncer<StateChange?>(
|
||||||
|
const Duration(milliseconds: FcmUtils.durationMessageComing),
|
||||||
|
initialValue: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
_stateChangeDebouncer?.values.listen(_handleStateChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleStateChange(StateChange? stateChange) {
|
||||||
|
try {
|
||||||
|
if (stateChange == null || accountId == null || session == null) return;
|
||||||
|
|
||||||
|
final mapTypeState = stateChange.getMapTypeState(accountId!);
|
||||||
|
mappingTypeStateToAction(
|
||||||
|
mapTypeState,
|
||||||
|
accountId!,
|
||||||
|
emailChangeListener: EmailChangeListener.instance,
|
||||||
|
mailboxChangeListener: MailboxChangeListener.instance,
|
||||||
|
session!.username,
|
||||||
|
session: session,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
logError('WebSocketController::_handleStateChange:Exception = $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/state.dart' as jmap;
|
||||||
|
|
||||||
|
class WebSocketMessage with EquatableMixin {
|
||||||
|
final jmap.State newState;
|
||||||
|
|
||||||
|
WebSocketMessage({required this.newState});
|
||||||
|
|
||||||
|
String get id => newState.value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [newState];
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:core/utils/app_logger.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:tmail_ui_user/features/push_notification/presentation/websocket/web_socket_message.dart';
|
||||||
|
|
||||||
|
typedef ProcessMessageCallback = Future<void> Function(WebSocketMessage message);
|
||||||
|
typedef OnErrorCallback = void Function(dynamic error, StackTrace stackTrace);
|
||||||
|
|
||||||
|
class WebSocketQueueHandler {
|
||||||
|
static const int _maxQueueSize = 128;
|
||||||
|
static const int _maxProcessedIdsSize = 128;
|
||||||
|
|
||||||
|
final Queue<WebSocketMessage> _messageQueue = Queue<WebSocketMessage>();
|
||||||
|
final Queue<String> _processedMessageIds = Queue<String>();
|
||||||
|
|
||||||
|
Completer<void>? _processingLock;
|
||||||
|
|
||||||
|
final _queueController = StreamController<WebSocketMessage>.broadcast();
|
||||||
|
|
||||||
|
final ProcessMessageCallback processMessageCallback;
|
||||||
|
final OnErrorCallback? onErrorCallback;
|
||||||
|
|
||||||
|
WebSocketQueueHandler({
|
||||||
|
required this.processMessageCallback,
|
||||||
|
this.onErrorCallback,
|
||||||
|
}) {
|
||||||
|
_queueController.stream.listen((_) {
|
||||||
|
_processQueue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void enqueue(WebSocketMessage message) {
|
||||||
|
if (isMessageProcessed(message.id)) {
|
||||||
|
log('WebSocketQueueHandler::enqueue:Message ${message.id} already processed, skipping');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queueSize >= _maxQueueSize) {
|
||||||
|
log('WebSocketQueueHandler::enqueue:Queue full, removing oldest message');
|
||||||
|
_messageQueue.removeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
_messageQueue.add(message);
|
||||||
|
_queueController.add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _processQueue() async {
|
||||||
|
if (_processingLock != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_processingLock = Completer<void>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (queueSize > 0) {
|
||||||
|
final message = _messageQueue.removeFirst();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await processMessageCallback(message);
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
logError('WebSocketQueueHandler::_processQueue:Error processing message ${message.id}: $e');
|
||||||
|
onErrorCallback?.call(e, stackTrace);
|
||||||
|
} finally {
|
||||||
|
_addToProcessedMessages(message.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_processingLock?.complete();
|
||||||
|
_processingLock = null;
|
||||||
|
|
||||||
|
if (queueSize > 0) {
|
||||||
|
scheduleMicrotask(() => _queueController.add(_messageQueue.first));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addToProcessedMessages(String messageId) {
|
||||||
|
if (_processedMessageIds.length >= _maxProcessedIdsSize) {
|
||||||
|
_processedMessageIds.removeFirst();
|
||||||
|
}
|
||||||
|
_processedMessageIds.add(messageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeMessagesUpToCurrent(String messageId) {
|
||||||
|
final isCurrentStateExist = _messageQueue
|
||||||
|
.any((message) => message.id == messageId);
|
||||||
|
|
||||||
|
if (!isCurrentStateExist) {
|
||||||
|
log('WebSocketQueueHandler::removeMessagesUpToCurrent:Current state $messageId not found in the queue.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (queueSize > 0) {
|
||||||
|
final removedMessage = _messageQueue.removeFirst();
|
||||||
|
if (removedMessage.id == messageId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log('WebSocketQueueHandler::removeMessagesUpToCurrent:Updated Queue: $queueSize');
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
Future<void> waitForEmpty() async {
|
||||||
|
while (_messageQueue.isNotEmpty || _processingLock != null) {
|
||||||
|
if (_processingLock != null) {
|
||||||
|
await _processingLock!.future;
|
||||||
|
}
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int get queueSize => _messageQueue.length;
|
||||||
|
|
||||||
|
bool isMessageProcessed(String messageId) => _processedMessageIds.contains(messageId);
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
_queueController.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -127,7 +127,7 @@ class RulesFilterCreatorController extends BaseMailboxController {
|
|||||||
if (success is GetAllMailboxSuccess) {
|
if (success is GetAllMailboxSuccess) {
|
||||||
await buildTree(success.mailboxList);
|
await buildTree(success.mailboxList);
|
||||||
if (currentContext != null) {
|
if (currentContext != null) {
|
||||||
await syncAllMailboxWithDisplayName(currentContext!);
|
syncAllMailboxWithDisplayName(currentContext!);
|
||||||
}
|
}
|
||||||
} else if (success is GetAllRulesSuccess) {
|
} else if (success is GetAllRulesSuccess) {
|
||||||
log('RulesFilterCreatorController::handleSuccessViewState():GetAllRulesSuccess: ${success.rules}');
|
log('RulesFilterCreatorController::handleSuccessViewState():GetAllRulesSuccess: ${success.rules}');
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
|
import 'package:core/presentation/extensions/either_view_state_extension.dart';
|
||||||
import 'package:core/presentation/state/failure.dart';
|
import 'package:core/presentation/state/failure.dart';
|
||||||
import 'package:core/presentation/state/success.dart';
|
import 'package:core/presentation/state/success.dart';
|
||||||
import 'package:core/presentation/utils/keyboard_utils.dart';
|
import 'package:core/presentation/utils/keyboard_utils.dart';
|
||||||
@@ -10,6 +11,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:jmap_dart_client/jmap/account_id.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/core/session/session.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/state.dart' as jmap;
|
||||||
import 'package:jmap_dart_client/jmap/core/unsigned_int.dart';
|
import 'package:jmap_dart_client/jmap/core/unsigned_int.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/utc_date.dart';
|
import 'package:jmap_dart_client/jmap/core/utc_date.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||||
@@ -50,6 +52,8 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/sear
|
|||||||
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart';
|
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart'
|
import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart'
|
||||||
if (dart.library.html) 'package:tmail_ui_user/features/network_connection/presentation/web_network_connection_controller.dart';
|
if (dart.library.html) 'package:tmail_ui_user/features/network_connection/presentation/web_network_connection_controller.dart';
|
||||||
|
import 'package:tmail_ui_user/features/push_notification/presentation/websocket/web_socket_message.dart';
|
||||||
|
import 'package:tmail_ui_user/features/push_notification/presentation/websocket/web_socket_queue_handler.dart';
|
||||||
import 'package:tmail_ui_user/features/search/email/domain/state/refresh_changes_search_email_state.dart';
|
import 'package:tmail_ui_user/features/search/email/domain/state/refresh_changes_search_email_state.dart';
|
||||||
import 'package:tmail_ui_user/features/search/email/domain/usecases/refresh_changes_search_email_interactor.dart';
|
import 'package:tmail_ui_user/features/search/email/domain/usecases/refresh_changes_search_email_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/search/email/presentation/model/search_more_state.dart';
|
import 'package:tmail_ui_user/features/search/email/presentation/model/search_more_state.dart';
|
||||||
@@ -105,6 +109,7 @@ class SearchEmailController extends BaseController
|
|||||||
late Worker dashBoardActionWorker;
|
late Worker dashBoardActionWorker;
|
||||||
late SearchMoreState searchMoreState;
|
late SearchMoreState searchMoreState;
|
||||||
late bool canSearchMore;
|
late bool canSearchMore;
|
||||||
|
WebSocketQueueHandler? _webSocketQueueHandler;
|
||||||
|
|
||||||
PresentationMailbox? get currentMailbox => mailboxDashBoardController.selectedMailbox.value;
|
PresentationMailbox? get currentMailbox => mailboxDashBoardController.selectedMailbox.value;
|
||||||
|
|
||||||
@@ -145,6 +150,7 @@ class SearchEmailController extends BaseController
|
|||||||
_initializeDebounceTimeTextSearchChange();
|
_initializeDebounceTimeTextSearchChange();
|
||||||
_initializeTextInputFocus();
|
_initializeTextInputFocus();
|
||||||
_initWorkerListener();
|
_initWorkerListener();
|
||||||
|
_initWebSocketQueueHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -165,8 +171,6 @@ class SearchEmailController extends BaseController
|
|||||||
searchMoreState = SearchMoreState.waiting;
|
searchMoreState = SearchMoreState.waiting;
|
||||||
} else if (success is SearchMoreEmailSuccess) {
|
} else if (success is SearchMoreEmailSuccess) {
|
||||||
_searchMoreEmailsSuccess(success);
|
_searchMoreEmailsSuccess(success);
|
||||||
} else if (success is RefreshChangesSearchEmailSuccess) {
|
|
||||||
_refreshChangesSearchEmailsSuccess(success);
|
|
||||||
} else if (success is SearchingState) {
|
} else if (success is SearchingState) {
|
||||||
resultSearchViewState.value = Right(success);
|
resultSearchViewState.value = Right(success);
|
||||||
}
|
}
|
||||||
@@ -252,40 +256,88 @@ class SearchEmailController extends BaseController
|
|||||||
mailboxDashBoardController.emailUIAction,
|
mailboxDashBoardController.emailUIAction,
|
||||||
(action) {
|
(action) {
|
||||||
if (action is RefreshChangeEmailAction) {
|
if (action is RefreshChangeEmailAction) {
|
||||||
_refreshEmailChanges();
|
_refreshEmailChanges(newState: action.newState);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _refreshEmailChanges({jmap.State? newState}) {
|
||||||
|
log('SearchEmailController::_refreshEmailChanges(): newState: $newState');
|
||||||
|
if (accountId == null ||
|
||||||
|
session == null ||
|
||||||
|
mailboxDashBoardController.currentEmailState == null ||
|
||||||
|
newState == null ||
|
||||||
|
searchIsRunning.isFalse) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_webSocketQueueHandler?.enqueue(WebSocketMessage(newState: newState));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initWebSocketQueueHandler() {
|
||||||
|
_webSocketQueueHandler = WebSocketQueueHandler(
|
||||||
|
processMessageCallback: _handleWebSocketMessage,
|
||||||
|
onErrorCallback: onError,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleWebSocketMessage(WebSocketMessage message) async {
|
||||||
|
try {
|
||||||
|
if (mailboxDashBoardController.currentEmailState == null ||
|
||||||
|
mailboxDashBoardController.currentEmailState == message.newState) {
|
||||||
|
log('SearchEmailController::_handleWebSocketMessage:Skipping redundant state: ${message.newState}');
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
final limit = listResultSearch.isNotEmpty
|
||||||
|
? UnsignedInt(listResultSearch.length)
|
||||||
|
: ThreadConstants.defaultLimit;
|
||||||
|
|
||||||
|
if (limit.value > ThreadConstants.maximumEmailQueryLimit &&
|
||||||
|
resultSearchScrollController.hasClients) {
|
||||||
|
resultSearchScrollController.jumpTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateSimpleSearchFilter(
|
||||||
|
beforeOption: const None(),
|
||||||
|
positionOption: option(searchEmailFilter.value.sortOrderType.isScrollByPosition(), 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
final searchViewState = await _refreshChangesSearchEmailInteractor.execute(
|
||||||
|
session!,
|
||||||
|
accountId!,
|
||||||
|
limit: limit,
|
||||||
|
position: searchEmailFilter.value.position,
|
||||||
|
sort: searchEmailFilter.value.sortOrderType.getSortOrder().toNullable(),
|
||||||
|
filter: searchEmailFilter.value.mappingToEmailFilterCondition(),
|
||||||
|
properties: EmailUtils.getPropertiesForEmailGetMethod(session!, accountId!),
|
||||||
|
).last;
|
||||||
|
|
||||||
|
final searchState = searchViewState
|
||||||
|
.foldSuccessWithResult<RefreshChangesSearchEmailSuccess>();
|
||||||
|
|
||||||
|
if (searchState is RefreshChangesSearchEmailSuccess) {
|
||||||
|
_handleRefreshChangesSearchEmailsSuccess(searchState);
|
||||||
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
logError('SearchEmailController::_handleWebSocketMessage:Error processing state: $e');
|
||||||
|
onError(e, stackTrace);
|
||||||
|
} finally {
|
||||||
|
if (mailboxDashBoardController.currentEmailState != null) {
|
||||||
|
_webSocketQueueHandler?.removeMessagesUpToCurrent(
|
||||||
|
mailboxDashBoardController.currentEmailState!.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onSearchTextInputListener() {
|
void _onSearchTextInputListener() {
|
||||||
if (textInputSearchFocus.hasFocus) {
|
if (textInputSearchFocus.hasFocus) {
|
||||||
searchIsRunning.value = false;
|
searchIsRunning.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _refreshEmailChanges() {
|
void _handleRefreshChangesSearchEmailsSuccess(RefreshChangesSearchEmailSuccess success) {
|
||||||
if (searchIsRunning.isTrue && session != null && accountId != null) {
|
|
||||||
final limit = listResultSearch.isNotEmpty
|
|
||||||
? UnsignedInt(listResultSearch.length)
|
|
||||||
: ThreadConstants.defaultLimit;
|
|
||||||
_updateSimpleSearchFilter(
|
|
||||||
beforeOption: const None(),
|
|
||||||
positionOption: option(searchEmailFilter.value.sortOrderType.isScrollByPosition(), 0)
|
|
||||||
);
|
|
||||||
consumeState(_refreshChangesSearchEmailInteractor.execute(
|
|
||||||
session!,
|
|
||||||
accountId!,
|
|
||||||
limit: limit,
|
|
||||||
position: searchEmailFilter.value.position,
|
|
||||||
sort: searchEmailFilter.value.sortOrderType.getSortOrder().toNullable(),
|
|
||||||
filter: searchEmailFilter.value.mappingToEmailFilterCondition(),
|
|
||||||
properties: EmailUtils.getPropertiesForEmailGetMethod(session!, accountId!),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _refreshChangesSearchEmailsSuccess(RefreshChangesSearchEmailSuccess success) {
|
|
||||||
final resultEmailSearchList = success.emailList
|
final resultEmailSearchList = success.emailList
|
||||||
.map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailboxById))
|
.map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailboxById))
|
||||||
.toList();
|
.toList();
|
||||||
@@ -306,8 +358,8 @@ class SearchEmailController extends BaseController
|
|||||||
return _getAllRecentSearchLatestInteractor
|
return _getAllRecentSearchLatestInteractor
|
||||||
.execute(pattern: pattern)
|
.execute(pattern: pattern)
|
||||||
.then((result) => result.fold(
|
.then((result) => result.fold(
|
||||||
(failure) => <RecentSearch>[],
|
(failure) => <RecentSearch>[],
|
||||||
(success) => success is GetAllRecentSearchLatestSuccess
|
(success) => success is GetAllRecentSearchLatestSuccess
|
||||||
? success.listRecentSearch
|
? success.listRecentSearch
|
||||||
: <RecentSearch>[]));
|
: <RecentSearch>[]));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,13 +136,13 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa
|
|||||||
currentMailboxState = success.currentMailboxState;
|
currentMailboxState = success.currentMailboxState;
|
||||||
await buildTree(success.mailboxList);
|
await buildTree(success.mailboxList);
|
||||||
if (currentContext != null) {
|
if (currentContext != null) {
|
||||||
await syncAllMailboxWithDisplayName(currentContext!);
|
syncAllMailboxWithDisplayName(currentContext!);
|
||||||
}
|
}
|
||||||
} else if (success is RefreshChangesAllMailboxSuccess) {
|
} else if (success is RefreshChangesAllMailboxSuccess) {
|
||||||
currentMailboxState = success.currentMailboxState;
|
currentMailboxState = success.currentMailboxState;
|
||||||
await refreshTree(success.mailboxList);
|
await refreshTree(success.mailboxList);
|
||||||
if (currentContext != null) {
|
if (currentContext != null) {
|
||||||
await syncAllMailboxWithDisplayName(currentContext!);
|
syncAllMailboxWithDisplayName(currentContext!);
|
||||||
}
|
}
|
||||||
searchMailboxAction();
|
searchMailboxAction();
|
||||||
} else if (success is SearchMailboxSuccess) {
|
} else if (success is SearchMailboxSuccess) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:model/email/email_property.dart';
|
|||||||
|
|
||||||
class ThreadConstants {
|
class ThreadConstants {
|
||||||
static const maxCountEmails = 20;
|
static const maxCountEmails = 20;
|
||||||
|
static const maximumEmailQueryLimit = 256;
|
||||||
static final defaultLimit = UnsignedInt(maxCountEmails);
|
static final defaultLimit = UnsignedInt(maxCountEmails);
|
||||||
static final propertiesDefault = Properties({
|
static final propertiesDefault = Properties({
|
||||||
EmailProperty.id,
|
EmailProperty.id,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:core/presentation/extensions/either_view_state_extension.dart';
|
||||||
import 'package:core/presentation/state/failure.dart';
|
import 'package:core/presentation/state/failure.dart';
|
||||||
import 'package:core/presentation/state/success.dart';
|
import 'package:core/presentation/state/success.dart';
|
||||||
import 'package:core/utils/app_logger.dart';
|
import 'package:core/utils/app_logger.dart';
|
||||||
@@ -32,6 +33,8 @@ import 'package:tmail_ui_user/features/manage_account/domain/state/create_new_ru
|
|||||||
import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new_email_rule_filter_interactor.dart';
|
import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new_email_rule_filter_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart'
|
import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart'
|
||||||
if (dart.library.html) 'package:tmail_ui_user/features/network_connection/presentation/web_network_connection_controller.dart';
|
if (dart.library.html) 'package:tmail_ui_user/features/network_connection/presentation/web_network_connection_controller.dart';
|
||||||
|
import 'package:tmail_ui_user/features/push_notification/presentation/websocket/web_socket_message.dart';
|
||||||
|
import 'package:tmail_ui_user/features/push_notification/presentation/websocket/web_socket_queue_handler.dart';
|
||||||
import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/rules_filter_creator_arguments.dart';
|
import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/rules_filter_creator_arguments.dart';
|
||||||
import 'package:tmail_ui_user/features/search/email/presentation/search_email_bindings.dart';
|
import 'package:tmail_ui_user/features/search/email/presentation/search_email_bindings.dart';
|
||||||
import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart';
|
import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart';
|
||||||
@@ -90,12 +93,12 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
bool canLoadMore = false;
|
bool canLoadMore = false;
|
||||||
bool canSearchMore = false;
|
bool canSearchMore = false;
|
||||||
MailboxId? _currentMemoryMailboxId;
|
MailboxId? _currentMemoryMailboxId;
|
||||||
jmap.State? _currentEmailState;
|
|
||||||
final ScrollController listEmailController = ScrollController();
|
final ScrollController listEmailController = ScrollController();
|
||||||
final FocusNode focusNodeKeyBoard = FocusNode();
|
final FocusNode focusNodeKeyBoard = FocusNode();
|
||||||
final latestEmailSelectedOrUnselected = Rxn<PresentationEmail>();
|
final latestEmailSelectedOrUnselected = Rxn<PresentationEmail>();
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
bool isListEmailScrollViewJumping = false;
|
bool isListEmailScrollViewJumping = false;
|
||||||
|
WebSocketQueueHandler? _webSocketQueueHandler;
|
||||||
|
|
||||||
StreamSubscription<html.Event>? _resizeBrowserStreamSubscription;
|
StreamSubscription<html.Event>? _resizeBrowserStreamSubscription;
|
||||||
|
|
||||||
@@ -128,6 +131,7 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
if (PlatformInfo.isWeb) {
|
if (PlatformInfo.isWeb) {
|
||||||
_registerBrowserResizeListener();
|
_registerBrowserResizeListener();
|
||||||
}
|
}
|
||||||
|
_initWebSocketQueueHandler();
|
||||||
super.onInit();
|
super.onInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +144,6 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
_currentMemoryMailboxId = null;
|
_currentMemoryMailboxId = null;
|
||||||
_currentEmailState = null;
|
|
||||||
listEmailController.dispose();
|
listEmailController.dispose();
|
||||||
focusNodeKeyBoard.dispose();
|
focusNodeKeyBoard.dispose();
|
||||||
if (PlatformInfo.isWeb) {
|
if (PlatformInfo.isWeb) {
|
||||||
@@ -154,8 +157,6 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
super.handleSuccessViewState(success);
|
super.handleSuccessViewState(success);
|
||||||
if (success is GetAllEmailSuccess) {
|
if (success is GetAllEmailSuccess) {
|
||||||
_getAllEmailSuccess(success);
|
_getAllEmailSuccess(success);
|
||||||
} else if (success is RefreshChangesAllEmailSuccess) {
|
|
||||||
_refreshChangesAllEmailSuccess(success);
|
|
||||||
} else if (success is LoadMoreEmailsSuccess) {
|
} else if (success is LoadMoreEmailsSuccess) {
|
||||||
_loadMoreEmailsSuccess(success);
|
_loadMoreEmailsSuccess(success);
|
||||||
} else if (success is SearchEmailSuccess) {
|
} else if (success is SearchEmailSuccess) {
|
||||||
@@ -231,6 +232,13 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _initWebSocketQueueHandler() {
|
||||||
|
_webSocketQueueHandler = WebSocketQueueHandler(
|
||||||
|
processMessageCallback: _handleWebSocketMessage,
|
||||||
|
onErrorCallback: onError,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _resetLoadingMore() {
|
void _resetLoadingMore() {
|
||||||
if (loadingMoreStatus.value == LoadingMoreStatus.running) {
|
if (loadingMoreStatus.value == LoadingMoreStatus.running) {
|
||||||
loadingMoreStatus.value = LoadingMoreStatus.idle;
|
loadingMoreStatus.value = LoadingMoreStatus.idle;
|
||||||
@@ -383,8 +391,8 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
log('ThreadController::_getAllEmailSuccess: GetAllForMailboxId = ${success.currentMailboxId?.asString} | SELECTED_MAILBOX_ID = ${selectedMailboxId?.asString} | SELECTED_MAILBOX_NAME = ${selectedMailbox?.name?.name}');
|
log('ThreadController::_getAllEmailSuccess: GetAllForMailboxId = ${success.currentMailboxId?.asString} | SELECTED_MAILBOX_ID = ${selectedMailboxId?.asString} | SELECTED_MAILBOX_NAME = ${selectedMailbox?.name?.name}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_currentEmailState = success.currentEmailState;
|
mailboxDashBoardController.setCurrentEmailState(success.currentEmailState);
|
||||||
log('ThreadController::_getAllEmailSuccess():COUNT = ${success.emailList.length} | EMAIL_STATE = $_currentEmailState');
|
log('ThreadController::_getAllEmailSuccess():COUNT = ${success.emailList.length} | EMAIL_STATE = ${mailboxDashBoardController.currentEmailState}');
|
||||||
final newListEmail = success.emailList.syncPresentationEmail(
|
final newListEmail = success.emailList.syncPresentationEmail(
|
||||||
mapMailboxById: mailboxDashBoardController.mapMailboxById,
|
mapMailboxById: mailboxDashBoardController.mapMailboxById,
|
||||||
selectedMailbox: selectedMailbox,
|
selectedMailbox: selectedMailbox,
|
||||||
@@ -413,8 +421,7 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
log('ThreadController::_refreshChangesAllEmailSuccess: RefreshedMailboxId = ${success.currentMailboxId?.asString} | SELECTED_MAILBOX_ID = ${selectedMailboxId?.asString} | SELECTED_MAILBOX_NAME = ${selectedMailbox?.name?.name}');
|
log('ThreadController::_refreshChangesAllEmailSuccess: RefreshedMailboxId = ${success.currentMailboxId?.asString} | SELECTED_MAILBOX_ID = ${selectedMailboxId?.asString} | SELECTED_MAILBOX_NAME = ${selectedMailbox?.name?.name}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
mailboxDashBoardController.setCurrentEmailState(success.currentEmailState);
|
||||||
_currentEmailState = success.currentEmailState;
|
|
||||||
log('ThreadController::_refreshChangesAllEmailSuccess: COUNT = ${success.emailList.length}');
|
log('ThreadController::_refreshChangesAllEmailSuccess: COUNT = ${success.emailList.length}');
|
||||||
final emailsBeforeChanges = mailboxDashBoardController.emailsInCurrentMailbox;
|
final emailsBeforeChanges = mailboxDashBoardController.emailsInCurrentMailbox;
|
||||||
final emailsAfterChanges = success.emailList;
|
final emailsAfterChanges = success.emailList;
|
||||||
@@ -515,31 +522,112 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
|
|
||||||
void _refreshEmailChanges({jmap.State? newState}) {
|
void _refreshEmailChanges({jmap.State? newState}) {
|
||||||
log('ThreadController::_refreshEmailChanges(): newState: $newState');
|
log('ThreadController::_refreshEmailChanges(): newState: $newState');
|
||||||
if (searchController.isSearchEmailRunning) {
|
if (_accountId == null ||
|
||||||
_searchEmail(limit: limitEmailFetched, needRefreshSearchState: true);
|
_session == null ||
|
||||||
} else {
|
mailboxDashBoardController.currentEmailState == null ||
|
||||||
if (_currentEmailState == null ||
|
newState == null) {
|
||||||
_currentEmailState == newState ||
|
return;
|
||||||
_session == null ||
|
}
|
||||||
_accountId == null) {
|
|
||||||
return;
|
_webSocketQueueHandler?.enqueue(WebSocketMessage(newState: newState));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleWebSocketMessage(WebSocketMessage message) async {
|
||||||
|
try {
|
||||||
|
if (mailboxDashBoardController.currentEmailState == null ||
|
||||||
|
mailboxDashBoardController.currentEmailState == message.newState) {
|
||||||
|
log('ThreadController::_handleWebSocketMessage:Skipping redundant state: ${message.newState}');
|
||||||
|
return Future.value();
|
||||||
}
|
}
|
||||||
consumeState(_refreshChangesEmailsInMailboxInteractor.execute(
|
|
||||||
|
if (searchController.isSearchEmailRunning) {
|
||||||
|
await _refreshChangeSearchEmail();
|
||||||
|
} else {
|
||||||
|
await _refreshChangeListEmail();
|
||||||
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
logError('ThreadController::_handleWebSocketMessage:Error processing state: $e');
|
||||||
|
onError(e, stackTrace);
|
||||||
|
} finally {
|
||||||
|
if (mailboxDashBoardController.currentEmailState != null) {
|
||||||
|
_webSocketQueueHandler?.removeMessagesUpToCurrent(
|
||||||
|
mailboxDashBoardController.currentEmailState!.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _refreshChangeSearchEmail() async {
|
||||||
|
log('ThreadController::_refreshChangeSearchEmail:');
|
||||||
|
if (limitEmailFetched.value > ThreadConstants.maximumEmailQueryLimit &&
|
||||||
|
listEmailController.hasClients) {
|
||||||
|
listEmailController.jumpTo(0);
|
||||||
|
}
|
||||||
|
canSearchMore = false;
|
||||||
|
searchController.updateFilterEmail(
|
||||||
|
positionOption: option(
|
||||||
|
_searchEmailFilter.sortOrderType.isScrollByPosition(),
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
beforeOption: const None(),
|
||||||
|
);
|
||||||
|
searchController.activateSimpleSearch();
|
||||||
|
|
||||||
|
final searchViewState = await _searchEmailInteractor.execute(
|
||||||
|
_session!,
|
||||||
|
_accountId!,
|
||||||
|
limit: limitEmailFetched,
|
||||||
|
position: _searchEmailFilter.position,
|
||||||
|
sort: _searchEmailFilter.sortOrderType.getSortOrder().toNullable(),
|
||||||
|
filter: _searchEmailFilter.mappingToEmailFilterCondition(
|
||||||
|
moreFilterCondition: _getFilterCondition(),
|
||||||
|
),
|
||||||
|
properties: EmailUtils.getPropertiesForEmailGetMethod(
|
||||||
_session!,
|
_session!,
|
||||||
_accountId!,
|
_accountId!,
|
||||||
_currentEmailState!,
|
),
|
||||||
sort: EmailSortOrderType.mostRecent.getSortOrder().toNullable(),
|
needRefreshSearchState: true,
|
||||||
propertiesCreated: EmailUtils.getPropertiesForEmailGetMethod(
|
).last;
|
||||||
_session!,
|
|
||||||
_accountId!,
|
final searchState = searchViewState
|
||||||
),
|
.foldSuccessWithResult<SearchEmailSuccess>();
|
||||||
propertiesUpdated: ThreadConstants.propertiesUpdatedDefault,
|
|
||||||
emailFilter: EmailFilter(
|
if (searchState is SearchEmailSuccess) {
|
||||||
filter: _getFilterCondition(mailboxIdSelected: selectedMailboxId),
|
_searchEmailsSuccess(searchState);
|
||||||
filterOption: mailboxDashBoardController.filterMessageOption.value,
|
} else {
|
||||||
mailboxId: selectedMailboxId,
|
mailboxDashBoardController.updateRefreshAllEmailState(
|
||||||
),
|
Left(RefreshAllEmailFailure()));
|
||||||
));
|
canSearchMore = false;
|
||||||
|
mailboxDashBoardController.emailsInCurrentMailbox.clear();
|
||||||
|
onDataFailureViewState(searchState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _refreshChangeListEmail() async {
|
||||||
|
log('ThreadController::_refreshChangeListEmail:');
|
||||||
|
final refreshViewState = await _refreshChangesEmailsInMailboxInteractor.execute(
|
||||||
|
_session!,
|
||||||
|
_accountId!,
|
||||||
|
mailboxDashBoardController.currentEmailState!,
|
||||||
|
sort: EmailSortOrderType.mostRecent.getSortOrder().toNullable(),
|
||||||
|
propertiesCreated: EmailUtils.getPropertiesForEmailGetMethod(
|
||||||
|
_session!,
|
||||||
|
_accountId!,
|
||||||
|
),
|
||||||
|
propertiesUpdated: ThreadConstants.propertiesUpdatedDefault,
|
||||||
|
emailFilter: EmailFilter(
|
||||||
|
filter: _getFilterCondition(mailboxIdSelected: selectedMailboxId),
|
||||||
|
filterOption: mailboxDashBoardController.filterMessageOption.value,
|
||||||
|
mailboxId: selectedMailboxId,
|
||||||
|
),
|
||||||
|
).last;
|
||||||
|
|
||||||
|
final refreshState = refreshViewState
|
||||||
|
.foldSuccessWithResult<RefreshChangesAllEmailSuccess>();
|
||||||
|
|
||||||
|
if (refreshState is RefreshChangesAllEmailSuccess) {
|
||||||
|
_refreshChangesAllEmailSuccess(refreshState);
|
||||||
|
} else {
|
||||||
|
onDataFailureViewState(refreshState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1168,12 +1256,10 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void goToCreateEmailRuleView() async {
|
void goToCreateEmailRuleView() async {
|
||||||
final accountId = mailboxDashBoardController.accountId.value;
|
if (_accountId != null && _session != null) {
|
||||||
final session = mailboxDashBoardController.sessionCurrent;
|
|
||||||
if (accountId != null && session != null) {
|
|
||||||
final arguments = RulesFilterCreatorArguments(
|
final arguments = RulesFilterCreatorArguments(
|
||||||
accountId,
|
_accountId!,
|
||||||
session,
|
_session!,
|
||||||
mailboxDestination: selectedMailbox
|
mailboxDestination: selectedMailbox
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1182,7 +1268,7 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
: await push(AppRoutes.rulesFilterCreator, arguments: arguments);
|
: await push(AppRoutes.rulesFilterCreator, arguments: arguments);
|
||||||
|
|
||||||
if (newRuleFilterRequest is CreateNewEmailRuleFilterRequest) {
|
if (newRuleFilterRequest is CreateNewEmailRuleFilterRequest) {
|
||||||
_createNewRuleFilterAction(accountId, newRuleFilterRequest);
|
_createNewRuleFilterAction(_accountId!, newRuleFilterRequest);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logError('ThreadController::goToCreateEmailRuleView: Account or Session is NULL');
|
logError('ThreadController::goToCreateEmailRuleView: Account or Session is NULL');
|
||||||
|
|||||||
+197
@@ -0,0 +1,197 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/state.dart';
|
||||||
|
import 'package:tmail_ui_user/features/push_notification/presentation/websocket/web_socket_message.dart';
|
||||||
|
import 'package:tmail_ui_user/features/push_notification/presentation/websocket/web_socket_queue_handler.dart';
|
||||||
|
|
||||||
|
class MockWebSocketMessage extends WebSocketMessage {
|
||||||
|
MockWebSocketMessage(String message)
|
||||||
|
: super(
|
||||||
|
newState: State(message),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('WebSocketQueueHandler::test', () {
|
||||||
|
late Queue<String> processedMessages;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
processedMessages = Queue<String>();
|
||||||
|
});
|
||||||
|
|
||||||
|
WebSocketQueueHandler createHandler({
|
||||||
|
required ProcessMessageCallback processMessageCallback,
|
||||||
|
OnErrorCallback? onErrorCallback,
|
||||||
|
}) {
|
||||||
|
return WebSocketQueueHandler(
|
||||||
|
processMessageCallback: processMessageCallback,
|
||||||
|
onErrorCallback: onErrorCallback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
group('Basic Operations', () {
|
||||||
|
late WebSocketQueueHandler handler;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
handler = createHandler(
|
||||||
|
processMessageCallback: (message) async {
|
||||||
|
processedMessages.add(message.id);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() => handler.dispose());
|
||||||
|
|
||||||
|
test('Should process messages in correct order', () async {
|
||||||
|
final messages = List.generate(5, (index) => MockWebSocketMessage('$index'));
|
||||||
|
|
||||||
|
for (var message in messages) {
|
||||||
|
handler.enqueue(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
await handler.waitForEmpty();
|
||||||
|
|
||||||
|
expect(processedMessages, containsAllInOrder(['0', '1', '2', '3', '4']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should correctly remove messages up to specified ID', () async {
|
||||||
|
final messages = List.generate(5, (index) => MockWebSocketMessage('$index'));
|
||||||
|
|
||||||
|
for (var message in messages) {
|
||||||
|
handler.enqueue(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.removeMessagesUpToCurrent('2');
|
||||||
|
|
||||||
|
expect(handler.queueSize, 2);
|
||||||
|
|
||||||
|
await handler.waitForEmpty();
|
||||||
|
|
||||||
|
expect(processedMessages.length, 2);
|
||||||
|
expect(processedMessages.first, '3');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Concurrent Operations', () {
|
||||||
|
late WebSocketQueueHandler handler;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
handler = createHandler(
|
||||||
|
processMessageCallback: (message) async {
|
||||||
|
processedMessages.add(message.id);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() => handler.dispose());
|
||||||
|
|
||||||
|
test('Should handle concurrent message enqueueing', () async {
|
||||||
|
final messages = List.generate(5, (index) => MockWebSocketMessage('$index'));
|
||||||
|
|
||||||
|
await Future.wait(messages.map((message) => Future(() => handler.enqueue(message))));
|
||||||
|
|
||||||
|
await handler.waitForEmpty();
|
||||||
|
|
||||||
|
expect(processedMessages, containsAllInOrder(['0', '1', '2', '3', '4']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should maintain order under high concurrency', () async {
|
||||||
|
final messages = List.generate(10, (index) => MockWebSocketMessage('$index'));
|
||||||
|
|
||||||
|
for (var message in messages) {
|
||||||
|
handler.enqueue(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
await handler.waitForEmpty();
|
||||||
|
|
||||||
|
expect(processedMessages, containsAllInOrder(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Error Handling', () {
|
||||||
|
test('Should continue processing after message failure', () async {
|
||||||
|
final handler = createHandler(
|
||||||
|
processMessageCallback: (message) async {
|
||||||
|
if (message.id == '2') throw Exception('Simulated Failure');
|
||||||
|
processedMessages.add(message.id);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
handler.enqueue(MockWebSocketMessage('1'));
|
||||||
|
handler.enqueue(MockWebSocketMessage('2'));
|
||||||
|
handler.enqueue(MockWebSocketMessage('3'));
|
||||||
|
|
||||||
|
await handler.waitForEmpty();
|
||||||
|
|
||||||
|
expect(processedMessages, ['1', '3']);
|
||||||
|
handler.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should handle exception in process callback', () async {
|
||||||
|
final handler = createHandler(
|
||||||
|
processMessageCallback: (message) async {
|
||||||
|
if (message.id == '1') throw Exception('Simulated Failure');
|
||||||
|
processedMessages.add(message.id);
|
||||||
|
},
|
||||||
|
onErrorCallback: (error, stackTrace) {
|
||||||
|
expect(error, isA<Exception>());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
handler.enqueue(MockWebSocketMessage('1'));
|
||||||
|
handler.enqueue(MockWebSocketMessage('2'));
|
||||||
|
|
||||||
|
await handler.waitForEmpty();
|
||||||
|
|
||||||
|
expect(processedMessages, ['2']);
|
||||||
|
handler.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Stress Testing', () {
|
||||||
|
late WebSocketQueueHandler handler;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
handler = createHandler(
|
||||||
|
processMessageCallback: (message) async {
|
||||||
|
processedMessages.add(message.id);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() => handler.dispose());
|
||||||
|
|
||||||
|
test('Should handle large bursts of messages', () async {
|
||||||
|
handler = createHandler(
|
||||||
|
processMessageCallback: (message) async {
|
||||||
|
processedMessages.add(message.id);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const int burstSize = 1000;
|
||||||
|
for (int i = 0; i < burstSize; i++) {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 10));
|
||||||
|
handler.enqueue(MockWebSocketMessage('$i'));
|
||||||
|
}
|
||||||
|
|
||||||
|
await handler.waitForEmpty();
|
||||||
|
|
||||||
|
expect(processedMessages.length, burstSize);
|
||||||
|
expect(processedMessages, List.generate(burstSize, (i) => '$i'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should handle interleaved slow and fast messages', () async {
|
||||||
|
handler.enqueue(MockWebSocketMessage('1'));
|
||||||
|
|
||||||
|
Future.delayed(const Duration(milliseconds: 10), () {
|
||||||
|
handler.enqueue(MockWebSocketMessage('2'));
|
||||||
|
});
|
||||||
|
|
||||||
|
await handler.waitForEmpty();
|
||||||
|
|
||||||
|
expect(processedMessages, ['1', '2']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -286,6 +286,7 @@ void main() {
|
|||||||
when(mockMailboxDashBoardController.emailUIAction).thenReturn(Rxn(null));
|
when(mockMailboxDashBoardController.emailUIAction).thenReturn(Rxn(null));
|
||||||
when(mockMailboxDashBoardController.viewState).thenReturn(Rx(Right(UIState.idle)));
|
when(mockMailboxDashBoardController.viewState).thenReturn(Rx(Right(UIState.idle)));
|
||||||
when(mockMailboxDashBoardController.filterMessageOption).thenReturn(Rx(FilterMessageOption.all));
|
when(mockMailboxDashBoardController.filterMessageOption).thenReturn(Rx(FilterMessageOption.all));
|
||||||
|
when(mockMailboxDashBoardController.currentEmailState).thenReturn(State('old-state'));
|
||||||
when(mockSearchController.searchState).thenReturn(Rx(SearchState.initial()));
|
when(mockSearchController.searchState).thenReturn(Rx(SearchState.initial()));
|
||||||
when(mockSearchController.isAdvancedSearchViewOpen).thenReturn(RxBool(false));
|
when(mockSearchController.isAdvancedSearchViewOpen).thenReturn(RxBool(false));
|
||||||
when(mockSearchController.isSearchEmailRunning).thenReturn(true);
|
when(mockSearchController.isSearchEmailRunning).thenReturn(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user