Display a badge for messages decrypted using shared keys (#6023)
The EXA side of element-hq/element-meta#2877: if the keys for a message have been forwarded by another user, indicate that in the UI via the text shown when tapping the event shield.
This commit is contained in:
committed by
GitHub
parent
a9c1da5aac
commit
ae76e8b0ea
@@ -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
|
||||
|
||||
|
||||
@@ -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<UniqueId?>(null) }
|
||||
|
||||
val newEventState = remember { mutableStateOf(NewEventState.None) }
|
||||
val messageShield: MutableState<MessageShield?> = remember { mutableStateOf(null) }
|
||||
val messageShieldDialogData: MutableState<MessageShieldData?> = 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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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) },
|
||||
|
||||
@@ -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))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -68,4 +68,6 @@ internal fun aMessageEvent(
|
||||
timelineItemDebugInfoProvider = debugInfoProvider,
|
||||
messageShieldProvider = messageShieldProvider,
|
||||
sendHandleProvider = sendHandleProvider,
|
||||
forwarder = null,
|
||||
forwarderProfile = null,
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -99,6 +99,8 @@ fun aRedactedMatrixTimeline(eventId: EventId) = listOf<MatrixTimelineItem>(
|
||||
},
|
||||
messageShieldProvider = { null },
|
||||
sendHandleProvider = { FakeSendHandle() },
|
||||
forwarder = null,
|
||||
forwarderProfile = null,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,8 @@ fun anEventTimelineItem(
|
||||
timelineItemDebugInfoProvider = debugInfoProvider,
|
||||
messageShieldProvider = messageShieldProvider,
|
||||
sendHandleProvider = sendHandleProvider,
|
||||
forwarder = null,
|
||||
forwarderProfile = null,
|
||||
)
|
||||
|
||||
fun aProfileDetails(
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user