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:
parent
d4aca61a07
commit
ad622b0ac2
18 changed files with 124 additions and 38 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c46805397e92a263c4c19aa67c6a7b6ada90323604f1171c9dae21b29aa0d425
|
||||
size 45307
|
||||
oid sha256:d01613c76c2d699c8a53147debfebd20e9029d1f14dc1af8f664b9f707c099a5
|
||||
size 66532
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ad9ebc7d4700f7f8b50c478bbf996e3d3b7d9b7f267c27fff9108274fb6d3f28
|
||||
size 43948
|
||||
oid sha256:2a7459fafcfa5f366e51b5ba64a4b9a9fe4f0af96f8c52c2af174a6f6206eba6
|
||||
size 64563
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue