Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TF-3278 Mobile handle corner cases of TWP redirection #3283

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,17 @@
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:scheme="twakemail.mobile"
android:host="openApp" />
</intent-filter>
</activity>

<activity
Expand Down
6 changes: 6 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
PODS:
- app_links (0.0.2):
- Flutter
- app_settings (5.1.1):
- Flutter
- AppAuth (1.7.4):
Expand Down Expand Up @@ -204,6 +206,7 @@ PODS:
- Flutter

DEPENDENCIES:
- app_links (from `.symlinks/plugins/app_links/ios`)
- app_settings (from `.symlinks/plugins/app_settings/ios`)
- better_open_file (from `.symlinks/plugins/better_open_file/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
Expand Down Expand Up @@ -264,6 +267,8 @@ SPEC REPOS:
- UniversalDetector2

EXTERNAL SOURCES:
app_links:
:path: ".symlinks/plugins/app_links/ios"
app_settings:
:path: ".symlinks/plugins/app_settings/ios"
better_open_file:
Expand Down Expand Up @@ -334,6 +339,7 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/workmanager/ios"

SPEC CHECKSUMS:
app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0
app_settings: 017320c6a680cdc94c799949d95b84cb69389ebc
AppAuth: 182c5b88630569df5acb672720534756c29b3358
better_open_file: 03cf320415d4d3f46b6e00adc4a567d76c1a399d
Expand Down
1 change: 1 addition & 0 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<array>
<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<string>teammail.mobile</string>
<string>twakemail.mobile</string>
<string>mailto</string>
</array>
</dict>
Expand Down
98 changes: 91 additions & 7 deletions lib/features/base/base_controller.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'dart:async';

import 'package:flutter/services.dart' as services;
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';
Expand Down Expand Up @@ -32,7 +32,9 @@ import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_mixin.da
import 'package:tmail_ui_user/features/base/mixin/popup_context_menu_action_mixin.dart';
import 'package:tmail_ui_user/features/caching/caching_manager.dart';
import 'package:tmail_ui_user/features/email/presentation/bindings/mdn_interactor_bindings.dart';
import 'package:tmail_ui_user/features/login/data/network/config/oidc_constant.dart';
import 'package:tmail_ui_user/features/login/data/network/interceptors/authorization_interceptors.dart';
import 'package:tmail_ui_user/features/login/domain/exceptions/logout_exception.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/delete_authority_oidc_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_interactor.dart';
import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart';
Expand Down Expand Up @@ -213,6 +215,8 @@ abstract class BaseController extends GetxController
clearDataAndGoToLoginPage();
}

void onCancelReconnectWhenSessionExpired() {}

void _handleConnectionErrorException() {
if (currentOverlayContext != null && currentContext != null) {
appToast.showToastErrorMessage(
Expand Down Expand Up @@ -251,7 +255,9 @@ abstract class BaseController extends GetxController
outsideDismissible: false,
titleActionButtonMaxLines: 1,
icon: SvgPicture.asset(imagePaths.icTMailLogo, width: 64, height: 64),
onConfirmAction: _executeBeforeReconnectAndLogOut);
onConfirmAction: _executeBeforeReconnectAndLogOut,
onCancelAction: onCancelReconnectWhenSessionExpired
);
} else if (PlatformInfo.isMobile) {
if (currentContext == null) {
clearDataAndGoToLoginPage();
Expand All @@ -267,7 +273,9 @@ abstract class BaseController extends GetxController
outsideDismissible: false,
titleActionButtonMaxLines: 1,
icon: SvgPicture.asset(imagePaths.icTMailLogo, width: 64, height: 64),
onConfirmAction: clearDataAndGoToLoginPage);
onConfirmAction: clearDataAndGoToLoginPage,
onCancelAction: onCancelReconnectWhenSessionExpired
);
}
}

Expand Down Expand Up @@ -452,6 +460,78 @@ abstract class BaseController extends GetxController
}
}

Future<void> logoutToSignInNewAccount({
required Session session,
required AccountId accountId,
required Function onSuccessCallback,
required Function({Object? exception}) onFailureCallback,
}) async {
try {
_isFcmEnabled = _isFcmActivated(session, accountId);

if (isAuthenticatedWithOidc) {
final logoutViewState = await logoutOidcInteractor.execute().last;

logoutViewState.fold(
(failure) async {
if (failure is LogoutOidcFailure && _validateUserCancelledLogoutOidcFlow(failure.exception)) {
await _handleDeleteFCMAndClearData();
onFailureCallback(exception: UserCancelledLogoutOIDCFlowException());
} else {
onFailureCallback();
}
},
(success) async {
if (success is LogoutOidcSuccess) {
await _handleDeleteFCMAndClearData();
onSuccessCallback();
} else {
onFailureCallback();
}
},
);
} else {
await _handleDeleteFCMAndClearData();
onSuccessCallback();
}
} catch (e) {
logError('BaseController::logoutToSignInNewAccount:Exception = $e');
onFailureCallback();
}
}

bool _validateUserCancelledLogoutOidcFlow(dynamic exception) {
return exception is services.PlatformException &&
exception.code == OIDCConstant.endSessionFailedCode;
}

Future<void> _handleDeleteFCMAndClearData() async {
await Future.wait([
if (_isFcmEnabled)
_handleDeleteFCMRegistration(),
clearAllData(),
]);
}

Future<void> _handleDeleteFCMRegistration() async {
try {
_getStoredFirebaseRegistrationInteractor = getBinding<GetStoredFirebaseRegistrationInteractor>();
final fcmRegistration = await _getStoredFirebaseRegistrationInteractor?.execute().last;

fcmRegistration?.fold(
(failure) => null,
(success) async {
if (success is GetStoredFirebaseRegistrationSuccess) {
_destroyFirebaseRegistrationInteractor = getBinding<DestroyFirebaseRegistrationInteractor>();
await _destroyFirebaseRegistrationInteractor?.execute(success.firebaseRegistration.id!).last;
}
},
);
} catch (e) {
logError('BaseController::_handleDeleteFCMRegistration:Exception = $e');
}
}

void _destroyFirebaseRegistration(FirebaseRegistrationId firebaseRegistrationId) async {
_destroyFirebaseRegistrationInteractor = getBinding<DestroyFirebaseRegistrationInteractor>();
if (_destroyFirebaseRegistrationInteractor != null) {
Expand Down Expand Up @@ -482,10 +562,14 @@ abstract class BaseController extends GetxController
}

Future<void> clearAllData() async {
if (isAuthenticatedWithOidc) {
await _clearOidcAuthData();
} else {
await _clearBasicAuthData();
try {
if (isAuthenticatedWithOidc) {
await _clearOidcAuthData();
} else {
await _clearBasicAuthData();
}
} catch (e) {
logError('BaseController::clearAllData:Exception = $e');
}
}

Expand Down
20 changes: 9 additions & 11 deletions lib/features/base/mixin/message_dialog_action_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,17 @@ mixin MessageDialogActionMixin {
PopInvokedCallback? onPopInvoked,
bool isArrangeActionButtonsVertical = false,
int? titleActionButtonMaxLines,
EdgeInsetsGeometry? titlePadding,
}
) async {
final responsiveUtils = Get.find<ResponsiveUtils>();
final imagePaths = Get.find<ImagePaths>();

final paddingTitle = titlePadding ??
(icon != null
? const EdgeInsetsDirectional.only(top: 24, start: 24, end: 24)
: const EdgeInsetsDirectional.symmetric(horizontal: 24));

if (alignCenter) {
final childWidget = PointerInterceptor(
child: (ConfirmDialogBuilder(
Expand All @@ -57,10 +63,7 @@ mixin MessageDialogActionMixin {
..colorConfirmButton(actionButtonColor ?? AppColor.colorTextButton)
..colorCancelButton(cancelButtonColor ?? AppColor.colorCancelButton)
..marginIcon(icon != null ? (marginIcon ?? const EdgeInsets.only(top: 24)) : null)
..paddingTitle(icon != null
? const EdgeInsetsDirectional.only(top: 24, start: 24, end: 24)
: const EdgeInsetsDirectional.symmetric(horizontal: 24)
)
..paddingTitle(paddingTitle)
..radiusButton(12)
..paddingButton(paddingButton)
..paddingContent(const EdgeInsets.only(left: 24, right: 24, bottom: 24, top: 12))
Expand Down Expand Up @@ -114,10 +117,7 @@ mixin MessageDialogActionMixin {
..widthDialog(responsiveUtils.getSizeScreenWidth(context))
..colorConfirmButton(actionButtonColor ?? AppColor.colorTextButton)
..colorCancelButton(cancelButtonColor ?? AppColor.colorCancelButton)
..paddingTitle(icon != null
? const EdgeInsetsDirectional.only(top: 24, start: 24, end: 24)
: const EdgeInsetsDirectional.symmetric(horizontal: 24)
)
..paddingTitle(paddingTitle)
..marginIcon(EdgeInsets.zero)
..paddingContent(const EdgeInsets.only(left: 44, right: 44, bottom: 24, top: 12))
..marginButton(hasCancelButton ? null : const EdgeInsets.only(bottom: 16, left: 44, right: 44))
Expand Down Expand Up @@ -194,9 +194,7 @@ mixin MessageDialogActionMixin {
..colorConfirmButton(actionButtonColor ?? AppColor.colorTextButton)
..colorCancelButton(cancelButtonColor ?? AppColor.colorCancelButton)
..marginIcon(icon != null ? const EdgeInsets.only(top: 24) : null)
..paddingTitle(icon != null
? const EdgeInsetsDirectional.only(top: 24, start: 24, end: 24)
: const EdgeInsetsDirectional.symmetric(horizontal: 24))
..paddingTitle(paddingTitle)
..marginIcon(EdgeInsets.zero)
..paddingContent(const EdgeInsets.only(left: 44, right: 44, bottom: 24, top: 12))
..marginButton(hasCancelButton ? null : const EdgeInsets.only(bottom: 16, left: 44, right: 44))
Expand Down
22 changes: 13 additions & 9 deletions lib/features/base/reloadable/reloadable_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,19 @@ import 'package:tmail_ui_user/main/error/capability_validator.dart';
import 'package:tmail_ui_user/main/exceptions/remote_exception.dart';

abstract class ReloadableController extends BaseController {
final GetSessionInteractor _getSessionInteractor = Get.find<GetSessionInteractor>();
final GetSessionInteractor getSessionInteractor = Get.find<GetSessionInteractor>();
final GetAuthenticatedAccountInteractor _getAuthenticatedAccountInteractor = Get.find<GetAuthenticatedAccountInteractor>();
final UpdateAccountCacheInteractor _updateAccountCacheInteractor = Get.find<UpdateAccountCacheInteractor>();

@override
void handleFailureViewState(Failure failure) {
if (failure is GetCredentialFailure ||
failure is GetStoredTokenOidcFailure ||
failure is GetAuthenticatedAccountFailure) {
if (isNotSignedIn(failure)) {
logError('$runtimeType::handleFailureViewState():Failure = $failure');
goToLogin();
} else if (failure is GetSessionFailure) {
logError('$runtimeType::handleFailureViewState():Failure = $failure');
handleGetSessionFailure(failure.exception);
} else if (failure is UpdateAccountCacheFailure) {
} else if (failure is UpdateAccountCacheFailure) {
logError('$runtimeType::handleFailureViewState():Failure = $failure');
_handleUpdateAccountCacheCompleted(
session: failure.session,
Expand Down Expand Up @@ -82,15 +80,15 @@ abstract class ReloadableController extends BaseController {
}

void _handleGetCredentialSuccess(GetCredentialViewState success) {
_setDataToInterceptors(
setDataToInterceptors(
baseUrl: success.baseUrl.toString(),
userName: success.userName,
password: success.password);
getSessionAction();
}

void _handleGetStoredTokenOidcSuccess(GetStoredTokenOidcSuccess success) {
_setDataToInterceptors(
setDataToInterceptors(
baseUrl: success.baseUrl.toString(),
tokenOIDC: success.tokenOidc,
oidcConfiguration: success.oidcConfiguration);
Expand All @@ -102,7 +100,7 @@ abstract class ReloadableController extends BaseController {
handleReloaded(session);
}

void _setDataToInterceptors({
void setDataToInterceptors({
required String baseUrl,
UserName? userName,
Password? password,
Expand All @@ -124,7 +122,7 @@ abstract class ReloadableController extends BaseController {
}

void getSessionAction() {
consumeState(_getSessionInteractor.execute());
consumeState(getSessionInteractor.execute());
}

void handleGetSessionFailure(GetSessionFailure failure) {
Expand Down Expand Up @@ -154,4 +152,10 @@ abstract class ReloadableController extends BaseController {
baseUrl: baseUrl
));
}

bool isNotSignedIn(Failure failure) {
return failure is GetCredentialFailure ||
failure is GetStoredTokenOidcFailure ||
failure is GetAuthenticatedAccountFailure;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:model/oidc/oidc_configuration.dart';
import 'package:model/oidc/token_oidc.dart';

class AutoSignInViaDeepLinkLoading extends LoadingState {}

class AutoSignInViaDeepLinkSuccess extends Success {
final TokenOIDC tokenOIDC;
final Uri baseUri;
final OIDCConfiguration oidcConfiguration;

AutoSignInViaDeepLinkSuccess(this.tokenOIDC, this.baseUri, this.oidcConfiguration);

@override
List<Object> get props => [tokenOIDC, baseUri, oidcConfiguration];
}

class AutoSignInViaDeepLinkFailure extends FeatureFailure {
AutoSignInViaDeepLinkFailure(dynamic exception) : super(exception: exception);
}
Loading
Loading