Merge pull request #6042 from vector-im/feature/adm/ftue-analytics

FTUE - Sign up analytics
This commit is contained in:
Adam Brown 2022-06-06 13:52:01 +01:00 committed by GitHub
commit a5f404f247
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 212 additions and 61 deletions

1
changelog.d/5285.wip Normal file
View File

@ -0,0 +1 @@
FTUE - Adds Sign Up tracking

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2022 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.analytics.extensions
import im.vector.app.features.analytics.plan.Signup
import im.vector.app.features.onboarding.AuthenticationDescription
fun AuthenticationDescription.AuthenticationType.toAnalyticsType() = when (this) {
AuthenticationDescription.AuthenticationType.Password -> Signup.AuthenticationType.Password
AuthenticationDescription.AuthenticationType.Apple -> Signup.AuthenticationType.Apple
AuthenticationDescription.AuthenticationType.Facebook -> Signup.AuthenticationType.Facebook
AuthenticationDescription.AuthenticationType.GitHub -> Signup.AuthenticationType.GitHub
AuthenticationDescription.AuthenticationType.GitLab -> Signup.AuthenticationType.GitLab
AuthenticationDescription.AuthenticationType.Google -> Signup.AuthenticationType.Google
AuthenticationDescription.AuthenticationType.SSO -> Signup.AuthenticationType.SSO
AuthenticationDescription.AuthenticationType.Other -> Signup.AuthenticationType.Other
}

View File

@ -56,6 +56,7 @@ import im.vector.app.features.matrixto.MatrixToBottomSheet
import im.vector.app.features.matrixto.OriginOfMatrixTo import im.vector.app.features.matrixto.OriginOfMatrixTo
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.onboarding.AuthenticationDescription
import im.vector.app.features.permalink.NavigationInterceptor import im.vector.app.features.permalink.NavigationInterceptor
import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.permalink.PermalinkHandler
import im.vector.app.features.permalink.PermalinkHandler.Companion.MATRIX_TO_CUSTOM_SCHEME_URL_BASE import im.vector.app.features.permalink.PermalinkHandler.Companion.MATRIX_TO_CUSTOM_SCHEME_URL_BASE
@ -91,7 +92,7 @@ import javax.inject.Inject
@Parcelize @Parcelize
data class HomeActivityArgs( data class HomeActivityArgs(
val clearNotification: Boolean, val clearNotification: Boolean,
val accountCreation: Boolean, val authenticationDescription: AuthenticationDescription? = null,
val hasExistingSession: Boolean = false, val hasExistingSession: Boolean = false,
val inviteNotificationRoomId: String? = null val inviteNotificationRoomId: String? = null
) : Parcelable ) : Parcelable
@ -612,13 +613,13 @@ class HomeActivity :
fun newIntent( fun newIntent(
context: Context, context: Context,
clearNotification: Boolean = false, clearNotification: Boolean = false,
accountCreation: Boolean = false, authenticationDescription: AuthenticationDescription? = null,
existingSession: Boolean = false, existingSession: Boolean = false,
inviteNotificationRoomId: String? = null inviteNotificationRoomId: String? = null
): Intent { ): Intent {
val args = HomeActivityArgs( val args = HomeActivityArgs(
clearNotification = clearNotification, clearNotification = clearNotification,
accountCreation = accountCreation, authenticationDescription = authenticationDescription,
hasExistingSession = existingSession, hasExistingSession = existingSession,
inviteNotificationRoomId = inviteNotificationRoomId inviteNotificationRoomId = inviteNotificationRoomId
) )

View File

@ -28,8 +28,12 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsType
import im.vector.app.features.analytics.plan.Signup
import im.vector.app.features.analytics.store.AnalyticsStore import im.vector.app.features.analytics.store.AnalyticsStore
import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.ReAuthHelper
import im.vector.app.features.onboarding.AuthenticationDescription
import im.vector.app.features.raw.wellknown.ElementWellKnown import im.vector.app.features.raw.wellknown.ElementWellKnown
import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.getElementWellknown
import im.vector.app.features.raw.wellknown.isSecureBackupRequired import im.vector.app.features.raw.wellknown.isSecureBackupRequired
@ -38,8 +42,11 @@ import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
@ -73,7 +80,8 @@ class HomeActivityViewModel @AssistedInject constructor(
private val reAuthHelper: ReAuthHelper, private val reAuthHelper: ReAuthHelper,
private val analyticsStore: AnalyticsStore, private val analyticsStore: AnalyticsStore,
private val lightweightSettingsStorage: LightweightSettingsStorage, private val lightweightSettingsStorage: LightweightSettingsStorage,
private val vectorPreferences: VectorPreferences private val vectorPreferences: VectorPreferences,
private val analyticsTracker: AnalyticsTracker
) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) { ) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
@ -85,7 +93,7 @@ class HomeActivityViewModel @AssistedInject constructor(
override fun initialState(viewModelContext: ViewModelContext): HomeActivityViewState? { override fun initialState(viewModelContext: ViewModelContext): HomeActivityViewState? {
val activity: HomeActivity = viewModelContext.activity() val activity: HomeActivity = viewModelContext.activity()
val args: HomeActivityArgs? = activity.intent.getParcelableExtra(Mavericks.KEY_ARG) val args: HomeActivityArgs? = activity.intent.getParcelableExtra(Mavericks.KEY_ARG)
return args?.let { HomeActivityViewState(accountCreation = it.accountCreation) } return args?.let { HomeActivityViewState(authenticationDescription = it.authenticationDescription) }
?: super.initialState(viewModelContext) ?: super.initialState(viewModelContext)
} }
} }
@ -114,8 +122,31 @@ class HomeActivityViewModel @AssistedInject constructor(
} }
} }
.launchIn(viewModelScope) .launchIn(viewModelScope)
when (val recentAuthentication = initialState.authenticationDescription) {
is AuthenticationDescription.Register -> {
viewModelScope.launch {
analyticsStore.onUserGaveConsent {
analyticsTracker.capture(Signup(authenticationType = recentAuthentication.type.toAnalyticsType()))
} }
} }
}
AuthenticationDescription.Login -> {
// do nothing
}
null -> {
// do nothing
}
}
}
}
private suspend fun AnalyticsStore.onUserGaveConsent(action: () -> Unit) {
userConsentFlow
.takeWhile { !it }
.onCompletion { action() }
.collect()
}
private fun cleanupFiles() { private fun cleanupFiles() {
// Mitigation: delete all cached decrypted files each time the application is started. // Mitigation: delete all cached decrypted files each time the application is started.
@ -285,7 +316,7 @@ class HomeActivityViewModel @AssistedInject constructor(
val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() ?: false val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() ?: false
// In case of account creation, it is already done before // In case of account creation, it is already done before
if (initialState.accountCreation) { if (initialState.authenticationDescription is AuthenticationDescription.Register) {
if (isSecureBackupRequired) { if (isSecureBackupRequired) {
_viewEvents.post(HomeActivityViewEvents.StartRecoverySetupFlow) _viewEvents.post(HomeActivityViewEvents.StartRecoverySetupFlow)
} else { } else {

View File

@ -17,9 +17,10 @@
package im.vector.app.features.home package im.vector.app.features.home
import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksState
import im.vector.app.features.onboarding.AuthenticationDescription
import org.matrix.android.sdk.api.session.initsync.SyncStatusService import org.matrix.android.sdk.api.session.initsync.SyncStatusService
data class HomeActivityViewState( data class HomeActivityViewState(
val syncStatusServiceStatus: SyncStatusService.Status = SyncStatusService.Status.Idle, val syncStatusServiceStatus: SyncStatusService.Status = SyncStatusService.Status.Idle,
val accountCreation: Boolean = false val authenticationDescription: AuthenticationDescription? = null
) : MavericksState ) : MavericksState

View File

@ -42,6 +42,7 @@ import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.home.HomeActivity import im.vector.app.features.home.HomeActivity
import im.vector.app.features.login.terms.LoginTermsFragment import im.vector.app.features.login.terms.LoginTermsFragment
import im.vector.app.features.login.terms.LoginTermsFragmentArgument import im.vector.app.features.login.terms.LoginTermsFragmentArgument
import im.vector.app.features.onboarding.AuthenticationDescription
import im.vector.app.features.pin.UnlockedActivity import im.vector.app.features.pin.UnlockedActivity
import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.FlowResult
import org.matrix.android.sdk.api.auth.registration.Stage import org.matrix.android.sdk.api.auth.registration.Stage
@ -218,10 +219,8 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedA
// change the screen name // change the screen name
analyticsScreenName = MobileScreen.ScreenName.Register analyticsScreenName = MobileScreen.ScreenName.Register
} }
val intent = HomeActivity.newIntent( val authDescription = inferAuthDescription(loginViewState)
this, val intent = HomeActivity.newIntent(this, authenticationDescription = authDescription)
accountCreation = loginViewState.signMode == SignMode.SignUp
)
startActivity(intent) startActivity(intent)
finish() finish()
return return
@ -231,6 +230,13 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedA
views.loginLoading.isVisible = loginViewState.isLoading() views.loginLoading.isVisible = loginViewState.isLoading()
} }
private fun inferAuthDescription(loginViewState: LoginViewState) = when (loginViewState.signMode) {
SignMode.Unknown -> null
SignMode.SignUp -> AuthenticationDescription.Register(type = AuthenticationDescription.AuthenticationType.Other)
SignMode.SignIn -> AuthenticationDescription.Login
SignMode.SignInWithMatrixId -> AuthenticationDescription.Login
}
private fun onWebLoginError(onWebLoginError: LoginViewEvents.OnWebLoginError) { private fun onWebLoginError(onWebLoginError: LoginViewEvents.OnWebLoginError) {
// Pop the backstack // Pop the backstack
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)

View File

@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidPassword
@ -202,11 +203,11 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
views.loginSocialLoginContainer.isVisible = true views.loginSocialLoginContainer.isVisible = true
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted() views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted()
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(id: String?) { override fun onProviderSelected(provider: SsoIdentityProvider?) {
loginViewModel.getSsoUrl( loginViewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId, deviceId = state.deviceId,
providerId = id providerId = provider?.id
) )
?.let { openInCustomTab(it) } ?.let { openInCustomTab(it) }
} }

View File

@ -25,6 +25,7 @@ import com.airbnb.mvrx.withState
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.toReducedUrl import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.databinding.FragmentLoginSignupSigninSelectionBinding import im.vector.app.databinding.FragmentLoginSignupSigninSelectionBinding
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -74,11 +75,11 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders()?.sorted() views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders()?.sorted()
views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(id: String?) { override fun onProviderSelected(provider: SsoIdentityProvider?) {
loginViewModel.getSsoUrl( loginViewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId, deviceId = state.deviceId,
providerId = id providerId = provider?.id
) )
?.let { openInCustomTab(it) } ?.let { openInCustomTab(it) }
} }

View File

@ -31,7 +31,7 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
LinearLayout(context, attrs, defStyle) { LinearLayout(context, attrs, defStyle) {
fun interface InteractionListener { fun interface InteractionListener {
fun onProviderSelected(id: String?) fun onProviderSelected(provider: SsoIdentityProvider?)
} }
enum class Mode { enum class Mode {
@ -113,7 +113,7 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
button.text = getButtonTitle(identityProvider.name) button.text = getButtonTitle(identityProvider.name)
button.setTag(R.id.loginSignupSigninSocialLoginButtons, identityProvider.id) button.setTag(R.id.loginSignupSigninSocialLoginButtons, identityProvider.id)
button.setOnClickListener { button.setOnClickListener {
listener?.onProviderSelected(identityProvider.id) listener?.onProviderSelected(identityProvider)
} }
addView(button) addView(button)
} }
@ -160,7 +160,7 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
} }
} }
fun SocialLoginButtonsView.render(ssoProviders: List<SsoIdentityProvider>?, mode: SocialLoginButtonsView.Mode, listener: (String?) -> Unit) { fun SocialLoginButtonsView.render(ssoProviders: List<SsoIdentityProvider>?, mode: SocialLoginButtonsView.Mode, listener: (SsoIdentityProvider?) -> Unit) {
this.mode = mode this.mode = mode
this.ssoIdentityProviders = ssoProviders?.sorted() this.ssoIdentityProviders = ssoProviders?.sorted()
this.listener = SocialLoginButtonsView.InteractionListener { listener(it) } this.listener = SocialLoginButtonsView.InteractionListener { listener(it) }

View File

@ -35,6 +35,7 @@ import im.vector.app.features.login.SocialLoginButtonsView
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import reactivecircus.flowbinding.android.widget.textChanges import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
@ -96,11 +97,11 @@ class LoginFragmentSignupUsername2 @Inject constructor() : AbstractSSOLoginFragm
views.loginSocialLoginContainer.isVisible = true views.loginSocialLoginContainer.isVisible = true
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted() views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted()
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(id: String?) { override fun onProviderSelected(provider: SsoIdentityProvider?) {
loginViewModel.getSsoUrl( loginViewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId, deviceId = state.deviceId,
providerId = id providerId = provider?.id
) )
?.let { openInCustomTab(it) } ?.let { openInCustomTab(it) }
} }

View File

@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidPassword
@ -123,11 +124,11 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2<Frag
views.loginSocialLoginContainer.isVisible = true views.loginSocialLoginContainer.isVisible = true
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted() views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted()
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(id: String?) { override fun onProviderSelected(provider: SsoIdentityProvider?) {
loginViewModel.getSsoUrl( loginViewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId, deviceId = state.deviceId,
providerId = id providerId = provider?.id
) )
?.let { openInCustomTab(it) } ?.let { openInCustomTab(it) }
} }

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2022 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.onboarding
import android.os.Parcelable
import im.vector.app.features.onboarding.AuthenticationDescription.AuthenticationType
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
sealed interface AuthenticationDescription : Parcelable {
@Parcelize
object Login : AuthenticationDescription
@Parcelize
data class Register(val type: AuthenticationType) : AuthenticationDescription
enum class AuthenticationType {
Password,
Apple,
Facebook,
GitHub,
GitLab,
Google,
SSO,
Other
}
}
fun SsoIdentityProvider?.toAuthenticationType() = when (this?.brand) {
SsoIdentityProvider.BRAND_GOOGLE -> AuthenticationType.Google
SsoIdentityProvider.BRAND_GITHUB -> AuthenticationType.GitHub
SsoIdentityProvider.BRAND_APPLE -> AuthenticationType.Apple
SsoIdentityProvider.BRAND_FACEBOOK -> AuthenticationType.Facebook
SsoIdentityProvider.BRAND_GITLAB -> AuthenticationType.GitLab
SsoIdentityProvider.BRAND_TWITTER -> AuthenticationType.SSO
null -> AuthenticationType.SSO
else -> AuthenticationType.SSO
}

View File

@ -276,7 +276,7 @@ class Login2Variant(
is LoginViewEvents2.OnLoginModeNotSupported -> is LoginViewEvents2.OnLoginModeNotSupported ->
onLoginModeNotSupported(event.supportedTypes) onLoginModeNotSupported(event.supportedTypes)
is LoginViewEvents2.OnSessionCreated -> handleOnSessionCreated(event) is LoginViewEvents2.OnSessionCreated -> handleOnSessionCreated(event)
is LoginViewEvents2.Finish -> terminate(true) is LoginViewEvents2.Finish -> terminate()
is LoginViewEvents2.CancelRegistration -> handleCancelRegistration() is LoginViewEvents2.CancelRegistration -> handleCancelRegistration()
} }
} }
@ -296,14 +296,13 @@ class Login2Variant(
option = commonOption option = commonOption
) )
} else { } else {
terminate(false) terminate()
} }
} }
private fun terminate(newAccount: Boolean) { private fun terminate() {
val intent = HomeActivity.newIntent( val intent = HomeActivity.newIntent(
activity, activity
accountCreation = newAccount
) )
activity.startActivity(intent) activity.startActivity(intent)
activity.finish() activity.finish()

View File

@ -54,6 +54,7 @@ import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.auth.HomeServerHistoryService
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.FlowResult
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
@ -255,7 +256,7 @@ class OnboardingViewModel @AssistedInject constructor(
currentJob = viewModelScope.launch { currentJob = viewModelScope.launch {
try { try {
val result = safeLoginWizard.loginWithToken(action.loginToken) val result = safeLoginWizard.loginWithToken(action.loginToken)
onSessionCreated(result, isAccountCreated = false) onSessionCreated(result, authenticationDescription = AuthenticationDescription.Login)
} catch (failure: Throwable) { } catch (failure: Throwable) {
setState { copy(isLoading = false) } setState { copy(isLoading = false) }
_viewEvents.post(OnboardingViewEvents.Failure(failure)) _viewEvents.post(OnboardingViewEvents.Failure(failure))
@ -289,7 +290,11 @@ class OnboardingViewModel @AssistedInject constructor(
// do nothing // do nothing
} }
else -> when (it) { else -> when (it) {
is RegistrationResult.Complete -> onSessionCreated(it.session, isAccountCreated = true) is RegistrationResult.Complete -> onSessionCreated(
it.session,
authenticationDescription = awaitState().selectedAuthenticationState.description
?: AuthenticationDescription.Register(AuthenticationDescription.AuthenticationType.Other)
)
is RegistrationResult.NextStep -> onFlowResponse(it.flowResult, onNextRegistrationStepAction) is RegistrationResult.NextStep -> onFlowResponse(it.flowResult, onNextRegistrationStepAction)
is RegistrationResult.SendEmailSuccess -> _viewEvents.post(OnboardingViewEvents.OnSendEmailSuccess(it.email)) is RegistrationResult.SendEmailSuccess -> _viewEvents.post(OnboardingViewEvents.OnSendEmailSuccess(it.email))
is RegistrationResult.Error -> _viewEvents.post(OnboardingViewEvents.Failure(it.cause)) is RegistrationResult.Error -> _viewEvents.post(OnboardingViewEvents.Failure(it.cause))
@ -319,6 +324,10 @@ class OnboardingViewModel @AssistedInject constructor(
private fun OnboardingViewState.hasSelectedMatrixOrg() = selectedHomeserver.userFacingUrl == matrixOrgUrl private fun OnboardingViewState.hasSelectedMatrixOrg() = selectedHomeserver.userFacingUrl == matrixOrgUrl
private fun handleRegisterWith(action: AuthenticateAction.Register) { private fun handleRegisterWith(action: AuthenticateAction.Register) {
setState {
val authDescription = AuthenticationDescription.Register(AuthenticationDescription.AuthenticationType.Password)
copy(selectedAuthenticationState = SelectedAuthenticationState(authDescription))
}
reAuthHelper.data = action.password reAuthHelper.data = action.password
handleRegisterAction( handleRegisterAction(
RegisterAction.CreateAccount( RegisterAction.CreateAccount(
@ -499,7 +508,7 @@ class OnboardingViewModel @AssistedInject constructor(
setState { copy(isLoading = true) } setState { copy(isLoading = true) }
currentJob = viewModelScope.launch { currentJob = viewModelScope.launch {
directLoginUseCase.execute(action, homeServerConnectionConfig).fold( directLoginUseCase.execute(action, homeServerConnectionConfig).fold(
onSuccess = { onSessionCreated(it, isAccountCreated = false) }, onSuccess = { onSessionCreated(it, authenticationDescription = AuthenticationDescription.Login) },
onFailure = { onFailure = {
setState { copy(isLoading = false) } setState { copy(isLoading = false) }
_viewEvents.post(OnboardingViewEvents.Failure(it)) _viewEvents.post(OnboardingViewEvents.Failure(it))
@ -524,7 +533,7 @@ class OnboardingViewModel @AssistedInject constructor(
action.initialDeviceName action.initialDeviceName
) )
reAuthHelper.data = action.password reAuthHelper.data = action.password
onSessionCreated(result, isAccountCreated = false) onSessionCreated(result, authenticationDescription = AuthenticationDescription.Login)
} catch (failure: Throwable) { } catch (failure: Throwable) {
setState { copy(isLoading = false) } setState { copy(isLoading = false) }
_viewEvents.post(OnboardingViewEvents.Failure(failure)) _viewEvents.post(OnboardingViewEvents.Failure(failure))
@ -553,7 +562,7 @@ class OnboardingViewModel @AssistedInject constructor(
internalRegisterAction(RegisterAction.RegisterDummy, onNextRegistrationStepAction) internalRegisterAction(RegisterAction.RegisterDummy, onNextRegistrationStepAction)
} }
private suspend fun onSessionCreated(session: Session, isAccountCreated: Boolean) { private suspend fun onSessionCreated(session: Session, authenticationDescription: AuthenticationDescription) {
val state = awaitState() val state = awaitState()
state.useCase?.let { useCase -> state.useCase?.let { useCase ->
session.vectorStore(applicationContext).setUseCase(useCase) session.vectorStore(applicationContext).setUseCase(useCase)
@ -564,15 +573,15 @@ class OnboardingViewModel @AssistedInject constructor(
authenticationService.reset() authenticationService.reset()
session.configureAndStart(applicationContext) session.configureAndStart(applicationContext)
when (isAccountCreated) { when (authenticationDescription) {
true -> { is AuthenticationDescription.Register -> {
val personalizationState = createPersonalizationState(session, state) val personalizationState = createPersonalizationState(session, state)
setState { setState {
copy(isLoading = false, personalizationState = personalizationState) copy(isLoading = false, personalizationState = personalizationState)
} }
_viewEvents.post(OnboardingViewEvents.OnAccountCreated) _viewEvents.post(OnboardingViewEvents.OnAccountCreated)
} }
false -> { AuthenticationDescription.Login -> {
setState { copy(isLoading = false) } setState { copy(isLoading = false) }
_viewEvents.post(OnboardingViewEvents.OnAccountSignedIn) _viewEvents.post(OnboardingViewEvents.OnAccountSignedIn)
} }
@ -603,7 +612,7 @@ class OnboardingViewModel @AssistedInject constructor(
currentJob = viewModelScope.launch { currentJob = viewModelScope.launch {
try { try {
val result = authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials) val result = authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials)
onSessionCreated(result, isAccountCreated = false) onSessionCreated(result, authenticationDescription = AuthenticationDescription.Login)
} catch (failure: Throwable) { } catch (failure: Throwable) {
setState { copy(isLoading = false) } setState { copy(isLoading = false) }
} }
@ -745,8 +754,12 @@ class OnboardingViewModel @AssistedInject constructor(
return loginConfig?.homeServerUrl return loginConfig?.homeServerUrl
} }
fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? { fun fetchSsoUrl(redirectUrl: String, deviceId: String?, provider: SsoIdentityProvider?): String? {
return authenticationService.getSsoUrl(redirectUrl, deviceId, providerId) setState {
val authDescription = AuthenticationDescription.Register(provider.toAuthenticationType())
copy(selectedAuthenticationState = SelectedAuthenticationState(authDescription))
}
return authenticationService.getSsoUrl(redirectUrl, deviceId, provider?.id)
} }
fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? { fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? {

View File

@ -51,6 +51,9 @@ data class OnboardingViewState(
@PersistState @PersistState
val selectedHomeserver: SelectedHomeserverState = SelectedHomeserverState(), val selectedHomeserver: SelectedHomeserverState = SelectedHomeserverState(),
@PersistState
val selectedAuthenticationState: SelectedAuthenticationState = SelectedAuthenticationState(),
@PersistState @PersistState
val personalizationState: PersonalizationState = PersonalizationState() val personalizationState: PersonalizationState = PersonalizationState()
) : MavericksState ) : MavericksState
@ -80,3 +83,8 @@ data class PersonalizationState(
fun supportsPersonalization() = supportsChangingDisplayName || supportsChangingProfilePicture fun supportsPersonalization() = supportsChangingDisplayName || supportsChangingProfilePicture
} }
@Parcelize
data class SelectedAuthenticationState(
val description: AuthenticationDescription? = null,
) : Parcelable

View File

@ -90,10 +90,10 @@ abstract class AbstractSSOFtueAuthFragment<VB : ViewBinding> : AbstractFtueAuthF
withState(viewModel) { state -> withState(viewModel) { state ->
if (state.selectedHomeserver.preferredLoginMode.hasSso() && state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders().isNullOrEmpty()) { if (state.selectedHomeserver.preferredLoginMode.hasSso() && state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders().isNullOrEmpty()) {
// in this case we can prefetch (not other cases for privacy concerns) // in this case we can prefetch (not other cases for privacy concerns)
viewModel.getSsoUrl( viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId, deviceId = state.deviceId,
providerId = null provider = null
) )
?.let { prefetchUrl(it) } ?.let { prefetchUrl(it) }
} }

View File

@ -131,10 +131,10 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
views.ssoButtonsHeader.isVisible = views.ssoGroup.isVisible && views.loginEntryGroup.isVisible views.ssoButtonsHeader.isVisible = views.ssoGroup.isVisible && views.loginEntryGroup.isVisible
views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id -> views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
viewModel.getSsoUrl( viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = deviceId, deviceId = deviceId,
providerId = id provider = id
)?.let { openInCustomTab(it) } )?.let { openInCustomTab(it) }
} }
} }

View File

@ -164,11 +164,11 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
private fun renderSsoProviders(deviceId: String?, ssoProviders: List<SsoIdentityProvider>?) { private fun renderSsoProviders(deviceId: String?, ssoProviders: List<SsoIdentityProvider>?) {
views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id -> views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { provider ->
viewModel.getSsoUrl( viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = deviceId, deviceId = deviceId,
providerId = id provider = provider
)?.let { openInCustomTab(it) } )?.let { openInCustomTab(it) }
} }
} }

View File

@ -45,6 +45,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidPassword
import org.matrix.android.sdk.api.failure.isInvalidUsername import org.matrix.android.sdk.api.failure.isInvalidUsername
import org.matrix.android.sdk.api.failure.isLoginEmailUnknown import org.matrix.android.sdk.api.failure.isLoginEmailUnknown
@ -216,11 +217,11 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
views.loginSocialLoginContainer.isVisible = true views.loginSocialLoginContainer.isVisible = true
views.loginSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders?.sorted() views.loginSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders?.sorted()
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(id: String?) { override fun onProviderSelected(provider: SsoIdentityProvider?) {
viewModel.getSsoUrl( viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId, deviceId = state.deviceId,
providerId = id provider = provider
) )
?.let { openInCustomTab(it) } ?.let { openInCustomTab(it) }
} }

View File

@ -34,6 +34,7 @@ import im.vector.app.features.login.SocialLoginButtonsView
import im.vector.app.features.login.ssoIdentityProviders import im.vector.app.features.login.ssoIdentityProviders
import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingViewState import im.vector.app.features.onboarding.OnboardingViewState
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -81,11 +82,11 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders()?.sorted() views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders()?.sorted()
views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(id: String?) { override fun onProviderSelected(provider: SsoIdentityProvider?) {
viewModel.getSsoUrl( viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId, deviceId = state.deviceId,
providerId = id provider = provider
) )
?.let { openInCustomTab(it) } ?.let { openInCustomTab(it) }
} }
@ -123,10 +124,10 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF
private fun submit() = withState(viewModel) { state -> private fun submit() = withState(viewModel) { state ->
if (state.selectedHomeserver.preferredLoginMode is LoginMode.Sso) { if (state.selectedHomeserver.preferredLoginMode is LoginMode.Sso) {
viewModel.getSsoUrl( viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId, deviceId = state.deviceId,
providerId = null provider = null
) )
?.let { openInCustomTab(it) } ?.let { openInCustomTab(it) }
} else { } else {

View File

@ -216,7 +216,7 @@ class FtueAuthVariant(
is OnboardingViewEvents.OnAccountCreated -> onAccountCreated() is OnboardingViewEvents.OnAccountCreated -> onAccountCreated()
OnboardingViewEvents.OnAccountSignedIn -> onAccountSignedIn() OnboardingViewEvents.OnAccountSignedIn -> onAccountSignedIn()
OnboardingViewEvents.OnChooseDisplayName -> onChooseDisplayName() OnboardingViewEvents.OnChooseDisplayName -> onChooseDisplayName()
OnboardingViewEvents.OnTakeMeHome -> navigateToHome(createdAccount = true) OnboardingViewEvents.OnTakeMeHome -> navigateToHome()
OnboardingViewEvents.OnChooseProfilePicture -> onChooseProfilePicture() OnboardingViewEvents.OnChooseProfilePicture -> onChooseProfilePicture()
OnboardingViewEvents.OnPersonalizationComplete -> onPersonalizationComplete() OnboardingViewEvents.OnPersonalizationComplete -> onPersonalizationComplete()
OnboardingViewEvents.OnBack -> activity.popBackstack() OnboardingViewEvents.OnBack -> activity.popBackstack()
@ -467,7 +467,7 @@ class FtueAuthVariant(
} }
private fun onAccountSignedIn() { private fun onAccountSignedIn() {
navigateToHome(createdAccount = false) navigateToHome()
} }
private fun onAccountCreated() { private fun onAccountCreated() {
@ -479,11 +479,13 @@ class FtueAuthVariant(
) )
} }
private fun navigateToHome(createdAccount: Boolean) { private fun navigateToHome() {
val intent = HomeActivity.newIntent(activity, accountCreation = createdAccount) withState(onboardingViewModel) {
val intent = HomeActivity.newIntent(activity, authenticationDescription = it.selectedAuthenticationState.description)
activity.startActivity(intent) activity.startActivity(intent)
activity.finish() activity.finish()
} }
}
private fun onChooseDisplayName() { private fun onChooseDisplayName() {
activity.addFragmentToBackstack( activity.addFragmentToBackstack(