diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt index 262c4f9522..c7fe37b531 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt @@ -8,12 +8,12 @@ package io.element.android.features.messages.impl.timeline +import io.element.android.features.messages.impl.timeline.components.MessageShieldData import io.element.android.features.messages.impl.timeline.model.TimelineItem 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.ThreadId import io.element.android.libraries.matrix.api.timeline.Timeline -import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import kotlin.time.Duration sealed interface TimelineEvents { @@ -31,7 +31,7 @@ sealed interface TimelineEvents { sealed interface EventFromTimelineItem : TimelineEvents data class ComputeVerifiedUserSendFailure(val event: TimelineItem.Event) : EventFromTimelineItem - data class ShowShieldDialog(val messageShield: MessageShield) : EventFromTimelineItem + data class ShowShieldDialog(val messageShieldData: MessageShieldData) : EventFromTimelineItem data class LoadMore(val direction: Timeline.PaginationDirection) : EventFromTimelineItem data class OpenThread(val threadRootEventId: ThreadId, val focusedEvent: EventId?) : EventFromTimelineItem diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index fd7d200a52..8d1612ff1e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -27,6 +27,7 @@ import io.element.android.features.messages.impl.MessagesNavigator import io.element.android.features.messages.impl.UserEventPermissions import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureEvents import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState +import io.element.android.features.messages.impl.timeline.components.MessageShieldData import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig import io.element.android.features.messages.impl.timeline.model.NewEventState @@ -52,7 +53,6 @@ import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsSta import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.api.timeline.Timeline -import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.DisplayFirstTimelineItems @@ -133,7 +133,7 @@ class TimelinePresenter( val prevMostRecentItemId = rememberSaveable { mutableStateOf(null) } val newEventState = remember { mutableStateOf(NewEventState.None) } - val messageShield: MutableState = remember { mutableStateOf(null) } + val messageShieldDialogData: MutableState = remember { mutableStateOf(null) } val resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailurePresenter.present() val isSendPublicReadReceiptsEnabled by remember { @@ -215,8 +215,8 @@ class TimelinePresenter( is TimelineEvents.JumpToLive -> { timelineController.focusOnLive() } - TimelineEvents.HideShieldDialog -> messageShield.value = null - is TimelineEvents.ShowShieldDialog -> messageShield.value = event.messageShield + TimelineEvents.HideShieldDialog -> messageShieldDialogData.value = null + is TimelineEvents.ShowShieldDialog -> messageShieldDialogData.value = event.messageShieldData is TimelineEvents.ComputeVerifiedUserSendFailure -> { resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(event.event)) } @@ -312,7 +312,7 @@ class TimelinePresenter( newEventState = newEventState.value, isLive = isLive, focusRequestState = focusRequestState.value, - messageShield = messageShield.value, + messageShieldDialogData = messageShieldDialogData.value, resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailureState, displayThreadSummaries = displayThreadSummaries, eventSink = ::handleEvent, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt index 3ec168fd47..a53960f63b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt @@ -10,6 +10,7 @@ package io.element.android.features.messages.impl.timeline import androidx.compose.runtime.Immutable import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState +import io.element.android.features.messages.impl.timeline.components.MessageShieldData import io.element.android.features.messages.impl.timeline.model.NewEventState import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.typing.TypingNotificationState @@ -18,7 +19,6 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom import io.element.android.libraries.matrix.api.timeline.Timeline -import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import kotlinx.collections.immutable.ImmutableList import kotlin.time.Duration @@ -31,7 +31,7 @@ data class TimelineState( val isLive: Boolean, val focusRequestState: FocusRequestState, // If not null, info will be rendered in a dialog - val messageShield: MessageShield?, + val messageShieldDialogData: MessageShieldData?, val resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState, val displayThreadSummaries: Boolean, val eventSink: (TimelineEvents) -> Unit, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index b5f9e328dc..e2ed3d4307 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -10,6 +10,7 @@ package io.element.android.features.messages.impl.timeline import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState import io.element.android.features.messages.impl.crypto.sendfailure.resolve.aResolveVerifiedUserSendFailureState +import io.element.android.features.messages.impl.timeline.components.MessageShieldData import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData import io.element.android.features.messages.impl.timeline.model.NewEventState import io.element.android.features.messages.impl.timeline.model.ReadReceiptData @@ -71,7 +72,7 @@ fun aTimelineState( newEventState = NewEventState.None, isLive = isLive, focusRequestState = focusRequestState, - messageShield = messageShield, + messageShieldDialogData = messageShield?.let { MessageShieldData(it) }, resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailureState, displayThreadSummaries = displayThreadSummaries, eventSink = eventSink, @@ -176,7 +177,9 @@ internal fun aTimelineItemEvent( origin = null, timelineItemDebugInfoProvider = { debugInfo }, messageShieldProvider = { messageShield }, - sendHandleProvider = { null } + sendHandleProvider = { null }, + forwarder = null, + forwarderProfile = null, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index 73a15b797f..f8d488c0ba 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -220,7 +220,7 @@ fun TimelineView( @Composable private fun MessageShieldDialog(state: TimelineState) { - val messageShield = state.messageShield ?: return + val messageShield = state.messageShieldDialogData ?: return AlertDialog( content = messageShield.toText(), onDismiss = { state.eventSink.invoke(TimelineEvents.HideShieldDialog) }, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt index 401fd7eec8..541f77f9cb 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt @@ -27,14 +27,17 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight 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.UserId import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails +import io.element.android.libraries.matrix.api.timeline.item.event.getDisplayName import io.element.android.libraries.matrix.api.timeline.item.event.isCritical import io.element.android.libraries.ui.strings.CommonStrings @Composable internal fun MessageShieldView( - shield: MessageShield, - modifier: Modifier = Modifier + shield: MessageShieldData, + modifier: Modifier = Modifier, ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -55,8 +58,24 @@ internal fun MessageShieldView( } } +data class MessageShieldData( + /** + * The message shield that the rust layer thinks we should show. + */ + val shield: MessageShield, + /** + * If the keys to this message were forwarded by another user via history sharing (MSC4268), the ID of that user. + */ + val forwarder: UserId? = null, + /** If [forwarder] is set, the profile of the forwarding user, if it was cached at the time the `EventTimelineItem` was created. */ + val forwarderProfile: ProfileDetails? = null, +) + +val MessageShieldData.isCritical: Boolean + get() = shield.isCritical + @Composable -internal fun MessageShield.toIconColor(): Color { +internal fun MessageShieldData.toIconColor(): Color { return when (isCritical) { true -> ElementTheme.colors.iconCriticalPrimary false -> ElementTheme.colors.iconSecondary @@ -64,7 +83,7 @@ internal fun MessageShield.toIconColor(): Color { } @Composable -private fun MessageShield.toTextColor(): Color { +private fun MessageShieldData.toTextColor(): Color { return when (isCritical) { true -> ElementTheme.colors.textCriticalPrimary false -> ElementTheme.colors.textSecondary @@ -72,9 +91,24 @@ private fun MessageShield.toTextColor(): Color { } @Composable -internal fun MessageShield.toText(): String { +internal fun MessageShieldData.toText(): String { + if (shield is MessageShield.AuthenticityNotGuaranteed && forwarder != null) { + var displayName = forwarderProfile?.getDisplayName() + return if (displayName == null) { + stringResource( + CommonStrings.crypto_event_key_forwarded_unknown_profile_dialog_content, + forwarder.toString(), + ) + } else { + stringResource( + CommonStrings.crypto_event_key_forwarded_known_profile_dialog_content, + displayName, + forwarder.toString(), + ) + } + } return stringResource( - id = when (this) { + id = when (shield) { is MessageShield.AuthenticityNotGuaranteed -> CommonStrings.event_shield_reason_authenticity_not_guaranteed is MessageShield.UnknownDevice -> CommonStrings.event_shield_reason_unknown_device is MessageShield.UnsignedDevice -> CommonStrings.event_shield_reason_unsigned_device @@ -87,8 +121,8 @@ internal fun MessageShield.toText(): String { } @Composable -internal fun MessageShield.toIcon(): ImageVector { - return when (this) { +internal fun MessageShieldData.toIcon(): ImageVector { + return when (shield) { is MessageShield.AuthenticityNotGuaranteed -> CompoundIcons.Info() is MessageShield.UnknownDevice, is MessageShield.UnsignedDevice, @@ -108,25 +142,42 @@ internal fun MessageShieldViewPreview() { verticalArrangement = Arrangement.spacedBy(16.dp), ) { MessageShieldView( - shield = MessageShield.UnknownDevice(true) + shield = MessageShieldData(MessageShield.UnknownDevice(true)) ) MessageShieldView( - shield = MessageShield.UnverifiedIdentity(true) + shield = MessageShieldData(MessageShield.UnverifiedIdentity(true)) ) MessageShieldView( - shield = MessageShield.AuthenticityNotGuaranteed(false) + shield = MessageShieldData(MessageShield.AuthenticityNotGuaranteed(false)) ) MessageShieldView( - shield = MessageShield.UnsignedDevice(false) + shield = MessageShieldData( + MessageShield.AuthenticityNotGuaranteed(false), + forwarder = UserId("@alice:example.com"), + ) ) MessageShieldView( - shield = MessageShield.SentInClear(false) + shield = MessageShieldData( + MessageShield.AuthenticityNotGuaranteed(false), + forwarder = UserId("@alice:example.com"), + forwarderProfile = ProfileDetails.Ready( + displayName = "Alice", + displayNameAmbiguous = false, + avatarUrl = null, + ), + ) ) MessageShieldView( - shield = MessageShield.VerificationViolation(false) + shield = MessageShieldData(MessageShield.UnsignedDevice(false)) ) MessageShieldView( - shield = MessageShield.MismatchedSender(false) + shield = MessageShieldData(MessageShield.SentInClear(false)) + ) + MessageShieldView( + shield = MessageShieldData(MessageShield.VerificationViolation(false)) + ) + MessageShieldView( + shield = MessageShieldData(MessageShield.MismatchedSender(false)) ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt index 77f1f96332..366c88157e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt @@ -118,6 +118,8 @@ class TimelineItemEventFactory( timelineItemDebugInfoProvider = currentTimelineItem.event.timelineItemDebugInfoProvider, messageShieldProvider = currentTimelineItem.event.messageShieldProvider, sendHandleProvider = currentTimelineItem.event.sendHandleProvider, + forwarder = currentTimelineItem.event.forwarder, + forwarderProfile = currentTimelineItem.event.forwarderProfile, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt index 1f01b16012..e169b10403 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt @@ -9,6 +9,7 @@ package io.element.android.features.messages.impl.timeline.model import androidx.compose.runtime.Immutable +import io.element.android.features.messages.impl.timeline.components.MessageShieldData import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent @@ -87,6 +88,13 @@ sealed interface TimelineItem { val timelineItemDebugInfoProvider: TimelineItemDebugInfoProvider, val messageShieldProvider: MessageShieldProvider, val sendHandleProvider: SendHandleProvider, + /** + * If the keys to this message were forwarded by another user via history sharing (MSC4268), the ID of that user. + * If this is non-null, then [messageShieldProvider] will also return [MessageShield.AuthenticityNotGuaranteed]. + */ + val forwarder: UserId?, + /** If [forwarder] is set, the profile of the forwarding user, if it was cached at the time the `EventTimelineItem` was created. */ + val forwarderProfile: ProfileDetails?, ) : TimelineItem { val showSenderInformation = groupPosition.isNew() && !isMine @@ -115,7 +123,9 @@ sealed interface TimelineItem { get() = EventOrTransactionId.from(eventId = eventId, transactionId = transactionId) // No need to be lazy here? - val messageShield: MessageShield? = messageShieldProvider(strict = false) + val messageShield: MessageShieldData? = messageShieldProvider(strict = false)?.let { + MessageShieldData(it, forwarder, forwarderProfile) + } val debugInfo: TimelineItemDebugInfo get() = timelineItemDebugInfoProvider() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt index 00babf4218..516cd9ea77 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt @@ -68,4 +68,6 @@ internal fun aMessageEvent( timelineItemDebugInfoProvider = debugInfoProvider, messageShieldProvider = messageShieldProvider, sendHandleProvider = sendHandleProvider, + forwarder = null, + forwarderProfile = null, ) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt index b84975c6a5..529e7fcdbe 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt @@ -15,6 +15,7 @@ import io.element.android.features.messages.impl.FakeMessagesNavigator import io.element.android.features.messages.impl.crypto.sendfailure.resolve.aResolveVerifiedUserSendFailureState import io.element.android.features.messages.impl.fixtures.aMessageEvent import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator +import io.element.android.features.messages.impl.timeline.components.MessageShieldData import io.element.android.features.messages.impl.timeline.components.aCriticalShield import io.element.android.features.messages.impl.timeline.model.NewEventState import io.element.android.features.messages.impl.timeline.model.TimelineItem @@ -851,14 +852,15 @@ class TimelinePresenterTest { val shield = aCriticalShield() presenter.test { val initialState = awaitFirstItem() - assertThat(initialState.messageShield).isNull() - initialState.eventSink(TimelineEvents.ShowShieldDialog(shield)) + assertThat(initialState.messageShieldDialogData).isNull() + val shieldData = MessageShieldData(shield, null, null) + initialState.eventSink(TimelineEvents.ShowShieldDialog(shieldData)) awaitItem().also { state -> - assertThat(state.messageShield).isEqualTo(shield) + assertThat(state.messageShieldDialogData).isEqualTo(shieldData) state.eventSink(TimelineEvents.HideShieldDialog) } awaitItem().also { state -> - assertThat(state.messageShield).isNull() + assertThat(state.messageShieldDialogData).isNull() } } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt index cdc0a6c9d0..587b8f0839 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollToIndex import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.messages.impl.timeline.components.MessageShieldData import io.element.android.features.messages.impl.timeline.components.aCriticalShield import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent @@ -122,7 +123,7 @@ class TimelineViewTest { eventsRecorder.assertList( listOf( TimelineEvents.OnScrollFinished(0), - TimelineEvents.ShowShieldDialog(MessageShield.UnverifiedIdentity(true)), + TimelineEvents.ShowShieldDialog(MessageShieldData(MessageShield.UnverifiedIdentity(true))), ) ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt index 2a31d9063e..7a7d4cdfd4 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt @@ -47,6 +47,8 @@ class TimelineItemGrouperTest { timelineItemDebugInfoProvider = { aTimelineItemDebugInfo() }, messageShieldProvider = { null }, sendHandleProvider = { FakeSendHandle() }, + forwarder = null, + forwarderProfile = null, ) private val aNonGroupableItem = aMessageEvent() private val aNonGroupableItemNoEvent = TimelineItem.Virtual(UniqueId("virtual"), aTimelineItemDaySeparatorModel("Today")) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt index 8bced93023..a1ffd1f02b 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt @@ -99,6 +99,8 @@ fun aRedactedMatrixTimeline(eventId: EventId) = listOf( }, messageShieldProvider = { null }, sendHandleProvider = { FakeSendHandle() }, + forwarder = null, + forwarderProfile = null, ), ) ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt index 401240f927..f3982e83d2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt @@ -34,6 +34,13 @@ data class EventTimelineItem( val timelineItemDebugInfoProvider: TimelineItemDebugInfoProvider, val messageShieldProvider: MessageShieldProvider, val sendHandleProvider: SendHandleProvider, + /** + * If the keys to this message were forwarded by another user via history sharing (MSC4268), the ID of that user. + * If this is set, then [messageShieldProvider] will also return [MessageShield.AuthenticityNotGuaranteed]. + */ + val forwarder: UserId?, + /** If [forwarder] is set, the profile of the forwarding user, if it was cached at the time this `EventTimelineItem` was created. */ + val forwarderProfile: ProfileDetails?, ) { fun inReplyTo(): InReplyTo? { return (content as? MessageContent)?.inReplyTo diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt index a95b2acccc..297b0b31a4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -59,7 +59,9 @@ class EventTimelineItemMapper( origin = origin?.map(), timelineItemDebugInfoProvider = { lazyProvider.debugInfo().map() }, messageShieldProvider = { strict -> lazyProvider.getShields(strict).map() }, - sendHandleProvider = { lazyProvider.getSendHandle()?.let(::RustSendHandle) } + sendHandleProvider = { lazyProvider.getSendHandle()?.let(::RustSendHandle) }, + forwarder = forwarder?.let { UserId(it) }, + forwarderProfile = forwarderProfile?.map(), ) } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt index 24c26a6734..2825e7c5be 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt @@ -77,6 +77,8 @@ fun anEventTimelineItem( timelineItemDebugInfoProvider = debugInfoProvider, messageShieldProvider = messageShieldProvider, sendHandleProvider = sendHandleProvider, + forwarder = null, + forwarderProfile = null, ) fun aProfileDetails( diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_MessageShieldView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_MessageShieldView_Day_0_en.png index b2de7d213c..bdc463b94b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_MessageShieldView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_MessageShieldView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c46805397e92a263c4c19aa67c6a7b6ada90323604f1171c9dae21b29aa0d425 -size 45307 +oid sha256:d01613c76c2d699c8a53147debfebd20e9029d1f14dc1af8f664b9f707c099a5 +size 66532 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_MessageShieldView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_MessageShieldView_Night_0_en.png index f3d7df2758..4bdcc92bb4 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_MessageShieldView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_MessageShieldView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad9ebc7d4700f7f8b50c478bbf996e3d3b7d9b7f267c27fff9108274fb6d3f28 -size 43948 +oid sha256:2a7459fafcfa5f366e51b5ba64a4b9a9fe4f0af96f8c52c2af174a6f6206eba6 +size 64563