Only offer to verify if a cross-signed device is available (#5433)
* Only offer to verify if a cross-signed device is available * Fix tests * use the right exception mapper * adjust flag name and logic in ChooseSelfVerificationState * add comment * switch order of states to match previous logic
This commit is contained in:
parent
a9912e4a1e
commit
7c61c70b62
10 changed files with 47 additions and 14 deletions
|
|
@ -26,7 +26,7 @@ class ChooseSelfVerificationModePresenter(
|
||||||
) : Presenter<ChooseSelfVerificationModeState> {
|
) : Presenter<ChooseSelfVerificationModeState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): ChooseSelfVerificationModeState {
|
override fun present(): ChooseSelfVerificationModeState {
|
||||||
val isLastDevice by encryptionService.isLastDevice.collectAsState()
|
val hasDevicesToVerifyAgainst by encryptionService.hasDevicesToVerifyAgainst.collectAsState()
|
||||||
val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState()
|
val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState()
|
||||||
val canEnterRecoveryKey by remember { derivedStateOf { recoveryState == RecoveryState.INCOMPLETE } }
|
val canEnterRecoveryKey by remember { derivedStateOf { recoveryState == RecoveryState.INCOMPLETE } }
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ class ChooseSelfVerificationModePresenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
return ChooseSelfVerificationModeState(
|
return ChooseSelfVerificationModeState(
|
||||||
isLastDevice = isLastDevice,
|
canUseAnotherDevice = hasDevicesToVerifyAgainst,
|
||||||
canEnterRecoveryKey = canEnterRecoveryKey,
|
canEnterRecoveryKey = canEnterRecoveryKey,
|
||||||
directLogoutState = directLogoutState,
|
directLogoutState = directLogoutState,
|
||||||
eventSink = ::eventHandler,
|
eventSink = ::eventHandler,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ package io.element.android.features.ftue.impl.sessionverification.choosemode
|
||||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||||
|
|
||||||
data class ChooseSelfVerificationModeState(
|
data class ChooseSelfVerificationModeState(
|
||||||
val isLastDevice: Boolean,
|
val canUseAnotherDevice: Boolean,
|
||||||
val canEnterRecoveryKey: Boolean,
|
val canEnterRecoveryKey: Boolean,
|
||||||
val directLogoutState: DirectLogoutState,
|
val directLogoutState: DirectLogoutState,
|
||||||
val eventSink: (ChooseSelfVerificationModeEvent) -> Unit,
|
val eventSink: (ChooseSelfVerificationModeEvent) -> Unit,
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,18 @@ import io.element.android.features.logout.api.direct.aDirectLogoutState
|
||||||
class ChooseSelfVerificationModeStateProvider :
|
class ChooseSelfVerificationModeStateProvider :
|
||||||
PreviewParameterProvider<ChooseSelfVerificationModeState> {
|
PreviewParameterProvider<ChooseSelfVerificationModeState> {
|
||||||
override val values = sequenceOf(
|
override val values = sequenceOf(
|
||||||
aChooseSelfVerificationModeState(isLastDevice = true, canEnterRecoveryKey = true),
|
aChooseSelfVerificationModeState(canUseAnotherDevice = false, canEnterRecoveryKey = true),
|
||||||
aChooseSelfVerificationModeState(isLastDevice = true, canEnterRecoveryKey = false),
|
aChooseSelfVerificationModeState(canUseAnotherDevice = false, canEnterRecoveryKey = false),
|
||||||
aChooseSelfVerificationModeState(isLastDevice = false, canEnterRecoveryKey = true),
|
aChooseSelfVerificationModeState(canUseAnotherDevice = true, canEnterRecoveryKey = true),
|
||||||
aChooseSelfVerificationModeState(isLastDevice = false, canEnterRecoveryKey = false),
|
aChooseSelfVerificationModeState(canUseAnotherDevice = true, canEnterRecoveryKey = false),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aChooseSelfVerificationModeState(
|
fun aChooseSelfVerificationModeState(
|
||||||
isLastDevice: Boolean = false,
|
canUseAnotherDevice: Boolean = true,
|
||||||
canEnterRecoveryKey: Boolean = true,
|
canEnterRecoveryKey: Boolean = true,
|
||||||
) = ChooseSelfVerificationModeState(
|
) = ChooseSelfVerificationModeState(
|
||||||
isLastDevice = isLastDevice,
|
canUseAnotherDevice = canUseAnotherDevice,
|
||||||
canEnterRecoveryKey = canEnterRecoveryKey,
|
canEnterRecoveryKey = canEnterRecoveryKey,
|
||||||
directLogoutState = aDirectLogoutState(),
|
directLogoutState = aDirectLogoutState(),
|
||||||
eventSink = {},
|
eventSink = {},
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ fun ChooseSelfVerificationModeView(
|
||||||
ButtonColumnMolecule(
|
ButtonColumnMolecule(
|
||||||
modifier = Modifier.padding(bottom = 16.dp)
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
) {
|
) {
|
||||||
if (state.isLastDevice.not()) {
|
if (state.canUseAnotherDevice) {
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(R.string.screen_identity_use_another_device),
|
text = stringResource(R.string.screen_identity_use_another_device),
|
||||||
|
|
|
||||||
|
|
@ -24,15 +24,15 @@ class ChooseSessionVerificationModePresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `initial state - is relayed from EncryptionService`() = runTest {
|
fun `initial state - is relayed from EncryptionService`() = runTest {
|
||||||
val encryptionService = FakeEncryptionService().apply {
|
val encryptionService = FakeEncryptionService().apply {
|
||||||
// Is last device
|
// Has device to verify against
|
||||||
emitIsLastDevice(true)
|
emitHasDevicesToVerifyAgainst(false)
|
||||||
// Can enter recovery key
|
// Can enter recovery key
|
||||||
emitRecoveryState(RecoveryState.INCOMPLETE)
|
emitRecoveryState(RecoveryState.INCOMPLETE)
|
||||||
}
|
}
|
||||||
val presenter = createPresenter(encryptionService = encryptionService)
|
val presenter = createPresenter(encryptionService = encryptionService)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
awaitItem().run {
|
awaitItem().run {
|
||||||
assertThat(isLastDevice).isTrue()
|
assertThat(canUseAnotherDevice).isFalse()
|
||||||
assertThat(canEnterRecoveryKey).isTrue()
|
assertThat(canEnterRecoveryKey).isTrue()
|
||||||
assertThat(directLogoutState.logoutAction.isUninitialized()).isTrue()
|
assertThat(directLogoutState.logoutAction.isUninitialized()).isTrue()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class ChooseSessionVerificationModeViewTest {
|
||||||
fun `clicking on use another device calls the callback`() {
|
fun `clicking on use another device calls the callback`() {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setChooseSelfVerificationModeView(
|
rule.setChooseSelfVerificationModeView(
|
||||||
aChooseSelfVerificationModeState(isLastDevice = false),
|
aChooseSelfVerificationModeState(canUseAnotherDevice = true),
|
||||||
onUseAnotherDevice = callback,
|
onUseAnotherDevice = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_identity_use_another_device)
|
rule.clickOn(R.string.screen_identity_use_another_device)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ interface EncryptionService {
|
||||||
val recoveryStateStateFlow: StateFlow<RecoveryState>
|
val recoveryStateStateFlow: StateFlow<RecoveryState>
|
||||||
val enableRecoveryProgressStateFlow: StateFlow<EnableRecoveryProgress>
|
val enableRecoveryProgressStateFlow: StateFlow<EnableRecoveryProgress>
|
||||||
val isLastDevice: StateFlow<Boolean>
|
val isLastDevice: StateFlow<Boolean>
|
||||||
|
val hasDevicesToVerifyAgainst: StateFlow<Boolean>
|
||||||
|
|
||||||
suspend fun enableBackups(): Result<Unit>
|
suspend fun enableBackups(): Result<Unit>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.RecoveryState
|
||||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
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.api.sync.SyncState
|
||||||
|
import io.element.android.libraries.matrix.impl.exception.mapClientException
|
||||||
import io.element.android.libraries.matrix.impl.sync.RustSyncService
|
import io.element.android.libraries.matrix.impl.sync.RustSyncService
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
|
@ -96,6 +97,20 @@ internal class RustEncryptionService(
|
||||||
}
|
}
|
||||||
.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false)
|
.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<Boolean> = flow {
|
||||||
|
while (currentCoroutineContext().isActive) {
|
||||||
|
val result = hasDevicesToVerifyAgainst().getOrDefault(false)
|
||||||
|
emit(result)
|
||||||
|
delay(5_000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false)
|
||||||
|
|
||||||
override suspend fun enableBackups(): Result<Unit> = withContext(dispatchers.io) {
|
override suspend fun enableBackups(): Result<Unit> = withContext(dispatchers.io) {
|
||||||
runCatchingExceptions {
|
runCatchingExceptions {
|
||||||
service.enableBackups()
|
service.enableBackups()
|
||||||
|
|
@ -171,6 +186,14 @@ internal class RustEncryptionService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun hasDevicesToVerifyAgainst(): Result<Boolean> = withContext(dispatchers.io) {
|
||||||
|
runCatchingExceptions {
|
||||||
|
service.hasDevicesToVerifyAgainst()
|
||||||
|
}.mapFailure {
|
||||||
|
it.mapClientException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun resetRecoveryKey(): Result<String> = withContext(dispatchers.io) {
|
override suspend fun resetRecoveryKey(): Result<String> = withContext(dispatchers.io) {
|
||||||
runCatchingExceptions {
|
runCatchingExceptions {
|
||||||
service.resetRecoveryKey()
|
service.resetRecoveryKey()
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,10 @@ class FakeFfiEncryption : Encryption(NoPointer) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun hasDevicesToVerifyAgainst(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
override fun backupState(): BackupState {
|
override fun backupState(): BackupState {
|
||||||
return BackupState.ENABLED
|
return BackupState.ENABLED
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ class FakeEncryptionService(
|
||||||
override val recoveryStateStateFlow: MutableStateFlow<RecoveryState> = MutableStateFlow(RecoveryState.UNKNOWN)
|
override val recoveryStateStateFlow: MutableStateFlow<RecoveryState> = MutableStateFlow(RecoveryState.UNKNOWN)
|
||||||
override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Starting)
|
override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Starting)
|
||||||
override val isLastDevice: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
override val isLastDevice: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
override val hasDevicesToVerifyAgainst: MutableStateFlow<Boolean> = MutableStateFlow(true)
|
||||||
private var waitForBackupUploadSteadyStateFlow: Flow<BackupUploadState> = flowOf()
|
private var waitForBackupUploadSteadyStateFlow: Flow<BackupUploadState> = flowOf()
|
||||||
|
|
||||||
private var recoverFailure: Exception? = null
|
private var recoverFailure: Exception? = null
|
||||||
|
|
@ -83,6 +84,10 @@ class FakeEncryptionService(
|
||||||
this.isLastDevice.value = isLastDevice
|
this.isLastDevice.value = isLastDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun emitHasDevicesToVerifyAgainst(hasDevicesToVerifyAgainst: Boolean) {
|
||||||
|
this.hasDevicesToVerifyAgainst.value = hasDevicesToVerifyAgainst
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun resetRecoveryKey(): Result<String> = simulateLongTask {
|
override suspend fun resetRecoveryKey(): Result<String> = simulateLongTask {
|
||||||
return Result.success(FAKE_RECOVERY_KEY)
|
return Result.success(FAKE_RECOVERY_KEY)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue