Merge pull request #1832 from vector-im/renovate/org.matrix.rustcomponents-sdk-android-0.x
Update dependency org.matrix.rustcomponents:sdk-android to v0.1.68
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.securebackup.impl.root
|
||||
|
||||
sealed interface SecureBackupRootEvents {
|
||||
data object RetryKeyBackupState : SecureBackupRootEvents
|
||||
}
|
||||
@@ -17,14 +17,24 @@
|
||||
package io.element.android.features.securebackup.impl.root
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import io.element.android.features.securebackup.impl.loggerTagRoot
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -36,6 +46,7 @@ class SecureBackupRootPresenter @Inject constructor(
|
||||
|
||||
@Composable
|
||||
override fun present(): SecureBackupRootState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
|
||||
|
||||
val backupState by encryptionService.backupStateStateFlow.collectAsState()
|
||||
@@ -44,11 +55,33 @@ class SecureBackupRootPresenter @Inject constructor(
|
||||
Timber.tag(loggerTagRoot.value).d("backupState: $backupState")
|
||||
Timber.tag(loggerTagRoot.value).d("recoveryState: $recoveryState")
|
||||
|
||||
val doesBackupExistOnServerAction: MutableState<Async<Boolean>> = remember { mutableStateOf(Async.Uninitialized) }
|
||||
|
||||
LaunchedEffect(backupState) {
|
||||
if (backupState == BackupState.UNKNOWN) {
|
||||
getKeyBackupStatus(doesBackupExistOnServerAction)
|
||||
}
|
||||
}
|
||||
|
||||
fun handleEvents(event: SecureBackupRootEvents) {
|
||||
when (event) {
|
||||
SecureBackupRootEvents.RetryKeyBackupState -> localCoroutineScope.getKeyBackupStatus(doesBackupExistOnServerAction)
|
||||
}
|
||||
}
|
||||
|
||||
return SecureBackupRootState(
|
||||
backupState = backupState,
|
||||
doesBackupExistOnServer = doesBackupExistOnServerAction.value,
|
||||
recoveryState = recoveryState,
|
||||
appName = buildMeta.applicationName,
|
||||
snackbarMessage = snackbarMessage,
|
||||
eventSink = ::handleEvents,
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.getKeyBackupStatus(action: MutableState<Async<Boolean>>) = launch {
|
||||
suspend {
|
||||
encryptionService.doesBackupExistOnServer().getOrThrow()
|
||||
}.runCatchingUpdatingState(action)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,16 @@
|
||||
|
||||
package io.element.android.features.securebackup.impl.root
|
||||
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
|
||||
data class SecureBackupRootState(
|
||||
val backupState: BackupState,
|
||||
val doesBackupExistOnServer: Async<Boolean>,
|
||||
val recoveryState: RecoveryState,
|
||||
val appName: String,
|
||||
val snackbarMessage: SnackbarMessage?,
|
||||
val eventSink: (SecureBackupRootEvents) -> Unit,
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package io.element.android.features.securebackup.impl.root
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
@@ -24,9 +25,11 @@ import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
open class SecureBackupRootStateProvider : PreviewParameterProvider<SecureBackupRootState> {
|
||||
override val values: Sequence<SecureBackupRootState>
|
||||
get() = sequenceOf(
|
||||
aSecureBackupRootState(backupState = BackupState.UNKNOWN),
|
||||
aSecureBackupRootState(backupState = BackupState.UNKNOWN, doesBackupExistOnServer = Async.Uninitialized),
|
||||
aSecureBackupRootState(backupState = BackupState.UNKNOWN, doesBackupExistOnServer = Async.Success(true)),
|
||||
aSecureBackupRootState(backupState = BackupState.UNKNOWN, doesBackupExistOnServer = Async.Success(false)),
|
||||
aSecureBackupRootState(backupState = BackupState.UNKNOWN, doesBackupExistOnServer = Async.Failure(Exception("An error"))),
|
||||
aSecureBackupRootState(backupState = BackupState.ENABLED),
|
||||
aSecureBackupRootState(backupState = BackupState.DISABLED),
|
||||
aSecureBackupRootState(recoveryState = RecoveryState.UNKNOWN),
|
||||
aSecureBackupRootState(recoveryState = RecoveryState.ENABLED),
|
||||
aSecureBackupRootState(recoveryState = RecoveryState.DISABLED),
|
||||
@@ -37,11 +40,14 @@ open class SecureBackupRootStateProvider : PreviewParameterProvider<SecureBackup
|
||||
|
||||
fun aSecureBackupRootState(
|
||||
backupState: BackupState = BackupState.UNKNOWN,
|
||||
doesBackupExistOnServer: Async<Boolean> = Async.Uninitialized,
|
||||
recoveryState: RecoveryState = RecoveryState.UNKNOWN,
|
||||
snackbarMessage: SnackbarMessage? = null,
|
||||
) = SecureBackupRootState(
|
||||
backupState = backupState,
|
||||
doesBackupExistOnServer = doesBackupExistOnServer,
|
||||
recoveryState = recoveryState,
|
||||
appName = "Element",
|
||||
snackbarMessage = snackbarMessage,
|
||||
eventSink = {},
|
||||
)
|
||||
|
||||
@@ -16,18 +16,27 @@
|
||||
|
||||
package io.element.android.features.securebackup.impl.root
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.features.securebackup.impl.R
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncLoading
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceDivider
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
@@ -70,13 +79,58 @@ fun SecureBackupRootView(
|
||||
|
||||
// Disable / Enable backup
|
||||
when (state.backupState) {
|
||||
BackupState.WAITING_FOR_SYNC,
|
||||
BackupState.UNKNOWN -> Unit
|
||||
BackupState.DISABLED -> {
|
||||
PreferenceText(
|
||||
title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable),
|
||||
onClick = onEnableClicked,
|
||||
)
|
||||
BackupState.WAITING_FOR_SYNC -> Unit
|
||||
BackupState.UNKNOWN -> {
|
||||
when (state.doesBackupExistOnServer) {
|
||||
is Async.Success -> when (state.doesBackupExistOnServer.data) {
|
||||
true -> {
|
||||
PreferenceText(
|
||||
title = stringResource(id = R.string.screen_chat_backup_key_backup_action_disable),
|
||||
tintColor = ElementTheme.colors.textCriticalPrimary,
|
||||
onClick = onDisableClicked,
|
||||
)
|
||||
}
|
||||
false -> {
|
||||
PreferenceText(
|
||||
title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable),
|
||||
onClick = onEnableClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
is Async.Loading,
|
||||
Async.Uninitialized -> {
|
||||
ListItem(headlineContent = {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
})
|
||||
}
|
||||
is Async.Failure -> {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = stringResource(id = CommonStrings.error_unknown),
|
||||
)
|
||||
},
|
||||
trailingContent = ListItemContent.Custom {
|
||||
TextButton(
|
||||
text = stringResource(
|
||||
id = CommonStrings.action_retry
|
||||
),
|
||||
onClick = { state.eventSink.invoke(SecureBackupRootEvents.RetryKeyBackupState) }
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
PreferenceText(
|
||||
title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable),
|
||||
onClick = onEnableClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
BackupState.CREATING,
|
||||
BackupState.ENABLING,
|
||||
|
||||
@@ -20,9 +20,12 @@ import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
@@ -40,9 +43,39 @@ class SecureBackupRootPresenterTest {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(2)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.backupState).isEqualTo(BackupState.UNKNOWN)
|
||||
assertThat(initialState.doesBackupExistOnServer.dataOrNull()).isTrue()
|
||||
assertThat(initialState.recoveryState).isEqualTo(RecoveryState.UNKNOWN)
|
||||
assertThat(initialState.appName).isEqualTo("Element")
|
||||
assertThat(initialState.snackbarMessage).isNull()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - Unknown state`() = runTest {
|
||||
val encryptionService = FakeEncryptionService()
|
||||
val presenter = createSecureBackupRootPresenter(
|
||||
encryptionService = encryptionService,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
encryptionService.givenDoesBackupExistOnServerResult(Result.failure(AN_EXCEPTION))
|
||||
assertThat(initialState.backupState).isEqualTo(BackupState.UNKNOWN)
|
||||
assertThat(initialState.doesBackupExistOnServer).isEqualTo(Async.Uninitialized)
|
||||
val loadingState1 = awaitItem()
|
||||
assertThat(loadingState1.doesBackupExistOnServer).isInstanceOf(Async.Loading::class.java)
|
||||
val errorState = awaitItem()
|
||||
assertThat(errorState.doesBackupExistOnServer).isEqualTo(Async.Failure<Boolean>(AN_EXCEPTION))
|
||||
encryptionService.givenDoesBackupExistOnServerResult(Result.success(false))
|
||||
errorState.eventSink.invoke(SecureBackupRootEvents.RetryKeyBackupState)
|
||||
val loadingState2 = awaitItem()
|
||||
assertThat(loadingState2.doesBackupExistOnServer).isInstanceOf(Async.Loading::class.java)
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.doesBackupExistOnServer.dataOrNull()).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ jsoup = "org.jsoup:jsoup:1.16.2"
|
||||
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
|
||||
molecule-runtime = "app.cash.molecule:molecule-runtime:1.3.0"
|
||||
timber = "com.jakewharton.timber:timber:5.0.1"
|
||||
matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.67"
|
||||
matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.68"
|
||||
matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" }
|
||||
matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" }
|
||||
sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
|
||||
|
||||
@@ -31,6 +31,5 @@ enum class BackupState {
|
||||
RESUMING,
|
||||
ENABLED,
|
||||
DOWNLOADING,
|
||||
DISABLING,
|
||||
DISABLED;
|
||||
DISABLING;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ interface EncryptionService {
|
||||
|
||||
suspend fun disableRecovery(): Result<Unit>
|
||||
|
||||
suspend fun doesBackupExistOnServer(): Result<Boolean>
|
||||
|
||||
/**
|
||||
* Note: accept bot recoveryKey and passphrase.
|
||||
*/
|
||||
|
||||
@@ -29,7 +29,6 @@ class BackupStateMapper {
|
||||
RustBackupState.ENABLED -> BackupState.ENABLED
|
||||
RustBackupState.DOWNLOADING -> BackupState.DOWNLOADING
|
||||
RustBackupState.DISABLING -> BackupState.DISABLING
|
||||
RustBackupState.DISABLED -> BackupState.DISABLED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +130,12 @@ internal class RustEncryptionService(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun doesBackupExistOnServer(): Result<Boolean> = withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
service.backupExistsOnServer()
|
||||
}
|
||||
}
|
||||
|
||||
override fun waitForBackupUploadSteadyState(): Flow<BackupUploadState> {
|
||||
return callbackFlow {
|
||||
runCatching {
|
||||
|
||||
@@ -34,6 +34,7 @@ class FakeEncryptionService : EncryptionService {
|
||||
private var waitForBackupUploadSteadyStateFlow: Flow<BackupUploadState> = flowOf()
|
||||
|
||||
private var fixRecoveryIssuesFailure: Exception? = null
|
||||
private var doesBackupExistOnServerResult: Result<Boolean> = Result.success(true)
|
||||
|
||||
override suspend fun enableBackups(): Result<Unit> = simulateLongTask {
|
||||
return Result.success(Unit)
|
||||
@@ -52,6 +53,14 @@ class FakeEncryptionService : EncryptionService {
|
||||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
fun givenDoesBackupExistOnServerResult(result: Result<Boolean>) {
|
||||
doesBackupExistOnServerResult = result
|
||||
}
|
||||
|
||||
override suspend fun doesBackupExistOnServer(): Result<Boolean> = simulateLongTask {
|
||||
return doesBackupExistOnServerResult
|
||||
}
|
||||
|
||||
override suspend fun fixRecoveryIssues(recoveryKey: String): Result<Unit> = simulateLongTask {
|
||||
fixRecoveryIssuesFailure?.let { return Result.failure(it) }
|
||||
return Result.success(Unit)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user