passing the authentication state from the onboarding and tracking sign up after the user has consented to tracking

This commit is contained in:
Adam Brown 2022-05-13 08:45:45 +01:00
parent ac89495348
commit 28050488ba
12 changed files with 112 additions and 40 deletions

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
@ -248,7 +249,7 @@ class HomeActivity :
if (isFirstCreation()) { if (isFirstCreation()) {
handleIntent(intent) handleIntent(intent)
} }
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted) homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted(args?.authenticationDescription))
} }
private fun openGroup(shouldClearFragment: Boolean) { private fun openGroup(shouldClearFragment: Boolean) {
@ -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

@ -17,8 +17,9 @@
package im.vector.app.features.home package im.vector.app.features.home
import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.onboarding.AuthenticationDescription
sealed interface HomeActivityViewActions : VectorViewModelAction { sealed interface HomeActivityViewActions : VectorViewModelAction {
object ViewStarted : HomeActivityViewActions data class ViewStarted(val recentAuthentication: AuthenticationDescription?) : HomeActivityViewActions
object PushPromptHasBeenReviewed : HomeActivityViewActions object PushPromptHasBeenReviewed : HomeActivityViewActions
} }

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
@ -37,8 +41,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
@ -72,7 +79,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
@ -84,7 +92,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)
} }
} }
@ -93,18 +101,18 @@ class HomeActivityViewModel @AssistedInject constructor(
private var hasCheckedBootstrap = false private var hasCheckedBootstrap = false
private var onceTrusted = false private var onceTrusted = false
private fun initialize() { private fun initialize(recentAuthentication: AuthenticationDescription?) {
if (isInitialized) return if (isInitialized) return
isInitialized = true isInitialized = true
cleanupFiles() cleanupFiles()
observeInitialSync() observeInitialSync()
checkSessionPushIsOn() checkSessionPushIsOn()
observeCrossSigningReset() observeCrossSigningReset()
observeAnalytics() observeAnalytics(recentAuthentication)
initThreadsMigration() initThreadsMigration()
} }
private fun observeAnalytics() { private fun observeAnalytics(recentAuthentication: AuthenticationDescription?) {
if (analyticsConfig.isEnabled) { if (analyticsConfig.isEnabled) {
analyticsStore.didAskUserConsentFlow analyticsStore.didAskUserConsentFlow
.onEach { didAskUser -> .onEach { didAskUser ->
@ -113,8 +121,30 @@ class HomeActivityViewModel @AssistedInject constructor(
} }
} }
.launchIn(viewModelScope) .launchIn(viewModelScope)
recentAuthentication?.let {
when (recentAuthentication) {
is AuthenticationDescription.Register -> {
viewModelScope.launch {
analyticsStore.onUserGaveConsent {
analyticsTracker.capture(Signup(authenticationType = recentAuthentication.type.toAnalyticsType()))
} }
} }
}
AuthenticationDescription.Login -> {
// 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 +315,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 {
@ -395,8 +425,8 @@ class HomeActivityViewModel @AssistedInject constructor(
HomeActivityViewActions.PushPromptHasBeenReviewed -> { HomeActivityViewActions.PushPromptHasBeenReviewed -> {
vectorPreferences.setDidAskUserToEnableSessionPush() vectorPreferences.setDidAskUserToEnableSessionPush()
} }
HomeActivityViewActions.ViewStarted -> { is HomeActivityViewActions.ViewStarted -> {
initialize() initialize(action.recentAuthentication)
} }
} }
} }

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

@ -218,10 +218,7 @@ 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 intent = HomeActivity.newIntent(this)
this,
accountCreation = loginViewState.signMode == SignMode.SignUp
)
startActivity(intent) startActivity(intent)
finish() finish()
return return

View File

@ -16,12 +16,17 @@
package im.vector.app.features.onboarding package im.vector.app.features.onboarding
import android.os.Parcelable
import im.vector.app.features.onboarding.AuthenticationDescription.AuthenticationType import im.vector.app.features.onboarding.AuthenticationDescription.AuthenticationType
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
sealed interface AuthenticationDescription { sealed interface AuthenticationDescription : Parcelable {
@Parcelize
object Login : AuthenticationDescription object Login : AuthenticationDescription
data class AccountCreated(val type: AuthenticationType) : AuthenticationDescription
@Parcelize
data class Register(val type: AuthenticationType) : AuthenticationDescription
enum class AuthenticationType { enum class AuthenticationType {
Password, Password,

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

@ -292,9 +292,8 @@ class OnboardingViewModel @AssistedInject constructor(
else -> when (it) { else -> when (it) {
is RegistrationResult.Complete -> onSessionCreated( is RegistrationResult.Complete -> onSessionCreated(
it.session, it.session,
authenticationDescription = AuthenticationDescription.AccountCreated( authenticationDescription = awaitState().selectedAuthenticationState.description
awaitState().selectedAuthenticationState.type ?: AuthenticationDescription.AuthenticationType.Other ?: 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))
@ -325,7 +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 { copy(selectedAuthenticationState = SelectedAuthenticationState(AuthenticationDescription.AuthenticationType.Password)) } 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(
@ -572,7 +574,7 @@ class OnboardingViewModel @AssistedInject constructor(
session.configureAndStart(applicationContext) session.configureAndStart(applicationContext)
when (authenticationDescription) { when (authenticationDescription) {
is AuthenticationDescription.AccountCreated -> { 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)
@ -753,7 +755,10 @@ class OnboardingViewModel @AssistedInject constructor(
} }
fun getSsoUrl(redirectUrl: String, deviceId: String?, provider: SsoIdentityProvider?): String? { fun getSsoUrl(redirectUrl: String, deviceId: String?, provider: SsoIdentityProvider?): String? {
setState { copy(selectedAuthenticationState = SelectedAuthenticationState(provider.toAuthenticationType())) } setState {
val authDescription = AuthenticationDescription.Register(provider.toAuthenticationType())
copy(selectedAuthenticationState = SelectedAuthenticationState(authDescription))
}
return authenticationService.getSsoUrl(redirectUrl, deviceId, provider?.id) return authenticationService.getSsoUrl(redirectUrl, deviceId, provider?.id)
} }

View File

@ -86,5 +86,5 @@ data class PersonalizationState(
@Parcelize @Parcelize
data class SelectedAuthenticationState( data class SelectedAuthenticationState(
val type: AuthenticationDescription.AuthenticationType? = null, val description: AuthenticationDescription? = null,
) : Parcelable ) : Parcelable

View File

@ -134,7 +134,7 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
viewModel.getSsoUrl( viewModel.getSsoUrl(
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

@ -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(