From a4872dcfc09e5d9f7259603c6ea15c0178ac028d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 26 Jun 2024 12:45:30 +0200 Subject: [PATCH 1/2] FFs can now be toggle in release builds too: - Removed `StaticFeatureFlagProvider`. - Always provide `PreferencesFeatureFlagProvider`. - For the default values of feature flags, use a lambda with a `BuildMeta` parameter so we can customize the return value based on its data. --- changelog.d/3073.bugfix | 1 + .../messages/impl/MessagesPresenterTest.kt | 2 +- .../MessageComposerPresenterTest.kt | 4 +- .../impl/OnBoardingPresenterTest.kt | 17 ++++-- libraries/featureflag/api/build.gradle.kts | 2 + .../libraries/featureflag/api/Feature.kt | 6 +- .../libraries/featureflag/api/FeatureFlags.kt | 34 +++++++---- libraries/featureflag/impl/build.gradle.kts | 1 + .../impl/DefaultFeatureFlagService.kt | 6 +- .../impl/PreferencesFeatureFlagProvider.kt | 8 ++- .../impl/StaticFeatureFlagProvider.kt | 56 ------------------- .../featureflag/impl/di/FeatureFlagModule.kt | 16 +----- .../impl/DefaultFeatureFlagServiceTest.kt | 25 +++++---- .../impl/FakeMutableFeatureFlagProvider.kt | 8 ++- libraries/featureflag/test/build.gradle.kts | 2 + .../test/FakeFeatureFlagService.kt | 12 ++-- samples/minimal/build.gradle.kts | 4 ++ .../android/samples/minimal/RoomListScreen.kt | 30 +++++++++- 18 files changed, 117 insertions(+), 117 deletions(-) create mode 100644 changelog.d/3073.bugfix delete mode 100644 libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt diff --git a/changelog.d/3073.bugfix b/changelog.d/3073.bugfix new file mode 100644 index 00000000000..2f7dbf19f9c --- /dev/null +++ b/changelog.d/3073.bugfix @@ -0,0 +1 @@ +Fix feature flags not being able to be toggle in developer settings in release builds. diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index e91f39260fb..74cffb30fdc 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -745,7 +745,7 @@ class MessagesPresenterTest { private suspend fun ReceiveTurbine.awaitFirstItem(): T { // Skip 2 item if Mentions feature is enabled, else 1 - skipItems(if (FeatureFlags.Mentions.defaultValue) 2 else 1) + skipItems(if (FeatureFlags.Mentions.defaultValue(aBuildMeta())) 2 else 1) return awaitItem() } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt index b2683dc895f..f2d64453da9 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt @@ -63,6 +63,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID_3 import io.element.android.libraries.matrix.test.A_USER_ID_4 +import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.room.FakeMatrixRoom @@ -828,7 +829,6 @@ class MessageComposerPresenterTest { // If room is a DM, `RoomMemberSuggestion.Room` is not returned room.givenCanTriggerRoomNotification(Result.success(true)) - room.isDirect } } @@ -1299,7 +1299,7 @@ class MessageComposerPresenterTest { private suspend fun ReceiveTurbine.awaitFirstItem(): T { // Skip 2 item if Mentions feature is enabled, else 1 - skipItems(if (FeatureFlags.Mentions.defaultValue) 2 else 1) + skipItems(if (FeatureFlags.Mentions.defaultValue(aBuildMeta())) 2 else 1) return awaitItem() } } diff --git a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt index 651d8c5cf44..66b6baf48b8 100644 --- a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt +++ b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt @@ -35,13 +35,18 @@ class OnBoardingPresenterTest { @Test fun `present - initial state`() = runTest { + val buildMeta = aBuildMeta( + applicationName = "A", + productionApplicationName = "B", + desktopApplicationName = "C", + ) + val featureFlagService = FakeFeatureFlagService( + initialState = mapOf(FeatureFlags.QrCodeLogin.key to true), + buildMeta = buildMeta, + ) val presenter = OnBoardingPresenter( - buildMeta = aBuildMeta( - applicationName = "A", - productionApplicationName = "B", - desktopApplicationName = "C", - ), - featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.QrCodeLogin.name to true)), + buildMeta = buildMeta, + featureFlagService = featureFlagService, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() diff --git a/libraries/featureflag/api/build.gradle.kts b/libraries/featureflag/api/build.gradle.kts index 9420821932b..4351dd521fc 100644 --- a/libraries/featureflag/api/build.gradle.kts +++ b/libraries/featureflag/api/build.gradle.kts @@ -23,5 +23,7 @@ android { } dependencies { + implementation(projects.appconfig) + implementation(projects.libraries.core) implementation(libs.coroutines.core) } diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/Feature.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/Feature.kt index ee980919533..8d220e13bfc 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/Feature.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/Feature.kt @@ -16,6 +16,8 @@ package io.element.android.libraries.featureflag.api +import io.element.android.libraries.core.meta.BuildMeta + interface Feature { /** * Unique key to identify the feature. @@ -33,9 +35,9 @@ interface Feature { val description: String? /** - * The default value of the feature (enabled or disabled). + * Calculate the default value of the feature (enabled or disabled) given a [BuildMeta]. */ - val defaultValue: Boolean + val defaultValue: (BuildMeta) -> Boolean /** * Whether the feature is finished or not. diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index 4d0c50a5330..17ea03f7a9b 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -16,6 +16,10 @@ package io.element.android.libraries.featureflag.api +import io.element.android.appconfig.OnBoardingConfig +import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.core.meta.BuildType + /** * To enable or disable a FeatureFlags, change the `defaultValue` value. * Warning: to enable a flag for the release app, you MUST update the file @@ -25,82 +29,88 @@ enum class FeatureFlags( override val key: String, override val title: String, override val description: String? = null, - override val defaultValue: Boolean, + override val defaultValue: (BuildMeta) -> Boolean, override val isFinished: Boolean, ) : Feature { LocationSharing( key = "feature.locationsharing", title = "Allow user to share location", - defaultValue = true, + defaultValue = { true }, isFinished = true, ), Polls( key = "feature.polls", title = "Polls", description = "Create poll and render poll events in the timeline", - defaultValue = true, + defaultValue = { true }, isFinished = true, ), NotificationSettings( key = "feature.notificationsettings", title = "Show notification settings", - defaultValue = true, + defaultValue = { true }, isFinished = true, ), VoiceMessages( key = "feature.voicemessages", title = "Voice messages", description = "Send and receive voice messages", - defaultValue = true, + defaultValue = { true }, isFinished = true, ), PinUnlock( key = "feature.pinunlock", title = "Pin unlock", description = "Allow user to lock/unlock the app with a pin code or biometrics", - defaultValue = true, + defaultValue = { true }, isFinished = true, ), Mentions( key = "feature.mentions", title = "Mentions", description = "Type `@` to get mention suggestions and insert them", - defaultValue = true, + defaultValue = { true }, isFinished = false, ), MarkAsUnread( key = "feature.markAsUnread", title = "Mark as unread", description = "Allow user to mark a room as unread", - defaultValue = true, + defaultValue = { true }, isFinished = false, ), RoomDirectorySearch( key = "feature.roomdirectorysearch", title = "Room directory search", description = "Allow user to search for public rooms in their homeserver", - defaultValue = false, + defaultValue = { false }, isFinished = false, ), ShowBlockedUsersDetails( key = "feature.showBlockedUsersDetails", title = "Show blocked users details", description = "Show the name and avatar of blocked users in the blocked users list", - defaultValue = false, + defaultValue = { false }, isFinished = false, ), QrCodeLogin( key = "feature.qrCodeLogin", title = "Enable login using QR code", description = "Allow the user to login using the QR code flow", - defaultValue = true, + defaultValue = { buildMeta -> + when (buildMeta.buildType) { + // TODO remove once the feature is ready to publish + BuildType.RELEASE -> false + else -> OnBoardingConfig.CAN_LOGIN_WITH_QR_CODE + } + }, isFinished = false, ), IncomingShare( key = "feature.incomingShare", title = "Incoming Share support", description = "Allow the application to receive data from other applications", - defaultValue = true, + defaultValue = { true }, isFinished = false, ), } diff --git a/libraries/featureflag/impl/build.gradle.kts b/libraries/featureflag/impl/build.gradle.kts index 76b6836eb3c..26667b02dde 100644 --- a/libraries/featureflag/impl/build.gradle.kts +++ b/libraries/featureflag/impl/build.gradle.kts @@ -41,4 +41,5 @@ dependencies { testImplementation(libs.coroutines.test) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) + testImplementation(projects.libraries.matrix.test) } diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt index 0a6b97e13d1..b06f99e95ef 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt @@ -17,6 +17,7 @@ package io.element.android.libraries.featureflag.impl import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SingleIn import io.element.android.libraries.featureflag.api.Feature @@ -28,14 +29,15 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) class DefaultFeatureFlagService @Inject constructor( - private val providers: Set<@JvmSuppressWildcards FeatureFlagProvider> + private val providers: Set<@JvmSuppressWildcards FeatureFlagProvider>, + private val buildMeta: BuildMeta, ) : FeatureFlagService { override fun isFeatureEnabledFlow(feature: Feature): Flow { return providers.filter { it.hasFeature(feature) } .sortedByDescending(FeatureFlagProvider::priority) .firstOrNull() ?.isFeatureEnabledFlow(feature) - ?: flowOf(feature.defaultValue) + ?: flowOf(feature.defaultValue(buildMeta)) } override suspend fun setFeatureEnabled(feature: Feature, enabled: Boolean): Boolean { diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/PreferencesFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/PreferencesFeatureFlagProvider.kt index 76344e078c9..e5e1be63ec5 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/PreferencesFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/PreferencesFeatureFlagProvider.kt @@ -22,6 +22,7 @@ import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.preferencesDataStore +import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.featureflag.api.Feature import kotlinx.coroutines.flow.Flow @@ -34,7 +35,10 @@ private val Context.dataStore: DataStore by preferencesDataStore(na /** * Note: this will be used only in the nightly and in the debug build. */ -class PreferencesFeatureFlagProvider @Inject constructor(@ApplicationContext context: Context) : MutableFeatureFlagProvider { +class PreferencesFeatureFlagProvider @Inject constructor( + @ApplicationContext context: Context, + private val buildMeta: BuildMeta, +) : MutableFeatureFlagProvider { private val store = context.dataStore override val priority = MEDIUM_PRIORITY @@ -47,7 +51,7 @@ class PreferencesFeatureFlagProvider @Inject constructor(@ApplicationContext con override fun isFeatureEnabledFlow(feature: Feature): Flow { return store.data.map { prefs -> - prefs[booleanPreferencesKey(feature.key)] ?: feature.defaultValue + prefs[booleanPreferencesKey(feature.key)] ?: feature.defaultValue(buildMeta) }.distinctUntilChanged() } diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt deleted file mode 100644 index 7574144066b..00000000000 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.impl - -import io.element.android.appconfig.OnBoardingConfig -import io.element.android.libraries.featureflag.api.Feature -import io.element.android.libraries.featureflag.api.FeatureFlags -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import javax.inject.Inject - -/** - * This provider is used for release build. - * This is the place to enable or disable feature for the release build. - */ -class StaticFeatureFlagProvider @Inject constructor() : - FeatureFlagProvider { - override val priority = LOW_PRIORITY - - override fun isFeatureEnabledFlow(feature: Feature): Flow { - val isFeatureEnabled = if (feature is FeatureFlags) { - when (feature) { - FeatureFlags.LocationSharing -> true - FeatureFlags.Polls -> true - FeatureFlags.NotificationSettings -> true - FeatureFlags.VoiceMessages -> true - FeatureFlags.PinUnlock -> true - FeatureFlags.Mentions -> true - FeatureFlags.MarkAsUnread -> true - FeatureFlags.RoomDirectorySearch -> false - FeatureFlags.ShowBlockedUsersDetails -> false - FeatureFlags.QrCodeLogin -> OnBoardingConfig.CAN_LOGIN_WITH_QR_CODE - FeatureFlags.IncomingShare -> true - } - } else { - false - } - return flowOf(isFeatureEnabled) - } - - override fun hasFeature(feature: Feature) = true -} diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt index 19d4b1336df..99755992afe 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt @@ -20,11 +20,9 @@ import com.squareup.anvil.annotations.ContributesTo import dagger.Module import dagger.Provides import dagger.multibindings.ElementsIntoSet -import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.di.AppScope import io.element.android.libraries.featureflag.impl.FeatureFlagProvider import io.element.android.libraries.featureflag.impl.PreferencesFeatureFlagProvider -import io.element.android.libraries.featureflag.impl.StaticFeatureFlagProvider @Module @ContributesTo(AppScope::class) @@ -33,20 +31,10 @@ object FeatureFlagModule { @Provides @ElementsIntoSet fun providesFeatureFlagProvider( - buildType: BuildType, mutableFeatureFlagProvider: PreferencesFeatureFlagProvider, - staticFeatureFlagProvider: StaticFeatureFlagProvider, ): Set { - val providers = HashSet() - when (buildType) { - BuildType.RELEASE -> { - providers.add(staticFeatureFlagProvider) - } - BuildType.NIGHTLY, - BuildType.DEBUG -> { - providers.add(mutableFeatureFlagProvider) - } + return buildSet { + add(mutableFeatureFlagProvider) } - return providers } } diff --git a/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagServiceTest.kt b/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagServiceTest.kt index 0fd503d6d43..df1feef14e3 100644 --- a/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagServiceTest.kt +++ b/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagServiceTest.kt @@ -19,38 +19,42 @@ package io.element.android.libraries.featureflag.impl import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.matrix.test.core.aBuildMeta import kotlinx.coroutines.test.runTest import org.junit.Test class DefaultFeatureFlagServiceTest { @Test fun `given service without provider when feature is checked then it returns the default value`() = runTest { - val featureFlagService = DefaultFeatureFlagService(emptySet()) + val buildMeta = aBuildMeta() + val featureFlagService = DefaultFeatureFlagService(emptySet(), buildMeta) featureFlagService.isFeatureEnabledFlow(FeatureFlags.LocationSharing).test { - assertThat(awaitItem()).isEqualTo(FeatureFlags.LocationSharing.defaultValue) + assertThat(awaitItem()).isEqualTo(FeatureFlags.LocationSharing.defaultValue(buildMeta)) cancelAndIgnoreRemainingEvents() } } @Test fun `given service without provider when set enabled feature is called then it returns false`() = runTest { - val featureFlagService = DefaultFeatureFlagService(emptySet()) + val featureFlagService = DefaultFeatureFlagService(emptySet(), aBuildMeta()) val result = featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, true) assertThat(result).isFalse() } @Test fun `given service with a runtime provider when set enabled feature is called then it returns true`() = runTest { - val featureFlagProvider = FakeMutableFeatureFlagProvider(0) - val featureFlagService = DefaultFeatureFlagService(setOf(featureFlagProvider)) + val buildMeta = aBuildMeta() + val featureFlagProvider = FakeMutableFeatureFlagProvider(0, buildMeta) + val featureFlagService = DefaultFeatureFlagService(setOf(featureFlagProvider), buildMeta) val result = featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, true) assertThat(result).isTrue() } @Test fun `given service with a runtime provider and feature enabled when feature is checked then it returns the correct value`() = runTest { - val featureFlagProvider = FakeMutableFeatureFlagProvider(0) - val featureFlagService = DefaultFeatureFlagService(setOf(featureFlagProvider)) + val buildMeta = aBuildMeta() + val featureFlagProvider = FakeMutableFeatureFlagProvider(0, buildMeta) + val featureFlagService = DefaultFeatureFlagService(setOf(featureFlagProvider), buildMeta) featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, true) featureFlagService.isFeatureEnabledFlow(FeatureFlags.LocationSharing).test { assertThat(awaitItem()).isTrue() @@ -61,9 +65,10 @@ class DefaultFeatureFlagServiceTest { @Test fun `given service with 2 runtime providers when feature is checked then it uses the priority correctly`() = runTest { - val lowPriorityFeatureFlagProvider = FakeMutableFeatureFlagProvider(LOW_PRIORITY) - val highPriorityFeatureFlagProvider = FakeMutableFeatureFlagProvider(HIGH_PRIORITY) - val featureFlagService = DefaultFeatureFlagService(setOf(lowPriorityFeatureFlagProvider, highPriorityFeatureFlagProvider)) + val buildMeta = aBuildMeta() + val lowPriorityFeatureFlagProvider = FakeMutableFeatureFlagProvider(LOW_PRIORITY, buildMeta) + val highPriorityFeatureFlagProvider = FakeMutableFeatureFlagProvider(HIGH_PRIORITY, buildMeta) + val featureFlagService = DefaultFeatureFlagService(setOf(lowPriorityFeatureFlagProvider, highPriorityFeatureFlagProvider), buildMeta) lowPriorityFeatureFlagProvider.setFeatureEnabled(FeatureFlags.LocationSharing, false) highPriorityFeatureFlagProvider.setFeatureEnabled(FeatureFlags.LocationSharing, true) featureFlagService.isFeatureEnabledFlow(FeatureFlags.LocationSharing).test { diff --git a/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeMutableFeatureFlagProvider.kt b/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeMutableFeatureFlagProvider.kt index 315624d474e..583fb081e3e 100644 --- a/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeMutableFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeMutableFeatureFlagProvider.kt @@ -16,11 +16,15 @@ package io.element.android.libraries.featureflag.impl +import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.featureflag.api.Feature import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -class FakeMutableFeatureFlagProvider(override val priority: Int) : MutableFeatureFlagProvider { +class FakeMutableFeatureFlagProvider( + override val priority: Int, + private val buildMeta: BuildMeta, +) : MutableFeatureFlagProvider { private val enabledFeatures = mutableMapOf>() override suspend fun setFeatureEnabled(feature: Feature, enabled: Boolean) { @@ -29,7 +33,7 @@ class FakeMutableFeatureFlagProvider(override val priority: Int) : MutableFeatur } override fun isFeatureEnabledFlow(feature: Feature): Flow { - return enabledFeatures.getOrPut(feature.key) { MutableStateFlow(feature.defaultValue) } + return enabledFeatures.getOrPut(feature.key) { MutableStateFlow(feature.defaultValue(buildMeta)) } } override fun hasFeature(feature: Feature): Boolean = true diff --git a/libraries/featureflag/test/build.gradle.kts b/libraries/featureflag/test/build.gradle.kts index 3c3b199ce68..bb18234ea0d 100644 --- a/libraries/featureflag/test/build.gradle.kts +++ b/libraries/featureflag/test/build.gradle.kts @@ -23,6 +23,8 @@ android { dependencies { api(projects.libraries.featureflag.api) + implementation(projects.libraries.core) + implementation(projects.libraries.matrix.test) implementation(libs.coroutines.core) } } diff --git a/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeatureFlagService.kt b/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeatureFlagService.kt index 1cdde032864..c5104d054b8 100644 --- a/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeatureFlagService.kt +++ b/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeatureFlagService.kt @@ -16,19 +16,19 @@ package io.element.android.libraries.featureflag.test +import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.featureflag.api.Feature import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.matrix.test.core.aBuildMeta import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow class FakeFeatureFlagService( - initialState: Map = emptyMap() + initialState: Map = emptyMap(), + private val buildMeta: BuildMeta = aBuildMeta(), ) : FeatureFlagService { private val enabledFeatures = initialState - .map { - it.key to MutableStateFlow(it.value) - } - .toMap() + .mapValues { MutableStateFlow(it.value) } .toMutableMap() override suspend fun setFeatureEnabled(feature: Feature, enabled: Boolean): Boolean { @@ -38,6 +38,6 @@ class FakeFeatureFlagService( } override fun isFeatureEnabledFlow(feature: Feature): Flow { - return enabledFeatures.getOrPut(feature.key) { MutableStateFlow(feature.defaultValue) } + return enabledFeatures.getOrPut(feature.key) { MutableStateFlow(feature.defaultValue(buildMeta)) } } } diff --git a/samples/minimal/build.gradle.kts b/samples/minimal/build.gradle.kts index 794761bdb41..91cc4bd1109 100644 --- a/samples/minimal/build.gradle.kts +++ b/samples/minimal/build.gradle.kts @@ -30,6 +30,10 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } + buildFeatures { + buildConfig = true + } + buildTypes { release { isMinifyEnabled = false diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index 494d5b1ee8c..c2a9d2dd8f5 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -35,6 +35,8 @@ import io.element.android.features.roomlist.impl.migration.SharedPreferencesMigr import io.element.android.features.roomlist.impl.search.RoomListSearchDataSource import io.element.android.features.roomlist.impl.search.RoomListSearchPresenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.dateformatter.impl.DateFormatters import io.element.android.libraries.dateformatter.impl.DefaultLastMessageTimestampFormatter import io.element.android.libraries.dateformatter.impl.LocalDateTimeProvider @@ -44,7 +46,7 @@ import io.element.android.libraries.eventformatter.impl.ProfileChangeContentForm import io.element.android.libraries.eventformatter.impl.RoomMembershipContentFormatter import io.element.android.libraries.eventformatter.impl.StateContentFormatter import io.element.android.libraries.featureflag.impl.DefaultFeatureFlagService -import io.element.android.libraries.featureflag.impl.StaticFeatureFlagProvider +import io.element.android.libraries.featureflag.impl.PreferencesFeatureFlagProvider import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsPresenter import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState import io.element.android.libraries.indicator.impl.DefaultIndicatorService @@ -78,8 +80,12 @@ class RoomListScreen( private val sessionVerificationService = matrixClient.sessionVerificationService() private val encryptionService = matrixClient.encryptionService() private val stringProvider = AndroidStringProvider(context.resources) + private val buildMeta = getBuildMeta(context) private val featureFlagService = DefaultFeatureFlagService( - providers = setOf(StaticFeatureFlagProvider()) + providers = setOf( + PreferencesFeatureFlagProvider(context = context, buildMeta = buildMeta) + ), + buildMeta = buildMeta, ) private val roomListRoomSummaryFactory = RoomListRoomSummaryFactory( lastMessageTimestampFormatter = DefaultLastMessageTimestampFormatter( @@ -195,4 +201,24 @@ class RoomListScreen( } } } + + private fun getBuildMeta(context: Context): BuildMeta { + val buildType = BuildType.valueOf(BuildConfig.BUILD_TYPE.uppercase()) + val name = context.getString(R.string.app_name) + return BuildMeta( + isDebuggable = BuildConfig.DEBUG, + buildType = buildType, + applicationName = name, + productionApplicationName = name, + desktopApplicationName = name, + applicationId = BuildConfig.APPLICATION_ID, + lowPrivacyLoggingEnabled = false, + versionName = BuildConfig.VERSION_NAME, + versionCode = BuildConfig.VERSION_CODE.toLong(), + gitRevision = "", + gitBranchName = "", + flavorDescription = "", + flavorShortDescription = "", + ) + } } From d7a2340dfdaf0fde6b0ee41ffa853480e3b8e51b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 2 Jul 2024 17:48:22 +0200 Subject: [PATCH 2/2] Fix QR code login config for debug & nightly builds --- .../kotlin/io/element/android/appconfig/OnBoardingConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt index e2a78fc5227..dd30e20b8dd 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt @@ -18,7 +18,7 @@ package io.element.android.appconfig object OnBoardingConfig { /** Whether the user can use QR code login. */ - const val CAN_LOGIN_WITH_QR_CODE = false + const val CAN_LOGIN_WITH_QR_CODE = true /** Whether the user can create an account using the app. */ const val CAN_CREATE_ACCOUNT = false