DirectLogoutEvents -> DirectLogoutEvent

This commit is contained in:
Benoit Marty
2026-08-25 10:05:04 +02:00
parent a039fcd655
commit 93efe3348f
10 changed files with 42 additions and 42 deletions
@@ -15,7 +15,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember 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.DirectLogoutEvent
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.AsyncData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
@@ -67,7 +67,7 @@ class ChooseSelfVerificationModePresenter(
fun handleEvent(event: ChooseSelfVerificationModeEvent) { fun handleEvent(event: ChooseSelfVerificationModeEvent) {
when (event) { when (event) {
ChooseSelfVerificationModeEvent.SignOut -> directLogoutState.eventSink(DirectLogoutEvents.Logout(ignoreSdkError = false)) ChooseSelfVerificationModeEvent.SignOut -> directLogoutState.eventSink(DirectLogoutEvent.Logout(ignoreSdkError = false))
} }
} }
@@ -9,7 +9,7 @@
package io.element.android.features.ftue.impl.sessionverification.choosemode package io.element.android.features.ftue.impl.sessionverification.choosemode
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.logout.api.direct.DirectLogoutEvents import io.element.android.features.logout.api.direct.DirectLogoutEvent
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.AsyncData
@@ -112,7 +112,7 @@ class ChooseSessionVerificationModePresenterTest {
@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<DirectLogoutEvent, Unit> {}
val logoutPresenter = Presenter<DirectLogoutState> { val logoutPresenter = Presenter<DirectLogoutState> {
aDirectLogoutState(eventSink = logoutEventRecorder) aDirectLogoutState(eventSink = logoutEventRecorder)
} }
@@ -121,7 +121,7 @@ class ChooseSessionVerificationModePresenterTest {
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(DirectLogoutEvent.Logout(ignoreSdkError = false)))
} }
} }
@@ -8,7 +8,7 @@
package io.element.android.features.logout.api.direct package io.element.android.features.logout.api.direct
sealed interface DirectLogoutEvents { sealed interface DirectLogoutEvent {
data class Logout(val ignoreSdkError: Boolean) : DirectLogoutEvents data class Logout(val ignoreSdkError: Boolean) : DirectLogoutEvent
data object CloseDialogs : DirectLogoutEvents data object CloseDialogs : DirectLogoutEvent
} }
@@ -13,5 +13,5 @@ import io.element.android.libraries.architecture.AsyncAction
data class DirectLogoutState( data class DirectLogoutState(
val canDoDirectSignOut: Boolean, val canDoDirectSignOut: Boolean,
val logoutAction: AsyncAction<Unit>, val logoutAction: AsyncAction<Unit>,
val eventSink: (DirectLogoutEvents) -> Unit, val eventSink: (DirectLogoutEvent) -> Unit,
) )
@@ -25,7 +25,7 @@ open class DirectLogoutStatePreviewParam : PreviewParameterProvider<DirectLogout
fun aDirectLogoutState( fun aDirectLogoutState(
canDoDirectSignOut: Boolean = true, canDoDirectSignOut: Boolean = true,
logoutAction: AsyncAction<Unit> = AsyncAction.Uninitialized, logoutAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
eventSink: (DirectLogoutEvents) -> Unit = {}, eventSink: (DirectLogoutEvent) -> Unit = {},
) = DirectLogoutState( ) = DirectLogoutState(
canDoDirectSignOut = canDoDirectSignOut, canDoDirectSignOut = canDoDirectSignOut,
logoutAction = logoutAction, logoutAction = logoutAction,
@@ -11,7 +11,7 @@ package io.element.android.features.logout.impl.direct
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.ContributesBinding
import io.element.android.features.logout.api.direct.DirectLogoutEvents import io.element.android.features.logout.api.direct.DirectLogoutEvent
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.DirectLogoutStatePreviewParam import io.element.android.features.logout.api.direct.DirectLogoutStatePreviewParam
import io.element.android.features.logout.api.direct.DirectLogoutView import io.element.android.features.logout.api.direct.DirectLogoutView
@@ -28,13 +28,13 @@ class DefaultDirectLogoutView : DirectLogoutView {
LogoutActionDialog( LogoutActionDialog(
state.logoutAction, state.logoutAction,
onConfirmClick = { onConfirmClick = {
eventSink(DirectLogoutEvents.Logout(ignoreSdkError = false)) eventSink(DirectLogoutEvent.Logout(ignoreSdkError = false))
}, },
onForceLogoutClick = { onForceLogoutClick = {
eventSink(DirectLogoutEvents.Logout(ignoreSdkError = true)) eventSink(DirectLogoutEvent.Logout(ignoreSdkError = true))
}, },
onDismissDialog = { onDismissDialog = {
eventSink(DirectLogoutEvents.CloseDialogs) eventSink(DirectLogoutEvent.CloseDialogs)
}, },
) )
} }
@@ -16,7 +16,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
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.DirectLogoutEvent
import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.features.logout.api.direct.DirectLogoutState
import io.element.android.features.logout.impl.tools.isBackingUp import io.element.android.features.logout.impl.tools.isBackingUp
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
@@ -48,16 +48,16 @@ class DirectLogoutPresenter(
val isLastDevice by encryptionService.isLastDevice.collectAsState() val isLastDevice by encryptionService.isLastDevice.collectAsState()
fun handleEvent(event: DirectLogoutEvents) { fun handleEvent(event: DirectLogoutEvent) {
when (event) { when (event) {
is DirectLogoutEvents.Logout -> { is DirectLogoutEvent.Logout -> {
if (logoutAction.value.isConfirming() || event.ignoreSdkError) { if (logoutAction.value.isConfirming() || event.ignoreSdkError) {
localCoroutineScope.logout(logoutAction, event.ignoreSdkError) localCoroutineScope.logout(logoutAction, event.ignoreSdkError)
} else { } else {
logoutAction.value = AsyncAction.ConfirmingNoParams logoutAction.value = AsyncAction.ConfirmingNoParams
} }
} }
DirectLogoutEvents.CloseDialogs -> { DirectLogoutEvent.CloseDialogs -> {
logoutAction.value = AsyncAction.Uninitialized logoutAction.value = AsyncAction.Uninitialized
} }
} }
@@ -14,7 +14,7 @@ import androidx.activity.ComponentActivity
import androidx.compose.ui.test.AndroidComposeUiTest import androidx.compose.ui.test.AndroidComposeUiTest
import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import io.element.android.features.logout.api.direct.DirectLogoutEvents import io.element.android.features.logout.api.direct.DirectLogoutEvent
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.AsyncAction import io.element.android.libraries.architecture.AsyncAction
@@ -29,7 +29,7 @@ import org.junit.Test
class DefaultDirectLogoutViewTest : RobolectricTest() { class DefaultDirectLogoutViewTest : RobolectricTest() {
@Test @Test
fun `clicking on confirm logout sends expected Event`() = runAndroidComposeUiTest { fun `clicking on confirm logout sends expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<DirectLogoutEvents>() val eventsRecorder = EventsRecorder<DirectLogoutEvent>()
setDefaultDirectLogoutView( setDefaultDirectLogoutView(
state = aDirectLogoutState( state = aDirectLogoutState(
logoutAction = AsyncAction.ConfirmingNoParams, logoutAction = AsyncAction.ConfirmingNoParams,
@@ -37,12 +37,12 @@ class DefaultDirectLogoutViewTest : RobolectricTest() {
) )
) )
clickOn(CommonStrings.action_signout) clickOn(CommonStrings.action_signout)
eventsRecorder.assertSingle(DirectLogoutEvents.Logout(false)) eventsRecorder.assertSingle(DirectLogoutEvent.Logout(false))
} }
@Test @Test
fun `clicking on cancel logout sends expected Event`() = runAndroidComposeUiTest { fun `clicking on cancel logout sends expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<DirectLogoutEvents>() val eventsRecorder = EventsRecorder<DirectLogoutEvent>()
setDefaultDirectLogoutView( setDefaultDirectLogoutView(
state = aDirectLogoutState( state = aDirectLogoutState(
logoutAction = AsyncAction.ConfirmingNoParams, logoutAction = AsyncAction.ConfirmingNoParams,
@@ -50,13 +50,13 @@ class DefaultDirectLogoutViewTest : RobolectricTest() {
) )
) )
clickOn(CommonStrings.action_cancel) clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs) eventsRecorder.assertSingle(DirectLogoutEvent.CloseDialogs)
} }
@Ignore("Pressing back key should dismiss the dialog, and so generate the expected event, but it's not the case.") @Ignore("Pressing back key should dismiss the dialog, and so generate the expected event, but it's not the case.")
@Test @Test
fun `clicking on back invoke back callback`() = runAndroidComposeUiTest { fun `clicking on back invoke back callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<DirectLogoutEvents>() val eventsRecorder = EventsRecorder<DirectLogoutEvent>()
setDefaultDirectLogoutView( setDefaultDirectLogoutView(
state = aDirectLogoutState( state = aDirectLogoutState(
logoutAction = AsyncAction.ConfirmingNoParams, logoutAction = AsyncAction.ConfirmingNoParams,
@@ -64,12 +64,12 @@ class DefaultDirectLogoutViewTest : RobolectricTest() {
) )
) )
pressBackKey() pressBackKey()
eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs) eventsRecorder.assertSingle(DirectLogoutEvent.CloseDialogs)
} }
@Test @Test
fun `clicking on confirm after error sends expected Event`() = runAndroidComposeUiTest { fun `clicking on confirm after error sends expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<DirectLogoutEvents>() val eventsRecorder = EventsRecorder<DirectLogoutEvent>()
setDefaultDirectLogoutView( setDefaultDirectLogoutView(
state = aDirectLogoutState( state = aDirectLogoutState(
logoutAction = AsyncAction.Failure(Exception("Error")), logoutAction = AsyncAction.Failure(Exception("Error")),
@@ -77,12 +77,12 @@ class DefaultDirectLogoutViewTest : RobolectricTest() {
) )
) )
clickOn(CommonStrings.action_signout_anyway) clickOn(CommonStrings.action_signout_anyway)
eventsRecorder.assertSingle(DirectLogoutEvents.Logout(true)) eventsRecorder.assertSingle(DirectLogoutEvent.Logout(true))
} }
@Test @Test
fun `clicking on cancel after error sends expected Event`() = runAndroidComposeUiTest { fun `clicking on cancel after error sends expected Event`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<DirectLogoutEvents>() val eventsRecorder = EventsRecorder<DirectLogoutEvent>()
setDefaultDirectLogoutView( setDefaultDirectLogoutView(
state = aDirectLogoutState( state = aDirectLogoutState(
logoutAction = AsyncAction.Failure(Exception("Error")), logoutAction = AsyncAction.Failure(Exception("Error")),
@@ -90,7 +90,7 @@ class DefaultDirectLogoutViewTest : RobolectricTest() {
) )
) )
clickOn(CommonStrings.action_cancel) clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs) eventsRecorder.assertSingle(DirectLogoutEvent.CloseDialogs)
} }
} }
@@ -13,7 +13,7 @@ import app.cash.molecule.moleculeFlow
import app.cash.turbine.ReceiveTurbine import app.cash.turbine.ReceiveTurbine
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.logout.api.direct.DirectLogoutEvents import io.element.android.features.logout.api.direct.DirectLogoutEvent
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.encryption.BackupUploadState import io.element.android.libraries.matrix.api.encryption.BackupUploadState
@@ -87,10 +87,10 @@ class DirectLogoutPresenterTest {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) initialState.eventSink.invoke(DirectLogoutEvent.Logout(ignoreSdkError = false))
val confirmationState = awaitItem() val confirmationState = awaitItem()
assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.ConfirmingNoParams) assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.ConfirmingNoParams)
initialState.eventSink.invoke(DirectLogoutEvents.CloseDialogs) initialState.eventSink.invoke(DirectLogoutEvent.CloseDialogs)
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.logoutAction).isEqualTo(AsyncAction.Uninitialized) assertThat(finalState.logoutAction).isEqualTo(AsyncAction.Uninitialized)
} }
@@ -103,10 +103,10 @@ class DirectLogoutPresenterTest {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) initialState.eventSink.invoke(DirectLogoutEvent.Logout(ignoreSdkError = false))
val confirmationState = awaitItem() val confirmationState = awaitItem()
assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.ConfirmingNoParams) assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.ConfirmingNoParams)
confirmationState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) confirmationState.eventSink.invoke(DirectLogoutEvent.Logout(ignoreSdkError = false))
val loadingState = awaitItem() val loadingState = awaitItem()
assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java)
val successState = awaitItem() val successState = awaitItem()
@@ -128,15 +128,15 @@ class DirectLogoutPresenterTest {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) initialState.eventSink.invoke(DirectLogoutEvent.Logout(ignoreSdkError = false))
val confirmationState = awaitItem() val confirmationState = awaitItem()
assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.ConfirmingNoParams) assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.ConfirmingNoParams)
confirmationState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) confirmationState.eventSink.invoke(DirectLogoutEvent.Logout(ignoreSdkError = false))
val loadingState = awaitItem() val loadingState = awaitItem()
assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java)
val errorState = awaitItem() val errorState = awaitItem()
assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION))
errorState.eventSink.invoke(DirectLogoutEvents.CloseDialogs) errorState.eventSink.invoke(DirectLogoutEvent.CloseDialogs)
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.logoutAction).isEqualTo(AsyncAction.Uninitialized) assertThat(finalState.logoutAction).isEqualTo(AsyncAction.Uninitialized)
} }
@@ -158,15 +158,15 @@ class DirectLogoutPresenterTest {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) initialState.eventSink.invoke(DirectLogoutEvent.Logout(ignoreSdkError = false))
val confirmationState = awaitItem() val confirmationState = awaitItem()
assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.ConfirmingNoParams) assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.ConfirmingNoParams)
confirmationState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) confirmationState.eventSink.invoke(DirectLogoutEvent.Logout(ignoreSdkError = false))
val loadingState = awaitItem() val loadingState = awaitItem()
assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java)
val errorState = awaitItem() val errorState = awaitItem()
assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION))
errorState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = true)) errorState.eventSink.invoke(DirectLogoutEvent.Logout(ignoreSdkError = true))
val loadingState2 = awaitItem() val loadingState2 = awaitItem()
assertThat(loadingState2.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) assertThat(loadingState2.logoutAction).isInstanceOf(AsyncAction.Loading::class.java)
val successState = awaitItem() val successState = awaitItem()
@@ -19,7 +19,7 @@ import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode import io.element.android.annotations.ContributesNode
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.features.logout.api.direct.DirectLogoutEvents import io.element.android.features.logout.api.direct.DirectLogoutEvent
import io.element.android.features.logout.api.direct.DirectLogoutView import io.element.android.features.logout.api.direct.DirectLogoutView
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.callback
@@ -96,7 +96,7 @@ class PreferencesRootNode(
onOpenBlockedUsers = callback::navigateToBlockedUsers, onOpenBlockedUsers = callback::navigateToBlockedUsers,
onSignOutClick = { onSignOutClick = {
if (state.directLogoutState.canDoDirectSignOut) { if (state.directLogoutState.canDoDirectSignOut) {
state.directLogoutState.eventSink(DirectLogoutEvents.Logout(ignoreSdkError = false)) state.directLogoutState.eventSink(DirectLogoutEvent.Logout(ignoreSdkError = false))
} else { } else {
callback.startSignOutFlow() callback.startSignOutFlow()
} }