Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/aris/threads analytics #5378

Merged
merged 9 commits into from
Mar 16, 2022
1 change: 1 addition & 0 deletions changelog.d/5378.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add analytics support for threads
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.matrix.android.sdk.api.session.room.timeline

import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.RelationType
Expand Down Expand Up @@ -159,6 +160,13 @@ fun TimelineEvent.isSticker(): Boolean {
return root.isSticker()
}

/**
* Returns whether or not the event is a root thread event
*/
fun TimelineEvent.isRootThread(): Boolean {
return root.threadDetails?.isRootThread.orFalse()
}

/**
* Get the latest message body, after a possible edition, stripping the reply prefix if necessary
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.app.features.analytics.extensions

import im.vector.app.features.analytics.plan.Composer
import im.vector.app.features.home.room.detail.composer.MessageComposerViewState
import im.vector.app.features.home.room.detail.composer.SendMode

fun MessageComposerViewState.toAnalyticsComposer(): Composer =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tiny formatting comment (ideally the formatter would do this for us), the kotlin convention is typically to move the closing ) to a new line

fun MessageComposerViewState.toAnalyticsComposer() = Composer(
    inThread = isInThreadTimeline(),
    isEditing = sendMode is SendMode.Edit,
    isReply = sendMode is SendMode.Reply,
    startsThread = startsThread
)

same for https://github.com/vector-im/element-android/pull/5378/files#diff-5f35fe0e05f4f8d301892d428e93ca87334bd099a0821f068ea5f5ecfc90adcaR21

Composer(
inThread = isInThreadTimeline(),
isEditing = sendMode is SendMode.Edit,
isReply = sendMode is SendMode.Reply,
startsThread = startsThread
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.app.features.analytics.extensions

import im.vector.app.features.analytics.plan.Interaction

fun Interaction.Name.toAnalyticsInteraction(interactionType: Interaction.InteractionType = Interaction.InteractionType.Touch) =
Interaction(
name = this,
interactionType = interactionType
)
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,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.FragmentTimelineBinding
import im.vector.app.features.analytics.plan.Composer
import im.vector.app.features.analytics.extensions.toAnalyticsInteraction
import im.vector.app.features.analytics.plan.Interaction
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.attachments.AttachmentTypeSelectorView
import im.vector.app.features.attachments.AttachmentsHelper
Expand Down Expand Up @@ -1491,9 +1492,6 @@ class TimelineFragment @Inject constructor(
return
}
if (text.isNotBlank()) {
withState(messageComposerViewModel) { state ->
analyticsTracker.capture(Composer(isThreadTimeLine(), isEditing = state.sendMode is SendMode.Edit, isReply = state.sendMode is SendMode.Reply))
}
// We collapse ASAP, if not there will be a slight annoying delay
views.composerLayout.collapse(true)
lockSendButton = true
Expand Down Expand Up @@ -2174,7 +2172,7 @@ class TimelineFragment @Inject constructor(
}
is EventSharedAction.ReplyInThread -> {
if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) {
navigateToThreadTimeline(action.eventId)
navigateToThreadTimeline(action.eventId, action.startsThread)
} else {
requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit)
}
Expand Down Expand Up @@ -2333,9 +2331,11 @@ class TimelineFragment @Inject constructor(
* using the ThreadsActivity
*/

private fun navigateToThreadTimeline(rootThreadEventId: String) {
private fun navigateToThreadTimeline(rootThreadEventId: String, startsThread: Boolean = false) {
analyticsTracker.capture(Interaction.Name.MobileRoomThreadSummaryItem.toAnalyticsInteraction())
context?.let {
val roomThreadDetailArgs = ThreadTimelineArgs(
startsThread = startsThread,
roomId = timelineArgs.roomId,
displayName = timelineViewModel.getRoomSummary()?.displayName,
avatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl,
Expand All @@ -2351,6 +2351,7 @@ class TimelineFragment @Inject constructor(
*/

private fun navigateToThreadList() {
analyticsTracker.capture(Interaction.Name.MobileRoomThreadListButton.toAnalyticsInteraction())
context?.let {
val roomThreadDetailArgs = ThreadTimelineArgs(
roomId = timelineArgs.roomId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ 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.toAnalyticsComposer
import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom
import im.vector.app.features.attachments.toContentAttachmentData
import im.vector.app.features.command.CommandParser
Expand Down Expand Up @@ -188,6 +189,9 @@ class MessageComposerViewModel @AssistedInject constructor(

private fun handleSendMessage(action: MessageComposerAction.SendMessage) {
withState { state ->
analyticsTracker.capture(state.toAnalyticsComposer()).also {
setState { copy(startsThread = false) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if there's a way to avoid this mutable state but I guess we need to be sure we only track the first message

}
when (state.sendMode) {
is SendMode.Regular -> {
when (val slashCommandResult = commandParser.parseSlashCommand(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.composer
import com.airbnb.mvrx.MavericksState
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent

/**
Expand Down Expand Up @@ -62,6 +63,7 @@ data class MessageComposerViewState(
val canSendMessage: CanSendStatus = CanSendStatus.Allowed,
val isSendButtonVisible: Boolean = false,
val rootThreadEventId: String? = null,
val startsThread: Boolean = false,
val sendMode: SendMode = SendMode.Regular("", false),
val voiceRecordingUiState: VoiceMessageRecorderView.RecordingUiState = VoiceMessageRecorderView.RecordingUiState.Idle
) : MavericksState {
Expand All @@ -80,6 +82,7 @@ data class MessageComposerViewState(

constructor(args: TimelineArgs) : this(
roomId = args.roomId,
startsThread = args.threadTimelineArgs?.startsThread.orFalse(),
rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId)

fun isInThreadTimeline(): Boolean = rootThreadEventId != null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ sealed class EventSharedAction(@StringRes val titleRes: Int,
data class Reply(val eventId: String) :
EventSharedAction(R.string.reply, R.drawable.ic_reply)

data class ReplyInThread(val eventId: String) :
data class ReplyInThread(val eventId: String, val startsThread: Boolean) :
EventSharedAction(R.string.reply_in_thread, R.drawable.ic_reply_in_thread)

object ViewInRoom :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited
import org.matrix.android.sdk.api.session.room.timeline.isPoll
import org.matrix.android.sdk.api.session.room.timeline.isRootThread
import org.matrix.android.sdk.api.session.room.timeline.isSticker
import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.flow.unwrap
Expand Down Expand Up @@ -329,7 +330,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
}

if (canReplyInThread(timelineEvent, messageContent, actionPermissions)) {
add(EventSharedAction.ReplyInThread(eventId))
add(EventSharedAction.ReplyInThread(eventId, !timelineEvent.isRootThread()))
}

if (canViewInRoom(timelineEvent, messageContent, actionPermissions)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ class MessageItemFactory @Inject constructor(

if (event.root.isRedacted()) {
// message is redacted
val attributes = messageItemAttributesFactory.create(null, informationData, callback, params.reactionsSummaryEvents)
val attributes = messageItemAttributesFactory.create(null, informationData, callback, params.reactionsSummaryEvents, threadDetails)
return buildRedactedItem(attributes, highlight)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityThreadsBinding
import im.vector.app.features.analytics.extensions.toAnalyticsInteraction
import im.vector.app.features.analytics.plan.Interaction
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.TimelineFragment
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
Expand Down Expand Up @@ -92,6 +94,7 @@ class ThreadsActivity : VectorBaseActivity<ActivityThreadsBinding>() {
* One usage of that is from the Threads Activity
*/
fun navigateToThreadTimeline(threadTimelineArgs: ThreadTimelineArgs) {
analyticsTracker.capture(Interaction.Name.MobileThreadListThreadItem.toAnalyticsInteraction())
val commonOption: (FragmentTransaction) -> Unit = {
it.setCustomAnimations(
R.anim.animation_slide_in_right,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ data class ThreadTimelineArgs(
val displayName: String?,
val avatarUrl: String?,
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel?,
val rootThreadEventId: String? = null
val rootThreadEventId: String? = null,
val startsThread: Boolean = false
) : Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import dagger.assisted.AssistedInject
import im.vector.app.core.platform.EmptyAction
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.toAnalyticsInteraction
import im.vector.app.features.analytics.plan.Interaction
import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
Expand All @@ -34,6 +37,7 @@ import org.matrix.android.sdk.api.session.threads.ThreadTimelineEvent
import org.matrix.android.sdk.flow.flow

class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState: ThreadListViewState,
private val analyticsTracker: AnalyticsTracker,
private val session: Session) :
VectorViewModel<ThreadListViewState, EmptyAction, EmptyViewEvents>(initialState) {

Expand Down Expand Up @@ -113,9 +117,10 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState
}
}

fun canHomeserverUseThreading() = session.getHomeServerCapabilities().canUseThreading
fun canHomeserverUseThreading() = session.getHomeServerCapabilities().canUseThreading

fun applyFiltering(shouldFilterThreads: Boolean) {
analyticsTracker.capture(Interaction.Name.MobileThreadListFilterItem.toAnalyticsInteraction())
setState {
copy(shouldFilterThreads = shouldFilterThreads)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentThreadListBinding
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.animation.TimelineItemAnimator
import im.vector.app.features.home.room.threads.ThreadsActivity
Expand Down Expand Up @@ -62,6 +63,7 @@ class ThreadListFragment @Inject constructor(

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
analyticsScreenName = MobileScreen.ScreenName.ThreadList
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
Expand Down