diff --git a/build.gradle b/build.gradle index 85b155e403..4449e4366d 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ buildscript { classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.0.0.2929' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6' classpath "com.likethesalad.android:stem-plugin:2.9.0" - classpath 'org.owasp:dependency-check-gradle:8.2.1' + classpath 'org.owasp:dependency-check-gradle:11.1.0' classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.8.10" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' diff --git a/changelog.d/1104.feature b/changelog.d/1104.feature new file mode 100644 index 0000000000..e9aa5e0e82 --- /dev/null +++ b/changelog.d/1104.feature @@ -0,0 +1 @@ +Connexion des utilisateurs avec ProConnect \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values-fr/strings.xml b/library/ui-strings/src/main/res/values-fr/strings.xml index 521c4d5328..8bb1be8c41 100644 --- a/library/ui-strings/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -251,7 +251,7 @@ Nouvel appel vidéo Envoyer des fichiers Prendre une photo ou une vidéo - Je me connecte + Se connecter Valider Nom d’utilisateur et/ou mot de passe incorrect Mot de passe oublié \? @@ -1054,7 +1054,7 @@ S’authentifier sur %1$s Je m’inscris Je me connecte - Continuer avec l’authentification unique + Continuer avec %s Adresse Element Matrix Services Adresse Hébergement privé pour les organisations diff --git a/library/ui-strings/src/main/res/values-fr/strings_tchap.xml b/library/ui-strings/src/main/res/values-fr/strings_tchap.xml index 87e059a5ad..a61fdf2f97 100644 --- a/library/ui-strings/src/main/res/values-fr/strings_tchap.xml +++ b/library/ui-strings/src/main/res/values-fr/strings_tchap.xml @@ -8,6 +8,7 @@ Les mots de passe ne correspondent pas + %s est désactivé pour votre domaine Inviter par e\u2011mail @@ -76,9 +77,11 @@ Nouvel e\u2011mail envoyé - Je n’ai pas\nde compte - J’ai un compte - Adresse email + Se connecter par mot de passe + La messagerie instantanée du secteur public + ➜ Qu’est-ce que %s ? + ➜ Est-ce que %s est activé pour mon administration ? + Adresse mail professionnelle Utilisez votre adresse professionnelle Votre mot de passe doit contenir au moins 8 caractères, avec au moins un caractère de chaque type : majuscule, minuscule, chiffre, caractère spécial. Confirmer le mot de passe diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index d85ec22e45..350dd852dc 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -2129,7 +2129,7 @@ Sign in to %1$s Sign Up Sign In - Continue with SSO + Continue with %s Clear history Element Matrix Services Address diff --git a/library/ui-strings/src/main/res/values/strings_tchap.xml b/library/ui-strings/src/main/res/values/strings_tchap.xml index 24df4d73ff..33130f10af 100644 --- a/library/ui-strings/src/main/res/values/strings_tchap.xml +++ b/library/ui-strings/src/main/res/values/strings_tchap.xml @@ -8,6 +8,7 @@ Passwords don’t match + %s is disabled for your domain Send an invitation @@ -76,9 +77,11 @@ Email sent - I do not have\nan account - I already have\nan account - Email + Login by password + Instant messaging of public sector + ➜ What is %s ? + Is %s available for my organization ? + Professional email Use your business address Your password must include a lower-case letter, an upper-case letter, a number and a symbol and be at a minimum 8 characters in length. Password confirmation diff --git a/library/ui-styles/src/main/res/drawable/ic_tchap_proconnect.xml b/library/ui-styles/src/main/res/drawable/ic_tchap_proconnect.xml new file mode 100644 index 0000000000..225b46e561 --- /dev/null +++ b/library/ui-styles/src/main/res/drawable/ic_tchap_proconnect.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt index 98542d2086..a866ff63b1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt @@ -111,5 +111,15 @@ fun RegistrationFlowResponse.toFlowResult(): FlowResult { fun RegistrationFlowResponse.nextUncompletedStage(flowIndex: Int = 0): String? { val completed = completedStages ?: emptyList() - return flows?.getOrNull(flowIndex)?.stages?.firstOrNull { completed.contains(it).not() } + val flows = flows ?: return null + + // TCHAP return LoginFlowTypes.SSO if SSO type is supported by UIA. + if (flowIndex == 0) { + flows.forEach { + if (!it.stages.isNullOrEmpty() && LoginFlowTypes.SSO !in completed && LoginFlowTypes.SSO in it.stages) { + return LoginFlowTypes.SSO + } + } + } + return flows.getOrNull(flowIndex)?.stages?.firstOrNull { it !in completed } } diff --git a/towncrier.toml b/towncrier.toml index 8b5f9ff767..967a53a495 100644 --- a/towncrier.toml +++ b/towncrier.toml @@ -1,5 +1,5 @@ [tool.towncrier] - version = "2.15.1" + version = "2.16.0" directory = "changelog.d" filename = "TCHAP_CHANGES.md" name = "Changes in Tchap" diff --git a/vector-app/build.gradle b/vector-app/build.gradle index 228fde72da..4919d244ef 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -33,11 +33,11 @@ knit { // Note: 2 digits max for each value ext.versionMajor = 2 -ext.versionMinor = 15 +ext.versionMinor = 16 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -ext.versionPatch = 1 +ext.versionPatch = 0 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index 6a3b20899a..f0fba975ae 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -52,6 +52,8 @@ class DebugVectorFeatures( override fun tchapIsSecureBackupRequired() = vectorFeatures.tchapIsSecureBackupRequired() + override fun tchapIsSSOEnabled() = vectorFeatures.tchapIsSSOEnabled() + override fun onboardingVariant(): OnboardingVariant { return readPreferences().getEnum() ?: vectorFeatures.onboardingVariant() } diff --git a/vector-config/src/btchap/res/values/config-features.xml b/vector-config/src/btchap/res/values/config-features.xml index 60e0cb1865..c890574e82 100755 --- a/vector-config/src/btchap/res/values/config-features.xml +++ b/vector-config/src/btchap/res/values/config-features.xml @@ -4,6 +4,7 @@ true false true + true diff --git a/vector-config/src/devTchap/res/values/config-features.xml b/vector-config/src/devTchap/res/values/config-features.xml index 60e0cb1865..c890574e82 100755 --- a/vector-config/src/devTchap/res/values/config-features.xml +++ b/vector-config/src/devTchap/res/values/config-features.xml @@ -4,6 +4,7 @@ true false true + true diff --git a/vector-config/src/tchap/res/values/config-features.xml b/vector-config/src/tchap/res/values/config-features.xml index d5c1f74fb4..612c7a050d 100755 --- a/vector-config/src/tchap/res/values/config-features.xml +++ b/vector-config/src/tchap/res/values/config-features.xml @@ -4,6 +4,7 @@ true false false + true diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index a15f56a045..c4b8e3e4ce 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -35,7 +35,7 @@ - + @@ -60,7 +60,7 @@ - @@ -102,7 +102,7 @@ android:name="android.max_aspect" android:value="9.9" /> - + @@ -120,16 +120,16 @@ android:theme="@style/Theme.Vector.Black.Transparent"> - - + + - - + + - - - - + + - + diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt index f0b8a6c060..64029ac065 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -32,6 +32,7 @@ interface VectorFeatures { fun tchapIsThreadEnabled(): Boolean fun tchapIsLabsVisible(domain: String): Boolean fun tchapIsSecureBackupRequired(): Boolean + fun tchapIsSSOEnabled(): Boolean fun onboardingVariant(): OnboardingVariant fun isOnboardingAlreadyHaveAccountSplashEnabled(): Boolean fun isOnboardingSplashCarouselEnabled(): Boolean @@ -72,6 +73,7 @@ class DefaultVectorFeatures @Inject constructor( override fun tchapIsLabsVisible(domain: String) = booleanProvider.getBoolean(im.vector.app.config.R.bool.settings_root_labs_visible) || domain == appNameProvider.getAppName() override fun tchapIsSecureBackupRequired() = booleanProvider.getBoolean(im.vector.app.config.R.bool.tchap_is_secure_backup_required) + override fun tchapIsSSOEnabled() = booleanProvider.getBoolean(im.vector.app.config.R.bool.tchap_is_sso_enabled) override fun onboardingVariant() = Config.ONBOARDING_VARIANT override fun isOnboardingAlreadyHaveAccountSplashEnabled() = true override fun isOnboardingSplashCarouselEnabled() = false // TCHAP no carousel diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt index 634a537de5..af5887a96a 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt @@ -235,6 +235,7 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA } private fun inferAuthDescription(loginViewState: LoginViewState) = when (loginViewState.signMode) { + SignMode.TchapSignInWithSSO, SignMode.TchapSignUp, SignMode.TchapSignIn -> error("developer error") SignMode.Unknown -> null @@ -271,6 +272,7 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA private fun onSignModeSelected(loginViewEvents: LoginViewEvents.OnSignModeSelected) = withState(loginViewModel) { state -> // state.signMode could not be ready yet. So use value from the ViewEvent when (loginViewEvents.signMode) { + SignMode.TchapSignInWithSSO, SignMode.TchapSignUp, SignMode.TchapSignIn -> error("developer error") SignMode.Unknown -> error("Sign mode has to be set before calling this method") diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt index 6cb419adab..80cd5f289f 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt @@ -89,6 +89,7 @@ class LoginFragment : private fun setupAutoFill(state: LoginViewState) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { when (state.signMode) { + SignMode.TchapSignInWithSSO, SignMode.TchapSignUp, SignMode.TchapSignIn, SignMode.Unknown -> error("developer error") @@ -106,6 +107,7 @@ class LoginFragment : } private fun ssoMode(state: LoginViewState) = when (state.signMode) { + SignMode.TchapSignInWithSSO, SignMode.TchapSignUp, SignMode.TchapSignIn, SignMode.Unknown -> error("developer error") @@ -161,6 +163,7 @@ class LoginFragment : private fun setupUi(state: LoginViewState) { views.loginFieldTil.hint = getString( when (state.signMode) { + SignMode.TchapSignInWithSSO, SignMode.TchapSignUp, SignMode.TchapSignIn, SignMode.Unknown -> error("developer error") @@ -178,6 +181,7 @@ class LoginFragment : views.loginPasswordNotice.isVisible = true } else { val resId = when (state.signMode) { + SignMode.TchapSignInWithSSO, SignMode.TchapSignUp, SignMode.TchapSignIn, SignMode.Unknown -> error("developer error") @@ -231,6 +235,7 @@ class LoginFragment : views.loginSubmit.text = getString( when (state.signMode) { + SignMode.TchapSignInWithSSO, SignMode.TchapSignUp, SignMode.TchapSignIn, SignMode.Unknown -> error("developer error") diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt index 4f5f2bd882..965a05b4df 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt @@ -431,6 +431,7 @@ class LoginViewModel @AssistedInject constructor( } when (action.signMode) { + SignMode.TchapSignInWithSSO, SignMode.TchapSignUp, SignMode.TchapSignIn -> error("developer error") SignMode.SignUp -> startRegistrationFlow() @@ -570,6 +571,7 @@ class LoginViewModel @AssistedInject constructor( private fun handleLoginOrRegister(action: LoginAction.LoginOrRegister) = withState { state -> when (state.signMode) { + SignMode.TchapSignInWithSSO, SignMode.TchapSignIn, SignMode.TchapSignUp, SignMode.Unknown -> error("Developer error, invalid sign mode") diff --git a/vector/src/main/java/im/vector/app/features/login/SSORedirectRouterActivity.kt b/vector/src/main/java/im/vector/app/features/login/SSORedirectRouterActivity.kt index 19c549fd45..4704048612 100644 --- a/vector/src/main/java/im/vector/app/features/login/SSORedirectRouterActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/SSORedirectRouterActivity.kt @@ -35,6 +35,6 @@ class SSORedirectRouterActivity : AppCompatActivity() { companion object { // Note that the domain can be displayed to the user for confirmation that he trusts it. So use a human readable string - const val VECTOR_REDIRECT_URL = "element://connect" + const val VECTOR_REDIRECT_URL = "tchap://connect" } } diff --git a/vector/src/main/java/im/vector/app/features/login/SignMode.kt b/vector/src/main/java/im/vector/app/features/login/SignMode.kt index 3438dbe41e..336a0a7eef 100644 --- a/vector/src/main/java/im/vector/app/features/login/SignMode.kt +++ b/vector/src/main/java/im/vector/app/features/login/SignMode.kt @@ -23,6 +23,9 @@ enum class SignMode { // TCHAP Account creation TchapSignUp, + // TCHAP Login with SSO + TchapSignInWithSSO, + Unknown, // Account creation diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt index 6497a84f7c..27b2ce5930 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt @@ -56,6 +56,7 @@ sealed interface OnboardingAction : VectorViewModelAction { data class Registration(val userId: String) : UserNameEnteredAction data class Login(val userId: String) : UserNameEnteredAction } + data class LoginWithSSO(val email: String) : OnboardingAction sealed interface AuthenticateAction : OnboardingAction { data class TchapRegister(val email: String, val password: String, val initialDeviceName: String) : AuthenticateAction data class TchapLogin(val email: String, val password: String, val initialDeviceName: String) : AuthenticateAction diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 9169e9686e..62afb5be84 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -179,6 +179,7 @@ class OnboardingViewModel @AssistedInject constructor( override fun handle(action: OnboardingAction) { when (action) { + is OnboardingAction.LoginWithSSO -> tchap.handleLoginWithSSO(action) is OnboardingAction.SplashAction -> handleSplashAction(action) is OnboardingAction.UpdateUseCase -> handleUpdateUseCase(action) OnboardingAction.ResetUseCase -> resetUseCase() @@ -282,8 +283,11 @@ class OnboardingViewModel @AssistedInject constructor( private fun continueToPageAfterSplash(onboardingFlow: OnboardingFlow) { when (onboardingFlow) { + // TCHAP login with SSO + OnboardingFlow.TchapSignInWithSSO -> handleUpdateSignMode(OnboardingAction.UpdateSignMode(SignMode.TchapSignInWithSSO)) OnboardingFlow.SignUp -> { handleUpdateSignMode(OnboardingAction.UpdateSignMode(SignMode.TchapSignUp)) + // TCHAP disable homeserver selection // _viewEvents.post( // if (vectorFeatures.isOnboardingUseCaseEnabled()) { // OnboardingViewEvents.OpenUseCaseSelection @@ -483,6 +487,7 @@ class OnboardingViewModel @AssistedInject constructor( private fun handleUpdateSignMode(action: OnboardingAction.UpdateSignMode) { updateSignMode(action.signMode) when (action.signMode) { + SignMode.TchapSignInWithSSO -> _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.TchapSignInWithSSO)) SignMode.TchapSignIn -> _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.TchapSignIn)) SignMode.TchapSignUp -> _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.TchapSignUp)) SignMode.SignUp -> handleRegisterAction(RegisterAction.StartRegistration) @@ -808,6 +813,7 @@ class OnboardingViewModel @AssistedInject constructor( updateServerSelection(config, serverTypeOverride, authResult) if (authResult.selectedHomeserver.preferredLoginMode.supportsSignModeScreen()) { when (awaitState().onboardingFlow) { + OnboardingFlow.TchapSignInWithSSO -> error("developer error") OnboardingFlow.SignIn -> { updateSignMode(SignMode.SignIn) when (vectorFeatures.isOnboardingCombinedLoginEnabled()) { @@ -835,6 +841,7 @@ class OnboardingViewModel @AssistedInject constructor( updateServerSelection(config, serverTypeOverride, authResult) _viewEvents.post(OnboardingViewEvents.OnHomeserverEdited) } + OnboardingFlow.TchapSignInWithSSO, OnboardingFlow.SignIn -> { updateServerSelection(config, serverTypeOverride, authResult) _viewEvents.post(OnboardingViewEvents.OnHomeserverEdited) @@ -1003,6 +1010,10 @@ class OnboardingViewModel @AssistedInject constructor( } } + fun handleLoginWithSSO(action: OnboardingAction.LoginWithSSO) { + startTchapAuthenticationFlow(action.email) {} + } + fun startResetPasswordFlow(email: String, onSuccess: () -> Unit) { startTchapAuthenticationFlow(email) { this@OnboardingViewModel.startResetPasswordFlow(email, onSuccess) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt index 58b28ac4e4..983fb18885 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt @@ -64,6 +64,7 @@ data class OnboardingViewState( ) : MavericksState enum class OnboardingFlow { + TchapSignInWithSSO, SignIn, SignUp, SignInSignUp diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt index 7fa79535af..42b43c9463 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt @@ -31,6 +31,7 @@ import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewModel import im.vector.app.features.onboarding.OnboardingViewState +import im.vector.app.features.settings.VectorSettingsUrls import im.vector.lib.strings.CommonStrings import kotlinx.coroutines.CancellationException @@ -167,4 +168,10 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment() { + @Inject lateinit var buildMeta: BuildMeta + @Inject lateinit var vectorFeatures: VectorFeatures + private val tchap = Tchap() private var isSignupMode = false @@ -111,7 +120,11 @@ class FtueAuthLoginFragment : views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME) views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD) } - SignMode.TchapSignIn, + SignMode.TchapSignInWithSSO, + SignMode.TchapSignIn -> { + views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_EMAIL_ADDRESS) + views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD) + } SignMode.SignIn, SignMode.SignInWithMatrixId -> { views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME) @@ -125,6 +138,7 @@ class FtueAuthLoginFragment : SignMode.Unknown -> error("developer error") SignMode.TchapSignUp, SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP + SignMode.TchapSignInWithSSO, SignMode.TchapSignIn, SignMode.SignIn, SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN @@ -148,7 +162,7 @@ class FtueAuthLoginFragment : views.loginFieldTil.error = getString(CommonStrings.error_forbidden_digits_only_username) error++ } - if (password.isEmpty()) { + if (password.isEmpty() && state.signMode != SignMode.TchapSignInWithSSO) { views.passwordFieldTil.error = getString( if (isSignupMode) { CommonStrings.error_empty_field_choose_password @@ -166,8 +180,12 @@ class FtueAuthLoginFragment : } if (error == 0) { - val initialDeviceName = getString(CommonStrings.login_default_session_public_name) - viewModel.handle(state.signMode.toAuthenticateAction(login, password, initialDeviceName)) + if (state.signMode != SignMode.TchapSignInWithSSO) { + val initialDeviceName = getString(CommonStrings.login_default_session_public_name) + viewModel.handle(state.signMode.toAuthenticateAction(login, password, initialDeviceName)) + } else { + viewModel.handle(OnboardingAction.LoginWithSSO(login)) + } } } } @@ -186,6 +204,7 @@ class FtueAuthLoginFragment : SignMode.SignUp -> CommonStrings.login_signup_username_hint SignMode.SignIn -> CommonStrings.login_signin_username_hint SignMode.TchapSignUp, + SignMode.TchapSignInWithSSO, SignMode.TchapSignIn -> CommonStrings.tchap_connection_email SignMode.SignInWithMatrixId -> CommonStrings.login_signin_matrix_id_hint } @@ -199,12 +218,12 @@ class FtueAuthLoginFragment : views.loginPasswordNotice.isVisible = true } else { val resId = when (state.signMode) { - SignMode.Unknown -> error("developer error") SignMode.TchapSignUp, SignMode.SignUp -> CommonStrings.login_signup_to SignMode.TchapSignIn -> CommonStrings.login_connect_to SignMode.SignIn -> CommonStrings.login_connect_to - SignMode.SignInWithMatrixId -> CommonStrings.login_connect_to + SignMode.TchapSignInWithSSO -> CommonStrings.login_social_signin_with + else -> error("developer error") } when (state.serverType) { @@ -228,14 +247,17 @@ class FtueAuthLoginFragment : ServerType.Unknown -> { // TCHAP Hide views if empty views.loginServerIcon.isVisible = false - views.loginTitle.isVisible = false views.loginNotice.isVisible = false + if (state.signMode == SignMode.TchapSignInWithSSO) { + views.loginTitle.text = getString(resId, TCHAP_SSO_PROVIDER) + } else { + views.loginTitle.text = getString(resId, buildMeta.applicationName) + } } } views.loginPasswordNotice.isVisible = false if (state.selectedHomeserver.preferredLoginMode is LoginMode.SsoAndPassword) { - views.loginSocialLoginContainer.isVisible = true views.loginSocialLoginButtons.render(state.selectedHomeserver.preferredLoginMode, ssoMode(state)) { provider -> viewModel.fetchSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, @@ -246,7 +268,6 @@ class FtueAuthLoginFragment : ?.let { openInCustomTab(it) } } } else { - views.loginSocialLoginContainer.isVisible = false views.loginSocialLoginButtons.ssoIdentityProviders = null } } @@ -260,20 +281,21 @@ class FtueAuthLoginFragment : SignMode.Unknown -> error("developer error") SignMode.TchapSignUp, SignMode.SignUp -> CommonStrings.login_signup_submit + SignMode.TchapSignInWithSSO -> CommonStrings.login_signin_sso SignMode.TchapSignIn, SignMode.SignIn, SignMode.SignInWithMatrixId -> CommonStrings.login_signin - } + }, TCHAP_SSO_PROVIDER ) } - private fun setupSubmitButton() { + private fun setupSubmitButton() = withState(viewModel) { state -> views.loginSubmit.setOnClickListener { submit() } combine( views.loginField.textChanges().map { it.trim().isNotEmpty() }, views.passwordField.textChanges().map { it.isNotEmpty() } ) { isLoginNotEmpty, isPasswordNotEmpty -> - isLoginNotEmpty && isPasswordNotEmpty + (isLoginNotEmpty && isPasswordNotEmpty) || state.signMode == SignMode.TchapSignInWithSSO && views.loginField.text?.isEmail().orFalse() } .onEach { views.loginFieldTil.error = null @@ -327,6 +349,7 @@ class FtueAuthLoginFragment : tchap.setupUi(state) setupAutoFill(state) setupButtons(state) + tchap.tryLoginSSO(state) if (state.isLoading) { // Ensure password is hidden @@ -343,15 +366,61 @@ class FtueAuthLoginFragment : fun setupUi(state: OnboardingViewState) { this@FtueAuthLoginFragment.setupUi(state) // call "super" method - if (state.signMode == SignMode.TchapSignUp) { - views.loginFieldTil.isHelperTextEnabled = true - views.passwordFieldTil.isHelperTextEnabled = true - views.tchapPasswordConfirmationFieldTil.isVisible = true - } else { - views.loginFieldTil.isHelperTextEnabled = false - views.passwordFieldTil.isHelperTextEnabled = false - views.tchapPasswordConfirmationFieldTil.isVisible = false - views.passwordField.imeOptions = EditorInfo.IME_ACTION_DONE + + val isSignUpMode = state.signMode == SignMode.TchapSignUp + views.loginFieldTil.isHelperTextEnabled = isSignUpMode + views.passwordFieldTil.isHelperTextEnabled = isSignUpMode + views.tchapPasswordConfirmationFieldTil.isVisible = isSignUpMode + views.loginSocialLoginContainer.isVisible = isSignUpMode && vectorFeatures.tchapIsSSOEnabled() + + when(state.signMode) { + SignMode.TchapSignUp -> { + views.loginSSOSubmit.text = getString(CommonStrings.login_signin_sso, TCHAP_SSO_PROVIDER) + views.loginSSOSubmit.debouncedClicks { + viewModel.handle( + OnboardingAction.SplashAction.OnIAlreadyHaveAnAccount( + onboardingFlow = OnboardingFlow.TchapSignInWithSSO + ) + ) + } + } + SignMode.TchapSignInWithSSO -> { + views.loginSubmit.setLeftDrawable(im.vector.lib.ui.styles.R.drawable.ic_tchap_proconnect) + views.loginSSOHelp.text = getString(CommonStrings.tchap_connection_sso_help, TCHAP_SSO_PROVIDER) + views.loginSSODescription.text = getString(CommonStrings.tchap_connection_sso_description, TCHAP_SSO_PROVIDER) + views.loginSSOHelp.debouncedClicks { openUrlInExternalBrowser(requireContext(), TCHAP_SSO_URL) } + views.loginSSODescription.debouncedClicks { openUrlInExternalBrowser(requireContext(), TCHAP_SSO_FAQ_URL) } + views.passwordFieldTil.isVisible = false + views.loginSSOHelp.isVisible = true + views.loginSSODescription.isVisible = true + } + else -> { + views.passwordField.imeOptions = EditorInfo.IME_ACTION_DONE + views.loginSSOHelp.isVisible = false + } + } + } + + fun tryLoginSSO(state: OnboardingViewState) { + if (state.signMode != SignMode.TchapSignInWithSSO) return + if (views.loginField.text.isNullOrEmpty()) return + if (state.selectedHomeserver.upstreamUrl.isNullOrEmpty()) return + if (views.loginSocialLoginButtons.ssoIdentityProviders.isNullOrEmpty()) { + views.loginFieldTil.error = getString(CommonStrings.tchap_auth_sso_inactive, TCHAP_SSO_PROVIDER) + viewModel.handle(OnboardingAction.ResetHomeServerUrl) + return + } + + views.loginSocialLoginButtons.ssoIdentityProviders?.first().let { + viewModel.fetchSsoUrl( + redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, + deviceId = state.deviceId, + provider = it, + action = SSOAction.LOGIN + ) + ?.let { url -> openInCustomTab(url) } + + views.loginField.text?.clear() } } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt index d203919cfc..0224c8b946 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt @@ -17,13 +17,18 @@ package im.vector.app.features.onboarding.ftueauth import android.annotation.SuppressLint +import android.graphics.Typeface import android.os.Bundle +import android.text.Spannable +import android.text.SpannableString +import android.text.style.StyleSpan import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.resources.BuildMeta +import im.vector.app.core.utils.openUrlInExternalBrowser import im.vector.app.databinding.FragmentTchapWelcomeBinding import im.vector.app.features.VectorFeatures import im.vector.app.features.onboarding.OnboardingAction @@ -53,13 +58,27 @@ class FtueAuthSplashFragment : } private fun setupViews() { + // TCHAP Login with SSO val isAlreadyHaveAccountEnabled = vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled() + views.loginSplashSSO.apply { + val spannable = SpannableString(getString(CommonStrings.login_social_signin_with, TCHAP_SSO_PROVIDER)) + spannable.setSpan(StyleSpan(Typeface.BOLD), spannable.length - TCHAP_SSO_PROVIDER.length, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + + text = spannable + isVisible = isAlreadyHaveAccountEnabled && vectorFeatures.tchapIsSSOEnabled() + debouncedClicks { alreadyHaveAnAccountWithSSO() } + } + views.loginSplashSSOHelp.apply { + text = getString(CommonStrings.tchap_connection_sso_help, TCHAP_SSO_PROVIDER) + isVisible = isAlreadyHaveAccountEnabled && vectorFeatures.tchapIsSSOEnabled() + debouncedClicks { openUrlInExternalBrowser(requireContext(), TCHAP_SSO_URL) } + } views.loginSplashSubmit.apply { setText(if (isAlreadyHaveAccountEnabled) CommonStrings.login_splash_create_account else CommonStrings.login_splash_submit) debouncedClicks { splashSubmit(isAlreadyHaveAccountEnabled) } } views.loginSplashAlreadyHaveAccount.apply { - isVisible = vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled() + isVisible = isAlreadyHaveAccountEnabled debouncedClicks { alreadyHaveAnAccount() } } @@ -72,6 +91,11 @@ class FtueAuthSplashFragment : } } + /** TCHAP Login with SSO. */ + private fun alreadyHaveAnAccountWithSSO() { + viewModel.handle(OnboardingAction.SplashAction.OnIAlreadyHaveAnAccount(onboardingFlow = OnboardingFlow.TchapSignInWithSSO)) + } + private fun splashSubmit(isAlreadyHaveAccountEnabled: Boolean) { val getStartedFlow = if (isAlreadyHaveAccountEnabled) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp viewModel.handle(OnboardingAction.SplashAction.OnGetStarted(onboardingFlow = getStartedFlow)) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index 67f8864ac3..6a5cb403c2 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -261,6 +261,7 @@ class FtueAuthVariant( else -> { withState(onboardingViewModel) { state -> when (state.onboardingFlow) { + OnboardingFlow.TchapSignInWithSSO -> error("developer error") OnboardingFlow.SignIn -> onStartCombinedLogin() OnboardingFlow.SignUp -> onStartCombinedRegister() OnboardingFlow.SignInSignUp, @@ -323,6 +324,7 @@ class FtueAuthVariant( // state.signMode could not be ready yet. So use value from the ViewEvent when (onboardingViewEvents.signMode) { SignMode.Unknown -> error("Sign mode has to be set before calling this method") + SignMode.TchapSignInWithSSO -> tchap.handleSignInWithSSO() SignMode.TchapSignUp -> tchap.handleSignUpSelected() SignMode.TchapSignIn -> tchap.handleSignInSelected() SignMode.SignUp -> Unit // This case is processed in handleOnboardingViewEvents @@ -558,5 +560,6 @@ class FtueAuthVariant( private inner class Tchap { fun handleSignInSelected() = openAuthLoginFragmentWithTag(FRAGMENT_LOGIN_TAG) fun handleSignUpSelected() = openAuthLoginFragmentWithTag(FRAGMENT_LOGIN_TAG) + fun handleSignInWithSSO() = openAuthLoginFragmentWithTag(FRAGMENT_LOGIN_TAG) } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueExtensions.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueExtensions.kt index 7427eb0593..b0c1f59104 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueExtensions.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueExtensions.kt @@ -23,6 +23,7 @@ import im.vector.lib.ui.styles.R fun SignMode.toAuthenticateAction(login: String, password: String, initialDeviceName: String): OnboardingAction.AuthenticateAction { return when (this) { + SignMode.TchapSignInWithSSO, SignMode.Unknown -> error("developer error") SignMode.TchapSignUp -> OnboardingAction.AuthenticateAction.TchapRegister(email = login, password, initialDeviceName) SignMode.TchapSignIn -> OnboardingAction.AuthenticateAction.TchapLogin(email = login, password, initialDeviceName) diff --git a/vector/src/main/res/layout/fragment_login.xml b/vector/src/main/res/layout/fragment_login.xml index 60ebf88d1e..600a121391 100644 --- a/vector/src/main/res/layout/fragment_login.xml +++ b/vector/src/main/res/layout/fragment_login.xml @@ -43,11 +43,53 @@ android:textColor="?vctr_content_secondary" tools:text="@string/login_server_matrix_org_text" /> + + + +