Merge pull request #1648 from vector-im/feature/bma/secureBackup
Secure backup
This commit is contained in:
commit
63cf5ef5b1
77 changed files with 284 additions and 158 deletions
|
|
@ -14,9 +14,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.securebackup.impl
|
package io.element.android.appconfig
|
||||||
|
|
||||||
// TODO Move to appconfig module when it will be available
|
|
||||||
object SecureBackupConfig {
|
object SecureBackupConfig {
|
||||||
const val LearnMoreUrl: String = "https://element.io/help#encryption5"
|
const val LearnMoreUrl: String = "https://element.io/help#encryption5"
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +58,7 @@ class LogoutNode @AssistedInject constructor(
|
||||||
state = state,
|
state = state,
|
||||||
onChangeRecoveryKeyClicked = ::onChangeRecoveryKeyClicked,
|
onChangeRecoveryKeyClicked = ::onChangeRecoveryKeyClicked,
|
||||||
onSuccessLogout = { onSuccessLogout(activity, it) },
|
onSuccessLogout = { onSuccessLogout(activity, it) },
|
||||||
|
onBackClicked = ::navigateUp,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -46,13 +47,15 @@ class LogoutPresenter @Inject constructor(
|
||||||
mutableStateOf(Async.Uninitialized)
|
mutableStateOf(Async.Uninitialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
val backupUploadState by encryptionService.backupUploadStateStateFlow.collectAsState()
|
val backupUploadState: BackupUploadState by remember {
|
||||||
|
encryptionService.waitForBackupUploadSteadyState()
|
||||||
|
}
|
||||||
|
.collectAsState(initial = BackupUploadState.Unknown)
|
||||||
|
|
||||||
var showLogoutDialog by remember { mutableStateOf(false) }
|
var showLogoutDialog by remember { mutableStateOf(false) }
|
||||||
var isLastSession by remember { mutableStateOf(false) }
|
var isLastSession by remember { mutableStateOf(false) }
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
isLastSession = encryptionService.isLastDevice().getOrNull() ?: false
|
isLastSession = encryptionService.isLastDevice().getOrNull() ?: false
|
||||||
encryptionService.waitForBackupUploadSteadyState()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleEvents(event: LogoutEvents) {
|
fun handleEvents(event: LogoutEvents) {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
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.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -32,6 +33,7 @@ import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMo
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
|
@ -39,16 +41,19 @@ import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator
|
import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator
|
||||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.designsystem.theme.progressIndicatorTrackColor
|
import io.element.android.libraries.designsystem.theme.progressIndicatorTrackColor
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||||
import io.element.android.libraries.theme.ElementTheme
|
import io.element.android.libraries.theme.ElementTheme
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun LogoutView(
|
fun LogoutView(
|
||||||
state: LogoutState,
|
state: LogoutState,
|
||||||
onChangeRecoveryKeyClicked: () -> Unit,
|
onChangeRecoveryKeyClicked: () -> Unit,
|
||||||
|
onBackClicked: () -> Unit,
|
||||||
onSuccessLogout: (logoutUrlResult: String?) -> Unit,
|
onSuccessLogout: (logoutUrlResult: String?) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -56,6 +61,12 @@ fun LogoutView(
|
||||||
|
|
||||||
HeaderFooterPage(
|
HeaderFooterPage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
navigationIcon = { BackButton(onClick = onBackClicked) },
|
||||||
|
title = {},
|
||||||
|
)
|
||||||
|
},
|
||||||
header = {
|
header = {
|
||||||
HeaderContent(state = state)
|
HeaderContent(state = state)
|
||||||
},
|
},
|
||||||
|
|
@ -134,7 +145,7 @@ private fun HeaderContent(
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
val paddingTop = 60.dp
|
val paddingTop = 0.dp
|
||||||
IconTitleSubtitleMolecule(
|
IconTitleSubtitleMolecule(
|
||||||
modifier = modifier.padding(top = paddingTop),
|
modifier = modifier.padding(top = paddingTop),
|
||||||
iconResourceId = CommonDrawables.ic_key,
|
iconResourceId = CommonDrawables.ic_key,
|
||||||
|
|
@ -219,6 +230,7 @@ internal fun LogoutViewPreview(
|
||||||
LogoutView(
|
LogoutView(
|
||||||
state,
|
state,
|
||||||
onChangeRecoveryKeyClicked = {},
|
onChangeRecoveryKeyClicked = {},
|
||||||
onSuccessLogout = {}
|
onSuccessLogout = {},
|
||||||
|
onBackClicked = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -73,6 +75,15 @@ class LogoutPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state - backing up`() = runTest {
|
fun `present - initial state - backing up`() = runTest {
|
||||||
val encryptionService = FakeEncryptionService()
|
val encryptionService = FakeEncryptionService()
|
||||||
|
encryptionService.givenWaitForBackupUploadSteadyStateFlow(
|
||||||
|
flow {
|
||||||
|
emit(BackupUploadState.Waiting)
|
||||||
|
delay(1)
|
||||||
|
emit(BackupUploadState.Uploading(backedUpCount = 1, totalCount = 2))
|
||||||
|
delay(1)
|
||||||
|
emit(BackupUploadState.Done)
|
||||||
|
}
|
||||||
|
)
|
||||||
val presenter = createLogoutPresenter(
|
val presenter = createLogoutPresenter(
|
||||||
encryptionService = encryptionService
|
encryptionService = encryptionService
|
||||||
)
|
)
|
||||||
|
|
@ -84,10 +95,10 @@ class LogoutPresenterTest {
|
||||||
assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Unknown)
|
assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Unknown)
|
||||||
assertThat(initialState.showConfirmationDialog).isFalse()
|
assertThat(initialState.showConfirmationDialog).isFalse()
|
||||||
assertThat(initialState.logoutAction).isEqualTo(Async.Uninitialized)
|
assertThat(initialState.logoutAction).isEqualTo(Async.Uninitialized)
|
||||||
encryptionService.emitBackupUploadState(BackupUploadState.Uploading(backedUpCount = 1, totalCount = 2))
|
val waitingState = awaitItem()
|
||||||
val state = awaitItem()
|
assertThat(waitingState.backupUploadState).isEqualTo(BackupUploadState.Waiting)
|
||||||
assertThat(state.backupUploadState).isEqualTo(BackupUploadState.Uploading(backedUpCount = 1, totalCount = 2))
|
val uploadingState = awaitItem()
|
||||||
encryptionService.emitBackupUploadState(BackupUploadState.Done)
|
assertThat(uploadingState.backupUploadState).isEqualTo(BackupUploadState.Uploading(backedUpCount = 1, totalCount = 2))
|
||||||
val doneState = awaitItem()
|
val doneState = awaitItem()
|
||||||
assertThat(doneState.backupUploadState).isEqualTo(BackupUploadState.Done)
|
assertThat(doneState.backupUploadState).isEqualTo(BackupUploadState.Done)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,8 @@ import io.element.android.features.roomlist.impl.components.RoomListTopBar
|
||||||
import io.element.android.features.roomlist.impl.components.RoomSummaryRow
|
import io.element.android.features.roomlist.impl.components.RoomSummaryRow
|
||||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||||
import io.element.android.features.roomlist.impl.search.RoomListSearchResultView
|
import io.element.android.features.roomlist.impl.search.RoomListSearchResultView
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.FloatingActionButton
|
import io.element.android.libraries.designsystem.theme.components.FloatingActionButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
|
|
@ -190,19 +190,22 @@ private fun RoomListContent(
|
||||||
.nestedScroll(nestedScrollConnection),
|
.nestedScroll(nestedScrollConnection),
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
) {
|
) {
|
||||||
if (state.displayVerificationPrompt) {
|
when {
|
||||||
item {
|
state.displayVerificationPrompt -> {
|
||||||
RequestVerificationHeader(
|
item {
|
||||||
onVerifyClicked = onVerifyClicked,
|
RequestVerificationHeader(
|
||||||
onDismissClicked = { state.eventSink(RoomListEvents.DismissRequestVerificationPrompt) }
|
onVerifyClicked = onVerifyClicked,
|
||||||
)
|
onDismissClicked = { state.eventSink(RoomListEvents.DismissRequestVerificationPrompt) }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (state.displayRecoveryKeyPrompt) {
|
state.displayRecoveryKeyPrompt -> {
|
||||||
item {
|
item {
|
||||||
ConfirmRecoveryKeyBanner(
|
ConfirmRecoveryKeyBanner(
|
||||||
onContinueClicked = onOpenSettings,
|
onContinueClicked = onOpenSettings,
|
||||||
onDismissClicked = { state.eventSink(RoomListEvents.DismissRecoveryKeyPrompt) }
|
onDismissClicked = { state.eventSink(RoomListEvents.DismissRecoveryKeyPrompt) }
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ dependencies {
|
||||||
anvil(projects.anvilcodegen)
|
anvil(projects.anvilcodegen)
|
||||||
implementation(projects.anvilannotations)
|
implementation(projects.anvilannotations)
|
||||||
|
|
||||||
|
implementation(projects.appconfig)
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ class SecureBackupDisableNode @AssistedInject constructor(
|
||||||
state = state,
|
state = state,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onDone = ::navigateUp,
|
onDone = ::navigateUp,
|
||||||
|
onBackClicked = ::navigateUp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.Row
|
||||||
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.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -33,6 +34,7 @@ import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||||
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
|
@ -40,13 +42,16 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
import io.element.android.libraries.theme.ElementTheme
|
import io.element.android.libraries.theme.ElementTheme
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SecureBackupDisableView(
|
fun SecureBackupDisableView(
|
||||||
state: SecureBackupDisableState,
|
state: SecureBackupDisableState,
|
||||||
onDone: () -> Unit,
|
onDone: () -> Unit,
|
||||||
|
onBackClicked: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
LaunchedEffect(state.disableAction) {
|
LaunchedEffect(state.disableAction) {
|
||||||
|
|
@ -56,6 +61,12 @@ fun SecureBackupDisableView(
|
||||||
}
|
}
|
||||||
HeaderFooterPage(
|
HeaderFooterPage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
navigationIcon = { BackButton(onClick = onBackClicked) },
|
||||||
|
title = {},
|
||||||
|
)
|
||||||
|
},
|
||||||
header = {
|
header = {
|
||||||
HeaderContent()
|
HeaderContent()
|
||||||
},
|
},
|
||||||
|
|
@ -95,7 +106,7 @@ private fun HeaderContent(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
IconTitleSubtitleMolecule(
|
IconTitleSubtitleMolecule(
|
||||||
modifier = modifier.padding(top = 60.dp),
|
modifier = modifier.padding(top = 0.dp),
|
||||||
iconResourceId = CommonDrawables.ic_key_off,
|
iconResourceId = CommonDrawables.ic_key_off,
|
||||||
title = stringResource(id = R.string.screen_key_backup_disable_title),
|
title = stringResource(id = R.string.screen_key_backup_disable_title),
|
||||||
subTitle = stringResource(id = R.string.screen_key_backup_disable_description),
|
subTitle = stringResource(id = R.string.screen_key_backup_disable_description),
|
||||||
|
|
@ -158,5 +169,6 @@ internal fun SecureBackupDisableViewPreview(
|
||||||
SecureBackupDisableView(
|
SecureBackupDisableView(
|
||||||
state = state,
|
state = state,
|
||||||
onDone = {},
|
onDone = {},
|
||||||
|
onBackClicked = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ class SecureBackupEnableNode @AssistedInject constructor(
|
||||||
state = state,
|
state = state,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onDone = ::navigateUp,
|
onDone = ::navigateUp,
|
||||||
|
onBackClicked = ::navigateUp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.securebackup.impl.enable
|
||||||
|
|
||||||
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.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -29,16 +30,20 @@ import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||||
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SecureBackupEnableView(
|
fun SecureBackupEnableView(
|
||||||
state: SecureBackupEnableState,
|
state: SecureBackupEnableState,
|
||||||
onDone: () -> Unit,
|
onDone: () -> Unit,
|
||||||
|
onBackClicked: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
LaunchedEffect(state.enableAction) {
|
LaunchedEffect(state.enableAction) {
|
||||||
|
|
@ -48,6 +53,12 @@ fun SecureBackupEnableView(
|
||||||
}
|
}
|
||||||
HeaderFooterPage(
|
HeaderFooterPage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
navigationIcon = { BackButton(onClick = onBackClicked) },
|
||||||
|
title = {},
|
||||||
|
)
|
||||||
|
},
|
||||||
header = {
|
header = {
|
||||||
HeaderContent()
|
HeaderContent()
|
||||||
},
|
},
|
||||||
|
|
@ -68,7 +79,7 @@ private fun HeaderContent(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
IconTitleSubtitleMolecule(
|
IconTitleSubtitleMolecule(
|
||||||
modifier = modifier.padding(top = 60.dp),
|
modifier = modifier.padding(top = 0.dp),
|
||||||
iconResourceId = CommonDrawables.ic_key,
|
iconResourceId = CommonDrawables.ic_key,
|
||||||
title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable),
|
title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable),
|
||||||
subTitle = null,
|
subTitle = null,
|
||||||
|
|
@ -99,5 +110,6 @@ internal fun SecureBackupEnableViewPreview(
|
||||||
SecureBackupEnableView(
|
SecureBackupEnableView(
|
||||||
state = state,
|
state = state,
|
||||||
onDone = {},
|
onDone = {},
|
||||||
|
onBackClicked = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,8 @@ class SecureBackupEnterRecoveryKeyNode @AssistedInject constructor(
|
||||||
onDone = {
|
onDone = {
|
||||||
coroutineScope.postSuccessSnackbar()
|
coroutineScope.postSuccessSnackbar()
|
||||||
navigateUp()
|
navigateUp()
|
||||||
}
|
},
|
||||||
|
onBackClicked = ::navigateUp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.securebackup.impl.enter
|
||||||
|
|
||||||
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.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -30,17 +31,21 @@ import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||||
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SecureBackupEnterRecoveryKeyView(
|
fun SecureBackupEnterRecoveryKeyView(
|
||||||
state: SecureBackupEnterRecoveryKeyState,
|
state: SecureBackupEnterRecoveryKeyState,
|
||||||
onDone: () -> Unit,
|
onDone: () -> Unit,
|
||||||
|
onBackClicked: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
when (state.submitAction) {
|
when (state.submitAction) {
|
||||||
|
|
@ -59,6 +64,12 @@ fun SecureBackupEnterRecoveryKeyView(
|
||||||
|
|
||||||
HeaderFooterPage(
|
HeaderFooterPage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
navigationIcon = { BackButton(onClick = onBackClicked) },
|
||||||
|
title = {},
|
||||||
|
)
|
||||||
|
},
|
||||||
header = {
|
header = {
|
||||||
HeaderContent()
|
HeaderContent()
|
||||||
},
|
},
|
||||||
|
|
@ -75,6 +86,9 @@ fun SecureBackupEnterRecoveryKeyView(
|
||||||
state = state,
|
state = state,
|
||||||
onChange = {
|
onChange = {
|
||||||
state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.OnRecoveryKeyChange(it))
|
state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.OnRecoveryKeyChange(it))
|
||||||
|
},
|
||||||
|
onSubmit = {
|
||||||
|
state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.Submit)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +98,7 @@ private fun HeaderContent(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
IconTitleSubtitleMolecule(
|
IconTitleSubtitleMolecule(
|
||||||
modifier = modifier.padding(top = 60.dp),
|
modifier = modifier.padding(top = 0.dp),
|
||||||
iconResourceId = CommonDrawables.ic_key,
|
iconResourceId = CommonDrawables.ic_key,
|
||||||
title = stringResource(id = R.string.screen_recovery_key_confirm_title),
|
title = stringResource(id = R.string.screen_recovery_key_confirm_title),
|
||||||
subTitle = stringResource(id = R.string.screen_recovery_key_confirm_description),
|
subTitle = stringResource(id = R.string.screen_recovery_key_confirm_description),
|
||||||
|
|
@ -112,13 +126,15 @@ private fun BottomMenu(
|
||||||
@Composable
|
@Composable
|
||||||
private fun Content(
|
private fun Content(
|
||||||
state: SecureBackupEnterRecoveryKeyState,
|
state: SecureBackupEnterRecoveryKeyState,
|
||||||
onChange: ((String) -> Unit)?,
|
onChange: (String) -> Unit,
|
||||||
|
onSubmit: () -> Unit,
|
||||||
) {
|
) {
|
||||||
RecoveryKeyView(
|
RecoveryKeyView(
|
||||||
modifier = Modifier.padding(top = 52.dp),
|
modifier = Modifier.padding(top = 52.dp),
|
||||||
state = state.recoveryKeyViewState,
|
state = state.recoveryKeyViewState,
|
||||||
onClick = null,
|
onClick = null,
|
||||||
onChange = onChange,
|
onChange = onChange,
|
||||||
|
onSubmit = onSubmit,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,5 +146,6 @@ internal fun SecureBackupEnterRecoveryKeyViewPreview(
|
||||||
SecureBackupEnterRecoveryKeyView(
|
SecureBackupEnterRecoveryKeyView(
|
||||||
state = state,
|
state = state,
|
||||||
onDone = {},
|
onDone = {},
|
||||||
|
onBackClicked = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import com.bumble.appyx.core.plugin.plugins
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.features.securebackup.impl.SecureBackupConfig
|
import io.element.android.appconfig.SecureBackupConfig
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ class SecureBackupSetupNode @AssistedInject constructor(
|
||||||
coroutineScope.postSuccessSnackbar()
|
coroutineScope.postSuccessSnackbar()
|
||||||
navigateUp()
|
navigateUp()
|
||||||
},
|
},
|
||||||
|
onBackClicked = ::navigateUp,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,10 @@
|
||||||
|
|
||||||
package io.element.android.features.securebackup.impl.setup
|
package io.element.android.features.securebackup.impl.setup
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
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.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
@ -32,24 +34,42 @@ import io.element.android.libraries.androidutils.system.startSharePlainTextInten
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||||
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SecureBackupSetupView(
|
fun SecureBackupSetupView(
|
||||||
state: SecureBackupSetupState,
|
state: SecureBackupSetupState,
|
||||||
onDone: () -> Unit,
|
onDone: () -> Unit,
|
||||||
|
onBackClicked: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val canGoBack = state.canGoBack()
|
||||||
|
BackHandler(enabled = canGoBack) {
|
||||||
|
onBackClicked()
|
||||||
|
}
|
||||||
HeaderFooterPage(
|
HeaderFooterPage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
navigationIcon = {
|
||||||
|
if (canGoBack) {
|
||||||
|
BackButton(onClick = onBackClicked)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {},
|
||||||
|
)
|
||||||
|
},
|
||||||
header = {
|
header = {
|
||||||
HeaderContent(state = state)
|
HeaderContent(state = state)
|
||||||
},
|
},
|
||||||
|
|
@ -109,6 +129,10 @@ fun SecureBackupSetupView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun SecureBackupSetupState.canGoBack(): Boolean {
|
||||||
|
return recoveryKeyViewState.formattedRecoveryKey == null
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun HeaderContent(
|
private fun HeaderContent(
|
||||||
state: SecureBackupSetupState,
|
state: SecureBackupSetupState,
|
||||||
|
|
@ -136,7 +160,7 @@ private fun HeaderContent(
|
||||||
stringResource(id = R.string.screen_recovery_key_save_description)
|
stringResource(id = R.string.screen_recovery_key_save_description)
|
||||||
}
|
}
|
||||||
IconTitleSubtitleMolecule(
|
IconTitleSubtitleMolecule(
|
||||||
modifier = modifier.padding(top = 60.dp),
|
modifier = modifier.padding(top = 0.dp),
|
||||||
iconResourceId = CommonDrawables.ic_key,
|
iconResourceId = CommonDrawables.ic_key,
|
||||||
title = title,
|
title = title,
|
||||||
subTitle = subTitle,
|
subTitle = subTitle,
|
||||||
|
|
@ -192,6 +216,7 @@ private fun Content(
|
||||||
state = state,
|
state = state,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onChange = null,
|
onChange = null,
|
||||||
|
onSubmit = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -203,5 +228,6 @@ internal fun SecureBackupSetupViewPreview(
|
||||||
SecureBackupSetupView(
|
SecureBackupSetupView(
|
||||||
state = state,
|
state = state,
|
||||||
onDone = {},
|
onDone = {},
|
||||||
|
onBackClicked = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,5 +33,6 @@ internal fun SecureBackupSetupViewChangePreview(
|
||||||
recoveryKeyViewState = state.recoveryKeyViewState.copy(recoveryKeyUserStory = RecoveryKeyUserStory.Change),
|
recoveryKeyViewState = state.recoveryKeyViewState.copy(recoveryKeyUserStory = RecoveryKeyUserStory.Change),
|
||||||
),
|
),
|
||||||
onDone = {},
|
onDone = {},
|
||||||
|
onBackClicked = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,15 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.progressSemantics
|
import androidx.compose.foundation.progressSemantics
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
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.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
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
|
||||||
|
|
@ -52,6 +55,7 @@ internal fun RecoveryKeyView(
|
||||||
state: RecoveryKeyViewState,
|
state: RecoveryKeyViewState,
|
||||||
onClick: (() -> Unit)?,
|
onClick: (() -> Unit)?,
|
||||||
onChange: ((String) -> Unit)?,
|
onChange: ((String) -> Unit)?,
|
||||||
|
onSubmit: (() -> Unit)?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -63,7 +67,7 @@ internal fun RecoveryKeyView(
|
||||||
modifier = Modifier.padding(start = 16.dp),
|
modifier = Modifier.padding(start = 16.dp),
|
||||||
style = ElementTheme.typography.fontBodyMdRegular,
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
)
|
)
|
||||||
RecoveryKeyContent(state, onClick, onChange)
|
RecoveryKeyContent(state, onClick, onChange, onSubmit)
|
||||||
RecoveryKeyFooter(state)
|
RecoveryKeyFooter(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -73,11 +77,12 @@ private fun RecoveryKeyContent(
|
||||||
state: RecoveryKeyViewState,
|
state: RecoveryKeyViewState,
|
||||||
onClick: (() -> Unit)?,
|
onClick: (() -> Unit)?,
|
||||||
onChange: ((String) -> Unit)?,
|
onChange: ((String) -> Unit)?,
|
||||||
|
onSubmit: (() -> Unit)?,
|
||||||
) {
|
) {
|
||||||
when (state.recoveryKeyUserStory) {
|
when (state.recoveryKeyUserStory) {
|
||||||
RecoveryKeyUserStory.Setup,
|
RecoveryKeyUserStory.Setup,
|
||||||
RecoveryKeyUserStory.Change -> RecoveryKeyStaticContent(state, onClick)
|
RecoveryKeyUserStory.Change -> RecoveryKeyStaticContent(state, onClick)
|
||||||
RecoveryKeyUserStory.Enter -> RecoveryKeyFormContent(state, onChange)
|
RecoveryKeyUserStory.Enter -> RecoveryKeyFormContent(state, onChange, onSubmit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,8 +148,13 @@ private fun RecoveryKeyStaticContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RecoveryKeyFormContent(state: RecoveryKeyViewState, onChange: ((String) -> Unit)?) {
|
private fun RecoveryKeyFormContent(
|
||||||
|
state: RecoveryKeyViewState,
|
||||||
|
onChange: ((String) -> Unit)?,
|
||||||
|
onSubmit: (() -> Unit)?,
|
||||||
|
) {
|
||||||
onChange ?: error("onChange should not be null")
|
onChange ?: error("onChange should not be null")
|
||||||
|
onSubmit ?: error("onSubmit should not be null")
|
||||||
val recoveryKeyVisualTransformation = remember {
|
val recoveryKeyVisualTransformation = remember {
|
||||||
RecoveryKeyVisualTransformation()
|
RecoveryKeyVisualTransformation()
|
||||||
}
|
}
|
||||||
|
|
@ -155,6 +165,12 @@ private fun RecoveryKeyFormContent(state: RecoveryKeyViewState, onChange: ((Stri
|
||||||
onValueChange = onChange,
|
onValueChange = onChange,
|
||||||
enabled = state.inProgress.not(),
|
enabled = state.inProgress.not(),
|
||||||
visualTransformation = recoveryKeyVisualTransformation,
|
visualTransformation = recoveryKeyVisualTransformation,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
imeAction = ImeAction.Done,
|
||||||
|
),
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onDone = { onSubmit() }
|
||||||
|
),
|
||||||
label = { Text(text = stringResource(id = R.string.screen_recovery_key_confirm_key_placeholder)) }
|
label = { Text(text = stringResource(id = R.string.screen_recovery_key_confirm_key_placeholder)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -217,5 +233,6 @@ internal fun RecoveryKeyViewPreview(
|
||||||
state = state,
|
state = state,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
onChange = {},
|
onChange = {},
|
||||||
|
onSubmit = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,12 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.api.encryption
|
package io.element.android.libraries.matrix.api.encryption
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
interface EncryptionService {
|
interface EncryptionService {
|
||||||
val backupStateStateFlow: StateFlow<BackupState>
|
val backupStateStateFlow: StateFlow<BackupState>
|
||||||
val recoveryStateStateFlow: StateFlow<RecoveryState>
|
val recoveryStateStateFlow: StateFlow<RecoveryState>
|
||||||
val backupUploadStateStateFlow: StateFlow<BackupUploadState>
|
|
||||||
val enableRecoveryProgressStateFlow: StateFlow<EnableRecoveryProgress>
|
val enableRecoveryProgressStateFlow: StateFlow<EnableRecoveryProgress>
|
||||||
|
|
||||||
suspend fun enableBackups(): Result<Unit>
|
suspend fun enableBackups(): Result<Unit>
|
||||||
|
|
@ -46,7 +46,7 @@ interface EncryptionService {
|
||||||
suspend fun fixRecoveryIssues(recoveryKey: String): Result<Unit>
|
suspend fun fixRecoveryIssues(recoveryKey: String): Result<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observe [backupUploadStateStateFlow] to get progress.
|
* Wait for backup upload steady state.
|
||||||
*/
|
*/
|
||||||
suspend fun waitForBackupUploadSteadyState(): Result<Unit>
|
fun waitForBackupUploadSteadyState(): Flow<BackupUploadState>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,10 @@ import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||||
import io.element.android.libraries.matrix.api.encryption.EnableRecoveryProgress
|
import io.element.android.libraries.matrix.api.encryption.EnableRecoveryProgress
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.rustcomponents.sdk.BackupStateListener
|
import org.matrix.rustcomponents.sdk.BackupStateListener
|
||||||
import org.matrix.rustcomponents.sdk.BackupSteadyStateListener
|
import org.matrix.rustcomponents.sdk.BackupSteadyStateListener
|
||||||
|
|
@ -50,7 +53,6 @@ internal class RustEncryptionService(
|
||||||
override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(service.backupState().let(backupStateMapper::map))
|
override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(service.backupState().let(backupStateMapper::map))
|
||||||
override val recoveryStateStateFlow: MutableStateFlow<RecoveryState> = MutableStateFlow(service.recoveryState().let(recoveryStateMapper::map))
|
override val recoveryStateStateFlow: MutableStateFlow<RecoveryState> = MutableStateFlow(service.recoveryState().let(recoveryStateMapper::map))
|
||||||
override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Unknown)
|
override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Unknown)
|
||||||
override val backupUploadStateStateFlow: MutableStateFlow<BackupUploadState> = MutableStateFlow(BackupUploadState.Unknown)
|
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
service.backupStateListener(object : BackupStateListener {
|
service.backupStateListener(object : BackupStateListener {
|
||||||
|
|
@ -94,16 +96,19 @@ internal class RustEncryptionService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun waitForBackupUploadSteadyState(
|
override fun waitForBackupUploadSteadyState(): Flow<BackupUploadState> {
|
||||||
): Result<Unit> = withContext(dispatchers.io) {
|
return callbackFlow {
|
||||||
runCatching {
|
|
||||||
service.waitForBackupUploadSteadyState(
|
service.waitForBackupUploadSteadyState(
|
||||||
progressListener = object : BackupSteadyStateListener {
|
progressListener = object : BackupSteadyStateListener {
|
||||||
override fun onUpdate(status: RustBackupUploadState) {
|
override fun onUpdate(status: RustBackupUploadState) {
|
||||||
backupUploadStateStateFlow.value = backupUploadStateMapper.map(status)
|
trySend(backupUploadStateMapper.map(status))
|
||||||
|
if (status == RustBackupUploadState.Done) {
|
||||||
|
close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
awaitClose {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,16 @@ import io.element.android.libraries.matrix.api.encryption.EnableRecoveryProgress
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||||
import io.element.android.tests.testutils.simulateLongTask
|
import io.element.android.tests.testutils.simulateLongTask
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
|
||||||
class FakeEncryptionService : EncryptionService {
|
class FakeEncryptionService : EncryptionService {
|
||||||
private var disableRecoveryFailure: Exception? = null
|
private var disableRecoveryFailure: Exception? = null
|
||||||
override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(BackupState.UNKNOWN)
|
override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(BackupState.UNKNOWN)
|
||||||
override val recoveryStateStateFlow: MutableStateFlow<RecoveryState> = MutableStateFlow(RecoveryState.UNKNOWN)
|
override val recoveryStateStateFlow: MutableStateFlow<RecoveryState> = MutableStateFlow(RecoveryState.UNKNOWN)
|
||||||
override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Unknown)
|
override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Unknown)
|
||||||
override val backupUploadStateStateFlow: MutableStateFlow<BackupUploadState> = MutableStateFlow(BackupUploadState.Unknown)
|
private var waitForBackupUploadSteadyStateFlow: Flow<BackupUploadState> = flowOf()
|
||||||
|
|
||||||
private var fixRecoveryIssuesFailure: Exception? = null
|
private var fixRecoveryIssuesFailure: Exception? = null
|
||||||
|
|
||||||
|
|
@ -73,12 +75,12 @@ class FakeEncryptionService : EncryptionService {
|
||||||
return Result.success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun waitForBackupUploadSteadyState(): Result<Unit> {
|
fun givenWaitForBackupUploadSteadyStateFlow(flow: Flow<BackupUploadState>) {
|
||||||
return Result.success(Unit)
|
waitForBackupUploadSteadyStateFlow = flow
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun emitBackupUploadState(state: BackupUploadState) {
|
override fun waitForBackupUploadSteadyState(): Flow<BackupUploadState> {
|
||||||
backupUploadStateStateFlow.emit(state)
|
return waitForBackupUploadSteadyStateFlow
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun emitBackupState(state: BackupState) {
|
suspend fun emitBackupState(state: BackupState) {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c8c36602ad1aa6a62cae8f69b4e0a185715a4105a958a17451e3f5573618a499
|
oid sha256:75e44003e66f73983d63b0735217a3f99d1ef2af5778d53e256ab3c644c59cb8
|
||||||
size 28590
|
size 29114
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:30777b75b1464bbaf0ab63c73189ae8d6b380d677ae87c22ad59a9052dbda564
|
oid sha256:1a9b3f53a32a0924ae1db60f01df80e1e098d332ae29fdbfda527fb69b6a8860
|
||||||
size 32530
|
size 33097
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:23d11018a66dcfca97713d7dfd5916a1c00f01881c21aad6014101a17b5b8912
|
oid sha256:1807fd9e347c29c853d925c28f1e00259865eb9e7990fca1ad63e90a9eea8a1d
|
||||||
size 30167
|
size 30676
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8b3b64d82224c1e28cfad0d5ac81796a2461311d63d23e84f9bf19b290b08bcd
|
oid sha256:0c140c29ad4a849893e08ed5b1943346ebc7ae5b888fb98b97088d2a5f025087
|
||||||
size 37255
|
size 37967
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:aeee9a02b5bfa15ff7dc2e45f374237b2fa198df9e3a0a92598a950af7431a5b
|
oid sha256:d329bff55f22efa99e8afa20775a25f4c7e717cf5797ecc7a2c0f47e4cc1f69a
|
||||||
size 34809
|
size 34937
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:5437f1dd640de8500ce78fe810a92c5174879918b592e746f09a7244b4b98d74
|
oid sha256:b72c4ecdb641d2adf53c092ac1ccc85018a747ffb0f4b911d3b10db6524ea897
|
||||||
size 31993
|
size 31588
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:b3c05dd55dfd945a7c7528492d47698586089b618ae6ab811ae188a808a0a4b8
|
oid sha256:704aac22e5063a3cc9b831c9079f1348385a1251c91630575200c33528fe3d76
|
||||||
size 33355
|
size 33480
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:39091e9262d0d57d51468ae809598d44829a2a33759e62f18154444c0c614f50
|
oid sha256:ac0f94f62f6d89475fde477b095dc1242a606d9cd4bfa45a8cfaa8e368e22707
|
||||||
size 26893
|
size 27385
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:424a67c305485dc3d151d7771df9b9e724aa076e3758dc9cb341b86184163a39
|
oid sha256:9f843cb5d7bceebbc6508257f0410af14242b23407ecea4643ab8a75329acca2
|
||||||
size 30551
|
size 30994
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2a1e679206fef656da4fbcba44220cc169d45e01faf74c6bdb174a807ccf89a2
|
oid sha256:d9690fdef2aaabadb890c8649195de83cd60306f109346d292951ff6694037bd
|
||||||
size 28529
|
size 28955
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:0f1a452799ce1f0055c17ec86a5dabe7d3f5f5688b15bc9e9f5cdde7c39774b7
|
oid sha256:c75561844b2e5d163c7cdb49a36eb4f3adb4119fda2b9e4b6d9552daf447b907
|
||||||
size 35541
|
size 35817
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:edf4ff593991f2cc88999034ff5ba80599d48ced6d23825a385ecf82d61d4686
|
oid sha256:996388f47d4cd4c7c6318d335204a1b3610272ea1b340e4e36f10da222f3bcb6
|
||||||
size 30383
|
size 30476
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:63b528173b275d15fb8c0ec5d173b63636f24a3897227f4fb37e06a817a7521e
|
oid sha256:aa9f65a1f2e08e65f21aae2b4b6fb17b980262196443aec6b9cbc6c912077c78
|
||||||
size 28881
|
size 28497
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:5adffcc0b87707daba799293ec36b9e8202c72de2248835ed593b87fc82ea9b4
|
oid sha256:9f63c93ba36566926aa471375f0bb341e1c989e5b1be9090f771597f91a7a226
|
||||||
size 29168
|
size 29258
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2b042f7e1248b739be1b0b96782d3970c0bfafa06c48ca507acbe882df471de3
|
oid sha256:83e046cae9a59996697172a964c55c53a35ab22404b46cb2fdbf45956254d36d
|
||||||
size 58735
|
size 59003
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6df1c5767b684843e39d31e6056133e72eb76d5d229db0d7176ff1be02852880
|
oid sha256:576dc46e025bfa281a8f160315c7df052f8e9de85d1894a4a6b630eb10b4fad6
|
||||||
size 48100
|
size 47781
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d2c79522043f06f1a2d5810b1e2206d8b6d2ccea18389fb9a11a18887dc2d895
|
oid sha256:4335f4f058d36b6cf9364ee2135fe692783c3a73302ad7267599ca0d62f20fd5
|
||||||
size 59411
|
size 59675
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:9f5e1a5524453cc1814322315bde1180aa1e26f3f2bf22ec5e797c8767bd8675
|
oid sha256:4ec952a7c1f8e032689bd045e7bf094987ef75e7ae63357ba247fbce087ff843
|
||||||
size 30634
|
size 30449
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:49f4d57ff2771fb32f198b506f0a0b7b7fd4f8f0af7f5b78c2369dfaec2e6543
|
oid sha256:17747da89c21acc64c3b869d1aaea3ce6308139e8e505a973e6190cd2b48e6ed
|
||||||
size 57060
|
size 57249
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:97c8f3ca9f5f9f4c89930b1279261125a548d5770a361f0f9749904212bb3fb3
|
oid sha256:b5c652cb90b4e8d5849270ba732417c0dc9cbe5e1f84dcf6916dfad43f712dff
|
||||||
size 42305
|
size 41926
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d34567f158c2480ba0c6884663b8c996d4754e069f558103b8de90a25a22a8e0
|
oid sha256:5e68b8e510524f04f4f764e0fd33b2b0b3e395bd87eeae0e467007bf60ab2708
|
||||||
size 57550
|
size 57739
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d50b5caf3f62d349985102f951a6bf679923b3726f420c6d55e128d6763d5b40
|
oid sha256:4e3b46d43c8a9ce48c7226fc3159d1ec72fd55cbf7dd5b4de866fb4081e52116
|
||||||
size 27043
|
size 26804
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8ffce711923870d9b43a9d4c2871ac1794d3d7e2b34a2063ab6f9e56c4e22dc0
|
oid sha256:8a86b726999a4d994fa4f7a778e4f80b122e912c5c15285ea91fd8814d8eb743
|
||||||
size 15203
|
size 15694
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:5ef1cae71114c56ad6173cdeef356da56ce2769d5bd4a1332133f048d241a8d1
|
oid sha256:5778ff8908d601c853a0eef84ce72026baa7ca461b8dc5b2c30e48c95153bc91
|
||||||
size 15777
|
size 16278
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:82d71934698b4e25afaaf9024c5dbb0571c3f8fd3270b5b07ff8d8dd9045bdd9
|
oid sha256:e4b919c589d65d61c7ee11588ff628e02603cd3e4c5fc79a672d3991d64fcebd
|
||||||
size 21841
|
size 22276
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4036acde06bd1fb9e256a97b53a6711ec45db4b67141b01231b491512608e457
|
oid sha256:01653b0165eaf33b77948e41899cb21177fa70d9101eb04ad43ee7c7f4ab3607
|
||||||
size 14271
|
size 14718
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1d4d58607c126ae4a01a0ba62e09666fac11d3a2b05596742bc4e25cf75696fb
|
oid sha256:39d22d5c0e0b88869900ddf28de055188c17c3e59dfa528a2d81b552ebb140a3
|
||||||
size 14830
|
size 15282
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e10389377cd21f189794e8a91cecee43b3c44a0030d5f312558dfef7add80dd1
|
oid sha256:2d4856648cb21a768d174fe1ab6675be9c1c565e1d535cd3fe7d92d3f6c05d1c
|
||||||
size 18548
|
size 18970
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:90d7dab73ed32794eceac418541c2cf851d1e8bae0fdd5c34f8d9f0425890dc1
|
oid sha256:05af89ec739d652391d64c56db2d5704075c34b8392386bfa6ca819b863b2fa0
|
||||||
size 33998
|
size 34283
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e9949ae01bf9aa195e88cb4d06ec0abd61e4bc3dee402f0cdf6617b7559ec318
|
oid sha256:ad0763e5588212f988a56f1f2b3558c29b332ce832cb9ebaaffcbcebab4d457c
|
||||||
size 46006
|
size 46477
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:386e41bb476411e1836be260ae309361b1493a914508484fda618783dffae00d
|
oid sha256:383a54396c54861554b53c95700ac49e84c0b9e0ded95aeb4246ab8aa7e9a703
|
||||||
size 44521
|
size 44834
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e9893ff64a23c98a13892235f36c52e2ab5ce5a57b3ee7953b884d81ebcfcd89
|
oid sha256:f9892182ed80b39ebc3151335979967a516793b0ca761df35a7649091372d390
|
||||||
size 33201
|
size 32737
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:691eb429d1e3a799d99213225a2bd92fa0b7b3ed8b384c8910ba114b5e05b514
|
oid sha256:cad8f3834b84f1374fce154a90acc0b6d2279ecca29630575764962ef8a107e9
|
||||||
size 32221
|
size 32510
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:27e4d8474ca09ee1a76947464c1b8bd3c6aeb2f0b73b67cca5736d62d92e286d
|
oid sha256:9e421009e738803d834e4fe603900b828f5e4a2d85ffa5784e8d4f7a40810b80
|
||||||
size 42834
|
size 43156
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:119fa492046cbfa51e9eb406fea4c4a9a6fb7dd152e3779153a2df2947800f76
|
oid sha256:e254d17ebeb9e991eaeac000b9148a0c0279d31eac2996e8bf78491747089b30
|
||||||
size 41383
|
size 41767
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:dab45727bd828a76376b796e0d76794d7fb42ac80bc8c721b661eaf0926bb382
|
oid sha256:b8ae3debf28076d7d0d71a2f3784c7d5cfd9dfc50ea534a4b44a952da1a4d947
|
||||||
size 29544
|
size 29166
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d2f9a83985ed5f607f6f6618794857468c4e71451bb58ba160478f8fb39a78a8
|
oid sha256:09e3eb3b83d8f0b74e0d237b3424c68a9b90e077c37d2e472a194583086f7115
|
||||||
size 51611
|
size 51884
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:cad87dcb5fb62c8d577c6e0ac4a13681a827c9d9bd1e2880d3cc3ad802802376
|
oid sha256:a8cd8131baca93c8fb4c40cde91671155052cc749b0cefe4ba5a276f89f212ab
|
||||||
size 49737
|
size 50017
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4ac22cb35d4842fdab146bcaa8b89b6b09e53354c79fb6c865ab3b76b08c35a7
|
oid sha256:744f0f6ec4910b54fd13ff423b1e0555ac0cff1c59123e6f5d096496f14603af
|
||||||
size 53186
|
size 53238
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4ac22cb35d4842fdab146bcaa8b89b6b09e53354c79fb6c865ab3b76b08c35a7
|
oid sha256:744f0f6ec4910b54fd13ff423b1e0555ac0cff1c59123e6f5d096496f14603af
|
||||||
size 53186
|
size 53238
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:14fd53641ce5e6d7246bbd575ba4d91d73a1ddda332fa134619afd011e9086bb
|
oid sha256:5d958c172b24915c6b03d8e75ca2611f95cf523eae30cdfb0af4bc40e27cb6c5
|
||||||
size 51267
|
size 48553
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:385b8e5012fad2197297fa1782226a0938a63e53bc4ed1dfa13d82b885a2ca74
|
oid sha256:9973919b98ce55f98cb9bc00b98af8b90b7b7355235cad6bc1b4b8af799f6bc3
|
||||||
size 49691
|
size 49981
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e87bac6fa87595efb59245e2289ec7badeab60b959f7d99898cb404cf3c484f4
|
oid sha256:ebc7011596661eafd90f646f59e986e7f46d942188fb0224fec03b8362284ab1
|
||||||
size 47704
|
size 47988
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d8f02343264623c0ee8d1eeb3ea48702f45d672bb4f319fd78e6eb488aab10a0
|
oid sha256:5b4290983b3a2b2f13df38f6d95c2ae777230aae7018209f807793186eb2565b
|
||||||
size 50268
|
size 50426
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d8f02343264623c0ee8d1eeb3ea48702f45d672bb4f319fd78e6eb488aab10a0
|
oid sha256:5b4290983b3a2b2f13df38f6d95c2ae777230aae7018209f807793186eb2565b
|
||||||
size 50268
|
size 50426
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8be58c4edb9be3cadbd15ac98f8eabbf4ed72af5cac31f0d3d0a1f18c86c5046
|
oid sha256:20530d92efe5c6c52daaa99342923ad7310f801515677899f77d1f5bf1de41f2
|
||||||
size 45221
|
size 42597
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:dba09f9888d8efd1036c66baf702d05bdd3f4a26c39ca14ae5c59ae2571d00b7
|
oid sha256:dbe8d4ff5c2fca528c12e2475ec2179489c6cbaf3e98d0fa45f0980dcd4f9aab
|
||||||
size 50245
|
size 50590
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ad43dd16b6b0232978b83795b739dcb71778a3a0faf7b8a4da599f23ec653237
|
oid sha256:f1950689df7774679f0a10f679fc5be61917d3f89fe127dff037c119d675a0f7
|
||||||
size 48125
|
size 48482
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4ac22cb35d4842fdab146bcaa8b89b6b09e53354c79fb6c865ab3b76b08c35a7
|
oid sha256:744f0f6ec4910b54fd13ff423b1e0555ac0cff1c59123e6f5d096496f14603af
|
||||||
size 53186
|
size 53238
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4ac22cb35d4842fdab146bcaa8b89b6b09e53354c79fb6c865ab3b76b08c35a7
|
oid sha256:744f0f6ec4910b54fd13ff423b1e0555ac0cff1c59123e6f5d096496f14603af
|
||||||
size 53186
|
size 53238
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:14fd53641ce5e6d7246bbd575ba4d91d73a1ddda332fa134619afd011e9086bb
|
oid sha256:5d958c172b24915c6b03d8e75ca2611f95cf523eae30cdfb0af4bc40e27cb6c5
|
||||||
size 51267
|
size 48553
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:67c874c41e3ce38f3cf2ccc6cf3759d6fec0a675fffa9cea94df341a8792c61e
|
oid sha256:1c426686054a0b72361c9ec2b93318c91f01f0d365d03593b03596dcfeef162d
|
||||||
size 48296
|
size 48559
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:9b32141d05f2e1b89eab149b91e02354b7db464a7c0b42516da74e18af777aa6
|
oid sha256:1fca0245cec36a31642087fcafd4d65b1d670dfe315b6cadd0203be3e029984c
|
||||||
size 46048
|
size 46307
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d8f02343264623c0ee8d1eeb3ea48702f45d672bb4f319fd78e6eb488aab10a0
|
oid sha256:5b4290983b3a2b2f13df38f6d95c2ae777230aae7018209f807793186eb2565b
|
||||||
size 50268
|
size 50426
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d8f02343264623c0ee8d1eeb3ea48702f45d672bb4f319fd78e6eb488aab10a0
|
oid sha256:5b4290983b3a2b2f13df38f6d95c2ae777230aae7018209f807793186eb2565b
|
||||||
size 50268
|
size 50426
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8be58c4edb9be3cadbd15ac98f8eabbf4ed72af5cac31f0d3d0a1f18c86c5046
|
oid sha256:20530d92efe5c6c52daaa99342923ad7310f801515677899f77d1f5bf1de41f2
|
||||||
size 45221
|
size 42597
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue