Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/5285.wip
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FTUE - Adds Sign Up tracking
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,24 @@ 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
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
Expand Down Expand Up @@ -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
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -113,9 +121,32 @@ class HomeActivityViewModel @AssistedInject constructor(
}
}
.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() {
// Mitigation: delete all cached decrypted files each time the application is started.
activeSessionHolder.getSafeActiveSession()?.fileService()?.clearDecryptedCache()
Expand Down Expand Up @@ -191,10 +222,10 @@ class HomeActivityViewModel @AssistedInject constructor(
.asFlow()
.onEach { status ->
when (status) {
is SyncStatusService.Status.Idle -> {
is SyncStatusService.Status.Idle -> {
maybeVerifyOrBootstrapCrossSigning()
}
else -> Unit
else -> Unit
}

setState {
Expand Down Expand Up @@ -285,7 +316,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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
14 changes: 10 additions & 4 deletions vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.home.HomeActivity
import im.vector.app.features.login.terms.LoginTermsFragment
import im.vector.app.features.login.terms.LoginTermsFragmentArgument
import im.vector.app.features.onboarding.AuthenticationDescription
import im.vector.app.features.pin.UnlockedActivity
import org.matrix.android.sdk.api.auth.registration.FlowResult
import org.matrix.android.sdk.api.auth.registration.Stage
Expand Down Expand Up @@ -218,10 +219,8 @@ 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 authDescription = inferAuthDescription(loginViewState)
val intent = HomeActivity.newIntent(this, authenticationDescription = authDescription)
startActivity(intent)
finish()
return
Expand All @@ -231,6 +230,13 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedA
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) {
// Pop the backstack
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
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.MatrixError
import org.matrix.android.sdk.api.failure.isInvalidPassword
Expand Down Expand Up @@ -202,11 +203,11 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
views.loginSocialLoginContainer.isVisible = true
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted()
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(id: String?) {
override fun onProviderSelected(provider: SsoIdentityProvider?) {
Copy link
Contributor Author

@ouchadam ouchadam Jun 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to know the SsoIdentityProvider in order to provide a tracking value (only used by the FTUE classes)

loginViewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = id
providerId = provider?.id
)
?.let { openInCustomTab(it) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.databinding.FragmentLoginSignupSigninSelectionBinding
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import javax.inject.Inject

/**
Expand Down Expand Up @@ -74,11 +75,11 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders()?.sorted()
views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(id: String?) {
override fun onProviderSelected(provider: SsoIdentityProvider?) {
loginViewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = id
providerId = provider?.id
)
?.let { openInCustomTab(it) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
LinearLayout(context, attrs, defStyle) {

fun interface InteractionListener {
fun onProviderSelected(id: String?)
fun onProviderSelected(provider: SsoIdentityProvider?)
}

enum class Mode {
Expand Down Expand Up @@ -113,7 +113,7 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
button.text = getButtonTitle(identityProvider.name)
button.setTag(R.id.loginSignupSigninSocialLoginButtons, identityProvider.id)
button.setOnClickListener {
listener?.onProviderSelected(identityProvider.id)
listener?.onProviderSelected(identityProvider)
}
addView(button)
}
Expand Down Expand Up @@ -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.ssoIdentityProviders = ssoProviders?.sorted()
this.listener = SocialLoginButtonsView.InteractionListener { listener(it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import im.vector.app.features.login.SocialLoginButtonsView
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject

Expand Down Expand Up @@ -96,11 +97,11 @@ class LoginFragmentSignupUsername2 @Inject constructor() : AbstractSSOLoginFragm
views.loginSocialLoginContainer.isVisible = true
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted()
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(id: String?) {
override fun onProviderSelected(provider: SsoIdentityProvider?) {
loginViewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = id
providerId = provider?.id
)
?.let { openInCustomTab(it) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
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.MatrixError
import org.matrix.android.sdk.api.failure.isInvalidPassword
Expand Down Expand Up @@ -123,11 +124,11 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2<Frag
views.loginSocialLoginContainer.isVisible = true
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted()
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(id: String?) {
override fun onProviderSelected(provider: SsoIdentityProvider?) {
loginViewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = id
providerId = provider?.id
)
?.let { openInCustomTab(it) }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Loading