TF-3278 Handle open app via deep link at TwakeWelcome screen

This commit is contained in:
dab246
2024-11-19 04:17:59 +07:00
committed by Dat H. Pham
parent c6ed9f7c2b
commit 4817b31191
13 changed files with 385 additions and 189 deletions
@@ -18,7 +18,6 @@ import 'package:tmail_ui_user/features/caching/utils/local_storage_manager.dart'
import 'package:tmail_ui_user/features/caching/utils/session_storage_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/deep_links/deep_links_manager.dart';
import 'package:tmail_ui_user/main/utils/email_receive_manager.dart';
import 'package:tmail_ui_user/main/utils/ios_notification_manager.dart';
import 'package:tmail_ui_user/main/utils/toast_manager.dart';
@@ -76,9 +75,6 @@ class CoreBindings extends Bindings {
if (PlatformInfo.isIOS) {
Get.put(IOSNotificationManager());
}
if (PlatformInfo.isMobile) {
Get.put(DeepLinksManager());
}
Get.put(PreviewEmlFileUtils());
}
@@ -0,0 +1,20 @@
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/home/domain/usecases/auto_sign_in_via_deep_link_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart';
import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart';
import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart';
import 'package:tmail_ui_user/main/deep_links/deep_links_manager.dart';
class DeepLinkBindings extends Bindings {
@override
void dependencies() {
Get.put(AutoSignInViaDeepLinkInteractor(
Get.find<AuthenticationOIDCRepository>(),
Get.find<AccountRepository>(),
Get.find<CredentialRepository>(),
));
Get.put(DeepLinksManager(Get.find<AutoSignInViaDeepLinkInteractor>()));
}
}
+5
View File
@@ -1,6 +1,8 @@
import 'package:core/utils/platform_info.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/main/bindings/core/core_bindings.dart';
import 'package:tmail_ui_user/main/bindings/credential/credential_bindings.dart';
import 'package:tmail_ui_user/main/bindings/deep_link/deep_link_bindings.dart';
import 'package:tmail_ui_user/main/bindings/local/local_bindings.dart';
import 'package:tmail_ui_user/main/bindings/local/local_isolate_bindings.dart';
import 'package:tmail_ui_user/main/bindings/network/network_bindings.dart';
@@ -19,5 +21,8 @@ class MainBindings extends Bindings {
CredentialBindings().dependencies();
SessionBindings().dependencies();
NetWorkConnectionBindings().dependencies();
if (PlatformInfo.isMobile) {
DeepLinkBindings().dependencies();
}
}
}
@@ -0,0 +1,9 @@
import 'package:tmail_ui_user/features/home/domain/state/auto_sign_in_via_deep_link_state.dart';
import 'package:tmail_ui_user/main/deep_links/deep_link_data.dart';
typedef OnDeepLinkSuccessCallback = Function(AutoSignInViaDeepLinkSuccess success);
typedef OnDeepLinkFailureCallback = Function();
typedef OnDeepLinkConfirmLogoutCallback = Function(DeepLinkData deepLinkData);
+212 -1
View File
@@ -1,11 +1,33 @@
import 'dart:async';
import 'package:app_links/app_links.dart';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/utils/app_logger.dart';
import 'package:flutter/material.dart';
import 'package:model/oidc/oidc_configuration.dart';
import 'package:rxdart/subjects.dart';
import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_mixin.dart';
import 'package:tmail_ui_user/features/home/domain/state/auto_sign_in_via_deep_link_state.dart';
import 'package:tmail_ui_user/features/home/domain/usecases/auto_sign_in_via_deep_link_interactor.dart';
import 'package:tmail_ui_user/features/login/data/network/config/oidc_constant.dart';
import 'package:tmail_ui_user/main/deep_links/deep_link_action_define.dart';
import 'package:tmail_ui_user/main/deep_links/deep_link_data.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import 'package:tmail_ui_user/main/utils/app_config.dart';
class DeepLinksManager with MessageDialogActionMixin {
final AutoSignInViaDeepLinkInteractor _autoSignInViaDeepLinkInteractor;
BehaviorSubject<DeepLinkData?> _pendingDeepLinkData = BehaviorSubject.seeded(null);
BehaviorSubject<DeepLinkData?> get pendingDeepLinkData => _pendingDeepLinkData;
StreamSubscription<Uri>? _deepLinkStreamSubscription;
DeepLinksManager(this._autoSignInViaDeepLinkInteractor);
class DeepLinksManager {
Future<DeepLinkData?> getDeepLinkData() async {
final uriLink = await AppLinks().getInitialLink();
log('DeepLinksManager::getDeepLinkData:uriLink = $uriLink');
@@ -15,6 +37,30 @@ class DeepLinksManager {
return deepLinkData;
}
void registerDeepLinkStreamListener() {
_deepLinkStreamSubscription =
AppLinks().uriLinkStream.listen(_handleUriLinkStream);
}
void _handleUriLinkStream(Uri uri) {
final deepLinkData = parseDeepLink(uri.toString());
log('DeepLinksManager::_handleUriLinkStream:DeepLinkData = $deepLinkData');
setPendingDeepLinkData(deepLinkData);
}
void setPendingDeepLinkData(DeepLinkData? deepLinkData) {
clearPendingDeepLinkData();
_pendingDeepLinkData.add(deepLinkData);
}
void clearPendingDeepLinkData() {
if(_pendingDeepLinkData.isClosed) {
_pendingDeepLinkData = BehaviorSubject.seeded(null);
} else {
_pendingDeepLinkData.add(null);
}
}
DeepLinkData? parseDeepLink(String url) {
try {
final updatedUrl = url.replaceFirst(
@@ -22,6 +68,7 @@ class DeepLinksManager {
'https',
);
final uri = Uri.parse(updatedUrl);
log('DeepLinksManager::parseDeepLink:uri = $uri');
final action = uri.host;
final accessToken = uri.queryParameters['access_token'];
final refreshToken = uri.queryParameters['refresh_token'];
@@ -46,4 +93,168 @@ class DeepLinksManager {
return null;
}
}
Future<void> handleDeepLinksWhenAppOnForegroundNotSignedIn({
required DeepLinkData deepLinkData,
required OnDeepLinkSuccessCallback onSuccessCallback,
OnDeepLinkFailureCallback? onFailureCallback,
}) async {
log('DeepLinksManager::handleDeepLinksWhenAppOnForegroundNotSignedIn:DeepLinkData = $deepLinkData');
if (deepLinkData.action.toLowerCase() == AppConfig.openAppHostDeepLink.toLowerCase()) {
_handleOpenApp(
deepLinkData: deepLinkData,
onFailureCallback: onFailureCallback,
onSuccessCallback: onSuccessCallback,
);
} else {
onFailureCallback?.call();
}
}
Future<void> handleDeepLinksWhenAppTerminatedNotSignedIn({
required OnDeepLinkSuccessCallback onSuccessCallback,
required OnDeepLinkFailureCallback onFailureCallback,
}) async {
final deepLinkData = await getDeepLinkData();
log('DeepLinksManager::handleDeepLinksWhenAppTerminatedNotSignedIn:DeepLinkData = $deepLinkData');
if (deepLinkData == null) {
onFailureCallback();
return;
}
if (deepLinkData.action.toLowerCase() == AppConfig.openAppHostDeepLink.toLowerCase()) {
_handleOpenApp(
deepLinkData: deepLinkData,
onFailureCallback: onFailureCallback,
onSuccessCallback: onSuccessCallback,
);
} else {
onFailureCallback();
}
}
Future<void> handleDeepLinksWhenAppTerminatedSignedIn({
required String? username,
required OnDeepLinkConfirmLogoutCallback onConfirmCallback,
required OnDeepLinkFailureCallback onFailureCallback,
}) async {
final deepLinkData = await getDeepLinkData();
log('DeepLinksManager::handleDeepLinksWhenAppTerminatedSignedIn:DeepLinkData = $deepLinkData');
if (deepLinkData == null) {
onFailureCallback();
return;
}
if (deepLinkData.action.toLowerCase() == AppConfig.openAppHostDeepLink.toLowerCase()) {
if (deepLinkData.username?.isNotEmpty != true || username?.isNotEmpty != true) {
onFailureCallback();
return;
}
if (deepLinkData.username == username || currentContext == null) {
onFailureCallback();
} else {
_showConfirmDialogSwitchAccount(
context: currentContext!,
username: username!,
onConfirmAction: () => onConfirmCallback(deepLinkData),
onCancelAction: onFailureCallback,
);
}
} else {
onFailureCallback();
}
}
void _handleOpenApp({
required DeepLinkData deepLinkData,
required OnDeepLinkSuccessCallback onSuccessCallback,
OnDeepLinkFailureCallback? onFailureCallback,
}) {
autoSignInViaDeepLink(
deepLinkData: deepLinkData,
onFailureCallback: onFailureCallback,
onSuccessCallback: onSuccessCallback,
);
}
Future<void> autoSignInViaDeepLink({
required DeepLinkData deepLinkData,
required OnDeepLinkSuccessCallback onSuccessCallback,
OnDeepLinkFailureCallback? onFailureCallback,
}) async {
try {
if (deepLinkData.isValidToken()) {
final autoSignInViewState = await _autoSignInViaDeepLinkInteractor.execute(
baseUri: Uri.parse(AppConfig.saasJmapServerUrl),
tokenOIDC: deepLinkData.getTokenOIDC(),
oidcConfiguration: OIDCConfiguration(
authority: AppConfig.saasRegistrationUrl,
clientId: OIDCConstant.clientId,
scopes: AppConfig.oidcScopes,
),
).last;
autoSignInViewState.fold(
(failure) => onFailureCallback?.call(),
(success) {
if (success is AutoSignInViaDeepLinkSuccess) {
onSuccessCallback(success);
} else {
onFailureCallback?.call();
}
},
);
} else {
onFailureCallback?.call();
}
} catch (e) {
logError('DeepLinksManager::_autoSignInViaDeepLink:Exception = $e');
onFailureCallback?.call();
}
}
void _showConfirmDialogSwitchAccount({
required BuildContext context,
required String username,
required Function onConfirmAction,
required Function onCancelAction,
}) {
final appLocalizations = AppLocalizations.of(context);
showConfirmDialogAction(
context,
'',
appLocalizations.yesLogout,
title: appLocalizations.logoutConfirmation,
alignCenter: true,
outsideDismissible: false,
titleActionButtonMaxLines: 1,
titlePadding: const EdgeInsetsDirectional.only(start: 24, top: 24, end: 24, bottom: 12),
messageStyle: const TextStyle(
color: AppColor.colorTextBody,
fontSize: 15,
fontWeight: FontWeight.w400,
),
listTextSpan: [
TextSpan(text: appLocalizations.messageConfirmationLogout),
TextSpan(
text: ' $username',
style: const TextStyle(
color: AppColor.colorTextBody,
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
const TextSpan(text: ' ?'),
],
onConfirmAction: onConfirmAction,
onCancelAction: onCancelAction,
);
}
void dispose() {
_deepLinkStreamSubscription?.cancel();
_pendingDeepLinkData.close();
}
}
@@ -4315,13 +4315,6 @@ class AppLocalizations {
);
}
String get tokenInvalid {
return Intl.message(
'Token invalid',
name: 'tokenInvalid',
);
}
String get getHelpOrReportABug {
return Intl.message(
'Get help or report a bug',