Make sure we know the session verification state before showing the option to verify the session. #5521

This commit is contained in:
Benoit Marty 2025-11-04 12:11:11 +01:00
parent 32b1856dbd
commit ff67c8beef
9 changed files with 228 additions and 60 deletions

View file

@ -15,7 +15,9 @@ import androidx.compose.runtime.remember
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import io.element.android.features.logout.api.direct.DirectLogoutEvents import io.element.android.features.logout.api.direct.DirectLogoutEvents
import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.features.logout.api.direct.DirectLogoutState
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.mapState
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
@ -27,8 +29,33 @@ class ChooseSelfVerificationModePresenter(
@Composable @Composable
override fun present(): ChooseSelfVerificationModeState { override fun present(): ChooseSelfVerificationModeState {
val hasDevicesToVerifyAgainst by encryptionService.hasDevicesToVerifyAgainst.collectAsState() val hasDevicesToVerifyAgainst by encryptionService.hasDevicesToVerifyAgainst.collectAsState()
val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState() val canEnterRecoveryKey by encryptionService.recoveryStateStateFlow
val canEnterRecoveryKey by remember { derivedStateOf { recoveryState == RecoveryState.INCOMPLETE } } .mapState { recoveryState ->
when (recoveryState) {
RecoveryState.WAITING_FOR_SYNC,
RecoveryState.UNKNOWN -> AsyncData.Loading()
RecoveryState.INCOMPLETE -> AsyncData.Success(true)
RecoveryState.ENABLED,
RecoveryState.DISABLED -> AsyncData.Success(false)
}
}
.collectAsState()
val buttonsState by remember {
derivedStateOf {
val canUseAnotherDevice = hasDevicesToVerifyAgainst.dataOrNull()
val canEnterRecoveryKey = canEnterRecoveryKey.dataOrNull()
if (canUseAnotherDevice == null || canEnterRecoveryKey == null) {
AsyncData.Loading()
} else {
AsyncData.Success(
ChooseSelfVerificationModeState.ButtonsState(
canUseAnotherDevice = canUseAnotherDevice,
canEnterRecoveryKey = canEnterRecoveryKey,
)
)
}
}
}
val directLogoutState = directLogoutPresenter.present() val directLogoutState = directLogoutPresenter.present()
@ -39,8 +66,7 @@ class ChooseSelfVerificationModePresenter(
} }
return ChooseSelfVerificationModeState( return ChooseSelfVerificationModeState(
canUseAnotherDevice = hasDevicesToVerifyAgainst, buttonsState = buttonsState,
canEnterRecoveryKey = canEnterRecoveryKey,
directLogoutState = directLogoutState, directLogoutState = directLogoutState,
eventSink = ::eventHandler, eventSink = ::eventHandler,
) )

View file

@ -8,10 +8,15 @@
package io.element.android.features.ftue.impl.sessionverification.choosemode 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
import io.element.android.libraries.architecture.AsyncData
data class ChooseSelfVerificationModeState( data class ChooseSelfVerificationModeState(
val canUseAnotherDevice: Boolean, val buttonsState: AsyncData<ButtonsState>,
val canEnterRecoveryKey: Boolean,
val directLogoutState: DirectLogoutState, val directLogoutState: DirectLogoutState,
val eventSink: (ChooseSelfVerificationModeEvent) -> Unit, val eventSink: (ChooseSelfVerificationModeEvent) -> Unit,
) ) {
data class ButtonsState(
val canUseAnotherDevice: Boolean,
val canEnterRecoveryKey: Boolean,
)
}

View file

@ -9,23 +9,49 @@ package io.element.android.features.ftue.impl.sessionverification.choosemode
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.logout.api.direct.aDirectLogoutState import io.element.android.features.logout.api.direct.aDirectLogoutState
import io.element.android.libraries.architecture.AsyncData
class ChooseSelfVerificationModeStateProvider : class ChooseSelfVerificationModeStateProvider :
PreviewParameterProvider<ChooseSelfVerificationModeState> { PreviewParameterProvider<ChooseSelfVerificationModeState> {
override val values = sequenceOf( override val values = sequenceOf(
aChooseSelfVerificationModeState(canUseAnotherDevice = false, canEnterRecoveryKey = true), aChooseSelfVerificationModeState(
aChooseSelfVerificationModeState(canUseAnotherDevice = false, canEnterRecoveryKey = false), buttonsState = AsyncData.Success(
aChooseSelfVerificationModeState(canUseAnotherDevice = true, canEnterRecoveryKey = true), aButtonsState(canUseAnotherDevice = false, canEnterRecoveryKey = true),
aChooseSelfVerificationModeState(canUseAnotherDevice = true, canEnterRecoveryKey = false), ),
),
aChooseSelfVerificationModeState(
buttonsState = AsyncData.Success(
aButtonsState(canUseAnotherDevice = false, canEnterRecoveryKey = false),
),
),
aChooseSelfVerificationModeState(
buttonsState = AsyncData.Success(
aButtonsState(canUseAnotherDevice = true, canEnterRecoveryKey = true),
),
),
aChooseSelfVerificationModeState(
buttonsState = AsyncData.Success(
aButtonsState(canUseAnotherDevice = true, canEnterRecoveryKey = false),
),
),
aChooseSelfVerificationModeState(
buttonsState = AsyncData.Loading(),
),
) )
} }
fun aChooseSelfVerificationModeState( fun aChooseSelfVerificationModeState(
canUseAnotherDevice: Boolean = true, buttonsState: AsyncData<ChooseSelfVerificationModeState.ButtonsState> = AsyncData.Success(aButtonsState()),
canEnterRecoveryKey: Boolean = true,
) = ChooseSelfVerificationModeState( ) = ChooseSelfVerificationModeState(
canUseAnotherDevice = canUseAnotherDevice, buttonsState = buttonsState,
canEnterRecoveryKey = canEnterRecoveryKey,
directLogoutState = aDirectLogoutState(), directLogoutState = aDirectLogoutState(),
eventSink = {}, eventSink = {},
) )
fun aButtonsState(
canUseAnotherDevice: Boolean = true,
canEnterRecoveryKey: Boolean = true,
) = ChooseSelfVerificationModeState.ButtonsState(
canUseAnotherDevice = canUseAnotherDevice,
canEnterRecoveryKey = canEnterRecoveryKey,
)

View file

@ -23,6 +23,7 @@ import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.ftue.impl.R import io.element.android.features.ftue.impl.R
import io.element.android.libraries.architecture.AsyncData
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
@ -50,7 +51,6 @@ fun ChooseSelfVerificationModeView(
BackHandler { BackHandler {
activity?.finish() activity?.finish()
} }
HeaderFooterPage( HeaderFooterPage(
modifier = modifier, modifier = modifier,
topBar = { topBar = {
@ -73,29 +73,12 @@ fun ChooseSelfVerificationModeView(
) )
}, },
footer = { footer = {
ButtonColumnMolecule( ChooseSelfVerificationModeButtons(
modifier = Modifier.padding(bottom = 16.dp) state = state,
) { onUseAnotherDevice = onUseAnotherDevice,
if (state.canUseAnotherDevice) { onUseRecoveryKey = onUseRecoveryKey,
Button( onResetKey = onResetKey,
modifier = Modifier.fillMaxWidth(), )
text = stringResource(R.string.screen_identity_use_another_device),
onClick = onUseAnotherDevice,
)
}
if (state.canEnterRecoveryKey) {
Button(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.screen_session_verification_enter_recovery_key),
onClick = onUseRecoveryKey,
)
}
OutlinedButton(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.screen_identity_confirmation_cannot_confirm),
onClick = onResetKey,
)
}
} }
) { ) {
Row( Row(
@ -113,6 +96,53 @@ fun ChooseSelfVerificationModeView(
} }
} }
@Composable
private fun ChooseSelfVerificationModeButtons(
state: ChooseSelfVerificationModeState,
onUseAnotherDevice: () -> Unit,
onUseRecoveryKey: () -> Unit,
onResetKey: () -> Unit,
) {
ButtonColumnMolecule(
modifier = Modifier.padding(bottom = 16.dp)
) {
when (state.buttonsState) {
AsyncData.Uninitialized,
is AsyncData.Failure,
is AsyncData.Loading -> {
Button(
modifier = Modifier.fillMaxWidth(),
enabled = false,
showProgress = true,
text = stringResource(CommonStrings.common_loading),
onClick = {},
)
}
is AsyncData.Success -> {
if (state.buttonsState.data.canUseAnotherDevice) {
Button(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.screen_identity_use_another_device),
onClick = onUseAnotherDevice,
)
}
if (state.buttonsState.data.canEnterRecoveryKey) {
Button(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.screen_session_verification_enter_recovery_key),
onClick = onUseRecoveryKey,
)
}
OutlinedButton(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.screen_identity_confirmation_cannot_confirm),
onClick = onResetKey,
)
}
}
}
}
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
internal fun ChooseSelfVerificationModeViewPreview( internal fun ChooseSelfVerificationModeViewPreview(

View file

@ -11,6 +11,7 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.features.logout.api.direct.DirectLogoutEvents import io.element.android.features.logout.api.direct.DirectLogoutEvents
import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.features.logout.api.direct.DirectLogoutState
import io.element.android.features.logout.api.direct.aDirectLogoutState import io.element.android.features.logout.api.direct.aDirectLogoutState
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
@ -22,23 +23,92 @@ import org.junit.Test
class ChooseSessionVerificationModePresenterTest { class ChooseSessionVerificationModePresenterTest {
@Test @Test
fun `initial state - is relayed from EncryptionService`() = runTest { fun `present - initial state`() = runTest {
val encryptionService = FakeEncryptionService().apply { val presenter = createPresenter()
// Has device to verify against
emitHasDevicesToVerifyAgainst(false)
// Can enter recovery key
emitRecoveryState(RecoveryState.INCOMPLETE)
}
val presenter = createPresenter(encryptionService = encryptionService)
presenter.test { presenter.test {
awaitItem().run { awaitItem().run {
assertThat(canUseAnotherDevice).isFalse() assertThat(buttonsState.isLoading()).isTrue()
assertThat(canEnterRecoveryKey).isTrue()
assertThat(directLogoutState.logoutAction.isUninitialized()).isTrue() assertThat(directLogoutState.logoutAction.isUninitialized()).isTrue()
} }
} }
} }
@Test
fun `present - state is relayed from EncryptionService, order 1`() = runTest {
val encryptionService = FakeEncryptionService()
val presenter = createPresenter(encryptionService = encryptionService)
presenter.test {
assertThat(awaitItem().buttonsState.isLoading()).isTrue()
// Has device to verify against
encryptionService.emitHasDevicesToVerifyAgainst(AsyncData.Success(false))
// Can enter recovery key
encryptionService.emitRecoveryState(RecoveryState.DISABLED)
assertThat(awaitItem().buttonsState.dataOrNull()).isEqualTo(
ChooseSelfVerificationModeState.ButtonsState(
canUseAnotherDevice = false,
canEnterRecoveryKey = false,
)
)
}
}
@Test
fun `present - state is relayed from EncryptionService, order 2`() = runTest {
val encryptionService = FakeEncryptionService()
val presenter = createPresenter(encryptionService = encryptionService)
presenter.test {
assertThat(awaitItem().buttonsState.isLoading()).isTrue()
// Can enter recovery key
encryptionService.emitRecoveryState(RecoveryState.DISABLED)
// Has device to verify against
encryptionService.emitHasDevicesToVerifyAgainst(AsyncData.Success(false))
assertThat(awaitItem().buttonsState.dataOrNull()).isEqualTo(
ChooseSelfVerificationModeState.ButtonsState(
canUseAnotherDevice = false,
canEnterRecoveryKey = false,
)
)
}
}
@Test
fun `present - can use another device`() = runTest {
val encryptionService = FakeEncryptionService()
val presenter = createPresenter(encryptionService = encryptionService)
presenter.test {
assertThat(awaitItem().buttonsState.isLoading()).isTrue()
// Can enter recovery key
encryptionService.emitRecoveryState(RecoveryState.DISABLED)
// Has device to verify against
encryptionService.emitHasDevicesToVerifyAgainst(AsyncData.Success(true))
assertThat(awaitItem().buttonsState.dataOrNull()).isEqualTo(
ChooseSelfVerificationModeState.ButtonsState(
canUseAnotherDevice = true,
canEnterRecoveryKey = false,
)
)
}
}
@Test
fun `present - can enter recovery key`() = runTest {
val encryptionService = FakeEncryptionService()
val presenter = createPresenter(encryptionService = encryptionService)
presenter.test {
assertThat(awaitItem().buttonsState.isLoading()).isTrue()
// Can enter recovery key
encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE)
// Has device to verify against
encryptionService.emitHasDevicesToVerifyAgainst(AsyncData.Success(false))
assertThat(awaitItem().buttonsState.dataOrNull()).isEqualTo(
ChooseSelfVerificationModeState.ButtonsState(
canUseAnotherDevice = false,
canEnterRecoveryKey = true,
)
)
}
}
@Test @Test
fun `sing out action triggers a direct logout`() = runTest { fun `sing out action triggers a direct logout`() = runTest {
val logoutEventRecorder = lambdaRecorder<DirectLogoutEvents, Unit> {} val logoutEventRecorder = lambdaRecorder<DirectLogoutEvents, Unit> {}
@ -49,8 +119,8 @@ class ChooseSessionVerificationModePresenterTest {
presenter.test { presenter.test {
val initial = awaitItem() val initial = awaitItem()
initial.eventSink(ChooseSelfVerificationModeEvent.SignOut) initial.eventSink(ChooseSelfVerificationModeEvent.SignOut)
logoutEventRecorder.assertions().isCalledOnce()
logoutEventRecorder.assertions().isCalledOnce().with(value(DirectLogoutEvents.Logout(ignoreSdkError = false))) .with(value(DirectLogoutEvents.Logout(ignoreSdkError = false)))
} }
} }

View file

@ -12,6 +12,7 @@ import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.ftue.impl.R import io.element.android.features.ftue.impl.R
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.clickOn
@ -43,7 +44,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(canUseAnotherDevice = true), aChooseSelfVerificationModeState(AsyncData.Success(aButtonsState(canUseAnotherDevice = true))),
onUseAnotherDevice = callback, onUseAnotherDevice = callback,
) )
rule.clickOn(R.string.screen_identity_use_another_device) rule.clickOn(R.string.screen_identity_use_another_device)
@ -55,7 +56,7 @@ class ChooseSessionVerificationModeViewTest {
fun `clicking on enter recovery key calls the callback`() { fun `clicking on enter recovery key calls the callback`() {
ensureCalledOnce { callback -> ensureCalledOnce { callback ->
rule.setChooseSelfVerificationModeView( rule.setChooseSelfVerificationModeView(
aChooseSelfVerificationModeState(canEnterRecoveryKey = true), aChooseSelfVerificationModeState(AsyncData.Success(aButtonsState(canEnterRecoveryKey = true))),
onEnterRecoveryKey = callback, onEnterRecoveryKey = callback,
) )
rule.clickOn(R.string.screen_session_verification_enter_recovery_key) rule.clickOn(R.string.screen_session_verification_enter_recovery_key)

View file

@ -7,6 +7,7 @@
package io.element.android.libraries.matrix.api.encryption package io.element.android.libraries.matrix.api.encryption
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -17,7 +18,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> val hasDevicesToVerifyAgainst: StateFlow<AsyncData<Boolean>>
suspend fun enableBackups(): Result<Unit> suspend fun enableBackups(): Result<Unit>

View file

@ -7,6 +7,7 @@
package io.element.android.libraries.matrix.impl.encryption package io.element.android.libraries.matrix.impl.encryption
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.flatMap import io.element.android.libraries.core.extensions.flatMap
import io.element.android.libraries.core.extensions.mapFailure import io.element.android.libraries.core.extensions.mapFailure
@ -42,6 +43,7 @@ import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.EnableRecoveryProgressListener import org.matrix.rustcomponents.sdk.EnableRecoveryProgressListener
import org.matrix.rustcomponents.sdk.Encryption import org.matrix.rustcomponents.sdk.Encryption
import org.matrix.rustcomponents.sdk.UserIdentity import org.matrix.rustcomponents.sdk.UserIdentity
import timber.log.Timber
import org.matrix.rustcomponents.sdk.BackupUploadState as RustBackupUploadState import org.matrix.rustcomponents.sdk.BackupUploadState as RustBackupUploadState
import org.matrix.rustcomponents.sdk.EnableRecoveryProgress as RustEnableRecoveryProgress import org.matrix.rustcomponents.sdk.EnableRecoveryProgress as RustEnableRecoveryProgress
import org.matrix.rustcomponents.sdk.RecoveryException as RustRecoveryException import org.matrix.rustcomponents.sdk.RecoveryException as RustRecoveryException
@ -103,14 +105,20 @@ class RustEncryptionService(
* TODO This is a temporary workaround, when we will have a way to observe * TODO This is a temporary workaround, when we will have a way to observe
* the sessions, this code will have to be updated. * the sessions, this code will have to be updated.
*/ */
override val hasDevicesToVerifyAgainst: StateFlow<Boolean> = flow { override val hasDevicesToVerifyAgainst: StateFlow<AsyncData<Boolean>> = flow {
while (currentCoroutineContext().isActive) { while (currentCoroutineContext().isActive) {
val result = hasDevicesToVerifyAgainst().getOrDefault(false) val result = hasDevicesToVerifyAgainst()
emit(result) result
.onSuccess {
emit(AsyncData.Success(it))
}
.onFailure {
Timber.e(it, "Failed to get hasDevicesToVerifyAgainst, retrying in 5s...")
}
delay(5_000) delay(5_000)
} }
} }
.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false) .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, AsyncData.Uninitialized)
override suspend fun enableBackups(): Result<Unit> = withContext(dispatchers.io) { override suspend fun enableBackups(): Result<Unit> = withContext(dispatchers.io) {
runCatchingExceptions { runCatchingExceptions {

View file

@ -7,6 +7,7 @@
package io.element.android.libraries.matrix.test.encryption package io.element.android.libraries.matrix.test.encryption
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.BackupState
import io.element.android.libraries.matrix.api.encryption.BackupUploadState import io.element.android.libraries.matrix.api.encryption.BackupUploadState
@ -34,7 +35,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) override val hasDevicesToVerifyAgainst: MutableStateFlow<AsyncData<Boolean>> = MutableStateFlow(AsyncData.Uninitialized)
private var waitForBackupUploadSteadyStateFlow: Flow<BackupUploadState> = flowOf() private var waitForBackupUploadSteadyStateFlow: Flow<BackupUploadState> = flowOf()
private var recoverFailure: Exception? = null private var recoverFailure: Exception? = null
@ -84,7 +85,7 @@ class FakeEncryptionService(
this.isLastDevice.value = isLastDevice this.isLastDevice.value = isLastDevice
} }
fun emitHasDevicesToVerifyAgainst(hasDevicesToVerifyAgainst: Boolean) { fun emitHasDevicesToVerifyAgainst(hasDevicesToVerifyAgainst: AsyncData<Boolean>) {
this.hasDevicesToVerifyAgainst.value = hasDevicesToVerifyAgainst this.hasDevicesToVerifyAgainst.value = hasDevicesToVerifyAgainst
} }