diff --git a/changelog.d/1448.feature b/changelog.d/1448.feature new file mode 100644 index 0000000000..6224008e7a --- /dev/null +++ b/changelog.d/1448.feature @@ -0,0 +1 @@ +Tapping on a user mention pill opens their profile. diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 7a5c06721c..38ba51fa72 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -44,6 +44,7 @@ import io.element.android.libraries.di.SingleIn import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.core.ProgressCallback +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.Mention @@ -335,7 +336,7 @@ class MessageComposerPresenter @Inject constructor( add(Mention.AtRoom) } for (userId in state.userIds) { - add(Mention.User(userId)) + add(Mention.User(UserId(userId))) } } }.orEmpty() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 80fe46a397..15bd03db54 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -62,8 +62,8 @@ import androidx.compose.ui.zIndex import androidx.constraintlayout.compose.ConstrainScope import androidx.constraintlayout.compose.ConstraintLayout import io.element.android.compound.theme.ElementTheme -import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.event.toExtraPadding @@ -98,10 +98,10 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkParser +import io.element.android.libraries.matrix.api.room.Mention import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.coroutines.launch -import timber.log.Timber import kotlin.math.abs import kotlin.math.roundToInt @@ -138,6 +138,13 @@ fun TimelineItemEventRow( inReplyToClick(inReplyToEventId) } + fun onMentionClicked(mention: Mention) { + when (mention) { + is Mention.User -> onUserDataClick(mention.userId) + else -> Unit // TODO implement actions for other mentions being clicked + } + } + Column(modifier = modifier.fillMaxWidth()) { if (event.groupPosition.isNew()) { Spacer(modifier = Modifier.height(16.dp)) @@ -182,6 +189,7 @@ fun TimelineItemEventRow( onReactionClicked = { emoji -> onReactionClick(emoji, event) }, onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) }, onMoreReactionsClicked = { onMoreReactionsClick(event) }, + onMentionClicked = ::onMentionClicked, eventSink = eventSink, ) } @@ -200,6 +208,7 @@ fun TimelineItemEventRow( onReactionClicked = { emoji -> onReactionClick(emoji, event) }, onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) }, onMoreReactionsClicked = { onMoreReactionsClick(event) }, + onMentionClicked = ::onMentionClicked, eventSink = eventSink, ) } @@ -254,6 +263,7 @@ private fun TimelineItemEventRowContent( onReactionClicked: (emoji: String) -> Unit, onReactionLongClicked: (emoji: String) -> Unit, onMoreReactionsClicked: (event: TimelineItem.Event) -> Unit, + onMentionClicked: (Mention) -> Unit, eventSink: (TimelineEvents) -> Unit, modifier: Modifier = Modifier, ) { @@ -316,6 +326,7 @@ private fun TimelineItemEventRowContent( onTimestampClicked = { onTimestampClicked(event) }, + onMentionClicked = onMentionClicked, eventSink = eventSink, ) } @@ -387,6 +398,7 @@ private fun MessageEventBubbleContent( onMessageLongClick: () -> Unit, inReplyToClick: () -> Unit, onTimestampClicked: () -> Unit, + onMentionClicked: (Mention) -> Unit, eventSink: (TimelineEvents) -> Unit, @SuppressLint("ModifierParameter") @Suppress("ModifierNaming") @@ -512,15 +524,17 @@ private fun MessageEventBubbleContent( isMine = event.isMine, isEditable = event.isEditable, onLinkClicked = { url -> - Timber.d("Clicked on: $url") - when (PermalinkParser.parse(Uri.parse(url))) { + when (val permalink = PermalinkParser.parse(Uri.parse(url))) { is PermalinkData.UserLink -> { - // TODO open member details + onMentionClicked(Mention.User(UserId(permalink.userId))) + } + is PermalinkData.RoomLink -> { + onMentionClicked(Mention.Room(permalink.getRoomId(), permalink.getRoomAlias())) } - is PermalinkData.FallbackLink -> { + is PermalinkData.FallbackLink, + is PermalinkData.RoomEmailInviteLink -> { context.openUrlInExternalApp(url) } - else -> Unit // TODO handle other types of links, as room ones } }, extraPadding = event.toExtraPadding(), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt index e8a1559409..f892a27bf2 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt @@ -862,7 +862,7 @@ class MessageComposerPresenterTest { advanceUntilIdle() - assertThat(room.sendMessageMentions).isEqualTo(listOf(Mention.User(A_USER_ID.value))) + assertThat(room.sendMessageMentions).isEqualTo(listOf(Mention.User(A_USER_ID))) // Check intentional mentions on reply sent initialState.eventSink(MessageComposerEvents.SetMode(aReplyMode())) @@ -877,7 +877,7 @@ class MessageComposerPresenterTest { initialState.eventSink(MessageComposerEvents.SendMessage(A_MESSAGE.toMessage())) advanceUntilIdle() - assertThat(room.sendMessageMentions).isEqualTo(listOf(Mention.User(A_USER_ID_2.value))) + assertThat(room.sendMessageMentions).isEqualTo(listOf(Mention.User(A_USER_ID_2))) // Check intentional mentions on edit message skipItems(1) @@ -893,7 +893,7 @@ class MessageComposerPresenterTest { initialState.eventSink(MessageComposerEvents.SendMessage(A_MESSAGE.toMessage())) advanceUntilIdle() - assertThat(room.sendMessageMentions).isEqualTo(listOf(Mention.User(A_USER_ID_3.value))) + assertThat(room.sendMessageMentions).isEqualTo(listOf(Mention.User(A_USER_ID_3))) skipItems(1) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt index e1036011f6..1fc350775d 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.api.permalink import android.net.Uri import androidx.compose.runtime.Immutable +import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.collections.immutable.ImmutableList /** @@ -32,7 +33,15 @@ sealed interface PermalinkData { val isRoomAlias: Boolean, val eventId: String?, val viaParameters: ImmutableList - ) : PermalinkData + ) : PermalinkData { + fun getRoomId(): RoomId? { + return roomIdOrAlias.takeIf { !isRoomAlias }?.let(::RoomId) + } + + fun getRoomAlias(): String? { + return roomIdOrAlias.takeIf { isRoomAlias } + } + } /* * &room_name=Team2 diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/Mention.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/Mention.kt index 30aba3c3c0..6a1b1f60ef 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/Mention.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/Mention.kt @@ -16,7 +16,11 @@ package io.element.android.libraries.matrix.api.room +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.UserId + sealed interface Mention { - data class User(val userId: String): Mention + data class User(val userId: UserId): Mention data object AtRoom: Mention + data class Room(val roomId: RoomId?, val roomAlias: String?): Mention } diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkDataTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkDataTest.kt new file mode 100644 index 0000000000..08955411d8 --- /dev/null +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkDataTest.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 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 io.element.android.libraries.matrix.api.permalink + +import com.google.common.truth.Truth.assertThat +import kotlinx.collections.immutable.persistentListOf +import org.junit.Test + +class PermalinkDataTest { + + @Test + fun `getRoomId() returns value when isRoomAlias is false`() { + val permalinkData = PermalinkData.RoomLink( + roomIdOrAlias = "!abcdef123456:matrix.org", + isRoomAlias = false, + eventId = null, + viaParameters = persistentListOf(), + ) + assertThat(permalinkData.getRoomId()).isNotNull() + assertThat(permalinkData.getRoomAlias()).isNull() + } + + @Test + fun `getRoomAlias() returns value when isRoomAlias is true`() { + val permalinkData = PermalinkData.RoomLink( + roomIdOrAlias = "#room:matrix.org", + isRoomAlias = true, + eventId = null, + viaParameters = persistentListOf(), + ) + assertThat(permalinkData.getRoomId()).isNull() + assertThat(permalinkData.getRoomAlias()).isNotNull() + } + +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/Mention.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/Mention.kt index 463496ffe7..795ac2e003 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/Mention.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/Mention.kt @@ -21,6 +21,6 @@ import org.matrix.rustcomponents.sdk.Mentions fun List.map(): Mentions { val hasAtRoom = any { it is Mention.AtRoom } - val userIds = filterIsInstance().map { it.userId } + val userIds = filterIsInstance().map { it.userId.value } return Mentions(userIds, hasAtRoom) }