Handle tapping on user mentions (#2021)
This commit is contained in:
parent
8d0db8d6d3
commit
3b59b70e65
8 changed files with 92 additions and 14 deletions
1
changelog.d/1448.feature
Normal file
1
changelog.d/1448.feature
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Tapping on a user mention pill opens their profile.
|
||||||
|
|
@ -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.FeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
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.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.permalink.PermalinkBuilder
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.Mention
|
import io.element.android.libraries.matrix.api.room.Mention
|
||||||
|
|
@ -335,7 +336,7 @@ class MessageComposerPresenter @Inject constructor(
|
||||||
add(Mention.AtRoom)
|
add(Mention.AtRoom)
|
||||||
}
|
}
|
||||||
for (userId in state.userIds) {
|
for (userId in state.userIds) {
|
||||||
add(Mention.User(userId))
|
add(Mention.User(UserId(userId)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.orEmpty()
|
}.orEmpty()
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,8 @@ import androidx.compose.ui.zIndex
|
||||||
import androidx.constraintlayout.compose.ConstrainScope
|
import androidx.constraintlayout.compose.ConstrainScope
|
||||||
import androidx.constraintlayout.compose.ConstraintLayout
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
import io.element.android.compound.theme.ElementTheme
|
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.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.aTimelineItemEvent
|
||||||
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
||||||
import io.element.android.features.messages.impl.timeline.components.event.toExtraPadding
|
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.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
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.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.matrix.ui.components.AttachmentThumbnail
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
@ -138,6 +138,13 @@ fun TimelineItemEventRow(
|
||||||
inReplyToClick(inReplyToEventId)
|
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()) {
|
Column(modifier = modifier.fillMaxWidth()) {
|
||||||
if (event.groupPosition.isNew()) {
|
if (event.groupPosition.isNew()) {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
@ -182,6 +189,7 @@ fun TimelineItemEventRow(
|
||||||
onReactionClicked = { emoji -> onReactionClick(emoji, event) },
|
onReactionClicked = { emoji -> onReactionClick(emoji, event) },
|
||||||
onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) },
|
onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) },
|
||||||
onMoreReactionsClicked = { onMoreReactionsClick(event) },
|
onMoreReactionsClicked = { onMoreReactionsClick(event) },
|
||||||
|
onMentionClicked = ::onMentionClicked,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -200,6 +208,7 @@ fun TimelineItemEventRow(
|
||||||
onReactionClicked = { emoji -> onReactionClick(emoji, event) },
|
onReactionClicked = { emoji -> onReactionClick(emoji, event) },
|
||||||
onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) },
|
onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) },
|
||||||
onMoreReactionsClicked = { onMoreReactionsClick(event) },
|
onMoreReactionsClicked = { onMoreReactionsClick(event) },
|
||||||
|
onMentionClicked = ::onMentionClicked,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -254,6 +263,7 @@ private fun TimelineItemEventRowContent(
|
||||||
onReactionClicked: (emoji: String) -> Unit,
|
onReactionClicked: (emoji: String) -> Unit,
|
||||||
onReactionLongClicked: (emoji: String) -> Unit,
|
onReactionLongClicked: (emoji: String) -> Unit,
|
||||||
onMoreReactionsClicked: (event: TimelineItem.Event) -> Unit,
|
onMoreReactionsClicked: (event: TimelineItem.Event) -> Unit,
|
||||||
|
onMentionClicked: (Mention) -> Unit,
|
||||||
eventSink: (TimelineEvents) -> Unit,
|
eventSink: (TimelineEvents) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -316,6 +326,7 @@ private fun TimelineItemEventRowContent(
|
||||||
onTimestampClicked = {
|
onTimestampClicked = {
|
||||||
onTimestampClicked(event)
|
onTimestampClicked(event)
|
||||||
},
|
},
|
||||||
|
onMentionClicked = onMentionClicked,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -387,6 +398,7 @@ private fun MessageEventBubbleContent(
|
||||||
onMessageLongClick: () -> Unit,
|
onMessageLongClick: () -> Unit,
|
||||||
inReplyToClick: () -> Unit,
|
inReplyToClick: () -> Unit,
|
||||||
onTimestampClicked: () -> Unit,
|
onTimestampClicked: () -> Unit,
|
||||||
|
onMentionClicked: (Mention) -> Unit,
|
||||||
eventSink: (TimelineEvents) -> Unit,
|
eventSink: (TimelineEvents) -> Unit,
|
||||||
@SuppressLint("ModifierParameter")
|
@SuppressLint("ModifierParameter")
|
||||||
@Suppress("ModifierNaming")
|
@Suppress("ModifierNaming")
|
||||||
|
|
@ -512,15 +524,17 @@ private fun MessageEventBubbleContent(
|
||||||
isMine = event.isMine,
|
isMine = event.isMine,
|
||||||
isEditable = event.isEditable,
|
isEditable = event.isEditable,
|
||||||
onLinkClicked = { url ->
|
onLinkClicked = { url ->
|
||||||
Timber.d("Clicked on: $url")
|
when (val permalink = PermalinkParser.parse(Uri.parse(url))) {
|
||||||
when (PermalinkParser.parse(Uri.parse(url))) {
|
|
||||||
is PermalinkData.UserLink -> {
|
is PermalinkData.UserLink -> {
|
||||||
// TODO open member details
|
onMentionClicked(Mention.User(UserId(permalink.userId)))
|
||||||
}
|
}
|
||||||
is PermalinkData.FallbackLink -> {
|
is PermalinkData.RoomLink -> {
|
||||||
|
onMentionClicked(Mention.Room(permalink.getRoomId(), permalink.getRoomAlias()))
|
||||||
|
}
|
||||||
|
is PermalinkData.FallbackLink,
|
||||||
|
is PermalinkData.RoomEmailInviteLink -> {
|
||||||
context.openUrlInExternalApp(url)
|
context.openUrlInExternalApp(url)
|
||||||
}
|
}
|
||||||
else -> Unit // TODO handle other types of links, as room ones
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
extraPadding = event.toExtraPadding(),
|
extraPadding = event.toExtraPadding(),
|
||||||
|
|
|
||||||
|
|
@ -862,7 +862,7 @@ class MessageComposerPresenterTest {
|
||||||
|
|
||||||
advanceUntilIdle()
|
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
|
// Check intentional mentions on reply sent
|
||||||
initialState.eventSink(MessageComposerEvents.SetMode(aReplyMode()))
|
initialState.eventSink(MessageComposerEvents.SetMode(aReplyMode()))
|
||||||
|
|
@ -877,7 +877,7 @@ class MessageComposerPresenterTest {
|
||||||
initialState.eventSink(MessageComposerEvents.SendMessage(A_MESSAGE.toMessage()))
|
initialState.eventSink(MessageComposerEvents.SendMessage(A_MESSAGE.toMessage()))
|
||||||
advanceUntilIdle()
|
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
|
// Check intentional mentions on edit message
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
|
|
@ -893,7 +893,7 @@ class MessageComposerPresenterTest {
|
||||||
initialState.eventSink(MessageComposerEvents.SendMessage(A_MESSAGE.toMessage()))
|
initialState.eventSink(MessageComposerEvents.SendMessage(A_MESSAGE.toMessage()))
|
||||||
advanceUntilIdle()
|
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)
|
skipItems(1)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.api.permalink
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -32,7 +33,15 @@ sealed interface PermalinkData {
|
||||||
val isRoomAlias: Boolean,
|
val isRoomAlias: Boolean,
|
||||||
val eventId: String?,
|
val eventId: String?,
|
||||||
val viaParameters: ImmutableList<String>
|
val viaParameters: ImmutableList<String>
|
||||||
) : PermalinkData
|
) : PermalinkData {
|
||||||
|
fun getRoomId(): RoomId? {
|
||||||
|
return roomIdOrAlias.takeIf { !isRoomAlias }?.let(::RoomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRoomAlias(): String? {
|
||||||
|
return roomIdOrAlias.takeIf { isRoomAlias }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* &room_name=Team2
|
* &room_name=Team2
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,11 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.api.room
|
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 {
|
sealed interface Mention {
|
||||||
data class User(val userId: String): Mention
|
data class User(val userId: UserId): Mention
|
||||||
data object AtRoom: Mention
|
data object AtRoom: Mention
|
||||||
|
data class Room(val roomId: RoomId?, val roomAlias: String?): Mention
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,6 @@ import org.matrix.rustcomponents.sdk.Mentions
|
||||||
|
|
||||||
fun List<Mention>.map(): Mentions {
|
fun List<Mention>.map(): Mentions {
|
||||||
val hasAtRoom = any { it is Mention.AtRoom }
|
val hasAtRoom = any { it is Mention.AtRoom }
|
||||||
val userIds = filterIsInstance<Mention.User>().map { it.userId }
|
val userIds = filterIsInstance<Mention.User>().map { it.userId.value }
|
||||||
return Mentions(userIds, hasAtRoom)
|
return Mentions(userIds, hasAtRoom)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue