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.navigation.Navigator
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.PermalinkHandler
import im.vector.app.features.permalink.PermalinkHandler.Companion.MATRIX_TO_CUSTOM_SCHEME_URL_BASE
@ -91,7 +92,7 @@ import javax.inject.Inject
@Parcelize
data class HomeActivityArgs(
val clearNotification: Boolean,
val accountCreation: Boolean,
val authenticationDescription: AuthenticationDescription? = null,
val hasExistingSession: Boolean = false,
val inviteNotificationRoomId: String? = null
) : Parcelable
@ -248,7 +249,7 @@ class HomeActivity :
if (isFirstCreation()) {
handleIntent(intent)
}
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted)
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted(args?.authenticationDescription))
}
private fun openGroup(shouldClearFragment: Boolean) {
@ -612,13 +613,13 @@ class HomeActivity :
fun newIntent(
context: Context,
clearNotification: Boolean = false,
accountCreation: Boolean = false,
authenticationDescription: AuthenticationDescription? = null,
existingSession: Boolean = false,
inviteNotificationRoomId: String? = null
): Intent {
val args = HomeActivityArgs(
clearNotification = clearNotification,
accountCreation = accountCreation,
authenticationDescription = authenticationDescription,
hasExistingSession = existingSession,
inviteNotificationRoomId = inviteNotificationRoomId
)

View File

@ -17,8 +17,9 @@
package im.vector.app.features.home
import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.onboarding.AuthenticationDescription
sealed interface HomeActivityViewActions : VectorViewModelAction {
object ViewStarted : HomeActivityViewActions
data class ViewStarted(val recentAuthentication: AuthenticationDescription?) : 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.hiltMavericksViewModelFactory
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.login.ReAuthHelper
import im.vector.app.features.onboarding.AuthenticationDescription
import im.vector.app.features.raw.wellknown.ElementWellKnown
import im.vector.app.features.raw.wellknown.getElementWellknown
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
@ -72,7 +79,8 @@ class HomeActivityViewModel @AssistedInject constructor(
private val reAuthHelper: ReAuthHelper,
private val analyticsStore: AnalyticsStore,
private val lightweightSettingsStorage: LightweightSettingsStorage,
private val vectorPreferences: VectorPreferences
private val vectorPreferences: VectorPreferences,
private val analyticsTracker: AnalyticsTracker
) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) {
@AssistedFactory
@ -84,7 +92,7 @@ class HomeActivityViewModel @AssistedInject constructor(
override fun initialState(viewModelContext: ViewModelContext): HomeActivityViewState? {
val activity: HomeActivity = viewModelContext.activity()
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)
}
}
@ -93,18 +101,18 @@ class HomeActivityViewModel @AssistedInject constructor(
private var hasCheckedBootstrap = false
private var onceTrusted = false
private fun initialize() {
private fun initialize(recentAuthentication: AuthenticationDescription?) {
if (isInitialized) return
isInitialized = true
cleanupFiles()
observeInitialSync()
checkSessionPushIsOn()
observeCrossSigningReset()
observeAnalytics()
observeAnalytics(recentAuthentication)
initThreadsMigration()
}
private fun observeAnalytics() {
private fun observeAnalytics(recentAuthentication: AuthenticationDescription?) {
if (analyticsConfig.isEnabled) {
analyticsStore.didAskUserConsentFlow
.onEach { didAskUser ->
@ -113,9 +121,31 @@ class HomeActivityViewModel @AssistedInject constructor(
}
}
.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() {
// Mitigation: delete all cached decrypted files each time the application is started.
activeSessionHolder.getSafeActiveSession()?.fileService()?.clearDecryptedCache()
@ -285,7 +315,7 @@ class HomeActivityViewModel @AssistedInject constructor(
val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() ?: false
// In case of account creation, it is already done before
if (initialState.accountCreation) {
if (initialState.authenticationDescription is AuthenticationDescription.Register) {
if (isSecureBackupRequired) {
_viewEvents.post(HomeActivityViewEvents.StartRecoverySetupFlow)
} else {
@ -395,8 +425,8 @@ class HomeActivityViewModel @AssistedInject constructor(
HomeActivityViewActions.PushPromptHasBeenReviewed -> {
vectorPreferences.setDidAskUserToEnableSessionPush()
}
HomeActivityViewActions.ViewStarted -> {
initialize()
is HomeActivityViewActions.ViewStarted -> {
initialize(action.recentAuthentication)
}
}
}

View File

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

View File

@ -218,10 +218,7 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedA
// change the screen name
analyticsScreenName = MobileScreen.ScreenName.Register
}
val intent = HomeActivity.newIntent(
this,
accountCreation = loginViewState.signMode == SignMode.SignUp
)
val intent = HomeActivity.newIntent(this)
startActivity(intent)
finish()
return

View File

@ -16,12 +16,17 @@
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 {
sealed interface AuthenticationDescription : Parcelable {
@Parcelize
object Login : AuthenticationDescription
data class AccountCreated(val type: AuthenticationType) : AuthenticationDescription
@Parcelize
data class Register(val type: AuthenticationType) : AuthenticationDescription
enum class AuthenticationType {
Password,

View File

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

View File

@ -292,9 +292,8 @@ class OnboardingViewModel @AssistedInject constructor(
else -> when (it) {
is RegistrationResult.Complete -> onSessionCreated(
it.session,
authenticationDescription = AuthenticationDescription.AccountCreated(
awaitState().selectedAuthenticationState.type ?: AuthenticationDescription.AuthenticationType.Other
)
authenticationDescription = awaitState().selectedAuthenticationState.description
?: AuthenticationDescription.Register(AuthenticationDescription.AuthenticationType.Other)
)
is RegistrationResult.NextStep -> onFlowResponse(it.flowResult, onNextRegistrationStepAction)
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 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
handleRegisterAction(
RegisterAction.CreateAccount(
@ -572,14 +574,14 @@ class OnboardingViewModel @AssistedInject constructor(
session.configureAndStart(applicationContext)
when (authenticationDescription) {
is AuthenticationDescription.AccountCreated -> {
is AuthenticationDescription.Register -> {
val personalizationState = createPersonalizationState(session, state)
setState {
copy(isLoading = false, personalizationState = personalizationState)
}
_viewEvents.post(OnboardingViewEvents.OnAccountCreated)
}
AuthenticationDescription.Login -> {
AuthenticationDescription.Login -> {
setState { copy(isLoading = false) }
_viewEvents.post(OnboardingViewEvents.OnAccountSignedIn)
}
@ -753,7 +755,10 @@ class OnboardingViewModel @AssistedInject constructor(
}
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)
}

View File

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

View File

@ -134,7 +134,7 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
viewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = deviceId,
providerId = id
provider = id
)?.let { openInCustomTab(it) }
}
}

View File

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