From b9f4403408d1fcebff811eda4b6c8d1f7dcccf6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 19 Jul 2024 14:46:42 +0200 Subject: [PATCH] Add simplified sliding sync toggle to developer options --- .../impl/developer/DeveloperSettingsEvents.kt | 1 + .../developer/DeveloperSettingsPresenter.kt | 7 ++++++ .../impl/developer/DeveloperSettingsState.kt | 1 + .../DeveloperSettingsStateProvider.kt | 2 ++ .../impl/developer/DeveloperSettingsView.kt | 9 ++++++++ .../DeveloperSettingsPresenterTest.kt | 22 +++++++++++++++++++ .../developer/DeveloperSettingsViewTest.kt | 13 +++++++++++ libraries/matrix/impl/build.gradle.kts | 5 +++-- .../libraries/matrix/impl/RustMatrixClient.kt | 3 +++ .../matrix/impl/RustMatrixClientFactory.kt | 12 +++++++++- .../api/store/AppPreferencesStore.kt | 3 +++ .../impl/store/DefaultAppPreferencesStore.kt | 13 +++++++++++ .../test/InMemoryAppPreferencesStore.kt | 10 +++++++++ 13 files changed, 98 insertions(+), 3 deletions(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt index 58f7ac7c02..bb0252e04d 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt @@ -21,5 +21,6 @@ import io.element.android.libraries.featureflag.ui.model.FeatureUiModel sealed interface DeveloperSettingsEvents { data class UpdateEnabledFeature(val feature: FeatureUiModel, val isEnabled: Boolean) : DeveloperSettingsEvents data class SetCustomElementCallBaseUrl(val baseUrl: String?) : DeveloperSettingsEvents + data class SetSimplifiedSlidingSyncEnabled(val isEnabled: Boolean) : DeveloperSettingsEvents data object ClearCache : DeveloperSettingsEvents } 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 8295b815c9..4f4b833367 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 @@ -75,6 +75,9 @@ class DeveloperSettingsPresenter @Inject constructor( val customElementCallBaseUrl by appPreferencesStore .getCustomElementCallBaseUrlFlow() .collectAsState(initial = null) + val isSimplifiedSlidingSyncEnabled by appPreferencesStore + .isSimplifiedSlidingSyncEnabledFlow() + .collectAsState(initial = false) LaunchedEffect(Unit) { FeatureFlags.entries @@ -114,6 +117,9 @@ class DeveloperSettingsPresenter @Inject constructor( appPreferencesStore.setCustomElementCallBaseUrl(urlToSave) } DeveloperSettingsEvents.ClearCache -> coroutineScope.clearCache(clearCacheAction) + is DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled -> coroutineScope.launch { + appPreferencesStore.setSimplifiedSlidingSyncEnabled(event.isEnabled) + } } } @@ -127,6 +133,7 @@ class DeveloperSettingsPresenter @Inject constructor( defaultUrl = ElementCallConfig.DEFAULT_BASE_URL, validator = ::customElementCallUrlValidator, ), + isSimpleSlidingSyncEnabled = isSimplifiedSlidingSyncEnabled, eventSink = ::handleEvents ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt index 9a12823686..91cf7051c3 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt @@ -27,6 +27,7 @@ data class DeveloperSettingsState( val rageshakeState: RageshakePreferencesState, val clearCacheAction: AsyncData, val customElementCallBaseUrlState: CustomElementCallBaseUrlState, + val isSimpleSlidingSyncEnabled: Boolean, val eventSink: (DeveloperSettingsEvents) -> Unit ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt index fb93a63ffc..01c2971777 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt @@ -39,6 +39,7 @@ open class DeveloperSettingsStateProvider : PreviewParameterProvider = AsyncData.Uninitialized, customElementCallBaseUrlState: CustomElementCallBaseUrlState = aCustomElementCallBaseUrlState(), + isSimplifiedSlidingSyncEnabled: Boolean = false, eventSink: (DeveloperSettingsEvents) -> Unit = {}, ) = DeveloperSettingsState( features = aFeatureUiModelList(), @@ -46,6 +47,7 @@ fun aDeveloperSettingsState( cacheSize = AsyncData.Success("1.2 MB"), clearCacheAction = clearCacheAction, customElementCallBaseUrlState = customElementCallBaseUrlState, + isSimpleSlidingSyncEnabled = isSimplifiedSlidingSyncEnabled, eventSink = eventSink, ) 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 dd684fcaaa..3fab690191 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 @@ -26,6 +26,7 @@ import io.element.android.features.preferences.impl.R import io.element.android.features.rageshake.api.preferences.RageshakePreferencesView import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferencePage +import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch import io.element.android.libraries.designsystem.components.preferences.PreferenceText import io.element.android.libraries.designsystem.components.preferences.PreferenceTextField import io.element.android.libraries.designsystem.preview.ElementPreview @@ -60,6 +61,14 @@ fun DeveloperSettingsView( title = "Configure tracing", onClick = onOpenConfigureTracing, ) + PreferenceSwitch( + title = "Enable Simplified Sliding Sync", + subtitle = "This option requires an app restart to work.", + isChecked = state.isSimpleSlidingSyncEnabled, + onCheckedChange = { + state.eventSink(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(it)) + } + ) } PreferenceCategory(title = "Showkase") { PreferenceText( 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 113125d950..4c7a16f8cd 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 @@ -35,6 +35,7 @@ import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.awaitLastSequentialItem +import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -55,6 +56,7 @@ class DeveloperSettingsPresenterTest { assertThat(initialState.cacheSize).isEqualTo(AsyncData.Uninitialized) assertThat(initialState.customElementCallBaseUrlState).isNotNull() assertThat(initialState.customElementCallBaseUrlState.baseUrl).isNull() + assertThat(initialState.isSimpleSlidingSyncEnabled).isFalse() val loadedState = awaitItem() assertThat(loadedState.rageshakeState.isEnabled).isFalse() assertThat(loadedState.rageshakeState.isSupported).isTrue() @@ -160,6 +162,26 @@ class DeveloperSettingsPresenterTest { } } + @Test + fun `present - toggling simplified sliding sync changes the preferences`() = runTest { + val preferences = InMemoryAppPreferencesStore() + val presenter = createDeveloperSettingsPresenter(preferencesStore = preferences) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitLastSequentialItem() + assertThat(initialState.isSimpleSlidingSyncEnabled).isFalse() + + initialState.eventSink(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(true)) + assertThat(awaitItem().isSimpleSlidingSyncEnabled).isTrue() + assertThat(preferences.isSimplifiedSlidingSyncEnabledFlow().first()).isTrue() + + initialState.eventSink(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(false)) + assertThat(awaitItem().isSimpleSlidingSyncEnabled).isFalse() + assertThat(preferences.isSimplifiedSlidingSyncEnabledFlow().first()).isFalse() + } + } + private fun createDeveloperSettingsPresenter( featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(), cacheSizeUseCase: FakeComputeCacheSizeUseCase = FakeComputeCacheSizeUseCase(), diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt index 7288b76d3c..aa993c39bc 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt @@ -109,6 +109,19 @@ class DeveloperSettingsViewTest { rule.onNodeWithText("Clear cache").performClick() eventsRecorder.assertSingle(DeveloperSettingsEvents.ClearCache) } + + @Config(qualifiers = "h1500dp") + @Test + fun `clicking on the simplified sliding sync switch emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setDeveloperSettingsView( + state = aDeveloperSettingsState( + eventSink = eventsRecorder + ), + ) + rule.onNodeWithText("Enable Simplified Sliding Sync").performClick() + eventsRecorder.assertSingle(DeveloperSettingsEvents.SetSimplifiedSlidingSyncEnabled(true)) + } } private fun AndroidComposeTestRule.setDeveloperSettingsView( diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts index 523ea2fda1..c74cb6ce07 100644 --- a/libraries/matrix/impl/build.gradle.kts +++ b/libraries/matrix/impl/build.gradle.kts @@ -37,12 +37,13 @@ dependencies { debugImplementation(libs.matrix.sdk) } implementation(projects.appconfig) - implementation(projects.libraries.di) implementation(projects.libraries.androidutils) + implementation(projects.libraries.di) + implementation(projects.libraries.featureflag.api) implementation(projects.libraries.network) + implementation(projects.libraries.preferences.api) implementation(projects.services.analytics.api) implementation(projects.services.toolbox.api) - implementation(projects.libraries.featureflag.api) api(projects.libraries.matrix.api) implementation(libs.dagger) implementation(projects.libraries.core) 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 db86e1a2a5..1f9a531027 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,6 +69,7 @@ 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 @@ -126,6 +127,7 @@ 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() @@ -554,6 +556,7 @@ 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 7ddf54a300..f9c2a5b6d1 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 @@ -23,10 +23,12 @@ import io.element.android.libraries.matrix.impl.certificates.UserCertificatesPro import io.element.android.libraries.matrix.impl.proxy.ProxyProvider import io.element.android.libraries.matrix.impl.util.anonymizedTokens import io.element.android.libraries.network.useragent.UserAgentProvider +import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.ClientBuilder import org.matrix.rustcomponents.sdk.Session @@ -46,9 +48,14 @@ class RustMatrixClientFactory @Inject constructor( private val proxyProvider: ProxyProvider, private val clock: SystemClock, private val utdTracker: UtdTracker, + private val appPreferencesStore: AppPreferencesStore, ) { suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) { - val client = getBaseClientBuilder(sessionData.sessionPath, sessionData.passphrase) + val client = getBaseClientBuilder( + sessionPath = sessionData.sessionPath, + passphrase = sessionData.passphrase, + useSimplifiedSlidingSync = appPreferencesStore.isSimplifiedSlidingSyncEnabledFlow().first(), + ) .homeserverUrl(sessionData.homeserverUrl) .username(sessionData.userId) .use { it.build() } @@ -70,6 +77,7 @@ 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'") } @@ -79,6 +87,7 @@ class RustMatrixClientFactory @Inject constructor( sessionPath: String, passphrase: String?, slidingSyncProxy: String? = null, + useSimplifiedSlidingSync: Boolean = false, ): ClientBuilder { return ClientBuilder() .sessionPath(sessionPath) @@ -88,6 +97,7 @@ class RustMatrixClientFactory @Inject constructor( .addRootCertificates(userCertificatesProvider.provides()) .autoEnableBackups(true) .autoEnableCrossSigning(true) + .simplifiedSlidingSync(useSimplifiedSlidingSync) .run { // Workaround for non-nullable proxy parameter in the SDK, since each call to the ClientBuilder returns a new reference we need to keep proxyProvider.provides()?.let { proxy(it) } ?: this diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt index 5d12adbe74..73a42a4ff4 100644 --- a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt +++ b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt @@ -28,5 +28,8 @@ interface AppPreferencesStore { suspend fun setTheme(theme: String) fun getThemeFlow(): Flow + suspend fun setSimplifiedSlidingSyncEnabled(enabled: Boolean) + fun isSimplifiedSlidingSyncEnabledFlow(): Flow + suspend fun reset() } diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt index d25cda96d0..e80c6d60ad 100644 --- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt @@ -38,6 +38,7 @@ private val Context.dataStore: DataStore by preferencesDataStore(na private val developerModeKey = booleanPreferencesKey("developerMode") private val customElementCallBaseUrlKey = stringPreferencesKey("elementCallBaseUrl") private val themeKey = stringPreferencesKey("theme") +private val simplifiedSlidingSyncKey = booleanPreferencesKey("useSimplifiedSlidingSync") @ContributesBinding(AppScope::class) class DefaultAppPreferencesStore @Inject constructor( @@ -87,6 +88,18 @@ class DefaultAppPreferencesStore @Inject constructor( } } + override suspend fun setSimplifiedSlidingSyncEnabled(enabled: Boolean) { + store.edit { prefs -> + prefs[simplifiedSlidingSyncKey] = enabled + } + } + + override fun isSimplifiedSlidingSyncEnabledFlow(): Flow { + return store.data.map { prefs -> + prefs[simplifiedSlidingSyncKey] ?: false + } + } + override suspend fun reset() { store.edit { it.clear() } } diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt index c920f8c6b3..432c7a9772 100644 --- a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt @@ -24,10 +24,12 @@ class InMemoryAppPreferencesStore( isDeveloperModeEnabled: Boolean = false, customElementCallBaseUrl: String? = null, theme: String? = null, + simplifiedSlidingSyncEnabled: Boolean = false ) : AppPreferencesStore { private val isDeveloperModeEnabled = MutableStateFlow(isDeveloperModeEnabled) private val customElementCallBaseUrl = MutableStateFlow(customElementCallBaseUrl) private val theme = MutableStateFlow(theme) + private val simplifiedSlidingSyncEnabled = MutableStateFlow(simplifiedSlidingSyncEnabled) override suspend fun setDeveloperModeEnabled(enabled: Boolean) { isDeveloperModeEnabled.value = enabled @@ -53,6 +55,14 @@ class InMemoryAppPreferencesStore( return theme } + override suspend fun setSimplifiedSlidingSyncEnabled(enabled: Boolean) { + simplifiedSlidingSyncEnabled.value = enabled + } + + override fun isSimplifiedSlidingSyncEnabledFlow(): Flow { + return simplifiedSlidingSyncEnabled + } + override suspend fun reset() { // No op }