Add Labs screen for beta testing of public features (#5465)
* Add Labs screen: - Make `Feature` have an `isInLabs` boolean to distinguish private feature flags from public ones. - Have `FeatureFlagsService` provide the list of available flags. - Display the labs item in the settings screen only if there are available public features. - Remove public feature toggles from developer options. - Implement the labs screen with the public features. - Add a clear cache step to the threads feature toggle - Update screenshots --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
a497703a90
commit
9714abe032
34 changed files with 684 additions and 17 deletions
|
|
@ -36,4 +36,10 @@ interface Feature {
|
|||
* If true: the feature is finished, it will not appear in the developer options screen.
|
||||
*/
|
||||
val isFinished: Boolean
|
||||
|
||||
/**
|
||||
* Whether the feature is only available in Labs (and not in developer options).
|
||||
* Feature flags that set this to `true` can be enabled by any users, not only those that have enabled developer mode.
|
||||
*/
|
||||
val isInLabs: Boolean
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,4 +33,9 @@ interface FeatureFlagService {
|
|||
* is registered
|
||||
*/
|
||||
suspend fun setFeatureEnabled(feature: Feature, enabled: Boolean): Boolean
|
||||
|
||||
/**
|
||||
* @return the list of available (not finished) features that can be toggled.
|
||||
*/
|
||||
fun getAvailableFeatures(): List<Feature>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ enum class FeatureFlags(
|
|||
override val description: String? = null,
|
||||
override val defaultValue: (BuildMeta) -> Boolean,
|
||||
override val isFinished: Boolean,
|
||||
override val isInLabs: Boolean = false,
|
||||
) : Feature {
|
||||
RoomDirectorySearch(
|
||||
key = "feature.roomdirectorysearch",
|
||||
|
|
@ -98,6 +99,7 @@ enum class FeatureFlags(
|
|||
description = "Renders thread messages as a dedicated timeline. Restarting the app is required for this setting to fully take effect.",
|
||||
defaultValue = { false },
|
||||
isFinished = false,
|
||||
isInLabs = true,
|
||||
),
|
||||
MultiAccount(
|
||||
key = "feature.multi_account",
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import dev.zacsweers.metro.SingleIn
|
|||
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.featureflag.api.FeatureFlags
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
|
|
@ -40,4 +41,8 @@ class DefaultFeatureFlagService(
|
|||
?.let { true }
|
||||
?: false
|
||||
}
|
||||
|
||||
override fun getAvailableFeatures(): List<Feature> {
|
||||
return FeatureFlags.entries.filter { !it.isFinished }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.featureflag.test
|
||||
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.featureflag.api.Feature
|
||||
|
||||
data class FakeFeature(
|
||||
override val key: String,
|
||||
override val title: String,
|
||||
override val description: String? = null,
|
||||
override val defaultValue: (BuildMeta) -> Boolean = { false },
|
||||
override val isFinished: Boolean = false,
|
||||
override val isInLabs: Boolean = false,
|
||||
) : Feature
|
||||
|
|
@ -17,6 +17,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
class FakeFeatureFlagService(
|
||||
initialState: Map<String, Boolean> = emptyMap(),
|
||||
private val buildMeta: BuildMeta = aBuildMeta(),
|
||||
var providedAvailableFeatures: List<Feature> = emptyList(),
|
||||
) : FeatureFlagService {
|
||||
private val enabledFeatures = initialState
|
||||
.mapValues { MutableStateFlow(it.value) }
|
||||
|
|
@ -31,4 +32,8 @@ class FakeFeatureFlagService(
|
|||
override fun isFeatureEnabledFlow(feature: Feature): Flow<Boolean> {
|
||||
return enabledFeatures.getOrPut(feature.key) { MutableStateFlow(feature.defaultValue(buildMeta)) }
|
||||
}
|
||||
|
||||
override fun getAvailableFeatures(): List<Feature> {
|
||||
return providedAvailableFeatures
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,12 @@
|
|||
|
||||
package io.element.android.libraries.featureflag.ui.model
|
||||
|
||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||
|
||||
data class FeatureUiModel(
|
||||
val key: String,
|
||||
val title: String,
|
||||
val description: String?,
|
||||
val icon: IconSource?,
|
||||
val isEnabled: Boolean
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
|
||||
fun aFeatureUiModelList(): ImmutableList<FeatureUiModel> {
|
||||
return persistentListOf(
|
||||
FeatureUiModel("key1", "Display State Events", "Show state events in the timeline", true),
|
||||
FeatureUiModel("key2", "Display Room Events", null, false),
|
||||
FeatureUiModel(key = "key1", title = "Display State Events", description = "Show state events in the timeline", icon = null, isEnabled = true),
|
||||
FeatureUiModel(key = "key2", title = "Display Room Events", description = null, icon = null, isEnabled = false),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue