TF-2271 Implement dns lookup to get jmap url

Signed-off-by: dab246 <tdvu@linagora.com>
(cherry picked from commit 7b0c0015a15a243b6b4aef3d78e7653d556ad109)
This commit is contained in:
dab246
2023-11-10 17:06:50 +07:00
committed by Dat Vu
parent c0bef69435
commit 9b661b0688
33 changed files with 773 additions and 483 deletions
+1 -1
View File
@@ -105,7 +105,7 @@ class FileUtils {
log('FileUtils::removeFolder():folderPath: $folderPath');
final dir = Directory(folderPath);
if (await dir.exists()) {
await dir.delete(recursive: true);
await dir.delete(recursive: true);
}
} catch (e) {
logError('FileUtils::removeFolder():EXCEPTION: $e');
+27 -21
View File
@@ -141,21 +141,7 @@ abstract class BaseController extends GetxController
);
}
return error;
} else if (error is BadCredentialsException) {
if (currentOverlayContext != null && currentContext != null) {
appToast.showToastErrorMessage(
currentOverlayContext!,
AppLocalizations.of(currentContext!).badCredentials
);
}
return error;
} else if (error is ConnectionError) {
if (authorizationInterceptors.isAppRunning && currentOverlayContext != null && currentContext != null) {
appToast.showToastErrorMessage(
currentOverlayContext!,
AppLocalizations.of(currentContext!).connectionError
);
}
} else if (error is BadCredentialsException || error is ConnectionError) {
return error;
}
@@ -166,6 +152,22 @@ abstract class BaseController extends GetxController
void handleExceptionAction({Failure? failure, Exception? exception}) {
logError('BaseController::handleExceptionAction():failure: $failure | exception: $exception');
if (exception is BadCredentialsException) {
if (currentOverlayContext != null && currentContext != null) {
appToast.showToastErrorMessage(
currentOverlayContext!,
AppLocalizations.of(currentContext!).badCredentials
);
}
} else if (exception is ConnectionError) {
if (currentOverlayContext != null && currentContext != null) {
appToast.showToastErrorMessage(
currentOverlayContext!,
AppLocalizations.of(currentContext!).connectionError
);
}
}
if (!authorizationInterceptors.isAppRunning) {
return;
}
@@ -182,7 +184,7 @@ abstract class BaseController extends GetxController
} else {
await clearDataAndGoToLoginPage();
}
} else if (failure is GetFCMSubscriptionLocalFailure ||
} else if (failure is GetFCMSubscriptionLocalFailure ||
failure is DestroySubscriptionFailure) {
await clearDataAndGoToLoginPage();
}
@@ -326,9 +328,9 @@ abstract class BaseController extends GetxController
log('BaseController::clearDataAndGoToLoginPage:');
await clearAllData();
goToLogin(arguments: LoginArguments(
isAuthenticatedWithOidc
? LoginFormType.ssoForm
: LoginFormType.credentialForm
PlatformInfo.isWeb
? LoginFormType.none
: LoginFormType.dnsLookupForm
));
}
@@ -346,7 +348,9 @@ abstract class BaseController extends GetxController
cachingManager.clearAll(),
languageCacheManager.removeLanguage(),
]);
await cachingManager.clearAllFileInStorage();
if (PlatformInfo.isMobile) {
await cachingManager.clearAllFileInStorage();
}
authorizationInterceptors.clear();
authorizationIsolateInterceptors.clear();
if (_isFcmEnabled) {
@@ -361,7 +365,9 @@ abstract class BaseController extends GetxController
cachingManager.clearAll(),
languageCacheManager.removeLanguage(),
]);
await cachingManager.clearAllFileInStorage();
if (PlatformInfo.isMobile) {
await cachingManager.clearAllFileInStorage();
}
authorizationIsolateInterceptors.clear();
authorizationInterceptors.clear();
if (_isFcmEnabled) {
@@ -1,7 +1,7 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/utils/app_logger.dart';
import 'package:flutter/cupertino.dart';
import 'package:core/utils/platform_info.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart';
@@ -10,11 +10,9 @@ import 'package:jmap_dart_client/jmap/core/user_name.dart';
import 'package:model/extensions/session_extension.dart';
import 'package:model/oidc/token_oidc.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart';
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
import 'package:tmail_ui_user/features/home/domain/state/get_session_state.dart';
import 'package:tmail_ui_user/features/home/domain/usecases/get_session_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_authenticated_account_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_credential_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_stored_token_oidc_state.dart';
@@ -24,9 +22,9 @@ import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart';
import 'package:tmail_ui_user/features/login/presentation/model/login_arguments.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/vacation/vacation_interactors_bindings.dart';
import 'package:tmail_ui_user/main/error/capability_validator.dart';
import 'package:tmail_ui_user/main/exceptions/remote_exception.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/message_toast_utils.dart';
abstract class ReloadableController extends BaseController {
final GetSessionInteractor _getSessionInteractor = Get.find<GetSessionInteractor>();
@@ -37,16 +35,19 @@ abstract class ReloadableController extends BaseController {
void handleFailureViewState(Failure failure) {
super.handleFailureViewState(failure);
if (failure is GetCredentialFailure) {
goToLogin(arguments: LoginArguments(LoginFormType.credentialForm));
goToLogin(arguments: LoginArguments(LoginFormType.dnsLookupForm));
} else if (failure is GetSessionFailure) {
_handleGetSessionFailure(
sessionCacheException: failure.exception,
sessionRemoteException: failure.remoteException
);
_handleGetSessionFailure(failure.exception);
} else if (failure is GetStoredTokenOidcFailure) {
goToLogin(arguments: LoginArguments(LoginFormType.none));
} else if (failure is GetAuthenticatedAccountFailure) {
goToLogin(arguments: LoginArguments(LoginFormType.credentialForm));
goToLogin(
arguments: LoginArguments(
PlatformInfo.isMobile
? LoginFormType.dnsLookupForm
: LoginFormType.none
)
);
}
}
@@ -95,56 +96,16 @@ abstract class ReloadableController extends BaseController {
consumeState(_getSessionInteractor.execute());
}
void _handleGetSessionFailure({
dynamic sessionRemoteException,
dynamic sessionCacheException
}) {
showToastErrorMessageSessionFailure(
sessionRemoteException: sessionRemoteException,
sessionCacheException: sessionCacheException,
);
void _handleGetSessionFailure(dynamic exception) {
if (currentContext != null || currentOverlayContext != null) {
appToast.showToastErrorMessage(
currentOverlayContext!,
MessageToastUtils.getMessageByException(currentContext!, exception) ?? AppLocalizations.of(currentContext!).unknownError
);
}
clearDataAndGoToLoginPage();
}
void showToastErrorMessageSessionFailure({
dynamic sessionRemoteException,
dynamic sessionCacheException
}) {
if (currentContext == null || currentOverlayContext == null) {
return;
}
appToast.showToastErrorMessage(
currentOverlayContext!,
getErrorMessageFromSessionFailure(
context: currentContext!,
sessionRemoteException: sessionRemoteException,
sessionCacheException: sessionCacheException,
)
);
}
String getErrorMessageFromSessionFailure({
required BuildContext context,
dynamic sessionRemoteException,
dynamic sessionCacheException,
}) {
if (sessionRemoteException is ConnectionTimeout ||
sessionRemoteException is BadGateway ||
sessionRemoteException is SocketError) {
return AppLocalizations.of(context).wrongUrlMessage;
} else if (sessionRemoteException is BadCredentialsException) {
return AppLocalizations.of(context).badCredentials;
} else if (sessionRemoteException is ConnectionError) {
return AppLocalizations.of(context).connectionError;
} else if (sessionRemoteException is UnknownError && sessionRemoteException.message != null) {
return '[${sessionRemoteException.code ?? ''}] ${sessionRemoteException.message}';
} else if (sessionCacheException is NotFoundSessionException) {
return AppLocalizations.of(context).notFoundSession;
} else {
return AppLocalizations.of(context).unknownError;
}
}
void _handleGetSessionSuccess(GetSessionSuccess success) {
final session = success.session;
final personalAccount = session.personalAccount;
@@ -2,6 +2,7 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/login/domain/model/recent_login_url.dart';
import 'package:tmail_ui_user/features/login/domain/model/recent_login_username.dart';
@@ -9,13 +10,15 @@ class RecentItemTileWidget<T> extends StatelessWidget {
final T item;
final EdgeInsets? contentPadding;
final ImagePaths imagePath;
final _imagePath = Get.find<ImagePaths>();
const RecentItemTileWidget(this.item, {
required this.imagePath,
RecentItemTileWidget(
this.item,
{
Key? key,
this.contentPadding,
}) : super(key: key);
}
) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -23,14 +26,17 @@ class RecentItemTileWidget<T> extends StatelessWidget {
padding: contentPadding ?? const EdgeInsets.all(12),
child: Row(
children: [
SvgPicture.asset(imagePath.icClockSB),
SvgPicture.asset(_imagePath.icClockSB),
const SizedBox(width: 8),
Expanded(
child: Text(_getTitle(item),
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.normal,
color: Colors.black)),
child: Text(
_getTitle(item),
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.normal,
color: Colors.black
)
),
),
],
),
+4 -6
View File
@@ -129,12 +129,10 @@ class CachingManager {
}
Future<void> clearAllFileInStorage() async {
if (PlatformInfo.isMobile) {
await Future.wait([
_fileUtils.removeFolder(CachingConstants.newEmailsContentFolderName),
_fileUtils.removeFolder(CachingConstants.openedEmailContentFolderName),
]);
}
await Future.wait([
_fileUtils.removeFolder(CachingConstants.newEmailsContentFolderName),
_fileUtils.removeFolder(CachingConstants.openedEmailContentFolderName),
]);
}
Future<void> _clearSendingEmailCache() async {
@@ -14,7 +14,6 @@ class GetSessionSuccess extends UIState {
}
class GetSessionFailure extends FeatureFailure {
final dynamic remoteException;
GetSessionFailure({dynamic exception, this.remoteException}) : super(exception: exception);
GetSessionFailure(dynamic exception) : super(exception: exception);
}
@@ -20,22 +20,19 @@ class GetSessionInteractor {
if (PlatformInfo.isMobile) {
yield* _getStoredSessionFromCache(remoteException: e);
} else {
yield Left<Failure, Success>(GetSessionFailure(exception: e));
yield Left<Failure, Success>(GetSessionFailure(e));
}
}
}
Stream<Either<Failure, Success>> _getStoredSessionFromCache({dynamic remoteException}) async* {
try {
log('GetSessionInteractor::_getStoredSessionFromCache:');
log('GetSessionInteractor::_getStoredSessionFromCache:remoteException: $remoteException');
yield Right<Failure, Success>(GetSessionLoading());
final session = await sessionRepository.getStoredSession();
yield Right<Failure, Success>(GetSessionSuccess(session));
} catch (e) {
yield Left<Failure, Success>(GetSessionFailure(
exception: e,
remoteException: remoteException
));
yield Left<Failure, Success>(GetSessionFailure(remoteException ?? e));
}
}
}
@@ -46,6 +46,9 @@ class DNSService {
}
final url = _removeDotAtEndOfString(listFieldData.last);
log('DNSService::_parsingUrlFromDataResourceRecord:url: $url');
if (url.trim().isEmpty) {
throw NotFoundUrlException();
}
return url;
}
@@ -1,13 +1,11 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/utils/app_logger.dart';
import 'package:dartz/dartz.dart';
import 'package:jmap_dart_client/jmap/core/user_name.dart';
import 'package:model/account/authentication_type.dart';
import 'package:model/account/password.dart';
import 'package:model/account/personal_account.dart';
import 'package:tmail_ui_user/features/login/data/model/authentication_info_cache.dart';
import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart';
import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart';
import 'package:tmail_ui_user/features/login/domain/repository/authentication_repository.dart';
import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart';
@@ -24,33 +22,25 @@ class AuthenticationInteractor {
this._accountRepository
);
Stream<Either<Failure, Success>> execute({Uri? baseUrl, UserName? userName, Password? password}) async* {
Stream<Either<Failure, Success>> execute({
required Uri baseUrl,
required UserName userName,
required Password password
}) async* {
try {
yield Right(AuthenticationUserLoading());
if (baseUrl != null && userName != null && password != null) {
final user = await authenticationRepository.authenticationUser(baseUrl, userName, password);
await Future.wait([
credentialRepository.saveBaseUrl(baseUrl),
credentialRepository.storeAuthenticationInfo(AuthenticationInfoCache(userName.value, password.value)),
_accountRepository.setCurrentAccount(PersonalAccount(
userName.value,
AuthenticationType.basic,
isSelected: true
))
]);
yield Right(AuthenticationUserSuccess(user));
} else if (baseUrl == null) {
yield Left(AuthenticationUserFailure(CanNotFoundBaseUrl()));
} else if (userName == null) {
yield Left(AuthenticationUserFailure(CanNotFoundUserName()));
} else if (password == null) {
yield Left(AuthenticationUserFailure(CanNotFoundPassword()));
} else {
yield Left(AuthenticationUserFailure(null));
}
final user = await authenticationRepository.authenticationUser(baseUrl, userName, password);
await Future.wait([
credentialRepository.saveBaseUrl(baseUrl),
credentialRepository.storeAuthenticationInfo(AuthenticationInfoCache(userName.value, password.value)),
_accountRepository.setCurrentAccount(PersonalAccount(
userName.value,
AuthenticationType.basic,
isSelected: true
))
]);
yield Right(AuthenticationUserSuccess(user));
} catch (e) {
logError('AuthenticationInteractor::execute(): $e');
yield Left(AuthenticationUserFailure(e));
}
}
@@ -1,24 +1,11 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/presentation/views/text/type_ahead_form_field_builder.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/widget/recent_item_tile_widget.dart';
import 'package:tmail_ui_user/features/home/domain/state/get_session_state.dart';
import 'package:tmail_ui_user/features/login/data/network/oidc_error.dart';
import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart';
import 'package:tmail_ui_user/features/login/domain/model/recent_login_username.dart';
import 'package:tmail_ui_user/features/login/domain/state/authenticate_oidc_on_browser_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/authentication_user_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/check_oidc_is_available_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_oidc_configuration_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_oidc_is_available_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_token_oidc_state.dart';
import 'package:tmail_ui_user/features/login/presentation/login_controller.dart';
import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart';
import 'package:tmail_ui_user/features/login/presentation/widgets/login_input_decoration_builder.dart';
import 'package:tmail_ui_user/features/login/presentation/widgets/login_text_input_builder.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
@@ -26,85 +13,20 @@ import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
abstract class BaseLoginView extends GetWidget<LoginController> {
const BaseLoginView({Key? key}) : super(key: key);
Widget buildLoginMessage(BuildContext context, Either<Failure, Success> viewState) {
return Padding(
padding: const EdgeInsets.only(top: 11, bottom: 36, left: 58, right: 58),
child: SizedBox(
width: controller.responsiveUtils.getWidthLoginTextField(context),
child: Text(
viewState.fold(
(failure) {
if (failure is CheckOIDCIsAvailableFailure) {
return _getMessageFailure(context, failure.exception);
} else if (failure is AuthenticationUserFailure) {
return _getMessageFailure(context, failure.exception);
} else if (failure is GetOIDCIsAvailableFailure) {
return _getMessageFailure(context, failure.exception);
} else if (failure is GetTokenOIDCFailure) {
return _getMessageFailure(context, failure.exception);
} else if (failure is AuthenticateOidcOnBrowserFailure) {
return _getMessageFailure(context, failure.exception);
} else if (failure is GetOIDCConfigurationFailure) {
return AppLocalizations.of(context).canNotVerifySSOConfiguration;
} else if (failure is GetSessionFailure) {
return controller.getErrorMessageFromSessionFailure(
context: context,
sessionRemoteException: failure.remoteException,
sessionCacheException: failure.exception
);
} else {
return AppLocalizations.of(context).unknownError;
}
},
(success) {
if (controller.loginFormType.value == LoginFormType.credentialForm) {
return AppLocalizations.of(context).loginInputCredentialMessage;
} else if (controller.loginFormType.value == LoginFormType.ssoForm) {
return AppLocalizations.of(context).loginInputSSOMessage;
}
return AppLocalizations.of(context).loginInputUrlMessage;
}),
key: const Key('login_message'),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w400,
color: viewState.fold(
(failure) => AppColor.textFieldErrorBorderColor,
(success) => AppColor.colorNameEmail)),
))
);
}
String _getMessageFailure(BuildContext context, dynamic exception) {
if (exception is CanNotFoundBaseUrl) {
return AppLocalizations.of(context).requiredUrl;
} else if (exception is CanNotFoundUserName) {
return AppLocalizations.of(context).requiredEmail;
} else if (exception is CanNotFoundPassword) {
return AppLocalizations.of(context).requiredPassword;
} else if (exception is CanNotFoundOIDCLinks) {
return AppLocalizations.of(context).ssoNotAvailable;
} else if (exception is CanNotFoundToken) {
return AppLocalizations.of(context).canNotGetToken;
} else {
return '';
}
}
Widget buildLoginButton(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 16, left: 24, right: 24),
width: controller.responsiveUtils.getDeviceWidth(context),height: 48,
margin: const EdgeInsetsDirectional.only(bottom: 16, start: 24, end: 24),
width: controller.responsiveUtils.getDeviceWidth(context),
height: 48,
child: ElevatedButton(
key: const Key('loginSubmitForm'),
style: ButtonStyle(
foregroundColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) => Colors.white),
backgroundColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) => AppColor.primaryColor),
shape: MaterialStateProperty.all(RoundedRectangleBorder(
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: AppColor.primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: const BorderSide(width: 0, color: AppColor.primaryColor)
))
)
),
onPressed: controller.handleLoginPressed,
child: Text(
@@ -135,7 +57,8 @@ abstract class BaseLoginView extends GetWidget<LoginController> {
return TypeAheadFormFieldBuilder<RecentLoginUsername>(
key: const Key('login_username_input'),
controller: controller.usernameInputController,
onTextChange: controller.setUserNameText,
onTextChange: controller.onUsernameChange,
focusNode: controller.userNameFocusNode,
textInputAction: TextInputAction.next,
autocorrect: false,
autofillHints: const [AutofillHints.email],
@@ -146,11 +69,8 @@ abstract class BaseLoginView extends GetWidget<LoginController> {
.build(),
debounceDuration: const Duration(milliseconds: 300),
suggestionsCallback: controller.getAllRecentLoginUsernameAction,
itemBuilder: (context, loginUsername) => RecentItemTileWidget(loginUsername, imagePath: controller.imagePaths),
onSuggestionSelected: (recentUsername) {
controller.setUsername(recentUsername.username);
controller.passFocusNode.requestFocus();
},
itemBuilder: (_, loginUsername) => RecentItemTileWidget(loginUsername),
onSuggestionSelected: controller.selectUsernameFromSuggestion,
suggestionsBoxDecoration: const SuggestionsBoxDecoration(borderRadius: BorderRadius.all(Radius.circular(14))),
noItemsFoundBuilder: (context) => const SizedBox(),
hideOnEmpty: true,
@@ -167,8 +87,8 @@ abstract class BaseLoginView extends GetWidget<LoginController> {
textInputAction: TextInputAction.done,
hintText: AppLocalizations.of(context).password,
focusNode: controller.passFocusNode,
onTextChange: controller.setPasswordText,
onSubmitted: (value) => controller.handleLoginPressed(),
onTextChange: controller.onPasswordChange,
onSubmitted: (_) => controller.handleLoginPressed(),
);
}
@@ -1,21 +1,21 @@
import 'package:core/data/model/source_type/data_source_type.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/base_bindings.dart';
import 'package:tmail_ui_user/features/caching/clients/recent_login_url_cache_client.dart';
import 'package:tmail_ui_user/features/caching/clients/recent_login_username_cache_client.dart';
import 'package:tmail_ui_user/features/login/data/datasource/login_url_datasource.dart';
import 'package:tmail_ui_user/features/login/data/datasource/login_username_datasource.dart';
import 'package:tmail_ui_user/features/login/data/datasource_impl/login_url_datasource_impl.dart';
import 'package:tmail_ui_user/features/login/data/datasource_impl/login_username_datasource_impl.dart';
import 'package:tmail_ui_user/features/login/data/repository/login_url_repository_impl.dart';
import 'package:tmail_ui_user/features/login/data/repository/login_username_repository_impl.dart';
import 'package:tmail_ui_user/features/login/data/datasource/login_datasource.dart';
import 'package:tmail_ui_user/features/login/data/datasource_impl/hive_login_datasource_impl.dart';
import 'package:tmail_ui_user/features/login/data/datasource_impl/login_datasource_impl.dart';
import 'package:tmail_ui_user/features/login/data/network/dns_service.dart';
import 'package:tmail_ui_user/features/login/data/repository/login_repository_impl.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/features/login/domain/repository/login_url_repository.dart';
import 'package:tmail_ui_user/features/login/domain/repository/login_username_repository.dart';
import 'package:tmail_ui_user/features/login/domain/repository/login_repository.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/authenticate_oidc_on_browser_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/authentication_user_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/check_oidc_is_available_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/dns_lookup_to_get_jmap_url_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/get_all_recent_login_url_on_mobile_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/get_all_recent_login_username_on_mobile_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/get_authentication_info_interactor.dart';
@@ -27,6 +27,7 @@ import 'package:tmail_ui_user/features/login/domain/usecases/save_login_url_on_m
import 'package:tmail_ui_user/features/login/domain/usecases/save_login_username_on_mobile_interactor.dart';
import 'package:tmail_ui_user/features/login/presentation/login_controller.dart';
import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart';
import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart';
class LoginBindings extends BaseBindings {
@@ -45,24 +46,26 @@ class LoginBindings extends BaseBindings {
Get.find<GetAllRecentLoginUrlOnMobileInteractor>(),
Get.find<SaveLoginUsernameOnMobileInteractor>(),
Get.find<GetAllRecentLoginUsernameOnMobileInteractor>(),
Get.find<DNSLookupToGetJmapUrlInteractor>(),
));
}
@override
void bindingsDataSource() {
Get.lazyPut<LoginUrlDataSource>(() => Get.find<LoginUrlDataSourceImpl>());
Get.lazyPut<LoginUsernameDataSource>(() => Get.find<LoginUsernameDataSourceImpl>());
Get.lazyPut<LoginDataSource>(() => Get.find<LoginDataSourceImpl>());
}
@override
void bindingsDataSourceImpl() {
Get.lazyPut(() => LoginUrlDataSourceImpl(
Get.lazyPut(() => HiveLoginDataSourceImpl(
Get.find<RecentLoginUrlCacheClient>(),
Get.find<CacheExceptionThrower>()));
Get.lazyPut(() => LoginUsernameDataSourceImpl(
Get.find<RecentLoginUsernameCacheClient>(),
Get.find<CacheExceptionThrower>()));
Get.find<CacheExceptionThrower>()
));
Get.lazyPut(() => LoginDataSourceImpl(
Get.find<DNSService>(),
Get.find<RemoteExceptionThrower>(),
));
}
@override
@@ -90,28 +93,25 @@ class LoginBindings extends BaseBindings {
Get.lazyPut(() => GetStoredOidcConfigurationInteractor(
Get.find<AuthenticationOIDCRepository>(),
));
Get.lazyPut(() => SaveLoginUrlOnMobileInteractor(
Get.find<LoginUrlRepository>(),
));
Get.lazyPut(() => GetAllRecentLoginUrlOnMobileInteractor(Get.find<LoginUrlRepository>()));
Get.lazyPut(() => SaveLoginUsernameOnMobileInteractor(
Get.find<LoginUsernameRepository>(),
));
Get.lazyPut(() => GetAllRecentLoginUsernameOnMobileInteractor(
Get.find<LoginUsernameRepository>()
));
Get.lazyPut(() => SaveLoginUrlOnMobileInteractor(Get.find<LoginRepository>(),));
Get.lazyPut(() => GetAllRecentLoginUrlOnMobileInteractor(Get.find<LoginRepository>()));
Get.lazyPut(() => SaveLoginUsernameOnMobileInteractor(Get.find<LoginRepository>(),));
Get.lazyPut(() => GetAllRecentLoginUsernameOnMobileInteractor(Get.find<LoginRepository>()));
Get.lazyPut(() => DNSLookupToGetJmapUrlInteractor(Get.find<LoginRepository>()));
}
@override
void bindingsRepository() {
Get.lazyPut<LoginUrlRepository>(() => Get.find<LoginUrlRepositoryImpl>());
Get.lazyPut<LoginUsernameRepository>(() => Get.find<LoginUsernameRepositoryImpl>());
Get.lazyPut<LoginRepository>(() => Get.find<LoginRepositoryImpl>());
}
@override
void bindingsRepositoryImpl() {
Get.lazyPut(() => LoginUrlRepositoryImpl(Get.find<LoginUrlDataSource>()));
Get.lazyPut(() => LoginUsernameRepositoryImpl(Get.find<LoginUsernameDataSource>()));
Get.lazyPut(() => LoginRepositoryImpl(
{
DataSourceType.hiveCache: Get.find<HiveLoginDataSourceImpl>(),
DataSourceType.network: Get.find<LoginDataSource>(),
}
));
}
}
@@ -24,6 +24,7 @@ import 'package:tmail_ui_user/features/login/domain/model/recent_login_username.
import 'package:tmail_ui_user/features/login/domain/state/authenticate_oidc_on_browser_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/authentication_user_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/check_oidc_is_available_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/dns_lookup_to_get_jmap_url_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_all_recent_login_url_latest_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_all_recent_login_username_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_authenticated_account_state.dart';
@@ -35,6 +36,7 @@ import 'package:tmail_ui_user/features/login/domain/state/get_token_oidc_state.d
import 'package:tmail_ui_user/features/login/domain/usecases/authenticate_oidc_on_browser_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/authentication_user_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/check_oidc_is_available_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/dns_lookup_to_get_jmap_url_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/get_all_recent_login_url_on_mobile_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/get_all_recent_login_username_on_mobile_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/get_authentication_info_interactor.dart';
@@ -67,12 +69,22 @@ class LoginController extends ReloadableController {
final GetAllRecentLoginUrlOnMobileInteractor _getAllRecentLoginUrlOnMobileInteractor;
final SaveLoginUsernameOnMobileInteractor _saveLoginUsernameOnMobileInteractor;
final GetAllRecentLoginUsernameOnMobileInteractor _getAllRecentLoginUsernameOnMobileInteractor;
final DNSLookupToGetJmapUrlInteractor _dnsLookupToGetJmapUrlInteractor;
final TextEditingController urlInputController = TextEditingController();
final TextEditingController usernameInputController = TextEditingController();
final TextEditingController passwordInputController = TextEditingController();
final FocusNode baseUrlFocusNode = FocusNode();
final FocusNode userNameFocusNode = FocusNode();
final FocusNode passFocusNode = FocusNode();
final loginFormType = LoginFormType.none.obs;
OIDCResponse? _oidcResponse;
UserName? _username;
Password? _password;
Uri? _baseUri;
LoginController(
this._authenticationInteractor,
this._checkOIDCIsAvailableInteractor,
@@ -86,42 +98,26 @@ class LoginController extends ReloadableController {
this._getAllRecentLoginUrlOnMobileInteractor,
this._saveLoginUsernameOnMobileInteractor,
this._getAllRecentLoginUsernameOnMobileInteractor,
this._dnsLookupToGetJmapUrlInteractor,
);
final loginFormType = LoginFormType.baseUrlForm.obs;
String? _urlText;
String? _userNameText;
String? _passwordText;
OIDCResponse? _oidcResponse;
void setUrlText(String url) => _urlText = url.trim().formatURLValid();
void setUserNameText(String userName) => _userNameText = userName;
void setPasswordText(String password) => _passwordText = password;
Uri? _parseUri(String? url) => url != null && url.trim().isNotEmpty
? Uri.parse(url.trim())
: null;
UserName? _parseUserName(String? userName) => userName != null && userName.trim().isNotEmpty
? UserName(userName.trim())
: null;
Password? _parsePassword(String? password) => password != null && password.trim().isNotEmpty
? Password(password.trim())
: null;
@override
void onReady() {
super.onReady();
if (PlatformInfo.isWeb) {
final arguments = Get.arguments;
if (arguments is LoginArguments) {
loginFormType.value = arguments.loginFormType;
_checkOIDCIsAvailable();
final arguments = Get.arguments;
if (arguments is LoginArguments) {
if (arguments.loginFormType == LoginFormType.passwordForm) {
loginFormType.value = LoginFormType.dnsLookupForm;
} else {
loginFormType.value = arguments.loginFormType;
}
if (PlatformInfo.isWeb) {
_checkOIDCIsAvailable();
} else if (arguments.baseUrl?.isNotEmpty == true) {
onBaseUrlChange(arguments.baseUrl!);
}
} else {
if (PlatformInfo.isWeb) {
_getAuthenticationInfo();
}
}
@@ -134,12 +130,21 @@ class LoginController extends ReloadableController {
} else if (failure is CheckOIDCIsAvailableFailure ||
failure is GetStoredOidcConfigurationFailure ||
failure is GetOIDCIsAvailableFailure ||
failure is GetOIDCConfigurationFailure ||
failure is GetTokenOIDCFailure) {
_showFormLoginWithCredentialAction();
if (PlatformInfo.isMobile && loginFormType.value == LoginFormType.dnsLookupForm) {
_showPasswordForm();
} else {
_showCredentialForm();
}
} else if (failure is GetAuthenticatedAccountFailure) {
_checkOIDCIsAvailable();
} else if (failure is GetSessionFailure) {
clearAllData();
} else if (failure is DNSLookupToGetJmapUrlFailure) {
_username = null;
_clearTextInputField();
_showBaseUrlForm();
} else {
super.handleFailureViewState(failure);
}
@@ -162,6 +167,8 @@ class LoginController extends ReloadableController {
_getTokenOIDCSuccess(success);
} else if (success is AuthenticationUserSuccess) {
_loginSuccessAction(success);
} else if (success is DNSLookupToGetJmapUrlSuccess) {
_handleDNSLookupToGetJmapUrlSuccess(success);
} else {
super.handleSuccessViewState(success);
}
@@ -169,14 +176,21 @@ class LoginController extends ReloadableController {
@override
void handleExceptionAction({Failure? failure, Exception? exception}) {
super.handleExceptionAction(failure: failure, exception: exception);
logError('LoginController::handleExceptionAction:exception: $exception | failure: ${failure.runtimeType}');
if (failure is CheckOIDCIsAvailableFailure ||
failure is GetStoredOidcConfigurationFailure ||
failure is GetOIDCConfigurationFailure ||
failure is GetOIDCIsAvailableFailure ||
failure is GetTokenOIDCFailure) {
_showFormLoginWithCredentialAction();
if (PlatformInfo.isMobile && loginFormType.value == LoginFormType.dnsLookupForm) {
_showPasswordForm();
} else {
_showCredentialForm();
}
} else if (failure is GetSessionFailure) {
clearAllData();
} else {
clearState();
super.handleExceptionAction(failure: failure, exception: exception);
}
}
@@ -197,19 +211,20 @@ class LoginController extends ReloadableController {
}
void handleNextInUrlInputFormPress() {
_saveRecentLoginUrl();
if (PlatformInfo.isMobile && _currentBaseUrl != null) {
_storeBaseUrlToCache(_currentBaseUrl!);
}
_checkOIDCIsAvailable();
}
void _checkOIDCIsAvailable() {
final baseUrl = _getBaseUrl();
if (baseUrl == null) {
if (_currentBaseUrl == null) {
dispatchState(Left(CheckOIDCIsAvailableFailure(CanNotFoundBaseUrl())));
} else {
consumeState(_checkOIDCIsAvailableInteractor.execute(
OIDCRequest(
baseUrl: baseUrl.toString(),
resourceUrl: baseUrl.origin
baseUrl: _currentBaseUrl!.toString(),
resourceUrl: _currentBaseUrl!.origin
)
));
}
@@ -217,46 +232,60 @@ class LoginController extends ReloadableController {
void _redirectToSSOLoginScreen(CheckOIDCIsAvailableSuccess success) {
_oidcResponse = success.oidcResponse;
handleSSOPressed();
consumeState(_getOIDCIsAvailableInteractor.execute(
OIDCRequest(
baseUrl: _currentBaseUrl!.toString(),
resourceUrl: _currentBaseUrl!.origin
)
));
}
void handleBackInCredentialForm() {
void handleBackButtonAction() {
clearState();
loginFormType.value = LoginFormType.baseUrlForm;
}
void _showFormLoginWithCredentialAction() {
clearState();
loginFormType.value = LoginFormType.credentialForm;
}
void handleLoginPressed() {
TextInput.finishAutofillContext();
_saveRecentLoginUsername();
log('LoginController::handleLoginPressed(): ${loginFormType.value}');
if (loginFormType.value == LoginFormType.ssoForm) {
_getOIDCConfiguration();
} else {
final baseUrl = _getBaseUrl();
final userName = _parseUserName(_userNameText);
final password = _parsePassword(_passwordText);
_loginAction(baseUrl: baseUrl, userName: userName, password: password);
if (loginFormType.value == LoginFormType.credentialForm) {
_password = null;
_username = null;
usernameInputController.clear();
passwordInputController.clear();
loginFormType.value = LoginFormType.baseUrlForm;
} else if (loginFormType.value == LoginFormType.passwordForm) {
_password = null;
_baseUri = null;
urlInputController.clear();
passwordInputController.clear();
loginFormType.value = LoginFormType.dnsLookupForm;
}
}
void handleSSOPressed() {
final baseUrl = _getBaseUrl();
if (baseUrl != null) {
consumeState(_getOIDCIsAvailableInteractor.execute(
OIDCRequest(
baseUrl: baseUrl.toString(),
resourceUrl: baseUrl.origin
)
));
void _showCredentialForm() {
clearState();
loginFormType.value = LoginFormType.credentialForm;
userNameFocusNode.requestFocus();
}
void handleLoginPressed() {
log('LoginController::handleLoginPressed:_currentBaseUrl: $_currentBaseUrl | _username: $_username | _password: $_password');
if (_currentBaseUrl == null) {
consumeState(Stream.value(Left(AuthenticationUserFailure(CanNotFoundBaseUrl()))));
} else if (_username == null) {
consumeState(Stream.value(Left(AuthenticationUserFailure(CanNotFoundUserName()))));
} else if (_password == null) {
consumeState(Stream.value(Left(AuthenticationUserFailure(CanNotFoundPassword()))));
} else {
dispatchState(Left(GetOIDCIsAvailableFailure(CanNotFoundBaseUrl())));
if (PlatformInfo.isMobile && loginFormType.value == LoginFormType.credentialForm) {
TextInput.finishAutofillContext();
if (_username!.value.isEmail) {
_storeUsernameToCache(_username!.value);
}
}
consumeState(
_authenticationInteractor.execute(
baseUrl: _currentBaseUrl!,
userName: _username!,
password: _password!
)
);
}
}
@@ -264,12 +293,11 @@ class LoginController extends ReloadableController {
if (_oidcResponse != null) {
consumeState(_getOIDCConfigurationInteractor.execute(_oidcResponse!));
} else {
dispatchState(Left(GetOIDCIsAvailableFailure(CanNotFoundOIDCLinks())));
dispatchState(Left(GetOIDCConfigurationFailure(CanNotFoundOIDCLinks())));
}
}
void _getOIDCConfigurationSuccess(GetOIDCConfigurationSuccess success) {
log('LoginController::_getOIDCConfigurationSuccess():success: $success');
if (PlatformInfo.isWeb) {
_authenticateOidcOnBrowserAction(success.oidcConfiguration);
} else {
@@ -277,10 +305,9 @@ class LoginController extends ReloadableController {
}
}
void _getTokenOIDCAction(OIDCConfiguration config) async {
final baseUri = _getBaseUrl();
if (baseUri != null) {
consumeState(_getTokenOIDCInteractor.execute(baseUri, config));
void _getTokenOIDCAction(OIDCConfiguration config) {
if (_currentBaseUrl != null) {
consumeState(_getTokenOIDCInteractor.execute(_currentBaseUrl!, config));
} else {
dispatchState(Left(GetTokenOIDCFailure(CanNotFoundBaseUrl())));
}
@@ -289,9 +316,8 @@ class LoginController extends ReloadableController {
void _authenticateOidcOnBrowserAction(OIDCConfiguration config) async {
_removeAuthDestinationUrlInSessionStorage();
final baseUri = _parseUri(AppConfig.baseUrl);
if (baseUri != null) {
consumeState(_authenticateOidcOnBrowserInteractor.execute(baseUri, config));
if (_currentBaseUrl != null) {
consumeState(_authenticateOidcOnBrowserInteractor.execute(_currentBaseUrl!, config));
} else {
dispatchState(Left(AuthenticateOidcOnBrowserFailure(CanNotFoundBaseUrl())));
}
@@ -305,9 +331,8 @@ class LoginController extends ReloadableController {
}
void _getTokenOIDCSuccess(GetTokenOIDCSuccess success) {
log('LoginController::_getTokenOIDCSuccess(): ${success.tokenOIDC.toString()}');
dynamicUrlInterceptors.setJmapUrl(_getBaseUrl().toString());
dynamicUrlInterceptors.changeBaseUrl(_getBaseUrl().toString());
dynamicUrlInterceptors.setJmapUrl(_currentBaseUrl?.toString());
dynamicUrlInterceptors.changeBaseUrl(_currentBaseUrl?.toString());
authorizationInterceptors.setTokenAndAuthorityOidc(
newToken: success.tokenOIDC.toToken(),
newConfig: success.configuration);
@@ -317,36 +342,24 @@ class LoginController extends ReloadableController {
getSessionAction();
}
void _loginAction({Uri? baseUrl, UserName? userName, Password? password}) {
consumeState(_authenticationInteractor.execute(
baseUrl: baseUrl,
userName: userName,
password: password
));
}
void _loginSuccessAction(AuthenticationUserSuccess success) {
dynamicUrlInterceptors.setJmapUrl(_getBaseUrl().toString());
dynamicUrlInterceptors.changeBaseUrl(_getBaseUrl().toString());
authorizationInterceptors.setBasicAuthorization(_userNameText, _passwordText);
authorizationIsolateInterceptors.setBasicAuthorization(_userNameText, _passwordText);
dynamicUrlInterceptors.setJmapUrl(_currentBaseUrl?.toString());
dynamicUrlInterceptors.changeBaseUrl(_currentBaseUrl?.toString());
authorizationInterceptors.setBasicAuthorization(_username!, _password!);
authorizationIsolateInterceptors.setBasicAuthorization(_username!, _password!);
getSessionAction();
}
void formatUrl(String url) {
log('LoginController::formatUrl(): $url');
if (url.isValid()) {
urlInputController.text = url.removePrefix();
}
setUrlText(urlInputController.text);
void selectBaseUrlFromSuggestion(String url) {
final validUrl = url.isValid() ? url.removePrefix() : url;
log('LoginController::selectBaseUrlFromSuggestion:validUrl: $validUrl');
urlInputController.text = validUrl;
onBaseUrlChange(validUrl);
}
void _saveRecentLoginUrl() {
if (_urlText?.isNotEmpty == true && PlatformInfo.isMobile) {
final recentLoginUrl = RecentLoginUrl.now(_urlText!);
log('LoginController::_saveRecentLoginUrl(): $recentLoginUrl');
_saveLoginUrlOnMobileInteractor.execute(recentLoginUrl);
}
void _storeBaseUrlToCache(Uri uri) {
log('LoginController::_storeBaseUrlToCache:uri: $uri');
_saveLoginUrlOnMobileInteractor.execute(RecentLoginUrl.now(uri.toString()));
}
Future<List<RecentLoginUrl>> getAllRecentLoginUrlAction(String pattern) async {
@@ -360,19 +373,19 @@ class LoginController extends ReloadableController {
));
}
void setUsername(String username) {
log('LoginController::formatUsername(): $username');
usernameInputController.text = username;
setUserNameText(usernameInputController.text);
void selectUsernameFromSuggestion(RecentLoginUsername recentLoginUsername) {
log('LoginController::selectUsernameFromSuggestion():recentLoginUsername: $recentLoginUsername');
usernameInputController.text = recentLoginUsername.username;
_username = UserName(recentLoginUsername.username);
if (loginFormType.value == LoginFormType.credentialForm) {
passFocusNode.requestFocus();
}
}
void _saveRecentLoginUsername() {
if(PlatformInfo.isWeb || _userNameText == null || _userNameText!.isEmpty || !_userNameText!.isEmail) {
return ;
}
final recentLoginUsername = RecentLoginUsername.now(_userNameText!);
log('LoginController::_saveRecentLoginUsername(): $recentLoginUsername');
_saveLoginUsernameOnMobileInteractor.execute(recentLoginUsername);
void _storeUsernameToCache(String userName) {
log('LoginController::_storeUsername():userName: $userName');
_saveLoginUsernameOnMobileInteractor.execute(RecentLoginUsername.now(userName));
}
Future<List<RecentLoginUsername>> getAllRecentLoginUsernameAction(String pattern) async {
@@ -386,11 +399,82 @@ class LoginController extends ReloadableController {
));
}
Uri? _getBaseUrl() => PlatformInfo.isWeb ? _parseUri(AppConfig.baseUrl) : _parseUri(_urlText);
Uri? get _currentBaseUrl => PlatformInfo.isWeb
? Uri.tryParse(AppConfig.baseUrl)
: _baseUri;
void invokeDNSLookupToGetJmapUrl() {
log('LoginController::invokeDNSLookupToGetJmapUrl:_username $_username');
if (_username == null) {
consumeState(Stream.value(Left(AuthenticationUserFailure(CanNotFoundUserName()))));
} else {
if (_username!.value.isEmail) {
_storeUsernameToCache(_username!.value);
consumeState(_dnsLookupToGetJmapUrlInteractor.execute(_username!.value));
} else {
_username = null;
_clearTextInputField();
_showBaseUrlForm();
}
}
}
void _handleDNSLookupToGetJmapUrlSuccess(DNSLookupToGetJmapUrlSuccess success) {
onBaseUrlChange(success.jmapUrl);
_checkOIDCIsAvailable();
}
void _showBaseUrlForm() {
clearState();
loginFormType.value = LoginFormType.baseUrlForm;
baseUrlFocusNode.requestFocus();
}
void _showPasswordForm() {
clearState();
loginFormType.value = LoginFormType.passwordForm;
passFocusNode.requestFocus();
}
void onUsernameChange(String value) {
if (value.isEmpty) {
_username = null;
} else {
_username = UserName(value);
}
}
void onPasswordChange(String value) {
if (value.isEmpty) {
_password = null;
} else {
_password = Password(value);
}
}
void onBaseUrlChange(String value) {
if (value.isEmpty) {
_baseUri = null;
} else {
if (value.isValid()) {
log('LoginController::onBaseUrlChange:value: $value');
urlInputController.text = value.removePrefix();
}
_baseUri = Uri.tryParse(value.formatURLValid());
}
}
void _clearTextInputField() {
urlInputController.clear();
usernameInputController.clear();
passwordInputController.clear();
}
@override
void onClose() {
passFocusNode.dispose();
baseUrlFocusNode.dispose();
userNameFocusNode.dispose();
urlInputController.dispose();
usernameInputController.dispose();
passwordInputController.dispose();
@@ -1,3 +1,7 @@
enum LoginFormType {
none, baseUrlForm, credentialForm, ssoForm
none,
baseUrlForm,
credentialForm,
passwordForm,
dnsLookupForm;
}
+58 -45
View File
@@ -3,7 +3,6 @@ import 'package:core/presentation/state/success.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:core/presentation/views/text/type_ahead_form_field_builder.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/widget/recent_item_tile_widget.dart';
@@ -11,7 +10,12 @@ import 'package:tmail_ui_user/features/login/domain/model/recent_login_url.dart'
import 'package:tmail_ui_user/features/login/presentation/base_login_view.dart';
import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart';
import 'package:tmail_ui_user/features/login/presentation/privacy_link_widget.dart';
import 'package:tmail_ui_user/features/login/presentation/widgets/dns_lookup_input_form.dart';
import 'package:tmail_ui_user/features/login/presentation/widgets/horizontal_progress_loading_button.dart';
import 'package:tmail_ui_user/features/login/presentation/widgets/login_back_button.dart';
import 'package:tmail_ui_user/features/login/presentation/widgets/login_input_decoration_builder.dart';
import 'package:tmail_ui_user/features/login/presentation/widgets/login_message_widget.dart';
import 'package:tmail_ui_user/features/login/presentation/widgets/password_input_form.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class LoginView extends BaseLoginView {
@@ -31,13 +35,16 @@ class LoginView extends BaseLoginView {
child: SafeArea(
child: _supportScrollForm(context)
? Stack(children: [
Center(child: SingleChildScrollView(
Center(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: _buildCenterForm(context))),
child: _buildCenterForm(context)
)
),
Obx(() {
if (controller.loginFormType.value == LoginFormType.credentialForm
|| controller.loginFormType.value == LoginFormType.ssoForm) {
return _buildBackButton(context);
if (controller.loginFormType.value == LoginFormType.passwordForm ||
controller.loginFormType.value == LoginFormType.credentialForm) {
return LoginBackButton(onBackAction: controller.handleBackButtonAction);
}
return const SizedBox.shrink();
})
@@ -45,9 +52,9 @@ class LoginView extends BaseLoginView {
: Stack(children: [
_buildCenterForm(context),
Obx(() {
if (controller.loginFormType.value == LoginFormType.credentialForm
|| controller.loginFormType.value == LoginFormType.ssoForm) {
return _buildBackButton(context);
if (controller.loginFormType.value == LoginFormType.passwordForm ||
controller.loginFormType.value == LoginFormType.credentialForm) {
return LoginBackButton(onBackAction: controller.handleBackButtonAction);
}
return const SizedBox.shrink();
})
@@ -75,9 +82,28 @@ class LoginView extends BaseLoginView {
style: const TextStyle(fontSize: 32, color: AppColor.colorNameEmail, fontWeight: FontWeight.w900)
)
),
Obx(() => buildLoginMessage(context, controller.viewState.value)),
Obx(() => LoginMessageWidget(
formType: controller.loginFormType.value,
viewState: controller.viewState.value,
)),
Obx(() {
switch (controller.loginFormType.value) {
case LoginFormType.dnsLookupForm:
return DNSLookupInputForm(
textEditingController: controller.usernameInputController,
onTextChange: controller.onUsernameChange,
onTextSubmitted: (_) => controller.invokeDNSLookupToGetJmapUrl(),
suggestionsCallback: controller.getAllRecentLoginUsernameAction,
onSuggestionSelected: controller.selectUsernameFromSuggestion
);
case LoginFormType.passwordForm:
return PasswordInputForm(
key: const Key('password_input_form'),
textEditingController: controller.passwordInputController,
focusNode: controller.passFocusNode,
onTextChange: controller.onPasswordChange,
onTextSubmitted: (_) => controller.handleLoginPressed(),
);
case LoginFormType.baseUrlForm:
return _buildUrlInput(context);
case LoginFormType.credentialForm:
@@ -97,22 +123,6 @@ class LoginView extends BaseLoginView {
);
}
Widget _buildBackButton(BuildContext context) {
return Positioned(
left: 12,
top: 12,
child: IconButton(
key: const Key('login_arrow_back_button'),
onPressed: () => controller.handleBackInCredentialForm(),
icon: SvgPicture.asset(
controller.imagePaths.icBack,
alignment: Alignment.center,
colorFilter: AppColor.primaryColor.asFilter()
)
),
);
}
Widget _buildUrlInput(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(right: 24, left: 24, bottom: 24),
@@ -120,19 +130,17 @@ class LoginView extends BaseLoginView {
controller: controller.urlInputController,
textInputAction: TextInputAction.next,
keyboardType: TextInputType.url,
focusNode: controller.baseUrlFocusNode,
onTextChange: controller.onBaseUrlChange,
onTextSubmitted: (value) => controller.handleNextInUrlInputFormPress(),
onTextChange: controller.formatUrl,
decoration: (LoginInputDecorationBuilder()
..setLabelText(AppLocalizations.of(context).prefix_https)
..setPrefixText(AppLocalizations.of(context).prefix_https))
.build(),
debounceDuration: const Duration(milliseconds: 300),
suggestionsCallback: (pattern) async {
controller.formatUrl(pattern);
return controller.getAllRecentLoginUrlAction(pattern);
},
itemBuilder: (context, loginUrl) => RecentItemTileWidget(loginUrl, imagePath: controller.imagePaths),
onSuggestionSelected: (loginUrl) => controller.formatUrl(loginUrl.url),
suggestionsCallback: controller.getAllRecentLoginUrlAction,
itemBuilder: (_, loginUrl) => RecentItemTileWidget(loginUrl),
onSuggestionSelected: (loginUrl) => controller.selectBaseUrlFromSuggestion(loginUrl.url),
suggestionsBoxDecoration: const SuggestionsBoxDecoration(borderRadius: BorderRadius.all(Radius.circular(14))),
noItemsFoundBuilder: (context) => const SizedBox(),
hideOnEmpty: true,
@@ -156,20 +164,23 @@ class LoginView extends BaseLoginView {
width: controller.responsiveUtils.getDeviceWidth(context),height: 48,
child: ElevatedButton(
key: const Key('nextToCredentialForm'),
style: ButtonStyle(
foregroundColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) => Colors.white),
backgroundColor: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) => AppColor.primaryColor),
shape: MaterialStateProperty.all(RoundedRectangleBorder(
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: AppColor.primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: const BorderSide(width: 0, color: AppColor.primaryColor)
))
)
),
child: Text(AppLocalizations.of(context).next,
style: const TextStyle(fontSize: 16, color: Colors.white)
),
onPressed: () {
controller.handleNextInUrlInputFormPress();
if (controller.loginFormType.value == LoginFormType.dnsLookupForm) {
controller.invokeDNSLookupToGetJmapUrl();
} else {
controller.handleNextInUrlInputFormPress();
}
}
)
);
@@ -195,27 +206,29 @@ class LoginView extends BaseLoginView {
return Obx(() => controller.viewState.value.fold(
(failure) {
switch (controller.loginFormType.value) {
case LoginFormType.dnsLookupForm:
case LoginFormType.baseUrlForm:
return _buildNextButtonInContext(context);
case LoginFormType.passwordForm:
case LoginFormType.credentialForm:
return _buildLoginButtonInContext(context);
case LoginFormType.ssoForm:
return _buildLoginButtonInContext(context);
default:
return const SizedBox.shrink();
}
},
(success) {
if (success is LoadingState) {
return buildLoadingCircularProgress();
return _supportScrollForm(context)
? const HorizontalProgressLoadingButton()
: _buildExpandedButton(context, const HorizontalProgressLoadingButton());
} else {
switch (controller.loginFormType.value) {
case LoginFormType.dnsLookupForm:
case LoginFormType.baseUrlForm:
return _buildNextButtonInContext(context);
case LoginFormType.passwordForm:
case LoginFormType.credentialForm:
return _buildLoginButtonInContext(context);
case LoginFormType.ssoForm:
return _buildLoginButtonInContext(context);
default:
return const SizedBox.shrink();
}
@@ -8,6 +8,7 @@ import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
import 'package:tmail_ui_user/features/login/presentation/base_login_view.dart';
import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart';
import 'package:tmail_ui_user/features/login/presentation/privacy_link_widget.dart';
import 'package:tmail_ui_user/features/login/presentation/widgets/login_message_widget.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class LoginView extends BaseLoginView {
@@ -47,13 +48,14 @@ class LoginView extends BaseLoginView {
style: const TextStyle(fontSize: 32, color: AppColor.colorNameEmail, fontWeight: FontWeight.w900)
)
),
Obx(() => buildLoginMessage(context, controller.viewState.value)),
Obx(() => LoginMessageWidget(
formType: controller.loginFormType.value,
viewState: controller.viewState.value,
)),
Obx(() {
switch (controller.loginFormType.value) {
case LoginFormType.credentialForm:
return buildInputCredentialForm(context);
case LoginFormType.ssoForm:
return const SizedBox(height: 150);
default:
return const SizedBox.shrink();
}
@@ -179,13 +181,14 @@ class LoginView extends BaseLoginView {
style: const TextStyle(fontSize: 32, color: AppColor.colorNameEmail, fontWeight: FontWeight.w900)
)
),
Obx(() => buildLoginMessage(context, controller.viewState.value)),
Obx(() => LoginMessageWidget(
formType: controller.loginFormType.value,
viewState: controller.viewState.value,
)),
Obx(() {
switch (controller.loginFormType.value) {
case LoginFormType.credentialForm:
return buildInputCredentialForm(context);
case LoginFormType.ssoForm:
return const SizedBox(height: 150);
default:
return const SizedBox.shrink();
}
@@ -232,41 +235,12 @@ class LoginView extends BaseLoginView {
);
}
Widget _buildSSOButton(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 16, left: 24, right: 24),
width: controller.responsiveUtils.getDeviceWidth(context),height: 48,
child: ElevatedButton(
key: const Key('ssoSubmitForm'),
style: ButtonStyle(
foregroundColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) => Colors.white),
backgroundColor: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) => AppColor.primaryColor),
shape: MaterialStateProperty.all(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: const BorderSide(width: 0, color: AppColor.primaryColor)
))
),
child: Text(AppLocalizations.of(context).singleSignOn,
style: const TextStyle(fontSize: 16, color: Colors.white)
),
onPressed: () {
controller.handleSSOPressed();
}
)
);
}
Widget _buildLoadingProgress(BuildContext context) {
return Obx(() => controller.viewState.value.fold(
(failure) {
switch (controller.loginFormType.value) {
case LoginFormType.baseUrlForm:
return const SizedBox.shrink();
case LoginFormType.credentialForm:
return buildLoginButton(context);
case LoginFormType.ssoForm:
return _buildSSOButton(context);
default:
return const SizedBox.shrink();
}
@@ -276,12 +250,8 @@ class LoginView extends BaseLoginView {
return buildLoadingCircularProgress();
} else {
switch (controller.loginFormType.value) {
case LoginFormType.baseUrlForm:
return const SizedBox.shrink();
case LoginFormType.credentialForm:
return buildLoginButton(context);
case LoginFormType.ssoForm:
return _buildSSOButton(context);
default:
return const SizedBox.shrink();
}
@@ -5,9 +5,13 @@ import 'package:tmail_ui_user/main/routes/router_arguments.dart';
class LoginArguments extends RouterArguments {
final LoginFormType loginFormType;
final String? baseUrl;
LoginArguments(this.loginFormType);
LoginArguments(this.loginFormType, {this.baseUrl});
@override
List<Object?> get props => [loginFormType];
List<Object?> get props => [
loginFormType,
baseUrl
];
}
@@ -0,0 +1,57 @@
import 'package:core/presentation/views/text/type_ahead_form_field_builder.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:tmail_ui_user/features/base/widget/recent_item_tile_widget.dart';
import 'package:tmail_ui_user/features/login/domain/model/recent_login_username.dart';
import 'package:tmail_ui_user/features/login/presentation/widgets/login_input_decoration_builder.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class DNSLookupInputForm extends StatelessWidget {
final TextEditingController textEditingController;
final ValueChanged<String> onTextChange;
final ValueChanged<String> onTextSubmitted;
final SuggestionsCallback<RecentLoginUsername> suggestionsCallback;
final SuggestionSelectionCallback<RecentLoginUsername> onSuggestionSelected;
const DNSLookupInputForm({
super.key,
required this.textEditingController,
required this.onTextChange,
required this.onTextSubmitted,
required this.suggestionsCallback,
required this.onSuggestionSelected
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsetsDirectional.only(bottom: 16, start: 24, end: 24),
child: TypeAheadFormFieldBuilder<RecentLoginUsername>(
controller: textEditingController,
onTextChange: onTextChange,
onTextSubmitted: onTextSubmitted,
textInputAction: TextInputAction.next,
autocorrect: false,
autofillHints: const [AutofillHints.email],
keyboardType: TextInputType.emailAddress,
decoration: (LoginInputDecorationBuilder()
..setLabelText(AppLocalizations.of(context).email)
..setHintText(AppLocalizations.of(context).email)
).build(),
debounceDuration: const Duration(milliseconds: 300),
suggestionsCallback: suggestionsCallback,
itemBuilder: (_, loginUsername) => RecentItemTileWidget(loginUsername),
onSuggestionSelected: onSuggestionSelected,
suggestionsBoxDecoration: const SuggestionsBoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(14))
),
noItemsFoundBuilder: (context) => const SizedBox(),
hideOnEmpty: true,
hideOnError: true,
hideOnLoading: true,
),
);
}
}
@@ -0,0 +1,30 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class HorizontalProgressLoadingButton extends StatelessWidget {
const HorizontalProgressLoadingButton({super.key});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsetsDirectional.only(bottom: 16, start: 24, end: 24),
width: context.width,
height: 48,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
disabledBackgroundColor: AppColor.primaryColor,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
)
),
onPressed: null,
child: const LinearProgressIndicator(
color: Colors.white,
backgroundColor: AppColor.primaryColor
)
)
);
}
}
@@ -0,0 +1,30 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
class LoginBackButton extends StatelessWidget {
final _imagePath = Get.find<ImagePaths>();
final VoidCallback onBackAction;
LoginBackButton({super.key, required this.onBackAction});
@override
Widget build(BuildContext context) {
return PositionedDirectional(
start: 12,
top: 12,
child: IconButton(
onPressed: onBackAction,
icon: SvgPicture.asset(
_imagePath.icBack,
alignment: Alignment.center,
colorFilter: AppColor.primaryColor.asFilter()
)
),
);
}
}
@@ -0,0 +1,83 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/login/domain/state/dns_lookup_to_get_jmap_url_state.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_oidc_configuration_state.dart';
import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/utils/message_toast_utils.dart';
class LoginMessageWidget extends StatelessWidget {
static const double _loginTextFieldWidthSmallScreen = 280.0;
static const double _loginTextFieldWidthLargeScreen = 320.0;
final LoginFormType formType;
final Either<Failure, Success> viewState;
const LoginMessageWidget({
super.key,
required this.formType,
required this.viewState
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsetsDirectional.only(
top: 11,
bottom: 36,
start: 58,
end: 58
),
child: SizedBox(
width: context.width < ResponsiveUtils.minTabletWidth
? _loginTextFieldWidthSmallScreen
: _loginTextFieldWidthLargeScreen,
child: Text(
viewState.fold(
(failure) {
if (failure is GetOIDCConfigurationFailure) {
return AppLocalizations.of(context).canNotVerifySSOConfiguration;
} else if (failure is DNSLookupToGetJmapUrlFailure) {
return AppLocalizations.of(context).dnsLookupLoginMessage;
} else if (failure is FeatureFailure) {
final errorMessage = MessageToastUtils.getMessageByException(context, failure.exception);
return errorMessage ?? AppLocalizations.of(context).unknownError;
} else {
return AppLocalizations.of(context).unknownError;
}
},
(success) {
if (formType == LoginFormType.credentialForm) {
return AppLocalizations.of(context).loginInputCredentialMessage;
} else if (formType == LoginFormType.dnsLookupForm) {
return AppLocalizations.of(context).dnsLookupLoginMessage;
} else if (formType == LoginFormType.passwordForm) {
return AppLocalizations.of(context).enterYourPasswordToSignIn;
} else if (formType == LoginFormType.baseUrlForm) {
return AppLocalizations.of(context).loginInputUrlMessage;
} else {
return '';
}
}
),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w400,
color: viewState.fold(
(failure) => AppColor.textFieldErrorBorderColor,
(success) => AppColor.colorNameEmail
)
),
)
)
);
}
}
@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/login/presentation/widgets/login_text_input_builder.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class PasswordInputForm extends StatelessWidget {
final TextEditingController textEditingController;
final FocusNode focusNode;
final ValueChanged<String> onTextChange;
final ValueChanged<String> onTextSubmitted;
const PasswordInputForm({
super.key,
required this.textEditingController,
required this.focusNode,
required this.onTextChange,
required this.onTextSubmitted
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsetsDirectional.only(bottom: 16, start: 24, end: 24),
child: LoginTextInputBuilder(
controller: textEditingController,
autofillHints: const [AutofillHints.password],
textInputAction: TextInputAction.done,
hintText: AppLocalizations.of(context).password,
focusNode: focusNode,
onTextChange: onTextChange,
onSubmitted: onTextSubmitted,
),
);
}
}
@@ -16,8 +16,10 @@ class CreateDefaultMailboxAllSuccess extends UIActionState {
}
class CreateDefaultMailboxFailure extends FeatureFailure {
CreateDefaultMailboxFailure(dynamic exception) : super(exception: exception);
final jmap.State? currentMailboxState;
CreateDefaultMailboxFailure(this.currentMailboxState, dynamic exception) : super(exception: exception);
@override
List<Object?> get props => [exception];
List<Object?> get props => [currentMailboxState, exception];
}
@@ -1,9 +1,10 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/utils/app_logger.dart';
import 'package:dartz/dartz.dart';
import 'package:dartz/dartz.dart' as dartz;
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/state.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart';
import 'package:tmail_ui_user/features/mailbox/domain/state/create_default_mailbox_state.dart';
@@ -13,30 +14,37 @@ class CreateDefaultMailboxInteractor {
CreateDefaultMailboxInteractor(this._mailboxRepository);
Stream<Either<Failure, Success>> execute(
Stream<dartz.Either<Failure, Success>> execute(
Session session,
AccountId accountId,
List<Role> listRole
) async* {
final currentMailboxState = await _getCurrentMailboxState(session, accountId);
try {
yield Right<Failure, Success>(CreateDefaultMailboxLoading());
final currentMailboxState = await _mailboxRepository.getMailboxState(session, accountId);
yield dartz.Right<Failure, Success>(CreateDefaultMailboxLoading());
final listMailboxCreated = await _mailboxRepository.createDefaultMailbox(
session,
accountId,
listRole
);
log('CreateDefaultMailboxInteractor::execute:listMailboxCreated: ${listMailboxCreated.length}');
await _mailboxRepository.setRoleDefaultMailbox(
session,
accountId,
listMailboxCreated
);
yield Right<Failure, Success>(CreateDefaultMailboxAllSuccess(currentMailboxState: currentMailboxState));
yield dartz.Right<Failure, Success>(CreateDefaultMailboxAllSuccess(currentMailboxState: currentMailboxState));
} catch (e) {
yield Left<Failure, Success>(CreateDefaultMailboxFailure(e));
yield dartz.Left<Failure, Success>(CreateDefaultMailboxFailure(currentMailboxState, e));
}
}
Future<State?> _getCurrentMailboxState(Session session, AccountId accountId) async {
try {
final currentMailboxState = await _mailboxRepository.getMailboxState(session, accountId);
log('CreateDefaultMailboxInteractor::_getCurrentMailboxState:currentMailboxState: $currentMailboxState');
return currentMailboxState;
} catch (e) {
return null;
}
}
}
@@ -193,7 +193,7 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM
} else if (failure is RefreshChangesAllMailboxFailure) {
_clearNewFolderId();
} else if (failure is CreateDefaultMailboxFailure) {
refreshAllMailbox();
_refreshMailboxChanges(currentMailboxState: failure.currentMailboxState);
}
}
@@ -127,9 +127,7 @@ class MailboxTree with EquatableMixin {
}
Map<Role, PresentationMailbox> get mapPresentationMailboxByRole {
if (root.childrenItems?.isEmpty == true) {
return {};
} else {
if (root.childrenItems?.isNotEmpty == true) {
final listPresentationMailboxHasRole = root.childrenItems!
.where((node) => node.item.role != null)
.map((node) => node.item)
@@ -139,6 +137,8 @@ class MailboxTree with EquatableMixin {
for (var mailbox in listPresentationMailboxHasRole)
mailbox.role!: mailbox
};
} else {
return {};
}
}
@@ -8,6 +8,7 @@ import 'package:jmap_dart_client/jmap/mail/mailbox/get/get_mailbox_method.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/get/get_mailbox_response.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox_filter_condition.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/query/query_mailbox_method.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/exceptions/spam_report_exception.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/unread_spam_emails_response.dart';
import 'package:tmail_ui_user/main/error/capability_validator.dart';
@@ -50,11 +51,10 @@ class SpamReportApi {
final mailboxResponse = result
.parse<GetMailboxResponse>(getMailboxInvocation.methodCallId, GetMailboxResponse.deserialize);
return Future.sync(() async {
final unreadSpamMailbox = mailboxResponse?.list.first;
return UnreadSpamEmailsResponse(unreadSpamMailbox: unreadSpamMailbox);
}).catchError((error) {
throw error;
});
if (mailboxResponse?.list.isNotEmpty == true) {
return UnreadSpamEmailsResponse(unreadSpamMailbox: mailboxResponse!.list.first);
} else {
throw NotFoundSpamMailboxException();
}
}
}
@@ -1,4 +1,6 @@
class NotFoundLastTimeDismissedSpamReportException implements Exception {}
class NotFoundSpamMailboxCachedException implements Exception {}
class NotFoundSpamMailboxCachedException implements Exception {}
class NotFoundSpamMailboxException implements Exception {}
@@ -0,0 +1,20 @@
import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart';
import 'package:tmail_ui_user/main/routes/router_arguments.dart';
class SessionArguments extends RouterArguments {
final LoginFormType loginFormType;
final String baseUrl;
SessionArguments(
this.loginFormType,
this.baseUrl
);
@override
List<Object?> get props => [
loginFormType,
baseUrl,
];
}
+13 -1
View File
@@ -1,5 +1,5 @@
{
"@@last_modified": "2023-11-14T16:28:55.718679",
"@@last_modified": "2023-11-10T16:53:43.713688",
"initializing_data": "Initializing data...",
"@initializing_data": {
"type": "text",
@@ -3481,5 +3481,17 @@
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"dnsLookupLoginMessage": "To login and access your message please enter your email",
"@dnsLookupLoginMessage": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"enterYourPasswordToSignIn": "Enter your password to sign in",
"@enterYourPasswordToSignIn": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
}
}
@@ -18,6 +18,7 @@ import 'package:tmail_ui_user/features/login/data/local/token_oidc_cache_manager
import 'package:tmail_ui_user/features/login/data/network/authentication_client/authentication_client_base.dart';
import 'package:tmail_ui_user/features/login/data/network/config/authorization_interceptors.dart';
import 'package:tmail_ui_user/features/login/data/network/config/time_out_interceptors.dart';
import 'package:tmail_ui_user/features/login/data/network/dns_service.dart';
import 'package:tmail_ui_user/features/login/data/network/oidc_http_client.dart';
import 'package:tmail_ui_user/features/login/data/utils/library_platform/app_auth_plugin/app_auth_plugin.dart';
import 'package:tmail_ui_user/features/mailbox/data/network/mailbox_api.dart';
@@ -41,6 +42,7 @@ class NetworkBindings extends Bindings {
_bindingDio();
_bindingApi();
_bindingTransformer();
_bindingServices();
_bindingException();
}
@@ -119,4 +121,8 @@ class NetworkBindings extends Bindings {
Get.put(HtmlTransform(Get.find<DioClient>(), Get.find<HtmlEscape>()));
Get.put(HtmlAnalyzer(Get.find<HtmlTransform>()));
}
void _bindingServices() {
Get.put(DNSService());
}
}
@@ -3610,4 +3610,18 @@ class AppLocalizations {
name: 'notFoundSession',
);
}
String get dnsLookupLoginMessage {
return Intl.message(
'To login and access your message please enter your email',
name: 'dnsLookupLoginMessage'
);
}
String get enterYourPasswordToSignIn {
return Intl.message(
'Enter your password to sign in',
name: 'enterYourPasswordToSignIn'
);
}
}
+36
View File
@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart';
import 'package:tmail_ui_user/features/login/data/network/oidc_error.dart';
import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart';
import 'package:tmail_ui_user/main/exceptions/remote_exception.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class MessageToastUtils {
static String? getMessageByException(BuildContext context, dynamic exception) {
if (exception is CanNotFoundBaseUrl) {
return AppLocalizations.of(context).requiredUrl;
} else if (exception is CanNotFoundUserName) {
return AppLocalizations.of(context).requiredEmail;
} else if (exception is CanNotFoundPassword) {
return AppLocalizations.of(context).requiredPassword;
} else if (exception is CanNotFoundOIDCLinks) {
return AppLocalizations.of(context).ssoNotAvailable;
} else if (exception is CanNotFoundToken) {
return AppLocalizations.of(context).canNotGetToken;
} else if (exception is ConnectionTimeout || exception is BadGateway || exception is SocketError) {
return AppLocalizations.of(context).wrongUrlMessage;
} else if (exception is BadCredentialsException) {
return AppLocalizations.of(context).badCredentials;
} else if (exception is ConnectionError) {
return AppLocalizations.of(context).connectionError;
} else if (exception is UnknownError && exception.message != null) {
return '[${exception.code ?? ''}] ${exception.message}';
} else if (exception is NotFoundSessionException) {
return AppLocalizations.of(context).notFoundSession;
} else {
return null;
}
}
}