From 42d1bf57f6d8a6d7d767e2153888ddc245689b73 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 3 Dec 2020 15:37:10 +0100 Subject: [PATCH] Refactor + fix sso in LoginFragment --- .../login/AbstractSSOLoginFragment.kt | 93 +++++++++++++++++++ .../app/features/login/LoginFragment.kt | 7 +- .../LoginSignUpSignInSelectionFragment.kt | 86 +---------------- 3 files changed, 103 insertions(+), 83 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt diff --git a/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt new file mode 100644 index 0000000000..085094be62 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.login + +import android.content.ComponentName +import android.net.Uri +import androidx.browser.customtabs.CustomTabsClient +import androidx.browser.customtabs.CustomTabsServiceConnection +import androidx.browser.customtabs.CustomTabsSession +import com.airbnb.mvrx.withState +import im.vector.app.core.utils.openUrlInChromeCustomTab + +abstract class AbstractSSOLoginFragment : AbstractLoginFragment() { + + // For sso + private var customTabsServiceConnection: CustomTabsServiceConnection? = null + private var customTabsClient: CustomTabsClient? = null + private var customTabsSession: CustomTabsSession? = null + + override fun onStart() { + super.onStart() + val hasSSO = withState(loginViewModel) { it.loginMode.hasSso() } + if (hasSSO) { + val packageName = CustomTabsClient.getPackageName(requireContext(), null) + + // packageName can be null if there are 0 or several CustomTabs compatible browsers installed on the device + if (packageName != null) { + customTabsServiceConnection = object : CustomTabsServiceConnection() { + override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) { + customTabsClient = client + .also { it.warmup(0L) } + prefetchIfNeeded() + } + + override fun onServiceDisconnected(name: ComponentName?) { + } + } + .also { + CustomTabsClient.bindCustomTabsService( + requireContext(), + // Despite the API, packageName cannot be null + packageName, + it + ) + } + } + } + } + + override fun onStop() { + super.onStop() + val hasSSO = withState(loginViewModel) { it.loginMode.hasSso() } + if (hasSSO) { + customTabsServiceConnection?.let { requireContext().unbindService(it) } + customTabsServiceConnection = null + } + } + + private fun prefetchUrl(url: String) { + if (customTabsSession == null) { + customTabsSession = customTabsClient?.newSession(null) + } + + customTabsSession?.mayLaunchUrl(Uri.parse(url), null, null) + } + + fun openInCustomTab(ssoUrl: String) { + openUrlInChromeCustomTab(requireContext(), customTabsSession, ssoUrl) + } + + private fun prefetchIfNeeded() { + withState(loginViewModel) { state -> + if (state.loginMode.hasSso() && state.loginMode.ssoProviders().isNullOrEmpty()) { + // in this case we can prefetch (not other cases for privacy concerns) + prefetchUrl(state.getSsoUrl(null)) + } + } + } +} 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 0ce4317deb..489b33abe9 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 @@ -51,7 +51,7 @@ import javax.inject.Inject * In signup mode: * - the user is asked for login and password */ -class LoginFragment @Inject constructor() : AbstractLoginFragment() { +class LoginFragment @Inject constructor() : AbstractSSOLoginFragment() { private var passwordShown = false private var isSignupMode = false @@ -176,6 +176,11 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() { if (state.loginMode is LoginMode.SsoAndPassword) { loginSocialLoginContainer.isVisible = true loginSocialLoginButtons.identityProviders = state.loginMode.identityProviders + loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { + override fun onProviderSelected(id: String?) { + openInCustomTab(state.getSsoUrl(id)) + } + } } else { loginSocialLoginContainer.isVisible = false loginSocialLoginButtons.identityProviders = null diff --git a/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt index d487ee50de..eb9b99c7ef 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginSignUpSignInSelectionFragment.kt @@ -16,31 +16,18 @@ package im.vector.app.features.login -import android.content.ComponentName -import android.net.Uri -import androidx.browser.customtabs.CustomTabsClient -import androidx.browser.customtabs.CustomTabsServiceConnection -import androidx.browser.customtabs.CustomTabsSession import androidx.core.view.isVisible import butterknife.OnClick import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.toReducedUrl -import im.vector.app.core.utils.openUrlInChromeCustomTab import kotlinx.android.synthetic.main.fragment_login_signup_signin_selection.* import javax.inject.Inject /** * In this screen, the user is asked to sign up or to sign in to the homeserver */ -class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFragment() { - - // Map of sso urls by providers if any - private var ssoUrls = emptyMap().toMutableMap() - - private var customTabsServiceConnection: CustomTabsServiceConnection? = null - private var customTabsClient: CustomTabsClient? = null - private var customTabsSession: CustomTabsSession? = null +class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLoginFragment() { override fun getLayoutResId() = R.layout.fragment_login_signup_signin_selection @@ -73,7 +60,8 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFr loginSignupSigninSocialLoginButtons.identityProviders = identityProviders loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { override fun onProviderSelected(id: String?) { - ssoUrls[id]?.let { openUrlInChromeCustomTab(requireContext(), customTabsSession, it) } + val url = withState(loginViewModel) { it.getSsoUrl(id) } + openInCustomTab(url) } } } @@ -85,72 +73,6 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFr } } - override fun onStart() { - super.onStart() - val hasSSO = withState(loginViewModel) { it.loginMode.hasSso() } - if (hasSSO) { - val packageName = CustomTabsClient.getPackageName(requireContext(), null) - - // packageName can be null if there are 0 or several CustomTabs compatible browsers installed on the device - if (packageName != null) { - customTabsServiceConnection = object : CustomTabsServiceConnection() { - override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) { - customTabsClient = client - .also { it.warmup(0L) } - - // prefetch urls - prefetchSsoUrls() - } - - override fun onServiceDisconnected(name: ComponentName?) { - } - } - .also { - CustomTabsClient.bindCustomTabsService( - requireContext(), - // Despite the API, packageName cannot be null - packageName, - it - ) - } - } - } - } - - override fun onStop() { - super.onStop() - val hasSSO = withState(loginViewModel) { it.loginMode.hasSso() } - if (hasSSO) { - customTabsServiceConnection?.let { requireContext().unbindService(it) } - customTabsServiceConnection = null - } - } - - private fun prefetchSsoUrls() = withState(loginViewModel) { state -> - val providers = state.loginMode.ssoProviders() - if (providers.isNullOrEmpty()) { - state.getSsoUrl(null).let { - ssoUrls[null] = it - prefetchUrl(it) - } - } else { - providers.forEach { identityProvider -> - state.getSsoUrl(identityProvider.id).let { - ssoUrls[identityProvider.id] = it - // we don't prefetch for privacy reasons - } - } - } - } - - private fun prefetchUrl(url: String) { - if (customTabsSession == null) { - customTabsSession = customTabsClient?.newSession(null) - } - - customTabsSession?.mayLaunchUrl(Uri.parse(url), null, null) - } - private fun setupButtons(state: LoginViewState) { when (state.loginMode) { is LoginMode.Sso -> { @@ -168,7 +90,7 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLoginFr @OnClick(R.id.loginSignupSigninSubmit) fun submit() = withState(loginViewModel) { state -> if (state.loginMode is LoginMode.Sso) { - ssoUrls[null]?.let { openUrlInChromeCustomTab(requireContext(), customTabsSession, it) } + openInCustomTab(state.getSsoUrl(null)) } else { loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignUp)) }