From 5681d7c6117bf0a50f3dfe84e1d39c15073ec5b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 20 Nov 2025 09:04:36 +0100 Subject: [PATCH] Add a performance check for notification tap -> populated timeline time --- .../element/android/x/intent/DefaultIntentProvider.kt | 1 + .../kotlin/io/element/android/appnav/RootFlowNode.kt | 10 +++++++++- .../messages/impl/timeline/TimelinePresenter.kt | 4 ++++ .../messages/impl/timeline/TimelinePresenterTest.kt | 2 ++ .../analytics/api/AnalyticsLongRunningTransaction.kt | 1 + 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/io/element/android/x/intent/DefaultIntentProvider.kt b/app/src/main/kotlin/io/element/android/x/intent/DefaultIntentProvider.kt index 204ee27313..a4272063ba 100644 --- a/app/src/main/kotlin/io/element/android/x/intent/DefaultIntentProvider.kt +++ b/app/src/main/kotlin/io/element/android/x/intent/DefaultIntentProvider.kt @@ -36,6 +36,7 @@ class DefaultIntentProvider( return Intent(context, MainActivity::class.java).apply { action = Intent.ACTION_VIEW data = deepLinkCreator.create(sessionId, roomId, threadId, eventId).toUri() + putExtra("from_notification", true) } } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 1a23bce132..6fce57926b 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -62,6 +62,8 @@ import io.element.android.libraries.oidc.api.OidcActionFlow import io.element.android.libraries.sessionstorage.api.LoggedInState import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.ui.common.nodes.emptyNode +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction +import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -86,6 +88,7 @@ class RootFlowNode( private val oidcActionFlow: OidcActionFlow, private val featureFlagService: FeatureFlagService, private val announcementService: AnnouncementService, + private val analyticsService: AnalyticsService, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.SplashScreen, @@ -310,7 +313,12 @@ class RootFlowNode( suspend fun handleIntent(intent: Intent) { val resolvedIntent = intentResolver.resolve(intent) ?: return when (resolvedIntent) { - is ResolvedIntent.Navigation -> navigateTo(resolvedIntent.deeplinkData) + is ResolvedIntent.Navigation -> { + if (intent.getBooleanExtra("from_notification", false) && resolvedIntent.deeplinkData is DeeplinkData.Room) { + analyticsService.startLongRunningTransaction(AnalyticsLongRunningTransaction.NotificationTapOpensTimeline) + } + navigateTo(resolvedIntent.deeplinkData) + } is ResolvedIntent.Login -> onLoginLink(resolvedIntent.params) is ResolvedIntent.Oidc -> onOidcAction(resolvedIntent.oidcAction) is ResolvedIntent.Permalink -> navigateTo(resolvedIntent.permalinkData) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index 596c1d87e4..14b873491b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -55,6 +55,8 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import io.element.android.libraries.matrix.ui.room.canSendMessageAsState import io.element.android.libraries.preferences.api.store.SessionPreferencesStore +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction +import io.element.android.services.analytics.api.AnalyticsService import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope @@ -86,6 +88,7 @@ class TimelinePresenter( private val typingNotificationPresenter: Presenter, private val roomCallStatePresenter: Presenter, private val featureFlagService: FeatureFlagService, + private val analyticsService: AnalyticsService, ) : Presenter { private val tag = "TimelinePresenter" @AssistedFactory @@ -225,6 +228,7 @@ class TimelinePresenter( LaunchedEffect(Unit) { timelineItemsFactory.timelineItems .onEach { newTimelineItems -> + analyticsService.removeLongRunningTransaction(AnalyticsLongRunningTransaction.NotificationTapOpensTimeline)?.finish() timelineItemIndexer.process(newTimelineItems) timelineItems = newTimelineItems } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt index fbfce752ea..13c28da6e9 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt @@ -60,6 +60,7 @@ import io.element.android.libraries.matrix.test.timeline.aMessageContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore +import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.awaitLastSequentialItem import io.element.android.tests.testutils.consumeItemsUntilPredicate @@ -1054,6 +1055,7 @@ class TimelinePresenterTest { typingNotificationPresenter = { aTypingNotificationState() }, roomCallStatePresenter = { aStandByCallState() }, featureFlagService = featureFlagService, + analyticsService = FakeAnalyticsService(), ) } } diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsLongRunningTransaction.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsLongRunningTransaction.kt index c4aa5cc69b..96dfe4ba3c 100644 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsLongRunningTransaction.kt +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsLongRunningTransaction.kt @@ -13,4 +13,5 @@ sealed class AnalyticsLongRunningTransaction( ) { data object FirstRoomsDisplayed : AnalyticsLongRunningTransaction("First rooms displayed after login or restoration", null) data object ResumeAppUntilNewRoomsReceived : AnalyticsLongRunningTransaction("App was resumed and new room list items arrived", null) + data object NotificationTapOpensTimeline : AnalyticsLongRunningTransaction("A notification was tapped and it opened a timeline", null) }