Make sure we know the session verification state before showing the option to verify the session. #5521
This commit is contained in:
parent
32b1856dbd
commit
ff67c8beef
9 changed files with 228 additions and 60 deletions
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue