Merge branch 'develop' into feature/fga/sending_queue_iteration
This commit is contained in:
commit
e93a41a5b5
31 changed files with 161 additions and 86 deletions
2
.github/workflows/danger.yml
vendored
2
.github/workflows/danger.yml
vendored
|
|
@ -11,7 +11,7 @@ jobs:
|
||||||
- run: |
|
- run: |
|
||||||
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
||||||
- name: Danger
|
- name: Danger
|
||||||
uses: danger/danger-js@12.3.1
|
uses: danger/danger-js@12.3.2
|
||||||
with:
|
with:
|
||||||
args: "--dangerfile ./tools/danger/dangerfile.js"
|
args: "--dangerfile ./tools/danger/dangerfile.js"
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
2
.github/workflows/quality.yml
vendored
2
.github/workflows/quality.yml
vendored
|
|
@ -245,7 +245,7 @@ jobs:
|
||||||
yarn add danger-plugin-lint-report --dev
|
yarn add danger-plugin-lint-report --dev
|
||||||
- name: Danger lint
|
- name: Danger lint
|
||||||
if: always()
|
if: always()
|
||||||
uses: danger/danger-js@12.3.1
|
uses: danger/danger-js@12.3.2
|
||||||
with:
|
with:
|
||||||
args: "--dangerfile ./tools/danger/dangerfile-lint.js"
|
args: "--dangerfile ./tools/danger/dangerfile-lint.js"
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ class MatrixClientsHolder @Inject constructor(private val authenticationService:
|
||||||
// Not ideal but should only happens in case of process recreation. This ensure we restore all the active sessions before restoring the node graphs.
|
// Not ideal but should only happens in case of process recreation. This ensure we restore all the active sessions before restoring the node graphs.
|
||||||
runBlocking {
|
runBlocking {
|
||||||
sessionIds.forEach { sessionId ->
|
sessionIds.forEach { sessionId ->
|
||||||
restore(sessionId)
|
getOrRestore(sessionId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ class MatrixClientsHolderTest {
|
||||||
fakeAuthenticationService.givenMatrixClient(fakeMatrixClient)
|
fakeAuthenticationService.givenMatrixClient(fakeMatrixClient)
|
||||||
assertThat(matrixClientsHolder.getOrNull(A_SESSION_ID)).isNull()
|
assertThat(matrixClientsHolder.getOrNull(A_SESSION_ID)).isNull()
|
||||||
assertThat(matrixClientsHolder.getOrRestore(A_SESSION_ID).getOrNull()).isEqualTo(fakeMatrixClient)
|
assertThat(matrixClientsHolder.getOrRestore(A_SESSION_ID).getOrNull()).isEqualTo(fakeMatrixClient)
|
||||||
// Do it again to it the cache
|
// Do it again to hit the cache
|
||||||
assertThat(matrixClientsHolder.getOrRestore(A_SESSION_ID).getOrNull()).isEqualTo(fakeMatrixClient)
|
assertThat(matrixClientsHolder.getOrRestore(A_SESSION_ID).getOrNull()).isEqualTo(fakeMatrixClient)
|
||||||
assertThat(matrixClientsHolder.getOrNull(A_SESSION_ID)).isEqualTo(fakeMatrixClient)
|
assertThat(matrixClientsHolder.getOrNull(A_SESSION_ID)).isEqualTo(fakeMatrixClient)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,22 @@
|
||||||
package io.element.android.features.login.impl.changeserver
|
package io.element.android.features.login.impl.changeserver
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.features.login.impl.error.ChangeServerError
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
open class ChangeServerStateProvider : PreviewParameterProvider<ChangeServerState> {
|
open class ChangeServerStateProvider : PreviewParameterProvider<ChangeServerState> {
|
||||||
override val values: Sequence<ChangeServerState>
|
override val values: Sequence<ChangeServerState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aChangeServerState(),
|
aChangeServerState(),
|
||||||
|
aChangeServerState(changeServerAction = AsyncData.Failure(ChangeServerError.Error(CommonStrings.error_unknown))),
|
||||||
|
aChangeServerState(changeServerAction = AsyncData.Failure(ChangeServerError.SlidingSyncAlert)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aChangeServerState() = ChangeServerState(
|
fun aChangeServerState(
|
||||||
changeServerAction = AsyncData.Uninitialized,
|
changeServerAction: AsyncData<Unit> = AsyncData.Uninitialized,
|
||||||
|
) = ChangeServerState(
|
||||||
|
changeServerAction = changeServerAction,
|
||||||
eventSink = {}
|
eventSink = {}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ fun SecureBackupEnterRecoveryKeyView(
|
||||||
|
|
||||||
FlowStepPage(
|
FlowStepPage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
isScrollable = true,
|
||||||
onBackClick = onBackClick,
|
onBackClick = onBackClick,
|
||||||
iconStyle = BigIcon.Style.Default(CompoundIcons.KeySolid()),
|
iconStyle = BigIcon.Style.Default(CompoundIcons.KeySolid()),
|
||||||
title = stringResource(id = R.string.screen_recovery_key_confirm_title),
|
title = stringResource(id = R.string.screen_recovery_key_confirm_title),
|
||||||
|
|
@ -70,7 +71,7 @@ private fun Content(
|
||||||
state: SecureBackupEnterRecoveryKeyState,
|
state: SecureBackupEnterRecoveryKeyState,
|
||||||
) {
|
) {
|
||||||
RecoveryKeyView(
|
RecoveryKeyView(
|
||||||
modifier = Modifier.padding(top = 52.dp),
|
modifier = Modifier.padding(top = 52.dp, bottom = 32.dp),
|
||||||
state = state.recoveryKeyViewState,
|
state = state.recoveryKeyViewState,
|
||||||
onClick = null,
|
onClick = null,
|
||||||
onChange = {
|
onChange = {
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,12 @@ package io.element.android.features.securebackup.impl.enter
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.performImeAction
|
||||||
|
import androidx.compose.ui.test.performTextInput
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.securebackup.impl.R
|
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.architecture.AsyncAction
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
|
|
@ -33,6 +37,7 @@ import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
import org.junit.rules.TestRule
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class SecureBackupEnterRecoveryKeyViewTest {
|
class SecureBackupEnterRecoveryKeyViewTest {
|
||||||
|
|
@ -61,6 +66,7 @@ class SecureBackupEnterRecoveryKeyViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Config(qualifiers = "h1024dp")
|
||||||
fun `tapping on Continue when key is valid - calls expected action`() {
|
fun `tapping on Continue when key is valid - calls expected action`() {
|
||||||
val recorder = EventsRecorder<SecureBackupEnterRecoveryKeyEvents>()
|
val recorder = EventsRecorder<SecureBackupEnterRecoveryKeyEvents>()
|
||||||
rule.setSecureBackupEnterRecoveryKeyView(
|
rule.setSecureBackupEnterRecoveryKeyView(
|
||||||
|
|
@ -72,6 +78,31 @@ class SecureBackupEnterRecoveryKeyViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
fun `entering a char emits the expected event`() {
|
||||||
|
val recorder = EventsRecorder<SecureBackupEnterRecoveryKeyEvents>()
|
||||||
|
val keyValue = aFormattedRecoveryKey()
|
||||||
|
rule.setSecureBackupEnterRecoveryKeyView(
|
||||||
|
aSecureBackupEnterRecoveryKeyState(isSubmitEnabled = true, eventSink = recorder),
|
||||||
|
)
|
||||||
|
rule.onNodeWithText(keyValue).performTextInput("X")
|
||||||
|
recorder.assertSingle(
|
||||||
|
SecureBackupEnterRecoveryKeyEvents.OnRecoveryKeyChange("X$keyValue")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `validating from keyboard emits the expected event`() {
|
||||||
|
val recorder = EventsRecorder<SecureBackupEnterRecoveryKeyEvents>()
|
||||||
|
val keyValue = aFormattedRecoveryKey()
|
||||||
|
rule.setSecureBackupEnterRecoveryKeyView(
|
||||||
|
aSecureBackupEnterRecoveryKeyState(isSubmitEnabled = true, eventSink = recorder),
|
||||||
|
)
|
||||||
|
rule.onNodeWithText(keyValue).performImeAction()
|
||||||
|
recorder.assertSingle(SecureBackupEnterRecoveryKeyEvents.Submit)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(qualifiers = "h1024dp")
|
||||||
fun `tapping on Lost your recovery key - calls onCreateNewRecoveryKey`() {
|
fun `tapping on Lost your recovery key - calls onCreateNewRecoveryKey`() {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setSecureBackupEnterRecoveryKeyView(
|
rule.setSecureBackupEnterRecoveryKeyView(
|
||||||
|
|
@ -98,7 +129,7 @@ class SecureBackupEnterRecoveryKeyViewTest {
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onCreateNewRecoveryKey: () -> Unit = EnsureNeverCalled(),
|
onCreateNewRecoveryKey: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
rule.setContent {
|
setContent {
|
||||||
SecureBackupEnterRecoveryKeyView(
|
SecureBackupEnterRecoveryKeyView(
|
||||||
state = state,
|
state = state,
|
||||||
onSuccess = onDone,
|
onSuccess = onDone,
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,7 @@ class VerifySelfSessionViewTest {
|
||||||
onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(),
|
onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(),
|
||||||
onFinished: () -> Unit = EnsureNeverCalled(),
|
onFinished: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
rule.setContent {
|
setContent {
|
||||||
VerifySelfSessionView(
|
VerifySelfSessionView(
|
||||||
state = state,
|
state = state,
|
||||||
onEnterRecoveryKey = onEnterRecoveryKey,
|
onEnterRecoveryKey = onEnterRecoveryKey,
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ fun FlowStepPage(
|
||||||
iconStyle: BigIcon.Style,
|
iconStyle: BigIcon.Style,
|
||||||
title: String,
|
title: String,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
isScrollable: Boolean = false,
|
||||||
onBackClick: (() -> Unit)? = null,
|
onBackClick: (() -> Unit)? = null,
|
||||||
subTitle: String? = null,
|
subTitle: String? = null,
|
||||||
buttons: @Composable ColumnScope.() -> Unit = {},
|
buttons: @Composable ColumnScope.() -> Unit = {},
|
||||||
|
|
@ -62,6 +63,7 @@ fun FlowStepPage(
|
||||||
}
|
}
|
||||||
HeaderFooterPage(
|
HeaderFooterPage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
isScrollable = isScrollable,
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -39,6 +41,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
* @param modifier Classical modifier.
|
* @param modifier Classical modifier.
|
||||||
* @param paddingValues padding values to apply to the content.
|
* @param paddingValues padding values to apply to the content.
|
||||||
* @param containerColor color of the container. Set to [Color.Transparent] if you provide a background in the [modifier].
|
* @param containerColor color of the container. Set to [Color.Transparent] if you provide a background in the [modifier].
|
||||||
|
* @param isScrollable if the whole content should be scrollable.
|
||||||
* @param background optional background component.
|
* @param background optional background component.
|
||||||
* @param topBar optional topBar.
|
* @param topBar optional topBar.
|
||||||
* @param header optional header.
|
* @param header optional header.
|
||||||
|
|
@ -50,6 +53,7 @@ fun HeaderFooterPage(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
paddingValues: PaddingValues = PaddingValues(20.dp),
|
paddingValues: PaddingValues = PaddingValues(20.dp),
|
||||||
containerColor: Color = MaterialTheme.colorScheme.background,
|
containerColor: Color = MaterialTheme.colorScheme.background,
|
||||||
|
isScrollable: Boolean = false,
|
||||||
background: @Composable () -> Unit = {},
|
background: @Composable () -> Unit = {},
|
||||||
topBar: @Composable () -> Unit = {},
|
topBar: @Composable () -> Unit = {},
|
||||||
header: @Composable () -> Unit = {},
|
header: @Composable () -> Unit = {},
|
||||||
|
|
@ -63,25 +67,53 @@ fun HeaderFooterPage(
|
||||||
) { padding ->
|
) { padding ->
|
||||||
Box {
|
Box {
|
||||||
background()
|
background()
|
||||||
Column(
|
if (isScrollable) {
|
||||||
modifier = Modifier
|
// Render in a LazyColumn
|
||||||
.padding(paddingValues = paddingValues)
|
LazyColumn(
|
||||||
.padding(padding)
|
modifier = Modifier
|
||||||
.consumeWindowInsets(padding)
|
.padding(paddingValues = paddingValues)
|
||||||
) {
|
.padding(padding)
|
||||||
// Header
|
.consumeWindowInsets(padding)
|
||||||
header()
|
.imePadding()
|
||||||
// Content
|
) {
|
||||||
|
// Header
|
||||||
|
item {
|
||||||
|
header()
|
||||||
|
}
|
||||||
|
// Content
|
||||||
|
item {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
// Footer
|
||||||
|
item {
|
||||||
|
Box(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
footer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Render in a Column
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.padding(paddingValues = paddingValues)
|
||||||
.fillMaxWidth(),
|
.padding(padding)
|
||||||
|
.consumeWindowInsets(padding)
|
||||||
|
.imePadding()
|
||||||
) {
|
) {
|
||||||
content()
|
// Header
|
||||||
}
|
header()
|
||||||
// Footer
|
// Content
|
||||||
Box(modifier = Modifier.padding(horizontal = 16.dp)) {
|
Column(
|
||||||
footer()
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
// Footer
|
||||||
|
Box(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
footer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.element.android.libraries.matrix.api.auth
|
package io.element.android.libraries.matrix.api.auth
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||||
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
|
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
|
||||||
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
|
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
|
@ -31,6 +32,7 @@ interface MatrixAuthenticationService {
|
||||||
/**
|
/**
|
||||||
* Restore a session from a [sessionId].
|
* Restore a session from a [sessionId].
|
||||||
* Do not restore anything it the access token is not valid anymore.
|
* Do not restore anything it the access token is not valid anymore.
|
||||||
|
* Generally this method should not be used directly, prefer using [MatrixClientProvider.getOrRestore] instead.
|
||||||
*/
|
*/
|
||||||
suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient>
|
suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient>
|
||||||
fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?>
|
fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?>
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ class RoomSummaryListProcessorTest {
|
||||||
|
|
||||||
override suspend fun applyInput(input: RoomListInput) = Unit
|
override suspend fun applyInput(input: RoomListInput) = Unit
|
||||||
|
|
||||||
override suspend fun room(roomId: String): RoomListItem {
|
override fun room(roomId: String): RoomListItem {
|
||||||
return RoomListItem(Pointer.NULL)
|
return RoomListItem(Pointer.NULL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
class FakeRoomListService : RoomListService {
|
class FakeRoomListService : RoomListService {
|
||||||
private val allRoomSummariesFlow = MutableStateFlow<List<RoomSummary>>(emptyList())
|
private val allRoomSummariesFlow = MutableStateFlow<List<RoomSummary>>(emptyList())
|
||||||
private val inviteRoomSummariesFlow = MutableStateFlow<List<RoomSummary>>(emptyList())
|
|
||||||
private val allRoomsLoadingStateFlow = MutableStateFlow<RoomList.LoadingState>(RoomList.LoadingState.NotLoaded)
|
private val allRoomsLoadingStateFlow = MutableStateFlow<RoomList.LoadingState>(RoomList.LoadingState.NotLoaded)
|
||||||
private val roomListStateFlow = MutableStateFlow<RoomListService.State>(RoomListService.State.Idle)
|
private val roomListStateFlow = MutableStateFlow<RoomListService.State>(RoomListService.State.Idle)
|
||||||
private val syncIndicatorStateFlow = MutableStateFlow<RoomListService.SyncIndicator>(RoomListService.SyncIndicator.Hide)
|
private val syncIndicatorStateFlow = MutableStateFlow<RoomListService.SyncIndicator>(RoomListService.SyncIndicator.Hide)
|
||||||
|
|
@ -35,10 +34,6 @@ class FakeRoomListService : RoomListService {
|
||||||
allRoomSummariesFlow.emit(roomSummaries)
|
allRoomSummariesFlow.emit(roomSummaries)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun postInviteRooms(roomSummaries: List<RoomSummary>) {
|
|
||||||
inviteRoomSummariesFlow.emit(roomSummaries)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun postAllRoomsLoadingState(loadingState: RoomList.LoadingState) {
|
suspend fun postAllRoomsLoadingState(loadingState: RoomList.LoadingState) {
|
||||||
allRoomsLoadingStateFlow.emit(loadingState)
|
allRoomsLoadingStateFlow.emit(loadingState)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.libraries.core.extensions.flatMap
|
import io.element.android.libraries.core.extensions.flatMap
|
||||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||||
|
|
@ -43,7 +43,7 @@ class DefaultFirebaseNewTokenHandler @Inject constructor(
|
||||||
private val pusherSubscriber: PusherSubscriber,
|
private val pusherSubscriber: PusherSubscriber,
|
||||||
private val sessionStore: SessionStore,
|
private val sessionStore: SessionStore,
|
||||||
private val userPushStoreFactory: UserPushStoreFactory,
|
private val userPushStoreFactory: UserPushStoreFactory,
|
||||||
private val matrixAuthenticationService: MatrixAuthenticationService,
|
private val matrixClientProvider: MatrixClientProvider,
|
||||||
private val firebaseStore: FirebaseStore,
|
private val firebaseStore: FirebaseStore,
|
||||||
) : FirebaseNewTokenHandler {
|
) : FirebaseNewTokenHandler {
|
||||||
override suspend fun handle(firebaseToken: String) {
|
override suspend fun handle(firebaseToken: String) {
|
||||||
|
|
@ -54,8 +54,8 @@ class DefaultFirebaseNewTokenHandler @Inject constructor(
|
||||||
.forEach { sessionId ->
|
.forEach { sessionId ->
|
||||||
val userDataStore = userPushStoreFactory.getOrCreate(sessionId)
|
val userDataStore = userPushStoreFactory.getOrCreate(sessionId)
|
||||||
if (userDataStore.getPushProviderName() == FirebaseConfig.NAME) {
|
if (userDataStore.getPushProviderName() == FirebaseConfig.NAME) {
|
||||||
matrixAuthenticationService
|
matrixClientProvider
|
||||||
.restoreSession(sessionId)
|
.getOrRestore(sessionId)
|
||||||
.onFailure {
|
.onFailure {
|
||||||
Timber.tag(loggerTag.value).e(it, "Failed to restore session $sessionId")
|
Timber.tag(loggerTag.value).e(it, "Failed to restore session $sessionId")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,13 @@ package io.element.android.libraries.pushproviders.firebase
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID_3
|
import io.element.android.libraries.matrix.test.A_USER_ID_3
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||||
import io.element.android.libraries.push.test.FakePusherSubscriber
|
import io.element.android.libraries.push.test.FakePusherSubscriber
|
||||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||||
|
|
@ -64,16 +64,14 @@ class DefaultFirebaseNewTokenHandlerTest {
|
||||||
storeData(aSessionData(A_USER_ID_2))
|
storeData(aSessionData(A_USER_ID_2))
|
||||||
storeData(aSessionData(A_USER_ID_3))
|
storeData(aSessionData(A_USER_ID_3))
|
||||||
},
|
},
|
||||||
matrixAuthenticationService = FakeMatrixAuthenticationService(
|
matrixClientProvider = FakeMatrixClientProvider { sessionId ->
|
||||||
matrixClientResult = { sessionId ->
|
when (sessionId) {
|
||||||
when (sessionId) {
|
A_USER_ID -> Result.success(aMatrixClient1)
|
||||||
A_USER_ID -> Result.success(aMatrixClient1)
|
A_USER_ID_2 -> Result.success(aMatrixClient2)
|
||||||
A_USER_ID_2 -> Result.success(aMatrixClient2)
|
A_USER_ID_3 -> Result.success(aMatrixClient3)
|
||||||
A_USER_ID_3 -> Result.success(aMatrixClient3)
|
else -> Result.failure(IllegalStateException())
|
||||||
else -> Result.failure(IllegalStateException())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
),
|
},
|
||||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||||
userPushStore = { sessionId ->
|
userPushStore = { sessionId ->
|
||||||
when (sessionId) {
|
when (sessionId) {
|
||||||
|
|
@ -103,11 +101,9 @@ class DefaultFirebaseNewTokenHandlerTest {
|
||||||
sessionStore = InMemoryMultiSessionsStore().apply {
|
sessionStore = InMemoryMultiSessionsStore().apply {
|
||||||
storeData(aSessionData(A_USER_ID))
|
storeData(aSessionData(A_USER_ID))
|
||||||
},
|
},
|
||||||
matrixAuthenticationService = FakeMatrixAuthenticationService(
|
matrixClientProvider = FakeMatrixClientProvider {
|
||||||
matrixClientResult = { _ ->
|
Result.failure(IllegalStateException())
|
||||||
Result.failure(IllegalStateException())
|
},
|
||||||
}
|
|
||||||
),
|
|
||||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||||
userPushStore = { _ ->
|
userPushStore = { _ ->
|
||||||
FakeUserPushStore(pushProviderName = FirebaseConfig.NAME)
|
FakeUserPushStore(pushProviderName = FirebaseConfig.NAME)
|
||||||
|
|
@ -129,11 +125,9 @@ class DefaultFirebaseNewTokenHandlerTest {
|
||||||
sessionStore = InMemoryMultiSessionsStore().apply {
|
sessionStore = InMemoryMultiSessionsStore().apply {
|
||||||
storeData(aSessionData(A_USER_ID))
|
storeData(aSessionData(A_USER_ID))
|
||||||
},
|
},
|
||||||
matrixAuthenticationService = FakeMatrixAuthenticationService(
|
matrixClientProvider = FakeMatrixClientProvider {
|
||||||
matrixClientResult = { _ ->
|
Result.success(aMatrixClient1)
|
||||||
Result.success(aMatrixClient1)
|
},
|
||||||
}
|
|
||||||
),
|
|
||||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||||
userPushStore = { _ ->
|
userPushStore = { _ ->
|
||||||
FakeUserPushStore(pushProviderName = FirebaseConfig.NAME)
|
FakeUserPushStore(pushProviderName = FirebaseConfig.NAME)
|
||||||
|
|
@ -152,14 +146,14 @@ class DefaultFirebaseNewTokenHandlerTest {
|
||||||
pusherSubscriber: PusherSubscriber = FakePusherSubscriber(),
|
pusherSubscriber: PusherSubscriber = FakePusherSubscriber(),
|
||||||
sessionStore: SessionStore = InMemorySessionStore(),
|
sessionStore: SessionStore = InMemorySessionStore(),
|
||||||
userPushStoreFactory: UserPushStoreFactory = FakeUserPushStoreFactory(),
|
userPushStoreFactory: UserPushStoreFactory = FakeUserPushStoreFactory(),
|
||||||
matrixAuthenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
matrixClientProvider: MatrixClientProvider = FakeMatrixClientProvider(),
|
||||||
firebaseStore: FirebaseStore = InMemoryFirebaseStore(),
|
firebaseStore: FirebaseStore = InMemoryFirebaseStore(),
|
||||||
): FirebaseNewTokenHandler {
|
): FirebaseNewTokenHandler {
|
||||||
return DefaultFirebaseNewTokenHandler(
|
return DefaultFirebaseNewTokenHandler(
|
||||||
pusherSubscriber = pusherSubscriber,
|
pusherSubscriber = pusherSubscriber,
|
||||||
sessionStore = sessionStore,
|
sessionStore = sessionStore,
|
||||||
userPushStoreFactory = userPushStoreFactory,
|
userPushStoreFactory = userPushStoreFactory,
|
||||||
matrixAuthenticationService = matrixAuthenticationService,
|
matrixClientProvider = matrixClientProvider,
|
||||||
firebaseStore = firebaseStore
|
firebaseStore = firebaseStore
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.libraries.core.extensions.flatMap
|
import io.element.android.libraries.core.extensions.flatMap
|
||||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||||
|
|
@ -41,7 +41,7 @@ class DefaultUnifiedPushNewGatewayHandler @Inject constructor(
|
||||||
private val pusherSubscriber: PusherSubscriber,
|
private val pusherSubscriber: PusherSubscriber,
|
||||||
private val userPushStoreFactory: UserPushStoreFactory,
|
private val userPushStoreFactory: UserPushStoreFactory,
|
||||||
private val pushClientSecret: PushClientSecret,
|
private val pushClientSecret: PushClientSecret,
|
||||||
private val matrixAuthenticationService: MatrixAuthenticationService,
|
private val matrixClientProvider: MatrixClientProvider,
|
||||||
) : UnifiedPushNewGatewayHandler {
|
) : UnifiedPushNewGatewayHandler {
|
||||||
override suspend fun handle(endpoint: String, pushGateway: String, clientSecret: String): Result<Unit> {
|
override suspend fun handle(endpoint: String, pushGateway: String, clientSecret: String): Result<Unit> {
|
||||||
// Register the pusher for the session with this client secret, if is it using UnifiedPush.
|
// Register the pusher for the session with this client secret, if is it using UnifiedPush.
|
||||||
|
|
@ -52,8 +52,8 @@ class DefaultUnifiedPushNewGatewayHandler @Inject constructor(
|
||||||
}
|
}
|
||||||
val userDataStore = userPushStoreFactory.getOrCreate(userId)
|
val userDataStore = userPushStoreFactory.getOrCreate(userId)
|
||||||
return if (userDataStore.getPushProviderName() == UnifiedPushConfig.NAME) {
|
return if (userDataStore.getPushProviderName() == UnifiedPushConfig.NAME) {
|
||||||
matrixAuthenticationService
|
matrixClientProvider
|
||||||
.restoreSession(userId)
|
.getOrRestore(userId)
|
||||||
.flatMap { client ->
|
.flatMap { client ->
|
||||||
pusherSubscriber.registerPusher(client, endpoint, pushGateway)
|
pusherSubscriber.registerPusher(client, endpoint, pushGateway)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@ package io.element.android.libraries.pushproviders.unifiedpush
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||||
import io.element.android.libraries.matrix.test.A_SECRET
|
import io.element.android.libraries.matrix.test.A_SECRET
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||||
import io.element.android.libraries.push.test.FakePusherSubscriber
|
import io.element.android.libraries.push.test.FakePusherSubscriber
|
||||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||||
|
|
@ -86,7 +86,7 @@ class DefaultUnifiedPushNewGatewayHandlerTest {
|
||||||
pusherSubscriber = FakePusherSubscriber(
|
pusherSubscriber = FakePusherSubscriber(
|
||||||
registerPusherResult = { _, _, _ -> Result.failure(IllegalStateException("an error")) }
|
registerPusherResult = { _, _, _ -> Result.failure(IllegalStateException("an error")) }
|
||||||
),
|
),
|
||||||
matrixAuthenticationService = FakeMatrixAuthenticationService(matrixClientResult = { Result.success(aMatrixClient) }),
|
matrixClientProvider = FakeMatrixClientProvider { Result.success(aMatrixClient) },
|
||||||
)
|
)
|
||||||
val result = defaultUnifiedPushNewGatewayHandler.handle(
|
val result = defaultUnifiedPushNewGatewayHandler.handle(
|
||||||
endpoint = "aEndpoint",
|
endpoint = "aEndpoint",
|
||||||
|
|
@ -114,7 +114,7 @@ class DefaultUnifiedPushNewGatewayHandlerTest {
|
||||||
pusherSubscriber = FakePusherSubscriber(
|
pusherSubscriber = FakePusherSubscriber(
|
||||||
registerPusherResult = lambda
|
registerPusherResult = lambda
|
||||||
),
|
),
|
||||||
matrixAuthenticationService = FakeMatrixAuthenticationService(matrixClientResult = { Result.success(aMatrixClient) }),
|
matrixClientProvider = FakeMatrixClientProvider { Result.success(aMatrixClient) },
|
||||||
)
|
)
|
||||||
val result = defaultUnifiedPushNewGatewayHandler.handle(
|
val result = defaultUnifiedPushNewGatewayHandler.handle(
|
||||||
endpoint = "aEndpoint",
|
endpoint = "aEndpoint",
|
||||||
|
|
@ -131,13 +131,13 @@ class DefaultUnifiedPushNewGatewayHandlerTest {
|
||||||
pusherSubscriber: PusherSubscriber = FakePusherSubscriber(),
|
pusherSubscriber: PusherSubscriber = FakePusherSubscriber(),
|
||||||
userPushStoreFactory: UserPushStoreFactory = FakeUserPushStoreFactory(),
|
userPushStoreFactory: UserPushStoreFactory = FakeUserPushStoreFactory(),
|
||||||
pushClientSecret: PushClientSecret = FakePushClientSecret(),
|
pushClientSecret: PushClientSecret = FakePushClientSecret(),
|
||||||
matrixAuthenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService()
|
matrixClientProvider: MatrixClientProvider = FakeMatrixClientProvider()
|
||||||
): DefaultUnifiedPushNewGatewayHandler {
|
): DefaultUnifiedPushNewGatewayHandler {
|
||||||
return DefaultUnifiedPushNewGatewayHandler(
|
return DefaultUnifiedPushNewGatewayHandler(
|
||||||
pusherSubscriber = pusherSubscriber,
|
pusherSubscriber = pusherSubscriber,
|
||||||
userPushStoreFactory = userPushStoreFactory,
|
userPushStoreFactory = userPushStoreFactory,
|
||||||
pushClientSecret = pushClientSecret,
|
pushClientSecret = pushClientSecret,
|
||||||
matrixAuthenticationService = matrixAuthenticationService
|
matrixClientProvider = matrixClientProvider,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,6 @@ class StableCharSequence(initialText: CharSequence = "") {
|
||||||
fun needsDisplaying(): Boolean = needsDisplaying
|
fun needsDisplaying(): Boolean = needsDisplaying
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "ImmutableCharSequence(value='$value', needsDisplaying=$needsDisplaying)"
|
return "StableCharSequence(value='$value', needsDisplaying=$needsDisplaying)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,7 @@ class MarkdownTextInputTest {
|
||||||
onTyping: (Boolean) -> Unit = {},
|
onTyping: (Boolean) -> Unit = {},
|
||||||
onSuggestionReceived: (Suggestion?) -> Unit = {},
|
onSuggestionReceived: (Suggestion?) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
rule.setContent {
|
setContent {
|
||||||
val style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.hasFocus)
|
val style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.hasFocus)
|
||||||
MarkdownTextInput(
|
MarkdownTextInput(
|
||||||
state = state,
|
state = state,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:d6eee7bae50007c89720f7a0f14512828a202db0a6c139690d09dd2e051af8ed
|
||||||
|
size 11766
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:73d053268a0b2e968a35c02ab58dc5f0a9ff8e9b3412a6c88f5bfeb94031bc9a
|
||||||
|
size 18742
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a096a8e2170f14cf87063bf25c65b8a019531979b7702755bd86da84bb1a2eda
|
||||||
|
size 10257
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:4fd3519872e9e0036da1cf158dc8c8c838545b281c9c5fa87c84a2220c066131
|
||||||
|
size 16723
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d703bbf9fa2dd21d4b7e1dea3f9096520e04dddc61a66286f28e1be2b6deae41
|
oid sha256:485585964998ee6bb7bb6ba208f2f465795a1b1ef59b3e44535b2d60ba9ef8a4
|
||||||
size 37724
|
size 37761
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:278063037575311b7ad24f91c2403c5e9656871ab956938d163fa94a89a2b23e
|
oid sha256:95a05799a39675cfbb8c2df269131e5dd236c3298a1dadafe15b7c89a62be962
|
||||||
size 49038
|
size 49077
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:eeca1217caff14f715fcc214dc7f2a01c5d022778c1d9331d41858b836df2abc
|
oid sha256:254973e1b41987fb6dd9db63b5acae4557467e2541867a4fb9448b88851e5fe4
|
||||||
size 46971
|
size 46993
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6c6277560b3b430a446fb4e87b3db59331e5e024798d85bf6be6672a6508c6ef
|
oid sha256:cb58ee8ee32d1e308b898980f27317193bff4b0e00f800037834b02793b34dce
|
||||||
size 41076
|
size 41111
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:700e9c08e7407d2d81e1a090720885cc59b92ab95106258a646532a277cf2012
|
oid sha256:2d2fc075202325aece0e74f962e5e6a387934d7782a1b39a9226c2b529ecf0b1
|
||||||
size 36646
|
size 36809
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:db6b5ec486e460be8cc3df6d827ae04d67ba136d2cdf20b14d1c1ab08198136b
|
oid sha256:6909fcc10374f46d14ed628a4c1df0f4aba1fba874d2aa1b5554a74409dae861
|
||||||
size 47557
|
size 47737
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:cf789850fac3937f98b7d0fc27f030fd0245212bc6087cb34e7d1aebae850e27
|
oid sha256:48e95b0eac9a83ce6abf5d82e904faf4081496d399eed9f8e4d806393f042a14
|
||||||
size 46279
|
size 46286
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:549c3981541b71058c6608a9cffee28a6cb95b1eab819db5de86c43eed9961e3
|
oid sha256:83c91d4e1bc3b9021c91d353b63a63c1893fdc79de57f91d8a6446b84bae657a
|
||||||
size 38319
|
size 38441
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue