Merge pull request #5426 from element-hq/feature/bma/incomingVerificationA11y

Improvement and bugfix on incoming verification request screen
This commit is contained in:
Benoit Marty 2025-09-29 18:38:52 +02:00 committed by GitHub
commit 19ceee2680
22 changed files with 101 additions and 77 deletions

View file

@ -155,7 +155,7 @@ class IncomingVerificationPresenter(
StateMachineState.RejectingIncomingVerification, StateMachineState.RejectingIncomingVerification,
null -> { null -> {
Step.Initial( Step.Initial(
deviceDisplayName = sessionVerificationRequestDetails.senderProfile.displayName ?: sessionVerificationRequestDetails.deviceId.value, deviceDisplayName = sessionVerificationRequestDetails.deviceDisplayName,
deviceId = sessionVerificationRequestDetails.deviceId, deviceId = sessionVerificationRequestDetails.deviceId,
formattedSignInTime = formattedSignInTime, formattedSignInTime = formattedSignInTime,
isWaiting = machineState == StateMachineState.AcceptingIncomingVerification || isWaiting = machineState == StateMachineState.AcceptingIncomingVerification ||

View file

@ -22,7 +22,7 @@ data class IncomingVerificationState(
@Stable @Stable
sealed interface Step { sealed interface Step {
data class Initial( data class Initial(
val deviceDisplayName: String, val deviceDisplayName: String?,
val deviceId: DeviceId, val deviceId: DeviceId,
val formattedSignInTime: String, val formattedSignInTime: String,
val isWaiting: Boolean, val isWaiting: Boolean,

View file

@ -14,6 +14,7 @@ import io.element.android.features.verifysession.impl.ui.aEmojisSessionVerificat
import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.FlowId import io.element.android.libraries.matrix.api.core.FlowId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails
import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.libraries.matrix.api.verification.VerificationRequest
@ -55,26 +56,28 @@ internal fun aStepInitial(
internal fun anIncomingSessionVerificationRequest() = VerificationRequest.Incoming.OtherSession( internal fun anIncomingSessionVerificationRequest() = VerificationRequest.Incoming.OtherSession(
details = SessionVerificationRequestDetails( details = SessionVerificationRequestDetails(
senderProfile = SessionVerificationRequestDetails.SenderProfile( senderProfile = MatrixUser(
userId = UserId("@alice:example.com"), userId = UserId("@alice:example.com"),
displayName = "Alice", displayName = "Alice",
avatarUrl = null, avatarUrl = null,
), ),
flowId = FlowId("1234"), flowId = FlowId("1234"),
deviceId = DeviceId("ILAKNDNASDLK"), deviceId = DeviceId("ILAKNDNASDLK"),
deviceDisplayName = "a device name",
firstSeenTimestamp = 0, firstSeenTimestamp = 0,
) )
) )
internal fun anIncomingUserVerificationRequest() = VerificationRequest.Incoming.User( internal fun anIncomingUserVerificationRequest() = VerificationRequest.Incoming.User(
details = SessionVerificationRequestDetails( details = SessionVerificationRequestDetails(
senderProfile = SessionVerificationRequestDetails.SenderProfile( senderProfile = MatrixUser(
userId = UserId("@alice:example.com"), userId = UserId("@alice:example.com"),
displayName = "Alice", displayName = "Alice",
avatarUrl = null, avatarUrl = null,
), ),
flowId = FlowId("1234"), flowId = FlowId("1234"),
deviceId = DeviceId("ILAKNDNASDLK"), deviceId = DeviceId("ILAKNDNASDLK"),
deviceDisplayName = "a device name",
firstSeenTimestamp = 0, firstSeenTimestamp = 0,
) )
) )

View file

@ -26,6 +26,7 @@ import androidx.compose.ui.semantics.focused
import androidx.compose.ui.semantics.progressBarRangeInfo import androidx.compose.ui.semantics.progressBarRangeInfo
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
@ -214,9 +215,7 @@ private fun ContentInitial(
.padding(top = 24.dp), .padding(top = 24.dp),
) { ) {
VerificationUserProfileContent( VerificationUserProfileContent(
userId = request.details.senderProfile.userId, user = request.details.senderProfile,
displayName = request.details.senderProfile.displayName,
avatarUrl = request.details.senderProfile.avatarUrl,
) )
} }
} }
@ -292,3 +291,11 @@ internal fun IncomingVerificationViewPreview(@PreviewParameter(IncomingVerificat
state = state, state = state,
) )
} }
@Preview
@Composable
internal fun IncomingVerificationViewA11yPreview() = ElementPreview {
IncomingVerificationView(
state = anIncomingVerificationState(),
)
}

View file

@ -34,7 +34,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun SessionDetailsView( fun SessionDetailsView(
deviceName: String, deviceName: String?,
deviceId: DeviceId, deviceId: DeviceId,
signInFormattedTimestamp: String, signInFormattedTimestamp: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -61,7 +61,7 @@ fun SessionDetailsView(
resourceId = CompoundDrawables.ic_compound_devices resourceId = CompoundDrawables.ic_compound_devices
) )
Text( Text(
text = deviceName, text = deviceName ?: deviceId.value,
style = ElementTheme.typography.fontBodyMdMedium, style = ElementTheme.typography.fontBodyMdMedium,
color = ElementTheme.colors.textPrimary, color = ElementTheme.colors.textPrimary,
) )
@ -87,9 +87,16 @@ fun SessionDetailsView(
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
internal fun SessionDetailsViewPreview() = ElementPreview { internal fun SessionDetailsViewPreview() = ElementPreview {
SessionDetailsView( Column {
deviceName = "Element X Android", SessionDetailsView(
deviceId = DeviceId("ILAKNDNASDLK"), deviceName = "Element X Android",
signInFormattedTimestamp = "12:34", deviceId = DeviceId("ILAKNDNASDLK"),
) signInFormattedTimestamp = "12:34",
)
SessionDetailsView(
deviceName = null,
deviceId = DeviceId("ILAKNDNASDLK"),
signInFormattedTimestamp = "12:34",
)
}
} }

View file

@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -23,7 +24,6 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.components.avatar.AvatarType
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
@ -31,18 +31,21 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.model.getBestName
/**
* Ref: https://www.figma.com/design/lMrKOhS8BEb75GXVq7FnNI/ER-96--User-Verification-by-Emoji?node-id=116-52049
*/
@Composable @Composable
fun VerificationUserProfileContent( fun VerificationUserProfileContent(
userId: UserId, user: MatrixUser,
displayName: String?,
avatarUrl: String?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val avatarData = remember(userId, displayName, avatarUrl) { val avatarData = remember(user) {
AvatarData(id = userId.value, name = displayName, url = avatarUrl, size = AvatarSize.UserVerification) user.getAvatarData(AvatarSize.UserVerification)
} }
Row( Row(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
@ -55,12 +58,20 @@ fun VerificationUserProfileContent(
avatarData = avatarData, avatarData = avatarData,
avatarType = AvatarType.User, avatarType = AvatarType.User,
) )
Spacer(modifier = Modifier.padding(12.dp)) Spacer(modifier = Modifier.width(12.dp))
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text(text = displayName ?: userId.value, style = ElementTheme.typography.fontBodyLgMedium, color = ElementTheme.colors.textPrimary) Text(
text = user.getBestName(),
style = ElementTheme.typography.fontBodyLgMedium,
color = ElementTheme.colors.textPrimary,
)
if (displayName != null) { if (user.displayName.isNullOrEmpty().not()) {
Text(text = userId.value, style = ElementTheme.typography.fontBodyMdRegular, color = ElementTheme.colors.textSecondary) Text(
text = user.userId.value,
style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.colors.textSecondary,
)
} }
} }
} }
@ -72,8 +83,10 @@ internal fun VerificationUserProfileContentPreview() = ElementPreview(
drawableFallbackForImages = CommonDrawables.sample_avatar drawableFallbackForImages = CommonDrawables.sample_avatar
) { ) {
VerificationUserProfileContent( VerificationUserProfileContent(
userId = UserId("@alice:example.com"), user = MatrixUser(
displayName = "Alice", userId = UserId("@alice:example.com"),
avatarUrl = "https://example.com/avatar.png", displayName = "Alice",
avatarUrl = "https://example.com/avatar.png",
)
) )
} }

View file

@ -12,6 +12,7 @@ import io.element.android.features.verifysession.impl.ui.aEmojisSessionVerificat
import io.element.android.libraries.dateformatter.api.DateFormatter import io.element.android.libraries.dateformatter.api.DateFormatter
import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.matrix.api.core.FlowId import io.element.android.libraries.matrix.api.core.FlowId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails
import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.VerificationFlowState import io.element.android.libraries.matrix.api.verification.VerificationFlowState
@ -293,13 +294,14 @@ class IncomingVerificationPresenterTest {
private val anIncomingSessionVerificationRequest = VerificationRequest.Incoming.OtherSession( private val anIncomingSessionVerificationRequest = VerificationRequest.Incoming.OtherSession(
details = SessionVerificationRequestDetails( details = SessionVerificationRequestDetails(
senderProfile = SessionVerificationRequestDetails.SenderProfile( senderProfile = MatrixUser(
userId = A_USER_ID, userId = A_USER_ID,
displayName = "a device name", displayName = "a user name",
avatarUrl = null, avatarUrl = null,
), ),
flowId = FlowId("flowId"), flowId = FlowId("flowId"),
deviceId = A_DEVICE_ID, deviceId = A_DEVICE_ID,
deviceDisplayName = "a device name",
firstSeenTimestamp = A_TIMESTAMP, firstSeenTimestamp = A_TIMESTAMP,
) )
) )

View file

@ -10,20 +10,14 @@ package io.element.android.libraries.matrix.api.verification
import android.os.Parcelable import android.os.Parcelable
import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.FlowId import io.element.android.libraries.matrix.api.core.FlowId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
data class SessionVerificationRequestDetails( data class SessionVerificationRequestDetails(
val senderProfile: SenderProfile, val senderProfile: MatrixUser,
val flowId: FlowId, val flowId: FlowId,
val deviceId: DeviceId, val deviceId: DeviceId,
val deviceDisplayName: String?,
val firstSeenTimestamp: Long, val firstSeenTimestamp: Long,
) : Parcelable { ) : Parcelable
@Parcelize
data class SenderProfile(
val userId: UserId,
val displayName: String?,
val avatarUrl: String?,
) : Parcelable
}

View file

@ -51,6 +51,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.impl.encryption.RustEncryptionService import io.element.android.libraries.matrix.impl.encryption.RustEncryptionService
import io.element.android.libraries.matrix.impl.exception.mapClientException import io.element.android.libraries.matrix.impl.exception.mapClientException
import io.element.android.libraries.matrix.impl.mapper.map
import io.element.android.libraries.matrix.impl.media.RustMediaLoader import io.element.android.libraries.matrix.impl.media.RustMediaLoader
import io.element.android.libraries.matrix.impl.media.RustMediaPreviewService import io.element.android.libraries.matrix.impl.media.RustMediaPreviewService
import io.element.android.libraries.matrix.impl.notification.RustNotificationService import io.element.android.libraries.matrix.impl.notification.RustNotificationService
@ -75,7 +76,6 @@ import io.element.android.libraries.matrix.impl.roomlist.roomOrNull
import io.element.android.libraries.matrix.impl.spaces.RustSpaceService import io.element.android.libraries.matrix.impl.spaces.RustSpaceService
import io.element.android.libraries.matrix.impl.sync.RustSyncService import io.element.android.libraries.matrix.impl.sync.RustSyncService
import io.element.android.libraries.matrix.impl.sync.map import io.element.android.libraries.matrix.impl.sync.map
import io.element.android.libraries.matrix.impl.usersearch.UserProfileMapper
import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper
import io.element.android.libraries.matrix.impl.util.SessionPathsProvider import io.element.android.libraries.matrix.impl.util.SessionPathsProvider
import io.element.android.libraries.matrix.impl.util.cancelAndDestroy import io.element.android.libraries.matrix.impl.util.cancelAndDestroy
@ -403,7 +403,7 @@ class RustMatrixClient(
override suspend fun getProfile(userId: UserId): Result<MatrixUser> = withContext(sessionDispatcher) { override suspend fun getProfile(userId: UserId): Result<MatrixUser> = withContext(sessionDispatcher) {
runCatchingExceptions { runCatchingExceptions {
innerClient.getProfile(userId.value).let(UserProfileMapper::map) innerClient.getProfile(userId.value).map()
} }
} }

View file

@ -5,17 +5,14 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
package io.element.android.libraries.matrix.impl.usersearch package io.element.android.libraries.matrix.impl.mapper
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import org.matrix.rustcomponents.sdk.UserProfile import org.matrix.rustcomponents.sdk.UserProfile
object UserProfileMapper { fun UserProfile.map() = MatrixUser(
fun map(userProfile: UserProfile): MatrixUser = userId = UserId(userId),
MatrixUser( displayName = displayName,
userId = UserId(userProfile.userId), avatarUrl = avatarUrl,
displayName = userProfile.displayName, )
avatarUrl = userProfile.avatarUrl,
)
}

View file

@ -8,13 +8,16 @@
package io.element.android.libraries.matrix.impl.usersearch package io.element.android.libraries.matrix.impl.usersearch
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.impl.mapper.map
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import org.matrix.rustcomponents.sdk.SearchUsersResults import org.matrix.rustcomponents.sdk.SearchUsersResults
object UserSearchResultMapper { object UserSearchResultMapper {
fun map(result: SearchUsersResults): MatrixSearchUserResults { fun map(result: SearchUsersResults): MatrixSearchUserResults {
return MatrixSearchUserResults( return MatrixSearchUserResults(
results = result.results.map(UserProfileMapper::map).toImmutableList(), results = result.results
.map { userProfile -> userProfile.map() }
.toImmutableList(),
limited = result.limited, limited = result.limited,
) )
} }

View file

@ -12,22 +12,17 @@ import io.element.android.libraries.matrix.api.core.FlowId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails
import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.libraries.matrix.api.verification.VerificationRequest
import io.element.android.libraries.matrix.impl.mapper.map
import org.matrix.rustcomponents.sdk.SessionVerificationRequestDetails as RustSessionVerificationRequestDetails import org.matrix.rustcomponents.sdk.SessionVerificationRequestDetails as RustSessionVerificationRequestDetails
import org.matrix.rustcomponents.sdk.UserProfile as RustUserProfile
fun RustSessionVerificationRequestDetails.map() = SessionVerificationRequestDetails( fun RustSessionVerificationRequestDetails.map() = SessionVerificationRequestDetails(
senderProfile = senderProfile.map(), senderProfile = senderProfile.map(),
flowId = FlowId(flowId), flowId = FlowId(flowId),
deviceId = DeviceId(deviceId), deviceId = DeviceId(deviceId),
deviceDisplayName = deviceDisplayName,
firstSeenTimestamp = firstSeenTimestamp.toLong(), firstSeenTimestamp = firstSeenTimestamp.toLong(),
) )
fun RustUserProfile.map() = SessionVerificationRequestDetails.SenderProfile(
userId = UserId(userId),
displayName = displayName,
avatarUrl = avatarUrl,
)
fun RustSessionVerificationRequestDetails.toVerificationRequest(currentUserId: UserId): VerificationRequest.Incoming { fun RustSessionVerificationRequestDetails.toVerificationRequest(currentUserId: UserId): VerificationRequest.Incoming {
val details = map() val details = map()
return if (currentUserId == details.senderProfile.userId) { return if (currentUserId == details.senderProfile.userId) {

View file

@ -5,7 +5,7 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
package io.element.android.libraries.matrix.impl.usersearch package io.element.android.libraries.matrix.impl.mapper
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
@ -16,7 +16,7 @@ import org.junit.Test
class UserProfileMapperTest { class UserProfileMapperTest {
@Test @Test
fun map() { fun map() {
assertThat(UserProfileMapper.map(aRustUserProfile(A_USER_ID.value, "displayName", "avatarUrl"))) assertThat(aRustUserProfile(A_USER_ID.value, "displayName", "avatarUrl").map())
.isEqualTo(MatrixUser(A_USER_ID, "displayName", "avatarUrl")) .isEqualTo(MatrixUser(A_USER_ID, "displayName", "avatarUrl"))
} }
} }

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:d345035d1ee105f7fa1702a3f953df99b0bfce2b0cf48cc92e6248d80690e4b5 oid sha256:537781aec261b622c47345b48e8fbfd68e3cf0817bb36bf0e34e83769d6d6d21
size 14582 size 24337

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:15b1f5afddbd729f1ec01326280b11e00f2b7c84e816bd980eac96dc61db92f2 oid sha256:9e5bf90db9ac805e4054c8d9c759181768a61f13bd30092bdf98fe0a962144f8
size 13970 size 23678

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2eecd4b52a31208444d14b5017e42fc31df0391bb0ead311c01c9359a7e42f03
size 74367

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:17d9ed21a98412af097ec5f9a209ecfeb33de2c94a3aec2afc1443469106a4f9 oid sha256:f0d95c8515dfe38a3de02fb68a035e41bedac03abadae44e371a85e6f32c9174
size 38451 size 38460

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:78507911cefb2a6c7ab9b0ddf1a7a55bc4c13adadbd3a60075ca7eb89969a8b7 oid sha256:74ecd0909c13895cd8d2e3827fb05fba98bd61f315735b281fd5502a9ae02bd2
size 32351 size 32354

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:541458c40f7b0e7c1dd18ba9211564fb053c264e965a7aaaede4e39c11a6420c oid sha256:18e41a828de1e73f0909449c096bc7d9660e5b677075845657267db731b913ab
size 37505 size 37597

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f268e84b9d477429d817fc4b80ff99fbaff45434e236bae0ce05d656b4337675 oid sha256:6242746ae0ba35d6e2e4a9c1d4c2a5e9433015eb7711767342afe4386edecfa1
size 31620 size 31701

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:84870e3b5554f97ff8093286bbf246b74b3bfa787a8318304740f55492809721 oid sha256:43836f421a5f9ab68e4a3f6b3430a6d3daeec9f737bc1b20940a3fc9597057c3
size 12397 size 12451

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:72e7183662d01486545e6702faf22dd867cc47ed72a50b8cd2c24a307cef58c9 oid sha256:053fe3e5699e98280bd40008a26f92a64e34c0f3bda1a52434137745e1ed9cf2
size 12408 size 12360