From 2d9cdd1acfabcd11ea17cd7bb306b856a079af40 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 14 Sep 2023 10:25:39 +0200 Subject: [PATCH] Add Advanced Settings screen in the preferences. --- features/preferences/impl/build.gradle.kts | 2 + .../preferences/impl/PreferencesFlowNode.kt | 13 +++- .../impl/advanced/AdvancedSettingsEvents.kt | 22 ++++++ .../impl/advanced/AdvancedSettingsNode.kt | 45 +++++++++++ .../advanced/AdvancedSettingsPresenter.kt | 59 +++++++++++++++ .../impl/advanced/AdvancedSettingsState.kt | 23 ++++++ .../advanced/AdvancedSettingsStateProvider.kt | 37 +++++++++ .../impl/advanced/AdvancedSettingsView.kt | 63 ++++++++++++++++ .../impl/root/PreferencesRootNode.kt | 6 ++ .../impl/root/PreferencesRootView.kt | 10 +++ libraries/preferences/api/build.gradle.kts | 27 +++++++ .../preferences/api/store/PreferencesStore.kt | 29 +++++++ libraries/preferences/impl/build.gradle.kts | 36 +++++++++ .../impl/store/DefaultPreferencesStore.kt | 75 +++++++++++++++++++ libraries/preferences/test/build.gradle.kts | 28 +++++++ .../test/InMemoryPreferencesStore.kt | 49 ++++++++++++ .../kotlin/extension/DependencyHandleScope.kt | 1 + 17 files changed, 524 insertions(+), 1 deletion(-) create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsNode.kt create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt create mode 100644 libraries/preferences/api/build.gradle.kts create mode 100644 libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/PreferencesStore.kt create mode 100644 libraries/preferences/impl/build.gradle.kts create mode 100644 libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesStore.kt create mode 100644 libraries/preferences/test/build.gradle.kts create mode 100644 libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index e2df4cfa9a..447adb5fa3 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { implementation(projects.libraries.network) implementation(projects.libraries.pushstore.api) implementation(projects.libraries.pushstore.test) + implementation(projects.libraries.preferences.api) implementation(projects.libraries.testtags) implementation(projects.libraries.uiStrings) implementation(projects.features.rageshake.api) @@ -55,6 +56,7 @@ dependencies { implementation(libs.accompanist.placeholder) implementation(libs.coil.compose) implementation(libs.androidx.browser) + implementation(libs.androidx.datastore.preferences) api(projects.features.preferences.api) ksp(libs.showkase.processor) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt index 85ae3ac55f..238b4006e7 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt @@ -31,11 +31,12 @@ import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.preferences.api.PreferencesEntryPoint import io.element.android.features.preferences.impl.about.AboutNode +import io.element.android.features.preferences.impl.advanced.AdvancedSettingsNode import io.element.android.features.preferences.impl.analytics.AnalyticsSettingsNode import io.element.android.features.preferences.impl.developer.DeveloperSettingsNode +import io.element.android.features.preferences.impl.developer.tracing.ConfigureTracingNode import io.element.android.features.preferences.impl.notifications.NotificationSettingsNode import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingNode -import io.element.android.features.preferences.impl.developer.tracing.ConfigureTracingNode import io.element.android.features.preferences.impl.root.PreferencesRootNode import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler @@ -63,6 +64,9 @@ class PreferencesFlowNode @AssistedInject constructor( @Parcelize data object DeveloperSettings : NavTarget + @Parcelize + data object AdvancedSettings : NavTarget + @Parcelize data object ConfigureTracing : NavTarget @@ -106,6 +110,10 @@ class PreferencesFlowNode @AssistedInject constructor( override fun onOpenNotificationSettings() { backstack.push(NavTarget.NotificationSettings) } + + override fun onOpenAdvancedSettings() { + backstack.push(NavTarget.AdvancedSettings) + } } createNode(buildContext, plugins = listOf(callback)) } @@ -138,6 +146,9 @@ class PreferencesFlowNode @AssistedInject constructor( val input = EditDefaultNotificationSettingNode.Inputs(navTarget.isOneToOne) createNode(buildContext, plugins = listOf(input)) } + NavTarget.AdvancedSettings -> { + createNode(buildContext) + } } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt new file mode 100644 index 0000000000..37641d684c --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 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 + * + * http://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.preferences.impl.advanced + +sealed interface AdvancedSettingsEvents { + data class SetRichTextEditorEnabled(val enabled: Boolean) : AdvancedSettingsEvents + data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsNode.kt new file mode 100644 index 0000000000..f7f0fd2eb8 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsNode.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023 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 + * + * http://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.preferences.impl.advanced + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.SessionScope + +@ContributesNode(SessionScope::class) +class AdvancedSettingsNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: AdvancedSettingsPresenter, +) : Node(buildContext, plugins = plugins) { + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + AdvancedSettingsView( + state = state, + modifier = modifier, + onBackPressed = ::navigateUp + ) + } +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt new file mode 100644 index 0000000000..b30ca63dc8 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023 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 + * + * http://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.preferences.impl.advanced + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import io.element.android.features.preferences.api.store.PreferencesStore +import io.element.android.libraries.architecture.Presenter +import kotlinx.coroutines.launch +import javax.inject.Inject + +class AdvancedSettingsPresenter @Inject constructor( + private val preferencesStore: PreferencesStore, +) : Presenter { + + @Composable + override fun present(): AdvancedSettingsState { + val localCoroutineScope = rememberCoroutineScope() + val isRichTextEditorEnabled by preferencesStore + .isRichTextEditorEnabledFlow() + .collectAsState(initial = false) + val isDeveloperModeEnabled by preferencesStore + .isDevelopModeEnabledFlow() + .collectAsState(initial = false) + + fun handleEvents(event: AdvancedSettingsEvents) { + when (event) { + is AdvancedSettingsEvents.SetRichTextEditorEnabled -> localCoroutineScope.launch { + preferencesStore.setRichTextEditorEnabled(event.enabled) + } + is AdvancedSettingsEvents.SetDeveloperModeEnabled -> localCoroutineScope.launch { + preferencesStore.setDevelopModeEnabled(event.enabled) + } + } + } + + return AdvancedSettingsState( + isRichTextEditorEnabled = isRichTextEditorEnabled, + isDeveloperModeEnabled = isDeveloperModeEnabled, + eventSink = ::handleEvents + ) + } +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt new file mode 100644 index 0000000000..19625b9ebc --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 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 + * + * http://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.preferences.impl.advanced + +data class AdvancedSettingsState constructor( + val isRichTextEditorEnabled: Boolean, + val isDeveloperModeEnabled: Boolean, + val eventSink: (AdvancedSettingsEvents) -> Unit +) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt new file mode 100644 index 0000000000..5ab50c8a16 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 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 + * + * http://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.preferences.impl.advanced + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +open class AdvancedSettingsStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aAdvancedSettingsState(), + aAdvancedSettingsState(isRichTextEditorEnabled = true), + aAdvancedSettingsState(isDeveloperModeEnabled = true), + ) +} + +fun aAdvancedSettingsState( + isRichTextEditorEnabled: Boolean = false, + isDeveloperModeEnabled: Boolean = false, +) = AdvancedSettingsState( + isRichTextEditorEnabled = isRichTextEditorEnabled, + isDeveloperModeEnabled = isDeveloperModeEnabled, + eventSink = {} +) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt new file mode 100644 index 0000000000..cff1454594 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023 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 + * + * http://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.preferences.impl.advanced + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch +import io.element.android.libraries.designsystem.components.preferences.PreferenceView +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun AdvancedSettingsView( + state: AdvancedSettingsState, + onBackPressed: () -> Unit, + modifier: Modifier = Modifier, +) { + PreferenceView( + modifier = modifier, + onBackPressed = onBackPressed, + title = stringResource(id = CommonStrings.common_advanced_settings) + ) { + PreferenceSwitch( + title = stringResource(id = CommonStrings.common_rich_text_editor), + // TODO i18n + subtitle = "Disable the rich text editor to type Markdown manually", + isChecked = state.isRichTextEditorEnabled, + onCheckedChange = { state.eventSink(AdvancedSettingsEvents.SetRichTextEditorEnabled(it)) }, + ) + PreferenceSwitch( + // TODO i18n + title = "Developer mode", + // TODO i18n + subtitle = "The developer mode activates hidden features. For developers only!", + isChecked = state.isDeveloperModeEnabled, + onCheckedChange = { state.eventSink(AdvancedSettingsEvents.SetDeveloperModeEnabled(it)) }, + ) + } +} + +@DayNightPreviews +@Composable +internal fun AdvancedSettingsViewPreview(@PreviewParameter(AdvancedSettingsStateProvider::class) state: AdvancedSettingsState) = + ElementPreview { + AdvancedSettingsView(state = state, onBackPressed = { }) + } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt index 5226d55b6b..b4495d8899 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt @@ -46,6 +46,7 @@ class PreferencesRootNode @AssistedInject constructor( fun onOpenAbout() fun onOpenDeveloperSettings() fun onOpenNotificationSettings() + fun onOpenAdvancedSettings() } private fun onOpenBugReport() { @@ -60,6 +61,10 @@ class PreferencesRootNode @AssistedInject constructor( plugins().forEach { it.onOpenDeveloperSettings() } } + private fun onOpenAdvancedSettings() { + plugins().forEach { it.onOpenAdvancedSettings() } + } + private fun onOpenAnalytics() { plugins().forEach { it.onOpenAnalytics() } } @@ -100,6 +105,7 @@ class PreferencesRootNode @AssistedInject constructor( onOpenAbout = this::onOpenAbout, onVerifyClicked = this::onVerifyClicked, onOpenDeveloperSettings = this::onOpenDeveloperSettings, + onOpenAdvancedSettings = this::onOpenAdvancedSettings, onSuccessLogout = { onSuccessLogout(activity, it) }, onManageAccountClicked = { onManageAccountClicked(activity, it, isDark) }, onOpenNotificationSettings = this::onOpenNotificationSettings diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index 3d901c4617..c6d34c6969 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -25,6 +25,7 @@ import androidx.compose.material.icons.outlined.Help import androidx.compose.material.icons.outlined.InsertChart import androidx.compose.material.icons.outlined.Notifications import androidx.compose.material.icons.outlined.OpenInNew +import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.VerifiedUser import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -58,6 +59,7 @@ fun PreferencesRootView( onOpenRageShake: () -> Unit, onOpenAbout: () -> Unit, onOpenDeveloperSettings: () -> Unit, + onOpenAdvancedSettings: () -> Unit, onSuccessLogout: (logoutUrlResult: String?) -> Unit, onOpenNotificationSettings: () -> Unit, modifier: Modifier = Modifier, @@ -125,6 +127,13 @@ fun PreferencesRootView( DeveloperPreferencesView(onOpenDeveloperSettings) HorizontalDivider() } + HorizontalDivider() + PreferenceText( + title = stringResource(id = CommonStrings.common_advanced_settings), + icon = Icons.Outlined.Settings, + onClick = onOpenAdvancedSettings, + ) + HorizontalDivider() LogoutPreferenceView( state = state.logoutState, onSuccessLogout = onSuccessLogout, @@ -168,6 +177,7 @@ private fun ContentToPreview(matrixUser: MatrixUser) { onOpenAnalytics = {}, onOpenRageShake = {}, onOpenDeveloperSettings = {}, + onOpenAdvancedSettings = {}, onOpenAbout = {}, onVerifyClicked = {}, onSuccessLogout = {}, diff --git a/libraries/preferences/api/build.gradle.kts b/libraries/preferences/api/build.gradle.kts new file mode 100644 index 0000000000..f782dd328b --- /dev/null +++ b/libraries/preferences/api/build.gradle.kts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 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 + * + * http://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. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.preferences.api" +} + +dependencies { + implementation(libs.coroutines.core) +} diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/PreferencesStore.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/PreferencesStore.kt new file mode 100644 index 0000000000..57ad7859c1 --- /dev/null +++ b/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/PreferencesStore.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 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 + * + * http://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.preferences.api.store + +import kotlinx.coroutines.flow.Flow + +interface PreferencesStore { + suspend fun setRichTextEditorEnabled(enabled: Boolean) + fun isRichTextEditorEnabledFlow(): Flow + + suspend fun setDevelopModeEnabled(enabled: Boolean) + fun isDevelopModeEnabledFlow(): Flow + + suspend fun reset() +} diff --git a/libraries/preferences/impl/build.gradle.kts b/libraries/preferences/impl/build.gradle.kts new file mode 100644 index 0000000000..9c31d83481 --- /dev/null +++ b/libraries/preferences/impl/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 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 + * + * http://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. + */ + +plugins { + id("io.element.android-library") + alias(libs.plugins.anvil) +} + +android { + namespace = "io.element.android.libraries.preferences.impl" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + api(projects.libraries.preferences.api) + implementation(libs.dagger) + implementation(libs.androidx.datastore.preferences) + implementation(projects.libraries.di) + implementation(projects.libraries.core) +} diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesStore.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesStore.kt new file mode 100644 index 0000000000..5b0cbfb9ee --- /dev/null +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesStore.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2023 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 + * + * http://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.libraries.preferences.impl.store + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.preferencesDataStore +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.preferences.api.store.PreferencesStore +import io.element.android.libraries.core.bool.orFalse +import io.element.android.libraries.core.bool.orTrue +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.ApplicationContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +private val Context.dataStore: DataStore by preferencesDataStore(name = "elementx_preferences") + +private val richTextEditorKey = booleanPreferencesKey("richTextEditor") +private val developerModeKey = booleanPreferencesKey("developerMode") + +@ContributesBinding(AppScope::class) +class DefaultPreferencesStore @Inject constructor( + @ApplicationContext context: Context +) : PreferencesStore { + private val store = context.dataStore + + override suspend fun setRichTextEditorEnabled(enabled: Boolean) { + store.edit { prefs -> + prefs[richTextEditorKey] = enabled + } + } + + override fun isRichTextEditorEnabledFlow(): Flow { + return store.data.map { prefs -> + // enabled by default + prefs[richTextEditorKey].orTrue() + } + } + + override suspend fun setDevelopModeEnabled(enabled: Boolean) { + store.edit { prefs -> + prefs[developerModeKey] = enabled + } + } + + override fun isDevelopModeEnabledFlow(): Flow { + return store.data.map { prefs -> + // disabled by default + prefs[developerModeKey].orFalse() + } + } + + override suspend fun reset() { + store.edit { it.clear() } + } +} diff --git a/libraries/preferences/test/build.gradle.kts b/libraries/preferences/test/build.gradle.kts new file mode 100644 index 0000000000..86b891b21e --- /dev/null +++ b/libraries/preferences/test/build.gradle.kts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 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 + * + * http://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. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.preferences.test" + + dependencies { + api(projects.libraries.preferences.api) + implementation(libs.coroutines.core) + } +} diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt new file mode 100644 index 0000000000..550713747e --- /dev/null +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 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 + * + * http://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.libraries.featureflag.test + +import io.element.android.features.preferences.api.store.PreferencesStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +class InMemoryPreferencesStore( + isRichTextEditorEnabled: Boolean = false, + isDeveloperModeEnabled: Boolean = false, +) : PreferencesStore { + private var _isRichTextEditorEnabled = MutableStateFlow(isRichTextEditorEnabled) + private var _isDeveloperModeEnabled = MutableStateFlow(isDeveloperModeEnabled) + + override suspend fun setRichTextEditorEnabled(enabled: Boolean) { + _isRichTextEditorEnabled.value = enabled + } + + override fun isRichTextEditorEnabledFlow(): Flow { + return _isRichTextEditorEnabled + } + + override suspend fun setDevelopModeEnabled(enabled: Boolean) { + _isDeveloperModeEnabled.value = enabled + } + + override fun isDevelopModeEnabledFlow(): Flow { + return _isDeveloperModeEnabled + } + + override suspend fun reset() { + // No op + } +} diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index 592cf3c52a..1029100823 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -92,6 +92,7 @@ fun DependencyHandlerScope.allLibrariesImpl() { implementation(project(":libraries:pushproviders:unifiedpush")) implementation(project(":libraries:featureflag:impl")) implementation(project(":libraries:pushstore:impl")) + implementation(project(":libraries:preferences:impl")) implementation(project(":libraries:architecture")) implementation(project(":libraries:dateformatter:impl")) implementation(project(":libraries:di"))