Pinned messages analytics : branch analytics

This commit is contained in:
ganfra 2024-09-23 16:11:08 +02:00
parent 3cd7979d6d
commit fb694c50fe
9 changed files with 98 additions and 22 deletions

View file

@ -24,6 +24,7 @@ import androidx.compose.runtime.setValue
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import im.vector.app.features.analytics.plan.PinUnpinAction
import io.element.android.appconfig.MessageComposerConfig import io.element.android.appconfig.MessageComposerConfig
import io.element.android.features.messages.api.timeline.HtmlConverterProvider import io.element.android.features.messages.api.timeline.HtmlConverterProvider
import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListEvents
@ -77,6 +78,7 @@ import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.room.canCall import io.element.android.libraries.matrix.ui.room.canCall
import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -104,6 +106,7 @@ class MessagesPresenter @AssistedInject constructor(
private val buildMeta: BuildMeta, private val buildMeta: BuildMeta,
private val timelineController: TimelineController, private val timelineController: TimelineController,
private val permalinkParser: PermalinkParser, private val permalinkParser: PermalinkParser,
private val analyticsService: AnalyticsService,
) : Presenter<MessagesState> { ) : Presenter<MessagesState> {
private val timelinePresenter = timelinePresenterFactory.create(navigator = navigator) private val timelinePresenter = timelinePresenterFactory.create(navigator = navigator)
private val actionListPresenter = actionListPresenterFactory.create(TimelineItemActionPostProcessor.Default) private val actionListPresenter = actionListPresenterFactory.create(TimelineItemActionPostProcessor.Default)
@ -285,6 +288,12 @@ class MessagesPresenter @AssistedInject constructor(
private suspend fun handlePinAction(targetEvent: TimelineItem.Event) { private suspend fun handlePinAction(targetEvent: TimelineItem.Event) {
if (targetEvent.eventId == null) return if (targetEvent.eventId == null) return
analyticsService.capture(
PinUnpinAction(
from = PinUnpinAction.From.Timeline,
kind = PinUnpinAction.Kind.Pin,
)
)
timelineController.invokeOnCurrentTimeline { timelineController.invokeOnCurrentTimeline {
pinEvent(targetEvent.eventId) pinEvent(targetEvent.eventId)
.onFailure { .onFailure {
@ -296,6 +305,12 @@ class MessagesPresenter @AssistedInject constructor(
private suspend fun handleUnpinAction(targetEvent: TimelineItem.Event) { private suspend fun handleUnpinAction(targetEvent: TimelineItem.Event) {
if (targetEvent.eventId == null) return if (targetEvent.eventId == null) return
analyticsService.capture(
PinUnpinAction(
from = PinUnpinAction.From.Timeline,
kind = PinUnpinAction.Kind.Unpin,
)
)
timelineController.invokeOnCurrentTimeline { timelineController.invokeOnCurrentTimeline {
unpinEvent(targetEvent.eventId) unpinEvent(targetEvent.eventId)
.onFailure { .onFailure {

View file

@ -39,6 +39,7 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import im.vector.app.features.analytics.plan.Interaction
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
@ -51,6 +52,8 @@ import io.element.android.libraries.designsystem.theme.pinnedMessageBannerIndica
import io.element.android.libraries.designsystem.utils.annotatedTextWithBold import io.element.android.libraries.designsystem.utils.annotatedTextWithBold
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.analytics.compose.LocalAnalyticsService
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
@Composable @Composable
fun PinnedMessagesBannerView( fun PinnedMessagesBannerView(
@ -79,6 +82,7 @@ private fun PinnedMessagesBannerRow(
onViewAllClick: () -> Unit, onViewAllClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val analyticsService = LocalAnalyticsService.current
val borderColor = ElementTheme.colors.pinnedMessageBannerBorder val borderColor = ElementTheme.colors.pinnedMessageBannerBorder
Row( Row(
modifier = modifier modifier = modifier
@ -88,6 +92,7 @@ private fun PinnedMessagesBannerRow(
.heightIn(min = 64.dp) .heightIn(min = 64.dp)
.clickable { .clickable {
if (state is PinnedMessagesBannerState.Loaded) { if (state is PinnedMessagesBannerState.Loaded) {
analyticsService.captureInteraction(Interaction.Name.PinnedMessageBannerClick)
onClick(state.currentPinnedMessage.eventId) onClick(state.currentPinnedMessage.eventId)
state.eventSink(PinnedMessagesBannerEvents.MoveToNextPinned) state.eventSink(PinnedMessagesBannerEvents.MoveToNextPinned)
} }
@ -112,7 +117,13 @@ private fun PinnedMessagesBannerRow(
message = state.formattedMessage(), message = state.formattedMessage(),
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
ViewAllButton(state, onViewAllClick) ViewAllButton(
state = state,
onViewAllClick = {
onViewAllClick()
analyticsService.captureInteraction(Interaction.Name.PinnedMessageBannerViewAllButton)
},
)
} }
} }

View file

@ -20,6 +20,8 @@ import androidx.compose.runtime.setValue
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import im.vector.app.features.analytics.plan.Interaction
import im.vector.app.features.analytics.plan.PinUnpinAction
import io.element.android.features.messages.impl.UserEventPermissions import io.element.android.features.messages.impl.UserEventPermissions
import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.ActionListPresenter
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
@ -39,6 +41,8 @@ import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn
import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.api.room.roomMembers
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
@ -58,6 +62,7 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
private val snackbarDispatcher: SnackbarDispatcher, private val snackbarDispatcher: SnackbarDispatcher,
actionListPresenterFactory: ActionListPresenter.Factory, actionListPresenterFactory: ActionListPresenter.Factory,
private val appCoroutineScope: CoroutineScope, private val appCoroutineScope: CoroutineScope,
private val analyticsService: AnalyticsService,
) : Presenter<PinnedMessagesListState> { ) : Presenter<PinnedMessagesListState> {
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
@ -128,6 +133,7 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
TimelineItemAction.Unpin -> handleUnpinAction(targetEvent) TimelineItemAction.Unpin -> handleUnpinAction(targetEvent)
TimelineItemAction.ViewInTimeline -> { TimelineItemAction.ViewInTimeline -> {
targetEvent.eventId?.let { eventId -> targetEvent.eventId?.let { eventId ->
analyticsService.captureInteraction(Interaction.Name.PinnedMessageListViewTimeline)
navigator.onViewInTimelineClick(eventId) navigator.onViewInTimelineClick(eventId)
} }
} }
@ -137,6 +143,12 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
private suspend fun handleUnpinAction(targetEvent: TimelineItem.Event) { private suspend fun handleUnpinAction(targetEvent: TimelineItem.Event) {
if (targetEvent.eventId == null) return if (targetEvent.eventId == null) return
analyticsService.capture(
PinUnpinAction(
from = PinUnpinAction.From.MessagePinningList,
kind = PinUnpinAction.Kind.Unpin,
)
)
timelineProvider.invokeOnTimeline { timelineProvider.invokeOnTimeline {
unpinEvent(targetEvent.eventId) unpinEvent(targetEvent.eventId)
.onFailure { .onFailure {

View file

@ -22,6 +22,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import im.vector.app.features.analytics.plan.Interaction
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListEvents
import io.element.android.features.messages.impl.actionlist.ActionListView import io.element.android.features.messages.impl.actionlist.ActionListView
@ -44,6 +45,8 @@ import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.analytics.compose.LocalAnalyticsService
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
@Composable @Composable
fun PinnedMessagesListView( fun PinnedMessagesListView(
@ -57,7 +60,14 @@ fun PinnedMessagesListView(
Scaffold( Scaffold(
modifier = modifier, modifier = modifier,
topBar = { topBar = {
PinnedMessagesListTopBar(state, onBackClick) val analyticsService = LocalAnalyticsService.current
PinnedMessagesListTopBar(
state = state,
onBackClick = {
analyticsService.captureInteraction(Interaction.Name.PinnedMessageBannerCloseListButton)
onBackClick()
}
)
}, },
content = { padding -> content = { padding ->
PinnedMessagesListContent( PinnedMessagesListContent(
@ -67,8 +77,8 @@ fun PinnedMessagesListView(
onLinkClick = onLinkClick, onLinkClick = onLinkClick,
onErrorDismiss = onBackClick, onErrorDismiss = onBackClick,
modifier = Modifier modifier = Modifier
.padding(padding) .padding(padding)
.consumeWindowInsets(padding), .consumeWindowInsets(padding),
) )
} }
) )

View file

@ -13,6 +13,7 @@ import app.cash.molecule.moleculeFlow
import app.cash.turbine.ReceiveTurbine import app.cash.turbine.ReceiveTurbine
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.PinUnpinAction
import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.ActionListState
import io.element.android.features.messages.impl.actionlist.FakeActionListPresenter import io.element.android.features.messages.impl.actionlist.FakeActionListPresenter
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
@ -896,6 +897,7 @@ class MessagesPresenterTest {
fun `present - handle action pin`() = runTest { fun `present - handle action pin`() = runTest {
val successPinEventLambda = lambdaRecorder { _: EventId -> Result.success(true) } val successPinEventLambda = lambdaRecorder { _: EventId -> Result.success(true) }
val failurePinEventLambda = lambdaRecorder { _: EventId -> Result.failure<Boolean>(A_THROWABLE) } val failurePinEventLambda = lambdaRecorder { _: EventId -> Result.failure<Boolean>(A_THROWABLE) }
val analyticsService = FakeAnalyticsService()
val timeline = FakeTimeline() val timeline = FakeTimeline()
val room = FakeMatrixRoom( val room = FakeMatrixRoom(
liveTimeline = timeline, liveTimeline = timeline,
@ -906,7 +908,7 @@ class MessagesPresenterTest {
typingNoticeResult = { Result.success(Unit) }, typingNoticeResult = { Result.success(Unit) },
canUserPinUnpinResult = { Result.success(true) }, canUserPinUnpinResult = { Result.success(true) },
) )
val presenter = createMessagesPresenter(matrixRoom = room) val presenter = createMessagesPresenter(matrixRoom = room, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -923,6 +925,10 @@ class MessagesPresenterTest {
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Pin, messageEvent)) initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Pin, messageEvent))
assert(failurePinEventLambda).isCalledOnce().with(value(messageEvent.eventId)) assert(failurePinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
assertThat(awaitItem().snackbarMessage).isNotNull() assertThat(awaitItem().snackbarMessage).isNotNull()
assertThat(analyticsService.capturedEvents).containsExactly(
PinUnpinAction(kind = PinUnpinAction.Kind.Pin, from = PinUnpinAction.From.Timeline),
PinUnpinAction(kind = PinUnpinAction.Kind.Pin, from = PinUnpinAction.From.Timeline)
)
} }
} }
@ -931,6 +937,7 @@ class MessagesPresenterTest {
val successUnpinEventLambda = lambdaRecorder { _: EventId -> Result.success(true) } val successUnpinEventLambda = lambdaRecorder { _: EventId -> Result.success(true) }
val failureUnpinEventLambda = lambdaRecorder { _: EventId -> Result.failure<Boolean>(A_THROWABLE) } val failureUnpinEventLambda = lambdaRecorder { _: EventId -> Result.failure<Boolean>(A_THROWABLE) }
val timeline = FakeTimeline() val timeline = FakeTimeline()
val analyticsService = FakeAnalyticsService()
val room = FakeMatrixRoom( val room = FakeMatrixRoom(
liveTimeline = timeline, liveTimeline = timeline,
canUserSendMessageResult = { _, _ -> Result.success(true) }, canUserSendMessageResult = { _, _ -> Result.success(true) },
@ -940,7 +947,7 @@ class MessagesPresenterTest {
typingNoticeResult = { Result.success(Unit) }, typingNoticeResult = { Result.success(Unit) },
canUserPinUnpinResult = { Result.success(true) }, canUserPinUnpinResult = { Result.success(true) },
) )
val presenter = createMessagesPresenter(matrixRoom = room) val presenter = createMessagesPresenter(matrixRoom = room, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -957,6 +964,10 @@ class MessagesPresenterTest {
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Unpin, messageEvent)) initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Unpin, messageEvent))
assert(failureUnpinEventLambda).isCalledOnce().with(value(messageEvent.eventId)) assert(failureUnpinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
assertThat(awaitItem().snackbarMessage).isNotNull() assertThat(awaitItem().snackbarMessage).isNotNull()
assertThat(analyticsService.capturedEvents).containsExactly(
PinUnpinAction(kind = PinUnpinAction.Kind.Unpin, from = PinUnpinAction.From.Timeline),
PinUnpinAction(kind = PinUnpinAction.Kind.Unpin, from = PinUnpinAction.From.Timeline)
)
} }
} }
@ -1074,6 +1085,7 @@ class MessagesPresenterTest {
htmlConverterProvider = FakeHtmlConverterProvider(), htmlConverterProvider = FakeHtmlConverterProvider(),
timelineController = TimelineController(matrixRoom), timelineController = TimelineController(matrixRoom),
permalinkParser = permalinkParser, permalinkParser = permalinkParser,
analyticsService = analyticsService,
) )
} }
} }

View file

@ -8,6 +8,7 @@
package io.element.android.features.messages.impl.pinned.list package io.element.android.features.messages.impl.pinned.list
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.PinUnpinAction
import io.element.android.features.messages.impl.actionlist.FakeActionListPresenter import io.element.android.features.messages.impl.actionlist.FakeActionListPresenter
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator
@ -30,6 +31,8 @@ import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.matrix.test.timeline.aMessageContent import io.element.android.libraries.matrix.test.timeline.aMessageContent
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.lambda.assert import io.element.android.tests.testutils.lambda.assert
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.lambda.value
@ -142,7 +145,7 @@ class PinnedMessagesListPresenterTest {
val successUnpinEventLambda = lambdaRecorder { _: EventId? -> Result.success(true) } val successUnpinEventLambda = lambdaRecorder { _: EventId? -> Result.success(true) }
val failureUnpinEventLambda = lambdaRecorder { _: EventId? -> Result.failure<Boolean>(A_THROWABLE) } val failureUnpinEventLambda = lambdaRecorder { _: EventId? -> Result.failure<Boolean>(A_THROWABLE) }
val pinnedEventsTimeline = createPinnedMessagesTimeline() val pinnedEventsTimeline = createPinnedMessagesTimeline()
val analyticsService = FakeAnalyticsService()
val room = FakeMatrixRoom( val room = FakeMatrixRoom(
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) }, pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) },
canRedactOwnResult = { Result.success(true) }, canRedactOwnResult = { Result.success(true) },
@ -151,7 +154,7 @@ class PinnedMessagesListPresenterTest {
).apply { ).apply {
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
} }
val presenter = createPinnedMessagesListPresenter(room = room, isFeatureEnabled = true) val presenter = createPinnedMessagesListPresenter(room = room, isFeatureEnabled = true, analyticsService = analyticsService)
presenter.test { presenter.test {
skipItems(3) skipItems(3)
val filledState = awaitItem() as PinnedMessagesListState.Filled val filledState = awaitItem() as PinnedMessagesListState.Filled
@ -174,6 +177,11 @@ class PinnedMessagesListPresenterTest {
assert(failureUnpinEventLambda) assert(failureUnpinEventLambda)
.isCalledOnce() .isCalledOnce()
.with(value(AN_EVENT_ID)) .with(value(AN_EVENT_ID))
assertThat(analyticsService.capturedEvents).containsExactly(
PinUnpinAction(kind = PinUnpinAction.Kind.Unpin, from = PinUnpinAction.From.MessagePinningList),
PinUnpinAction(kind = PinUnpinAction.Kind.Unpin, from = PinUnpinAction.From.MessagePinningList)
)
} }
} }
@ -286,6 +294,7 @@ class PinnedMessagesListPresenterTest {
room: MatrixRoom = FakeMatrixRoom(), room: MatrixRoom = FakeMatrixRoom(),
networkMonitor: NetworkMonitor = FakeNetworkMonitor(), networkMonitor: NetworkMonitor = FakeNetworkMonitor(),
isFeatureEnabled: Boolean = true, isFeatureEnabled: Boolean = true,
analyticsService: AnalyticsService = FakeAnalyticsService(),
): PinnedMessagesListPresenter { ): PinnedMessagesListPresenter {
val timelineProvider = PinnedEventsTimelineProvider( val timelineProvider = PinnedEventsTimelineProvider(
room = room, room = room,
@ -302,6 +311,7 @@ class PinnedMessagesListPresenterTest {
timelineProvider = timelineProvider, timelineProvider = timelineProvider,
snackbarDispatcher = SnackbarDispatcher(), snackbarDispatcher = SnackbarDispatcher(),
actionListPresenterFactory = FakeActionListPresenter.Factory, actionListPresenterFactory = FakeActionListPresenter.Factory,
analyticsService = analyticsService,
appCoroutineScope = this, appCoroutineScope = this,
) )
} }

View file

@ -50,7 +50,7 @@ dependencies {
implementation(projects.features.createroom.api) implementation(projects.features.createroom.api)
implementation(projects.features.leaveroom.api) implementation(projects.features.leaveroom.api)
implementation(projects.features.userprofile.shared) implementation(projects.features.userprofile.shared)
implementation(projects.services.analytics.api) implementation(projects.services.analytics.compose)
implementation(projects.features.poll.api) implementation(projects.features.poll.api)
implementation(projects.features.messages.api) implementation(projects.features.messages.api)

View file

@ -37,6 +37,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import im.vector.app.features.analytics.plan.Interaction
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.leaveroom.api.LeaveRoomView import io.element.android.features.leaveroom.api.LeaveRoomView
@ -80,6 +81,8 @@ import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.analytics.compose.LocalAnalyticsService
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentList
@ -111,9 +114,9 @@ fun RoomDetailsView(
) { padding -> ) { padding ->
Column( Column(
modifier = Modifier modifier = Modifier
.padding(padding) .padding(padding)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.consumeWindowInsets(padding) .consumeWindowInsets(padding)
) { ) {
LeaveRoomView(state = state.leaveRoomState) LeaveRoomView(state = state.leaveRoomState)
@ -270,8 +273,8 @@ private fun MainActionsSection(
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.SpaceEvenly, horizontalArrangement = Arrangement.SpaceEvenly,
) { ) {
val roomNotificationSettings = state.roomNotificationSettings val roomNotificationSettings = state.roomNotificationSettings
@ -330,8 +333,8 @@ private fun RoomHeaderSection(
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
CompositeAvatar( CompositeAvatar(
@ -340,8 +343,8 @@ private fun RoomHeaderSection(
user.getAvatarData(size = AvatarSize.RoomHeader) user.getAvatarData(size = AvatarSize.RoomHeader)
}.toPersistentList(), }.toPersistentList(),
modifier = Modifier modifier = Modifier
.clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) } .clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) }
.testTag(TestTags.roomDetailAvatar) .testTag(TestTags.roomDetailAvatar)
) )
TitleAndSubtitle(title = roomName, subtitle = roomAlias?.value) TitleAndSubtitle(title = roomName, subtitle = roomAlias?.value)
} }
@ -357,8 +360,8 @@ private fun DmHeaderSection(
) { ) {
Column( Column(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
DmAvatars( DmAvatars(
@ -509,6 +512,7 @@ private fun PinnedMessagesItem(
pinnedMessagesCount: Int?, pinnedMessagesCount: Int?,
onPinnedMessagesClick: () -> Unit, onPinnedMessagesClick: () -> Unit,
) { ) {
val analyticsService = LocalAnalyticsService.current
ListItem( ListItem(
headlineContent = { Text(stringResource(CommonStrings.screen_room_details_pinned_events_row_title)) }, headlineContent = { Text(stringResource(CommonStrings.screen_room_details_pinned_events_row_title)) },
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Pin())), leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Pin())),
@ -520,7 +524,10 @@ private fun PinnedMessagesItem(
} else { } else {
ListItemContent.Text(pinnedMessagesCount.toString()) ListItemContent.Text(pinnedMessagesCount.toString())
}, },
onClick = onPinnedMessagesClick, onClick = {
analyticsService.captureInteraction(Interaction.Name.PinnedMessageRoomInfoButton)
onPinnedMessagesClick()
}
) )
} }

View file

@ -10,7 +10,6 @@ plugins {
android { android {
namespace = "io.element.android.services.analytics.compose" namespace = "io.element.android.services.analytics.compose"
} }
dependencies { dependencies {
api(projects.services.analytics.api) api(projects.services.analytics.api)