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
### 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
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 im.vector.app.core.dialogs.UnrecognizedCertificateDialog
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.home.AvatarRenderer
import im.vector.app.features.navigation.Navigator
@ -56,7 +56,7 @@ interface SingletonEntryPoint {
fun pinLocker(): PinLocker
fun analytics(): VectorAnalytics
fun analyticsTracker(): AnalyticsTracker
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.time.Clock
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.impl.DefaultVectorAnalytics
import im.vector.app.features.invite.AutoAcceptInvites
@ -64,6 +65,9 @@ abstract class VectorBindModule {
@Binds
abstract fun bindVectorAnalytics(analytics: DefaultVectorAnalytics): VectorAnalytics
@Binds
abstract fun bindAnalyticsTracker(analytics: DefaultVectorAnalytics): AnalyticsTracker
@Binds
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.features.MainActivity
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.consent.ConsentNotGivenHelper
import im.vector.app.features.navigation.Navigator
@ -90,6 +92,15 @@ import timber.log.Timber
import javax.inject.Inject
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
* ========================================================================================== */
@ -133,7 +144,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
private lateinit var sessionListener: SessionListener
protected lateinit var bugReporter: BugReporter
private lateinit var pinLocker: PinLocker
protected lateinit var analytics: VectorAnalytics
@Inject
lateinit var rageShake: RageShake
@ -189,7 +199,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
configurationViewModel = viewModelProvider.get(ConfigurationViewModel::class.java)
bugReporter = singletonEntryPoint.bugReporter()
pinLocker = singletonEntryPoint.pinLocker()
analytics = singletonEntryPoint.analytics()
analyticsTracker = singletonEntryPoint.analyticsTracker()
navigator = singletonEntryPoint.navigator()
activeSessionHolder = singletonEntryPoint.activeSessionHolder()
vectorPreferences = singletonEntryPoint.vectorPreferences()
@ -324,7 +334,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
override fun onResume() {
super.onResume()
Timber.i("onResume Activity ${javaClass.simpleName}")
screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
configurationViewModel.onActivityResumed()
if (this !is BugReportActivity && vectorPreferences.useRageshake()) {
@ -363,6 +373,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
override fun onPause() {
super.onPause()
screenEvent?.send(analyticsTracker, analyticsScreenName)
Timber.i("onPause Activity ${javaClass.simpleName}")
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.toMvRxBundle
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.onEach
import reactivecircus.flowbinding.android.view.clicks
@ -47,6 +49,14 @@ import timber.log.Timber
* Add Mavericks capabilities, handle DI and bindings.
*/
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
@ -84,8 +94,6 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
open val showExpanded = false
protected lateinit var analytics: VectorAnalytics
interface ResultListener {
fun onBottomSheetResult(resultCode: Int, data: Any?)
@ -124,13 +132,19 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
val activityEntryPoint = EntryPointAccessors.fromActivity(vectorBaseActivity, ActivityEntryPoint::class.java)
viewModelFactory = activityEntryPoint.viewModelFactory()
val singletonEntryPoint = context.singletonEntryPoint()
analytics = singletonEntryPoint.analytics()
analyticsTracker = singletonEntryPoint.analyticsTracker()
super.onAttach(context)
}
override fun onResume() {
super.onResume()
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 {

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.extensions.singletonEntryPoint
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.lib.ui.styles.dialogs.MaterialProgressDialog
import kotlinx.coroutines.flow.launchIn
@ -51,6 +53,18 @@ import reactivecircus.flowbinding.android.view.clicks
import timber.log.Timber
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 {
activity as VectorBaseActivity<*>
@ -61,7 +75,6 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
* ========================================================================================== */
protected lateinit var navigator: Navigator
protected lateinit var analytics: VectorAnalytics
protected lateinit var errorFormatter: ErrorFormatter
protected lateinit var unrecognizedCertificateDialog: UnrecognizedCertificateDialog
@ -98,7 +111,7 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
val activityEntryPoint = EntryPointAccessors.fromActivity(vectorBaseActivity, ActivityEntryPoint::class.java)
navigator = singletonEntryPoint.navigator()
errorFormatter = singletonEntryPoint.errorFormatter()
analytics = singletonEntryPoint.analytics()
analyticsTracker = singletonEntryPoint.analyticsTracker()
unrecognizedCertificateDialog = singletonEntryPoint.unrecognizedCertificateDialog()
viewModelFactory = activityEntryPoint.viewModelFactory()
childFragmentManager.fragmentFactory = activityEntryPoint.fragmentFactory()
@ -125,12 +138,14 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
override fun onResume() {
super.onResume()
Timber.i("onResume Fragment ${javaClass.simpleName}")
screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
}
@CallSuper
override fun onPause() {
super.onPause()
Timber.i("onPause Fragment ${javaClass.simpleName}")
screenEvent?.send(analyticsTracker)
}
@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
class DecryptionFailureTracker @Inject constructor(
private val vectorAnalytics: VectorAnalytics,
private val analyticsTracker: AnalyticsTracker,
private val clock: Clock
) {
@ -136,7 +136,7 @@ class DecryptionFailureTracker @Inject constructor(
// for now we ignore events already reported even if displayed again?
.filter { alreadyReported.contains(it).not() }
.forEach { failedEventId ->
vectorAnalytics.capture(Error(failedEventId, Error.Domain.E2EE, aggregation.key))
analyticsTracker.capture(Error(failedEventId, Error.Domain.E2EE, aggregation.key))
alreadyReported.add(failedEventId)
}
}

View file

@ -16,11 +16,9 @@
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
interface VectorAnalytics {
interface VectorAnalytics : AnalyticsTracker {
/**
* 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
*/
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.
*/
data class CallEnded(
/**
* The duration of the call in milliseconds.
*/
val durationMs: Int,
/**
* Whether its a video call or not.
*/
val isVideo: Boolean,
/**
* Number of participants in the call.
*/
val numParticipants: Int,
/**
* Whether this user placed it.
*/
val placed: Boolean,
/**
* The duration of the call in milliseconds.
*/
val durationMs: Int,
/**
* Whether its a video call or not.
*/
val isVideo: Boolean,
/**
* Number of participants in the call.
*/
val numParticipants: Int,
/**
* Whether this user placed it.
*/
val placed: Boolean,
) : VectorAnalyticsEvent {
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.
*/
data class CallError(
/**
* Whether its a video call or not.
*/
val isVideo: Boolean,
/**
* Number of participants in the call.
*/
val numParticipants: Int,
/**
* Whether this user placed it.
*/
val placed: Boolean,
/**
* Whether its a video call or not.
*/
val isVideo: Boolean,
/**
* Number of participants in the call.
*/
val numParticipants: Int,
/**
* Whether this user placed it.
*/
val placed: Boolean,
) : VectorAnalyticsEvent {
override fun getName() = "CallError"

View file

@ -25,18 +25,18 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
* Triggered when a call is started.
*/
data class CallStarted(
/**
* Whether its a video call or not.
*/
val isVideo: Boolean,
/**
* Number of participants in the call.
*/
val numParticipants: Int,
/**
* Whether this user placed it.
*/
val placed: Boolean,
/**
* Whether its a video call or not.
*/
val isVideo: Boolean,
/**
* Number of participants in the call.
*/
val numParticipants: Int,
/**
* Whether this user placed it.
*/
val placed: Boolean,
) : VectorAnalyticsEvent {
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.
*/
data class Click(
/**
* The index of the element, if its in a list of elements.
*/
val index: Int? = null,
/**
* The unique name of this element.
*/
val name: Name,
/**
* The index of the element, if its in a list of elements.
*/
val index: Int? = null,
/**
* The unique name of this element.
*/
val name: Name,
) : VectorAnalyticsEvent {
enum class Name {

View file

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

View file

@ -25,12 +25,12 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
* Triggered when an error occurred
*/
data class Error(
/**
* Context - client defined, can be used for debugging
*/
val context: String? = null,
val domain: Domain,
val name: Name,
/**
* Context - client defined, can be used for debugging
*/
val context: String? = null,
val domain: Domain,
val name: Name,
) : VectorAnalyticsEvent {
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.
*/
data class JoinedRoom(
/**
* Whether the room is a DM.
*/
val isDM: Boolean,
/**
* The size of the room.
*/
val roomSize: RoomSize,
/**
* Whether the room is a DM.
*/
val isDM: Boolean,
/**
* The size of the room.
*/
val roomSize: RoomSize,
) : VectorAnalyticsEvent {
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.
*/
data class PerformanceTimer(
/**
* Client defined, can be used for debugging.
*/
val context: String? = null,
/**
* Client defined, an optional value to indicate how many items were handled during the operation.
*/
val itemCount: Int? = null,
/**
* The timer that is being reported.
*/
val name: Name,
/**
* The time reported by the timer in milliseconds.
*/
val timeMs: Int,
/**
* Client defined, can be used for debugging.
*/
val context: String? = null,
/**
* Client defined, an optional value to indicate how many items were
* handled during the operation.
*/
val itemCount: Int? = null,
/**
* The timer that is being reported.
*/
val name: Name,
/**
* The time reported by the timer in milliseconds.
*/
val timeMs: Int,
) : VectorAnalyticsEvent {
enum class Name {
@ -55,7 +56,8 @@ data class PerformanceTimer(
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,
@ -65,7 +67,8 @@ data class PerformanceTimer(
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,
@ -80,7 +83,8 @@ data class PerformanceTimer(
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,
}

View file

@ -25,28 +25,221 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
* Triggered when the user changed screen
*/
data class Screen(
/**
* How long the screen was displayed for in milliseconds.
*/
val durationMs: Int? = null,
val screenName: ScreenName,
/**
* How long the screen was displayed for in milliseconds.
*/
val durationMs: Int? = null,
val screenName: ScreenName,
) : VectorAnalyticsScreen {
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,
/**
* The Home tab on iOS | possibly the same on Android? | The Home space
* on Web?
*/
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,
/**
* 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,
/**
* 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,
/**
* 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,
/**
* ?
*/
WebCompleteSecurity,
/**
* ?
*/
WebE2ESetup,
WebForgotPassword,
/**
* ?
*/
WebLoading,
WebLogin,
WebRegister,
/**
* ?
*/
WebSoftLogout,
WebWelcome,
/**
* The splash screen.
*/
Welcome,
}
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
import android.content.ClipboardManager
import android.content.Context
import android.content.res.ColorStateList
import android.os.Bundle
import android.telephony.PhoneNumberFormattingTextWatcher
@ -37,6 +38,10 @@ import androidx.fragment.app.Fragment
import com.android.dialer.dialpadview.DialpadView
import com.android.dialer.dialpadview.DigitsEditText
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
class DialPadFragment : Fragment(), TextWatcher {
@ -53,6 +58,25 @@ class DialPadFragment : Fragment(), TextWatcher {
private var enableDelete = 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(
inflater: LayoutInflater,
container: ViewGroup?,

View file

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

View file

@ -22,6 +22,9 @@ import androidx.lifecycle.LifecycleOwner
import im.vector.app.ActiveSessionDataSource
import im.vector.app.BuildConfig
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.audio.CallAudioManager
import im.vector.app.features.call.lookup.CallProtocolsChecker
@ -68,7 +71,8 @@ private val loggerTag = LoggerTag("WebRtcCallManager", LoggerTag.VOIP)
@Singleton
class WebRtcCallManager @Inject constructor(
private val context: Context,
private val activeSessionDataSource: ActiveSessionDataSource
private val activeSessionDataSource: ActiveSessionDataSource,
private val analyticsTracker: AnalyticsTracker
) : CallListener,
DefaultLifecycleObserver {
@ -237,6 +241,7 @@ class WebRtcCallManager @Inject constructor(
val currentCall = getCurrentCall().takeIf { it != call }
currentCall?.updateRemoteOnHold(onHold = true)
audioManager.setMode(if (call.mxCall.isVideoCall) CallAudioManager.Mode.VIDEO_CALL else CallAudioManager.Mode.AUDIO_CALL)
call.trackCallStarted()
this.currentCall.setAndNotify(call)
}
@ -245,6 +250,7 @@ class WebRtcCallManager @Inject constructor(
val webRtcCall = callsByCallId.remove(callId) ?: return Unit.also {
Timber.tag(loggerTag.value).v("On call ended for unknown call $callId")
}
webRtcCall.trackCallEnded()
CallService.onCallTerminated(context, callId, endCallReason, rejected)
callsByRoomId[webRtcCall.signalingRoomId]?.remove(webRtcCall)
callsByRoomId[webRtcCall.nativeRoomId]?.remove(webRtcCall)
@ -443,4 +449,28 @@ class WebRtcCallManager @Inject constructor(
}
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.onPermissionDeniedSnackbar
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.userdirectory.UserListFragment
import im.vector.app.features.userdirectory.UserListFragmentArgs
@ -63,6 +64,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.StartChat
views.toolbar.visibility = View.GONE
sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)

View file

@ -24,6 +24,7 @@ import android.os.Bundle
import android.os.Parcelable
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.core.view.GravityCompat
import androidx.core.view.isVisible
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.MainActivityArgs
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.matrixto.MatrixToBottomSheet
import im.vector.app.features.navigation.Navigator
@ -104,6 +107,7 @@ class HomeActivity :
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
private val homeActivityViewModel: HomeActivityViewModel by viewModel()
@Suppress("UNUSED")
private val analyticsAccountDataViewModel: AnalyticsAccountDataViewModel by viewModel()
@Suppress("UNUSED")
@ -164,6 +168,16 @@ class HomeActivity :
}
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) {
hideKeyboard()
}
@ -175,6 +189,7 @@ class HomeActivity :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.Home
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
FcmHelper.ensureFcmTokenIsRetrieved(this, pushManager, vectorPreferences.areNotificationEnabledForDevice())
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.utils.startSharePlainTextIntent
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.VectorSettingsActivity
import im.vector.app.features.spaces.SpaceListFragment
@ -97,6 +98,7 @@ class HomeDrawerFragment @Inject constructor(
views.homeDrawerInviteFriendButton.debouncedClicks {
session.permalinkService().createPermalink(sharedActionViewModel.session.myUserId)?.let { permalink ->
analyticsTracker.screen(Screen(screenName = Screen.ScreenName.MobileInviteFriends))
val text = getString(R.string.invite_friends_text, permalink)
startSharePlainTextIntent(

View file

@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.core.view.GravityCompat
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.VectorBaseActivity
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.detail.timeline.helper.VoiceMessagePlaybackTracker
import im.vector.app.features.matrixto.MatrixToBottomSheet
@ -161,6 +164,16 @@ class RoomDetailActivity :
}
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) {
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.databinding.DialogReportContentBinding
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.AttachmentsHelper
import im.vector.app.features.attachments.ContactAttachment
@ -336,6 +338,7 @@ class RoomDetailFragment @Inject constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.Room
setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY) { _, bundle ->
bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId ->
roomDetailViewModel.handle(RoomDetailAction.RoomUpgradeSuccess(replacementRoomId))
@ -1396,6 +1399,7 @@ class RoomDetailFragment @Inject constructor(
return
}
if (text.isNotBlank()) {
analyticsTracker.capture(Click(name = Click.Name.SendMessageButton))
// We collapse ASAP, if not there will be a slight annoying delay
views.composerLayout.collapse(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.resources.StringProvider
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.extensions.toAnalyticsJoinedRoom
import im.vector.app.features.call.conference.ConferenceEvent
import im.vector.app.features.call.conference.JitsiActiveConferenceHolder
import im.vector.app.features.call.conference.JitsiService
@ -114,6 +116,7 @@ class RoomDetailViewModel @AssistedInject constructor(
private val chatEffectManager: ChatEffectManager,
private val directRoomHelper: DirectRoomHelper,
private val jitsiService: JitsiService,
private val analyticsTracker: AnalyticsTracker,
private val activeConferenceHolder: JitsiActiveConferenceHolder,
private val decryptionFailureTracker: DecryptionFailureTracker,
timelineFactory: TimelineFactory,
@ -730,7 +733,10 @@ class RoomDetailViewModel @AssistedInject constructor(
private fun handleAcceptInvite() {
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.platform.VectorViewModel
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.command.CommandParser
import im.vector.app.features.command.ParsedCommand
@ -69,6 +71,7 @@ class MessageComposerViewModel @AssistedInject constructor(
private val commandParser: CommandParser,
private val rainbowGenerator: RainbowGenerator,
private val voiceMessageHelper: VoiceMessageHelper,
private val analyticsTracker: AnalyticsTracker,
private val voicePlayerHelper: VoicePlayerHelper
) : VectorViewModel<MessageComposerViewState, MessageComposerAction, MessageComposerViewEvents>(initialState) {
@ -521,6 +524,7 @@ class MessageComposerViewModel @AssistedInject constructor(
return@launch
}
session.getRoomSummary(command.roomAlias)
?.also { analyticsTracker.capture(it.toAnalyticsJoinedRoom()) }
?.roomId
?.let {
_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.platform.VectorBaseActivity
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.room.list.RoomListFragment
import im.vector.app.features.home.room.list.RoomListParams
@ -42,6 +43,7 @@ class FilteredRoomsActivity : VectorBaseActivity<ActivityFilteredRoomsBinding>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.RoomFilter
configureToolbar(views.filteredRoomsToolbar)
if (isFirstCreation()) {
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.resources.UserPreferencesProvider
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.room.filtered.FilteredRoomFooterItem
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
@ -100,6 +101,15 @@ class RoomListFragment @Inject constructor(
private val adapterInfosList = mutableListOf<SectionAdapterInfo>()
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?) {
super.onViewCreated(view, savedInstanceState)
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.platform.VectorViewModel
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.invite.AutoAcceptInvites
import im.vector.app.features.settings.VectorPreferences
@ -56,7 +58,8 @@ class RoomListViewModel @AssistedInject constructor(
stringProvider: StringProvider,
appStateHandler: AppStateHandler,
vectorPreferences: VectorPreferences,
autoAcceptInvites: AutoAcceptInvites
autoAcceptInvites: AutoAcceptInvites,
private val analyticsTracker: AnalyticsTracker
) : VectorViewModel<RoomListViewState, RoomListAction, RoomListViewEvents>(initialState) {
@AssistedFactory
@ -223,6 +226,7 @@ class RoomListViewModel @AssistedInject constructor(
viewModelScope.launch {
try {
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.
// Instead, we wait for the room to be joined
} 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.VectorBaseActivity
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.login.terms.LoginTermsFragment
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 initUiAndData() {
analyticsScreenName = Screen.ScreenName.Login
if (isFirstCreation()) {
addFirstFragment()
}
@ -200,6 +203,10 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), ToolbarCo
private fun updateWithState(loginViewState: LoginViewState) {
if (loginViewState.isUserLogged()) {
if (loginViewState.signMode == SignMode.SignUp) {
// change the screen name
analyticsScreenName = Screen.ScreenName.Register
}
val intent = HomeActivity.newIntent(
this,
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.toReducedUrl
import im.vector.app.databinding.FragmentLoginResetPasswordBinding
import im.vector.app.features.analytics.plan.Screen
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
@ -46,6 +47,11 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment<F
// Show warning only once
private var showWarning = true
override fun onCreate(savedInstanceState: Bundle?) {
analyticsScreenName = Screen.ScreenName.ForgotPassword
super.onCreate(savedInstanceState)
}
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginResetPasswordBinding {
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.R
import im.vector.app.databinding.FragmentLoginSplashBinding
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.failure.Failure
import java.net.UnknownHostException
@ -42,6 +43,11 @@ class LoginSplashFragment @Inject constructor(
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?) {
super.onViewCreated(view, savedInstanceState)

View file

@ -23,6 +23,8 @@ import androidx.core.app.RemoteInput
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
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 kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
@ -41,6 +43,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var analyticsTracker: AnalyticsTracker
override fun onReceive(context: Context?, intent: Intent?) {
if (intent == null || context == null) return
@ -79,7 +82,10 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
val room = session.getRoom(roomId)
if (room != null) {
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) {
Timber.v("PublicRoomJoinClicked: $publicRoom")
viewModel.handle(RoomDirectoryAction.JoinRoom(publicRoom.roomId))
viewModel.handle(RoomDirectoryAction.JoinRoom(publicRoom))
}
override fun loadMore() {

View file

@ -17,10 +17,11 @@
package im.vector.app.features.roomdirectory
import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
sealed class RoomDirectoryAction : VectorViewModelAction {
data class SetRoomDirectoryData(val roomDirectoryData: RoomDirectoryData) : RoomDirectoryAction()
data class FilterWith(val filter: String) : 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.platform.VectorBaseActivity
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.navigation.Navigator
import im.vector.app.features.roomdirectory.createroom.CreateRoomArgs
@ -50,6 +51,7 @@ class RoomDirectoryActivity : VectorBaseActivity<ActivitySimpleBinding>(), Matri
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.RoomDirectory
sharedActionViewModel = viewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
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.hiltMavericksViewModelFactory
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 kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
@ -45,6 +47,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(
@Assisted initialState: PublicRoomsViewState,
vectorPreferences: VectorPreferences,
private val session: Session,
private val analyticsTracker: AnalyticsTracker,
private val explicitTermFilter: ExplicitTermFilter
) : VectorViewModel<PublicRoomsViewState, RoomDirectoryAction, RoomDirectoryViewEvents>(initialState) {
@ -213,7 +216,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(
}
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()) {
// Request already sent, 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)
viewModelScope.launch {
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.
// Instead, we wait for the room to be joined
} 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.VectorBaseActivity
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.RoomDirectorySharedActionViewModel
import kotlinx.coroutines.flow.launchIn
@ -62,6 +63,7 @@ class CreateRoomActivity : VectorBaseActivity<ActivitySimpleBinding>(), ToolbarC
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.CreateRoom
sharedActionViewModel = viewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
sharedActionViewModel
.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.extensions.exhaustive
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.isE2EByDefault
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 timber.log.Timber
class CreateRoomViewModel @AssistedInject constructor(@Assisted private val initialState: CreateRoomViewState,
private val session: Session,
private val rawService: RawService,
appStateHandler: AppStateHandler
class CreateRoomViewModel @AssistedInject constructor(
@Assisted private val initialState: CreateRoomViewState,
private val session: Session,
private val rawService: RawService,
appStateHandler: AppStateHandler,
private val analyticsTracker: AnalyticsTracker
) : VectorViewModel<CreateRoomViewState, CreateRoomAction, CreateRoomViewEvents>(initialState) {
@AssistedFactory
@ -296,7 +300,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
viewModelScope.launch {
runCatching { session.createRoom(createRoomParams) }.fold(
{ roomId ->
analyticsTracker.capture(CreatedRoom(isDM = createRoomParams.isDirect.orFalse()))
if (state.parentSpaceId != null) {
// add it as a child
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.VectorBaseFragment
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.RoomDirectoryData
import im.vector.app.features.roomdirectory.RoomDirectoryServer
@ -52,6 +53,11 @@ class RoomDirectoryPickerFragment @Inject constructor(private val roomDirectoryP
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?) {
super.onViewCreated(view, savedInstanceState)

View file

@ -40,6 +40,7 @@ data class RoomPreviewData(
val roomAlias: String? = null,
val roomType: String? = null,
val topic: String? = null,
val numJoinedMembers: Int? = null,
val worldReadable: Boolean = false,
val avatarUrl: String? = null,
val homeServers: List<String> = emptyList(),
@ -69,6 +70,7 @@ class RoomPreviewActivity : VectorBaseActivity<ActivitySimpleBinding>(), Toolbar
roomName = publicRoom.name,
roomAlias = publicRoom.getPrimaryAlias(),
topic = publicRoom.topic,
numJoinedMembers = publicRoom.numJoinedMembers,
worldReadable = publicRoom.worldReadable,
avatarUrl = publicRoom.avatarUrl,
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.platform.EmptyViewEvents
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 kotlinx.coroutines.Dispatchers
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 timber.log.Timber
class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val initialState: RoomPreviewViewState,
private val session: Session) :
VectorViewModel<RoomPreviewViewState, RoomPreviewAction, EmptyViewEvents>(initialState) {
class RoomPreviewViewModel @AssistedInject constructor(
@Assisted private val initialState: RoomPreviewViewState,
private val analyticsTracker: AnalyticsTracker,
private val session: Session
) : VectorViewModel<RoomPreviewViewState, RoomPreviewAction, EmptyViewEvents>(initialState) {
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<RoomPreviewViewModel, RoomPreviewViewState> {
@ -243,6 +248,11 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
viewModelScope.launch {
try {
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.
// Instead, we wait for the room to be joined
} catch (failure: Throwable) {

View file

@ -33,6 +33,7 @@ data class RoomPreviewViewState(
val roomName: String? = null,
val roomTopic: String? = null,
val numJoinMembers: Int? = null,
val avatarUrl: String? = null,
val shouldPeekFromServer: Boolean = false,
@ -56,6 +57,7 @@ data class RoomPreviewViewState(
homeServers = args.homeServers,
roomName = args.roomName,
roomTopic = args.topic,
numJoinMembers = args.numJoinedMembers,
avatarUrl = args.avatarUrl,
shouldPeekFromServer = args.peekFromServer,
fromEmailInvite = args.fromEmailInvite,
@ -64,6 +66,6 @@ data class RoomPreviewViewState(
fun matrixItem(): MatrixItem {
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.FragmentMatrixProfileBinding
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.displayname.getBestName
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 onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.User
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
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.databinding.FragmentMatrixProfileBinding
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.room.detail.RoomDetailPendingAction
import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
@ -88,6 +89,7 @@ class RoomProfileFragment @Inject constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.RoomSettings
setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY) { _, bundle ->
bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId ->
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.shareMedia
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.notifications.NotificationUtils
import im.vector.app.features.roomprofile.RoomProfileArgs
@ -54,6 +55,11 @@ class RoomUploadsFragment @Inject constructor(
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?) {
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.platform.VectorBaseActivity
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.onEach
import org.matrix.android.sdk.api.session.Session
@ -37,6 +39,18 @@ import reactivecircus.flowbinding.android.view.clicks
import timber.log.Timber
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 {
activity as VectorBaseActivity<*>
@ -47,7 +61,6 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick
// members
protected lateinit var session: Session
protected lateinit var errorFormatter: ErrorFormatter
protected lateinit var analytics: VectorAnalytics
/* ==========================================================================================
* Views
@ -72,17 +85,23 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick
super.onAttach(context)
session = singletonEntryPoint.activeSessionHolder().getActiveSession()
errorFormatter = singletonEntryPoint.errorFormatter()
analytics = singletonEntryPoint.analytics()
analyticsTracker = singletonEntryPoint.analyticsTracker()
}
override fun onResume() {
super.onResume()
Timber.i("onResume Fragment ${javaClass.simpleName}")
screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
vectorActivity.supportActionBar?.setTitle(titleRes)
// find the view from parent activity
mLoadingView = vectorActivity.findViewById(R.id.vector_settings_spinner_views)
}
override fun onPause() {
super.onPause()
screenEvent?.send(analyticsTracker)
}
abstract fun bindPref()
abstract var titleRes: Int

View file

@ -16,8 +16,10 @@
package im.vector.app.features.settings
import android.os.Bundle
import im.vector.app.R
import im.vector.app.core.preference.VectorPreference
import im.vector.app.features.analytics.plan.Screen
import javax.inject.Inject
class VectorSettingsRootFragment @Inject constructor() : VectorSettingsBaseFragment() {
@ -25,6 +27,11 @@ class VectorSettingsRootFragment @Inject constructor() : VectorSettingsBaseFragm
override var titleRes: Int = R.string.title_activity_settings
override val preferenceXmlRes = R.xml.vector_settings_root
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.Settings
}
override fun bindPref() {
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.toast
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.AnalyticsConsentViewModel
import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewState
@ -91,6 +92,11 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
private val analyticsConsentViewModel: AnalyticsConsentViewModel by fragmentViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.SettingsSecurity
}
// cryptography
private val mCryptographyCategory by lazy {
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.features.MainActivity
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.settings.VectorSettingsActivity
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
@ -47,7 +48,7 @@ class DeactivateAccountFragment @Inject constructor() : VectorBaseFragment<Fragm
private val reAuthActivityResultLauncher = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
when (activityResult.data?.extras?.getString(ReAuthActivity.RESULT_FLOW_TYPE)) {
LoginFlowTypes.SSO -> {
LoginFlowTypes.SSO -> {
viewModel.handle(DeactivateAccountAction.SsoAuthDone)
}
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() {
super.onResume()
(activity as? AppCompatActivity)?.supportActionBar?.setTitle(R.string.deactivate_account_title)

View file

@ -16,8 +16,10 @@
package im.vector.app.features.settings.notifications
import android.os.Bundle
import im.vector.app.R
import im.vector.app.core.preference.VectorPreferenceCategory
import im.vector.app.features.analytics.plan.Screen
import org.matrix.android.sdk.api.pushrules.RuleIds
class VectorSettingsDefaultNotificationPreferenceFragment :
@ -34,6 +36,11 @@ class VectorSettingsDefaultNotificationPreferenceFragment :
"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() {
super.bindPref()
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.VectorPreference
import im.vector.app.core.preference.VectorPreferenceCategory
import im.vector.app.features.analytics.plan.Screen
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
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
class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment :
VectorSettingsPushRuleNotificationPreferenceFragment() {
VectorSettingsPushRuleNotificationPreferenceFragment() {
override var titleRes: Int = R.string.settings_notification_mentions_and_keywords
@ -42,6 +43,11 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment :
private var keywordsHasFocus = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = Screen.ScreenName.SettingsMentionsAndKeywords
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
session.getKeywords().observe(viewLifecycleOwner, this::updateWithKeywords)
@ -71,7 +77,7 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment :
val keywords = editKeywordPreference.keywords
val newChecked = newValue as Boolean
displayLoadingView()
updateKeywordPushRules(keywords, newChecked) { result ->
updateKeywordPushRules(keywords, newChecked) { result ->
hideLoadingView()
if (!isAdded) {
return@updateKeywordPushRules
@ -88,7 +94,7 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment :
false
}
editKeywordPreference.listener = object : KeywordPreference.Listener {
editKeywordPreference.listener = object : KeywordPreference.Listener {
override fun onFocusDidChange(hasFocus: Boolean) {
keywordsHasFocus = true
}
@ -174,8 +180,8 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment :
}
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_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_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_MESSAGES_CONTAINING_AT_ROOM_PREFERENCE_KEY" to RuleIds.RULE_ID_ROOM_NOTIF
)
}