Merge pull request #4909 from element-hq/feature/fga/invalid-invite
Change : handle invalid invite error
This commit is contained in:
commit
0a4d32f3d3
19 changed files with 121 additions and 38 deletions
|
|
@ -10,16 +10,23 @@ package io.element.android.features.invite.impl
|
|||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.features.invite.api.SeenInvitesStore
|
||||
import io.element.android.libraries.core.extensions.mapFailure
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||
import io.element.android.libraries.matrix.api.exception.ErrorKind
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.push.api.notifications.NotificationCleaner
|
||||
import javax.inject.Inject
|
||||
|
||||
interface AcceptInvite {
|
||||
suspend operator fun invoke(roomId: RoomId): Result<RoomId>
|
||||
|
||||
sealed class Failures : Exception() {
|
||||
data object InvalidInvite : Failures()
|
||||
}
|
||||
}
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
|
|
@ -37,6 +44,15 @@ class DefaultAcceptInvite @Inject constructor(
|
|||
).onSuccess {
|
||||
notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, roomId)
|
||||
seenInvitesStore.markAsUnSeen(roomId)
|
||||
}.mapFailure {
|
||||
if (it is ClientException.MatrixApi) {
|
||||
when (it.kind) {
|
||||
ErrorKind.Unknown -> AcceptInvite.Failures.InvalidInvite
|
||||
else -> it
|
||||
}
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}.map { roomId }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.invite.api.acceptdecline
|
||||
package io.element.android.features.invite.impl.acceptdecline
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.acceptdecline.ConfirmingDeclineInvite
|
||||
import io.element.android.features.invite.impl.AcceptInvite
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
|
|
@ -18,26 +22,37 @@ open class AcceptDeclineInviteStateProvider : PreviewParameterProvider<AcceptDec
|
|||
anAcceptDeclineInviteState(),
|
||||
anAcceptDeclineInviteState(
|
||||
declineAction = ConfirmingDeclineInvite(
|
||||
InviteData(roomId = RoomId("!room:matrix.org"), isDm = true, roomName = "Alice"),
|
||||
InviteData(
|
||||
roomId = RoomId("!room:matrix.org"),
|
||||
isDm = true,
|
||||
roomName = "Alice"
|
||||
),
|
||||
blockUser = false,
|
||||
),
|
||||
),
|
||||
anAcceptDeclineInviteState(
|
||||
declineAction = ConfirmingDeclineInvite(
|
||||
InviteData(roomId = RoomId("!room:matrix.org"), isDm = true, roomName = "Alice"),
|
||||
InviteData(
|
||||
roomId = RoomId("!room:matrix.org"),
|
||||
isDm = true,
|
||||
roomName = "Alice"
|
||||
),
|
||||
blockUser = true,
|
||||
),
|
||||
),
|
||||
anAcceptDeclineInviteState(
|
||||
acceptAction = AsyncAction.Failure(RuntimeException("Error while accepting invite")),
|
||||
),
|
||||
anAcceptDeclineInviteState(
|
||||
acceptAction = AsyncAction.Failure(AcceptInvite.Failures.InvalidInvite),
|
||||
),
|
||||
anAcceptDeclineInviteState(
|
||||
declineAction = AsyncAction.Failure(RuntimeException("Error while declining invite")),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun anAcceptDeclineInviteState(
|
||||
private fun anAcceptDeclineInviteState(
|
||||
acceptAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||
declineAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||
eventSink: (AcceptDeclineInviteEvents) -> Unit = {}
|
||||
|
|
@ -15,8 +15,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteStateProvider
|
||||
import io.element.android.features.invite.api.acceptdecline.ConfirmingDeclineInvite
|
||||
import io.element.android.features.invite.impl.AcceptInvite
|
||||
import io.element.android.features.invite.impl.R
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
|
|
@ -39,6 +39,16 @@ fun AcceptDeclineInviteView(
|
|||
onErrorDismiss = {
|
||||
state.eventSink(InternalAcceptDeclineInviteEvents.DismissAcceptError)
|
||||
},
|
||||
errorTitle = {
|
||||
stringResource(CommonStrings.common_something_went_wrong)
|
||||
},
|
||||
errorMessage = { error ->
|
||||
if (error is AcceptInvite.Failures.InvalidInvite) {
|
||||
stringResource(CommonStrings.error_invalid_invite)
|
||||
} else {
|
||||
stringResource(CommonStrings.error_network_or_server_issue)
|
||||
}
|
||||
}
|
||||
)
|
||||
AsyncActionView(
|
||||
async = state.declineAction,
|
||||
|
|
@ -46,6 +56,12 @@ fun AcceptDeclineInviteView(
|
|||
onErrorDismiss = {
|
||||
state.eventSink(InternalAcceptDeclineInviteEvents.DismissDeclineError)
|
||||
},
|
||||
errorTitle = {
|
||||
stringResource(CommonStrings.common_something_went_wrong)
|
||||
},
|
||||
errorMessage = {
|
||||
stringResource(CommonStrings.error_network_or_server_issue)
|
||||
},
|
||||
confirmationDialog = { confirming ->
|
||||
// Note: confirming will always be of type ConfirmingDeclineInvite.
|
||||
if (confirming is ConfirmingDeclineInvite) {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ import io.element.android.features.roomdirectory.api.RoomDescription
|
|||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runUpdatingState
|
||||
import io.element.android.libraries.core.extensions.mapFailure
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
|
@ -222,13 +221,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
roomIdOrAlias = roomIdOrAlias,
|
||||
serverNames = serverNames,
|
||||
trigger = trigger
|
||||
).mapFailure {
|
||||
if (it is ClientException.MatrixApi && it.kind == ErrorKind.Forbidden) {
|
||||
JoinRoomFailures.UnauthorizedJoin
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
|
|||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||
|
||||
internal const val MAX_KNOCK_MESSAGE_LENGTH = 500
|
||||
|
|
@ -36,7 +37,7 @@ data class JoinRoomState(
|
|||
val canReportRoom: Boolean,
|
||||
val eventSink: (JoinRoomEvents) -> Unit
|
||||
) {
|
||||
val isJoinActionUnauthorized = joinAction is AsyncAction.Failure && joinAction.error is JoinRoomFailures.UnauthorizedJoin
|
||||
val isJoinActionUnauthorized = joinAction is AsyncAction.Failure && joinAction.error is JoinRoom.Failures.UnauthorizedJoin
|
||||
val joinAuthorisationStatus = when (contentState) {
|
||||
is ContentState.Loaded -> {
|
||||
when {
|
||||
|
|
@ -107,7 +108,3 @@ sealed interface JoinAuthorisationStatus {
|
|||
data object Unknown : JoinAuthorisationStatus
|
||||
data object Unauthorized : JoinAuthorisationStatus
|
||||
}
|
||||
|
||||
sealed class JoinRoomFailures : Exception() {
|
||||
data object UnauthorizedJoin : JoinRoomFailures()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ package io.element.android.features.joinroom.impl
|
|||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
|
|
@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.core.UserId
|
|||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||
|
||||
open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
||||
|
|
@ -44,7 +45,7 @@ open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
|
|||
),
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin),
|
||||
joinAction = AsyncAction.Failure(JoinRoomFailures.UnauthorizedJoin)
|
||||
joinAction = AsyncAction.Failure(JoinRoom.Failures.UnauthorizedJoin)
|
||||
),
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin),
|
||||
|
|
@ -198,6 +199,16 @@ fun aJoinRoomState(
|
|||
eventSink = eventSink
|
||||
)
|
||||
|
||||
internal fun anAcceptDeclineInviteState(
|
||||
acceptAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||
declineAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||
eventSink: (AcceptDeclineInviteEvents) -> Unit = {}
|
||||
) = AcceptDeclineInviteState(
|
||||
acceptAction = acceptAction,
|
||||
declineAction = declineAction,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
internal fun anInviteSender(
|
||||
userId: UserId = UserId("@bob:domain"),
|
||||
displayName: String = "Bob",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import io.element.android.features.invite.api.InviteData
|
|||
import io.element.android.features.invite.api.SeenInvitesStore
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.toInviteData
|
||||
import io.element.android.features.invite.test.InMemorySeenInvitesStore
|
||||
import io.element.android.features.joinroom.impl.di.CancelKnockRoom
|
||||
|
|
@ -36,6 +35,7 @@ import io.element.android.libraries.matrix.api.exception.ErrorKind
|
|||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipDetails
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
|
|
@ -304,7 +304,7 @@ class JoinRoomPresenterTest {
|
|||
val presenter = createJoinRoomPresenter(
|
||||
roomDescription = Optional.of(roomDescription),
|
||||
joinRoomLambda = { _, _, _ ->
|
||||
Result.failure(ClientException.MatrixApi(ErrorKind.Forbidden, "403", "Forbidden", null))
|
||||
Result.failure(JoinRoom.Failures.UnauthorizedJoin)
|
||||
},
|
||||
)
|
||||
presenter.test {
|
||||
|
|
@ -316,7 +316,7 @@ class JoinRoomPresenterTest {
|
|||
assertThat(state.joinAction).isEqualTo(AsyncAction.Loading)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.joinAction).isEqualTo(AsyncAction.Failure(JoinRoomFailures.UnauthorizedJoin))
|
||||
assertThat(state.joinAction).isEqualTo(AsyncAction.Failure(JoinRoom.Failures.UnauthorizedJoin))
|
||||
assertThat(state.joinAuthorisationStatus).isEqualTo(JoinAuthorisationStatus.Unauthorized)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import io.element.android.features.invite.api.InviteData
|
|||
import io.element.android.features.invite.test.anInviteData
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.room.RoomType
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
|
@ -199,9 +200,9 @@ class JoinRoomViewTest {
|
|||
canReportRoom = false,
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
rule.setJoinRoomView(state = joinRoomState,)
|
||||
rule.clickOn(R.string.screen_join_room_decline_and_block_button_title)
|
||||
eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite(inviteData, true))
|
||||
rule.setJoinRoomView(state = joinRoomState)
|
||||
rule.clickOn(R.string.screen_join_room_decline_and_block_button_title)
|
||||
eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite(inviteData, true))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -239,7 +240,7 @@ class JoinRoomViewTest {
|
|||
rule.setJoinRoomView(
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(),
|
||||
joinAction = AsyncAction.Failure(JoinRoomFailures.UnauthorizedJoin),
|
||||
joinAction = AsyncAction.Failure(JoinRoom.Failures.UnauthorizedJoin),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onBackClick = it
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@
|
|||
package io.element.android.features.roomlist.impl
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
import io.element.android.features.leaveroom.api.aLeaveRoomState
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
|
|
@ -22,9 +22,11 @@ import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary
|
|||
import io.element.android.features.roomlist.impl.model.anInviteSender
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||
import io.element.android.features.roomlist.impl.search.aRoomListSearchState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.push.api.battery.aBatteryOptimizationState
|
||||
|
|
@ -86,6 +88,16 @@ internal fun aRoomListState(
|
|||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
internal fun anAcceptDeclineInviteState(
|
||||
acceptAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||
declineAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||
eventSink: (AcceptDeclineInviteEvents) -> Unit = {}
|
||||
) = AcceptDeclineInviteState(
|
||||
acceptAction = acceptAction,
|
||||
declineAction = declineAction,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
internal fun aRoomListRoomSummaryList(): ImmutableList<RoomListRoomSummary> {
|
||||
return persistentListOf(
|
||||
aRoomListRoomSummary(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import im.vector.app.features.analytics.plan.Interaction
|
|||
import io.element.android.features.invite.api.SeenInvitesStore
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState
|
||||
import io.element.android.features.invite.test.InMemorySeenInvitesStore
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
|
|
|
|||
|
|
@ -16,4 +16,8 @@ interface JoinRoom {
|
|||
serverNames: List<String>,
|
||||
trigger: JoinedRoom.Trigger,
|
||||
): Result<Unit>
|
||||
|
||||
sealed class Failures : Exception() {
|
||||
data object UnauthorizedJoin : Failures()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,12 @@ package io.element.android.libraries.matrix.impl.room.join
|
|||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.libraries.core.extensions.mapFailure
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||
import io.element.android.libraries.matrix.api.exception.ErrorKind
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.matrix.impl.analytics.toAnalyticsJoinedRoom
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
|
|
@ -42,6 +45,15 @@ class DefaultJoinRoom @Inject constructor(
|
|||
if (roomInfo != null) {
|
||||
analyticsService.capture(roomInfo.toAnalyticsJoinedRoom(trigger))
|
||||
}
|
||||
}.mapFailure {
|
||||
if (it is ClientException.MatrixApi) {
|
||||
when (it.kind) {
|
||||
ErrorKind.Forbidden -> JoinRoom.Failures.UnauthorizedJoin
|
||||
else -> it
|
||||
}
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}.map { }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -333,6 +333,7 @@ Are you sure you want to continue?"</string>
|
|||
<string name="error_failed_loading_messages">"Failed loading messages"</string>
|
||||
<string name="error_failed_locating_user">"%1$s could not access your location. Please try again later."</string>
|
||||
<string name="error_failed_uploading_voice_message">"Failed to upload your voice message."</string>
|
||||
<string name="error_invalid_invite">"The room no longer exists or the invite is no longer valid."</string>
|
||||
<string name="error_message_not_found">"Message not found"</string>
|
||||
<string name="error_missing_location_auth_android">"%1$s does not have permission to access your location. You can enable access in Settings."</string>
|
||||
<string name="error_missing_location_rationale_android">"%1$s does not have permission to access your location. Enable access below."</string>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:05e7a67dfcec946de1e355afed430198c6c89f1938755907db1e83ab1bb68488
|
||||
size 12177
|
||||
oid sha256:db20190581472583a2f493275d0f7dc46ed4a2128cef475b98730e3695b918c7
|
||||
size 19149
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:96e2d9fd6b20b70dd3bb9b3caaea2cc3eaed1644c3371889cbc56d608bd4b49b
|
||||
size 11826
|
||||
oid sha256:784fd291db5fc825b8c1f0bac5ed3066e3aaa67fd6cbc35fd65b0df92f7cb052
|
||||
size 20504
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:db20190581472583a2f493275d0f7dc46ed4a2128cef475b98730e3695b918c7
|
||||
size 19149
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:24adccc976a5611f31d0caaa614212da841a5a9abd5e9ebb706fe1f9cbfb6408
|
||||
size 10729
|
||||
oid sha256:d4557838eceb90ec501062367413359550ccc927cd4e0c50c9a01c2d9eb0938f
|
||||
size 17146
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7f41edac4c092e5999950e97b0352ffa46c083ba2cd50c8b5b408eff8a85df06
|
||||
size 10376
|
||||
oid sha256:e620cc1fe0e21a92da88948bc8da2db49c3bcaea0480f8abaa61e22a0882a56e
|
||||
size 18341
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d4557838eceb90ec501062367413359550ccc927cd4e0c50c9a01c2d9eb0938f
|
||||
size 17146
|
||||
Loading…
Add table
Add a link
Reference in a new issue