import 'dart:async'; import 'package:core/presentation/extensions/url_extension.dart'; import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:core/presentation/utils/keyboard_utils.dart'; import 'package:core/utils/app_logger.dart'; import 'package:core/utils/platform_info.dart'; import 'package:dartz/dartz.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:model/account/password.dart'; import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/request/oidc_request.dart'; import 'package:model/oidc/response/oidc_response.dart'; import 'package:tmail_ui_user/features/base/reloadable/reloadable_controller.dart'; import 'package:tmail_ui_user/features/home/domain/state/auto_sign_in_via_deep_link_state.dart'; import 'package:tmail_ui_user/features/home/domain/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/exceptions/login_exception.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'; 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'; import 'package:tmail_ui_user/features/login/domain/state/get_authentication_info_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_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/try_guessing_web_finger_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'; 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'; import 'package:tmail_ui_user/features/login/domain/usecases/get_oidc_configuration_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/get_stored_oidc_configuration_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/get_token_oidc_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/remove_auth_destination_url_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/remove_company_server_login_info_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/save_company_server_login_info_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/try_guessing_web_finger_interactor.dart'; import 'package:tmail_ui_user/features/login/presentation/extensions/generate_oidc_guessing_urls.dart'; import 'package:tmail_ui_user/features/login/presentation/extensions/handle_company_server_login_info_extension.dart'; import 'package:tmail_ui_user/features/login/presentation/extensions/handle_openid_configuration.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'; import 'package:tmail_ui_user/features/starting_page/domain/usecase/sign_in_twake_workplace_interactor.dart'; import 'package:tmail_ui_user/main/deep_links/deep_link_data.dart'; import 'package:tmail_ui_user/main/deep_links/deep_links_manager.dart'; import 'package:tmail_ui_user/main/deep_links/open_app_deep_link_data.dart'; import 'package:tmail_ui_user/main/exceptions/remote/network_exception.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/routes/route_utils.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart'; class LoginController extends ReloadableController { final AuthenticationInteractor _authenticationInteractor; final CheckOIDCIsAvailableInteractor _checkOIDCIsAvailableInteractor; final GetOIDCConfigurationInteractor _getOIDCConfigurationInteractor; final GetTokenOIDCInteractor _getTokenOIDCInteractor; final AuthenticateOidcOnBrowserInteractor _authenticateOidcOnBrowserInteractor; final GetAuthenticationInfoInteractor _getAuthenticationInfoInteractor; final GetStoredOidcConfigurationInteractor _getStoredOidcConfigurationInteractor; final SaveLoginUrlOnMobileInteractor _saveLoginUrlOnMobileInteractor; final GetAllRecentLoginUrlOnMobileInteractor _getAllRecentLoginUrlOnMobileInteractor; final SaveLoginUsernameOnMobileInteractor _saveLoginUsernameOnMobileInteractor; final GetAllRecentLoginUsernameOnMobileInteractor _getAllRecentLoginUsernameOnMobileInteractor; final DNSLookupToGetJmapUrlInteractor _dnsLookupToGetJmapUrlInteractor; final SignInTwakeWorkplaceInteractor _signInTwakeWorkplaceInteractor; final TryGuessingWebFingerInteractor _tryGuessingWebFingerInteractor; final RemoveAuthDestinationUrlInteractor _removeAuthDestinationUrlInteractor; SaveCompanyServerLoginInfoInteractor? saveLoginInfoInteractor; RemoveCompanyServerLoginInfoInteractor? removeLoginInfoInteractor; 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; UserName? _username; Password? _password; Uri? _baseUri; FeatureFailure? featureFailure; DeepLinksManager? _deepLinksManager; StreamSubscription? _deepLinkDataStreamSubscription; LoginController( this._authenticationInteractor, this._checkOIDCIsAvailableInteractor, this._getOIDCConfigurationInteractor, this._getTokenOIDCInteractor, this._authenticateOidcOnBrowserInteractor, this._getAuthenticationInfoInteractor, this._getStoredOidcConfigurationInteractor, this._saveLoginUrlOnMobileInteractor, this._getAllRecentLoginUrlOnMobileInteractor, this._saveLoginUsernameOnMobileInteractor, this._getAllRecentLoginUsernameOnMobileInteractor, this._dnsLookupToGetJmapUrlInteractor, this._signInTwakeWorkplaceInteractor, this._tryGuessingWebFingerInteractor, this._removeAuthDestinationUrlInteractor, ); @override void onInit() { super.onInit(); if (PlatformInfo.isMobile) { _registerDeepLinks(); } } @override void onReady() { super.onReady(); final arguments = Get.arguments; log('LoginController::onReady: arguments = $arguments'); if (arguments is LoginArguments) { loginFormType.value = arguments.loginFormType == LoginFormType.passwordForm ? LoginFormType.dnsLookupForm : arguments.loginFormType; if (hasPreviousCompanyServerLogin(arguments)) { autoFillPreviousCompanyServerMail(arguments.loginInfo!); } else if (isCompanyServerLoginFlow(arguments)) { autoFillCompanyServerMail(); } else if (PlatformInfo.isWeb) { _checkOIDCIsAvailable(); } } else if (PlatformInfo.isWeb) { _getAuthenticationInfo(); } } @override void handleFailureViewState(Failure failure) { log('$runtimeType::handleFailureViewState():Failure = $failure'); if (failure is GetAuthenticationInfoFailure) { getAuthenticatedAccountAction(); } else if (failure is CheckOIDCIsAvailableFailure) { _handleCheckOIDCIsAvailableFailure(failure); } else if (failure is GetOIDCConfigurationFromBaseUrlFailure) { handleGetOIDCConfigurationFromBaseUrlFailure(); } else if (failure is GetStoredOidcConfigurationFailure || failure is GetOIDCConfigurationFailure || failure is SignInTwakeWorkplaceFailure ) { _handleCommonOIDCFailure(); } else if (failure is GetTokenOIDCFailure) { _handleNoSuitableBrowserOIDC(failure) .map((stillFailed) => _handleCommonOIDCFailure()); } else if (failure is GetAuthenticatedAccountFailure) { _checkOIDCIsAvailable(); } else if (failure is GetSessionFailure) { SmartDialog.dismiss(); clearAllData(); } else if (failure is DNSLookupToGetJmapUrlFailure) { _handleDNSLookupToGetJmapUrlFailure(failure); } else if (failure is TryGuessingWebFingerFailure) { _username = null; _clearTextInputField(); _showBaseUrlForm(); } else { super.handleFailureViewState(failure); } } @override void handleSuccessViewState(Success success) { log('$runtimeType::handleSuccessViewState:Success = ${success.runtimeType}'); if (success is GetAuthenticationInfoSuccess) { _getStoredOidcConfiguration(); } else if (success is GetStoredOidcConfigurationSuccess) { _getTokenOIDCAction(success.oidcConfiguration); } else if (success is CheckOIDCIsAvailableSuccess) { getOIDCConfiguration(success.oidcResponse); } else if (success is GetOIDCConfigurationSuccess) { _getOIDCConfigurationSuccess(success.oidcConfiguration); } else if (success is GetTokenOIDCSuccess) { _getTokenOIDCSuccess(success); } else if (success is AuthenticationUserSuccess) { _loginSuccessAction(success); } else if (success is DNSLookupToGetJmapUrlSuccess) { _handleDNSLookupToGetJmapUrlSuccess(success); } else if (success is TryGuessingWebFingerSuccess) { onBaseUrlChange(success.oidcResponse.subject); getOIDCConfiguration(success.oidcResponse); } else if (success is SignInTwakeWorkplaceSuccess) { if (PlatformInfo.isMobile) { removeCompanyServerLoginInfo(); } synchronizeTokenAndGetSession( baseUri: success.baseUri, tokenOIDC: success.tokenOIDC, oidcConfiguration: success.oidcConfiguration, ); } else { super.handleSuccessViewState(success); } } @override void handleUrgentException({Failure? failure, Exception? exception}) { logWarning('LoginController::handleUrgentException:Exception: $exception | Failure: $failure'); if (failure is CheckOIDCIsAvailableFailure) { _handleCheckOIDCIsAvailableFailure(failure); } else if (failure is GetOIDCConfigurationFromBaseUrlFailure) { handleGetOIDCConfigurationFromBaseUrlFailure(); } else if (failure is GetStoredOidcConfigurationFailure || failure is GetOIDCConfigurationFailure || failure is SignInTwakeWorkplaceFailure ) { _handleCommonOIDCFailure(); } else if (failure is GetTokenOIDCFailure) { _handleNoSuitableBrowserOIDC(failure) .map((stillFailed) => _handleCommonOIDCFailure()); } else if (failure is GetSessionFailure) { SmartDialog.dismiss(); clearAllData(); } else if (connectionErrorWhenLookupDns(failure)) { _handleDNSLookupToGetJmapUrlFailure( failure as DNSLookupToGetJmapUrlFailure, ); } else { super.handleUrgentException(failure: failure, exception: exception); } } @override Future handleReloaded(Session session) async { SmartDialog.dismiss(); if (PlatformInfo.isWeb) { try { await cachingManager.clearAllEmailAndStateCache(); } catch (e) { logWarning('$runtimeType::handleReloaded: Failed to clear cache: $e'); } } popAndPush( RouteUtils.generateNavigationRoute(AppRoutes.dashboard), arguments: session, ); } bool connectionErrorWhenLookupDns(Failure? failure) { return failure is DNSLookupToGetJmapUrlFailure && failure.exception is ConnectionError; } void _handleDNSLookupToGetJmapUrlFailure( DNSLookupToGetJmapUrlFailure failure, ) { consumeState(_tryGuessingWebFingerInteractor.execute( generateOidcGuessingUrls(failure.email), )); } void _registerDeepLinks() { _deepLinksManager = getBinding(); _deepLinksManager?.clearPendingDeepLinkData(); _deepLinkDataStreamSubscription = _deepLinksManager ?.pendingDeepLinkData.stream .listen(_handlePendingDeepLinkDataStream); } void _handlePendingDeepLinkDataStream(DeepLinkData? deepLinkData) { log('LoginController::_handlePendingDeepLinkDataStream:DeepLinkData = $deepLinkData'); _deepLinksManager?.handleDeepLinksWhenAppRunning( deepLinkData: deepLinkData, onSuccessCallback: (deepLinkData) { if (deepLinkData is! OpenAppDeepLinkData) return; if (currentContext != null) { SmartDialog.showLoading(msg: AppLocalizations.of(currentContext!).loadingPleaseWait); } _deepLinksManager?.autoSignInViaDeepLink( openAppDeepLinkData: deepLinkData, onAutoSignInSuccessCallback: _handleAutoSignInViaDeepLinkSuccess, onFailureCallback: SmartDialog.dismiss, ); }, ); } void _handleAutoSignInViaDeepLinkSuccess(AutoSignInViaDeepLinkSuccess success) { if (PlatformInfo.isMobile) { removeCompanyServerLoginInfo(); } synchronizeTokenAndGetSession( baseUri: success.baseUri, tokenOIDC: success.tokenOIDC, oidcConfiguration: success.oidcConfiguration, ); } void _handleCheckOIDCIsAvailableFailure(CheckOIDCIsAvailableFailure failure) { try { featureFailure = failure; tryGetOIDCConfigurationFromBaseUrl(_currentBaseUrl!); } catch (e) { logWarning('LoginController::_handleCheckOIDCIsAvailableFailure:Exception = $e'); handleOIDCIsNotAvailable(failure); } } void handleOIDCIsNotAvailable(FeatureFailure? failure) { if (failure?.exception is CanNotFoundOIDCLinks || failure?.exception is InvalidOIDCResponseException) { _handleCommonOIDCFailure(); } else { loginFormType.value = LoginFormType.retry; } featureFailure = null; } void retryCheckOidc() { if (PlatformInfo.isMobile) { loginFormType.value = LoginFormType.dnsLookupForm; } else { loginFormType.value = LoginFormType.none; } _checkOIDCIsAvailable(); } void _getAuthenticationInfo() { consumeState(_getAuthenticationInfoInteractor.execute()); } void _getStoredOidcConfiguration() { consumeState(_getStoredOidcConfigurationInteractor.execute()); } void handleNextInUrlInputFormPress() { if (PlatformInfo.isMobile && _currentBaseUrl != null) { _storeBaseUrlToCache(_currentBaseUrl!); } _checkOIDCIsAvailable(); } void _checkOIDCIsAvailable() { if (_currentBaseUrl == null) { dispatchState(Left(CheckOIDCIsAvailableFailure(CanNotFoundBaseUrl()))); } else { consumeState(_checkOIDCIsAvailableInteractor.execute( OIDCRequest( baseUrl: _currentBaseUrl!.toString(), resourceUrl: _currentBaseUrl!.origin ) )); } } void handleBackButtonAction(BuildContext context) { KeyboardUtils.hideKeyboard(context); clearState(); switch(loginFormType.value) { case LoginFormType.dnsLookupForm: case LoginFormType.baseUrlForm: navigateToTwakeWelcomePage(); break; case LoginFormType.passwordForm: _password = null; _baseUri = null; urlInputController.clear(); passwordInputController.clear(); loginFormType.value = LoginFormType.dnsLookupForm; break; case LoginFormType.credentialForm: _password = null; _username = null; usernameInputController.clear(); passwordInputController.clear(); loginFormType.value = LoginFormType.baseUrlForm; break; default: break; } } void _showCredentialForm() { clearState(); loginFormType.value = LoginFormType.credentialForm; userNameFocusNode.requestFocus(); } void handleLoginPressed(BuildContext context) { KeyboardUtils.hideKeyboard(context); 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 { 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! ) ); } } void getOIDCConfiguration(OIDCResponse oidcResponse) { final loginHint = PlatformInfo.isMobile ? _username?.value : null; log('$runtimeType::getOIDCConfiguration:loginHint = $loginHint'); consumeState( _getOIDCConfigurationInteractor.execute( oidcResponse, loginHint: loginHint, ), ); } void _getOIDCConfigurationSuccess(OIDCConfiguration config) { if (PlatformInfo.isWeb) { _authenticateOidcOnBrowserAction(config); } else if (config.authority == AppConfig.saasRegistrationUrl) { _getTokenOIDCOnSaaSPlatform(config); } else { _getTokenOIDCAction(config); } } void _getTokenOIDCOnSaaSPlatform(OIDCConfiguration oidcConfiguration) { if (_currentBaseUrl != null) { consumeState(_signInTwakeWorkplaceInteractor.execute( baseUri: _currentBaseUrl!, oidcConfiguration: oidcConfiguration, )); } else { dispatchState(Left(GetTokenOIDCFailure(CanNotFoundBaseUrl()))); } } void _getTokenOIDCAction(OIDCConfiguration config) { if (_currentBaseUrl != null) { consumeState(_getTokenOIDCInteractor.execute(_currentBaseUrl!, config)); } else { dispatchState(Left(GetTokenOIDCFailure(CanNotFoundBaseUrl()))); } } void _authenticateOidcOnBrowserAction(OIDCConfiguration config) { _removeAuthDestinationUrl(); consumeState(_authenticateOidcOnBrowserInteractor.execute(config)); } void _removeAuthDestinationUrl() { consumeState(_removeAuthDestinationUrlInteractor.execute()); } void _getTokenOIDCSuccess(GetTokenOIDCSuccess success) { if (isDnsLookupFormOnMobile && _username != null) { saveCompanyServerLoginInfo(_username!.value); } else if (PlatformInfo.isMobile) { removeCompanyServerLoginInfo(); } synchronizeTokenAndGetSession( baseUri: _currentBaseUrl!, tokenOIDC: success.tokenOIDC, oidcConfiguration: success.configuration, ); } void _loginSuccessAction(AuthenticationUserSuccess success) { dynamicUrlInterceptors.setJmapUrl(_currentBaseUrl?.toString()); dynamicUrlInterceptors.changeBaseUrl(_currentBaseUrl?.toString()); authorizationInterceptors.setBasicAuthorization(_username!, _password!); authorizationIsolateInterceptors.setBasicAuthorization(_username!, _password!); getSessionAction(); } void selectBaseUrlFromSuggestion(String url) { final validUrl = url.isValid() ? url.removePrefix() : url; log('LoginController::selectBaseUrlFromSuggestion:validUrl: $validUrl'); urlInputController.text = validUrl; onBaseUrlChange(validUrl); } void _storeBaseUrlToCache(Uri uri) { log('LoginController::_storeBaseUrlToCache:uri: $uri'); _saveLoginUrlOnMobileInteractor.execute(RecentLoginUrl.now(uri.toString())); } Future> getAllRecentLoginUrlAction(String pattern) async { return await _getAllRecentLoginUrlOnMobileInteractor .execute(pattern: pattern) .then((result) => result.fold( (failure) => [], (success) => success is GetAllRecentLoginUrlLatestSuccess ? success.listRecentLoginUrl : [] )); } 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 _storeUsernameToCache(String userName) { log('LoginController::_storeUsername():userName: $userName'); _saveLoginUsernameOnMobileInteractor.execute(RecentLoginUsername.now(userName)); } Future> getAllRecentLoginUsernameAction({String? pattern}) async { return await _getAllRecentLoginUsernameOnMobileInteractor .execute(pattern: pattern) .then((result) => result.fold( (failure) => [], (success) => success is GetAllRecentLoginUsernameLatestSuccess ? success.listRecentLoginUsername : [] )); } Uri? get _currentBaseUrl => PlatformInfo.isWeb ? Uri.tryParse(AppConfig.baseUrl) : _baseUri; void invokeDNSLookupToGetJmapUrl() { log('LoginController::invokeDNSLookupToGetJmapUrl:_username $_username'); FocusManager.instance.primaryFocus?.unfocus(); 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 _handleCommonOIDCFailure() { if (PlatformInfo.isMobile) { removeCompanyServerLoginInfo(); } if (PlatformInfo.isMobile && loginFormType.value == LoginFormType.dnsLookupForm) { _showPasswordForm(); } else { _showCredentialForm(); } } Option _handleNoSuitableBrowserOIDC(GetTokenOIDCFailure failure) { if (failure.exception is NoSuitableBrowserForOIDCException) { return const None(); } else { return Some(failure); } } 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); } if (loginFormType.value == LoginFormType.retry && PlatformInfo.isMobile) { loginFormType.value = LoginFormType.dnsLookupForm; } } 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(); } bool get isBackButtonActivated => loginFormType.value == LoginFormType.dnsLookupForm || loginFormType.value == LoginFormType.passwordForm || loginFormType.value == LoginFormType.credentialForm; @override void onClose() { featureFailure = null; passFocusNode.dispose(); baseUrlFocusNode.dispose(); userNameFocusNode.dispose(); urlInputController.dispose(); usernameInputController.dispose(); passwordInputController.dispose(); if (PlatformInfo.isMobile) { _deepLinkDataStreamSubscription?.cancel(); saveLoginInfoInteractor = null; removeLoginInfoInteractor = null; } super.onClose(); } }