Merge pull request #6342 from element-hq/feature/fga/live_location_sharing_setup

Setup live location sharing feature
This commit is contained in:
ganfra 2026-03-24 15:46:45 +01:00 committed by GitHub
commit 92920b862b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
197 changed files with 3767 additions and 2803 deletions

View file

@ -28,10 +28,10 @@ import io.element.android.features.call.api.CallType
import io.element.android.features.call.api.ElementCallEntryPoint
import io.element.android.features.forward.api.ForwardEntryPoint
import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint
import io.element.android.features.location.api.Location
import io.element.android.features.location.api.LocationService
import io.element.android.features.location.api.SendLocationEntryPoint
import io.element.android.features.location.api.ShareLocationEntryPoint
import io.element.android.features.location.api.ShowLocationEntryPoint
import io.element.android.features.location.api.ShowLocationMode
import io.element.android.features.messages.api.MessagesEntryPoint
import io.element.android.features.messages.impl.attachments.Attachment
import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewNode
@ -102,7 +102,7 @@ class MessagesFlowNode(
@Assisted plugins: List<Plugin>,
private val roomListService: RoomListService,
private val sessionId: SessionId,
private val sendLocationEntryPoint: SendLocationEntryPoint,
private val shareLocationEntryPoint: ShareLocationEntryPoint,
private val showLocationEntryPoint: ShowLocationEntryPoint,
private val createPollEntryPoint: CreatePollEntryPoint,
private val elementCallEntryPoint: ElementCallEntryPoint,
@ -148,7 +148,7 @@ class MessagesFlowNode(
data class AttachmentPreview(val timelineMode: Timeline.Mode, val attachment: Attachment, val inReplyToEventId: EventId?) : NavTarget
@Parcelize
data class LocationViewer(val location: Location, val description: String?) : NavTarget
data class LocationViewer(val mode: ShowLocationMode) : NavTarget
@Parcelize
data class EventDebugInfo(val eventId: EventId?, val debugInfo: TimelineItemDebugInfo) : NavTarget
@ -336,7 +336,7 @@ class MessagesFlowNode(
createNode<AttachmentsPreviewNode>(buildContext, listOf(inputs))
}
is NavTarget.LocationViewer -> {
val inputs = ShowLocationEntryPoint.Inputs(navTarget.location, navTarget.description)
val inputs = ShowLocationEntryPoint.Inputs(navTarget.mode)
showLocationEntryPoint.createNode(
parentNode = this,
buildContext = buildContext,
@ -374,7 +374,7 @@ class MessagesFlowNode(
createNode<ReportMessageNode>(buildContext, listOf(inputs))
}
is NavTarget.SendLocation -> {
sendLocationEntryPoint.createNode(
shareLocationEntryPoint.createNode(
parentNode = this,
buildContext = buildContext,
timelineMode = navTarget.timelineMode,
@ -558,9 +558,16 @@ class MessagesFlowNode(
)
}
is TimelineItemLocationContent -> {
NavTarget.LocationViewer(
val mode = ShowLocationMode.Static(
location = event.content.location,
description = event.content.description,
senderName = event.safeSenderName,
senderId = event.senderId,
senderAvatarUrl = event.senderAvatar.url,
timestamp = event.sentTimeMillis,
assetType = event.content.assetType,
)
NavTarget.LocationViewer(
mode = mode
).takeIf { locationService.isServiceAvailable() }
}
else -> null

View file

@ -39,7 +39,7 @@ import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugIn
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
import io.element.android.libraries.matrix.ui.messages.reply.aProfileTimelineDetailsReady
import io.element.android.libraries.matrix.ui.messages.reply.aProfileDetailsReady
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -166,7 +166,7 @@ internal fun aTimelineItemEvent(
isMine = isMine,
isEditable = isEditable,
canBeRepliedTo = canBeRepliedTo,
senderProfile = aProfileTimelineDetailsReady(
senderProfile = aProfileDetailsReady(
displayName = senderDisplayName,
displayNameAmbiguous = displayNameAmbiguous,
),

View file

@ -8,10 +8,8 @@
package io.element.android.features.messages.impl.timeline.components.event
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -21,31 +19,22 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContentProvider
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Text
@Composable
fun TimelineItemLocationView(
content: TimelineItemLocationContent,
modifier: Modifier = Modifier,
) {
Column(modifier = modifier.fillMaxWidth()) {
content.description?.let {
Text(
text = it,
modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 8.dp, bottom = 8.dp),
)
}
StaticMapView(
modifier = Modifier
.fillMaxWidth()
.heightIn(max = 188.dp),
lat = content.location.lat,
lon = content.location.lon,
zoom = 15.0,
contentDescription = content.body
)
}
StaticMapView(
modifier = modifier
.fillMaxWidth()
.heightIn(max = 188.dp),
pinVariant = content.pinVariant,
lat = content.location.lat,
lon = content.location.lon,
zoom = 15.0,
contentDescription = content.body
)
}
@PreviewsDayNight

View file

@ -9,8 +9,10 @@
package io.element.android.features.messages.impl.timeline.factories.event
import dev.zacsweers.metro.Inject
import io.element.android.features.location.api.Location
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
import io.element.android.libraries.matrix.api.core.EventId
@ -22,6 +24,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventTimeline
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
import io.element.android.libraries.matrix.api.timeline.item.event.LegacyCallInviteContent
import io.element.android.libraries.matrix.api.timeline.item.event.LiveLocationContent
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
@ -70,10 +73,10 @@ class TimelineItemContentFactory(
is FailedToParseMessageLikeContent -> failedToParseMessageFactory.create(itemContent)
is FailedToParseStateContent -> failedToParseStateFactory.create(itemContent)
is MessageContent -> {
val senderDisambiguatedDisplayName = senderProfile.getDisambiguatedDisplayName(sender)
messageFactory.create(
senderId = sender,
senderProfile = senderProfile,
content = itemContent,
senderDisambiguatedDisplayName = senderDisambiguatedDisplayName,
eventId = eventId,
)
}
@ -96,6 +99,24 @@ class TimelineItemContentFactory(
is UnableToDecryptContent -> utdFactory.create(itemContent)
is CallNotifyContent -> TimelineItemRtcNotificationContent()
is UnknownContent -> TimelineItemUnknownContent
is LiveLocationContent -> {
val lastKnownLocation = itemContent.locations.mapNotNull { beacon ->
Location.fromGeoUri(beacon.geoUri)
}.lastOrNull()
if (lastKnownLocation != null) {
TimelineItemLocationContent(
body = itemContent.body.trimEnd(),
description = itemContent.description?.trimEnd(),
assetType = itemContent.assetType,
senderId = sender,
senderProfile = senderProfile,
location = lastKnownLocation,
mode = TimelineItemLocationContent.Mode.Live(isActive = itemContent.isLive)
)
} else {
TimelineItemUnknownContent
}
}
}
}
}

View file

@ -30,6 +30,7 @@ import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
import io.element.android.libraries.androidutils.text.safeLinkify
import io.element.android.libraries.core.mimetype.MimeTypes
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.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
@ -39,10 +40,12 @@ import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessa
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
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.api.timeline.item.event.getDisambiguatedDisplayName
import io.element.android.libraries.matrix.ui.messages.toHtmlDocument
import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractor
import kotlinx.collections.immutable.persistentListOf
@ -65,11 +68,13 @@ class TimelineItemContentMessageFactory(
) {
fun create(
content: MessageContent,
senderDisambiguatedDisplayName: String,
senderId: UserId,
senderProfile: ProfileDetails,
eventId: EventId?,
): TimelineItemEventContent {
return when (val messageType = content.type) {
is EmoteMessageType -> {
val senderDisambiguatedDisplayName = senderProfile.getDisambiguatedDisplayName(senderId)
val emoteBody = "* $senderDisambiguatedDisplayName ${messageType.body.trimEnd()}"
val dom = messageType.formatted?.toHtmlDocument(
permalinkParser = permalinkParser,
@ -135,8 +140,8 @@ class TimelineItemContentMessageFactory(
}
is LocationMessageType -> {
val location = Location.fromGeoUri(messageType.geoUri)
val body = messageType.body.trimEnd()
if (location == null) {
val body = messageType.body.trimEnd()
TimelineItemTextContent(
body = body,
htmlDocument = null,
@ -145,9 +150,13 @@ class TimelineItemContentMessageFactory(
)
} else {
TimelineItemLocationContent(
body = messageType.body.trimEnd(),
body = body,
location = location,
description = messageType.description
description = messageType.description,
senderId = senderId,
senderProfile = senderProfile,
assetType = messageType.assetType,
mode = TimelineItemLocationContent.Mode.Static
)
}
}

View file

@ -31,6 +31,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyCon
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
import io.element.android.libraries.matrix.api.timeline.item.event.LegacyCallInviteContent
import io.element.android.libraries.matrix.api.timeline.item.event.LiveLocationContent
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
@ -81,7 +82,8 @@ internal fun MatrixTimelineItem.Event.canBeDisplayedInBubbleBlock(): Boolean {
RedactedContent,
is StickerContent,
is PollContent,
is UnableToDecryptContent -> true
is UnableToDecryptContent,
is LiveLocationContent -> true
// Can't be grouped
is FailedToParseStateContent,
is ProfileChangeContent,

View file

@ -28,14 +28,14 @@ class TimelineItemEventContentProvider : PreviewParameterProvider<TimelineItemEv
aTimelineItemAudioContent("An even bigger bigger bigger bigger bigger bigger bigger sound name which doesn't fit .mp3"),
aTimelineItemVoiceContent(),
aTimelineItemLocationContent(),
aTimelineItemLocationContent("Location description"),
aTimelineItemPollContent(),
aTimelineItemNoticeContent(),
aTimelineItemRedactedContent(),
aTimelineItemTextContent(),
aTimelineItemUnknownContent(),
aTimelineItemTextContent().copy(isEdited = true),
aTimelineItemTextContent(body = AN_EMOJI_ONLY_TEXT)
aTimelineItemTextContent(body = AN_EMOJI_ONLY_TEXT),
aTimelineItemLocationContent(mode = TimelineItemLocationContent.Mode.Live(isActive = true)),
)
}

View file

@ -9,11 +9,53 @@
package io.element.android.features.messages.impl.timeline.model.event
import io.element.android.features.location.api.Location
import io.element.android.libraries.designsystem.components.PinVariant
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.core.UserId
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl
import io.element.android.libraries.matrix.api.timeline.item.event.getDisplayName
data class TimelineItemLocationContent(
val body: String,
val senderId: UserId,
val senderProfile: ProfileDetails,
val location: Location,
val description: String? = null,
val assetType: AssetType? = null,
val mode: Mode,
) : TimelineItemEventContent {
val pinVariant = when (mode) {
is Mode.Live -> {
if (mode.isActive) {
PinVariant.UserLocation(avatarData = senderAvatar(), isLive = true)
} else {
PinVariant.StaleLocation
}
}
Mode.Static -> {
when (assetType) {
AssetType.PIN -> PinVariant.PinnedLocation
AssetType.SENDER,
AssetType.UNKNOWN,
null -> PinVariant.UserLocation(avatarData = senderAvatar(), isLive = false)
}
}
}
private fun senderAvatar() = AvatarData(
senderId.value,
name = senderProfile.getDisplayName(),
url = senderProfile.getAvatarUrl(),
size = AvatarSize.LocationPin
)
sealed interface Mode {
data object Static : Mode
data class Live(val isActive: Boolean) : Mode
}
override val type: String = "TimelineItemLocationContent"
}

View file

@ -10,21 +10,32 @@ package io.element.android.features.messages.impl.timeline.model.event
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.location.api.Location
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
import io.element.android.libraries.matrix.ui.messages.reply.aProfileDetailsReady
open class TimelineItemLocationContentProvider : PreviewParameterProvider<TimelineItemLocationContent> {
override val values: Sequence<TimelineItemLocationContent>
get() = sequenceOf(
aTimelineItemLocationContent(),
aTimelineItemLocationContent("This is a description!"),
aTimelineItemLocationContent(mode = TimelineItemLocationContent.Mode.Live(isActive = true)),
aTimelineItemLocationContent(mode = TimelineItemLocationContent.Mode.Live(isActive = false)),
)
}
fun aTimelineItemLocationContent(description: String? = null) = TimelineItemLocationContent(
body = "User location geo:52.2445,0.7186;u=5000",
fun aTimelineItemLocationContent(
body: String = "",
senderId: UserId = UserId("@sender:matrix.org"),
senderProfile: ProfileDetails = aProfileDetailsReady(),
mode: TimelineItemLocationContent.Mode = TimelineItemLocationContent.Mode.Static,
) = TimelineItemLocationContent(
body = body,
location = Location(
lat = 52.2445,
lon = 0.7186,
accuracy = 5000f,
),
description = description,
senderId = senderId,
senderProfile = senderProfile,
mode = mode
)

View file

@ -17,7 +17,7 @@ import io.element.android.features.call.test.FakeElementCallEntryPoint
import io.element.android.features.forward.test.FakeForwardEntryPoint
import io.element.android.features.knockrequests.test.FakeKnockRequestsListEntryPoint
import io.element.android.features.location.test.FakeLocationService
import io.element.android.features.location.test.FakeSendLocationEntryPoint
import io.element.android.features.location.test.FakeShareLocationEntryPoint
import io.element.android.features.location.test.FakeShowLocationEntryPoint
import io.element.android.features.messages.api.MessagesEntryPoint
import io.element.android.features.messages.impl.pinned.banner.createPinnedEventsTimelineProvider
@ -62,7 +62,7 @@ class DefaultMessagesEntryPointTest {
plugins = plugins,
roomListService = FakeRoomListService(),
sessionId = A_SESSION_ID,
sendLocationEntryPoint = FakeSendLocationEntryPoint(),
shareLocationEntryPoint = FakeShareLocationEntryPoint(),
showLocationEntryPoint = FakeShowLocationEntryPoint(),
createPollEntryPoint = FakeCreatePollEntryPoint(),
elementCallEntryPoint = FakeElementCallEntryPoint(),

View file

@ -31,7 +31,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_NAME
import io.element.android.libraries.matrix.test.core.FakeSendHandle
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
import io.element.android.libraries.matrix.ui.messages.reply.aProfileTimelineDetailsReady
import io.element.android.libraries.matrix.ui.messages.reply.aProfileDetailsReady
import kotlinx.collections.immutable.toImmutableList
internal fun aMessageEvent(
@ -52,7 +52,7 @@ internal fun aMessageEvent(
eventId = eventId,
transactionId = transactionId,
senderId = A_USER_ID,
senderProfile = aProfileTimelineDetailsReady(displayName = A_USER_NAME),
senderProfile = aProfileDetailsReady(displayName = A_USER_NAME),
senderAvatar = AvatarData(A_USER_ID.value, A_USER_NAME, size = AvatarSize.TimelineSender),
content = content,
sentTime = "",

View file

@ -41,6 +41,7 @@ import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.media.ThumbnailInfo
import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.permalink.PermalinkData
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
@ -59,8 +60,10 @@ 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.A_USER_ID
import io.element.android.libraries.matrix.test.media.aMediaSource
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.matrix.test.timeline.aProfileDetails
import io.element.android.libraries.matrix.test.timeline.aStickerContent
import io.element.android.libraries.matrix.ui.components.A_BLUR_HASH
import io.element.android.libraries.mediaviewer.test.util.FileExtensionExtractorWithoutValidation
@ -83,7 +86,8 @@ class TimelineItemContentMessageFactoryTest {
val sut = createTimelineItemContentMessageFactory()
val result = sut.create(
content = createMessageContent(type = OtherMessageType(msgType = "a_type", body = "body")),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
val expected = TimelineItemTextContent(
@ -98,15 +102,21 @@ class TimelineItemContentMessageFactoryTest {
@Test
fun `test create LocationMessageType not null`() = runTest {
val sut = createTimelineItemContentMessageFactory()
val assetType = AssetType.SENDER
val result = sut.create(
content = createMessageContent(type = LocationMessageType("body", "geo:1,2", "description")),
senderDisambiguatedDisplayName = "Bob",
content = createMessageContent(type = LocationMessageType("body", "geo:1,2", "description", assetType)),
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
val expected = TimelineItemLocationContent(
body = "body",
location = Location(lat = 1.0, lon = 2.0, accuracy = 0.0F),
location = Location(lat = 1.0, lon = 2.0, accuracy = null),
description = "description",
assetType = assetType,
mode = TimelineItemLocationContent.Mode.Static,
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
)
assertThat(result).isEqualTo(expected)
}
@ -115,8 +125,9 @@ class TimelineItemContentMessageFactoryTest {
fun `test create LocationMessageType null`() = runTest {
val sut = createTimelineItemContentMessageFactory()
val result = sut.create(
content = createMessageContent(type = LocationMessageType("body", "", null)),
senderDisambiguatedDisplayName = "Bob",
content = createMessageContent(type = LocationMessageType("body", "", null, null)),
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
val expected = TimelineItemTextContent(
@ -133,7 +144,8 @@ class TimelineItemContentMessageFactoryTest {
val sut = createTimelineItemContentMessageFactory()
val result = sut.create(
content = createMessageContent(type = TextMessageType("body", null)),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
val expected = TimelineItemTextContent(
@ -150,7 +162,8 @@ class TimelineItemContentMessageFactoryTest {
val sut = createTimelineItemContentMessageFactory()
val result = sut.create(
content = createMessageContent(type = TextMessageType("https://www.example.org", null)),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
) as TimelineItemTextContent
val expected = TimelineItemTextContent(
@ -197,7 +210,8 @@ class TimelineItemContentMessageFactoryTest {
formatted = FormattedBody(MessageFormat.HTML, expected.toString())
)
),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
assertThat((result as TimelineItemTextContent).formattedBody).isEqualTo(expected)
@ -215,7 +229,8 @@ class TimelineItemContentMessageFactoryTest {
formatted = FormattedBody(MessageFormat.UNKNOWN, "formatted")
)
),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
assertThat((result as TimelineItemTextContent).formattedBody).isEqualTo(SpannedString("body"))
@ -226,7 +241,8 @@ class TimelineItemContentMessageFactoryTest {
val sut = createTimelineItemContentMessageFactory()
val result = sut.create(
content = createMessageContent(type = VideoMessageType("filename", null, null, MediaSource("url"), null)),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
val expected = TimelineItemVideoContent(
@ -279,7 +295,8 @@ class TimelineItemContentMessageFactoryTest {
),
isEdited = true,
),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
val expected = TimelineItemVideoContent(
@ -309,7 +326,8 @@ class TimelineItemContentMessageFactoryTest {
val sut = createTimelineItemContentMessageFactory()
val result = sut.create(
content = createMessageContent(type = AudioMessageType("filename", null, null, MediaSource("url"), null)),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
val expected = TimelineItemAudioContent(
@ -345,7 +363,8 @@ class TimelineItemContentMessageFactoryTest {
),
isEdited = true,
),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
val expected = TimelineItemAudioContent(
@ -368,7 +387,8 @@ class TimelineItemContentMessageFactoryTest {
val sut = createTimelineItemContentMessageFactory()
val result = sut.create(
content = createMessageContent(type = VoiceMessageType("filename", null, null, MediaSource("url"), null, null)),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
val expected = TimelineItemVoiceContent(
@ -410,7 +430,8 @@ class TimelineItemContentMessageFactoryTest {
),
isEdited = true,
),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
val expected = TimelineItemVoiceContent(
@ -435,7 +456,8 @@ class TimelineItemContentMessageFactoryTest {
val sut = createTimelineItemContentMessageFactory()
val result = sut.create(
content = createMessageContent(type = ImageMessageType("filename", "body", null, MediaSource("url"), null)),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
val expected = TimelineItemImageContent(
@ -515,7 +537,8 @@ class TimelineItemContentMessageFactoryTest {
),
isEdited = true,
),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
val expected = TimelineItemImageContent(
@ -544,7 +567,8 @@ class TimelineItemContentMessageFactoryTest {
val sut = createTimelineItemContentMessageFactory()
val result = sut.create(
content = createMessageContent(type = FileMessageType("filename", null, null, MediaSource("url"), null)),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
val expected = TimelineItemFileContent(
@ -586,7 +610,8 @@ class TimelineItemContentMessageFactoryTest {
),
isEdited = true,
),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
val expected = TimelineItemFileContent(
@ -609,7 +634,8 @@ class TimelineItemContentMessageFactoryTest {
val sut = createTimelineItemContentMessageFactory()
val result = sut.create(
content = createMessageContent(type = NoticeMessageType("body", null)),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
val expected = TimelineItemNoticeContent(
@ -631,7 +657,8 @@ class TimelineItemContentMessageFactoryTest {
formatted = FormattedBody(MessageFormat.HTML, "formatted")
)
),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
(result as TimelineItemNoticeContent).formattedBody.assertSpannedEquals(SpannedString("formatted"))
@ -642,7 +669,8 @@ class TimelineItemContentMessageFactoryTest {
val sut = createTimelineItemContentMessageFactory()
val result = sut.create(
content = createMessageContent(type = EmoteMessageType("body", null)),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails("Bob"),
eventId = AN_EVENT_ID,
)
val expected = TimelineItemEmoteContent(
@ -664,7 +692,8 @@ class TimelineItemContentMessageFactoryTest {
formatted = FormattedBody(MessageFormat.HTML, "formatted")
)
),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails("Bob"),
eventId = AN_EVENT_ID,
)
@ -690,7 +719,8 @@ class TimelineItemContentMessageFactoryTest {
formatted = FormattedBody(MessageFormat.HTML, "Test <a href=\"https://www.example.org\">me@matrix.org</a>")
)
),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
(result as TimelineItemTextContent).formattedBody.assertSpannedEquals(expectedSpanned)
@ -715,7 +745,8 @@ class TimelineItemContentMessageFactoryTest {
formatted = FormattedBody(MessageFormat.HTML, "Test https://www.example.org")
)
),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)
(result as TimelineItemTextContent).formattedBody.assertSpannedEquals(expectedSpanned)
@ -741,7 +772,8 @@ class TimelineItemContentMessageFactoryTest {
formatted = FormattedBody(MessageFormat.HTML, "Test https://www.example.org")
)
),
senderDisambiguatedDisplayName = "Bob",
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
eventId = AN_EVENT_ID,
)

View file

@ -23,7 +23,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSen
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.core.FakeSendHandle
import io.element.android.libraries.matrix.ui.messages.reply.aProfileTimelineDetailsReady
import io.element.android.libraries.matrix.ui.messages.reply.aProfileDetailsReady
import kotlinx.collections.immutable.toImmutableList
import org.junit.Test
@ -34,7 +34,7 @@ class TimelineItemGrouperTest {
id = UniqueId("0"),
senderId = A_USER_ID,
senderAvatar = anAvatarData(),
senderProfile = aProfileTimelineDetailsReady(displayName = ""),
senderProfile = aProfileDetailsReady(displayName = ""),
content = TimelineItemStateEventContent(body = "a state event"),
reactionsState = aTimelineItemReactions(count = 0),
readReceiptState = TimelineItemReadReceipts(emptyList<ReadReceiptData>().toImmutableList()),