Merge pull request #2639 from element-hq/feature/bma/hideCallMemberEvents

Hide call member events
This commit is contained in:
Benoit Marty 2024-04-02 16:14:11 +02:00 committed by GitHub
commit 609f636d1c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 843 additions and 343 deletions

View file

@ -16,9 +16,12 @@
package io.element.android.features.messages.impl
import android.content.Context
import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
@ -31,11 +34,14 @@ import io.element.android.features.messages.impl.attachments.Attachment
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.libraries.androidutils.system.openUrlInExternalApp
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
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.MatrixRoom
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.mediaplayer.api.MediaPlayer
@ -52,6 +58,7 @@ class MessagesNode @AssistedInject constructor(
presenterFactory: MessagesPresenter.Factory,
private val timelineItemPresenterFactories: TimelineItemPresenterFactories,
private val mediaPlayer: MediaPlayer,
private val permalinkParser: PermalinkParser,
) : Node(buildContext, plugins = plugins), MessagesNavigator {
private val presenter = presenterFactory.create(this)
private val callback = plugins<Callback>().firstOrNull()
@ -96,6 +103,25 @@ class MessagesNode @AssistedInject constructor(
private fun onUserDataClicked(userId: UserId) {
callback?.onUserDataClicked(userId)
}
private fun onLinkClicked(
context: Context,
url: String,
) {
when (val permalink = permalinkParser.parse(Uri.parse(url))) {
is PermalinkData.UserLink -> {
callback?.onUserDataClicked(UserId(permalink.userId))
}
is PermalinkData.RoomLink -> {
// TODO Implement room link handling
}
is PermalinkData.FallbackLink,
is PermalinkData.RoomEmailInviteLink -> {
context.openUrlInExternalApp(url)
}
}
}
override fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) {
callback?.onShowEventDebugInfoClicked(eventId, debugInfo)
}
@ -126,6 +152,7 @@ class MessagesNode @AssistedInject constructor(
@Composable
override fun View(modifier: Modifier) {
val context = LocalContext.current
CompositionLocalProvider(
LocalTimelineItemPresenterFactories provides timelineItemPresenterFactories,
) {
@ -137,6 +164,7 @@ class MessagesNode @AssistedInject constructor(
onEventClicked = this::onEventClicked,
onPreviewAttachments = this::onPreviewAttachments,
onUserDataClicked = this::onUserDataClicked,
onLinkClicked = { onLinkClicked(context, it) },
onSendLocationClicked = this::onSendLocationClicked,
onCreatePollClicked = this::onCreatePollClicked,
onJoinCallClicked = this::onJoinCallClicked,

View file

@ -119,6 +119,7 @@ fun MessagesView(
onRoomDetailsClicked: () -> Unit,
onEventClicked: (event: TimelineItem.Event) -> Boolean,
onUserDataClicked: (UserId) -> Unit,
onLinkClicked: (String) -> Unit,
onPreviewAttachments: (ImmutableList<Attachment>) -> Unit,
onSendLocationClicked: () -> Unit,
onCreatePollClicked: () -> Unit,
@ -213,6 +214,7 @@ fun MessagesView(
onMessageClicked = ::onMessageClicked,
onMessageLongClicked = ::onMessageLongClicked,
onUserDataClicked = onUserDataClicked,
onLinkClicked = onLinkClicked,
onTimestampClicked = { event ->
if (event.localSendState is LocalEventSendState.SendingFailed) {
state.retrySendMenuState.eventSink(RetrySendMenuEvents.EventSelected(event))
@ -313,6 +315,7 @@ private fun MessagesViewContent(
state: MessagesState,
onMessageClicked: (TimelineItem.Event) -> Unit,
onUserDataClicked: (UserId) -> Unit,
onLinkClicked: (String) -> Unit,
onReactionClicked: (key: String, TimelineItem.Event) -> Unit,
onReactionLongClicked: (key: String, TimelineItem.Event) -> Unit,
onMoreReactionsClicked: (TimelineItem.Event) -> Unit,
@ -386,6 +389,7 @@ private fun MessagesViewContent(
onMessageClicked = onMessageClicked,
onMessageLongClicked = onMessageLongClicked,
onUserDataClicked = onUserDataClicked,
onLinkClicked = onLinkClicked,
onTimestampClicked = onTimestampClicked,
onReactionClicked = onReactionClicked,
onReactionLongClicked = onReactionLongClicked,
@ -570,6 +574,7 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class)
onEventClicked = { false },
onPreviewAttachments = {},
onUserDataClicked = {},
onLinkClicked = {},
onSendLocationClicked = {},
onCreatePollClicked = {},
onJoinCallClicked = {},

View file

@ -49,6 +49,7 @@ 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.permalink.PermalinkParser
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.user.CurrentSessionIdHolder
@ -96,6 +97,8 @@ class MessageComposerPresenter @Inject constructor(
private val messageComposerContext: MessageComposerContextImpl,
private val richTextEditorStateFactory: RichTextEditorStateFactory,
private val currentSessionIdHolder: CurrentSessionIdHolder,
private val permalinkParser: PermalinkParser,
private val permalinkBuilder: PermalinkBuilder,
permissionsPresenterFactory: PermissionsPresenter.Factory,
) : Presenter<MessageComposerState> {
private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA)
@ -334,7 +337,7 @@ class MessageComposerPresenter @Inject constructor(
}
is MentionSuggestion.Member -> {
val text = mention.roomMember.displayName?.prependIndent("@") ?: mention.roomMember.userId.value
val link = PermalinkBuilder.permalinkForUser(mention.roomMember.userId).getOrNull() ?: return@launch
val link = permalinkBuilder.permalinkForUser(mention.roomMember.userId).getOrNull() ?: return@launch
richTextEditorState.insertMentionAtSuggestion(text = text, link = link)
}
}
@ -345,6 +348,7 @@ class MessageComposerPresenter @Inject constructor(
return MessageComposerState(
richTextEditorState = richTextEditorState,
permalinkParser = permalinkParser,
isFullScreen = isFullScreen.value,
mode = messageComposerContext.composerMode,
showAttachmentSourcePicker = showAttachmentSourcePicker,

View file

@ -21,6 +21,7 @@ import androidx.compose.runtime.Stable
import io.element.android.features.messages.impl.attachments.Attachment
import io.element.android.features.messages.impl.mentions.MentionSuggestion
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.wysiwyg.compose.RichTextEditorState
import kotlinx.collections.immutable.ImmutableList
@ -28,6 +29,7 @@ import kotlinx.collections.immutable.ImmutableList
@Stable
data class MessageComposerState(
val richTextEditorState: RichTextEditorState,
val permalinkParser: PermalinkParser,
val isFullScreen: Boolean,
val mode: MessageComposerMode,
val showAttachmentSourcePicker: Boolean,

View file

@ -16,9 +16,12 @@
package io.element.android.features.messages.impl.messagecomposer
import android.net.Uri
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.messages.impl.mentions.MentionSuggestion
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.textcomposer.model.MessageComposerMode
import io.element.android.wysiwyg.compose.RichTextEditorState
import kotlinx.collections.immutable.ImmutableList
@ -43,6 +46,10 @@ fun aMessageComposerState(
memberSuggestions: ImmutableList<MentionSuggestion> = persistentListOf(),
) = MessageComposerState(
richTextEditorState = richTextEditorState,
permalinkParser = object : PermalinkParser {
override fun parse(uriString: String): PermalinkData = TODO()
override fun parse(uri: Uri): PermalinkData = TODO()
},
isFullScreen = isFullScreen,
mode = mode,
showTextFormatting = showTextFormatting,

View file

@ -109,6 +109,7 @@ internal fun MessageComposerView(
modifier = modifier,
state = state.richTextEditorState,
voiceMessageState = voiceMessageState.voiceMessageState,
permalinkParser = state.permalinkParser,
subcomposing = subcomposing,
onRequestFocus = ::onRequestFocus,
onSendMessage = ::sendMessage,

View file

@ -28,6 +28,7 @@ import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle
import io.element.android.libraries.textcomposer.mentions.rememberMentionSpanProvider
import io.element.android.wysiwyg.compose.StyledHtmlConverter
@ -39,7 +40,9 @@ import javax.inject.Inject
@ContributesBinding(SessionScope::class)
@SingleIn(SessionScope::class)
class DefaultHtmlConverterProvider @Inject constructor() : HtmlConverterProvider {
class DefaultHtmlConverterProvider @Inject constructor(
private val permalinkParser: PermalinkParser,
) : HtmlConverterProvider {
private val htmlConverter: MutableState<HtmlConverter?> = mutableStateOf(null)
@Composable
@ -50,7 +53,10 @@ class DefaultHtmlConverterProvider @Inject constructor() : HtmlConverterProvider
}
val editorStyle = ElementRichTextEditorStyle.textStyle()
val mentionSpanProvider = rememberMentionSpanProvider(currentUserId = currentUserId)
val mentionSpanProvider = rememberMentionSpanProvider(
currentUserId = currentUserId,
permalinkParser = permalinkParser,
)
val context = LocalContext.current

View file

@ -81,6 +81,7 @@ fun TimelineView(
typingNotificationState: TypingNotificationState,
roomName: String?,
onUserDataClicked: (UserId) -> Unit,
onLinkClicked: (String) -> Unit,
onMessageClicked: (TimelineItem.Event) -> Unit,
onMessageLongClicked: (TimelineItem.Event) -> Unit,
onTimestampClicked: (TimelineItem.Event) -> Unit,
@ -140,6 +141,7 @@ fun TimelineView(
onClick = onMessageClicked,
onLongClick = onMessageLongClicked,
onUserDataClick = onUserDataClicked,
onLinkClicked = onLinkClicked,
inReplyToClick = ::inReplyToClicked,
onReactionClick = onReactionClicked,
onReactionLongClick = onReactionLongClicked,
@ -276,6 +278,7 @@ internal fun TimelineViewPreview(
onMessageClicked = {},
onTimestampClicked = {},
onUserDataClicked = {},
onLinkClicked = {},
onMessageLongClicked = {},
onReactionClicked = { _, _ -> },
onReactionLongClicked = { _, _ -> },

View file

@ -38,6 +38,7 @@ internal fun ATimelineItemEventRow(
onClick = {},
onLongClick = {},
onUserDataClick = {},
onLinkClicked = {},
inReplyToClick = {},
onReactionClick = { _, _ -> },
onReactionLongClick = { _, _ -> },

View file

@ -17,7 +17,6 @@
package io.element.android.features.messages.impl.timeline.components
import android.annotation.SuppressLint
import android.net.Uri
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@ -50,7 +49,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.res.stringResource
@ -94,7 +92,6 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
import io.element.android.features.messages.impl.timeline.model.event.canBeRepliedTo
import io.element.android.features.messages.impl.timeline.model.metadata
import io.element.android.libraries.androidutils.system.openUrlInExternalApp
import io.element.android.libraries.designsystem.colors.AvatarColorsProvider
import io.element.android.libraries.designsystem.components.EqualWidthColumn
import io.element.android.libraries.designsystem.components.avatar.Avatar
@ -109,9 +106,6 @@ import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
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.testtags.TestTags
import io.element.android.libraries.ui.strings.CommonStrings
@ -128,6 +122,7 @@ fun TimelineItemEventRow(
isHighlighted: Boolean,
onClick: () -> Unit,
onLongClick: () -> Unit,
onLinkClicked: (String) -> Unit,
onUserDataClick: (UserId) -> Unit,
inReplyToClick: (EventId) -> Unit,
onTimestampClicked: (TimelineItem.Event) -> Unit,
@ -151,13 +146,6 @@ 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))
@ -203,7 +191,7 @@ fun TimelineItemEventRow(
onReactionClicked = { emoji -> onReactionClick(emoji, event) },
onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) },
onMoreReactionsClicked = { onMoreReactionsClick(event) },
onMentionClicked = ::onMentionClicked,
onLinkClicked = onLinkClicked,
eventSink = eventSink,
)
}
@ -222,7 +210,7 @@ fun TimelineItemEventRow(
onReactionClicked = { emoji -> onReactionClick(emoji, event) },
onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) },
onMoreReactionsClicked = { onMoreReactionsClick(event) },
onMentionClicked = ::onMentionClicked,
onLinkClicked = onLinkClicked,
eventSink = eventSink,
)
}
@ -278,7 +266,7 @@ private fun TimelineItemEventRowContent(
onReactionClicked: (emoji: String) -> Unit,
onReactionLongClicked: (emoji: String) -> Unit,
onMoreReactionsClicked: (event: TimelineItem.Event) -> Unit,
onMentionClicked: (Mention) -> Unit,
onLinkClicked: (String) -> Unit,
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
modifier: Modifier = Modifier,
) {
@ -346,7 +334,7 @@ private fun TimelineItemEventRowContent(
onTimestampClicked = {
onTimestampClicked(event)
},
onMentionClicked = onMentionClicked,
onLinkClicked = onLinkClicked,
eventSink = eventSink,
)
}
@ -429,7 +417,7 @@ private fun MessageEventBubbleContent(
@Suppress("UNUSED_PARAMETER")
inReplyToClick: () -> Unit,
onTimestampClicked: () -> Unit,
onMentionClicked: (Mention) -> Unit,
onLinkClicked: (String) -> Unit,
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
@SuppressLint("ModifierParameter")
// need to rename this modifier to prevent linter false positives
@ -530,7 +518,6 @@ private fun MessageEventBubbleContent(
modifier: Modifier = Modifier,
canShrinkContent: Boolean = false,
) {
val context = LocalContext.current
val timestampLayoutModifier: Modifier
val contentModifier: Modifier
when {
@ -566,20 +553,7 @@ private fun MessageEventBubbleContent(
) { onContentLayoutChanged ->
TimelineItemEventContentView(
content = event.content,
onLinkClicked = { url ->
when (val permalink = PermalinkParser.parse(Uri.parse(url))) {
is PermalinkData.UserLink -> {
onMentionClicked(Mention.User(UserId(permalink.userId)))
}
is PermalinkData.RoomLink -> {
onMentionClicked(Mention.Room(permalink.getRoomId(), permalink.getRoomAlias()))
}
is PermalinkData.FallbackLink,
is PermalinkData.RoomEmailInviteLink -> {
context.openUrlInExternalApp(url)
}
}
},
onLinkClicked = onLinkClicked,
eventSink = eventSink,
onContentLayoutChanged = onContentLayoutChanged,
modifier = contentModifier

View file

@ -51,6 +51,7 @@ fun TimelineItemGroupedEventsRow(
onLongClick: (TimelineItem.Event) -> Unit,
inReplyToClick: (EventId) -> Unit,
onUserDataClick: (UserId) -> Unit,
onLinkClicked: (String) -> Unit,
onTimestampClicked: (TimelineItem.Event) -> Unit,
onReactionClick: (key: String, TimelineItem.Event) -> Unit,
onReactionLongClick: (key: String, TimelineItem.Event) -> Unit,
@ -78,6 +79,7 @@ fun TimelineItemGroupedEventsRow(
onLongClick = onLongClick,
inReplyToClick = inReplyToClick,
onUserDataClick = onUserDataClick,
onLinkClicked = onLinkClicked,
onTimestampClicked = onTimestampClicked,
onReactionClick = onReactionClick,
onReactionLongClick = onReactionLongClick,
@ -102,6 +104,7 @@ private fun TimelineItemGroupedEventsRowContent(
onLongClick: (TimelineItem.Event) -> Unit,
inReplyToClick: (EventId) -> Unit,
onUserDataClick: (UserId) -> Unit,
onLinkClicked: (String) -> Unit,
onTimestampClicked: (TimelineItem.Event) -> Unit,
onReactionClick: (key: String, TimelineItem.Event) -> Unit,
onReactionLongClick: (key: String, TimelineItem.Event) -> Unit,
@ -135,6 +138,7 @@ private fun TimelineItemGroupedEventsRowContent(
onLongClick = onLongClick,
inReplyToClick = inReplyToClick,
onUserDataClick = onUserDataClick,
onLinkClicked = onLinkClicked,
onTimestampClicked = onTimestampClicked,
onReactionClick = onReactionClick,
onReactionLongClick = onReactionLongClick,
@ -175,6 +179,7 @@ internal fun TimelineItemGroupedEventsRowContentExpandedPreview() = ElementPrevi
onLongClick = {},
inReplyToClick = {},
onUserDataClick = {},
onLinkClicked = {},
onTimestampClicked = {},
onReactionClick = { _, _ -> },
onReactionLongClick = { _, _ -> },
@ -200,6 +205,7 @@ internal fun TimelineItemGroupedEventsRowContentCollapsePreview() = ElementPrevi
onLongClick = {},
inReplyToClick = {},
onUserDataClick = {},
onLinkClicked = {},
onTimestampClicked = {},
onReactionClick = { _, _ -> },
onReactionLongClick = { _, _ -> },

View file

@ -36,6 +36,7 @@ internal fun TimelineItemRow(
highlightedItem: String?,
sessionState: SessionState,
onUserDataClick: (UserId) -> Unit,
onLinkClicked: (String) -> Unit,
onClick: (TimelineItem.Event) -> Unit,
onLongClick: (TimelineItem.Event) -> Unit,
inReplyToClick: (EventId) -> Unit,
@ -79,6 +80,7 @@ internal fun TimelineItemRow(
onClick = { onClick(timelineItem) },
onLongClick = { onLongClick(timelineItem) },
onUserDataClick = onUserDataClick,
onLinkClicked = onLinkClicked,
inReplyToClick = inReplyToClick,
onReactionClick = onReactionClick,
onReactionLongClick = onReactionLongClick,
@ -103,6 +105,7 @@ internal fun TimelineItemRow(
onLongClick = onLongClick,
inReplyToClick = inReplyToClick,
onUserDataClick = onUserDataClick,
onLinkClicked = onLinkClicked,
onTimestampClicked = onTimestampClicked,
onReactionClick = onReactionClick,
onReactionLongClick = onReactionLongClick,

View file

@ -41,6 +41,7 @@ import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
@ -67,6 +68,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
private val fileExtensionExtractor: FileExtensionExtractor,
private val featureFlagService: FeatureFlagService,
private val htmlConverterProvider: HtmlConverterProvider,
private val permalinkParser: PermalinkParser,
) {
suspend fun create(content: MessageContent, senderDisplayName: String, eventId: EventId?): TimelineItemEventContent {
return when (val messageType = content.type) {
@ -74,7 +76,10 @@ class TimelineItemContentMessageFactory @Inject constructor(
val emoteBody = "* $senderDisplayName ${messageType.body.trimEnd()}"
TimelineItemEmoteContent(
body = emoteBody,
htmlDocument = messageType.formatted?.toHtmlDocument(prefix = "* $senderDisplayName"),
htmlDocument = messageType.formatted?.toHtmlDocument(
permalinkParser = permalinkParser,
prefix = "* $senderDisplayName",
),
formattedBody = parseHtml(messageType.formatted, prefix = "* $senderDisplayName") ?: emoteBody.withLinks(),
isEdited = content.isEdited,
)
@ -197,7 +202,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
val body = messageType.body.trimEnd()
TimelineItemNoticeContent(
body = body,
htmlDocument = messageType.formatted?.toHtmlDocument(),
htmlDocument = messageType.formatted?.toHtmlDocument(permalinkParser = permalinkParser),
formattedBody = parseHtml(messageType.formatted) ?: body.withLinks(),
isEdited = content.isEdited,
)
@ -206,7 +211,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
val body = messageType.body.trimEnd()
TimelineItemTextContent(
body = body,
htmlDocument = messageType.formatted?.toHtmlDocument(),
htmlDocument = messageType.formatted?.toHtmlDocument(permalinkParser = permalinkParser),
formattedBody = parseHtml(messageType.formatted) ?: body.withLinks(),
isEdited = content.isEdited,
)

View file

@ -30,6 +30,7 @@ import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormat
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
@ -43,6 +44,7 @@ class TimelineItemEventFactory @Inject constructor(
private val contentFactory: TimelineItemContentFactory,
private val matrixClient: MatrixClient,
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
private val permalinkParser: PermalinkParser,
) {
suspend fun create(
currentTimelineItem: MatrixTimelineItem.Event,
@ -80,7 +82,7 @@ class TimelineItemEventFactory @Inject constructor(
reactionsState = currentTimelineItem.computeReactionsState(),
readReceiptState = currentTimelineItem.computeReadReceiptState(roomMembers),
localSendState = currentTimelineItem.event.localSendState,
inReplyTo = currentTimelineItem.event.inReplyTo()?.map(),
inReplyTo = currentTimelineItem.event.inReplyTo()?.map(permalinkParser = permalinkParser),
isThreaded = currentTimelineItem.event.isThreaded(),
debugInfo = currentTimelineItem.event.debugInfo,
origin = currentTimelineItem.event.origin,

View file

@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.timeline.model
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.PermalinkParser
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
@ -34,7 +35,9 @@ data class InReplyToDetails(
val textContent: String?,
)
fun InReplyTo.map() = when (this) {
fun InReplyTo.map(
permalinkParser: PermalinkParser,
) = when (this) {
is InReplyTo.Ready -> InReplyToDetails(
eventId = eventId,
senderId = senderId,
@ -44,7 +47,7 @@ fun InReplyTo.map() = when (this) {
textContent = when (content) {
is MessageContent -> {
val messageContent = content as MessageContent
(messageContent.type as? TextMessageType)?.toPlainText() ?: messageContent.body
(messageContent.type as? TextMessageType)?.toPlainText(permalinkParser = permalinkParser) ?: messageContent.body
}
is StickerContent -> {
val stickerContent = content as StickerContent

View file

@ -35,6 +35,7 @@ internal fun MessagesViewWithTypingPreview(
onEventClicked = { false },
onPreviewAttachments = {},
onUserDataClicked = {},
onLinkClicked = {},
onSendLocationClicked = {},
onCreatePollClicked = {},
onJoinCallClicked = {},

View file

@ -77,6 +77,8 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID_2
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.matrix.test.room.aRoomMember
@ -725,6 +727,8 @@ class MessagesPresenterTest {
richTextEditorStateFactory = TestRichTextEditorStateFactory(),
permissionsPresenterFactory = permissionsPresenterFactory,
currentSessionIdHolder = CurrentSessionIdHolder(FakeMatrixClient(A_SESSION_ID)),
permalinkParser = FakePermalinkParser(),
permalinkBuilder = FakePermalinkBuilder(),
)
val voiceMessageComposerPresenter = VoiceMessageComposerPresenter(
this,

View file

@ -478,6 +478,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMessa
onRoomDetailsClicked: () -> Unit = EnsureNeverCalled(),
onEventClicked: (event: TimelineItem.Event) -> Boolean = EnsureNeverCalledWithParamAndResult(),
onUserDataClicked: (UserId) -> Unit = EnsureNeverCalledWithParam(),
onLinkClicked: (String) -> Unit = EnsureNeverCalledWithParam(),
onPreviewAttachments: (ImmutableList<Attachment>) -> Unit = EnsureNeverCalledWithParam(),
onSendLocationClicked: () -> Unit = EnsureNeverCalled(),
onCreatePollClicked: () -> Unit = EnsureNeverCalled(),
@ -492,6 +493,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMessa
onRoomDetailsClicked = onRoomDetailsClicked,
onEventClicked = onEventClicked,
onUserDataClicked = onUserDataClicked,
onLinkClicked = onLinkClicked,
onPreviewAttachments = onPreviewAttachments,
onSendLocationClicked = onSendLocationClicked,
onCreatePollClicked = onCreatePollClicked,

View file

@ -41,6 +41,7 @@ import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractorWithoutValidation
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.TestScope
@ -57,6 +58,7 @@ internal fun TestScope.aTimelineItemsFactory(): TimelineItemsFactory {
fileExtensionExtractor = FileExtensionExtractorWithoutValidation(),
featureFlagService = FakeFeatureFlagService(),
htmlConverterProvider = FakeHtmlConverterProvider(),
permalinkParser = FakePermalinkParser(),
),
redactedMessageFactory = TimelineItemContentRedactedFactory(),
stickerFactory = TimelineItemContentStickerFactory(
@ -73,6 +75,7 @@ internal fun TestScope.aTimelineItemsFactory(): TimelineItemsFactory {
),
matrixClient = matrixClient,
lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(),
permalinkParser = FakePermalinkParser(),
),
virtualItemFactory = TimelineItemVirtualFactory(
daySeparatorFactory = TimelineItemDaySeparatorFactory(

View file

@ -43,6 +43,7 @@ import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.TransactionId
import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.VideoInfo
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.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.Mention
@ -60,6 +61,8 @@ import io.element.android.libraries.matrix.test.A_USER_ID_3
import io.element.android.libraries.matrix.test.A_USER_ID_4
import io.element.android.libraries.matrix.test.A_USER_NAME
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.mediapickers.api.PickerProvider
@ -805,7 +808,14 @@ class MessageComposerPresenterTest {
@Test
fun `present - insertMention`() = runTest {
val presenter = createPresenter(this)
val presenter = createPresenter(
coroutineScope = this,
permalinkBuilder = FakePermalinkBuilder(
result = {
Result.success("https://matrix.to/#/${A_USER_ID_2.value}")
}
)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@ -941,6 +951,7 @@ class MessageComposerPresenterTest {
mediaPreProcessor: MediaPreProcessor = this.mediaPreProcessor,
snackbarDispatcher: SnackbarDispatcher = this.snackbarDispatcher,
permissionPresenter: PermissionsPresenter = FakePermissionsPresenter(),
permalinkBuilder: PermalinkBuilder = FakePermalinkBuilder()
) = MessageComposerPresenter(
coroutineScope,
room,
@ -955,6 +966,8 @@ class MessageComposerPresenterTest {
TestRichTextEditorStateFactory(),
currentSessionIdHolder = CurrentSessionIdHolder(FakeMatrixClient(A_SESSION_ID)),
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionPresenter),
permalinkParser = FakePermalinkParser(),
permalinkBuilder = permalinkBuilder,
)
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {

View file

@ -21,6 +21,7 @@ import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.test.junit4.createComposeRule
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@ -32,7 +33,9 @@ class DefaultHtmlConverterProviderTest {
@Test
fun `calling provide without calling Update first should throw an exception`() {
val provider = DefaultHtmlConverterProvider()
val provider = DefaultHtmlConverterProvider(
permalinkParser = FakePermalinkParser(),
)
val exception = runCatching { provider.provide() }.exceptionOrNull()
@ -41,7 +44,9 @@ class DefaultHtmlConverterProviderTest {
@Test
fun `calling provide after calling Update first should return an HtmlConverter`() {
val provider = DefaultHtmlConverterProvider()
val provider = DefaultHtmlConverterProvider(
permalinkParser = FakePermalinkParser(),
)
composeTestRule.setContent {
CompositionLocalProvider(LocalInspectionMode provides true) {
provider.Update(currentUserId = A_USER_ID)

View file

@ -45,6 +45,7 @@ class TimelineViewTest {
typingNotificationState = aTypingNotificationState(),
roomName = null,
onUserDataClicked = EnsureNeverCalledWithParam(),
onLinkClicked = EnsureNeverCalledWithParam(),
onMessageClicked = EnsureNeverCalledWithParam(),
onMessageLongClicked = EnsureNeverCalledWithParam(),
onTimestampClicked = EnsureNeverCalledWithParam(),
@ -72,6 +73,7 @@ class TimelineViewTest {
typingNotificationState = aTypingNotificationState(),
roomName = null,
onUserDataClicked = EnsureNeverCalledWithParam(),
onLinkClicked = EnsureNeverCalledWithParam(),
onMessageClicked = EnsureNeverCalledWithParam(),
onMessageLongClicked = EnsureNeverCalledWithParam(),
onTimestampClicked = EnsureNeverCalledWithParam(),

View file

@ -63,6 +63,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageTy
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.matrix.ui.components.A_BLUR_HASH
import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractorWithoutValidation
import kotlinx.collections.immutable.persistentListOf
@ -664,6 +665,7 @@ class TimelineItemContentMessageFactoryTest {
fileExtensionExtractor = FileExtensionExtractorWithoutValidation(),
featureFlagService = featureFlagService,
htmlConverterProvider = FakeHtmlConverterProvider(htmlConverterTransform),
permalinkParser = FakePermalinkParser(),
)
private fun createStickerContent(

View file

@ -26,14 +26,27 @@ import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershi
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import org.junit.Test
class InReplyToDetailTest {
@Test
fun `map - with a not ready InReplyTo does not work`() {
assertThat(InReplyTo.Pending.map()).isNull()
assertThat(InReplyTo.NotLoaded(AN_EVENT_ID).map()).isNull()
assertThat(InReplyTo.Error.map()).isNull()
assertThat(
InReplyTo.Pending.map(
permalinkParser = FakePermalinkParser()
)
).isNull()
assertThat(
InReplyTo.NotLoaded(AN_EVENT_ID).map(
permalinkParser = FakePermalinkParser()
)
).isNull()
assertThat(
InReplyTo.Error.map(
permalinkParser = FakePermalinkParser()
)
).isNull()
}
@Test
@ -48,7 +61,9 @@ class InReplyToDetailTest {
change = MembershipChange.INVITED,
)
)
val inReplyToDetails = inReplyTo.map()
val inReplyToDetails = inReplyTo.map(
permalinkParser = FakePermalinkParser()
)
assertThat(inReplyToDetails).isNotNull()
assertThat(inReplyToDetails?.textContent).isNull()
}
@ -74,7 +89,11 @@ class InReplyToDetailTest {
)
)
)
assertThat(inReplyTo.map()?.textContent).isEqualTo("Hello!")
assertThat(
inReplyTo.map(
permalinkParser = FakePermalinkParser()
)?.textContent
).isEqualTo("Hello!")
}
@Test
@ -95,6 +114,10 @@ class InReplyToDetailTest {
)
)
)
assertThat(inReplyTo.map()?.textContent).isEqualTo("**Hello!**")
assertThat(
inReplyTo.map(
permalinkParser = FakePermalinkParser()
)?.textContent
).isEqualTo("**Hello!**")
}
}

View file

@ -45,6 +45,7 @@ class RoomDetailsNode @AssistedInject constructor(
private val presenter: RoomDetailsPresenter,
private val room: MatrixRoom,
private val analyticsService: AnalyticsService,
private val permalinkBuilder: PermalinkBuilder,
) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin {
fun openRoomMemberList()
@ -84,8 +85,8 @@ class RoomDetailsNode @AssistedInject constructor(
private fun onShareRoom(context: Context) {
val alias = room.alias ?: room.alternativeAliases.firstOrNull()
val permalinkResult = alias?.let { PermalinkBuilder.permalinkForRoomAlias(it) }
?: PermalinkBuilder.permalinkForRoomId(room.roomId)
val permalinkResult = alias?.let { permalinkBuilder.permalinkForRoomAlias(it) }
?: permalinkBuilder.permalinkForRoomId(room.roomId)
permalinkResult.onSuccess { permalink ->
context.startSharePlainTextIntent(
activityResultLauncher = null,
@ -99,7 +100,7 @@ class RoomDetailsNode @AssistedInject constructor(
}
private fun onShareMember(context: Context, member: RoomMember) {
val permalinkResult = PermalinkBuilder.permalinkForUser(member.userId)
val permalinkResult = permalinkBuilder.permalinkForUser(member.userId)
permalinkResult.onSuccess { permalink ->
context.startSharePlainTextIntent(
activityResultLauncher = null,

View file

@ -46,6 +46,7 @@ class RoomMemberDetailsNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val analyticsService: AnalyticsService,
private val permalinkBuilder: PermalinkBuilder,
presenterFactory: RoomMemberDetailsPresenter.Factory,
) : Node(buildContext, plugins = plugins) {
interface Callback : NodeInputs {
@ -74,7 +75,7 @@ class RoomMemberDetailsNode @AssistedInject constructor(
val context = LocalContext.current
fun onShareUser() {
val permalinkResult = PermalinkBuilder.permalinkForUser(inputs.roomMemberId)
val permalinkResult = permalinkBuilder.permalinkForUser(inputs.roomMemberId)
permalinkResult.onSuccess { permalink ->
context.startSharePlainTextIntent(
activityResultLauncher = null,