Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update dependency org.matrix.rustcomponents:sdk-android to v0.1.68 #1832

Merged
merged 6 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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()
Expand All @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@
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

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),
Expand All @@ -37,11 +40,14 @@

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 = {},

Check warning on line 52 in features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt

View check run for this annotation

Codecov / codecov/patch

features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt#L52

Added line #L52 was not covered by tests
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -70,13 +79,58 @@

// 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,
)
}
}

Check warning on line 99 in features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt

View check run for this annotation

Codecov / codecov/patch

features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt#L99

Added line #L99 was not covered by tests
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) }
)

Check warning on line 124 in features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt

View check run for this annotation

Codecov / codecov/patch

features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt#L123-L124

Added lines #L123 - L124 were not covered by tests
}
)

PreferenceText(
title = stringResource(id = R.string.screen_chat_backup_key_backup_action_enable),
onClick = onEnableClicked,
)
}
}

Check warning on line 133 in features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt

View check run for this annotation

Codecov / codecov/patch

features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt#L133

Added line #L133 was not covered by tests
}
BackupState.CREATING,
BackupState.ENABLING,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
}
}

Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,5 @@ enum class BackupState {
RESUMING,
ENABLED,
DOWNLOADING,
DISABLING,
DISABLED;
DISABLING;
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ interface EncryptionService {

suspend fun disableRecovery(): Result<Unit>

suspend fun doesBackupExistOnServer(): Result<Boolean>

/**
* Note: accept bot recoveryKey and passphrase.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ class BackupStateMapper {
RustBackupState.ENABLED -> BackupState.ENABLED
RustBackupState.DOWNLOADING -> BackupState.DOWNLOADING
RustBackupState.DISABLING -> BackupState.DISABLING
RustBackupState.DISABLED -> BackupState.DISABLED
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading