From e59f2bba0a05b6962c8ebc40261af993331a2283 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 28 Feb 2022 17:13:06 +0200 Subject: [PATCH 01/50] Add analytics to threads --- .../session/room/timeline/TimelineEvent.kt | 8 ++ .../analytics/extensions/ComposerExt.kt | 28 ++++++ .../analytics/extensions/InteractionExt.kt | 24 +++++ .../app/features/analytics/plan/Composer.kt | 5 + .../features/analytics/plan/Interaction.kt | 95 +++++++++++++++++-- .../features/analytics/plan/MobileScreen.kt | 5 + .../home/room/detail/TimelineFragment.kt | 13 +-- .../composer/MessageComposerViewModel.kt | 4 + .../composer/MessageComposerViewState.kt | 3 + .../timeline/action/EventSharedAction.kt | 2 +- .../action/MessageActionsViewModel.kt | 3 +- .../home/room/threads/ThreadsActivity.kt | 3 + .../threads/arguments/ThreadTimelineArgs.kt | 3 +- .../list/viewmodel/ThreadListViewModel.kt | 7 +- .../threads/list/views/ThreadListFragment.kt | 2 + 15 files changed, 189 insertions(+), 16 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/analytics/extensions/ComposerExt.kt create mode 100644 vector/src/main/java/im/vector/app/features/analytics/extensions/InteractionExt.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index c03d0fd17b..d7796c8808 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -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 @@ -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 */ diff --git a/vector/src/main/java/im/vector/app/features/analytics/extensions/ComposerExt.kt b/vector/src/main/java/im/vector/app/features/analytics/extensions/ComposerExt.kt new file mode 100644 index 0000000000..80675ac57c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/analytics/extensions/ComposerExt.kt @@ -0,0 +1,28 @@ +/* + * 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 = + Composer( + inThread = isInThreadTimeline(), + isEditing = sendMode is SendMode.Edit, + isReply = sendMode is SendMode.Reply, + startsThread = startsThread) diff --git a/vector/src/main/java/im/vector/app/features/analytics/extensions/InteractionExt.kt b/vector/src/main/java/im/vector/app/features/analytics/extensions/InteractionExt.kt new file mode 100644 index 0000000000..c46230cdd1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/analytics/extensions/InteractionExt.kt @@ -0,0 +1,24 @@ +/* + * 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) diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/Composer.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/Composer.kt index a3b847a1bd..79be8aae2b 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/Composer.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/Composer.kt @@ -39,6 +39,10 @@ data class Composer( * sent event. */ val isReply: Boolean, + /** + * Whether this message begins a new thread or not. + */ + val startsThread: Boolean? = null, ) : VectorAnalyticsEvent { override fun getName() = "Composer" @@ -48,6 +52,7 @@ data class Composer( put("inThread", inThread) put("isEditing", isEditing) put("isReply", isReply) + startsThread?.let { put("startsThread", it) } }.takeIf { it.isNotEmpty() } } } diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt index 7bdc7740e1..2007f75fbc 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt @@ -40,6 +40,36 @@ data class Interaction( ) : VectorAnalyticsEvent { enum class Name { + /** + * User tapped on Add to Home button on Room Details screen. + */ + MobileRoomAddHome, + + /** + * User tapped on Leave Room button on Room Details screen. + */ + MobileRoomLeave, + + /** + * User tapped on Threads button on Room screen. + */ + MobileRoomThreadListButton, + + /** + * User tapped on a thread summary item on Room screen. + */ + MobileRoomThreadSummaryItem, + + /** + * User tapped on the filter button on ThreadList screen. + */ + MobileThreadListFilterItem, + + /** + * User selected a thread on ThreadList screen. + */ + MobileThreadListThreadItem, + /** * User tapped the already selected space from the space list. */ @@ -52,8 +82,8 @@ data class Interaction( SpacePanelSwitchSpace, /** - * User clicked the create room button in the + context menu of the room - * list header in Element Web/Desktop. + * User clicked the create room button in the add existing room to space + * dialog in Element Web/Desktop. */ WebAddExistingToSpaceDialogCreateRoomButton, @@ -105,12 +135,24 @@ data class Interaction( */ WebRightPanelRoomUserInfoInviteButton, + /** + * User clicked the threads 'show' filter dropdown in the threads panel + * in Element Web/Desktop. + */ + WebRightPanelThreadPanelFilterDropdown, + /** * User clicked the create room button in the room directory of Element * Web/Desktop. */ WebRoomDirectoryCreateRoomButton, + /** + * User clicked the Threads button in the top right of a room in Element + * Web/Desktop. + */ + WebRoomHeaderButtonsThreadsButton, + /** * User adjusted their favourites using the context menu on the header * of a room in Element Web/Desktop. @@ -153,6 +195,12 @@ data class Interaction( */ WebRoomListHeaderPlusMenuCreateRoomItem, + /** + * User clicked the explore rooms button in the + context menu of the + * room list header in Element Web/Desktop. + */ + WebRoomListHeaderPlusMenuExploreRoomsItem, + /** * User adjusted their favourites using the context menu on a room tile * in the room list in Element Web/Desktop. @@ -189,6 +237,12 @@ data class Interaction( */ WebRoomListRoomsSublistPlusMenuCreateRoomItem, + /** + * User clicked the explore rooms button in the + context menu of the + * rooms sublist in Element Web/Desktop. + */ + WebRoomListRoomsSublistPlusMenuExploreRoomsItem, + /** * User interacted with leave action in the general tab of the room * settings dialog in Element Web/Desktop. @@ -201,6 +255,12 @@ data class Interaction( */ WebRoomSettingsSecurityTabCreateNewRoomButton, + /** + * User clicked a thread summary in the timeline of a room in Element + * Web/Desktop. + */ + WebRoomTimelineThreadSummaryButton, + /** * User interacted with the theme radio selector in the Appearance tab * of Settings in Element Web/Desktop. @@ -214,17 +274,40 @@ data class Interaction( WebSettingsSidebarTabSpacesCheckbox, /** - * User clicked the create room button in the + context menu of the room - * list header in Element Web/Desktop. + * User clicked the explore rooms button in the context menu of a space + * in Element Web/Desktop. + */ + WebSpaceContextMenuExploreRoomsItem, + + /** + * User clicked the home button in the context menu of a space in + * Element Web/Desktop. + */ + WebSpaceContextMenuHomeItem, + + /** + * User clicked the new room button in the context menu of a space in + * Element Web/Desktop. */ WebSpaceContextMenuNewRoomItem, /** - * User clicked the create room button in the + context menu of the room - * list header in Element Web/Desktop. + * User clicked the new room button in the context menu on the space + * home in Element Web/Desktop. */ WebSpaceHomeCreateRoomButton, + /** + * User clicked the back button on a Thread view going back to the + * Threads Panel of Element Web/Desktop. + */ + WebThreadViewBackButton, + + /** + * User selected a thread in the Threads panel in Element Web/Desktop. + */ + WebThreadsPanelThreadItem, + /** * User clicked the theme toggle button in the user menu of Element * Web/Desktop. diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt index 758a0540bf..33976cb4cc 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt @@ -225,6 +225,11 @@ data class MobileScreen( */ SwitchDirectory, + /** + * Screen that displays list of threads for a room + */ + ThreadList, + /** * A screen that shows information about a room member. */ diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 1a40018526..67fb595378 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -119,7 +119,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 @@ -1505,9 +1506,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 @@ -2204,7 +2202,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) } @@ -2363,9 +2361,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, @@ -2381,6 +2381,7 @@ class TimelineFragment @Inject constructor( */ private fun navigateToThreadList() { + analyticsTracker.capture(Interaction.Name.MobileRoomThreadListButton.toAnalyticsInteraction()) context?.let { val roomThreadDetailArgs = ThreadTimelineArgs( roomId = timelineArgs.roomId, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index 6adf248af9..a07d01fed5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -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 @@ -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) } + } when (state.sendMode) { is SendMode.Regular -> { when (val slashCommandResult = commandParser.parseSlashCommand( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt index f90f3975c6..95553eb1cd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt @@ -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 /** @@ -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 { @@ -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 diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt index 048a4754f5..5f12c2f174 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt @@ -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 : diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 745cb0c731..20cf0b46fd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -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 @@ -328,7 +329,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)) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt index fc76535c4c..726138ed93 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt @@ -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 @@ -92,6 +94,7 @@ class ThreadsActivity : VectorBaseActivity() { * 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, diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt index aadad3d97c..d3a80811ea 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt @@ -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 diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt index d68e0a3248..8da9d83e10 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt @@ -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 @@ -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(initialState) { @@ -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) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt index 949778629b..d5659efa49 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt @@ -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 @@ -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 { From f0f98ce019e87eb47532fdb869c9612298de0958 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 28 Feb 2022 17:44:53 +0200 Subject: [PATCH 02/50] Add changelog file --- changelog.d/5378.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5378.misc diff --git a/changelog.d/5378.misc b/changelog.d/5378.misc new file mode 100644 index 0000000000..1cf6da5e59 --- /dev/null +++ b/changelog.d/5378.misc @@ -0,0 +1 @@ +Add analytics support for threads \ No newline at end of file From 9fa285e6ca8e0ed276a2b287b5160a418938019b Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 8 Mar 2022 14:06:28 +0300 Subject: [PATCH 03/50] Support showing push notifications for poll start events. --- .../app/features/notifications/NotifiableEventResolver.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index ec034173fc..0cf586afea 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt @@ -94,7 +94,7 @@ class NotifiableEventResolver @Inject constructor( } suspend fun resolveInMemoryEvent(session: Session, event: Event, canBeReplaced: Boolean): NotifiableEvent? { - if (event.getClearType() != EventType.MESSAGE) return null + if (event.getClearType() !in listOf(EventType.MESSAGE, EventType.POLL_START)) return null // Ignore message edition if (event.isEdition()) return null @@ -153,7 +153,8 @@ class NotifiableEventResolver @Inject constructor( event.attemptToDecryptIfNeeded(session) // only convert encrypted messages to NotifiableMessageEvents when (event.root.getClearType()) { - EventType.MESSAGE -> { + EventType.MESSAGE, + EventType.POLL_START -> { val body = displayableEventFormatter.format(event, isDm = room.roomSummary()?.isDirect.orFalse(), appendAuthor = false).toString() val roomName = room.roomSummary()?.displayName ?: "" val senderDisplayName = event.senderInfo.disambiguatedDisplayName @@ -185,7 +186,7 @@ class NotifiableEventResolver @Inject constructor( soundName = null ) } - else -> null + else -> null } } } From 7a1d3aa3f27fb184bea9e082321e69cec13d1ca2 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 8 Mar 2022 14:07:14 +0300 Subject: [PATCH 04/50] Filter poll response events in latest event query. --- .../sdk/internal/database/query/TimelineEventEntityQueries.kt | 1 + .../android/sdk/internal/database/query/TimelineEventFilter.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt index 63f41ebf2c..81d5ac835f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt @@ -97,6 +97,7 @@ internal fun RealmQuery.filterEvents(filters: TimelineEvent if (filters.filterEdits) { not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT) not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE) + not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.REFERENCE) } if (filters.filterRedacted) { not().like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, TimelineEventFilter.Unsigned.REDACTED) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventFilter.kt index 10a0d1dcec..a7317506a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventFilter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventFilter.kt @@ -26,6 +26,7 @@ internal object TimelineEventFilter { internal object Content { internal const val EDIT = """{*"m.relates_to"*"rel_type":*"m.replace"*}""" internal const val RESPONSE = """{*"m.relates_to"*"rel_type":*"org.matrix.response"*}""" + internal const val REFERENCE = """{*"m.relates_to"*"rel_type":*"m.reference"*}""" } /** From 83ff898ce5399d5bbe301143068d4f77ef1d7be8 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 8 Mar 2022 14:14:45 +0300 Subject: [PATCH 05/50] Changelog added. --- changelog.d/4780.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4780.bugfix diff --git a/changelog.d/4780.bugfix b/changelog.d/4780.bugfix new file mode 100644 index 0000000000..51eb1e4ad7 --- /dev/null +++ b/changelog.d/4780.bugfix @@ -0,0 +1 @@ +Poll system notifications on Android are not user friendly \ No newline at end of file From 5dfa3623453ff8be873f500fec5d8c1d19a9525d Mon Sep 17 00:00:00 2001 From: bmarty Date: Mon, 14 Mar 2022 00:05:30 +0000 Subject: [PATCH 06/50] Sync analytics plan --- .../app/features/analytics/plan/Composer.kt | 5 ++ .../features/analytics/plan/Interaction.kt | 51 ++++++++++++++++++- .../features/analytics/plan/MobileScreen.kt | 5 ++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/Composer.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/Composer.kt index a3b847a1bd..79be8aae2b 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/Composer.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/Composer.kt @@ -39,6 +39,10 @@ data class Composer( * sent event. */ val isReply: Boolean, + /** + * Whether this message begins a new thread or not. + */ + val startsThread: Boolean? = null, ) : VectorAnalyticsEvent { override fun getName() = "Composer" @@ -48,6 +52,7 @@ data class Composer( put("inThread", inThread) put("isEditing", isEditing) put("isReply", isReply) + startsThread?.let { put("startsThread", it) } }.takeIf { it.isNotEmpty() } } } diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt index 4c00cfd014..2007f75fbc 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/Interaction.kt @@ -50,6 +50,26 @@ data class Interaction( */ MobileRoomLeave, + /** + * User tapped on Threads button on Room screen. + */ + MobileRoomThreadListButton, + + /** + * User tapped on a thread summary item on Room screen. + */ + MobileRoomThreadSummaryItem, + + /** + * User tapped on the filter button on ThreadList screen. + */ + MobileThreadListFilterItem, + + /** + * User selected a thread on ThreadList screen. + */ + MobileThreadListThreadItem, + /** * User tapped the already selected space from the space list. */ @@ -115,12 +135,24 @@ data class Interaction( */ WebRightPanelRoomUserInfoInviteButton, + /** + * User clicked the threads 'show' filter dropdown in the threads panel + * in Element Web/Desktop. + */ + WebRightPanelThreadPanelFilterDropdown, + /** * User clicked the create room button in the room directory of Element * Web/Desktop. */ WebRoomDirectoryCreateRoomButton, + /** + * User clicked the Threads button in the top right of a room in Element + * Web/Desktop. + */ + WebRoomHeaderButtonsThreadsButton, + /** * User adjusted their favourites using the context menu on the header * of a room in Element Web/Desktop. @@ -207,7 +239,7 @@ data class Interaction( /** * User clicked the explore rooms button in the + context menu of the - * rooms sublist in Element Web/Desktop. + * rooms sublist in Element Web/Desktop. */ WebRoomListRoomsSublistPlusMenuExploreRoomsItem, @@ -223,6 +255,12 @@ data class Interaction( */ WebRoomSettingsSecurityTabCreateNewRoomButton, + /** + * User clicked a thread summary in the timeline of a room in Element + * Web/Desktop. + */ + WebRoomTimelineThreadSummaryButton, + /** * User interacted with the theme radio selector in the Appearance tab * of Settings in Element Web/Desktop. @@ -259,6 +297,17 @@ data class Interaction( */ WebSpaceHomeCreateRoomButton, + /** + * User clicked the back button on a Thread view going back to the + * Threads Panel of Element Web/Desktop. + */ + WebThreadViewBackButton, + + /** + * User selected a thread in the Threads panel in Element Web/Desktop. + */ + WebThreadsPanelThreadItem, + /** * User clicked the theme toggle button in the user menu of Element * Web/Desktop. diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt index 29b6667cca..79bae544ec 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt @@ -286,6 +286,11 @@ data class MobileScreen( */ SwitchDirectory, + /** + * Screen that displays list of threads for a room + */ + ThreadList, + /** * A screen that shows information about a room member. */ From 4cf820cb12190339af911c863da1f226c32fa66b Mon Sep 17 00:00:00 2001 From: Arnaud Ringenbach Date: Mon, 14 Mar 2022 11:59:10 +0100 Subject: [PATCH 07/50] Use client permalink base url on mentions if available --- .../session/room/send/MarkdownParserTest.kt | 4 ++ .../session/permalinks/PermalinkFactory.kt | 2 +- .../session/room/send/pills/TextPillsUtils.kt | 49 ++++++++++++++++--- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt index 9856ee7770..9d8eacccf2 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt @@ -60,6 +60,10 @@ class MarkdownParserTest : InstrumentedTest { applicationFlavor = "TestFlavor", roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider() ) + ), + MatrixConfiguration( + applicationFlavor = "TestFlavor", + roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider() )) ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt index 39c1ddfdce..377b083bd6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt @@ -145,7 +145,7 @@ internal class PermalinkFactory @Inject constructor( companion object { private const val ROOM_PATH = "room/" - private const val USER_PATH = "user/" + const val USER_PATH = "user/" private const val GROUP_PATH = "group/" } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt index ccbfbfcded..3edae5e26a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt @@ -16,9 +16,12 @@ package org.matrix.android.sdk.internal.session.room.send.pills import android.text.SpannableString +import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE import org.matrix.android.sdk.api.session.room.send.MatrixItemSpan import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver +import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory import java.util.Collections import javax.inject.Inject @@ -28,7 +31,8 @@ import javax.inject.Inject */ internal class TextPillsUtils @Inject constructor( private val mentionLinkSpecComparator: MentionLinkSpecComparator, - private val displayNameResolver: DisplayNameResolver + private val displayNameResolver: DisplayNameResolver, + private val matrixConfiguration: MatrixConfiguration ) { /** @@ -36,7 +40,7 @@ internal class TextPillsUtils @Inject constructor( * @return the transformed String or null if no Span found */ fun processSpecialSpansToHtml(text: CharSequence): String? { - return transformPills(text, MENTION_SPAN_TO_HTML_TEMPLATE) + return transformPills(text, createHtmlMentionSpanTemplate(forceMatrixTo = false)) } /** @@ -44,7 +48,7 @@ internal class TextPillsUtils @Inject constructor( * @return the transformed String or null if no Span found */ fun processSpecialSpansToMarkdown(text: CharSequence): String? { - return transformPills(text, MENTION_SPAN_TO_MD_TEMPLATE) + return transformPills(text, createMdMentionSpanTemplate(forceMatrixTo = false)) } private fun transformPills(text: CharSequence, template: String): String? { @@ -109,9 +113,42 @@ internal class TextPillsUtils @Inject constructor( } } - companion object { - private const val MENTION_SPAN_TO_HTML_TEMPLATE = "%2\$s" + private fun baseUrl(forceMatrixTo: Boolean): String { + return matrixConfiguration.clientPermalinkBaseUrl + ?.takeUnless { forceMatrixTo } + ?: MATRIX_TO_URL_BASE + } - private const val MENTION_SPAN_TO_MD_TEMPLATE = "[%2\$s](https://matrix.to/#/%1\$s)" + private fun useClientFormat(forceMatrixTo: Boolean): Boolean { + return !forceMatrixTo && matrixConfiguration.clientPermalinkBaseUrl != null + } + + private fun createHtmlMentionSpanTemplate(forceMatrixTo: Boolean): String { + return buildString { + append(MENTION_SPAN_TO_HTML_TEMPLATE_BEGIN) + append(baseUrl(forceMatrixTo)) + if (useClientFormat(forceMatrixTo)) { + append(PermalinkFactory.USER_PATH) + } + append(MENTION_SPAN_TO_HTML_TEMPLATE_END) + } + } + + private fun createMdMentionSpanTemplate(forceMatrixTo: Boolean): String { + return buildString { + append(MENTION_SPAN_TO_MD_TEMPLATE_BEGIN) + append(baseUrl(forceMatrixTo)) + if (useClientFormat(forceMatrixTo)) { + append(PermalinkFactory.USER_PATH) + } + append(MENTION_SPAN_TO_MD_TEMPLATE_END) + } + } + + companion object { + private const val MENTION_SPAN_TO_HTML_TEMPLATE_BEGIN = "%2\$s" + private const val MENTION_SPAN_TO_MD_TEMPLATE_BEGIN = "[%2\$s](" + private const val MENTION_SPAN_TO_MD_TEMPLATE_END = "%1\$s)" } } From df794ee41f9af3d48e87071fce7895691293e9e8 Mon Sep 17 00:00:00 2001 From: Arnaud Ringenbach Date: Tue, 15 Mar 2022 11:26:47 +0100 Subject: [PATCH 08/50] Move template creation to PermalinkService --- .../session/permalinks/PermalinkService.kt | 21 +++++++++ .../permalinks/DefaultPermalinkService.kt | 8 ++++ .../session/permalinks/PermalinkFactory.kt | 28 ++++++++++- .../session/room/send/pills/TextPillsUtils.kt | 46 ++----------------- 4 files changed, 60 insertions(+), 43 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt index 920dc85c7a..36aec29b8d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api.session.permalinks import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory /** * Useful methods to create permalink (like matrix.to links or client permalinks). @@ -80,4 +81,24 @@ interface PermalinkService { * @return the id from the url, ex: "@benoit:matrix.org", or null if the url is not a permalink */ fun getLinkedId(url: String): String? + + /** + * Creates a HTML mention span template. Can be used to replace a mention with a permalink to mentioned user. + * Ex: "%2\$s" + * + * @param forceMatrixTo whether we should force using matrix.to base URL + * + * @return the HTML template + */ + fun createHtmlMentionSpanTemplate(forceMatrixTo: Boolean = false): String + + /** + * Creates a Markdown mention span template. Can be used to replace a mention with a permalink to mentioned user. + * Ex: "[%2\$s](https://matrix.to/#/%1\$s)" + * + * @param forceMatrixTo whether we should force using matrix.to base URL + * + * @return the HTML template + */ + fun createMdMentionSpanTemplate(forceMatrixTo: Boolean = false): String } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt index 144ebb5404..fb46a26751 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/DefaultPermalinkService.kt @@ -43,4 +43,12 @@ internal class DefaultPermalinkService @Inject constructor( override fun getLinkedId(url: String): String? { return permalinkFactory.getLinkedId(url) } + + override fun createHtmlMentionSpanTemplate(forceMatrixTo: Boolean): String { + return permalinkFactory.createHtmlMentionSpanTemplate(forceMatrixTo) + } + + override fun createMdMentionSpanTemplate(forceMatrixTo: Boolean): String { + return permalinkFactory.createMdMentionSpanTemplate(forceMatrixTo) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt index 377b083bd6..affd6c137c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/PermalinkFactory.kt @@ -105,6 +105,28 @@ internal class PermalinkFactory @Inject constructor( ?.substringBeforeLast("?") } + fun createHtmlMentionSpanTemplate(forceMatrixTo: Boolean): String { + return buildString { + append(MENTION_SPAN_TO_HTML_TEMPLATE_BEGIN) + append(baseUrl(forceMatrixTo)) + if (useClientFormat(forceMatrixTo)) { + append(USER_PATH) + } + append(MENTION_SPAN_TO_HTML_TEMPLATE_END) + } + } + + fun createMdMentionSpanTemplate(forceMatrixTo: Boolean): String { + return buildString { + append(MENTION_SPAN_TO_MD_TEMPLATE_BEGIN) + append(baseUrl(forceMatrixTo)) + if (useClientFormat(forceMatrixTo)) { + append(USER_PATH) + } + append(MENTION_SPAN_TO_MD_TEMPLATE_END) + } + } + /** * Escape '/' in id, because it is used as a separator * @@ -145,7 +167,11 @@ internal class PermalinkFactory @Inject constructor( companion object { private const val ROOM_PATH = "room/" - const val USER_PATH = "user/" + private const val USER_PATH = "user/" private const val GROUP_PATH = "group/" + private const val MENTION_SPAN_TO_HTML_TEMPLATE_BEGIN = "%2\$s" + private const val MENTION_SPAN_TO_MD_TEMPLATE_BEGIN = "[%2\$s](" + private const val MENTION_SPAN_TO_MD_TEMPLATE_END = "%1\$s)" } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt index 3edae5e26a..427f4d966e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.room.send.pills import android.text.SpannableString import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE import org.matrix.android.sdk.api.session.room.send.MatrixItemSpan import org.matrix.android.sdk.api.util.MatrixItem @@ -32,7 +33,7 @@ import javax.inject.Inject internal class TextPillsUtils @Inject constructor( private val mentionLinkSpecComparator: MentionLinkSpecComparator, private val displayNameResolver: DisplayNameResolver, - private val matrixConfiguration: MatrixConfiguration + private val permalinkService: PermalinkService ) { /** @@ -40,7 +41,7 @@ internal class TextPillsUtils @Inject constructor( * @return the transformed String or null if no Span found */ fun processSpecialSpansToHtml(text: CharSequence): String? { - return transformPills(text, createHtmlMentionSpanTemplate(forceMatrixTo = false)) + return transformPills(text, permalinkService.createHtmlMentionSpanTemplate()) } /** @@ -48,7 +49,7 @@ internal class TextPillsUtils @Inject constructor( * @return the transformed String or null if no Span found */ fun processSpecialSpansToMarkdown(text: CharSequence): String? { - return transformPills(text, createMdMentionSpanTemplate(forceMatrixTo = false)) + return transformPills(text, permalinkService.createMdMentionSpanTemplate()) } private fun transformPills(text: CharSequence, template: String): String? { @@ -112,43 +113,4 @@ internal class TextPillsUtils @Inject constructor( i++ } } - - private fun baseUrl(forceMatrixTo: Boolean): String { - return matrixConfiguration.clientPermalinkBaseUrl - ?.takeUnless { forceMatrixTo } - ?: MATRIX_TO_URL_BASE - } - - private fun useClientFormat(forceMatrixTo: Boolean): Boolean { - return !forceMatrixTo && matrixConfiguration.clientPermalinkBaseUrl != null - } - - private fun createHtmlMentionSpanTemplate(forceMatrixTo: Boolean): String { - return buildString { - append(MENTION_SPAN_TO_HTML_TEMPLATE_BEGIN) - append(baseUrl(forceMatrixTo)) - if (useClientFormat(forceMatrixTo)) { - append(PermalinkFactory.USER_PATH) - } - append(MENTION_SPAN_TO_HTML_TEMPLATE_END) - } - } - - private fun createMdMentionSpanTemplate(forceMatrixTo: Boolean): String { - return buildString { - append(MENTION_SPAN_TO_MD_TEMPLATE_BEGIN) - append(baseUrl(forceMatrixTo)) - if (useClientFormat(forceMatrixTo)) { - append(PermalinkFactory.USER_PATH) - } - append(MENTION_SPAN_TO_MD_TEMPLATE_END) - } - } - - companion object { - private const val MENTION_SPAN_TO_HTML_TEMPLATE_BEGIN = "%2\$s" - private const val MENTION_SPAN_TO_MD_TEMPLATE_BEGIN = "[%2\$s](" - private const val MENTION_SPAN_TO_MD_TEMPLATE_END = "%1\$s)" - } } From 9f6d3ec380d00284562435a7b6103d12b83b6a36 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 28 Feb 2022 16:52:43 +0000 Subject: [PATCH 09/50] playing confetti effect when unable to personalise account on account creation - extracts the logic to an extension for reuse from the timeline --- .../im/vector/app/core/animations/Konfetti.kt | 35 +++++++++++++++++++ .../home/room/detail/TimelineFragment.kt | 14 ++------ .../FtueAuthAccountCreatedFragment.kt | 9 +++++ .../layout/fragment_ftue_account_created.xml | 6 ++++ 4 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/animations/Konfetti.kt diff --git a/vector/src/main/java/im/vector/app/core/animations/Konfetti.kt b/vector/src/main/java/im/vector/app/core/animations/Konfetti.kt new file mode 100644 index 0000000000..18f135d527 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/animations/Konfetti.kt @@ -0,0 +1,35 @@ +/* + * 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.core.animations + +import android.graphics.Color +import nl.dionsegijn.konfetti.KonfettiView +import nl.dionsegijn.konfetti.models.Shape +import nl.dionsegijn.konfetti.models.Size + +fun KonfettiView.play() { + build() + .addColors(Color.YELLOW, Color.GREEN, Color.MAGENTA) + .setDirection(0.0, 359.0) + .setSpeed(2f, 5f) + .setFadeOutEnabled(true) + .setTimeToLive(2000L) + .addShapes(Shape.Square, Shape.Circle) + .addSizes(Size(12)) + .setPosition(-50f, width + 50f, -50f, -50f) + .streamFor(150, 3000L) +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index c638666cd7..969805e3ca 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -68,6 +68,7 @@ import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.vanniktech.emoji.EmojiPopup import im.vector.app.R +import im.vector.app.core.animations.play import im.vector.app.core.dialogs.ConfirmationDialogBuilder import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper import im.vector.app.core.epoxy.LayoutManagerStateRestorer @@ -203,8 +204,6 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import nl.dionsegijn.konfetti.models.Shape -import nl.dionsegijn.konfetti.models.Size import org.billcarsonfr.jsonviewer.JSonViewerDialog import org.commonmark.parser.Parser import org.matrix.android.sdk.api.session.Session @@ -562,16 +561,7 @@ class TimelineFragment @Inject constructor( when (chatEffect) { ChatEffect.CONFETTI -> { views.viewKonfetti.isVisible = true - views.viewKonfetti.build() - .addColors(Color.YELLOW, Color.GREEN, Color.MAGENTA) - .setDirection(0.0, 359.0) - .setSpeed(2f, 5f) - .setFadeOutEnabled(true) - .setTimeToLive(2000L) - .addShapes(Shape.Square, Shape.Circle) - .addSizes(Size(12)) - .setPosition(-50f, views.viewKonfetti.width + 50f, -50f, -50f) - .streamFor(150, 3000L) + views.viewKonfetti.play() } ChatEffect.SNOWFALL -> { views.viewSnowFall.isVisible = true diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt index ccfb863a5b..49db52da67 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthAccountCreatedFragment.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import im.vector.app.R +import im.vector.app.core.animations.play import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.databinding.FragmentFtueAccountCreatedBinding import im.vector.app.features.onboarding.OnboardingAction @@ -33,6 +34,8 @@ class FtueAuthAccountCreatedFragment @Inject constructor( private val activeSessionHolder: ActiveSessionHolder ) : AbstractFtueAuthFragment() { + private var hasPlayedConfetti = false + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueAccountCreatedBinding { return FragmentFtueAccountCreatedBinding.inflate(inflater, container, false) } @@ -53,6 +56,12 @@ class FtueAuthAccountCreatedFragment @Inject constructor( val canPersonalize = state.personalizationState.supportsPersonalization() views.personalizeButtonGroup.isVisible = canPersonalize views.takeMeHomeButtonGroup.isVisible = !canPersonalize + + if (!hasPlayedConfetti && !canPersonalize) { + hasPlayedConfetti = true + views.viewKonfetti.isVisible = true + views.viewKonfetti.play() + } } override fun resetViewModel() { diff --git a/vector/src/main/res/layout/fragment_ftue_account_created.xml b/vector/src/main/res/layout/fragment_ftue_account_created.xml index 65bcdf2b63..89e94804af 100644 --- a/vector/src/main/res/layout/fragment_ftue_account_created.xml +++ b/vector/src/main/res/layout/fragment_ftue_account_created.xml @@ -6,6 +6,12 @@ android:layout_height="match_parent" android:background="?colorSecondary"> + + Date: Mon, 28 Feb 2022 17:37:49 +0000 Subject: [PATCH 10/50] adding personalization complete screen --- .../im/vector/app/core/di/FragmentModule.kt | 6 + ...FtueAuthPersonalizationCompleteFragment.kt | 61 ++++++++++ .../onboarding/ftueauth/FtueAuthVariant.kt | 14 ++- .../src/main/res/drawable/ic_celebration.xml | 22 ++++ ...fragment_ftue_personalization_complete.xml | 113 ++++++++++++++++++ vector/src/main/res/values/donottranslate.xml | 4 +- 6 files changed, 217 insertions(+), 3 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPersonalizationCompleteFragment.kt create mode 100644 vector/src/main/res/drawable/ic_celebration.xml create mode 100644 vector/src/main/res/layout/fragment_ftue_personalization_complete.xml diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index 2ffdd7ddf3..4dcfbe16f8 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -103,6 +103,7 @@ import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseDisplayNameFragm import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseProfilePictureFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthGenericTextInputFormFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthLoginFragment +import im.vector.app.features.onboarding.ftueauth.FtueAuthPersonalizationCompleteFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordMailConfirmationFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordSuccessFragment @@ -491,6 +492,11 @@ interface FragmentModule { @FragmentKey(FtueAuthChooseProfilePictureFragment::class) fun bindFtueAuthChooseProfilePictureFragment(fragment: FtueAuthChooseProfilePictureFragment): Fragment + @Binds + @IntoMap + @FragmentKey(FtueAuthPersonalizationCompleteFragment::class) + fun bindFtueAuthPersonalizationCompleteFragment(fragment: FtueAuthPersonalizationCompleteFragment): Fragment + @Binds @IntoMap @FragmentKey(UserListFragment::class) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPersonalizationCompleteFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPersonalizationCompleteFragment.kt new file mode 100644 index 0000000000..6b47b9830c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthPersonalizationCompleteFragment.kt @@ -0,0 +1,61 @@ +/* + * 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.onboarding.ftueauth + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import im.vector.app.core.animations.play +import im.vector.app.databinding.FragmentFtuePersonalizationCompleteBinding +import im.vector.app.features.onboarding.OnboardingAction +import im.vector.app.features.onboarding.OnboardingViewEvents +import javax.inject.Inject + +class FtueAuthPersonalizationCompleteFragment @Inject constructor() : AbstractFtueAuthFragment() { + + private var hasPlayedConfetti = false + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtuePersonalizationCompleteBinding { + return FragmentFtuePersonalizationCompleteBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupViews() + } + + private fun setupViews() { + views.personalizationCompleteCta.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome)) } + + if (!hasPlayedConfetti) { + hasPlayedConfetti = true + views.viewKonfetti.isVisible = true + views.viewKonfetti.play() + } + } + + override fun resetViewModel() { + // Nothing to do + } + + override fun onBackPressed(toolbarButton: Boolean): Boolean { + viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome)) + return true + } +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index 2008726ac3..79a974038b 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -227,7 +227,7 @@ class FtueAuthVariant( OnboardingViewEvents.OnChooseDisplayName -> onChooseDisplayName() OnboardingViewEvents.OnTakeMeHome -> navigateToHome(createdAccount = true) OnboardingViewEvents.OnChooseProfilePicture -> onChooseProfilePicture() - OnboardingViewEvents.OnPersonalizationComplete -> navigateToHome(createdAccount = true) + OnboardingViewEvents.OnPersonalizationComplete -> onPersonalizationComplete() OnboardingViewEvents.OnBack -> activity.popBackstack() }.exhaustive } @@ -393,7 +393,8 @@ class FtueAuthVariant( activity.supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) activity.replaceFragment( views.loginFragmentContainer, - FtueAuthAccountCreatedFragment::class.java + FtueAuthAccountCreatedFragment::class.java, + useCustomAnimation = true ) } @@ -416,4 +417,13 @@ class FtueAuthVariant( option = commonOption ) } + + private fun onPersonalizationComplete() { + activity.supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) + activity.replaceFragment( + views.loginFragmentContainer, + FtueAuthPersonalizationCompleteFragment::class.java, + useCustomAnimation = true + ) + } } diff --git a/vector/src/main/res/drawable/ic_celebration.xml b/vector/src/main/res/drawable/ic_celebration.xml new file mode 100644 index 0000000000..3868a60a60 --- /dev/null +++ b/vector/src/main/res/drawable/ic_celebration.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/vector/src/main/res/layout/fragment_ftue_personalization_complete.xml b/vector/src/main/res/layout/fragment_ftue_personalization_complete.xml new file mode 100644 index 0000000000..64908a4133 --- /dev/null +++ b/vector/src/main/res/layout/fragment_ftue_personalization_complete.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + +