Skip to content

Commit

Permalink
Merge pull request #5009 from vector-im/feature/adm/storing-use-case
Browse files Browse the repository at this point in the history
Storing and tracking the onboarding messaging use case
  • Loading branch information
ouchadam authored Jan 31, 2022
2 parents 7dad086 + cbdeb54 commit 986d9f9
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 14 deletions.
3 changes: 3 additions & 0 deletions vector/src/main/java/im/vector/app/core/extensions/Session.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import im.vector.app.core.services.VectorSyncService
import im.vector.app.features.session.VectorSessionStore
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import org.matrix.android.sdk.api.session.sync.FilterService
Expand Down Expand Up @@ -76,3 +77,5 @@ fun Session.cannotLogoutSafely(): Boolean {
// That are not backed up
!sharedSecretStorageService.isRecoverySetup())
}

fun Session.vectorStore(context: Context) = VectorSessionStore(context, myUserId)
13 changes: 9 additions & 4 deletions vector/src/main/java/im/vector/app/features/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.startSyncing
import im.vector.app.core.extensions.vectorStore
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.deleteAllFiles
import im.vector.app.databinding.ActivityMainBinding
Expand All @@ -40,6 +41,7 @@ import im.vector.app.features.pin.PinCodeStore
import im.vector.app.features.pin.PinLocker
import im.vector.app.features.pin.UnlockedActivity
import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.session.VectorSessionStore
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.signout.hard.SignedOutActivity
import im.vector.app.features.themes.ActivityOtherThemes
Expand Down Expand Up @@ -143,13 +145,15 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
startNextActivityAndFinish()
return
}

val onboardingStore = session.vectorStore(this)
when {
args.isAccountDeactivated -> {
lifecycleScope.launch {
// Just do the local cleanup
Timber.w("Account deactivated, start app")
sessionHolder.clearActiveSession()
doLocalCleanup(clearPreferences = true)
doLocalCleanup(clearPreferences = true, onboardingStore)
startNextActivityAndFinish()
}
}
Expand All @@ -163,14 +167,14 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
}
Timber.w("SIGN_OUT: success, start app")
sessionHolder.clearActiveSession()
doLocalCleanup(clearPreferences = true)
doLocalCleanup(clearPreferences = true, onboardingStore)
startNextActivityAndFinish()
}
}
args.clearCache -> {
lifecycleScope.launch {
session.clearCache()
doLocalCleanup(clearPreferences = false)
doLocalCleanup(clearPreferences = false, onboardingStore)
session.startSyncing(applicationContext)
startNextActivityAndFinish()
}
Expand All @@ -183,7 +187,7 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
Timber.w("Ignoring invalid token global error")
}

private suspend fun doLocalCleanup(clearPreferences: Boolean) {
private suspend fun doLocalCleanup(clearPreferences: Boolean, vectorSessionStore: VectorSessionStore) {
// On UI Thread
Glide.get(this@MainActivity).clearMemory()

Expand All @@ -193,6 +197,7 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
pinLocker.unlock()
pinCodeStore.deleteEncodedPin()
vectorAnalytics.onSignOut()
vectorSessionStore.clear()
}
withContext(Dispatchers.IO) {
// On BG thread
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package im.vector.app.features.analytics

import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
import im.vector.app.features.analytics.plan.Identity

interface AnalyticsTracker {
/**
Expand All @@ -29,4 +30,9 @@ interface AnalyticsTracker {
* Track a displayed screen
*/
fun screen(screen: VectorAnalyticsScreen)

/**
* Update user specific properties
*/
fun updateUserProperties(identity: Identity)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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.Identity
import im.vector.app.features.onboarding.FtueUseCase

fun FtueUseCase.toTrackingValue(): Identity.FtueUseCaseSelection {
return when (this) {
FtueUseCase.FRIENDS_FAMILY -> Identity.FtueUseCaseSelection.PersonalMessaging
FtueUseCase.TEAMS -> Identity.FtueUseCaseSelection.WorkMessaging
FtueUseCase.COMMUNITIES -> Identity.FtueUseCaseSelection.CommunityMessaging
FtueUseCase.SKIP -> Identity.FtueUseCaseSelection.Skip
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package im.vector.app.features.analytics.impl

import android.content.Context
import com.posthog.android.Options
import com.posthog.android.PostHog
import com.posthog.android.Properties
import im.vector.app.BuildConfig
Expand All @@ -25,6 +26,7 @@ import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
import im.vector.app.features.analytics.log.analyticsTag
import im.vector.app.features.analytics.plan.Identity
import im.vector.app.features.analytics.store.AnalyticsStore
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.Flow
Expand All @@ -34,6 +36,9 @@ import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton

private val REUSE_EXISTING_ID: String? = null
private val IGNORED_OPTIONS: Options? = null

@Singleton
class DefaultVectorAnalytics @Inject constructor(
private val context: Context,
Expand Down Expand Up @@ -170,6 +175,10 @@ class DefaultVectorAnalytics @Inject constructor(
?.screen(screen.getName(), screen.getProperties()?.toPostHogProperties())
}

override fun updateUserProperties(identity: Identity) {
posthog?.identify(REUSE_EXISTING_ID, identity.getProperties().toPostHogProperties(), IGNORED_OPTIONS)
}

private fun Map<String, Any>?.toPostHogProperties(): Properties? {
if (this == null) return null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@

package im.vector.app.features.onboarding

enum class FtueUseCase {
FRIENDS_FAMILY,
TEAMS,
COMMUNITIES,
SKIP
enum class FtueUseCase(val persistableValue: String) {
FRIENDS_FAMILY("friends_family"),
TEAMS("teams"),
COMMUNITIES("communities"),
SKIP("skip");

companion object {
fun from(persistedValue: String) = values().first { it.persistableValue == persistedValue }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.configureAndStart
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.vectorStore
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.ensureTrailingSlash
import im.vector.app.features.VectorFeatures
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toTrackingValue
import im.vector.app.features.analytics.plan.Identity
import im.vector.app.features.login.HomeServerConnectionConfigFactory
import im.vector.app.features.login.LoginConfig
import im.vector.app.features.login.LoginMode
Expand Down Expand Up @@ -73,7 +77,8 @@ class OnboardingViewModel @AssistedInject constructor(
private val reAuthHelper: ReAuthHelper,
private val stringProvider: StringProvider,
private val homeServerHistoryService: HomeServerHistoryService,
private val vectorFeatures: VectorFeatures
private val vectorFeatures: VectorFeatures,
private val analyticsTracker: AnalyticsTracker
) : VectorViewModel<OnboardingViewState, OnboardingAction, OnboardingViewEvents>(initialState) {

@AssistedFactory
Expand Down Expand Up @@ -125,7 +130,7 @@ class OnboardingViewModel @AssistedInject constructor(
when (action) {
is OnboardingAction.OnGetStarted -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow)
is OnboardingAction.OnIAlreadyHaveAnAccount -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow)
is OnboardingAction.UpdateUseCase -> handleUpdateUseCase()
is OnboardingAction.UpdateUseCase -> handleUpdateUseCase(action)
OnboardingAction.ResetUseCase -> resetUseCase()
is OnboardingAction.UpdateServerType -> handleUpdateServerType(action)
is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action)
Expand Down Expand Up @@ -458,13 +463,15 @@ class OnboardingViewModel @AssistedInject constructor(
}
}

private fun handleUpdateUseCase() {
// TODO act on the use case selection
private fun handleUpdateUseCase(action: OnboardingAction.UpdateUseCase) {
setState { copy(useCase = action.useCase) }
analyticsTracker.updateUserProperties(Identity(ftueUseCaseSelection = action.useCase.toTrackingValue()))
_viewEvents.post(OnboardingViewEvents.OpenServerSelection)
}

private fun resetUseCase() {
// TODO remove stored use case
setState { copy(useCase = null) }
analyticsTracker.updateUserProperties(Identity(ftueUseCaseSelection = null))
}

private fun handleUpdateServerType(action: OnboardingAction.UpdateServerType) {
Expand Down Expand Up @@ -745,6 +752,9 @@ class OnboardingViewModel @AssistedInject constructor(
}

private suspend fun onSessionCreated(session: Session) {
awaitState().useCase?.let { useCase ->
session.vectorStore(applicationContext).setUseCase(useCase)
}
activeSessionHolder.setActiveSession(session)

authenticationService.reset()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ data class OnboardingViewState(
@PersistState
val serverType: ServerType = ServerType.Unknown,
@PersistState
val useCase: FtueUseCase? = null,
@PersistState
val signMode: SignMode = SignMode.Unknown,
@PersistState
val resetPasswordEmail: String? = null,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2021 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.session

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import im.vector.app.features.onboarding.FtueUseCase
import kotlinx.coroutines.flow.first
import org.matrix.android.sdk.internal.util.md5

/**
* Local storage for:
* - messaging use case (Enum/String)
*/
class VectorSessionStore constructor(
private val context: Context,
myUserId: String
) {

private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "vector_session_store_${myUserId.md5()}")
private val useCaseKey = stringPreferencesKey("use_case")

suspend fun readUseCase() = context.dataStore.data.first().let { preferences ->
preferences[useCaseKey]?.let { FtueUseCase.from(it) }
}

suspend fun setUseCase(useCase: FtueUseCase) {
context.dataStore.edit { settings ->
settings[useCaseKey] = useCase.persistableValue
}
}

suspend fun resetUseCase() {
context.dataStore.edit { settings ->
settings.remove(useCaseKey)
}
}

suspend fun clear() {
context.dataStore.edit { settings -> settings.clear() }
}
}

0 comments on commit 986d9f9

Please sign in to comment.