Hide the recovery key while we are entering it (#5147)
* Hide the recovery key while we are entering it (#5134) This is the Element X Android part of https://github.com/element-hq/element-meta/issues/2888 * Move the textfield contents being visible to the state so we can preview and test it * Always use the password visual transformation for the recovery key field * Update screenshots --------- Co-authored-by: Andy Balaam <andy.balaam@matrix.org> Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
a1c36d9afa
commit
31a952c389
35 changed files with 138 additions and 42 deletions
|
|
@ -9,6 +9,7 @@ package io.element.android.features.securebackup.impl.enter
|
|||
|
||||
sealed interface SecureBackupEnterRecoveryKeyEvents {
|
||||
data class OnRecoveryKeyChange(val recoveryKey: String) : SecureBackupEnterRecoveryKeyEvents
|
||||
data class ChangeRecoveryKeyFieldContentsVisibility(val visible: Boolean) : SecureBackupEnterRecoveryKeyEvents
|
||||
data object Submit : SecureBackupEnterRecoveryKeyEvents
|
||||
data object ClearDialog : SecureBackupEnterRecoveryKeyEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ class SecureBackupEnterRecoveryKeyPresenter @Inject constructor(
|
|||
@Composable
|
||||
override fun present(): SecureBackupEnterRecoveryKeyState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var displayRecoveryKeyFieldContents by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
var recoveryKey by rememberSaveable {
|
||||
mutableStateOf("")
|
||||
}
|
||||
|
|
@ -59,6 +62,9 @@ class SecureBackupEnterRecoveryKeyPresenter @Inject constructor(
|
|||
// No need to remove the spaces, the SDK will do it.
|
||||
coroutineScope.submitRecoveryKey(recoveryKey, submitAction)
|
||||
}
|
||||
is SecureBackupEnterRecoveryKeyEvents.ChangeRecoveryKeyFieldContentsVisibility -> {
|
||||
displayRecoveryKeyFieldContents = event.visible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +72,7 @@ class SecureBackupEnterRecoveryKeyPresenter @Inject constructor(
|
|||
recoveryKeyViewState = RecoveryKeyViewState(
|
||||
recoveryKeyUserStory = RecoveryKeyUserStory.Enter,
|
||||
formattedRecoveryKey = recoveryKey,
|
||||
displayTextFieldContents = displayRecoveryKeyFieldContents,
|
||||
inProgress = submitAction.value.isLoading(),
|
||||
),
|
||||
isSubmitEnabled = recoveryKey.isNotEmpty() && submitAction.value.isUninitialized(),
|
||||
|
|
|
|||
|
|
@ -20,18 +20,21 @@ open class SecureBackupEnterRecoveryKeyStateProvider : PreviewParameterProvider<
|
|||
aSecureBackupEnterRecoveryKeyState(),
|
||||
aSecureBackupEnterRecoveryKeyState(submitAction = AsyncAction.Loading),
|
||||
aSecureBackupEnterRecoveryKeyState(submitAction = AsyncAction.Failure(Exception("A Failure"))),
|
||||
aSecureBackupEnterRecoveryKeyState(displayTextFieldContents = false),
|
||||
)
|
||||
}
|
||||
|
||||
fun aSecureBackupEnterRecoveryKeyState(
|
||||
recoveryKey: String = aFormattedRecoveryKey(),
|
||||
isSubmitEnabled: Boolean = recoveryKey.isNotEmpty(),
|
||||
displayTextFieldContents: Boolean = true,
|
||||
submitAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
eventSink: (SecureBackupEnterRecoveryKeyEvents) -> Unit = {},
|
||||
) = SecureBackupEnterRecoveryKeyState(
|
||||
recoveryKeyViewState = RecoveryKeyViewState(
|
||||
recoveryKeyUserStory = RecoveryKeyUserStory.Enter,
|
||||
formattedRecoveryKey = recoveryKey,
|
||||
displayTextFieldContents = displayTextFieldContents,
|
||||
inProgress = submitAction.isLoading(),
|
||||
),
|
||||
isSubmitEnabled = isSubmitEnabled,
|
||||
|
|
|
|||
|
|
@ -102,6 +102,9 @@ private fun Content(
|
|||
onSubmit = {
|
||||
state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.Submit)
|
||||
},
|
||||
toggleRecoveryKeyVisibility = {
|
||||
state.eventSink(SecureBackupEnterRecoveryKeyEvents.ChangeRecoveryKeyFieldContentsVisibility(it))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ class SecureBackupSetupPresenter @AssistedInject constructor(
|
|||
val recoveryKeyViewState = RecoveryKeyViewState(
|
||||
recoveryKeyUserStory = if (isChangeRecoveryKeyUserStory) RecoveryKeyUserStory.Change else RecoveryKeyUserStory.Setup,
|
||||
formattedRecoveryKey = setupState.recoveryKey(),
|
||||
displayTextFieldContents = true,
|
||||
inProgress = setupState is SetupState.Creating,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ private fun SetupState.toRecoveryKeyViewState(): RecoveryKeyViewState {
|
|||
return RecoveryKeyViewState(
|
||||
recoveryKeyUserStory = RecoveryKeyUserStory.Setup,
|
||||
formattedRecoveryKey = recoveryKey(),
|
||||
displayTextFieldContents = true,
|
||||
inProgress = this is SetupState.Creating,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ private fun Content(
|
|||
onClick = clickLambda,
|
||||
onChange = null,
|
||||
onSubmit = null,
|
||||
toggleRecoveryKeyVisibility = {},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package io.element.android.features.securebackup.impl.setup.views
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -20,7 +21,9 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.autofill.ContentType
|
||||
|
|
@ -32,6 +35,7 @@ import androidx.compose.ui.semantics.semantics
|
|||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
|
|
@ -57,6 +61,7 @@ internal fun RecoveryKeyView(
|
|||
onClick: (() -> Unit)?,
|
||||
onChange: ((String) -> Unit)?,
|
||||
onSubmit: (() -> Unit)?,
|
||||
toggleRecoveryKeyVisibility: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -67,7 +72,7 @@ internal fun RecoveryKeyView(
|
|||
text = stringResource(id = CommonStrings.common_recovery_key),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
)
|
||||
RecoveryKeyContent(state, onClick, onChange, onSubmit)
|
||||
RecoveryKeyContent(state, onClick, onChange, onSubmit, toggleRecoveryKeyVisibility)
|
||||
RecoveryKeyFooter(state)
|
||||
}
|
||||
}
|
||||
|
|
@ -78,11 +83,17 @@ private fun RecoveryKeyContent(
|
|||
onClick: (() -> Unit)?,
|
||||
onChange: ((String) -> Unit)?,
|
||||
onSubmit: (() -> Unit)?,
|
||||
toggleRecoveryKeyVisibility: (Boolean) -> Unit,
|
||||
) {
|
||||
when (state.recoveryKeyUserStory) {
|
||||
RecoveryKeyUserStory.Setup,
|
||||
RecoveryKeyUserStory.Change -> RecoveryKeyStaticContent(state, onClick)
|
||||
RecoveryKeyUserStory.Enter -> RecoveryKeyFormContent(state, onChange, onSubmit)
|
||||
RecoveryKeyUserStory.Enter -> RecoveryKeyFormContent(
|
||||
state = state,
|
||||
toggleRecoveryKeyVisibility = toggleRecoveryKeyVisibility,
|
||||
onChange = onChange,
|
||||
onSubmit = onSubmit,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,15 +182,24 @@ private fun RecoveryKeyWithCopy(
|
|||
@Composable
|
||||
private fun RecoveryKeyFormContent(
|
||||
state: RecoveryKeyViewState,
|
||||
toggleRecoveryKeyVisibility: (Boolean) -> Unit,
|
||||
onChange: ((String) -> Unit)?,
|
||||
onSubmit: (() -> Unit)?,
|
||||
) {
|
||||
onChange ?: error("onChange should not be null")
|
||||
onSubmit ?: error("onSubmit should not be null")
|
||||
if (state.inProgress) {
|
||||
// Ensure recovery key is hidden when user submits the form
|
||||
toggleRecoveryKeyVisibility(false)
|
||||
}
|
||||
val keyHasSpace = state.formattedRecoveryKey.orEmpty().contains(" ")
|
||||
val recoveryKeyVisualTransformation = remember(keyHasSpace) {
|
||||
// Do not apply a visual transformation if the key has spaces, to let user enter passphrase
|
||||
if (keyHasSpace) VisualTransformation.None else RecoveryKeyVisualTransformation()
|
||||
val recoveryKeyVisualTransformation = remember(keyHasSpace, state.displayTextFieldContents) {
|
||||
if (state.displayTextFieldContents) {
|
||||
// Do not apply a visual transformation if the key has spaces, to let user enter passphrase
|
||||
if (keyHasSpace) VisualTransformation.None else RecoveryKeyVisualTransformation()
|
||||
} else {
|
||||
PasswordVisualTransformation()
|
||||
}
|
||||
}
|
||||
TextField(
|
||||
modifier = Modifier
|
||||
|
|
@ -201,6 +221,18 @@ private fun RecoveryKeyFormContent(
|
|||
onDone = { onSubmit() }
|
||||
),
|
||||
placeholder = stringResource(id = R.string.screen_recovery_key_confirm_key_placeholder),
|
||||
trailingIcon = {
|
||||
val image =
|
||||
if (state.displayTextFieldContents) CompoundIcons.VisibilityOn() else CompoundIcons.VisibilityOff()
|
||||
val description =
|
||||
if (state.displayTextFieldContents) stringResource(CommonStrings.a11y_hide_password) else stringResource(CommonStrings.a11y_show_password)
|
||||
Box(Modifier.clickable { toggleRecoveryKeyVisibility(!state.displayTextFieldContents) }) {
|
||||
Icon(
|
||||
imageVector = image,
|
||||
contentDescription = description,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -249,5 +281,6 @@ internal fun RecoveryKeyViewPreview(
|
|||
onClick = {},
|
||||
onChange = {},
|
||||
onSubmit = {},
|
||||
toggleRecoveryKeyVisibility = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ package io.element.android.features.securebackup.impl.setup.views
|
|||
data class RecoveryKeyViewState(
|
||||
val recoveryKeyUserStory: RecoveryKeyUserStory,
|
||||
val formattedRecoveryKey: String?,
|
||||
val displayTextFieldContents: Boolean,
|
||||
val inProgress: Boolean,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ open class RecoveryKeyViewStateProvider : PreviewParameterProvider<RecoveryKeyVi
|
|||
} + sequenceOf(
|
||||
aRecoveryKeyViewState(recoveryKeyUserStory = RecoveryKeyUserStory.Enter, formattedRecoveryKey = aFormattedRecoveryKey().replace(" ", "")),
|
||||
aRecoveryKeyViewState(recoveryKeyUserStory = RecoveryKeyUserStory.Enter, formattedRecoveryKey = "This is a passphrase with spaces"),
|
||||
aRecoveryKeyViewState(
|
||||
recoveryKeyUserStory = RecoveryKeyUserStory.Enter,
|
||||
formattedRecoveryKey = aFormattedRecoveryKey().replace(" ", ""),
|
||||
displayTextFieldContents = false
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -29,9 +34,11 @@ fun aRecoveryKeyViewState(
|
|||
recoveryKeyUserStory: RecoveryKeyUserStory = RecoveryKeyUserStory.Setup,
|
||||
formattedRecoveryKey: String? = null,
|
||||
inProgress: Boolean = false,
|
||||
displayTextFieldContents: Boolean = true,
|
||||
) = RecoveryKeyViewState(
|
||||
recoveryKeyUserStory = recoveryKeyUserStory,
|
||||
formattedRecoveryKey = formattedRecoveryKey,
|
||||
displayTextFieldContents = displayTextFieldContents,
|
||||
inProgress = inProgress,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ class SecureBackupEnterRecoveryKeyPresenterTest {
|
|||
RecoveryKeyViewState(
|
||||
recoveryKeyUserStory = RecoveryKeyUserStory.Enter,
|
||||
formattedRecoveryKey = "",
|
||||
displayTextFieldContents = false,
|
||||
inProgress = false,
|
||||
)
|
||||
)
|
||||
|
|
@ -61,6 +62,7 @@ class SecureBackupEnterRecoveryKeyPresenterTest {
|
|||
RecoveryKeyViewState(
|
||||
recoveryKeyUserStory = RecoveryKeyUserStory.Enter,
|
||||
formattedRecoveryKey = "1234",
|
||||
displayTextFieldContents = false,
|
||||
inProgress = false,
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,11 +10,12 @@ package io.element.android.features.securebackup.impl.enter
|
|||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performImeAction
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.securebackup.impl.R
|
||||
import io.element.android.features.securebackup.impl.setup.views.aFormattedRecoveryKey
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
|
@ -81,6 +82,23 @@ class SecureBackupEnterRecoveryKeyViewTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "h1024dp")
|
||||
fun `toggling the visibility of the textfield changes it`() {
|
||||
val recorder = EventsRecorder<SecureBackupEnterRecoveryKeyEvents>()
|
||||
val keyValue = aFormattedRecoveryKey()
|
||||
rule.setSecureBackupEnterRecoveryKeyView(aSecureBackupEnterRecoveryKeyState(isSubmitEnabled = true, eventSink = recorder))
|
||||
|
||||
// Initially, the text field should be visible
|
||||
rule.onNodeWithText(keyValue).assertExists()
|
||||
|
||||
rule.onNodeWithContentDescription(rule.activity.getString(CommonStrings.a11y_hide_password)).performClick()
|
||||
|
||||
rule.waitForIdle()
|
||||
|
||||
recorder.assertSingle(SecureBackupEnterRecoveryKeyEvents.ChangeRecoveryKeyFieldContentsVisibility(false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `validating from keyboard emits the expected event`() {
|
||||
val recorder = EventsRecorder<SecureBackupEnterRecoveryKeyEvents>()
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ class SecureBackupSetupPresenterTest {
|
|||
RecoveryKeyViewState(
|
||||
recoveryKeyUserStory = RecoveryKeyUserStory.Setup,
|
||||
formattedRecoveryKey = null,
|
||||
displayTextFieldContents = true,
|
||||
inProgress = false,
|
||||
)
|
||||
)
|
||||
|
|
@ -63,6 +64,7 @@ class SecureBackupSetupPresenterTest {
|
|||
RecoveryKeyViewState(
|
||||
recoveryKeyUserStory = RecoveryKeyUserStory.Setup,
|
||||
formattedRecoveryKey = null,
|
||||
displayTextFieldContents = true,
|
||||
inProgress = true,
|
||||
)
|
||||
)
|
||||
|
|
@ -73,6 +75,7 @@ class SecureBackupSetupPresenterTest {
|
|||
RecoveryKeyViewState(
|
||||
recoveryKeyUserStory = RecoveryKeyUserStory.Setup,
|
||||
formattedRecoveryKey = A_RECOVERY_KEY,
|
||||
displayTextFieldContents = true,
|
||||
inProgress = false,
|
||||
)
|
||||
)
|
||||
|
|
@ -103,6 +106,7 @@ class SecureBackupSetupPresenterTest {
|
|||
RecoveryKeyViewState(
|
||||
recoveryKeyUserStory = RecoveryKeyUserStory.Change,
|
||||
formattedRecoveryKey = null,
|
||||
displayTextFieldContents = true,
|
||||
inProgress = false,
|
||||
)
|
||||
)
|
||||
|
|
@ -155,6 +159,7 @@ class SecureBackupSetupPresenterTest {
|
|||
RecoveryKeyViewState(
|
||||
recoveryKeyUserStory = RecoveryKeyUserStory.Change,
|
||||
formattedRecoveryKey = null,
|
||||
displayTextFieldContents = true,
|
||||
inProgress = true,
|
||||
)
|
||||
)
|
||||
|
|
@ -164,6 +169,7 @@ class SecureBackupSetupPresenterTest {
|
|||
RecoveryKeyViewState(
|
||||
recoveryKeyUserStory = RecoveryKeyUserStory.Change,
|
||||
formattedRecoveryKey = FakeEncryptionService.FAKE_RECOVERY_KEY,
|
||||
displayTextFieldContents = true,
|
||||
inProgress = false,
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a1f1460739ae84a1231044e042e427d61cd7c94027035c1300aebf6695aec60a
|
||||
size 30986
|
||||
oid sha256:e3518f4ff808d3e61047834161935aa3603e3f8161288be14b87a1ea48c5e25f
|
||||
size 31968
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d046c96d9948e4b3754de60cb5dc1017e21c4e2b509f9e736cc0ef1def375af7
|
||||
size 41756
|
||||
oid sha256:3bdd192ae952875c6c6882ec6b02ef4855e4862431c78cf29842e25700dd3d0e
|
||||
size 42791
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:77c2e604b4c8281f284333a28608fb41f86adf56c3b9cf205d1eda0677c6c441
|
||||
size 41973
|
||||
oid sha256:23dce3fae5178c45a70171f762333a3e0f16e26b9b6c585ba7943a519de6e0dc
|
||||
size 42771
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e7e9ad9b6fe78b5028209f9f9683553fb6db2d2fc2e64f215c6abc02b19baecc
|
||||
size 35633
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0a0ca7c2dbd511607e61abc3165f8c2c8c70a6849e02415deb919ef90c9d16be
|
||||
size 29969
|
||||
oid sha256:e5e0e27543c3247402719dfe0bb0bcb2152b0d3aef7dc006cc55b36746c2fd67
|
||||
size 30923
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5f4b8abd3133c3754af7686266ab81ad7cae8f549609f3ddd53e1639e9671de3
|
||||
size 40484
|
||||
oid sha256:a081e06078ac0ef89e3c9f643766bc9c48474168227fbf011a92e31517344d8b
|
||||
size 41374
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d77618c41e5c06b9f350ece10bfe02180516a6553dd69b642b77663144ceb238
|
||||
size 40427
|
||||
oid sha256:d2cb1156bd56a8602d17d6db4e7e7089e244e6afbaf129d9ea02ba625d8a944c
|
||||
size 41173
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:69d2b8e9f30b06110a735161faf6e20b505ec9395c57349fb19144f07489a03c
|
||||
size 34700
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:777216c815b4198de503101e3e52fe371c0e1916799ac209aef993739ca39004
|
||||
size 25699
|
||||
oid sha256:ac0bb1a0a9d500f89d20bed5190f7d4e442fec7cb8bd6710f21657ef0c298fe2
|
||||
size 26672
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:47d063becbbeec9185ae3ea6dd9921d8a6620a084b8f153061fc05cf57040652
|
||||
size 25038
|
||||
oid sha256:910264e17c8737b5ee4efdca0853450524f21db7e7dd5a45424bfb87d221347b
|
||||
size 25796
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:777216c815b4198de503101e3e52fe371c0e1916799ac209aef993739ca39004
|
||||
size 25699
|
||||
oid sha256:ac0bb1a0a9d500f89d20bed5190f7d4e442fec7cb8bd6710f21657ef0c298fe2
|
||||
size 26672
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:46f7f3867698746018f5f6c12ca9aaccad4545a50f3b5db9193fe06a0e232601
|
||||
size 20575
|
||||
oid sha256:1fc08c8fa05f681d48ff886883aba32d71a74aa3db52e92964ad69f7fb367fd5
|
||||
size 21717
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1347518095b2a8abdd67a0843b3f4d06bc685dd37ef113035864e3d74cc28740
|
||||
size 18955
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:def874f1d40cd699de708cd3b4c2f55f7634de919587c2da25a2773ecdd39e4a
|
||||
size 15493
|
||||
oid sha256:5fd5183e7442a8e5945b8ffd8585204ede5b69b91ffb8944585678b4d88dcbeb
|
||||
size 16528
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2191feda845f1e255fa4387f73cb06f334b6687d08580e8c00fa94d6046b16f4
|
||||
size 15237
|
||||
oid sha256:c2b2f513d4523c1e00f8508c5531f300a71b7a7f2396d1d4e40fc605e4075e6c
|
||||
size 16235
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5200e8e2a0619bed9a9a6aad6a676a4292fe64d6d3ac11b55161887e44fd11f9
|
||||
size 24702
|
||||
oid sha256:3c908fe1b613b28a3d12d49f98f2363e361f2cab4b8c35c89482802525c5ef34
|
||||
size 25773
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ba1b220dd3866ed16eefacba1a390c2aafdf66e06db765743493177699c8c392
|
||||
size 24173
|
||||
oid sha256:0fb8568e2cd07f9c856b1e3beac52a1e6979878fd8c6fdb6b57b4faecaa2c3e1
|
||||
size 24961
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5200e8e2a0619bed9a9a6aad6a676a4292fe64d6d3ac11b55161887e44fd11f9
|
||||
size 24702
|
||||
oid sha256:3c908fe1b613b28a3d12d49f98f2363e361f2cab4b8c35c89482802525c5ef34
|
||||
size 25773
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f9dc5974fea6f46ad50b224a1aabbcde69fd87121578343df5d13eb9a5ec0cfd
|
||||
size 19488
|
||||
oid sha256:a3465fcc599b8fea97ad6347abd9525b983df5d2f4a28c0ce4a3b9743ef85c65
|
||||
size 20600
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ba81a7ac256c647460e5661ec36f78a4abb592fa98f9a5e472590d7234154dd7
|
||||
size 18101
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:696cead69a01bf5fce8b382a2b20b6823cdf66d47a30608cbbda4a3062bc6a05
|
||||
size 14743
|
||||
oid sha256:963c535c88475dd6f659ce4f22494314f29401b70b2a407a689bdccfb3b062c1
|
||||
size 15723
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bea33b82255867ef88c363d502872ced6cc063d877a4ea3da389b4fc75cfafa3
|
||||
size 14737
|
||||
oid sha256:985b5897ba1ae81ff8de6ac90daec70584f1586f0c5ff762ec154a9fd50e2cae
|
||||
size 15664
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue