diff --git a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutUseCase.kt b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutUseCase.kt new file mode 100644 index 0000000000..397f5ebed7 --- /dev/null +++ b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutUseCase.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.logout.api + +/** + * Used to trigger a log out of the current user from any part of the app. + */ +interface LogoutUseCase { + /** + * 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. + */ + suspend fun logout(ignoreSdkError: Boolean) +} diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutUseCase.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutUseCase.kt new file mode 100644 index 0000000000..52bf0425d4 --- /dev/null +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutUseCase.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.logout.impl + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.logout.api.LogoutUseCase +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.MatrixClient +import javax.inject.Inject + +@ContributesBinding(SessionScope::class) +class DefaultLogoutUseCase @Inject constructor( + private val matrixClient: MatrixClient +) : LogoutUseCase { + override suspend fun logout(ignoreSdkError: Boolean) { + matrixClient.logout(ignoreSdkError = ignoreSdkError) + } +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt index 4f4b833367..7b95463ac8 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt @@ -28,6 +28,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.snapshots.SnapshotStateMap import io.element.android.appconfig.ElementCallConfig +import io.element.android.features.logout.api.LogoutUseCase import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter @@ -55,6 +56,7 @@ class DeveloperSettingsPresenter @Inject constructor( private val rageshakePresenter: RageshakePreferencesPresenter, private val appPreferencesStore: AppPreferencesStore, private val buildMeta: BuildMeta, + private val logoutUseCase: LogoutUseCase, ) : Presenter { @Composable override fun present(): DeveloperSettingsState { @@ -119,6 +121,7 @@ class DeveloperSettingsPresenter @Inject constructor( DeveloperSettingsEvents.ClearCache -> coroutineScope.clearCache(clearCacheAction) is DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled -> coroutineScope.launch { appPreferencesStore.setSimplifiedSlidingSyncEnabled(event.isEnabled) + logoutUseCase.logout(ignoreSdkError = true) } } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt index 3fab690191..f46637e6cb 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt @@ -63,7 +63,7 @@ fun DeveloperSettingsView( ) PreferenceSwitch( title = "Enable Simplified Sliding Sync", - subtitle = "This option requires an app restart to work.", + subtitle = "When toggled you'll be logged out of the app and will need to log in again.", isChecked = state.isSimpleSlidingSyncEnabled, onCheckedChange = { state.eventSink(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(it)) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt index 4c7a16f8cd..be3c6bbb20 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt @@ -21,6 +21,7 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.appconfig.ElementCallConfig +import io.element.android.features.logout.api.LogoutUseCase import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase import io.element.android.features.rageshake.impl.preferences.DefaultRageshakePreferencesPresenter @@ -163,9 +164,10 @@ class DeveloperSettingsPresenterTest { } @Test - fun `present - toggling simplified sliding sync changes the preferences`() = runTest { + fun `present - toggling simplified sliding sync changes the preferences and logs out the user`() = runTest { + val logoutUseCase = FakeLogoutUseCase() val preferences = InMemoryAppPreferencesStore() - val presenter = createDeveloperSettingsPresenter(preferencesStore = preferences) + val presenter = createDeveloperSettingsPresenter(preferencesStore = preferences, logoutUseCase = logoutUseCase) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -175,10 +177,12 @@ class DeveloperSettingsPresenterTest { initialState.eventSink(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(true)) assertThat(awaitItem().isSimpleSlidingSyncEnabled).isTrue() assertThat(preferences.isSimplifiedSlidingSyncEnabledFlow().first()).isTrue() + assertThat(logoutUseCase.logoutCallCount).isEqualTo(1) initialState.eventSink(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(false)) assertThat(awaitItem().isSimpleSlidingSyncEnabled).isFalse() assertThat(preferences.isSimplifiedSlidingSyncEnabledFlow().first()).isFalse() + assertThat(logoutUseCase.logoutCallCount).isEqualTo(2) } } @@ -189,6 +193,7 @@ class DeveloperSettingsPresenterTest { rageshakePresenter: DefaultRageshakePreferencesPresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore()), preferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(), buildMeta: BuildMeta = aBuildMeta(), + logoutUseCase: FakeLogoutUseCase = FakeLogoutUseCase() ): DeveloperSettingsPresenter { return DeveloperSettingsPresenter( featureFlagService = featureFlagService, @@ -197,6 +202,16 @@ class DeveloperSettingsPresenterTest { rageshakePresenter = rageshakePresenter, appPreferencesStore = preferencesStore, buildMeta = buildMeta, + logoutUseCase = logoutUseCase, ) } } + +private class FakeLogoutUseCase : LogoutUseCase { + var logoutCallCount = 0 + private set + + override suspend fun logout(ignoreSdkError: Boolean) { + logoutCallCount++ + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 1f9a531027..db86e1a2a5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -69,7 +69,6 @@ import io.element.android.libraries.matrix.impl.util.anonymizedTokens import io.element.android.libraries.matrix.impl.util.cancelAndDestroy import io.element.android.libraries.matrix.impl.util.mxCallbackFlow import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService -import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.collections.immutable.ImmutableList @@ -127,7 +126,6 @@ class RustMatrixClient( private val baseDirectory: File, baseCacheDirectory: File, private val clock: SystemClock, - private val appPreferencesStore: AppPreferencesStore, ) : MatrixClient { override val sessionId: UserId = UserId(client.userId()) override val deviceId: String = client.deviceId() @@ -556,7 +554,6 @@ class RustMatrixClient( } close() deleteSessionDirectory(deleteCryptoDb = true) - appPreferencesStore.setSimplifiedSlidingSyncEnabled(false) if (removeSession) { sessionStore.removeSession(sessionId.value) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index f9c2a5b6d1..96d8e4939d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -77,7 +77,6 @@ class RustMatrixClientFactory @Inject constructor( baseDirectory = baseDirectory, baseCacheDirectory = cacheDirectory, clock = clock, - appPreferencesStore = appPreferencesStore, ).also { Timber.tag(it.toString()).d("Creating Client with access token '$anonymizedAccessToken' and refresh token '$anonymizedRefreshToken'") }