TF-2928 Show confirm dialog & handle exception when reload browser or loss of connection
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:contact/contact/model/capability_contact.dart';
|
||||
import 'package:core/data/network/config/dynamic_url_interceptors.dart';
|
||||
import 'package:core/domain/exceptions/platform_exception.dart';
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/resources/image_paths.dart';
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
@@ -60,6 +63,7 @@ import 'package:tmail_ui_user/main/routes/app_routes.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_utils.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
abstract class BaseController extends GetxController
|
||||
@@ -85,9 +89,42 @@ abstract class BaseController extends GetxController
|
||||
GetStoredFirebaseRegistrationInteractor? _getStoredFirebaseRegistrationInteractor;
|
||||
DestroyFirebaseRegistrationInteractor? _destroyFirebaseRegistrationInteractor;
|
||||
|
||||
StreamSubscription<html.Event>? _subscriptionBrowserOnBeforeUnload;
|
||||
StreamSubscription<html.Event>? _subscriptionBrowserOnUnload;
|
||||
|
||||
final viewState = Rx<Either<Failure, Success>>(Right(UIState.idle));
|
||||
FpsCallback? fpsCallback;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
if (PlatformInfo.isWeb) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_triggerBrowserReloadListener();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _triggerBrowserReloadListener() {
|
||||
_subscriptionBrowserOnBeforeUnload =
|
||||
html.window.onBeforeUnload.listen(handleBrowserBeforeReloadAction);
|
||||
_subscriptionBrowserOnUnload =
|
||||
html.window.onUnload.listen(handleBrowserReloadAction);
|
||||
}
|
||||
|
||||
Future<void> handleBrowserBeforeReloadAction(html.Event event) async {}
|
||||
|
||||
Future<void> handleBrowserReloadAction(html.Event event) async {}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
if (PlatformInfo.isWeb) {
|
||||
_subscriptionBrowserOnBeforeUnload?.cancel();
|
||||
_subscriptionBrowserOnUnload?.cancel();
|
||||
}
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
void consumeState(Stream<Either<Failure, Success>> newStateStream) async {
|
||||
newStateStream.listen(onData, onError: onError, onDone: onDone);
|
||||
}
|
||||
@@ -105,10 +142,9 @@ abstract class BaseController extends GetxController
|
||||
viewState.value.fold(
|
||||
(failure) {
|
||||
if (failure is FeatureFailure) {
|
||||
final exception = _performFilterExceptionInError(failure.exception);
|
||||
logError('$runtimeType::onData:exception: $exception');
|
||||
if (exception != null) {
|
||||
handleExceptionAction(failure: failure, exception: exception);
|
||||
final isUrgentException = _validateUrgentException(failure.exception);
|
||||
if (isUrgentException) {
|
||||
handleUrgentException(failure: failure, exception: failure.exception);
|
||||
} else {
|
||||
handleFailureViewState(failure);
|
||||
}
|
||||
@@ -119,11 +155,11 @@ abstract class BaseController extends GetxController
|
||||
handleSuccessViewState);
|
||||
}
|
||||
|
||||
void onError(Object error, StackTrace stackTrace) {
|
||||
logError('$runtimeType::onError():error: $error | stackTrace: $stackTrace');
|
||||
final exception = _performFilterExceptionInError(error);
|
||||
if (exception != null) {
|
||||
handleExceptionAction(exception: exception);
|
||||
void onError(dynamic error, StackTrace stackTrace) {
|
||||
logError('$runtimeType::onError():Error: $error | StackTrace: $stackTrace');
|
||||
final isUrgentException = _validateUrgentException(error);
|
||||
if (isUrgentException) {
|
||||
handleUrgentException(exception: error);
|
||||
} else {
|
||||
handleErrorViewState(error, stackTrace);
|
||||
}
|
||||
@@ -131,10 +167,41 @@ abstract class BaseController extends GetxController
|
||||
|
||||
void onDone() {}
|
||||
|
||||
Exception? _performFilterExceptionInError(dynamic error) {
|
||||
logError('$runtimeType::_performFilterExceptionInError(): $error');
|
||||
if (error is NoNetworkError || error is ConnectionTimeout || error is InternalServerError) {
|
||||
if (PlatformInfo.isWeb && currentOverlayContext != null && currentContext != null) {
|
||||
bool _validateUrgentException(dynamic exception) {
|
||||
return exception is NoNetworkError
|
||||
|| exception is BadCredentialsException
|
||||
|| exception is ConnectionError;
|
||||
}
|
||||
|
||||
void handleErrorViewState(Object error, StackTrace stackTrace) {}
|
||||
|
||||
void handleUrgentException({Failure? failure, Exception? exception}) {
|
||||
if (PlatformInfo.isWeb) {
|
||||
handleUrgentExceptionOnWeb(failure: failure, exception: exception);
|
||||
} else if (PlatformInfo.isMobile) {
|
||||
handleUrgentExceptionOnMobile(failure: failure, exception: exception);
|
||||
} else {
|
||||
throw NoSupportPlatformException();
|
||||
}
|
||||
}
|
||||
|
||||
void handleUrgentExceptionOnMobile({Failure? failure, Exception? exception}) {
|
||||
logError('$runtimeType::handleUrgentExceptionOnMobile():Failure: $failure | Exception: $exception');
|
||||
if (exception is ConnectionError) {
|
||||
if (currentOverlayContext != null && currentContext != null) {
|
||||
appToast.showToastErrorMessage(
|
||||
currentOverlayContext!,
|
||||
AppLocalizations.of(currentContext!).connectionError);
|
||||
}
|
||||
} else if (exception is BadCredentialsException) {
|
||||
_executeBeforeUnloadAndLogOut();
|
||||
}
|
||||
}
|
||||
|
||||
void handleUrgentExceptionOnWeb({Failure? failure, Exception? exception}) {
|
||||
logError('$runtimeType::handleUrgentExceptionOnWeb():Failure: $failure | Exception: $exception');
|
||||
if (exception is NoNetworkError) {
|
||||
if (currentOverlayContext != null && currentContext != null) {
|
||||
appToast.showToastMessage(
|
||||
currentOverlayContext!,
|
||||
AppLocalizations.of(currentContext!).no_internet_connection,
|
||||
@@ -143,43 +210,22 @@ abstract class BaseController extends GetxController
|
||||
leadingSVGIcon: imagePaths.icNotConnection,
|
||||
backgroundColor: AppColor.textFieldErrorBorderColor,
|
||||
textColor: Colors.white,
|
||||
infinityToast: true,
|
||||
);
|
||||
infinityToast: true,);
|
||||
}
|
||||
return error;
|
||||
} else if (error is BadCredentialsException || error is ConnectionError) {
|
||||
return error;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void handleErrorViewState(Object error, StackTrace stackTrace) {}
|
||||
|
||||
void handleExceptionAction({Failure? failure, Exception? exception}) {
|
||||
logError('$runtimeType::handleExceptionAction():failure: $failure | exception: $exception');
|
||||
if (exception is ConnectionError) {
|
||||
} else if (exception is ConnectionError) {
|
||||
if (currentOverlayContext != null && currentContext != null) {
|
||||
appToast.showToastErrorMessage(
|
||||
currentOverlayContext!,
|
||||
AppLocalizations.of(currentContext!).connectionError
|
||||
);
|
||||
AppLocalizations.of(currentContext!).connectionError);
|
||||
}
|
||||
} else if (exception is BadCredentialsException) {
|
||||
_executeBeforeUnloadAndLogOut();
|
||||
}
|
||||
|
||||
if (!authorizationInterceptors.isAppRunning) {
|
||||
return;
|
||||
}
|
||||
_executeBeforeUnloadAndLogOut(exception);
|
||||
}
|
||||
|
||||
Future<void> _executeBeforeUnloadAndLogOut(Exception? exception) async {
|
||||
if (exception is BadCredentialsException || exception is ConnectionError) {
|
||||
if (PlatformInfo.isWeb) {
|
||||
await executeBeforeUnload();
|
||||
}
|
||||
clearDataAndGoToLoginPage();
|
||||
}
|
||||
Future<void> _executeBeforeUnloadAndLogOut() async {
|
||||
await executeBeforeUnload();
|
||||
clearDataAndGoToLoginPage();
|
||||
}
|
||||
|
||||
void handleFailureViewState(Failure failure) async {
|
||||
|
||||
@@ -33,6 +33,7 @@ mixin MessageDialogActionMixin {
|
||||
Color? actionButtonColor,
|
||||
Color? cancelButtonColor,
|
||||
EdgeInsetsGeometry? marginIcon,
|
||||
EdgeInsetsGeometry? paddingButton,
|
||||
PopInvokedCallback? onPopInvoked,
|
||||
bool isArrangeActionButtonsVertical = false,
|
||||
int? titleActionButtonMaxLines,
|
||||
@@ -61,8 +62,9 @@ mixin MessageDialogActionMixin {
|
||||
: const EdgeInsetsDirectional.symmetric(horizontal: 24)
|
||||
)
|
||||
..radiusButton(12)
|
||||
..paddingButton(paddingButton)
|
||||
..paddingContent(const EdgeInsets.only(left: 24, right: 24, bottom: 24, top: 12))
|
||||
..paddingButton(hasCancelButton ? null : const EdgeInsets.only(bottom: 24, left: 24, right: 24))
|
||||
..marginButton(hasCancelButton ? null : const EdgeInsets.only(bottom: 24, left: 24, right: 24))
|
||||
..styleTitle(titleStyle ?? const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black))
|
||||
..styleContent(messageStyle ?? const TextStyle(fontSize: 14, fontWeight: FontWeight.normal, color: AppColor.colorContentEmail))
|
||||
..styleTextCancelButton(cancelStyle ?? const TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: AppColor.colorTextButton))
|
||||
@@ -107,6 +109,7 @@ mixin MessageDialogActionMixin {
|
||||
..title(title ?? '')
|
||||
..content(message)
|
||||
..addIcon(icon)
|
||||
..paddingButton(paddingButton)
|
||||
..margin(const EdgeInsets.only(bottom: 42))
|
||||
..widthDialog(responsiveUtils.getSizeScreenWidth(context))
|
||||
..colorConfirmButton(actionButtonColor ?? AppColor.colorTextButton)
|
||||
@@ -117,7 +120,7 @@ mixin MessageDialogActionMixin {
|
||||
)
|
||||
..marginIcon(EdgeInsets.zero)
|
||||
..paddingContent(const EdgeInsets.only(left: 44, right: 44, bottom: 24, top: 12))
|
||||
..paddingButton(hasCancelButton ? null : const EdgeInsets.only(bottom: 16, left: 44, right: 44))
|
||||
..marginButton(hasCancelButton ? null : const EdgeInsets.only(bottom: 16, left: 44, right: 44))
|
||||
..styleTitle(titleStyle ?? const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black))
|
||||
..styleContent(messageStyle ?? const TextStyle(fontSize: 14, fontWeight: FontWeight.normal, color: AppColor.colorContentEmail))
|
||||
..styleTextCancelButton(cancelStyle ?? const TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: AppColor.colorTextButton))
|
||||
@@ -187,6 +190,7 @@ mixin MessageDialogActionMixin {
|
||||
..title(title ?? '')
|
||||
..content(message)
|
||||
..addIcon(icon)
|
||||
..paddingButton(paddingButton)
|
||||
..colorConfirmButton(actionButtonColor ?? AppColor.colorTextButton)
|
||||
..colorCancelButton(cancelButtonColor ?? AppColor.colorCancelButton)
|
||||
..marginIcon(icon != null ? const EdgeInsets.only(top: 24) : null)
|
||||
@@ -195,7 +199,7 @@ mixin MessageDialogActionMixin {
|
||||
: const EdgeInsetsDirectional.symmetric(horizontal: 24))
|
||||
..marginIcon(EdgeInsets.zero)
|
||||
..paddingContent(const EdgeInsets.only(left: 44, right: 44, bottom: 24, top: 12))
|
||||
..paddingButton(hasCancelButton ? null : const EdgeInsets.only(bottom: 16, left: 44, right: 44))
|
||||
..marginButton(hasCancelButton ? null : const EdgeInsets.only(bottom: 16, left: 44, right: 44))
|
||||
..styleTitle(titleStyle ?? const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black))
|
||||
..styleContent(messageStyle ?? const TextStyle(fontSize: 14, fontWeight: FontWeight.normal, color: AppColor.colorContentEmail))
|
||||
..styleTextCancelButton(cancelStyle ?? const TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: AppColor.colorTextButton))
|
||||
|
||||
Reference in New Issue
Block a user