Bump rust-sdk version to rust-sdk 0.2.57 (#3735)
* Bump rust-sdk version to rust-sdk 0.2.57 * rust sdk update: Support persisted WedgeQueueError * Trust & Decoration | Support new expected UTD causes * Room Subscribtion settings not needed anymore (see https://github.com/matrix-org/matrix-rust-sdk/pull/4159) * File/Attachement upload: update to support `storeInCache` * feat(knock): update API to use reason and serverNames * Add another `Konsist` exception * Update screenshots --------- Co-authored-by: Jorge Martín <jorgem@element.io> Co-authored-by: ElementBot <android@element.io> Co-authored-by: Benoit Marty <benoit@matrix.org>
This commit is contained in:
parent
f44c8dd452
commit
9fb68fc58e
23 changed files with 223 additions and 58 deletions
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.messages.impl.timeline.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UtdCause
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemEventRowUtdPreview() = ElementPreview {
|
||||
Column {
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
senderDisplayName = "Alice",
|
||||
isMine = false,
|
||||
content = TimelineItemEncryptedContent(
|
||||
data = UnableToDecryptContent.Data.MegolmV1AesSha2(
|
||||
sessionId = "sessionId",
|
||||
utdCause = UtdCause.UnsignedDevice,
|
||||
)
|
||||
),
|
||||
timelineItemReactions = aTimelineItemReactions(count = 0),
|
||||
groupPosition = TimelineItemGroupPosition.First,
|
||||
),
|
||||
)
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
senderDisplayName = "Bob",
|
||||
isMine = false,
|
||||
content = TimelineItemEncryptedContent(
|
||||
data = UnableToDecryptContent.Data.MegolmV1AesSha2(
|
||||
sessionId = "sessionId",
|
||||
utdCause = UtdCause.VerificationViolation,
|
||||
)
|
||||
),
|
||||
groupPosition = TimelineItemGroupPosition.First,
|
||||
timelineItemReactions = aTimelineItemReactions(count = 0)
|
||||
),
|
||||
)
|
||||
|
||||
ATimelineItemEventRow(
|
||||
event = aTimelineItemEvent(
|
||||
senderDisplayName = "Bob",
|
||||
isMine = false,
|
||||
content = TimelineItemEncryptedContent(
|
||||
data = UnableToDecryptContent.Data.MegolmV1AesSha2(
|
||||
sessionId = "sessionId",
|
||||
utdCause = UtdCause.SentBeforeWeJoined,
|
||||
)
|
||||
),
|
||||
groupPosition = TimelineItemGroupPosition.Last,
|
||||
timelineItemReactions = aTimelineItemReactions(count = 0)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -27,11 +27,28 @@ fun TimelineItemEncryptedView(
|
|||
onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val isMembershipUtd = (content.data as? UnableToDecryptContent.Data.MegolmV1AesSha2)?.utdCause == UtdCause.Membership
|
||||
val (textId, iconId) = if (isMembershipUtd) {
|
||||
CommonStrings.common_unable_to_decrypt_no_access to CompoundDrawables.ic_compound_block
|
||||
} else {
|
||||
CommonStrings.common_waiting_for_decryption_key to CompoundDrawables.ic_compound_time
|
||||
val (textId, iconId) = when (content.data) {
|
||||
is UnableToDecryptContent.Data.MegolmV1AesSha2 -> {
|
||||
when (content.data.utdCause) {
|
||||
UtdCause.SentBeforeWeJoined -> {
|
||||
CommonStrings.common_unable_to_decrypt_no_access to CompoundDrawables.ic_compound_block
|
||||
}
|
||||
UtdCause.VerificationViolation -> {
|
||||
CommonStrings.common_unable_to_decrypt_verification_violation to CompoundDrawables.ic_compound_block
|
||||
}
|
||||
UtdCause.UnsignedDevice,
|
||||
UtdCause.UnknownDevice -> {
|
||||
CommonStrings.common_unable_to_decrypt_insecure_device to CompoundDrawables.ic_compound_block
|
||||
}
|
||||
else -> {
|
||||
CommonStrings.common_waiting_for_decryption_key to CompoundDrawables.ic_compound_time
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Should not happen, we only supports megolm in rooms
|
||||
CommonStrings.common_waiting_for_decryption_key to CompoundDrawables.ic_compound_time
|
||||
}
|
||||
}
|
||||
TimelineItemInformativeView(
|
||||
text = stringResource(id = textId),
|
||||
|
|
|
|||
|
|
@ -18,7 +18,19 @@ open class TimelineItemEncryptedContentProvider : PreviewParameterProvider<Timel
|
|||
aTimelineItemEncryptedContent(
|
||||
data = UnableToDecryptContent.Data.MegolmV1AesSha2(
|
||||
sessionId = "sessionId",
|
||||
utdCause = UtdCause.Membership,
|
||||
utdCause = UtdCause.SentBeforeWeJoined,
|
||||
)
|
||||
),
|
||||
aTimelineItemEncryptedContent(
|
||||
data = UnableToDecryptContent.Data.MegolmV1AesSha2(
|
||||
sessionId = "sessionId",
|
||||
utdCause = UtdCause.VerificationViolation,
|
||||
)
|
||||
),
|
||||
aTimelineItemEncryptedContent(
|
||||
data = UnableToDecryptContent.Data.MegolmV1AesSha2(
|
||||
sessionId = "sessionId",
|
||||
utdCause = UtdCause.UnsignedDevice,
|
||||
)
|
||||
),
|
||||
aTimelineItemEncryptedContent(
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ jsoup = "org.jsoup:jsoup:1.18.1"
|
|||
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
|
||||
molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0"
|
||||
timber = "com.jakewharton.timber:timber:5.0.1"
|
||||
matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.56"
|
||||
matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.57"
|
||||
matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" }
|
||||
matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" }
|
||||
sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
|
||||
|
|
@ -193,7 +193,7 @@ zxing_cpp = "io.github.zxing-cpp:android:2.2.0"
|
|||
posthog = "com.posthog:posthog-android:3.8.2"
|
||||
sentry = "io.sentry:sentry-android:7.15.0"
|
||||
# main branch can be tested replacing the version with main-SNAPSHOT
|
||||
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.25.0"
|
||||
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.27.0"
|
||||
|
||||
# Emojibase
|
||||
matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.3.3"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ sealed interface LocalEventSendState {
|
|||
data object Sending : LocalEventSendState
|
||||
sealed interface Failed : LocalEventSendState {
|
||||
data class Unknown(val error: String) : Failed
|
||||
data object CrossSigningNotSetup : Failed
|
||||
data object SendingFromUnverifiedDevice : Failed
|
||||
|
||||
sealed interface VerifiedUser : Failed
|
||||
|
|
|
|||
|
|
@ -9,5 +9,8 @@ package io.element.android.libraries.matrix.api.timeline.item.event
|
|||
|
||||
enum class UtdCause {
|
||||
Unknown,
|
||||
Membership,
|
||||
SentBeforeWeJoined,
|
||||
VerificationViolation,
|
||||
UnsignedDevice,
|
||||
UnknownDevice
|
||||
}
|
||||
|
|
|
|||
|
|
@ -398,7 +398,7 @@ class RustMatrixClient(
|
|||
sessionDispatcher
|
||||
) {
|
||||
runCatching {
|
||||
client.knock(roomIdOrAlias.identifier).destroy()
|
||||
client.knock(roomIdOrAlias.identifier, message, serverNames).destroy()
|
||||
try {
|
||||
awaitRoom(roomIdOrAlias, 10.seconds, CurrentUserMembership.KNOCKED)
|
||||
} catch (e: Exception) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,12 @@ class UtdTracker(
|
|||
Timber.d("onUtd for event ${info.eventId}, timeToDecryptMs: ${info.timeToDecryptMs}")
|
||||
val name = when (info.cause) {
|
||||
UtdCause.UNKNOWN -> Error.Name.OlmKeysNotSentError
|
||||
UtdCause.MEMBERSHIP -> Error.Name.ExpectedDueToMembership
|
||||
UtdCause.SENT_BEFORE_WE_JOINED -> Error.Name.ExpectedDueToMembership
|
||||
UtdCause.VERIFICATION_VIOLATION -> Error.Name.ExpectedVerificationViolation
|
||||
UtdCause.UNSIGNED_DEVICE,
|
||||
UtdCause.UNKNOWN_DEVICE -> {
|
||||
Error.Name.ExpectedSentByInsecureDevice
|
||||
}
|
||||
}
|
||||
val event = Error(
|
||||
context = null,
|
||||
|
|
|
|||
|
|
@ -9,18 +9,13 @@ package io.element.android.libraries.matrix.impl.room
|
|||
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.RequiredState
|
||||
import org.matrix.rustcomponents.sdk.RoomListService
|
||||
import org.matrix.rustcomponents.sdk.RoomSubscription
|
||||
import timber.log.Timber
|
||||
|
||||
private const val DEFAULT_TIMELINE_LIMIT = 20u
|
||||
|
||||
class RoomSyncSubscriber(
|
||||
private val roomListService: RoomListService,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
|
|
@ -28,28 +23,13 @@ class RoomSyncSubscriber(
|
|||
private val subscribedRoomIds = mutableSetOf<RoomId>()
|
||||
private val mutex = Mutex()
|
||||
|
||||
private val settings = RoomSubscription(
|
||||
requiredState = listOf(
|
||||
RequiredState(key = EventType.STATE_ROOM_NAME, value = ""),
|
||||
RequiredState(key = EventType.STATE_ROOM_TOPIC, value = ""),
|
||||
RequiredState(key = EventType.STATE_ROOM_AVATAR, value = ""),
|
||||
RequiredState(key = EventType.STATE_ROOM_CANONICAL_ALIAS, value = ""),
|
||||
RequiredState(key = EventType.STATE_ROOM_JOIN_RULES, value = ""),
|
||||
RequiredState(key = EventType.STATE_ROOM_POWER_LEVELS, value = ""),
|
||||
RequiredState(key = EventType.STATE_ROOM_PINNED_EVENT, value = ""),
|
||||
),
|
||||
timelineLimit = DEFAULT_TIMELINE_LIMIT,
|
||||
// We don't need heroes here as they're already included in the `all_rooms` list
|
||||
includeHeroes = false,
|
||||
)
|
||||
|
||||
suspend fun subscribe(roomId: RoomId) {
|
||||
mutex.withLock {
|
||||
withContext(dispatchers.io) {
|
||||
try {
|
||||
if (!isSubscribedTo(roomId)) {
|
||||
Timber.d("Subscribing to room $roomId}")
|
||||
roomListService.subscribeToRooms(listOf(roomId.value), settings)
|
||||
roomListService.subscribeToRooms(listOf(roomId.value))
|
||||
}
|
||||
subscribedRoomIds.add(roomId)
|
||||
} catch (exception: Exception) {
|
||||
|
|
@ -65,7 +45,7 @@ class RoomSyncSubscriber(
|
|||
val roomIdsToSubscribeTo = roomIds.filterNot { isSubscribedTo(it) }
|
||||
if (roomIdsToSubscribeTo.isNotEmpty()) {
|
||||
Timber.d("Subscribing to rooms: $roomIds")
|
||||
roomListService.subscribeToRooms(roomIdsToSubscribeTo.map { it.value }, settings)
|
||||
roomListService.subscribeToRooms(roomIdsToSubscribeTo.map { it.value })
|
||||
subscribedRoomIds.addAll(roomIds)
|
||||
}
|
||||
} catch (cancellationException: CancellationException) {
|
||||
|
|
|
|||
|
|
@ -339,6 +339,7 @@ class RustTimeline(
|
|||
formattedCaption = formattedBody?.let {
|
||||
FormattedBody(body = it, format = MessageFormat.Html)
|
||||
},
|
||||
storeInCache = true,
|
||||
progressWatcher = progressCallback?.toProgressWatcher()
|
||||
)
|
||||
}
|
||||
|
|
@ -361,6 +362,7 @@ class RustTimeline(
|
|||
formattedCaption = formattedBody?.let {
|
||||
FormattedBody(body = it, format = MessageFormat.Html)
|
||||
},
|
||||
storeInCache = true,
|
||||
progressWatcher = progressCallback?.toProgressWatcher()
|
||||
)
|
||||
}
|
||||
|
|
@ -374,6 +376,7 @@ class RustTimeline(
|
|||
// Maybe allow a caption in the future?
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
storeInCache = true,
|
||||
progressWatcher = progressCallback?.toProgressWatcher()
|
||||
)
|
||||
}
|
||||
|
|
@ -381,7 +384,7 @@ class RustTimeline(
|
|||
|
||||
override suspend fun sendFile(file: File, fileInfo: FileInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler> {
|
||||
return sendAttachment(listOf(file)) {
|
||||
inner.sendFile(file.path, fileInfo.map(), progressCallback?.toProgressWatcher())
|
||||
inner.sendFile(file.path, fileInfo.map(), false, progressCallback?.toProgressWatcher())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -496,6 +499,7 @@ class RustTimeline(
|
|||
// Maybe allow a caption in the future?
|
||||
caption = null,
|
||||
formattedCaption = null,
|
||||
storeInCache = true,
|
||||
progressWatcher = progressCallback?.toProgressWatcher(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.matrix.rustcomponents.sdk.EventOrTransactionId
|
||||
import org.matrix.rustcomponents.sdk.EventSendState
|
||||
import org.matrix.rustcomponents.sdk.QueueWedgeError
|
||||
import org.matrix.rustcomponents.sdk.Reaction
|
||||
import org.matrix.rustcomponents.sdk.ShieldState
|
||||
import uniffi.matrix_sdk_common.ShieldStateCode
|
||||
|
|
@ -78,25 +78,31 @@ fun RustEventSendState?.map(): LocalEventSendState? {
|
|||
null -> null
|
||||
RustEventSendState.NotSentYet -> LocalEventSendState.Sending
|
||||
is RustEventSendState.SendingFailed -> {
|
||||
if (isRecoverable) {
|
||||
LocalEventSendState.Sending
|
||||
} else {
|
||||
LocalEventSendState.Failed.Unknown(error)
|
||||
when (val queueWedgeError = error) {
|
||||
QueueWedgeError.CrossVerificationRequired -> {
|
||||
// The current device is not cross-signed (or cross signing is not setup)
|
||||
LocalEventSendState.Failed.SendingFromUnverifiedDevice
|
||||
}
|
||||
is QueueWedgeError.IdentityViolations -> {
|
||||
LocalEventSendState.Failed.VerifiedUserChangedIdentity(queueWedgeError.users.map { UserId(it) })
|
||||
}
|
||||
is QueueWedgeError.InsecureDevices -> {
|
||||
LocalEventSendState.Failed.VerifiedUserHasUnsignedDevice(
|
||||
devices = queueWedgeError.userDeviceMap.entries.associate { entry ->
|
||||
UserId(entry.key) to entry.value.map { DeviceId(it) }
|
||||
}
|
||||
)
|
||||
}
|
||||
is QueueWedgeError.GenericApiError -> {
|
||||
if (isRecoverable) {
|
||||
LocalEventSendState.Sending
|
||||
} else {
|
||||
LocalEventSendState.Failed.Unknown(queueWedgeError.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is RustEventSendState.Sent -> LocalEventSendState.Sent(EventId(eventId))
|
||||
is RustEventSendState.VerifiedUserChangedIdentity -> {
|
||||
LocalEventSendState.Failed.VerifiedUserChangedIdentity(users.map { UserId(it) })
|
||||
}
|
||||
is RustEventSendState.VerifiedUserHasUnsignedDevice -> {
|
||||
LocalEventSendState.Failed.VerifiedUserHasUnsignedDevice(
|
||||
devices = devices.entries.associate { entry ->
|
||||
UserId(entry.key) to entry.value.map { DeviceId(it) }
|
||||
}
|
||||
)
|
||||
}
|
||||
EventSendState.CrossSigningNotSetup -> LocalEventSendState.Failed.CrossSigningNotSetup
|
||||
EventSendState.SendingFromUnverifiedDevice -> LocalEventSendState.Failed.SendingFromUnverifiedDevice
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -140,8 +140,11 @@ private fun RustMembershipChange.map(): MembershipChange {
|
|||
|
||||
private fun RustUtdCause.map(): UtdCause {
|
||||
return when (this) {
|
||||
RustUtdCause.MEMBERSHIP -> UtdCause.Membership
|
||||
RustUtdCause.SENT_BEFORE_WE_JOINED -> UtdCause.SentBeforeWeJoined
|
||||
RustUtdCause.UNKNOWN -> UtdCause.Unknown
|
||||
RustUtdCause.VERIFICATION_VIOLATION -> UtdCause.VerificationViolation
|
||||
RustUtdCause.UNSIGNED_DEVICE -> UtdCause.UnsignedDevice
|
||||
RustUtdCause.UNKNOWN_DEVICE -> UtdCause.UnknownDevice
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ class UtdTrackerTest {
|
|||
UnableToDecryptInfo(
|
||||
eventId = AN_EVENT_ID.value,
|
||||
timeToDecryptMs = 123.toULong(),
|
||||
cause = UtdCause.MEMBERSHIP,
|
||||
cause = UtdCause.SENT_BEFORE_WE_JOINED,
|
||||
)
|
||||
)
|
||||
assertThat(fakeAnalyticsService.capturedEvents).containsExactly(
|
||||
|
|
@ -90,4 +90,50 @@ class UtdTrackerTest {
|
|||
assertThat(fakeAnalyticsService.screenEvents).isEmpty()
|
||||
assertThat(fakeAnalyticsService.trackedErrors).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when onUtd is called with insecure cause, the expected analytics Event is sent`() {
|
||||
val fakeAnalyticsService = FakeAnalyticsService()
|
||||
val sut = UtdTracker(fakeAnalyticsService)
|
||||
sut.onUtd(
|
||||
UnableToDecryptInfo(
|
||||
eventId = AN_EVENT_ID.value,
|
||||
timeToDecryptMs = 123.toULong(),
|
||||
cause = UtdCause.UNSIGNED_DEVICE,
|
||||
)
|
||||
)
|
||||
assertThat(fakeAnalyticsService.capturedEvents).containsExactly(
|
||||
Error(
|
||||
context = null,
|
||||
cryptoModule = Error.CryptoModule.Rust,
|
||||
cryptoSDK = Error.CryptoSDK.Rust,
|
||||
timeToDecryptMillis = 123,
|
||||
domain = Error.Domain.E2EE,
|
||||
name = Error.Name.ExpectedSentByInsecureDevice
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when onUtd is called with verification violation cause, the expected analytics Event is sent`() {
|
||||
val fakeAnalyticsService = FakeAnalyticsService()
|
||||
val sut = UtdTracker(fakeAnalyticsService)
|
||||
sut.onUtd(
|
||||
UnableToDecryptInfo(
|
||||
eventId = AN_EVENT_ID.value,
|
||||
timeToDecryptMs = 123.toULong(),
|
||||
cause = UtdCause.VERIFICATION_VIOLATION,
|
||||
)
|
||||
)
|
||||
assertThat(fakeAnalyticsService.capturedEvents).containsExactly(
|
||||
Error(
|
||||
context = null,
|
||||
cryptoModule = Error.CryptoModule.Rust,
|
||||
cryptoSDK = Error.CryptoSDK.Rust,
|
||||
timeToDecryptMillis = 123,
|
||||
domain = Error.Domain.E2EE,
|
||||
name = Error.Name.ExpectedVerificationViolation
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,7 +238,9 @@ Reason: %1$s."</string>
|
|||
<string name="common_topic">"Topic"</string>
|
||||
<string name="common_topic_placeholder">"What is this room about?"</string>
|
||||
<string name="common_unable_to_decrypt">"Unable to decrypt"</string>
|
||||
<string name="common_unable_to_decrypt_insecure_device">"Sent from an insecure device"</string>
|
||||
<string name="common_unable_to_decrypt_no_access">"You don\'t have access to this message"</string>
|
||||
<string name="common_unable_to_decrypt_verification_violation">"Sender\'s verified identity has changed"</string>
|
||||
<string name="common_unable_to_invite_message">"Invites couldn\'t be sent to one or more users."</string>
|
||||
<string name="common_unable_to_invite_title">"Unable to send invite(s)"</string>
|
||||
<string name="common_unlock">"Unlock"</string>
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ class KonsistPreviewTest {
|
|||
"TimelineItemEventRowForDirectRoomPreview",
|
||||
"TimelineItemEventRowShieldPreview",
|
||||
"TimelineItemEventRowTimestampPreview",
|
||||
"TimelineItemEventRowUtdPreview",
|
||||
"TimelineItemEventRowWithManyReactionsPreview",
|
||||
"TimelineItemEventRowWithRRPreview",
|
||||
"TimelineItemEventRowWithReplyPreview",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fee2af8462597d58274b7168c5cfe1f6d6b0909b048adc0f4fc5b3c12a90b859
|
||||
size 8861
|
||||
oid sha256:cfc3dc1420a58d25b5fd248f0b77c3c336bb03c402c904d0678c35a5abe291f2
|
||||
size 10753
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8beabd7aadf0324e9b66278c49fdd896b1451a805fd15e69aa6c4af37cb7c150
|
||||
size 9055
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fee2af8462597d58274b7168c5cfe1f6d6b0909b048adc0f4fc5b3c12a90b859
|
||||
size 8861
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d4f48cd7dc7e2bbf7363d1127e46721c25bb8dd887927dbcffe525f5bb5bae01
|
||||
size 8781
|
||||
oid sha256:d4d9879eda9ffa9c171c5351d9ef8e7b32cad50996717445c9b87cdad8a3d973
|
||||
size 10634
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:077f898ad7822b6a5ff54429bc91e4faef377fb06de28a4cea73483e6adc5ffd
|
||||
size 9025
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d4f48cd7dc7e2bbf7363d1127e46721c25bb8dd887927dbcffe525f5bb5bae01
|
||||
size 8781
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b1f04171ef894f299b58cc15158daf9655cc04f9ab2418973e7e6e78e22884e8
|
||||
size 31693
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8ce15bbbb3bc562f92a7d07a0f2f907d4e351e0b7133a61b7ef49540ef622c15
|
||||
size 30613
|
||||
Loading…
Add table
Add a link
Reference in a new issue