Let the order of FeatureFlag follow the order they are declared in the code.

Using map.keys does not guarantee that the order is kept, so using List instead.
This commit is contained in:
Benoit Marty 2025-10-17 10:44:03 +02:00 committed by Benoit Marty
parent 42c775d740
commit 828ee38c09

View file

@ -13,13 +13,13 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.key import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.runtime.snapshots.SnapshotStateList
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.features.preferences.impl.developer.tracing.toLogLevel import io.element.android.features.preferences.impl.developer.tracing.toLogLevel
@ -31,7 +31,6 @@ import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.core.meta.BuildType
@ -61,12 +60,8 @@ class DeveloperSettingsPresenter(
@Composable @Composable
override fun present(): DeveloperSettingsState { override fun present(): DeveloperSettingsState {
val rageshakeState = rageshakePresenter.present() val rageshakeState = rageshakePresenter.present()
val features = remember {
mutableStateMapOf<String, Feature>()
}
val enabledFeatures = remember { val enabledFeatures = remember {
mutableStateMapOf<String, Boolean>() mutableStateListOf<EnabledFeature>()
} }
val cacheSize = remember { val cacheSize = remember {
mutableStateOf<AsyncData<String>>(AsyncData.Uninitialized) mutableStateOf<AsyncData<String>>(AsyncData.Uninitialized)
@ -105,11 +100,10 @@ class DeveloperSettingsPresenter(
} }
} }
.forEach { feature -> .forEach { feature ->
features[feature.key] = feature enabledFeatures.add(EnabledFeature(feature, featureFlagService.isFeatureEnabled(feature)))
enabledFeatures[feature.key] = featureFlagService.isFeatureEnabled(feature)
} }
} }
val featureUiModels = createUiModels(features, enabledFeatures) val featureUiModels = createUiModels(enabledFeatures)
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
// Compute cache size each time the clear cache action value is changed // Compute cache size each time the clear cache action value is changed
LaunchedEffect(clearCacheAction.value.isSuccess()) { LaunchedEffect(clearCacheAction.value.isSuccess()) {
@ -119,7 +113,6 @@ class DeveloperSettingsPresenter(
fun handleEvents(event: DeveloperSettingsEvents) { fun handleEvents(event: DeveloperSettingsEvents) {
when (event) { when (event) {
is DeveloperSettingsEvents.UpdateEnabledFeature -> coroutineScope.updateEnabledFeature( is DeveloperSettingsEvents.UpdateEnabledFeature -> coroutineScope.updateEnabledFeature(
features,
enabledFeatures, enabledFeatures,
event.feature, event.feature,
event.isEnabled, event.isEnabled,
@ -172,19 +165,17 @@ class DeveloperSettingsPresenter(
@Composable @Composable
private fun createUiModels( private fun createUiModels(
features: SnapshotStateMap<String, Feature>, enabledFeatures: SnapshotStateList<EnabledFeature>,
enabledFeatures: SnapshotStateMap<String, Boolean>
): List<FeatureUiModel> { ): List<FeatureUiModel> {
return features.values.map { feature -> return enabledFeatures.map { enabledFeature ->
key(feature.key) { key(enabledFeature.feature.key) {
val isEnabled = enabledFeatures[feature.key].orFalse() remember(enabledFeature) {
remember(feature, isEnabled) {
FeatureUiModel( FeatureUiModel(
key = feature.key, key = enabledFeature.feature.key,
title = feature.title, title = enabledFeature.feature.title,
description = feature.description, description = enabledFeature.feature.description,
icon = null, icon = null,
isEnabled = isEnabled isEnabled = enabledFeature.isEnabled
) )
} }
} }
@ -192,15 +183,15 @@ class DeveloperSettingsPresenter(
} }
private fun CoroutineScope.updateEnabledFeature( private fun CoroutineScope.updateEnabledFeature(
features: SnapshotStateMap<String, Feature>, enabledFeatures: SnapshotStateList<EnabledFeature>,
enabledFeatures: SnapshotStateMap<String, Boolean>,
featureUiModel: FeatureUiModel, featureUiModel: FeatureUiModel,
enabled: Boolean, enabled: Boolean,
@Suppress("UNUSED_PARAMETER") triggerClearCache: () -> Unit, @Suppress("UNUSED_PARAMETER") triggerClearCache: () -> Unit,
) = launch { ) = launch {
val feature = features[featureUiModel.key] ?: return@launch val featureIndex = enabledFeatures.indexOfFirst { it.feature.key == featureUiModel.key }.takeIf { it != -1 } ?: return@launch
val feature = enabledFeatures[featureIndex].feature
if (featureFlagService.setFeatureEnabled(feature, enabled)) { if (featureFlagService.setFeatureEnabled(feature, enabled)) {
enabledFeatures[featureUiModel.key] = enabled enabledFeatures[featureIndex] = enabledFeatures[featureIndex].copy(isEnabled = enabled)
} }
} }
@ -225,3 +216,8 @@ private fun customElementCallUrlValidator(url: String?): Boolean {
if (parsedUrl.host.isNullOrBlank()) error("Missing host") if (parsedUrl.host.isNullOrBlank()) error("Missing host")
}.isSuccess }.isSuccess
} }
private data class EnabledFeature(
val feature: Feature,
val isEnabled: Boolean,
)