Upgrade SDK version to 25.02.26 (#4305)

* Upgrade SDK version to 25.02.26

* Remove OIDC URL result from logout, the SDK no longer provides it

* Handle room creation and destruction in a better way

* Remove `onSuccessLogout`
This commit is contained in:
Jorge Martin Espinosa 2025-02-26 10:04:49 +01:00 committed by GitHub
parent 3c30bec1c2
commit 274d9dc7c1
40 changed files with 46 additions and 230 deletions

View file

@ -9,9 +9,7 @@ package io.element.android.appnav.room.joined
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
@ -56,7 +54,7 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
roomComponentFactory: RoomComponentFactory, roomComponentFactory: RoomComponentFactory,
) : BaseFlowNode<JoinedRoomLoadedFlowNode.NavTarget>( ) : BaseFlowNode<JoinedRoomLoadedFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
initialElement = when (val input = plugins.filterIsInstance(Inputs::class.java).first().initialElement) { initialElement = when (val input = plugins.filterIsInstance<Inputs>().first().initialElement) {
is RoomNavigationTarget.Messages -> NavTarget.Messages(input.focusedEventId) is RoomNavigationTarget.Messages -> NavTarget.Messages(input.focusedEventId)
RoomNavigationTarget.Details -> NavTarget.RoomDetails RoomNavigationTarget.Details -> NavTarget.RoomDetails
RoomNavigationTarget.NotificationSettings -> NavTarget.RoomNotificationSettings RoomNavigationTarget.NotificationSettings -> NavTarget.RoomNotificationSettings
@ -197,16 +195,6 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
// Rely on the View Lifecycle in addition to the Node Lifecycle,
// because this node enters 'onDestroy' before his children, so it can leads to
// using the room in a child node where it's already closed.
DisposableEffect(Unit) {
onDispose {
if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
inputs.room.destroy()
}
}
}
BackstackView() BackstackView()
} }
} }

View file

@ -521,10 +521,9 @@ class LoggedInPresenterTest {
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@Test @Test
fun `present - LogoutAndMigrateToNativeSlidingSync logs out the user`() = runTest { fun `present - LogoutAndMigrateToNativeSlidingSync logs out the user`() = runTest {
val logoutLambda = lambdaRecorder<Boolean, Boolean, String?> { userInitiated, ignoreSdkError -> val logoutLambda = lambdaRecorder<Boolean, Boolean, Unit> { userInitiated, ignoreSdkError ->
assertThat(userInitiated).isTrue() assertThat(userInitiated).isTrue()
assertThat(ignoreSdkError).isTrue() assertThat(ignoreSdkError).isTrue()
null
} }
val matrixClient = FakeMatrixClient().apply { val matrixClient = FakeMatrixClient().apply {
this.logoutLambda = logoutLambda this.logoutLambda = logoutLambda

View file

@ -7,7 +7,6 @@
package io.element.android.features.lockscreen.impl.unlock package io.element.android.features.lockscreen.impl.unlock
import androidx.activity.compose.LocalActivity
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -18,8 +17,6 @@ import com.bumble.appyx.core.plugin.plugins
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.logout.api.util.onSuccessLogout
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
@ContributesNode(SessionScope::class) @ContributesNode(SessionScope::class)
@ -41,8 +38,6 @@ class PinUnlockNode @AssistedInject constructor(
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
val activity = requireNotNull(LocalActivity.current)
val isDark = ElementTheme.isLightTheme.not()
LaunchedEffect(state.isUnlocked) { LaunchedEffect(state.isUnlocked) {
if (state.isUnlocked) { if (state.isUnlocked) {
onUnlock() onUnlock()
@ -53,7 +48,6 @@ class PinUnlockNode @AssistedInject constructor(
// UnlockNode is only used for in-app unlock, so we can safely set isInAppUnlock to true. // UnlockNode is only used for in-app unlock, so we can safely set isInAppUnlock to true.
// It's set to false in PinUnlockActivity. // It's set to false in PinUnlockActivity.
isInAppUnlock = true, isInAppUnlock = true,
onSuccessLogout = { onSuccessLogout(activity, isDark, it) },
modifier = modifier modifier = modifier
) )
} }

View file

@ -53,7 +53,7 @@ class PinUnlockPresenter @Inject constructor(
mutableStateOf(false) mutableStateOf(false)
} }
val signOutAction = remember { val signOutAction = remember {
mutableStateOf<AsyncAction<String?>>(AsyncAction.Uninitialized) mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
} }
var biometricUnlockResult by remember { var biometricUnlockResult by remember {
mutableStateOf<BiometricAuthenticator.AuthenticationResult?>(null) mutableStateOf<BiometricAuthenticator.AuthenticationResult?>(null)
@ -169,7 +169,7 @@ class PinUnlockPresenter @Inject constructor(
} }
} }
private fun CoroutineScope.signOut(signOutAction: MutableState<AsyncAction<String?>>) = launch { private fun CoroutineScope.signOut(signOutAction: MutableState<AsyncAction<Unit>>) = launch {
suspend { suspend {
logoutUseCase.logout(ignoreSdkError = true) logoutUseCase.logout(ignoreSdkError = true)
}.runCatchingUpdatingState(signOutAction) }.runCatchingUpdatingState(signOutAction)

View file

@ -18,7 +18,7 @@ data class PinUnlockState(
val showWrongPinTitle: Boolean, val showWrongPinTitle: Boolean,
val remainingAttempts: AsyncData<Int>, val remainingAttempts: AsyncData<Int>,
val showSignOutPrompt: Boolean, val showSignOutPrompt: Boolean,
val signOutAction: AsyncAction<String?>, val signOutAction: AsyncAction<Unit>,
val showBiometricUnlock: Boolean, val showBiometricUnlock: Boolean,
val isUnlocked: Boolean, val isUnlocked: Boolean,
val biometricUnlockResult: BiometricAuthenticator.AuthenticationResult?, val biometricUnlockResult: BiometricAuthenticator.AuthenticationResult?,

View file

@ -41,7 +41,7 @@ fun aPinUnlockState(
showBiometricUnlock: Boolean = true, showBiometricUnlock: Boolean = true,
biometricUnlockResult: BiometricAuthenticator.AuthenticationResult? = null, biometricUnlockResult: BiometricAuthenticator.AuthenticationResult? = null,
isUnlocked: Boolean = false, isUnlocked: Boolean = false,
signOutAction: AsyncAction<String?> = AsyncAction.Uninitialized, signOutAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
) = PinUnlockState( ) = PinUnlockState(
pinEntry = AsyncData.Success(pinEntry), pinEntry = AsyncData.Success(pinEntry),
showWrongPinTitle = showWrongPinTitle, showWrongPinTitle = showWrongPinTitle,

View file

@ -29,9 +29,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.Lock
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
@ -67,7 +65,6 @@ import io.element.android.libraries.ui.strings.CommonStrings
fun PinUnlockView( fun PinUnlockView(
state: PinUnlockState, state: PinUnlockState,
isInAppUnlock: Boolean, isInAppUnlock: Boolean,
onSuccessLogout: (logoutUrlResult: String?) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
OnLifecycleEvent { _, event -> OnLifecycleEvent { _, event ->
@ -89,12 +86,7 @@ fun PinUnlockView(
AsyncAction.Loading -> { AsyncAction.Loading -> {
ProgressDialog(text = stringResource(id = R.string.screen_signout_in_progress_dialog_content)) ProgressDialog(text = stringResource(id = R.string.screen_signout_in_progress_dialog_content))
} }
is AsyncAction.Success -> { is AsyncAction.Success,
val latestOnSuccessLogout by rememberUpdatedState(onSuccessLogout)
LaunchedEffect(state) {
latestOnSuccessLogout(state.signOutAction.data)
}
}
is AsyncAction.Confirming, is AsyncAction.Confirming,
is AsyncAction.Failure, is AsyncAction.Failure,
AsyncAction.Uninitialized -> Unit AsyncAction.Uninitialized -> Unit
@ -369,7 +361,6 @@ internal fun PinUnlockViewInAppPreview(@PreviewParameter(PinUnlockStateProvider:
PinUnlockView( PinUnlockView(
state = state, state = state,
isInAppUnlock = true, isInAppUnlock = true,
onSuccessLogout = {},
) )
} }
} }
@ -381,7 +372,6 @@ internal fun PinUnlockViewPreview(@PreviewParameter(PinUnlockStateProvider::clas
PinUnlockView( PinUnlockView(
state = state, state = state,
isInAppUnlock = false, isInAppUnlock = false,
onSuccessLogout = {},
) )
} }
} }

View file

@ -15,14 +15,12 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.features.lockscreen.api.LockScreenLockState import io.element.android.features.lockscreen.api.LockScreenLockState
import io.element.android.features.lockscreen.api.LockScreenService import io.element.android.features.lockscreen.api.LockScreenService
import io.element.android.features.lockscreen.impl.unlock.PinUnlockPresenter import io.element.android.features.lockscreen.impl.unlock.PinUnlockPresenter
import io.element.android.features.lockscreen.impl.unlock.PinUnlockView import io.element.android.features.lockscreen.impl.unlock.PinUnlockView
import io.element.android.features.lockscreen.impl.unlock.di.PinUnlockBindings import io.element.android.features.lockscreen.impl.unlock.di.PinUnlockBindings
import io.element.android.features.logout.api.util.onSuccessLogout
import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.designsystem.theme.ElementThemeApp import io.element.android.libraries.designsystem.theme.ElementThemeApp
import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.libraries.preferences.api.store.AppPreferencesStore
@ -51,11 +49,9 @@ class PinUnlockActivity : AppCompatActivity() {
enterpriseService = enterpriseService, enterpriseService = enterpriseService,
) { ) {
val state = presenter.present() val state = presenter.present()
val isDark = ElementTheme.isLightTheme.not()
PinUnlockView( PinUnlockView(
state = state, state = state,
isInAppUnlock = false, isInAppUnlock = false,
onSuccessLogout = { onSuccessLogout(this, isDark, it) },
) )
} }
} }

View file

@ -98,7 +98,7 @@ class PinUnlockPresenterTest {
@Test @Test
fun `present - forgot pin flow`() = runTest { fun `present - forgot pin flow`() = runTest {
val signOutLambda = lambdaRecorder<Boolean, String?> { "" } val signOutLambda = lambdaRecorder<Boolean, Unit> {}
val signOut = FakeLogoutUseCase(signOutLambda) val signOut = FakeLogoutUseCase(signOutLambda)
val presenter = createPinUnlockPresenter(this, logoutUseCase = signOut) val presenter = createPinUnlockPresenter(this, logoutUseCase = signOut)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {

View file

@ -14,10 +14,8 @@ interface LogoutUseCase {
/** /**
* Log out the current user and then perform any needed cleanup tasks. * Log out the current user and then perform any needed cleanup tasks.
* @param ignoreSdkError if true, the SDK error will be ignored and the user will be logged out anyway. * @param ignoreSdkError if true, the SDK error will be ignored and the user will be logged out anyway.
* @return an optional URL. When the URL is there, it should be presented to the user after logout for
* Relying Party (RP) initiated logout on their account page.
*/ */
suspend fun logout(ignoreSdkError: Boolean): String? suspend fun logout(ignoreSdkError: Boolean)
interface Factory { interface Factory {
fun create(sessionId: String): LogoutUseCase fun create(sessionId: String): LogoutUseCase

View file

@ -11,6 +11,6 @@ import io.element.android.libraries.architecture.AsyncAction
data class DirectLogoutState( data class DirectLogoutState(
val canDoDirectSignOut: Boolean, val canDoDirectSignOut: Boolean,
val logoutAction: AsyncAction<String?>, val logoutAction: AsyncAction<Unit>,
val eventSink: (DirectLogoutEvents) -> Unit, val eventSink: (DirectLogoutEvents) -> Unit,
) )

View file

@ -17,13 +17,13 @@ open class DirectLogoutStateProvider : PreviewParameterProvider<DirectLogoutStat
aDirectLogoutState(logoutAction = AsyncAction.ConfirmingNoParams), aDirectLogoutState(logoutAction = AsyncAction.ConfirmingNoParams),
aDirectLogoutState(logoutAction = AsyncAction.Loading), aDirectLogoutState(logoutAction = AsyncAction.Loading),
aDirectLogoutState(logoutAction = AsyncAction.Failure(Exception("Error"))), aDirectLogoutState(logoutAction = AsyncAction.Failure(Exception("Error"))),
aDirectLogoutState(logoutAction = AsyncAction.Success("success")), aDirectLogoutState(logoutAction = AsyncAction.Success(Unit)),
) )
} }
fun aDirectLogoutState( fun aDirectLogoutState(
canDoDirectSignOut: Boolean = true, canDoDirectSignOut: Boolean = true,
logoutAction: AsyncAction<String?> = AsyncAction.Uninitialized, logoutAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
eventSink: (DirectLogoutEvents) -> Unit = {}, eventSink: (DirectLogoutEvents) -> Unit = {},
) = DirectLogoutState( ) = DirectLogoutState(
canDoDirectSignOut = canDoDirectSignOut, canDoDirectSignOut = canDoDirectSignOut,

View file

@ -11,8 +11,5 @@ import androidx.compose.runtime.Composable
interface DirectLogoutView { interface DirectLogoutView {
@Composable @Composable
fun Render( fun Render(state: DirectLogoutState)
state: DirectLogoutState,
onSuccessLogout: (logoutUrlResult: String?) -> Unit
)
} }

View file

@ -1,23 +0,0 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.logout.api.util
import android.app.Activity
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
import timber.log.Timber
fun onSuccessLogout(
activity: Activity,
darkTheme: Boolean,
url: String?,
) {
Timber.d("Success logout with result url: $url")
url?.let {
activity.openUrlInChromeCustomTab(null, darkTheme, it)
}
}

View file

@ -19,9 +19,9 @@ class DefaultLogoutUseCase @Inject constructor(
private val authenticationService: MatrixAuthenticationService, private val authenticationService: MatrixAuthenticationService,
private val matrixClientProvider: MatrixClientProvider, private val matrixClientProvider: MatrixClientProvider,
) : LogoutUseCase { ) : LogoutUseCase {
override suspend fun logout(ignoreSdkError: Boolean): String? { override suspend fun logout(ignoreSdkError: Boolean) {
val currentSession = authenticationService.getLatestSessionId() val currentSession = authenticationService.getLatestSessionId()
return if (currentSession != null) { if (currentSession != null) {
matrixClientProvider.getOrRestore(currentSession) matrixClientProvider.getOrRestore(currentSession)
.getOrThrow() .getOrThrow()
.logout(userInitiated = true, ignoreSdkError = true) .logout(userInitiated = true, ignoreSdkError = true)

View file

@ -7,7 +7,6 @@
package io.element.android.features.logout.impl package io.element.android.features.logout.impl
import androidx.activity.compose.LocalActivity
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
@ -17,9 +16,7 @@ import com.bumble.appyx.core.plugin.plugins
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.logout.api.LogoutEntryPoint import io.element.android.features.logout.api.LogoutEntryPoint
import io.element.android.features.logout.api.util.onSuccessLogout
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
@ContributesNode(SessionScope::class) @ContributesNode(SessionScope::class)
@ -35,14 +32,9 @@ class LogoutNode @AssistedInject constructor(
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
val activity = requireNotNull(LocalActivity.current)
val isDark = ElementTheme.isLightTheme.not()
LogoutView( LogoutView(
state = state, state = state,
onChangeRecoveryKeyClick = ::onChangeRecoveryKeyClick, onChangeRecoveryKeyClick = ::onChangeRecoveryKeyClick,
onSuccessLogout = {
onSuccessLogout(activity, isDark, it)
},
onBackClick = ::navigateUp, onBackClick = ::navigateUp,
modifier = modifier, modifier = modifier,
) )

View file

@ -35,7 +35,7 @@ class LogoutPresenter @Inject constructor(
@Composable @Composable
override fun present(): LogoutState { override fun present(): LogoutState {
val localCoroutineScope = rememberCoroutineScope() val localCoroutineScope = rememberCoroutineScope()
val logoutAction: MutableState<AsyncAction<String?>> = remember { val logoutAction: MutableState<AsyncAction<Unit>> = remember {
mutableStateOf(AsyncAction.Uninitialized) mutableStateOf(AsyncAction.Uninitialized)
} }
@ -91,7 +91,7 @@ class LogoutPresenter @Inject constructor(
} }
private fun CoroutineScope.logout( private fun CoroutineScope.logout(
logoutAction: MutableState<AsyncAction<String?>>, logoutAction: MutableState<AsyncAction<Unit>>,
ignoreSdkError: Boolean, ignoreSdkError: Boolean,
) = launch { ) = launch {
suspend { suspend {

View file

@ -18,6 +18,6 @@ data class LogoutState(
val doesBackupExistOnServer: Boolean, val doesBackupExistOnServer: Boolean,
val recoveryState: RecoveryState, val recoveryState: RecoveryState,
val backupUploadState: BackupUploadState, val backupUploadState: BackupUploadState,
val logoutAction: AsyncAction<String?>, val logoutAction: AsyncAction<Unit>,
val eventSink: (LogoutEvents) -> Unit, val eventSink: (LogoutEvents) -> Unit,
) )

View file

@ -38,7 +38,7 @@ fun aLogoutState(
doesBackupExistOnServer: Boolean = true, doesBackupExistOnServer: Boolean = true,
recoveryState: RecoveryState = RecoveryState.ENABLED, recoveryState: RecoveryState = RecoveryState.ENABLED,
backupUploadState: BackupUploadState = BackupUploadState.Unknown, backupUploadState: BackupUploadState = BackupUploadState.Unknown,
logoutAction: AsyncAction<String?> = AsyncAction.Uninitialized, logoutAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
eventSink: (LogoutEvents) -> Unit = {}, eventSink: (LogoutEvents) -> Unit = {},
) = LogoutState( ) = LogoutState(
isLastDevice = isLastDevice, isLastDevice = isLastDevice,

View file

@ -45,7 +45,6 @@ fun LogoutView(
state: LogoutState, state: LogoutState,
onChangeRecoveryKeyClick: () -> Unit, onChangeRecoveryKeyClick: () -> Unit,
onBackClick: () -> Unit, onBackClick: () -> Unit,
onSuccessLogout: (logoutUrlResult: String?) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val eventSink = state.eventSink val eventSink = state.eventSink
@ -80,9 +79,6 @@ fun LogoutView(
onDismissDialog = { onDismissDialog = {
eventSink(LogoutEvents.CloseDialogs) eventSink(LogoutEvents.CloseDialogs)
}, },
onSuccessLogout = {
onSuccessLogout(it)
},
) )
} }
@ -177,7 +173,6 @@ internal fun LogoutViewPreview(
LogoutView( LogoutView(
state, state,
onChangeRecoveryKeyClick = {}, onChangeRecoveryKeyClick = {},
onSuccessLogout = {},
onBackClick = {}, onBackClick = {},
) )
} }

View file

@ -23,10 +23,7 @@ import javax.inject.Inject
@ContributesBinding(SessionScope::class) @ContributesBinding(SessionScope::class)
class DefaultDirectLogoutView @Inject constructor() : DirectLogoutView { class DefaultDirectLogoutView @Inject constructor() : DirectLogoutView {
@Composable @Composable
override fun Render( override fun Render(state: DirectLogoutState) {
state: DirectLogoutState,
onSuccessLogout: (logoutUrlResult: String?) -> Unit,
) {
val eventSink = state.eventSink val eventSink = state.eventSink
LogoutActionDialog( LogoutActionDialog(
state.logoutAction, state.logoutAction,
@ -39,9 +36,6 @@ class DefaultDirectLogoutView @Inject constructor() : DirectLogoutView {
onDismissDialog = { onDismissDialog = {
eventSink(DirectLogoutEvents.CloseDialogs) eventSink(DirectLogoutEvents.CloseDialogs)
}, },
onSuccessLogout = {
onSuccessLogout(it)
},
) )
} }
} }
@ -51,8 +45,5 @@ class DefaultDirectLogoutView @Inject constructor() : DirectLogoutView {
internal fun DefaultDirectLogoutViewPreview( internal fun DefaultDirectLogoutViewPreview(
@PreviewParameter(DirectLogoutStateProvider::class) state: DirectLogoutState, @PreviewParameter(DirectLogoutStateProvider::class) state: DirectLogoutState,
) = ElementPreview { ) = ElementPreview {
DefaultDirectLogoutView().Render( DefaultDirectLogoutView().Render(state = state)
state = state,
onSuccessLogout = {},
)
} }

View file

@ -35,7 +35,7 @@ class DirectLogoutPresenter @Inject constructor(
override fun present(): DirectLogoutState { override fun present(): DirectLogoutState {
val localCoroutineScope = rememberCoroutineScope() val localCoroutineScope = rememberCoroutineScope()
val logoutAction: MutableState<AsyncAction<String?>> = remember { val logoutAction: MutableState<AsyncAction<Unit>> = remember {
mutableStateOf(AsyncAction.Uninitialized) mutableStateOf(AsyncAction.Uninitialized)
} }
@ -70,7 +70,7 @@ class DirectLogoutPresenter @Inject constructor(
} }
private fun CoroutineScope.logout( private fun CoroutineScope.logout(
logoutAction: MutableState<AsyncAction<String?>>, logoutAction: MutableState<AsyncAction<Unit>>,
ignoreSdkError: Boolean, ignoreSdkError: Boolean,
) = launch { ) = launch {
suspend { suspend {

View file

@ -8,9 +8,7 @@
package io.element.android.features.logout.impl.ui package io.element.android.features.logout.impl.ui
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import io.element.android.features.logout.impl.R import io.element.android.features.logout.impl.R
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
@ -20,11 +18,10 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun LogoutActionDialog( fun LogoutActionDialog(
state: AsyncAction<String?>, state: AsyncAction<Unit>,
onConfirmClick: () -> Unit, onConfirmClick: () -> Unit,
onForceLogoutClick: () -> Unit, onForceLogoutClick: () -> Unit,
onDismissDialog: () -> Unit, onDismissDialog: () -> Unit,
onSuccessLogout: (String?) -> Unit,
) { ) {
when (state) { when (state) {
AsyncAction.Uninitialized -> AsyncAction.Uninitialized ->
@ -44,11 +41,6 @@ fun LogoutActionDialog(
onRetry = onForceLogoutClick, onRetry = onForceLogoutClick,
onDismiss = onDismissDialog, onDismiss = onDismissDialog,
) )
is AsyncAction.Success -> { is AsyncAction.Success -> Unit
val latestOnSuccessLogout by rememberUpdatedState(onSuccessLogout)
LaunchedEffect(state) {
latestOnSuccessLogout(state.data)
}
}
} }
} }

View file

@ -15,11 +15,9 @@ import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
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.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.ensureCalledOnceWithParam
import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.pressTag import io.element.android.tests.testutils.pressTag
import org.junit.Rule import org.junit.Rule
@ -96,21 +94,6 @@ class LogoutViewTest {
eventsRecorder.assertSingle(LogoutEvents.CloseDialogs) eventsRecorder.assertSingle(LogoutEvents.CloseDialogs)
} }
@Test
fun `success logout invoke onSuccessLogout`() {
val data = "data"
val eventsRecorder = EventsRecorder<LogoutEvents>(expectEvents = false)
ensureCalledOnceWithParam<String?>(data) { callback ->
rule.setLogoutView(
aLogoutState(
logoutAction = AsyncAction.Success(data),
eventSink = eventsRecorder
),
onSuccessLogout = callback,
)
}
}
@Test @Test
fun `last session setting button invoke onChangeRecoveryKeyClicked`() { fun `last session setting button invoke onChangeRecoveryKeyClicked`() {
val eventsRecorder = EventsRecorder<LogoutEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<LogoutEvents>(expectEvents = false)
@ -131,14 +114,12 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setLogou
state: LogoutState, state: LogoutState,
onChangeRecoveryKeyClick: () -> Unit = EnsureNeverCalled(), onChangeRecoveryKeyClick: () -> Unit = EnsureNeverCalled(),
onBackClick: () -> Unit = EnsureNeverCalled(), onBackClick: () -> Unit = EnsureNeverCalled(),
onSuccessLogout: (logoutUrlResult: String?) -> Unit = EnsureNeverCalledWithParam()
) { ) {
setContent { setContent {
LogoutView( LogoutView(
state = state, state = state,
onChangeRecoveryKeyClick = onChangeRecoveryKeyClick, onChangeRecoveryKeyClick = onChangeRecoveryKeyClick,
onBackClick = onBackClick, onBackClick = onBackClick,
onSuccessLogout = onSuccessLogout,
) )
} }
} }

View file

@ -16,10 +16,8 @@ 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
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnceWithParam
import io.element.android.tests.testutils.pressBackKey import io.element.android.tests.testutils.pressBackKey
import org.junit.Ignore import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
@ -96,45 +94,12 @@ class DefaultDirectLogoutViewTest {
rule.clickOn(CommonStrings.action_cancel) rule.clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs) eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs)
} }
@Test
fun `success logout invoke expected callback and sends expected Event`() {
val eventsRecorder = EventsRecorder<DirectLogoutEvents>(expectEvents = false)
ensureCalledOnceWithParam<String?>(null) { callback ->
rule.setDefaultDirectLogoutView(
state = aDirectLogoutState(
logoutAction = AsyncAction.Success(null),
eventSink = eventsRecorder,
),
onSuccessLogout = callback
)
}
}
@Test
fun `success logout invoke expected callback and sends expected Event with data`() {
val eventsRecorder = EventsRecorder<DirectLogoutEvents>(expectEvents = false)
val data = "data"
ensureCalledOnceWithParam<String?>(data) { callback ->
rule.setDefaultDirectLogoutView(
state = aDirectLogoutState(
logoutAction = AsyncAction.Success(data),
eventSink = eventsRecorder,
),
onSuccessLogout = callback
)
}
}
} }
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setDefaultDirectLogoutView( private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setDefaultDirectLogoutView(
state: DirectLogoutState, state: DirectLogoutState,
onSuccessLogout: (String?) -> Unit = EnsureNeverCalledWithParam(),
) { ) {
setContent { setContent {
DefaultDirectLogoutView().Render( DefaultDirectLogoutView().Render(state)
state,
onSuccessLogout = onSuccessLogout,
)
} }
} }

View file

@ -12,9 +12,9 @@ import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask import io.element.android.tests.testutils.simulateLongTask
class FakeLogoutUseCase( class FakeLogoutUseCase(
var logoutLambda: (Boolean) -> String? = { lambdaError() } var logoutLambda: (Boolean) -> Unit = { lambdaError() }
) : LogoutUseCase { ) : LogoutUseCase {
override suspend fun logout(ignoreSdkError: Boolean): String? = simulateLongTask { override suspend fun logout(ignoreSdkError: Boolean) = simulateLongTask {
logoutLambda(ignoreSdkError) logoutLambda(ignoreSdkError)
} }
} }

View file

@ -21,7 +21,7 @@ class MatrixComposerDraftStore @Inject constructor(
private val client: MatrixClient, private val client: MatrixClient,
) : ComposerDraftStore { ) : ComposerDraftStore {
override suspend fun loadDraft(roomId: RoomId): ComposerDraft? { override suspend fun loadDraft(roomId: RoomId): ComposerDraft? {
return client.getRoom(roomId)?.use { room -> return client.getRoom(roomId)?.let { room ->
room.loadComposerDraft() room.loadComposerDraft()
.onFailure { .onFailure {
Timber.e(it, "Failed to load composer draft for room $roomId") Timber.e(it, "Failed to load composer draft for room $roomId")
@ -35,7 +35,7 @@ class MatrixComposerDraftStore @Inject constructor(
} }
override suspend fun updateDraft(roomId: RoomId, draft: ComposerDraft?) { override suspend fun updateDraft(roomId: RoomId, draft: ComposerDraft?) {
client.getRoom(roomId)?.use { room -> client.getRoom(roomId)?.let { room ->
val updateDraftResult = if (draft == null) { val updateDraftResult = if (draft == null) {
room.clearComposerDraft() room.clearComposerDraft()
} else { } else {

View file

@ -21,7 +21,6 @@ import io.element.android.anvilannotations.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.DirectLogoutEvents
import io.element.android.features.logout.api.direct.DirectLogoutView import io.element.android.features.logout.api.direct.DirectLogoutView
import io.element.android.features.logout.api.util.onSuccessLogout
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
@ -140,11 +139,6 @@ class PreferencesRootNode @AssistedInject constructor(
onDeactivateClick = this::onOpenAccountDeactivation onDeactivateClick = this::onOpenAccountDeactivation
) )
directLogoutView.Render( directLogoutView.Render(state = state.directLogoutState)
state = state.directLogoutState,
onSuccessLogout = {
onSuccessLogout(activity, isDark, it)
}
)
} }
} }

View file

@ -111,6 +111,6 @@ class RoomListNode @AssistedInject constructor(
) )
} }
directLogoutView.Render(state.directLogoutState) {} directLogoutView.Render(state.directLogoutState)
} }
} }

View file

@ -20,7 +20,6 @@ import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.appconfig.LearnMoreConfig import io.element.android.appconfig.LearnMoreConfig
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.features.logout.api.util.onSuccessLogout
import io.element.android.features.verifysession.api.VerifySessionEntryPoint import io.element.android.features.verifysession.api.VerifySessionEntryPoint
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
import io.element.android.libraries.architecture.inputs import io.element.android.libraries.architecture.inputs
@ -56,7 +55,6 @@ class VerifySelfSessionNode @AssistedInject constructor(
onEnterRecoveryKey = callback::onEnterRecoveryKey, onEnterRecoveryKey = callback::onEnterRecoveryKey,
onResetKey = callback::onResetKey, onResetKey = callback::onResetKey,
onFinish = callback::onDone, onFinish = callback::onDone,
onSuccessLogout = { onSuccessLogout(activity, isDark, it) },
) )
} }
} }

View file

@ -69,7 +69,7 @@ class VerifySelfSessionPresenter @AssistedInject constructor(
val skipVerification by sessionPreferencesStore.isSessionVerificationSkipped().collectAsState(initial = false) val skipVerification by sessionPreferencesStore.isSessionVerificationSkipped().collectAsState(initial = false)
val sessionVerifiedStatus by sessionVerificationService.sessionVerifiedStatus.collectAsState() val sessionVerifiedStatus by sessionVerificationService.sessionVerifiedStatus.collectAsState()
val signOutAction = remember { val signOutAction = remember {
mutableStateOf<AsyncAction<String?>>(AsyncAction.Uninitialized) mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
} }
val step by remember { val step by remember {
derivedStateOf { derivedStateOf {
@ -195,7 +195,7 @@ class VerifySelfSessionPresenter @AssistedInject constructor(
.launchIn(this) .launchIn(this)
} }
private fun CoroutineScope.signOut(signOutAction: MutableState<AsyncAction<String?>>) = launch { private fun CoroutineScope.signOut(signOutAction: MutableState<AsyncAction<Unit>>) = launch {
suspend { suspend {
logoutUseCase.logout(ignoreSdkError = true) logoutUseCase.logout(ignoreSdkError = true)
}.runCatchingUpdatingState(signOutAction) }.runCatchingUpdatingState(signOutAction)

View file

@ -16,7 +16,7 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationD
@Immutable @Immutable
data class VerifySelfSessionState( data class VerifySelfSessionState(
val step: Step, val step: Step,
val signOutAction: AsyncAction<String?>, val signOutAction: AsyncAction<Unit>,
val displaySkipButton: Boolean, val displaySkipButton: Boolean,
val eventSink: (VerifySelfSessionViewEvents) -> Unit, val eventSink: (VerifySelfSessionViewEvents) -> Unit,
) { ) {

View file

@ -65,7 +65,7 @@ open class VerifySelfSessionStateProvider : PreviewParameterProvider<VerifySelfS
internal fun aVerifySelfSessionState( internal fun aVerifySelfSessionState(
step: Step = Step.Initial(canEnterRecoveryKey = false), step: Step = Step.Initial(canEnterRecoveryKey = false),
signOutAction: AsyncAction<String?> = AsyncAction.Uninitialized, signOutAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
displaySkipButton: Boolean = false, displaySkipButton: Boolean = false,
eventSink: (VerifySelfSessionViewEvents) -> Unit = {}, eventSink: (VerifySelfSessionViewEvents) -> Unit = {},
) = VerifySelfSessionState( ) = VerifySelfSessionState(

View file

@ -58,7 +58,6 @@ fun VerifySelfSessionView(
onEnterRecoveryKey: () -> Unit, onEnterRecoveryKey: () -> Unit,
onResetKey: () -> Unit, onResetKey: () -> Unit,
onFinish: () -> Unit, onFinish: () -> Unit,
onSuccessLogout: (String?) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val step = state.step val step = state.step
@ -144,12 +143,7 @@ fun VerifySelfSessionView(
AsyncAction.Loading -> { AsyncAction.Loading -> {
ProgressDialog(text = stringResource(id = R.string.screen_signout_in_progress_dialog_content)) ProgressDialog(text = stringResource(id = R.string.screen_signout_in_progress_dialog_content))
} }
is AsyncAction.Success -> { is AsyncAction.Success,
val latestOnSuccessLogout by rememberUpdatedState(onSuccessLogout)
LaunchedEffect(state) {
latestOnSuccessLogout(state.signOutAction.data)
}
}
is AsyncAction.Confirming, is AsyncAction.Confirming,
is AsyncAction.Failure, is AsyncAction.Failure,
AsyncAction.Uninitialized -> Unit AsyncAction.Uninitialized -> Unit
@ -372,6 +366,5 @@ internal fun VerifySelfSessionViewPreview(@PreviewParameter(VerifySelfSessionSta
onEnterRecoveryKey = {}, onEnterRecoveryKey = {},
onResetKey = {}, onResetKey = {},
onFinish = {}, onFinish = {},
onSuccessLogout = {},
) )
} }

View file

@ -314,7 +314,7 @@ class VerifySelfSessionPresenterTest {
emitVerifiedStatus(SessionVerifiedStatus.Verified) emitVerifiedStatus(SessionVerifiedStatus.Verified)
emitVerificationFlowState(VerificationFlowState.DidFinish) emitVerificationFlowState(VerificationFlowState.DidFinish)
} }
val signOutLambda = lambdaRecorder<Boolean, String?> { "aUrl" } val signOutLambda = lambdaRecorder<Boolean, Unit> {}
val presenter = createVerifySelfSessionPresenter( val presenter = createVerifySelfSessionPresenter(
service, service,
logoutUseCase = FakeLogoutUseCase(signOutLambda) logoutUseCase = FakeLogoutUseCase(signOutLambda)
@ -326,7 +326,6 @@ class VerifySelfSessionPresenterTest {
assertThat(awaitItem().signOutAction.isLoading()).isTrue() assertThat(awaitItem().signOutAction.isLoading()).isTrue()
val finalItem = awaitItem() val finalItem = awaitItem()
assertThat(finalItem.signOutAction.isSuccess()).isTrue() assertThat(finalItem.signOutAction.isSuccess()).isTrue()
assertThat(finalItem.signOutAction.dataOrNull()).isEqualTo("aUrl")
signOutLambda.assertions().isCalledOnce().with(value(true)) signOutLambda.assertions().isCalledOnce().with(value(true))
} }
} }

View file

@ -13,7 +13,6 @@ 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.verifysession.impl.R import io.element.android.features.verifysession.impl.R
import io.element.android.features.verifysession.impl.ui.aEmojisSessionVerificationData import io.element.android.features.verifysession.impl.ui.aEmojisSessionVerificationData
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData 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
@ -21,7 +20,6 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.ensureCalledOnceWithParam
import io.element.android.tests.testutils.pressBackKey import io.element.android.tests.testutils.pressBackKey
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -224,27 +222,12 @@ class VerifySelfSessionViewTest {
} }
} }
@Test
fun `on success logout - onFinished callback is called immediately`() {
val aUrl = "aUrl"
ensureCalledOnceWithParam<String?>(aUrl) { callback ->
rule.setVerifySelfSessionView(
aVerifySelfSessionState(
signOutAction = AsyncAction.Success(aUrl),
eventSink = EnsureNeverCalledWithParam(),
),
onSuccessLogout = callback,
)
}
}
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setVerifySelfSessionView( private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setVerifySelfSessionView(
state: VerifySelfSessionState, state: VerifySelfSessionState,
onLearnMoreClick: () -> Unit = EnsureNeverCalled(), onLearnMoreClick: () -> Unit = EnsureNeverCalled(),
onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(), onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(),
onFinished: () -> Unit = EnsureNeverCalled(), onFinished: () -> Unit = EnsureNeverCalled(),
onResetKey: () -> Unit = EnsureNeverCalled(), onResetKey: () -> Unit = EnsureNeverCalled(),
onSuccessLogout: (String?) -> Unit = EnsureNeverCalledWithParam(),
) { ) {
setContent { setContent {
VerifySelfSessionView( VerifySelfSessionView(
@ -253,7 +236,6 @@ class VerifySelfSessionViewTest {
onEnterRecoveryKey = onEnterRecoveryKey, onEnterRecoveryKey = onEnterRecoveryKey,
onFinish = onFinished, onFinish = onFinished,
onResetKey = onResetKey, onResetKey = onResetKey,
onSuccessLogout = onSuccessLogout,
) )
} }
} }

View file

@ -173,7 +173,7 @@ jsoup = "org.jsoup:jsoup:1.18.3"
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0" molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0"
timber = "com.jakewharton.timber:timber:5.0.1" timber = "com.jakewharton.timber:timber:5.0.1"
matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.2.17" matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.2.26"
matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" }
matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" }
sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }

View file

@ -84,12 +84,11 @@ interface MatrixClient : Closeable {
/** /**
* Logout the user. * Logout the user.
* Returns an optional URL. When the URL is there, it should be presented to the user after logout for *
* Relying Party (RP) initiated logout on their account page.
* @param userInitiated if false, the logout came from the HS, no request will be made and the session entry will be kept in the store. * @param userInitiated if false, the logout came from the HS, no request will be made and the session entry will be kept in the store.
* @param ignoreSdkError if true, the SDK will ignore any error and delete the session data anyway. * @param ignoreSdkError if true, the SDK will ignore any error and delete the session data anyway.
*/ */
suspend fun logout(userInitiated: Boolean, ignoreSdkError: Boolean): String? suspend fun logout(userInitiated: Boolean, ignoreSdkError: Boolean)
/** /**
* Retrieve the user profile, will also eventually emit a new value to [userProfile]. * Retrieve the user profile, will also eventually emit a new value to [userProfile].

View file

@ -493,8 +493,7 @@ class RustMatrixClient(
deleteSessionDirectory(deleteCryptoDb = false) deleteSessionDirectory(deleteCryptoDb = false)
} }
override suspend fun logout(userInitiated: Boolean, ignoreSdkError: Boolean): String? { override suspend fun logout(userInitiated: Boolean, ignoreSdkError: Boolean) {
var result: String? = null
sessionCoroutineScope.cancel() sessionCoroutineScope.cancel()
// Remove current delegate so we don't receive an auth error // Remove current delegate so we don't receive an auth error
clientDelegateTaskHandle?.cancelAndDestroy() clientDelegateTaskHandle?.cancelAndDestroy()
@ -502,7 +501,7 @@ class RustMatrixClient(
withContext(sessionDispatcher) { withContext(sessionDispatcher) {
if (userInitiated) { if (userInitiated) {
try { try {
result = innerClient.logout() innerClient.logout()
} catch (failure: Throwable) { } catch (failure: Throwable) {
if (ignoreSdkError) { if (ignoreSdkError) {
Timber.e(failure, "Fail to call logout on HS. Still delete local files.") Timber.e(failure, "Fail to call logout on HS. Still delete local files.")
@ -521,7 +520,6 @@ class RustMatrixClient(
sessionStore.removeSession(sessionId.value) sessionStore.removeSession(sessionId.value)
} }
} }
return result
} }
override fun canDeactivateAccount(): Boolean { override fun canDeactivateAccount(): Boolean {

View file

@ -122,9 +122,7 @@ class FakeMatrixClient(
var getRoomSummaryFlowLambda = { _: RoomIdOrAlias -> var getRoomSummaryFlowLambda = { _: RoomIdOrAlias ->
flowOf<Optional<RoomSummary>>(Optional.empty()) flowOf<Optional<RoomSummary>>(Optional.empty())
} }
var logoutLambda: (Boolean, Boolean) -> String? = { _, _ -> var logoutLambda: (Boolean, Boolean) -> Unit = { _, _ -> }
null
}
override suspend fun getRoom(roomId: RoomId): MatrixRoom? { override suspend fun getRoom(roomId: RoomId): MatrixRoom? {
return getRoomResults[roomId] return getRoomResults[roomId]
@ -174,8 +172,8 @@ class FakeMatrixClient(
clearCacheLambda() clearCacheLambda()
} }
override suspend fun logout(userInitiated: Boolean, ignoreSdkError: Boolean): String? = simulateLongTask { override suspend fun logout(userInitiated: Boolean, ignoreSdkError: Boolean) = simulateLongTask {
return logoutLambda(ignoreSdkError, userInitiated) logoutLambda(ignoreSdkError, userInitiated)
} }
override fun canDeactivateAccount() = canDeactivateAccountResult() override fun canDeactivateAccount() = canDeactivateAccountResult()