Merge pull request #4734 from vector-im/feature/bma/analytics_next

Implement analytics plan
This commit is contained in:
Benoit Marty 2022-01-20 18:12:46 +01:00 committed by GitHub
commit 9bfeb6f814
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 882 additions and 151 deletions

1
changelog.d/4734.misc Normal file
View file

@ -0,0 +1 @@
Analytics: send more Events

View file

@ -160,7 +160,7 @@ Formatter\.formatShortFileSize===1
# android\.text\.TextUtils # android\.text\.TextUtils
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
enum class===119 enum class===121
### Do not import temporary legacy classes ### Do not import temporary legacy classes
import org.matrix.android.sdk.internal.legacy.riot===3 import org.matrix.android.sdk.internal.legacy.riot===3

View file

@ -21,7 +21,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import im.vector.app.core.dialogs.UnrecognizedCertificateDialog import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.features.analytics.VectorAnalytics import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
@ -56,7 +56,7 @@ interface SingletonEntryPoint {
fun pinLocker(): PinLocker fun pinLocker(): PinLocker
fun analytics(): VectorAnalytics fun analyticsTracker(): AnalyticsTracker
fun webRtcCallManager(): WebRtcCallManager fun webRtcCallManager(): WebRtcCallManager

View file

@ -33,6 +33,7 @@ import im.vector.app.core.error.DefaultErrorFormatter
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.time.Clock import im.vector.app.core.time.Clock
import im.vector.app.core.time.DefaultClock import im.vector.app.core.time.DefaultClock
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.VectorAnalytics import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.impl.DefaultVectorAnalytics import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.AutoAcceptInvites
@ -64,6 +65,9 @@ abstract class VectorBindModule {
@Binds @Binds
abstract fun bindVectorAnalytics(analytics: DefaultVectorAnalytics): VectorAnalytics abstract fun bindVectorAnalytics(analytics: DefaultVectorAnalytics): VectorAnalytics
@Binds
abstract fun bindAnalyticsTracker(analytics: DefaultVectorAnalytics): AnalyticsTracker
@Binds @Binds
abstract fun bindErrorFormatter(formatter: DefaultErrorFormatter): ErrorFormatter abstract fun bindErrorFormatter(formatter: DefaultErrorFormatter): ErrorFormatter

View file

@ -65,7 +65,9 @@ import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
import im.vector.app.features.MainActivity import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs import im.vector.app.features.MainActivityArgs
import im.vector.app.features.analytics.VectorAnalytics import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.analytics.screen.ScreenEvent
import im.vector.app.features.configuration.VectorConfiguration import im.vector.app.features.configuration.VectorConfiguration
import im.vector.app.features.consent.ConsentNotGivenHelper import im.vector.app.features.consent.ConsentNotGivenHelper
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
@ -90,6 +92,15 @@ import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), MavericksView { abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), MavericksView {
/* ==========================================================================================
* Analytics
* ========================================================================================== */
protected var analyticsScreenName: Screen.ScreenName? = null
private var screenEvent: ScreenEvent? = null
protected lateinit var analyticsTracker: AnalyticsTracker
/* ========================================================================================== /* ==========================================================================================
* View * View
* ========================================================================================== */ * ========================================================================================== */
@ -133,7 +144,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
private lateinit var sessionListener: SessionListener private lateinit var sessionListener: SessionListener
protected lateinit var bugReporter: BugReporter protected lateinit var bugReporter: BugReporter
private lateinit var pinLocker: PinLocker private lateinit var pinLocker: PinLocker
protected lateinit var analytics: VectorAnalytics
@Inject @Inject
lateinit var rageShake: RageShake lateinit var rageShake: RageShake
@ -189,7 +199,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
configurationViewModel = viewModelProvider.get(ConfigurationViewModel::class.java) configurationViewModel = viewModelProvider.get(ConfigurationViewModel::class.java)
bugReporter = singletonEntryPoint.bugReporter() bugReporter = singletonEntryPoint.bugReporter()
pinLocker = singletonEntryPoint.pinLocker() pinLocker = singletonEntryPoint.pinLocker()
analytics = singletonEntryPoint.analytics() analyticsTracker = singletonEntryPoint.analyticsTracker()
navigator = singletonEntryPoint.navigator() navigator = singletonEntryPoint.navigator()
activeSessionHolder = singletonEntryPoint.activeSessionHolder() activeSessionHolder = singletonEntryPoint.activeSessionHolder()
vectorPreferences = singletonEntryPoint.vectorPreferences() vectorPreferences = singletonEntryPoint.vectorPreferences()
@ -324,7 +334,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
Timber.i("onResume Activity ${javaClass.simpleName}") Timber.i("onResume Activity ${javaClass.simpleName}")
screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
configurationViewModel.onActivityResumed() configurationViewModel.onActivityResumed()
if (this !is BugReportActivity && vectorPreferences.useRageshake()) { if (this !is BugReportActivity && vectorPreferences.useRageshake()) {
@ -363,6 +373,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
screenEvent?.send(analyticsTracker, analyticsScreenName)
Timber.i("onPause Activity ${javaClass.simpleName}") Timber.i("onPause Activity ${javaClass.simpleName}")
rageShake.stop() rageShake.stop()

View file

@ -37,7 +37,9 @@ import im.vector.app.core.di.ActivityEntryPoint
import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.analytics.VectorAnalytics import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.analytics.screen.ScreenEvent
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks import reactivecircus.flowbinding.android.view.clicks
@ -47,6 +49,14 @@ import timber.log.Timber
* Add Mavericks capabilities, handle DI and bindings. * Add Mavericks capabilities, handle DI and bindings.
*/ */
abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomSheetDialogFragment(), MavericksView { abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomSheetDialogFragment(), MavericksView {
/* ==========================================================================================
* Analytics
* ========================================================================================== */
protected var analyticsScreenName: Screen.ScreenName? = null
private var screenEvent: ScreenEvent? = null
protected lateinit var analyticsTracker: AnalyticsTracker
/* ========================================================================================== /* ==========================================================================================
* View * View
@ -84,8 +94,6 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
open val showExpanded = false open val showExpanded = false
protected lateinit var analytics: VectorAnalytics
interface ResultListener { interface ResultListener {
fun onBottomSheetResult(resultCode: Int, data: Any?) fun onBottomSheetResult(resultCode: Int, data: Any?)
@ -124,13 +132,19 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
val activityEntryPoint = EntryPointAccessors.fromActivity(vectorBaseActivity, ActivityEntryPoint::class.java) val activityEntryPoint = EntryPointAccessors.fromActivity(vectorBaseActivity, ActivityEntryPoint::class.java)
viewModelFactory = activityEntryPoint.viewModelFactory() viewModelFactory = activityEntryPoint.viewModelFactory()
val singletonEntryPoint = context.singletonEntryPoint() val singletonEntryPoint = context.singletonEntryPoint()
analytics = singletonEntryPoint.analytics() analyticsTracker = singletonEntryPoint.analyticsTracker()
super.onAttach(context) super.onAttach(context)
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
Timber.i("onResume BottomSheet ${javaClass.simpleName}") Timber.i("onResume BottomSheet ${javaClass.simpleName}")
screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
}
override fun onPause() {
super.onPause()
screenEvent?.send(analyticsTracker)
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

View file

@ -42,7 +42,9 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.features.analytics.VectorAnalytics import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.analytics.screen.ScreenEvent
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -51,6 +53,18 @@ import reactivecircus.flowbinding.android.view.clicks
import timber.log.Timber import timber.log.Timber
abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView { abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView {
/* ==========================================================================================
* Analytics
* ========================================================================================== */
protected var analyticsScreenName: Screen.ScreenName? = null
private var screenEvent: ScreenEvent? = null
protected lateinit var analyticsTracker: AnalyticsTracker
/* ==========================================================================================
* Activity
* ========================================================================================== */
protected val vectorBaseActivity: VectorBaseActivity<*> by lazy { protected val vectorBaseActivity: VectorBaseActivity<*> by lazy {
activity as VectorBaseActivity<*> activity as VectorBaseActivity<*>
@ -61,7 +75,6 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
* ========================================================================================== */ * ========================================================================================== */
protected lateinit var navigator: Navigator protected lateinit var navigator: Navigator
protected lateinit var analytics: VectorAnalytics
protected lateinit var errorFormatter: ErrorFormatter protected lateinit var errorFormatter: ErrorFormatter
protected lateinit var unrecognizedCertificateDialog: UnrecognizedCertificateDialog protected lateinit var unrecognizedCertificateDialog: UnrecognizedCertificateDialog
@ -98,7 +111,7 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
val activityEntryPoint = EntryPointAccessors.fromActivity(vectorBaseActivity, ActivityEntryPoint::class.java) val activityEntryPoint = EntryPointAccessors.fromActivity(vectorBaseActivity, ActivityEntryPoint::class.java)
navigator = singletonEntryPoint.navigator() navigator = singletonEntryPoint.navigator()
errorFormatter = singletonEntryPoint.errorFormatter() errorFormatter = singletonEntryPoint.errorFormatter()
analytics = singletonEntryPoint.analytics() analyticsTracker = singletonEntryPoint.analyticsTracker()
unrecognizedCertificateDialog = singletonEntryPoint.unrecognizedCertificateDialog() unrecognizedCertificateDialog = singletonEntryPoint.unrecognizedCertificateDialog()
viewModelFactory = activityEntryPoint.viewModelFactory() viewModelFactory = activityEntryPoint.viewModelFactory()
childFragmentManager.fragmentFactory = activityEntryPoint.fragmentFactory() childFragmentManager.fragmentFactory = activityEntryPoint.fragmentFactory()
@ -125,12 +138,14 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
Timber.i("onResume Fragment ${javaClass.simpleName}") Timber.i("onResume Fragment ${javaClass.simpleName}")
screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
} }
@CallSuper @CallSuper
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
Timber.i("onPause Fragment ${javaClass.simpleName}") Timber.i("onPause Fragment ${javaClass.simpleName}")
screenEvent?.send(analyticsTracker)
} }
@CallSuper @CallSuper

View file

@ -0,0 +1,32 @@
/*
* 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.analytics
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
interface AnalyticsTracker {
/**
* Capture an Event
*/
fun capture(event: VectorAnalyticsEvent)
/**
* Track a displayed screen
*/
fun screen(screen: VectorAnalyticsScreen)
}

View file

@ -49,7 +49,7 @@ private const val CHECK_INTERVAL = 2_000L
*/ */
@Singleton @Singleton
class DecryptionFailureTracker @Inject constructor( class DecryptionFailureTracker @Inject constructor(
private val vectorAnalytics: VectorAnalytics, private val analyticsTracker: AnalyticsTracker,
private val clock: Clock private val clock: Clock
) { ) {
@ -136,7 +136,7 @@ class DecryptionFailureTracker @Inject constructor(
// for now we ignore events already reported even if displayed again? // for now we ignore events already reported even if displayed again?
.filter { alreadyReported.contains(it).not() } .filter { alreadyReported.contains(it).not() }
.forEach { failedEventId -> .forEach { failedEventId ->
vectorAnalytics.capture(Error(failedEventId, Error.Domain.E2EE, aggregation.key)) analyticsTracker.capture(Error(failedEventId, Error.Domain.E2EE, aggregation.key))
alreadyReported.add(failedEventId) alreadyReported.add(failedEventId)
} }
} }

View file

@ -16,11 +16,9 @@
package im.vector.app.features.analytics package im.vector.app.features.analytics
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface VectorAnalytics { interface VectorAnalytics : AnalyticsTracker {
/** /**
* Return a Flow of Boolean, true if the user has given their consent * Return a Flow of Boolean, true if the user has given their consent
*/ */
@ -60,14 +58,4 @@ interface VectorAnalytics {
* To be called when application is started * To be called when application is started
*/ */
fun init() fun init()
/**
* Capture an Event
*/
fun capture(event: VectorAnalyticsEvent)
/**
* Track a displayed screen
*/
fun screen(screen: VectorAnalyticsScreen)
} }

View file

@ -0,0 +1,47 @@
/*
* 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.analytics.extensions
import im.vector.app.features.analytics.plan.JoinedRoom
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
fun Int?.toAnalyticsRoomSize(): JoinedRoom.RoomSize {
return when (this) {
null,
2 -> JoinedRoom.RoomSize.Two
in 3..10 -> JoinedRoom.RoomSize.ThreeToTen
in 11..100 -> JoinedRoom.RoomSize.ElevenToOneHundred
in 101..1000 -> JoinedRoom.RoomSize.OneHundredAndOneToAThousand
else -> JoinedRoom.RoomSize.MoreThanAThousand
}
}
fun RoomSummary?.toAnalyticsJoinedRoom(): JoinedRoom {
return JoinedRoom(
isDM = this?.isDirect.orFalse(),
roomSize = this?.joinedMembersCount?.toAnalyticsRoomSize() ?: JoinedRoom.RoomSize.Two
)
}
fun PublicRoom.toAnalyticsJoinedRoom(): JoinedRoom {
return JoinedRoom(
isDM = false,
roomSize = numJoinedMembers.toAnalyticsRoomSize()
)
}

View file

@ -25,22 +25,22 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
* Triggered when a call has ended. * Triggered when a call has ended.
*/ */
data class CallEnded( data class CallEnded(
/** /**
* The duration of the call in milliseconds. * The duration of the call in milliseconds.
*/ */
val durationMs: Int, val durationMs: Int,
/** /**
* Whether its a video call or not. * Whether its a video call or not.
*/ */
val isVideo: Boolean, val isVideo: Boolean,
/** /**
* Number of participants in the call. * Number of participants in the call.
*/ */
val numParticipants: Int, val numParticipants: Int,
/** /**
* Whether this user placed it. * Whether this user placed it.
*/ */
val placed: Boolean, val placed: Boolean,
) : VectorAnalyticsEvent { ) : VectorAnalyticsEvent {
override fun getName() = "CallEnded" override fun getName() = "CallEnded"

View file

@ -25,18 +25,18 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
* Triggered when an error occurred in a call. * Triggered when an error occurred in a call.
*/ */
data class CallError( data class CallError(
/** /**
* Whether its a video call or not. * Whether its a video call or not.
*/ */
val isVideo: Boolean, val isVideo: Boolean,
/** /**
* Number of participants in the call. * Number of participants in the call.
*/ */
val numParticipants: Int, val numParticipants: Int,
/** /**
* Whether this user placed it. * Whether this user placed it.
*/ */
val placed: Boolean, val placed: Boolean,
) : VectorAnalyticsEvent { ) : VectorAnalyticsEvent {
override fun getName() = "CallError" override fun getName() = "CallError"

View file

@ -25,18 +25,18 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
* Triggered when a call is started. * Triggered when a call is started.
*/ */
data class CallStarted( data class CallStarted(
/** /**
* Whether its a video call or not. * Whether its a video call or not.
*/ */
val isVideo: Boolean, val isVideo: Boolean,
/** /**
* Number of participants in the call. * Number of participants in the call.
*/ */
val numParticipants: Int, val numParticipants: Int,
/** /**
* Whether this user placed it. * Whether this user placed it.
*/ */
val placed: Boolean, val placed: Boolean,
) : VectorAnalyticsEvent { ) : VectorAnalyticsEvent {
override fun getName() = "CallStarted" override fun getName() = "CallStarted"

View file

@ -25,14 +25,14 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
* Triggered when the user clicks/taps on a UI element. * Triggered when the user clicks/taps on a UI element.
*/ */
data class Click( data class Click(
/** /**
* The index of the element, if its in a list of elements. * The index of the element, if its in a list of elements.
*/ */
val index: Int? = null, val index: Int? = null,
/** /**
* The unique name of this element. * The unique name of this element.
*/ */
val name: Name, val name: Name,
) : VectorAnalyticsEvent { ) : VectorAnalyticsEvent {
enum class Name { enum class Name {

View file

@ -25,10 +25,10 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
* Triggered when the user creates a room. * Triggered when the user creates a room.
*/ */
data class CreatedRoom( data class CreatedRoom(
/** /**
* Whether the room is a DM. * Whether the room is a DM.
*/ */
val isDM: Boolean, val isDM: Boolean,
) : VectorAnalyticsEvent { ) : VectorAnalyticsEvent {
override fun getName() = "CreatedRoom" override fun getName() = "CreatedRoom"

View file

@ -25,12 +25,12 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
* Triggered when an error occurred * Triggered when an error occurred
*/ */
data class Error( data class Error(
/** /**
* Context - client defined, can be used for debugging * Context - client defined, can be used for debugging
*/ */
val context: String? = null, val context: String? = null,
val domain: Domain, val domain: Domain,
val name: Name, val name: Name,
) : VectorAnalyticsEvent { ) : VectorAnalyticsEvent {
enum class Domain { enum class Domain {

View file

@ -0,0 +1,63 @@
/*
* 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.analytics.plan
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* The user properties to apply when identifying
*/
data class Identity(
/**
* The selected messaging use case during the onboarding flow.
*/
val ftueUseCaseSelection: FtueUseCaseSelection? = null,
) : VectorAnalyticsEvent {
enum class FtueUseCaseSelection {
/**
* The third option, Communities.
*/
CommunityMessaging,
/**
* The first option, Friends and family.
*/
PersonalMessaging,
/**
* The footer option to skip the question.
*/
Skip,
/**
* The second option, Teams.
*/
WorkMessaging,
}
override fun getName() = "Identity"
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
ftueUseCaseSelection?.let { put("ftueUseCaseSelection", it.name) }
}.takeIf { it.isNotEmpty() }
}
}

View file

@ -25,14 +25,14 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
* Triggered when the user joins a room. * Triggered when the user joins a room.
*/ */
data class JoinedRoom( data class JoinedRoom(
/** /**
* Whether the room is a DM. * Whether the room is a DM.
*/ */
val isDM: Boolean, val isDM: Boolean,
/** /**
* The size of the room. * The size of the room.
*/ */
val roomSize: RoomSize, val roomSize: RoomSize,
) : VectorAnalyticsEvent { ) : VectorAnalyticsEvent {
enum class RoomSize { enum class RoomSize {

View file

@ -25,22 +25,23 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
* Triggered after timing an operation in the app. * Triggered after timing an operation in the app.
*/ */
data class PerformanceTimer( data class PerformanceTimer(
/** /**
* Client defined, can be used for debugging. * Client defined, can be used for debugging.
*/ */
val context: String? = null, val context: String? = null,
/** /**
* Client defined, an optional value to indicate how many items were handled during the operation. * Client defined, an optional value to indicate how many items were
*/ * handled during the operation.
val itemCount: Int? = null, */
/** val itemCount: Int? = null,
* The timer that is being reported. /**
*/ * The timer that is being reported.
val name: Name, */
/** val name: Name,
* The time reported by the timer in milliseconds. /**
*/ * The time reported by the timer in milliseconds.
val timeMs: Int, */
val timeMs: Int,
) : VectorAnalyticsEvent { ) : VectorAnalyticsEvent {
enum class Name { enum class Name {
@ -55,7 +56,8 @@ data class PerformanceTimer(
InitialSyncRequest, InitialSyncRequest,
/** /**
* The time taken to display an event in the timeline that was opened from a notification. * The time taken to display an event in the timeline that was opened
* from a notification.
*/ */
NotificationsOpenEvent, NotificationsOpenEvent,
@ -65,7 +67,8 @@ data class PerformanceTimer(
StartupIncrementalSync, StartupIncrementalSync,
/** /**
* The duration of an initial /sync request during startup (if the store has been wiped). * The duration of an initial /sync request during startup (if the store
* has been wiped).
*/ */
StartupInitialSync, StartupInitialSync,
@ -80,7 +83,8 @@ data class PerformanceTimer(
StartupStorePreload, StartupStorePreload,
/** /**
* The time to load all data from the store (including StartupStorePreload time). * The time to load all data from the store (including
* StartupStorePreload time).
*/ */
StartupStoreReady, StartupStoreReady,
} }

View file

@ -25,28 +25,221 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
* Triggered when the user changed screen * Triggered when the user changed screen
*/ */
data class Screen( data class Screen(
/** /**
* How long the screen was displayed for in milliseconds. * How long the screen was displayed for in milliseconds.
*/ */
val durationMs: Int? = null, val durationMs: Int? = null,
val screenName: ScreenName, val screenName: ScreenName,
) : VectorAnalyticsScreen { ) : VectorAnalyticsScreen {
enum class ScreenName { enum class ScreenName {
/**
* The screen shown to create a new (non-direct) room.
*/
CreateRoom,
/**
* The confirmation screen shown before deactivating an account.
*/
DeactivateAccount,
/**
* The form for the forgot password use case
*/
ForgotPassword,
/**
* Legacy: The screen that shows information about a specific group.
*/
Group, Group,
/**
* The Home tab on iOS | possibly the same on Android? | The Home space
* on Web?
*/
Home, Home,
/**
* The screen that displays the login flow (when the user already has an
* account).
*/
Login,
/**
* The screen that displays the user's breadcrumbs.
*/
MobileBreadcrumbs,
/**
* The tab on mobile that displays the dialpad.
*/
MobileDialpad,
/**
* The Favourites tab on mobile that lists your favourite people/rooms.
*/
MobileFavourites,
/**
* The screen shown to share a link to download the app.
*/
MobileInviteFriends,
/**
* The People tab on mobile that lists all the DM rooms you have joined.
*/
MobilePeople,
/**
* The Rooms tab on mobile that lists all the (non-direct) rooms you've
* joined.
*/
MobileRooms,
/**
* The Files tab shown in the global search screen on Mobile.
*/
MobileSearchFiles,
/**
* The Messages tab shown in the global search screen on Mobile.
*/
MobileSearchMessages,
/**
* The People tab shown in the global search screen on Mobile.
*/
MobileSearchPeople,
/**
* The Rooms tab shown in the global search screen on Mobile.
*/
MobileSearchRooms,
/**
* The sidebar shown on mobile with spaces, settings etc.
*/
MobileSidebar,
/**
* The screen shown to select which room directory you'd like to use.
*/
MobileSwitchDirectory,
/**
* Legacy: The screen that shows all groups/communities you have joined.
*/
MyGroups, MyGroups,
/**
* The screen that displays the registration flow (when the user wants
* to create an account)
*/
Register,
/**
* The screen that displays the messages and events received in a room.
*/
Room, Room,
/**
* The screen shown when tapping the name of a room from the Room
* screen.
*/
RoomDetails,
/**
* The screen that lists public rooms for you to discover.
*/
RoomDirectory, RoomDirectory,
/**
* The screen that lists all the user's rooms and let them filter the
* rooms.
*/
RoomFilter,
/**
* The screen that displays the list of members that are part of a room.
*/
RoomMembers,
/**
* The notifications settings screen shown from the Room Details screen.
*/
RoomNotifications,
/**
* The screen that allows you to search for messages/files in a specific
* room.
*/
RoomSearch,
/**
* The settings screen shown from the Room Details screen.
*/
RoomSettings,
/**
* The screen that allows you to see all of the files sent in a specific
* room.
*/
RoomUploads,
/**
* The global settings screen shown in the app.
*/
Settings,
/**
* The settings screen to change the default notification options.
*/
SettingsDefaultNotifications,
/**
* The settings screen to manage notification mentions and keywords.
*/
SettingsMentionsAndKeywords,
/**
* The global security settings screen.
*/
SettingsSecurity,
/**
* The screen shown to create a new direct room.
*/
StartChat,
/**
* A screen that shows information about a room member.
*/
User, User,
/**
* ?
*/
WebCompleteSecurity, WebCompleteSecurity,
/**
* ?
*/
WebE2ESetup, WebE2ESetup,
WebForgotPassword,
/**
* ?
*/
WebLoading, WebLoading,
WebLogin,
WebRegister, /**
* ?
*/
WebSoftLogout, WebSoftLogout,
WebWelcome,
/**
* The splash screen.
*/
Welcome,
} }
override fun getName() = screenName.name override fun getName() = screenName.name

View file

@ -0,0 +1,66 @@
/*
* 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.analytics.plan
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* Triggered when the user becomes unauthenticated without actually clicking
* sign out(E.g. Due to expiry of an access token without a way to refresh).
*/
data class UnauthenticatedError(
/**
* The error code as defined in matrix spec. The source of this error is
* from the homeserver.
*/
val errorCode: ErrorCode,
/**
* The reason for the error. The source of this error is from the
* homeserver, the reason can vary and is subject to change so there is
* no enum of possible values.
*/
val errorReason: String,
/**
* Whether the auth mechanism is refresh-token-based.
*/
val refreshTokenAuth: Boolean,
/**
* Whether a soft logout or hard logout was triggered.
*/
val softLogout: Boolean,
) : VectorAnalyticsEvent {
enum class ErrorCode {
M_FORBIDDEN,
M_UNKNOWN,
M_UNKNOWN_TOKEN,
}
override fun getName() = "UnauthenticatedError"
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
put("errorCode", errorCode.name)
put("errorReason", errorReason)
put("refreshTokenAuth", refreshTokenAuth)
put("softLogout", softLogout)
}.takeIf { it.isNotEmpty() }
}
}

View file

@ -0,0 +1,50 @@
/*
* 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.analytics.screen
import android.os.SystemClock
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.Screen
import timber.log.Timber
/**
* Track a screen display. Unique usage.
*/
class ScreenEvent(val screenName: Screen.ScreenName) {
private val startTime = SystemClock.elapsedRealtime()
// Protection to avoid multiple sending
private var isSent = false
/**
* @param screenNameOverride can be used to override the screen name passed in constructor parameter
*/
fun send(analyticsTracker: AnalyticsTracker,
screenNameOverride: Screen.ScreenName? = null) {
if (isSent) {
Timber.w("Event $screenName Already sent!")
return
}
isSent = true
analyticsTracker.screen(
Screen(
screenName = screenNameOverride ?: screenName,
durationMs = (SystemClock.elapsedRealtime() - startTime).toInt()
)
)
}
}

View file

@ -17,6 +17,7 @@
package im.vector.app.features.call.dialpad package im.vector.app.features.call.dialpad
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.os.Bundle import android.os.Bundle
import android.telephony.PhoneNumberFormattingTextWatcher import android.telephony.PhoneNumberFormattingTextWatcher
@ -37,6 +38,10 @@ import androidx.fragment.app.Fragment
import com.android.dialer.dialpadview.DialpadView import com.android.dialer.dialpadview.DialpadView
import com.android.dialer.dialpadview.DigitsEditText import com.android.dialer.dialpadview.DigitsEditText
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.analytics.screen.ScreenEvent
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
class DialPadFragment : Fragment(), TextWatcher { class DialPadFragment : Fragment(), TextWatcher {
@ -53,6 +58,25 @@ class DialPadFragment : Fragment(), TextWatcher {
private var enableDelete = true private var enableDelete = true
private var enableFabOk = true private var enableFabOk = true
private lateinit var analyticsTracker: AnalyticsTracker
override fun onAttach(context: Context) {
super.onAttach(context)
val singletonEntryPoint = context.singletonEntryPoint()
analyticsTracker = singletonEntryPoint.analyticsTracker()
}
private var screenEvent: ScreenEvent? = null
override fun onResume() {
super.onResume()
screenEvent = ScreenEvent(Screen.ScreenName.MobileDialpad)
}
override fun onPause() {
super.onPause()
screenEvent?.send(analyticsTracker)
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,

View file

@ -270,6 +270,10 @@ class WebRtcCall(
} }
} }
fun durationMillis(): Int {
return timer.elapsedTime().toInt()
}
fun formattedDuration(): String { fun formattedDuration(): String {
return formatDuration( return formatDuration(
Duration.ofMillis(timer.elapsedTime()) Duration.ofMillis(timer.elapsedTime())

View file

@ -22,6 +22,9 @@ import androidx.lifecycle.LifecycleOwner
import im.vector.app.ActiveSessionDataSource import im.vector.app.ActiveSessionDataSource
import im.vector.app.BuildConfig import im.vector.app.BuildConfig
import im.vector.app.core.services.CallService import im.vector.app.core.services.CallService
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.CallEnded
import im.vector.app.features.analytics.plan.CallStarted
import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.audio.CallAudioManager import im.vector.app.features.call.audio.CallAudioManager
import im.vector.app.features.call.lookup.CallProtocolsChecker import im.vector.app.features.call.lookup.CallProtocolsChecker
@ -68,7 +71,8 @@ private val loggerTag = LoggerTag("WebRtcCallManager", LoggerTag.VOIP)
@Singleton @Singleton
class WebRtcCallManager @Inject constructor( class WebRtcCallManager @Inject constructor(
private val context: Context, private val context: Context,
private val activeSessionDataSource: ActiveSessionDataSource private val activeSessionDataSource: ActiveSessionDataSource,
private val analyticsTracker: AnalyticsTracker
) : CallListener, ) : CallListener,
DefaultLifecycleObserver { DefaultLifecycleObserver {
@ -237,6 +241,7 @@ class WebRtcCallManager @Inject constructor(
val currentCall = getCurrentCall().takeIf { it != call } val currentCall = getCurrentCall().takeIf { it != call }
currentCall?.updateRemoteOnHold(onHold = true) currentCall?.updateRemoteOnHold(onHold = true)
audioManager.setMode(if (call.mxCall.isVideoCall) CallAudioManager.Mode.VIDEO_CALL else CallAudioManager.Mode.AUDIO_CALL) audioManager.setMode(if (call.mxCall.isVideoCall) CallAudioManager.Mode.VIDEO_CALL else CallAudioManager.Mode.AUDIO_CALL)
call.trackCallStarted()
this.currentCall.setAndNotify(call) this.currentCall.setAndNotify(call)
} }
@ -245,6 +250,7 @@ class WebRtcCallManager @Inject constructor(
val webRtcCall = callsByCallId.remove(callId) ?: return Unit.also { val webRtcCall = callsByCallId.remove(callId) ?: return Unit.also {
Timber.tag(loggerTag.value).v("On call ended for unknown call $callId") Timber.tag(loggerTag.value).v("On call ended for unknown call $callId")
} }
webRtcCall.trackCallEnded()
CallService.onCallTerminated(context, callId, endCallReason, rejected) CallService.onCallTerminated(context, callId, endCallReason, rejected)
callsByRoomId[webRtcCall.signalingRoomId]?.remove(webRtcCall) callsByRoomId[webRtcCall.signalingRoomId]?.remove(webRtcCall)
callsByRoomId[webRtcCall.nativeRoomId]?.remove(webRtcCall) callsByRoomId[webRtcCall.nativeRoomId]?.remove(webRtcCall)
@ -443,4 +449,28 @@ class WebRtcCallManager @Inject constructor(
} }
call.onCallAssertedIdentityReceived(callAssertedIdentityContent) call.onCallAssertedIdentityReceived(callAssertedIdentityContent)
} }
/**
* Analytics
*/
private fun WebRtcCall.trackCallStarted() {
analyticsTracker.capture(
CallStarted(
isVideo = mxCall.isVideoCall,
numParticipants = 2,
placed = mxCall.isOutgoing
)
)
}
private fun WebRtcCall.trackCallEnded() {
analyticsTracker.capture(
CallEnded(
durationMs = durationMillis(),
isVideo = mxCall.isVideoCall,
numParticipants = 2,
placed = mxCall.isOutgoing
)
)
}
} }

View file

@ -42,6 +42,7 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.onPermissionDeniedSnackbar import im.vector.app.core.utils.onPermissionDeniedSnackbar
import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.contactsbook.ContactsBookFragment import im.vector.app.features.contactsbook.ContactsBookFragment
import im.vector.app.features.userdirectory.UserListFragment import im.vector.app.features.userdirectory.UserListFragment
import im.vector.app.features.userdirectory.UserListFragmentArgs import im.vector.app.features.userdirectory.UserListFragmentArgs
@ -63,6 +64,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.StartChat
views.toolbar.visibility = View.GONE views.toolbar.visibility = View.GONE
sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java) sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)

View file

@ -24,6 +24,7 @@ import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
@ -49,6 +50,8 @@ import im.vector.app.databinding.ActivityHomeBinding
import im.vector.app.features.MainActivity import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs import im.vector.app.features.MainActivityArgs
import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewModel import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewModel
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.analytics.screen.ScreenEvent
import im.vector.app.features.disclaimer.showDisclaimerDialog import im.vector.app.features.disclaimer.showDisclaimerDialog
import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.matrixto.MatrixToBottomSheet
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
@ -104,6 +107,7 @@ class HomeActivity :
private lateinit var sharedActionViewModel: HomeSharedActionViewModel private lateinit var sharedActionViewModel: HomeSharedActionViewModel
private val homeActivityViewModel: HomeActivityViewModel by viewModel() private val homeActivityViewModel: HomeActivityViewModel by viewModel()
@Suppress("UNUSED") @Suppress("UNUSED")
private val analyticsAccountDataViewModel: AnalyticsAccountDataViewModel by viewModel() private val analyticsAccountDataViewModel: AnalyticsAccountDataViewModel by viewModel()
@Suppress("UNUSED") @Suppress("UNUSED")
@ -164,6 +168,16 @@ class HomeActivity :
} }
private val drawerListener = object : DrawerLayout.SimpleDrawerListener() { private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
private var drawerScreenEvent: ScreenEvent? = null
override fun onDrawerOpened(drawerView: View) {
drawerScreenEvent = ScreenEvent(Screen.ScreenName.MobileSidebar)
}
override fun onDrawerClosed(drawerView: View) {
drawerScreenEvent?.send(analyticsTracker)
drawerScreenEvent = null
}
override fun onDrawerStateChanged(newState: Int) { override fun onDrawerStateChanged(newState: Int) {
hideKeyboard() hideKeyboard()
} }
@ -175,6 +189,7 @@ class HomeActivity :
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.Home
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false) supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
FcmHelper.ensureFcmTokenIsRetrieved(this, pushManager, vectorPreferences.areNotificationEnabledForDevice()) FcmHelper.ensureFcmTokenIsRetrieved(this, pushManager, vectorPreferences.areNotificationEnabledForDevice())
sharedActionViewModel = viewModelProvider.get(HomeSharedActionViewModel::class.java) sharedActionViewModel = viewModelProvider.get(HomeSharedActionViewModel::class.java)

View file

@ -30,6 +30,7 @@ import im.vector.app.core.extensions.replaceChildFragment
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.databinding.FragmentHomeDrawerBinding import im.vector.app.databinding.FragmentHomeDrawerBinding
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.spaces.SpaceListFragment import im.vector.app.features.spaces.SpaceListFragment
@ -97,6 +98,7 @@ class HomeDrawerFragment @Inject constructor(
views.homeDrawerInviteFriendButton.debouncedClicks { views.homeDrawerInviteFriendButton.debouncedClicks {
session.permalinkService().createPermalink(sharedActionViewModel.session.myUserId)?.let { permalink -> session.permalinkService().createPermalink(sharedActionViewModel.session.myUserId)?.let { permalink ->
analyticsTracker.screen(Screen(screenName = Screen.ScreenName.MobileInviteFriends))
val text = getString(R.string.invite_friends_text, permalink) val text = getString(R.string.invite_friends_text, permalink)
startSharePlainTextIntent( startSharePlainTextIntent(

View file

@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
@ -36,6 +37,8 @@ import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.ToolbarConfigurable
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityRoomDetailBinding import im.vector.app.databinding.ActivityRoomDetailBinding
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.analytics.screen.ScreenEvent
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.matrixto.MatrixToBottomSheet
@ -161,6 +164,16 @@ class RoomDetailActivity :
} }
private val drawerListener = object : DrawerLayout.SimpleDrawerListener() { private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
private var drawerScreenEvent: ScreenEvent? = null
override fun onDrawerOpened(drawerView: View) {
drawerScreenEvent = ScreenEvent(Screen.ScreenName.MobileBreadcrumbs)
}
override fun onDrawerClosed(drawerView: View) {
drawerScreenEvent?.send(analyticsTracker)
drawerScreenEvent = null
}
override fun onDrawerStateChanged(newState: Int) { override fun onDrawerStateChanged(newState: Int) {
hideKeyboard() hideKeyboard()

View file

@ -116,6 +116,8 @@ import im.vector.app.core.utils.startInstallFromSourceIntent
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
import im.vector.app.databinding.DialogReportContentBinding import im.vector.app.databinding.DialogReportContentBinding
import im.vector.app.databinding.FragmentRoomDetailBinding import im.vector.app.databinding.FragmentRoomDetailBinding
import im.vector.app.features.analytics.plan.Click
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.attachments.AttachmentTypeSelectorView import im.vector.app.features.attachments.AttachmentTypeSelectorView
import im.vector.app.features.attachments.AttachmentsHelper import im.vector.app.features.attachments.AttachmentsHelper
import im.vector.app.features.attachments.ContactAttachment import im.vector.app.features.attachments.ContactAttachment
@ -336,6 +338,7 @@ class RoomDetailFragment @Inject constructor(
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.Room
setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY) { _, bundle -> setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY) { _, bundle ->
bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId -> bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId ->
roomDetailViewModel.handle(RoomDetailAction.RoomUpgradeSuccess(replacementRoomId)) roomDetailViewModel.handle(RoomDetailAction.RoomUpgradeSuccess(replacementRoomId))
@ -1396,6 +1399,7 @@ class RoomDetailFragment @Inject constructor(
return return
} }
if (text.isNotBlank()) { if (text.isNotBlank()) {
analyticsTracker.capture(Click(name = Click.Name.SendMessageButton))
// We collapse ASAP, if not there will be a slight annoying delay // We collapse ASAP, if not there will be a slight annoying delay
views.composerLayout.collapse(true) views.composerLayout.collapse(true)
lockSendButton = true lockSendButton = true

View file

@ -38,7 +38,9 @@ import im.vector.app.core.mvrx.runCatchingToAsync
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.BehaviorDataSource import im.vector.app.core.utils.BehaviorDataSource
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.DecryptionFailureTracker import im.vector.app.features.analytics.DecryptionFailureTracker
import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom
import im.vector.app.features.call.conference.ConferenceEvent import im.vector.app.features.call.conference.ConferenceEvent
import im.vector.app.features.call.conference.JitsiActiveConferenceHolder import im.vector.app.features.call.conference.JitsiActiveConferenceHolder
import im.vector.app.features.call.conference.JitsiService import im.vector.app.features.call.conference.JitsiService
@ -114,6 +116,7 @@ class RoomDetailViewModel @AssistedInject constructor(
private val chatEffectManager: ChatEffectManager, private val chatEffectManager: ChatEffectManager,
private val directRoomHelper: DirectRoomHelper, private val directRoomHelper: DirectRoomHelper,
private val jitsiService: JitsiService, private val jitsiService: JitsiService,
private val analyticsTracker: AnalyticsTracker,
private val activeConferenceHolder: JitsiActiveConferenceHolder, private val activeConferenceHolder: JitsiActiveConferenceHolder,
private val decryptionFailureTracker: DecryptionFailureTracker, private val decryptionFailureTracker: DecryptionFailureTracker,
timelineFactory: TimelineFactory, timelineFactory: TimelineFactory,
@ -730,7 +733,10 @@ class RoomDetailViewModel @AssistedInject constructor(
private fun handleAcceptInvite() { private fun handleAcceptInvite() {
viewModelScope.launch { viewModelScope.launch {
tryOrNull { room.join() } tryOrNull {
room.join()
analyticsTracker.capture(room.roomSummary().toAnalyticsJoinedRoom())
}
} }
} }

View file

@ -26,6 +26,8 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom
import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.attachments.toContentAttachmentData
import im.vector.app.features.command.CommandParser import im.vector.app.features.command.CommandParser
import im.vector.app.features.command.ParsedCommand import im.vector.app.features.command.ParsedCommand
@ -69,6 +71,7 @@ class MessageComposerViewModel @AssistedInject constructor(
private val commandParser: CommandParser, private val commandParser: CommandParser,
private val rainbowGenerator: RainbowGenerator, private val rainbowGenerator: RainbowGenerator,
private val voiceMessageHelper: VoiceMessageHelper, private val voiceMessageHelper: VoiceMessageHelper,
private val analyticsTracker: AnalyticsTracker,
private val voicePlayerHelper: VoicePlayerHelper private val voicePlayerHelper: VoicePlayerHelper
) : VectorViewModel<MessageComposerViewState, MessageComposerAction, MessageComposerViewEvents>(initialState) { ) : VectorViewModel<MessageComposerViewState, MessageComposerAction, MessageComposerViewEvents>(initialState) {
@ -521,6 +524,7 @@ class MessageComposerViewModel @AssistedInject constructor(
return@launch return@launch
} }
session.getRoomSummary(command.roomAlias) session.getRoomSummary(command.roomAlias)
?.also { analyticsTracker.capture(it.toAnalyticsJoinedRoom()) }
?.roomId ?.roomId
?.let { ?.let {
_viewEvents.post(MessageComposerViewEvents.JoinRoomCommandSuccess(it)) _viewEvents.post(MessageComposerViewEvents.JoinRoomCommandSuccess(it))

View file

@ -24,6 +24,7 @@ import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityFilteredRoomsBinding import im.vector.app.databinding.ActivityFilteredRoomsBinding
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.list.RoomListFragment import im.vector.app.features.home.room.list.RoomListFragment
import im.vector.app.features.home.room.list.RoomListParams import im.vector.app.features.home.room.list.RoomListParams
@ -42,6 +43,7 @@ class FilteredRoomsActivity : VectorBaseActivity<ActivityFilteredRoomsBinding>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.RoomFilter
configureToolbar(views.filteredRoomsToolbar) configureToolbar(views.filteredRoomsToolbar)
if (isFirstCreation()) { if (isFirstCreation()) {
val params = RoomListParams(RoomListDisplayMode.FILTERED) val params = RoomListParams(RoomListDisplayMode.FILTERED)

View file

@ -42,6 +42,7 @@ import im.vector.app.core.platform.StateView
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.databinding.FragmentRoomListBinding import im.vector.app.databinding.FragmentRoomListBinding
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
@ -100,6 +101,15 @@ class RoomListFragment @Inject constructor(
private val adapterInfosList = mutableListOf<SectionAdapterInfo>() private val adapterInfosList = mutableListOf<SectionAdapterInfo>()
private var concatAdapter: ConcatAdapter? = null private var concatAdapter: ConcatAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = when (roomListParams.displayMode) {
RoomListDisplayMode.PEOPLE -> Screen.ScreenName.MobilePeople
RoomListDisplayMode.ROOMS -> Screen.ScreenName.MobileRooms
else -> null
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
views.stateView.contentView = views.roomListView views.stateView.contentView = views.roomListView

View file

@ -32,6 +32,8 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom
import im.vector.app.features.displayname.getBestName import im.vector.app.features.displayname.getBestName
import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
@ -56,7 +58,8 @@ class RoomListViewModel @AssistedInject constructor(
stringProvider: StringProvider, stringProvider: StringProvider,
appStateHandler: AppStateHandler, appStateHandler: AppStateHandler,
vectorPreferences: VectorPreferences, vectorPreferences: VectorPreferences,
autoAcceptInvites: AutoAcceptInvites autoAcceptInvites: AutoAcceptInvites,
private val analyticsTracker: AnalyticsTracker
) : VectorViewModel<RoomListViewState, RoomListAction, RoomListViewEvents>(initialState) { ) : VectorViewModel<RoomListViewState, RoomListAction, RoomListViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
@ -223,6 +226,7 @@ class RoomListViewModel @AssistedInject constructor(
viewModelScope.launch { viewModelScope.launch {
try { try {
room.join() room.join()
analyticsTracker.capture(action.roomSummary.toAnalyticsJoinedRoom())
// We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data.
// Instead, we wait for the room to be joined // Instead, we wait for the room to be joined
} catch (failure: Throwable) { } catch (failure: Throwable) {

View file

@ -40,6 +40,7 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.ToolbarConfigurable
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityLoginBinding import im.vector.app.databinding.ActivityLoginBinding
import im.vector.app.features.analytics.plan.Screen
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
@ -82,6 +83,8 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), ToolbarCo
override fun getCoordinatorLayout() = views.coordinatorLayout override fun getCoordinatorLayout() = views.coordinatorLayout
override fun initUiAndData() { override fun initUiAndData() {
analyticsScreenName = Screen.ScreenName.Login
if (isFirstCreation()) { if (isFirstCreation()) {
addFirstFragment() addFirstFragment()
} }
@ -200,6 +203,10 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), ToolbarCo
private fun updateWithState(loginViewState: LoginViewState) { private fun updateWithState(loginViewState: LoginViewState) {
if (loginViewState.isUserLogged()) { if (loginViewState.isUserLogged()) {
if (loginViewState.signMode == SignMode.SignUp) {
// change the screen name
analyticsScreenName = Screen.ScreenName.Register
}
val intent = HomeActivity.newIntent( val intent = HomeActivity.newIntent(
this, this,
accountCreation = loginViewState.signMode == SignMode.SignUp accountCreation = loginViewState.signMode == SignMode.SignUp

View file

@ -31,6 +31,7 @@ import im.vector.app.core.extensions.hidePassword
import im.vector.app.core.extensions.isEmail import im.vector.app.core.extensions.isEmail
import im.vector.app.core.extensions.toReducedUrl import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.databinding.FragmentLoginResetPasswordBinding import im.vector.app.databinding.FragmentLoginResetPasswordBinding
import im.vector.app.features.analytics.plan.Screen
import kotlinx.coroutines.flow.combine 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
@ -46,6 +47,11 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment<F
// Show warning only once // Show warning only once
private var showWarning = true private var showWarning = true
override fun onCreate(savedInstanceState: Bundle?) {
analyticsScreenName = Screen.ScreenName.ForgotPassword
super.onCreate(savedInstanceState)
}
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginResetPasswordBinding { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginResetPasswordBinding {
return FragmentLoginResetPasswordBinding.inflate(inflater, container, false) return FragmentLoginResetPasswordBinding.inflate(inflater, container, false)
} }

View file

@ -26,6 +26,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.BuildConfig import im.vector.app.BuildConfig
import im.vector.app.R import im.vector.app.R
import im.vector.app.databinding.FragmentLoginSplashBinding import im.vector.app.databinding.FragmentLoginSplashBinding
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import java.net.UnknownHostException import java.net.UnknownHostException
@ -42,6 +43,11 @@ class LoginSplashFragment @Inject constructor(
return FragmentLoginSplashBinding.inflate(inflater, container, false) return FragmentLoginSplashBinding.inflate(inflater, container, false)
} }
override fun onCreate(savedInstanceState: Bundle?) {
analyticsScreenName = Screen.ScreenName.Welcome
super.onCreate(savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View file

@ -23,6 +23,8 @@ import androidx.core.app.RemoteInput
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom
import im.vector.app.features.session.coroutineScope import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
@ -41,6 +43,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager @Inject lateinit var notificationDrawerManager: NotificationDrawerManager
@Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var analyticsTracker: AnalyticsTracker
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
if (intent == null || context == null) return if (intent == null || context == null) return
@ -79,7 +82,10 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
val room = session.getRoom(roomId) val room = session.getRoom(roomId)
if (room != null) { if (room != null) {
session.coroutineScope.launch { session.coroutineScope.launch {
tryOrNull { room.join() } tryOrNull {
room.join()
analyticsTracker.capture(room.roomSummary().toAnalyticsJoinedRoom())
}
} }
} }
} }

View file

@ -160,7 +160,7 @@ class PublicRoomsFragment @Inject constructor(
override fun onPublicRoomJoin(publicRoom: PublicRoom) { override fun onPublicRoomJoin(publicRoom: PublicRoom) {
Timber.v("PublicRoomJoinClicked: $publicRoom") Timber.v("PublicRoomJoinClicked: $publicRoom")
viewModel.handle(RoomDirectoryAction.JoinRoom(publicRoom.roomId)) viewModel.handle(RoomDirectoryAction.JoinRoom(publicRoom))
} }
override fun loadMore() { override fun loadMore() {

View file

@ -17,10 +17,11 @@
package im.vector.app.features.roomdirectory package im.vector.app.features.roomdirectory
import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
sealed class RoomDirectoryAction : VectorViewModelAction { sealed class RoomDirectoryAction : VectorViewModelAction {
data class SetRoomDirectoryData(val roomDirectoryData: RoomDirectoryData) : RoomDirectoryAction() data class SetRoomDirectoryData(val roomDirectoryData: RoomDirectoryData) : RoomDirectoryAction()
data class FilterWith(val filter: String) : RoomDirectoryAction() data class FilterWith(val filter: String) : RoomDirectoryAction()
object LoadMore : RoomDirectoryAction() object LoadMore : RoomDirectoryAction()
data class JoinRoom(val roomId: String) : RoomDirectoryAction() data class JoinRoom(val publicRoom: PublicRoom) : RoomDirectoryAction()
} }

View file

@ -28,6 +28,7 @@ import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.extensions.popBackstack import im.vector.app.core.extensions.popBackstack
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.matrixto.MatrixToBottomSheet
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
import im.vector.app.features.roomdirectory.createroom.CreateRoomArgs import im.vector.app.features.roomdirectory.createroom.CreateRoomArgs
@ -50,6 +51,7 @@ class RoomDirectoryActivity : VectorBaseActivity<ActivitySimpleBinding>(), Matri
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.RoomDirectory
sharedActionViewModel = viewModelProvider.get(RoomDirectorySharedActionViewModel::class.java) sharedActionViewModel = viewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
if (isFirstCreation()) { if (isFirstCreation()) {

View file

@ -27,6 +27,8 @@ import dagger.assisted.AssistedInject
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.toAnalyticsJoinedRoom
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -45,6 +47,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(
@Assisted initialState: PublicRoomsViewState, @Assisted initialState: PublicRoomsViewState,
vectorPreferences: VectorPreferences, vectorPreferences: VectorPreferences,
private val session: Session, private val session: Session,
private val analyticsTracker: AnalyticsTracker,
private val explicitTermFilter: ExplicitTermFilter private val explicitTermFilter: ExplicitTermFilter
) : VectorViewModel<PublicRoomsViewState, RoomDirectoryAction, RoomDirectoryViewEvents>(initialState) { ) : VectorViewModel<PublicRoomsViewState, RoomDirectoryAction, RoomDirectoryViewEvents>(initialState) {
@ -213,7 +216,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(
} }
private fun joinRoom(action: RoomDirectoryAction.JoinRoom) = withState { state -> private fun joinRoom(action: RoomDirectoryAction.JoinRoom) = withState { state ->
val roomMembershipChange = state.changeMembershipStates[action.roomId] val roomMembershipChange = state.changeMembershipStates[action.publicRoom.roomId]
if (roomMembershipChange?.isInProgress().orFalse()) { if (roomMembershipChange?.isInProgress().orFalse()) {
// Request already sent, should not happen // Request already sent, should not happen
Timber.w("Try to join an already joining room. Should not happen") Timber.w("Try to join an already joining room. Should not happen")
@ -222,7 +225,8 @@ class RoomDirectoryViewModel @AssistedInject constructor(
val viaServers = listOfNotNull(state.roomDirectoryData.homeServer) val viaServers = listOfNotNull(state.roomDirectoryData.homeServer)
viewModelScope.launch { viewModelScope.launch {
try { try {
session.joinRoom(action.roomId, viaServers = viaServers) session.joinRoom(action.publicRoom.roomId, viaServers = viaServers)
analyticsTracker.capture(action.publicRoom.toAnalyticsJoinedRoom())
// We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data.
// Instead, we wait for the room to be joined // Instead, we wait for the room to be joined
} catch (failure: Throwable) { } catch (failure: Throwable) {

View file

@ -28,6 +28,7 @@ import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.ToolbarConfigurable
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.roomdirectory.RoomDirectorySharedAction import im.vector.app.features.roomdirectory.RoomDirectorySharedAction
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -62,6 +63,7 @@ class CreateRoomActivity : VectorBaseActivity<ActivitySimpleBinding>(), ToolbarC
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.CreateRoom
sharedActionViewModel = viewModelProvider.get(RoomDirectorySharedActionViewModel::class.java) sharedActionViewModel = viewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
sharedActionViewModel sharedActionViewModel
.stream() .stream()

View file

@ -30,6 +30,8 @@ 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.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
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.plan.CreatedRoom
import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.getElementWellknown
import im.vector.app.features.raw.wellknown.isE2EByDefault import im.vector.app.features.raw.wellknown.isE2EByDefault
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -52,10 +54,12 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset
import timber.log.Timber import timber.log.Timber
class CreateRoomViewModel @AssistedInject constructor(@Assisted private val initialState: CreateRoomViewState, class CreateRoomViewModel @AssistedInject constructor(
private val session: Session, @Assisted private val initialState: CreateRoomViewState,
private val rawService: RawService, private val session: Session,
appStateHandler: AppStateHandler private val rawService: RawService,
appStateHandler: AppStateHandler,
private val analyticsTracker: AnalyticsTracker
) : VectorViewModel<CreateRoomViewState, CreateRoomAction, CreateRoomViewEvents>(initialState) { ) : VectorViewModel<CreateRoomViewState, CreateRoomAction, CreateRoomViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
@ -296,7 +300,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
viewModelScope.launch { viewModelScope.launch {
runCatching { session.createRoom(createRoomParams) }.fold( runCatching { session.createRoom(createRoomParams) }.fold(
{ roomId -> { roomId ->
analyticsTracker.capture(CreatedRoom(isDM = createRoomParams.isDirect.orFalse()))
if (state.parentSpaceId != null) { if (state.parentSpaceId != null) {
// add it as a child // add it as a child
try { try {

View file

@ -30,6 +30,7 @@ import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.roomdirectory.RoomDirectoryAction import im.vector.app.features.roomdirectory.RoomDirectoryAction
import im.vector.app.features.roomdirectory.RoomDirectoryData import im.vector.app.features.roomdirectory.RoomDirectoryData
import im.vector.app.features.roomdirectory.RoomDirectoryServer import im.vector.app.features.roomdirectory.RoomDirectoryServer
@ -52,6 +53,11 @@ class RoomDirectoryPickerFragment @Inject constructor(private val roomDirectoryP
return FragmentRoomDirectoryPickerBinding.inflate(inflater, container, false) return FragmentRoomDirectoryPickerBinding.inflate(inflater, container, false)
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.MobileSwitchDirectory
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View file

@ -40,6 +40,7 @@ data class RoomPreviewData(
val roomAlias: String? = null, val roomAlias: String? = null,
val roomType: String? = null, val roomType: String? = null,
val topic: String? = null, val topic: String? = null,
val numJoinedMembers: Int? = null,
val worldReadable: Boolean = false, val worldReadable: Boolean = false,
val avatarUrl: String? = null, val avatarUrl: String? = null,
val homeServers: List<String> = emptyList(), val homeServers: List<String> = emptyList(),
@ -69,6 +70,7 @@ class RoomPreviewActivity : VectorBaseActivity<ActivitySimpleBinding>(), Toolbar
roomName = publicRoom.name, roomName = publicRoom.name,
roomAlias = publicRoom.getPrimaryAlias(), roomAlias = publicRoom.getPrimaryAlias(),
topic = publicRoom.topic, topic = publicRoom.topic,
numJoinedMembers = publicRoom.numJoinedMembers,
worldReadable = publicRoom.worldReadable, worldReadable = publicRoom.worldReadable,
avatarUrl = publicRoom.avatarUrl, avatarUrl = publicRoom.avatarUrl,
homeServers = listOfNotNull(roomDirectoryData.homeServer) homeServers = listOfNotNull(roomDirectoryData.homeServer)

View file

@ -27,6 +27,9 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.EmptyViewEvents
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.toAnalyticsRoomSize
import im.vector.app.features.analytics.plan.JoinedRoom
import im.vector.app.features.roomdirectory.JoinState import im.vector.app.features.roomdirectory.JoinState
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -44,9 +47,11 @@ import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.flow
import timber.log.Timber import timber.log.Timber
class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val initialState: RoomPreviewViewState, class RoomPreviewViewModel @AssistedInject constructor(
private val session: Session) : @Assisted private val initialState: RoomPreviewViewState,
VectorViewModel<RoomPreviewViewState, RoomPreviewAction, EmptyViewEvents>(initialState) { private val analyticsTracker: AnalyticsTracker,
private val session: Session
) : VectorViewModel<RoomPreviewViewState, RoomPreviewAction, EmptyViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<RoomPreviewViewModel, RoomPreviewViewState> { interface Factory : MavericksAssistedViewModelFactory<RoomPreviewViewModel, RoomPreviewViewState> {
@ -243,6 +248,11 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
viewModelScope.launch { viewModelScope.launch {
try { try {
session.joinRoom(state.roomId, viaServers = state.homeServers) session.joinRoom(state.roomId, viaServers = state.homeServers)
analyticsTracker.capture(JoinedRoom(
// Always false in this case (?)
isDM = false,
roomSize = state.numJoinMembers.toAnalyticsRoomSize()
))
// We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data.
// Instead, we wait for the room to be joined // Instead, we wait for the room to be joined
} catch (failure: Throwable) { } catch (failure: Throwable) {

View file

@ -33,6 +33,7 @@ data class RoomPreviewViewState(
val roomName: String? = null, val roomName: String? = null,
val roomTopic: String? = null, val roomTopic: String? = null,
val numJoinMembers: Int? = null,
val avatarUrl: String? = null, val avatarUrl: String? = null,
val shouldPeekFromServer: Boolean = false, val shouldPeekFromServer: Boolean = false,
@ -56,6 +57,7 @@ data class RoomPreviewViewState(
homeServers = args.homeServers, homeServers = args.homeServers,
roomName = args.roomName, roomName = args.roomName,
roomTopic = args.topic, roomTopic = args.topic,
numJoinMembers = args.numJoinedMembers,
avatarUrl = args.avatarUrl, avatarUrl = args.avatarUrl,
shouldPeekFromServer = args.peekFromServer, shouldPeekFromServer = args.peekFromServer,
fromEmailInvite = args.fromEmailInvite, fromEmailInvite = args.fromEmailInvite,
@ -64,6 +66,6 @@ data class RoomPreviewViewState(
fun matrixItem(): MatrixItem { fun matrixItem(): MatrixItem {
return if (roomType == RoomType.SPACE) MatrixItem.SpaceItem(roomId, roomName ?: roomAlias, avatarUrl) return if (roomType == RoomType.SPACE) MatrixItem.SpaceItem(roomId, roomName ?: roomAlias, avatarUrl)
else MatrixItem.RoomItem(roomId, roomName ?: roomAlias, avatarUrl) else MatrixItem.RoomItem(roomId, roomName ?: roomAlias, avatarUrl)
} }
} }

View file

@ -47,6 +47,7 @@ import im.vector.app.databinding.DialogBaseEditTextBinding
import im.vector.app.databinding.DialogShareQrCodeBinding import im.vector.app.databinding.DialogShareQrCodeBinding
import im.vector.app.databinding.FragmentMatrixProfileBinding import im.vector.app.databinding.FragmentMatrixProfileBinding
import im.vector.app.databinding.ViewStubRoomMemberProfileHeaderBinding import im.vector.app.databinding.ViewStubRoomMemberProfileHeaderBinding
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.crypto.verification.VerificationBottomSheet
import im.vector.app.features.displayname.getBestName import im.vector.app.features.displayname.getBestName
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
@ -88,6 +89,11 @@ class RoomMemberProfileFragment @Inject constructor(
override fun getMenuRes() = R.menu.vector_room_member_profile override fun getMenuRes() = R.menu.vector_room_member_profile
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.User
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupToolbar(views.matrixProfileToolbar) setupToolbar(views.matrixProfileToolbar)

View file

@ -44,6 +44,7 @@ import im.vector.app.core.utils.copyToClipboard
import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.databinding.FragmentMatrixProfileBinding import im.vector.app.databinding.FragmentMatrixProfileBinding
import im.vector.app.databinding.ViewStubRoomProfileHeaderBinding import im.vector.app.databinding.ViewStubRoomProfileHeaderBinding
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.RoomDetailPendingAction import im.vector.app.features.home.room.detail.RoomDetailPendingAction
import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
@ -88,6 +89,7 @@ class RoomProfileFragment @Inject constructor(
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.RoomSettings
setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY) { _, bundle -> setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY) { _, bundle ->
bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId -> bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId ->
roomDetailPendingActionStore.data = RoomDetailPendingAction.OpenRoom(replacementRoomId, closeCurrentRoom = true) roomDetailPendingActionStore.data = RoomDetailPendingAction.OpenRoom(replacementRoomId, closeCurrentRoom = true)

View file

@ -34,6 +34,7 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.saveMedia import im.vector.app.core.utils.saveMedia
import im.vector.app.core.utils.shareMedia import im.vector.app.core.utils.shareMedia
import im.vector.app.databinding.FragmentRoomUploadsBinding import im.vector.app.databinding.FragmentRoomUploadsBinding
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.RoomProfileArgs
@ -54,6 +55,11 @@ class RoomUploadsFragment @Inject constructor(
return FragmentRoomUploadsBinding.inflate(inflater, container, false) return FragmentRoomUploadsBinding.inflate(inflater, container, false)
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.RoomUploads
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View file

@ -29,7 +29,9 @@ import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
import im.vector.app.features.analytics.VectorAnalytics import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.analytics.screen.ScreenEvent
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
@ -37,6 +39,18 @@ import reactivecircus.flowbinding.android.view.clicks
import timber.log.Timber import timber.log.Timber
abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), MavericksView { abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), MavericksView {
/* ==========================================================================================
* Analytics
* ========================================================================================== */
protected var analyticsScreenName: Screen.ScreenName? = null
private var screenEvent: ScreenEvent? = null
protected lateinit var analyticsTracker: AnalyticsTracker
/* ==========================================================================================
* Activity
* ========================================================================================== */
val vectorActivity: VectorBaseActivity<*> by lazy { val vectorActivity: VectorBaseActivity<*> by lazy {
activity as VectorBaseActivity<*> activity as VectorBaseActivity<*>
@ -47,7 +61,6 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick
// members // members
protected lateinit var session: Session protected lateinit var session: Session
protected lateinit var errorFormatter: ErrorFormatter protected lateinit var errorFormatter: ErrorFormatter
protected lateinit var analytics: VectorAnalytics
/* ========================================================================================== /* ==========================================================================================
* Views * Views
@ -72,17 +85,23 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick
super.onAttach(context) super.onAttach(context)
session = singletonEntryPoint.activeSessionHolder().getActiveSession() session = singletonEntryPoint.activeSessionHolder().getActiveSession()
errorFormatter = singletonEntryPoint.errorFormatter() errorFormatter = singletonEntryPoint.errorFormatter()
analytics = singletonEntryPoint.analytics() analyticsTracker = singletonEntryPoint.analyticsTracker()
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
Timber.i("onResume Fragment ${javaClass.simpleName}") Timber.i("onResume Fragment ${javaClass.simpleName}")
screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
vectorActivity.supportActionBar?.setTitle(titleRes) vectorActivity.supportActionBar?.setTitle(titleRes)
// find the view from parent activity // find the view from parent activity
mLoadingView = vectorActivity.findViewById(R.id.vector_settings_spinner_views) mLoadingView = vectorActivity.findViewById(R.id.vector_settings_spinner_views)
} }
override fun onPause() {
super.onPause()
screenEvent?.send(analyticsTracker)
}
abstract fun bindPref() abstract fun bindPref()
abstract var titleRes: Int abstract var titleRes: Int

View file

@ -16,8 +16,10 @@
package im.vector.app.features.settings package im.vector.app.features.settings
import android.os.Bundle
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorPreference
import im.vector.app.features.analytics.plan.Screen
import javax.inject.Inject import javax.inject.Inject
class VectorSettingsRootFragment @Inject constructor() : VectorSettingsBaseFragment() { class VectorSettingsRootFragment @Inject constructor() : VectorSettingsBaseFragment() {
@ -25,6 +27,11 @@ class VectorSettingsRootFragment @Inject constructor() : VectorSettingsBaseFragm
override var titleRes: Int = R.string.title_activity_settings override var titleRes: Int = R.string.title_activity_settings
override val preferenceXmlRes = R.xml.vector_settings_root override val preferenceXmlRes = R.xml.vector_settings_root
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.Settings
}
override fun bindPref() { override fun bindPref() {
tintIcons() tintIcons()
} }

View file

@ -51,6 +51,7 @@ import im.vector.app.core.utils.copyToClipboard
import im.vector.app.core.utils.openFileSelection import im.vector.app.core.utils.openFileSelection
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
import im.vector.app.databinding.DialogImportE2eKeysBinding import im.vector.app.databinding.DialogImportE2eKeysBinding
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewActions import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewActions
import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewModel import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewModel
import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewState import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewState
@ -91,6 +92,11 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
private val analyticsConsentViewModel: AnalyticsConsentViewModel by fragmentViewModel() private val analyticsConsentViewModel: AnalyticsConsentViewModel by fragmentViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.SettingsSecurity
}
// cryptography // cryptography
private val mCryptographyCategory by lazy { private val mCryptographyCategory by lazy {
findPreference<PreferenceCategory>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY)!! findPreference<PreferenceCategory>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY)!!

View file

@ -31,6 +31,7 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentDeactivateAccountBinding import im.vector.app.databinding.FragmentDeactivateAccountBinding
import im.vector.app.features.MainActivity import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs import im.vector.app.features.MainActivityArgs
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.auth.ReAuthActivity import im.vector.app.features.auth.ReAuthActivity
import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.settings.VectorSettingsActivity
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
@ -47,7 +48,7 @@ class DeactivateAccountFragment @Inject constructor() : VectorBaseFragment<Fragm
private val reAuthActivityResultLauncher = registerStartForActivityResult { activityResult -> private val reAuthActivityResultLauncher = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) { if (activityResult.resultCode == Activity.RESULT_OK) {
when (activityResult.data?.extras?.getString(ReAuthActivity.RESULT_FLOW_TYPE)) { when (activityResult.data?.extras?.getString(ReAuthActivity.RESULT_FLOW_TYPE)) {
LoginFlowTypes.SSO -> { LoginFlowTypes.SSO -> {
viewModel.handle(DeactivateAccountAction.SsoAuthDone) viewModel.handle(DeactivateAccountAction.SsoAuthDone)
} }
LoginFlowTypes.PASSWORD -> { LoginFlowTypes.PASSWORD -> {
@ -63,6 +64,11 @@ class DeactivateAccountFragment @Inject constructor() : VectorBaseFragment<Fragm
} }
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.DeactivateAccount
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
(activity as? AppCompatActivity)?.supportActionBar?.setTitle(R.string.deactivate_account_title) (activity as? AppCompatActivity)?.supportActionBar?.setTitle(R.string.deactivate_account_title)

View file

@ -16,8 +16,10 @@
package im.vector.app.features.settings.notifications package im.vector.app.features.settings.notifications
import android.os.Bundle
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorPreferenceCategory
import im.vector.app.features.analytics.plan.Screen
import org.matrix.android.sdk.api.pushrules.RuleIds import org.matrix.android.sdk.api.pushrules.RuleIds
class VectorSettingsDefaultNotificationPreferenceFragment : class VectorSettingsDefaultNotificationPreferenceFragment :
@ -34,6 +36,11 @@ class VectorSettingsDefaultNotificationPreferenceFragment :
"SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_GROUP_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ENCRYPTED "SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_GROUP_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ENCRYPTED
) )
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.SettingsDefaultNotifications
}
override fun bindPref() { override fun bindPref() {
super.bindPref() super.bindPref()
val category = findPreference<VectorPreferenceCategory>("SETTINGS_DEFAULT")!! val category = findPreference<VectorPreferenceCategory>("SETTINGS_DEFAULT")!!

View file

@ -25,6 +25,7 @@ import im.vector.app.core.preference.KeywordPreference
import im.vector.app.core.preference.VectorCheckboxPreference import im.vector.app.core.preference.VectorCheckboxPreference
import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorPreference
import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorPreferenceCategory
import im.vector.app.features.analytics.plan.Screen
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -34,7 +35,7 @@ import org.matrix.android.sdk.api.pushrules.rest.PushRule
import org.matrix.android.sdk.api.pushrules.toJson import org.matrix.android.sdk.api.pushrules.toJson
class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment : class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment :
VectorSettingsPushRuleNotificationPreferenceFragment() { VectorSettingsPushRuleNotificationPreferenceFragment() {
override var titleRes: Int = R.string.settings_notification_mentions_and_keywords override var titleRes: Int = R.string.settings_notification_mentions_and_keywords
@ -42,6 +43,11 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment :
private var keywordsHasFocus = false private var keywordsHasFocus = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.SettingsMentionsAndKeywords
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
session.getKeywords().observe(viewLifecycleOwner, this::updateWithKeywords) session.getKeywords().observe(viewLifecycleOwner, this::updateWithKeywords)
@ -71,7 +77,7 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment :
val keywords = editKeywordPreference.keywords val keywords = editKeywordPreference.keywords
val newChecked = newValue as Boolean val newChecked = newValue as Boolean
displayLoadingView() displayLoadingView()
updateKeywordPushRules(keywords, newChecked) { result -> updateKeywordPushRules(keywords, newChecked) { result ->
hideLoadingView() hideLoadingView()
if (!isAdded) { if (!isAdded) {
return@updateKeywordPushRules return@updateKeywordPushRules
@ -88,7 +94,7 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment :
false false
} }
editKeywordPreference.listener = object : KeywordPreference.Listener { editKeywordPreference.listener = object : KeywordPreference.Listener {
override fun onFocusDidChange(hasFocus: Boolean) { override fun onFocusDidChange(hasFocus: Boolean) {
keywordsHasFocus = true keywordsHasFocus = true
} }
@ -174,8 +180,8 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment :
} }
override val prefKeyToPushRuleId = mapOf( override val prefKeyToPushRuleId = mapOf(
"SETTINGS_PUSH_RULE_CONTAINING_MY_DISPLAY_NAME_PREFERENCE_KEY" to RuleIds.RULE_ID_CONTAIN_DISPLAY_NAME, "SETTINGS_PUSH_RULE_CONTAINING_MY_DISPLAY_NAME_PREFERENCE_KEY" to RuleIds.RULE_ID_CONTAIN_DISPLAY_NAME,
"SETTINGS_PUSH_RULE_CONTAINING_MY_USER_NAME_PREFERENCE_KEY" to RuleIds.RULE_ID_CONTAIN_USER_NAME, "SETTINGS_PUSH_RULE_CONTAINING_MY_USER_NAME_PREFERENCE_KEY" to RuleIds.RULE_ID_CONTAIN_USER_NAME,
"SETTINGS_PUSH_RULE_MESSAGES_CONTAINING_AT_ROOM_PREFERENCE_KEY" to RuleIds.RULE_ID_ROOM_NOTIF "SETTINGS_PUSH_RULE_MESSAGES_CONTAINING_AT_ROOM_PREFERENCE_KEY" to RuleIds.RULE_ID_ROOM_NOTIF
) )
} }