diff --git a/lib/features/base/base_controller.dart b/lib/features/base/base_controller.dart index eacf5b8cb..e44350eed 100644 --- a/lib/features/base/base_controller.dart +++ b/lib/features/base/base_controller.dart @@ -23,6 +23,7 @@ import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:model/account/authentication_type.dart'; import 'package:rule_filter/rule_filter/capability_rule_filter.dart'; +import 'package:tmail_ui_user/features/base/before_unload_manager.dart'; import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_mixin.dart'; import 'package:tmail_ui_user/features/base/mixin/popup_context_menu_action_mixin.dart'; import 'package:tmail_ui_user/features/caching/caching_manager.dart'; @@ -169,7 +170,14 @@ abstract class BaseController extends GetxController if (!authorizationInterceptors.isAppRunning) { return; } + _executeBeforeUnloadAndLogOut(exception); + } + + Future _executeBeforeUnloadAndLogOut(Exception? exception) async { if (exception is BadCredentialsException || exception is ConnectionError) { + if (PlatformInfo.isWeb) { + await executeBeforeUnload(); + } clearDataAndGoToLoginPage(); } } @@ -334,6 +342,11 @@ abstract class BaseController extends GetxController } } + Future executeBeforeUnload() async { + final beforeUnloadManager = getBinding(); + await beforeUnloadManager?.executeBeforeUnloadListeners(); + } + Future clearDataAndGoToLoginPage() async { log('BaseController::clearDataAndGoToLoginPage:'); await clearAllData(); diff --git a/lib/features/base/before_unload_handler.dart b/lib/features/base/before_unload_handler.dart new file mode 100644 index 000000000..2c4bdc110 --- /dev/null +++ b/lib/features/base/before_unload_handler.dart @@ -0,0 +1,3 @@ +abstract class BeforeUnloadHandler { + Future onBeforeUnload(); +} \ No newline at end of file diff --git a/lib/features/base/before_unload_manager.dart b/lib/features/base/before_unload_manager.dart new file mode 100644 index 000000000..6b45ffad4 --- /dev/null +++ b/lib/features/base/before_unload_manager.dart @@ -0,0 +1,24 @@ +import 'package:core/utils/app_logger.dart'; + +typedef BeforeUnloadListener = Future Function(); + +class BeforeUnloadManager { + static final BeforeUnloadManager _instance = BeforeUnloadManager._(); + factory BeforeUnloadManager() => _instance; + BeforeUnloadManager._(); + + final _listeners = []; + + void addListener(BeforeUnloadListener listener) { + _listeners.add(listener); + } + + void removeListener(BeforeUnloadListener listener) { + _listeners.remove(listener); + } + + Future executeBeforeUnloadListeners() async { + await Future.wait(_listeners.map((listener) => listener.call())) + .onError((error, stackTrace) => [logError(error.toString())]); + } +} \ No newline at end of file diff --git a/lib/main/bindings/core/core_bindings.dart b/lib/main/bindings/core/core_bindings.dart index fea26aee3..5feaf5f03 100644 --- a/lib/main/bindings/core/core_bindings.dart +++ b/lib/main/bindings/core/core_bindings.dart @@ -12,6 +12,7 @@ import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:tmail_ui_user/features/base/before_unload_manager.dart'; import 'package:tmail_ui_user/features/sending_queue/presentation/utils/sending_queue_isolate_manager.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart'; import 'package:tmail_ui_user/main/utils/email_receive_manager.dart'; @@ -64,6 +65,7 @@ class CoreBindings extends Bindings { Get.put(FileUtils()); Get.put(PrintUtils()); Get.put(ApplicationManager(Get.find())); + Get.put(BeforeUnloadManager()); } void _bindingIsolate() { diff --git a/test/features/base/before_unload_manager_test.dart b/test/features/base/before_unload_manager_test.dart new file mode 100644 index 000000000..d5c4fb1d3 --- /dev/null +++ b/test/features/base/before_unload_manager_test.dart @@ -0,0 +1,96 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tmail_ui_user/features/base/before_unload_manager.dart'; + +void main() { + + group('before unload manager test:', () { + test( + 'should finish all futures ' + 'when no future fails', + () async { + // arrange + List result = []; + future1Second() => Future.delayed(const Duration(seconds: 1), () => result.add(1)); + future2Second() => Future.delayed(const Duration(seconds: 2), () => result.add(2)); + future3Second() => Future.delayed(const Duration(seconds: 3), () => result.add(3)); + final beforeUnloadManager = BeforeUnloadManager(); + beforeUnloadManager.addListener(future1Second); + beforeUnloadManager.addListener(future2Second); + beforeUnloadManager.addListener(future3Second); + + // act + await beforeUnloadManager.executeBeforeUnloadListeners(); + + // assert + expect(result.length, 3); + }); + + test( + 'should finish all possible futures ' + 'when one future fail', + () async { + // arrange + List result = []; + final exception = Exception('error'); + future1Second() => Future.delayed(const Duration(seconds: 1), () => result.add(1)); + future2Second() => Future.delayed(const Duration(seconds: 2), () => throw exception); + future3Second() => Future.delayed(const Duration(seconds: 3), () => result.add(3)); + final beforeUnloadManager = BeforeUnloadManager(); + beforeUnloadManager.addListener(future1Second); + beforeUnloadManager.addListener(future2Second); + beforeUnloadManager.addListener(future3Second); + + // act + await beforeUnloadManager.executeBeforeUnloadListeners(); + + // assert + expect(result.length, 2); + }); + + test( + 'should finish all possible futures ' + 'when more than one future fail', + () async { + // arrange + List result = []; + final exception1 = Exception('error 1'); + final exception2 = Exception('error 2'); + future1Second() => Future.delayed(const Duration(seconds: 1), () => throw exception1); + future2Second() => Future.delayed(const Duration(seconds: 2), () => throw exception2); + future3Second() => Future.delayed(const Duration(seconds: 3), () => result.add(3)); + final beforeUnloadManager = BeforeUnloadManager(); + beforeUnloadManager.addListener(future1Second); + beforeUnloadManager.addListener(future2Second); + beforeUnloadManager.addListener(future3Second); + + // act + await beforeUnloadManager.executeBeforeUnloadListeners(); + + // assert + expect(result.length, 1); + }); + + test( + 'should finish all possible futures ' + 'when more than one future fail', + () async { + // arrange + List result = []; + final exception3 = Exception('error 3'); + final exception2 = Exception('error 2'); + future1Second() => Future.delayed(const Duration(seconds: 1), () => result.add(1)); + future2Second() => Future.delayed(const Duration(seconds: 2), () => throw exception2); + future3Second() => Future.delayed(const Duration(seconds: 3), () => throw exception3); + final beforeUnloadManager = BeforeUnloadManager(); + beforeUnloadManager.addListener(future1Second); + beforeUnloadManager.addListener(future2Second); + beforeUnloadManager.addListener(future3Second); + + // act + await beforeUnloadManager.executeBeforeUnloadListeners(); + + // assert + expect(result.length, 1); + }); + }); +} \ No newline at end of file