diff --git a/lib/features/login/domain/state/sign_in_with_applicative_token_state.dart b/lib/features/login/domain/state/sign_in_with_applicative_token_state.dart new file mode 100644 index 0000000000..3dc54281c5 --- /dev/null +++ b/lib/features/login/domain/state/sign_in_with_applicative_token_state.dart @@ -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 SigningInWithApplicativeToken extends LoadingState {} + +class SignInWithApplicativeTokenSuccess extends UIState { + final TokenOIDC tokenOIDC; + final Uri baseUri; + final OIDCConfiguration oidcConfiguration; + + SignInWithApplicativeTokenSuccess(this.tokenOIDC, this.baseUri, this.oidcConfiguration); + + @override + List get props => [tokenOIDC, baseUri, oidcConfiguration]; +} + +class SignInWithApplicativeTokenFailure extends FeatureFailure { + SignInWithApplicativeTokenFailure({super.exception}); +} \ No newline at end of file diff --git a/lib/features/login/domain/usecases/sign_in_with_applicative_token_interactor.dart b/lib/features/login/domain/usecases/sign_in_with_applicative_token_interactor.dart new file mode 100644 index 0000000000..a800c23bf2 --- /dev/null +++ b/lib/features/login/domain/usecases/sign_in_with_applicative_token_interactor.dart @@ -0,0 +1,64 @@ +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:model/account/authentication_type.dart'; +import 'package:model/account/personal_account.dart'; +import 'package:model/oidc/oidc_configuration.dart'; +import 'package:model/oidc/token_id.dart'; +import 'package:model/oidc/token_oidc.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/state/sign_in_with_applicative_token_state.dart'; +import 'package:uuid/uuid.dart'; + +class SignInWithApplicativeTokenInteractor { + final CredentialRepository _credentialRepository; + final AuthenticationOIDCRepository _authenticationOIDCRepository; + final AccountRepository _accountRepository; + + const SignInWithApplicativeTokenInteractor( + this._credentialRepository, + this._authenticationOIDCRepository, + this._accountRepository, + ); + + Stream> execute({ + required String applicativeToken, + required Uri baseUri, + required Uuid uuid, + }) async* { + try { + yield Right(SigningInWithApplicativeToken()); + final tokenOIDC = TokenOIDC(applicativeToken, TokenId(uuid.v4()), ''); + final oidcConfiguration = OIDCConfiguration( + clientId: '', + scopes: [], + authority: '', + ); + await Future.wait([ + _credentialRepository.saveBaseUrl(baseUri), + _authenticationOIDCRepository.persistTokenOIDC(tokenOIDC), + _authenticationOIDCRepository.persistOidcConfiguration(oidcConfiguration), + ]); + + await _accountRepository.setCurrentAccount( + PersonalAccount( + tokenOIDC.tokenIdHash, + AuthenticationType.oidc, + isSelected: true + ) + ); + + yield Right(SignInWithApplicativeTokenSuccess( + tokenOIDC, + baseUri, + oidcConfiguration, + )); + } catch (e) { + logError('SignInWithApplicativeTokenInteractor::execute: $e'); + yield Left(SignInWithApplicativeTokenFailure(exception: e)); + } + } +} \ No newline at end of file diff --git a/lib/features/login/presentation/login_bindings.dart b/lib/features/login/presentation/login_bindings.dart index 2dd140624d..056e019e75 100644 --- a/lib/features/login/presentation/login_bindings.dart +++ b/lib/features/login/presentation/login_bindings.dart @@ -26,6 +26,7 @@ import 'package:tmail_ui_user/features/login/domain/usecases/get_stored_oidc_con import 'package:tmail_ui_user/features/login/domain/usecases/get_token_oidc_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/save_login_url_on_mobile_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/save_login_username_on_mobile_interactor.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/sign_in_with_applicative_token_interactor.dart'; import 'package:tmail_ui_user/features/login/presentation/login_controller.dart'; import 'package:tmail_ui_user/features/starting_page/data/datasource/saas_authentication_datasource.dart'; import 'package:tmail_ui_user/features/starting_page/data/datasource_impl/saas_authentication_datasource_impl.dart'; @@ -54,6 +55,7 @@ class LoginBindings extends BaseBindings { Get.find(), Get.find(), Get.find(), + Get.find(), )); } @@ -117,6 +119,11 @@ class LoginBindings extends BaseBindings { Get.find(), Get.find(), )); + Get.lazyPut(() => SignInWithApplicativeTokenInteractor( + Get.find(), + Get.find(), + Get.find(), + )); } @override diff --git a/lib/features/login/presentation/login_controller.dart b/lib/features/login/presentation/login_controller.dart index a093b9ca62..c0ff48c764 100644 --- a/lib/features/login/presentation/login_controller.dart +++ b/lib/features/login/presentation/login_controller.dart @@ -39,6 +39,8 @@ import 'package:tmail_ui_user/features/login/domain/state/get_oidc_configuration 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_stored_oidc_configuration_state.dart'; import 'package:tmail_ui_user/features/login/domain/state/get_token_oidc_state.dart'; +import 'package:tmail_ui_user/features/login/domain/state/sign_in_with_applicative_token_state.dart'; +import 'package:tmail_ui_user/features/login/domain/state/update_authentication_account_state.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'; @@ -52,6 +54,7 @@ import 'package:tmail_ui_user/features/login/domain/usecases/get_stored_oidc_con import 'package:tmail_ui_user/features/login/domain/usecases/get_token_oidc_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/save_login_url_on_mobile_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/save_login_username_on_mobile_interactor.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/sign_in_with_applicative_token_interactor.dart'; 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/starting_page/domain/state/sign_in_twake_workplace_state.dart'; @@ -82,6 +85,7 @@ class LoginController extends ReloadableController { final GetAllRecentLoginUsernameOnMobileInteractor _getAllRecentLoginUsernameOnMobileInteractor; final DNSLookupToGetJmapUrlInteractor _dnsLookupToGetJmapUrlInteractor; final SignInTwakeWorkplaceInteractor _signInTwakeWorkplaceInteractor; + final SignInWithApplicativeTokenInteractor _signInWithApplicativeTokenInteractor; final TextEditingController urlInputController = TextEditingController(); final TextEditingController usernameInputController = TextEditingController(); @@ -96,6 +100,20 @@ class LoginController extends ReloadableController { UserName? _username; Password? _password; Uri? _baseUri; + String _applicativeToken = ''; + bool get isShowingMessage { + return viewState.value.fold( + (failure) { + // Ignore message when login by applicative token + if (failure is UpdateAccountCacheFailure && _password == null && _applicativeToken.isNotEmpty) { + return false; + } + + return true; + }, + (success) => true + ); + } DeepLinksManager? _deepLinksManager; StreamSubscription? _deepLinkDataStreamSubscription; @@ -115,6 +133,7 @@ class LoginController extends ReloadableController { this._getAllRecentLoginUsernameOnMobileInteractor, this._dnsLookupToGetJmapUrlInteractor, this._signInTwakeWorkplaceInteractor, + this._signInWithApplicativeTokenInteractor, ); @override @@ -200,6 +219,12 @@ class LoginController extends ReloadableController { tokenOIDC: success.tokenOIDC, oidcConfiguration: success.oidcConfiguration, ); + } else if (success is SignInWithApplicativeTokenSuccess) { + _synchronizeTokenAndGetSession( + baseUri: success.baseUri, + tokenOIDC: success.tokenOIDC, + oidcConfiguration: success.oidcConfiguration, + ); } else { super.handleSuccessViewState(success); } @@ -366,25 +391,29 @@ class LoginController extends ReloadableController { log('LoginController::handleLoginPressed:_currentBaseUrl: $_currentBaseUrl | _username: $_username | _password: $_password'); if (_currentBaseUrl == null) { consumeState(Stream.value(Left(AuthenticationUserFailure(CanNotFoundBaseUrl())))); - } else if (_username == null) { + } else if (_username == null && _applicativeToken.isEmpty) { consumeState(Stream.value(Left(AuthenticationUserFailure(CanNotFoundUserName())))); - } else if (_password == null) { + } else if (_password == null && _applicativeToken.isEmpty) { consumeState(Stream.value(Left(AuthenticationUserFailure(CanNotFoundPassword())))); } else { - if (PlatformInfo.isMobile && loginFormType.value == LoginFormType.credentialForm) { + if (PlatformInfo.isMobile && loginFormType.value == LoginFormType.credentialForm && _username != null) { TextInput.finishAutofillContext(); if (_username!.value.isEmail) { _storeUsernameToCache(_username!.value); } } - consumeState( - _authenticationInteractor.execute( - baseUrl: _currentBaseUrl!, - userName: _username!, - password: _password! - ) - ); + if (_password != null && _username != null) { + consumeState( + _authenticationInteractor.execute( + baseUrl: _currentBaseUrl!, + userName: _username!, + password: _password! + ) + ); + } else { + _loginByApplicativeToken(_applicativeToken); + } } } @@ -621,6 +650,18 @@ class LoginController extends ReloadableController { loginFormType.value == LoginFormType.passwordForm || loginFormType.value == LoginFormType.credentialForm; + void onApplicativeTokenChange(String value) { + _applicativeToken = value; + } + + void _loginByApplicativeToken(String token) { + consumeState(_signInWithApplicativeTokenInteractor.execute( + applicativeToken: token, + baseUri: _currentBaseUrl!, + uuid: uuid, + )); + } + @override void onClose() { passFocusNode.dispose(); @@ -634,4 +675,4 @@ class LoginController extends ReloadableController { } super.onClose(); } -} \ No newline at end of file +} diff --git a/lib/features/login/presentation/login_view.dart b/lib/features/login/presentation/login_view.dart index 466c128fc6..04f0b943e9 100644 --- a/lib/features/login/presentation/login_view.dart +++ b/lib/features/login/presentation/login_view.dart @@ -3,6 +3,7 @@ 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_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/widget/application_version_widget.dart'; import 'package:tmail_ui_user/features/base/widget/recent_item_tile_widget.dart'; @@ -10,6 +11,7 @@ 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/applicative_token_field.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'; @@ -90,10 +92,22 @@ class LoginView extends BaseLoginView { style: const TextStyle(fontSize: 32, color: AppColor.colorNameEmail, fontWeight: FontWeight.w900) ) ), - Obx(() => LoginMessageWidget( - formType: controller.loginFormType.value, - viewState: controller.viewState.value, - )), + KeyboardVisibilityBuilder( + builder: (context, visible) { + return Obx(() { + if (visible && (controller.loginFormType.value == LoginFormType.passwordForm + || controller.loginFormType.value == LoginFormType.credentialForm)) { + return const SizedBox.shrink(); + } + + return LoginMessageWidget( + formType: controller.loginFormType.value, + viewState: controller.viewState.value, + isShowingMessage: controller.isShowingMessage, + ); + }); + } + ), Obx(() { switch (controller.loginFormType.value) { case LoginFormType.dnsLookupForm: @@ -122,12 +136,35 @@ class LoginView extends BaseLoginView { return const SizedBox.shrink(); } }), + Obx(() { + switch (controller.loginFormType.value) { + case LoginFormType.passwordForm: + case LoginFormType.credentialForm: + return ApplicativeTokenField( + onChanged: controller.onApplicativeTokenChange, + appLocalizations: AppLocalizations.of(context), + imagePath: controller.imagePaths, + ); + default: + return const SizedBox.shrink(); + } + }), _buildLoadingProgress(context), - const Padding( - padding: EdgeInsets.only(top: 16), - child: PrivacyLinkWidget(), + KeyboardVisibilityBuilder( + builder: (context, visible) { + if (visible) return const SizedBox.shrink(); + + return const Column( + children: [ + Padding( + padding: EdgeInsets.only(top: 16), + child: PrivacyLinkWidget(), + ), + ApplicationVersionWidget(), + ], + ); + }, ), - const ApplicationVersionWidget(), ] ), ) @@ -251,4 +288,4 @@ class LoginView extends BaseLoginView { } )); } -} \ No newline at end of file +} diff --git a/lib/features/login/presentation/widgets/applicative_token_field.dart b/lib/features/login/presentation/widgets/applicative_token_field.dart new file mode 100644 index 0000000000..0e8c2b332f --- /dev/null +++ b/lib/features/login/presentation/widgets/applicative_token_field.dart @@ -0,0 +1,99 @@ +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/views/text/text_field_builder.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:tmail_ui_user/features/base/widget/text_input_decoration_builder.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class ApplicativeTokenField extends StatefulWidget { + final ValueChanged onChanged; + final AppLocalizations appLocalizations; + final ImagePaths? imagePath; + + const ApplicativeTokenField({ + Key? key, + required this.onChanged, + required this.appLocalizations, + this.imagePath, + }) : super(key: key); + + @override + State createState() => _ApplicativeTokenFieldState(); +} + +class _ApplicativeTokenFieldState extends State with SingleTickerProviderStateMixin { + final controller = TextEditingController(); + final focusNode = FocusNode(); + late final AnimationController rotationController; + + @override + void initState() { + super.initState(); + rotationController = AnimationController( + duration: const Duration(milliseconds: 200), + vsync: this, + ); + } + + @override + void dispose() { + controller.dispose(); + focusNode.dispose(); + rotationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ExpansionTile( + title: Text(widget.appLocalizations.advancedSettings), + controlAffinity: ListTileControlAffinity.leading, + leading: widget.imagePath == null + ? null + : RotationTransition( + turns: Tween(begin: 0.0, end: 0.5).animate(rotationController), + child: SvgPicture.asset( + widget.imagePath!.icArrowDown, + width: 24, + height: 24, + fit: BoxFit.fill, + ), + ), + shape: const Border(), + tilePadding: const EdgeInsets.symmetric(horizontal: 32), + childrenPadding: const EdgeInsets.symmetric(horizontal: 32), + onExpansionChanged: (isExpanded) { + if (isExpanded) { + rotationController.forward(); + } else { + rotationController.reverse(); + } + }, + children: [ + Row( + children: [ + Expanded( + child: Text( + widget.appLocalizations.applicativeToken, + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + maxLines: 2, + ), + ), + const SizedBox(width: 8), + Expanded( + child: TextFieldBuilder( + controller: controller, + focusNode: focusNode, + autoFocus: true, + onTextChange: widget.onChanged, + maxLines: 1, + decoration: TextInputDecorationBuilder().build(), + ), + ), + ], + ), + Text(widget.appLocalizations.someJMAPServicesDoNotSupportLoginViaPassword), + ], + ); + } +} diff --git a/lib/features/login/presentation/widgets/login_message_widget.dart b/lib/features/login/presentation/widgets/login_message_widget.dart index be82e3da63..78ec6680d9 100644 --- a/lib/features/login/presentation/widgets/login_message_widget.dart +++ b/lib/features/login/presentation/widgets/login_message_widget.dart @@ -22,13 +22,15 @@ class LoginMessageWidget extends StatelessWidget { final LoginFormType formType; final Either viewState; + final bool isShowingMessage; final ToastManager? _toastManager = getBinding(); LoginMessageWidget({ super.key, required this.formType, - required this.viewState + required this.viewState, + this.isShowingMessage = true, }); @override @@ -45,35 +47,7 @@ class LoginMessageWidget extends StatelessWidget { ? _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 GetTokenOIDCFailure && failure.exception is NoSuitableBrowserForOIDCException) { - return AppLocalizations.of(context).noSuitableBrowserForOIDC; - } else if (failure is FeatureFailure) { - return _toastManager?.getMessageByException(context, failure.exception) - ?? 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 ''; - } - } - ), + isShowingMessage ? messageFromViewState(context) : '', textAlign: TextAlign.center, style: TextStyle( fontSize: 15, @@ -87,4 +61,36 @@ class LoginMessageWidget extends StatelessWidget { ) ); } + + String messageFromViewState(BuildContext context) { + return 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 GetTokenOIDCFailure && failure.exception is NoSuitableBrowserForOIDCException) { + return AppLocalizations.of(context).noSuitableBrowserForOIDC; + } else if (failure is FeatureFailure) { + return _toastManager?.getMessageByException(context, failure.exception) + ?? 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 ''; + } + } + ); + } } diff --git a/lib/features/mailbox_dashboard/data/network/linagora_ecosystem_api.dart b/lib/features/mailbox_dashboard/data/network/linagora_ecosystem_api.dart index 3d86b73c32..1339672734 100644 --- a/lib/features/mailbox_dashboard/data/network/linagora_ecosystem_api.dart +++ b/lib/features/mailbox_dashboard/data/network/linagora_ecosystem_api.dart @@ -14,15 +14,19 @@ class LinagoraEcosystemApi { LinagoraEcosystemApi(this._dioClient); Future getLinagoraEcosystem(String baseUrl) async { - final result = await _dioClient.get( - Endpoint.linagoraEcosystem.usingBaseUrl(baseUrl).generateEndpointPath(), - ); - log('LinagoraEcosystemApi::getLinagoraEcosystem: $result'); - if (result is Map) { - return LinagoraEcosystem.deserialize(result); - } else if (result is String) { - return LinagoraEcosystem.deserialize(jsonDecode(result)); - } else { + try { + final result = await _dioClient.get( + Endpoint.linagoraEcosystem.usingBaseUrl(baseUrl).generateEndpointPath(), + ); + log('LinagoraEcosystemApi::getLinagoraEcosystem: $result'); + if (result is Map) { + return LinagoraEcosystem.deserialize(result); + } else if (result is String) { + return LinagoraEcosystem.deserialize(jsonDecode(result)); + } else { + throw NotFoundLinagoraEcosystem(); + } + } catch (_) { throw NotFoundLinagoraEcosystem(); } } diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index 4591219a1c..8abc875d64 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -182,8 +182,6 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { }), mobile: Obx(() { switch(controller.dashboardRoute.value) { - case DashboardRoutes.thread: - return _buildScaffoldHaveDrawer(body: ThreadView()); case DashboardRoutes.emailDetailed: return const EmailView(); case DashboardRoutes.searchEmail: diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 8e390a7316..44f67d9891 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -4268,6 +4268,24 @@ "placeholders_order": [], "placeholders": {} }, + "advancedSettings": "Advanced settings", + "@advancedSettings": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "applicativeToken": "Applicative token", + "@applicativeToken": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "someJMAPServicesDoNotSupportLoginViaPassword": "Some JMAP services do not support login via password for third party apps but instead allow generate applicative tokens.", + "@someJMAPServicesDoNotSupportLoginViaPassword": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, "downloadAttachmentInEMLPreviewWarningMessage": "Downloading attachment. You can only download one file at a time.", "@downloadAttachmentInEMLPreviewWarningMessage": { "type": "text", diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 0e4c8373eb..f842ae0da2 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4472,6 +4472,27 @@ class AppLocalizations { ); } + String get advancedSettings { + return Intl.message( + 'Advanced settings', + name: 'advancedSettings', + ); + } + + String get applicativeToken { + return Intl.message( + 'Applicative token', + name: 'applicativeToken', + ); + } + + String get someJMAPServicesDoNotSupportLoginViaPassword { + return Intl.message( + 'Some JMAP services do not support login via password for third party apps but instead allow generate applicative tokens.', + name: 'someJMAPServicesDoNotSupportLoginViaPassword', + ); + } + String get downloadAttachmentInEMLPreviewWarningMessage { return Intl.message( 'Downloading attachment. You can only download one file at a time.', diff --git a/test/features/login/presentation/login_controller_test.dart b/test/features/login/presentation/login_controller_test.dart index 49424fc912..e5a04da1dc 100644 --- a/test/features/login/presentation/login_controller_test.dart +++ b/test/features/login/presentation/login_controller_test.dart @@ -28,6 +28,7 @@ import 'package:tmail_ui_user/features/login/domain/usecases/get_stored_oidc_con import 'package:tmail_ui_user/features/login/domain/usecases/get_token_oidc_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/save_login_url_on_mobile_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/save_login_username_on_mobile_interactor.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/sign_in_with_applicative_token_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/update_account_cache_interactor.dart'; import 'package:tmail_ui_user/features/login/presentation/login_controller.dart'; import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart'; @@ -73,6 +74,7 @@ import 'login_controller_test.mocks.dart'; MockSpec(), MockSpec(), MockSpec(), + MockSpec(), ]) void main() { late MockAuthenticationInteractor mockAuthenticationInteractor; @@ -106,7 +108,7 @@ void main() { late MockApplicationManager mockApplicationManager; late MockToastManager mockToastManager; late MockTwakeAppManager mockTwakeAppManager; - + late MockSignInWithApplicativeTokenInteractor mockSignInWithApplicativeTokenInteractor; late LoginController loginController; group('Test handleFailureViewState with GetTokenOIDCFailure', () { @@ -125,7 +127,7 @@ void main() { mockGetAllRecentLoginUsernameOnMobileInteractor = MockGetAllRecentLoginUsernameOnMobileInteractor(); mockDNSLookupToGetJmapUrlInteractor = MockDNSLookupToGetJmapUrlInteractor(); mockSignInTwakeWorkplaceInteractor = MockSignInTwakeWorkplaceInteractor(); - + mockSignInWithApplicativeTokenInteractor = MockSignInWithApplicativeTokenInteractor(); // mock reloadable controller mockGetSessionInteractor = MockGetSessionInteractor(); mockGetAuthenticatedAccountInteractor = MockGetAuthenticatedAccountInteractor(); @@ -185,6 +187,7 @@ void main() { mockGetAllRecentLoginUsernameOnMobileInteractor, mockDNSLookupToGetJmapUrlInteractor, mockSignInTwakeWorkplaceInteractor, + mockSignInWithApplicativeTokenInteractor, );