diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModePresenter.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModePresenter.kt index e278e803c4..eb3c1330b3 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModePresenter.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModePresenter.kt @@ -26,7 +26,7 @@ class ChooseSelfVerificationModePresenter( ) : Presenter { @Composable override fun present(): ChooseSelfVerificationModeState { - val isLastDevice by encryptionService.isLastDevice.collectAsState() + val hasDevicesToVerifyAgainst by encryptionService.hasDevicesToVerifyAgainst.collectAsState() val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState() val canEnterRecoveryKey by remember { derivedStateOf { recoveryState == RecoveryState.INCOMPLETE } } @@ -39,7 +39,7 @@ class ChooseSelfVerificationModePresenter( } return ChooseSelfVerificationModeState( - isLastDevice = isLastDevice, + canUseAnotherDevice = hasDevicesToVerifyAgainst, canEnterRecoveryKey = canEnterRecoveryKey, directLogoutState = directLogoutState, eventSink = ::eventHandler, diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeState.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeState.kt index 21c37a4ae2..117768a6d2 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeState.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeState.kt @@ -10,7 +10,7 @@ package io.element.android.features.ftue.impl.sessionverification.choosemode import io.element.android.features.logout.api.direct.DirectLogoutState data class ChooseSelfVerificationModeState( - val isLastDevice: Boolean, + val canUseAnotherDevice: Boolean, val canEnterRecoveryKey: Boolean, val directLogoutState: DirectLogoutState, val eventSink: (ChooseSelfVerificationModeEvent) -> Unit, diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeStateProvider.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeStateProvider.kt index 574aa367c6..e053728e2c 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeStateProvider.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeStateProvider.kt @@ -13,18 +13,18 @@ import io.element.android.features.logout.api.direct.aDirectLogoutState class ChooseSelfVerificationModeStateProvider : PreviewParameterProvider { override val values = sequenceOf( - aChooseSelfVerificationModeState(isLastDevice = true, canEnterRecoveryKey = true), - aChooseSelfVerificationModeState(isLastDevice = true, canEnterRecoveryKey = false), - aChooseSelfVerificationModeState(isLastDevice = false, canEnterRecoveryKey = true), - aChooseSelfVerificationModeState(isLastDevice = false, canEnterRecoveryKey = false), + aChooseSelfVerificationModeState(canUseAnotherDevice = false, canEnterRecoveryKey = true), + aChooseSelfVerificationModeState(canUseAnotherDevice = false, canEnterRecoveryKey = false), + aChooseSelfVerificationModeState(canUseAnotherDevice = true, canEnterRecoveryKey = true), + aChooseSelfVerificationModeState(canUseAnotherDevice = true, canEnterRecoveryKey = false), ) } fun aChooseSelfVerificationModeState( - isLastDevice: Boolean = false, + canUseAnotherDevice: Boolean = true, canEnterRecoveryKey: Boolean = true, ) = ChooseSelfVerificationModeState( - isLastDevice = isLastDevice, + canUseAnotherDevice = canUseAnotherDevice, canEnterRecoveryKey = canEnterRecoveryKey, directLogoutState = aDirectLogoutState(), eventSink = {}, diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt index 74eed0115c..b07c04ac9c 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt @@ -76,7 +76,7 @@ fun ChooseSelfVerificationModeView( ButtonColumnMolecule( modifier = Modifier.padding(bottom = 16.dp) ) { - if (state.isLastDevice.not()) { + if (state.canUseAnotherDevice) { Button( modifier = Modifier.fillMaxWidth(), text = stringResource(R.string.screen_identity_use_another_device), diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModePresenterTest.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModePresenterTest.kt index 3801001ed4..3dbf5a6932 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModePresenterTest.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModePresenterTest.kt @@ -24,15 +24,15 @@ class ChooseSessionVerificationModePresenterTest { @Test fun `initial state - is relayed from EncryptionService`() = runTest { val encryptionService = FakeEncryptionService().apply { - // Is last device - emitIsLastDevice(true) + // Has device to verify against + emitHasDevicesToVerifyAgainst(false) // Can enter recovery key emitRecoveryState(RecoveryState.INCOMPLETE) } val presenter = createPresenter(encryptionService = encryptionService) presenter.test { awaitItem().run { - assertThat(isLastDevice).isTrue() + assertThat(canUseAnotherDevice).isFalse() assertThat(canEnterRecoveryKey).isTrue() assertThat(directLogoutState.logoutAction.isUninitialized()).isTrue() } diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt index b89e3e42bd..ed7d99dd19 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt @@ -43,7 +43,7 @@ class ChooseSessionVerificationModeViewTest { fun `clicking on use another device calls the callback`() { ensureCalledOnce { callback -> rule.setChooseSelfVerificationModeView( - aChooseSelfVerificationModeState(isLastDevice = false), + aChooseSelfVerificationModeState(canUseAnotherDevice = true), onUseAnotherDevice = callback, ) rule.clickOn(R.string.screen_identity_use_another_device) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt index 1635cce375..961178ee3e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt @@ -17,6 +17,7 @@ interface EncryptionService { val recoveryStateStateFlow: StateFlow val enableRecoveryProgressStateFlow: StateFlow val isLastDevice: StateFlow + val hasDevicesToVerifyAgainst: StateFlow suspend fun enableBackups(): Result diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt index 7c87666fe7..977663c883 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt @@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.encryption.IdentityResetHandle import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.sync.SyncState +import io.element.android.libraries.matrix.impl.exception.mapClientException import io.element.android.libraries.matrix.impl.sync.RustSyncService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose @@ -96,6 +97,20 @@ internal class RustEncryptionService( } .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false) + /** + * Check if the user has any devices available to verify against every 5 seconds. + * TODO This is a temporary workaround, when we will have a way to observe + * the sessions, this code will have to be updated. + */ + override val hasDevicesToVerifyAgainst: StateFlow = flow { + while (currentCoroutineContext().isActive) { + val result = hasDevicesToVerifyAgainst().getOrDefault(false) + emit(result) + delay(5_000) + } + } + .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false) + override suspend fun enableBackups(): Result = withContext(dispatchers.io) { runCatchingExceptions { service.enableBackups() @@ -171,6 +186,14 @@ internal class RustEncryptionService( } } + private suspend fun hasDevicesToVerifyAgainst(): Result = withContext(dispatchers.io) { + runCatchingExceptions { + service.hasDevicesToVerifyAgainst() + }.mapFailure { + it.mapClientException() + } + } + override suspend fun resetRecoveryKey(): Result = withContext(dispatchers.io) { runCatchingExceptions { service.resetRecoveryKey() diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiEncryption.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiEncryption.kt index 536288caef..177ab5b251 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiEncryption.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiEncryption.kt @@ -32,6 +32,10 @@ class FakeFfiEncryption : Encryption(NoPointer) { return false } + override suspend fun hasDevicesToVerifyAgainst(): Boolean { + return true + } + override fun backupState(): BackupState { return BackupState.ENABLED } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt index 7226465772..b4199ff8e4 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt @@ -34,6 +34,7 @@ class FakeEncryptionService( override val recoveryStateStateFlow: MutableStateFlow = MutableStateFlow(RecoveryState.UNKNOWN) override val enableRecoveryProgressStateFlow: MutableStateFlow = MutableStateFlow(EnableRecoveryProgress.Starting) override val isLastDevice: MutableStateFlow = MutableStateFlow(false) + override val hasDevicesToVerifyAgainst: MutableStateFlow = MutableStateFlow(true) private var waitForBackupUploadSteadyStateFlow: Flow = flowOf() private var recoverFailure: Exception? = null @@ -83,6 +84,10 @@ class FakeEncryptionService( this.isLastDevice.value = isLastDevice } + fun emitHasDevicesToVerifyAgainst(hasDevicesToVerifyAgainst: Boolean) { + this.hasDevicesToVerifyAgainst.value = hasDevicesToVerifyAgainst + } + override suspend fun resetRecoveryKey(): Result = simulateLongTask { return Result.success(FAKE_RECOVERY_KEY) }