Remove Element Call feature flag and revert base URL to call.element.io (#1809)
Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
14cf2b0440
commit
5701e98d77
25 changed files with 164 additions and 191 deletions
|
|
@ -17,5 +17,5 @@
|
||||||
package io.element.android.appconfig
|
package io.element.android.appconfig
|
||||||
|
|
||||||
object ElementCallConfig {
|
object ElementCallConfig {
|
||||||
const val DEFAULT_BASE_URL = "https://call.element.dev"
|
const val DEFAULT_BASE_URL = "https://call.element.io"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
changelog.d/+remove-element-call-flag.misc
Normal file
4
changelog.d/+remove-element-call-flag.misc
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
Remove Element Call feature flag, it's not always enabled.
|
||||||
|
|
||||||
|
- Reverted the EC base URL to `https://call.element.io`.
|
||||||
|
- Moved the option to override this URL to developer settings from advanced settings.
|
||||||
|
|
@ -157,10 +157,10 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
val enableTextFormatting by preferencesStore.isRichTextEditorEnabledFlow().collectAsState(initial = true)
|
val enableTextFormatting by preferencesStore.isRichTextEditorEnabledFlow().collectAsState(initial = true)
|
||||||
|
|
||||||
var enableVoiceMessages by remember { mutableStateOf(false) }
|
var enableVoiceMessages by remember { mutableStateOf(false) }
|
||||||
var enableInRoomCalls by remember { mutableStateOf(false) }
|
// TODO add min power level to use this feature in the future?
|
||||||
|
val enableInRoomCalls = true
|
||||||
LaunchedEffect(featureFlagsService) {
|
LaunchedEffect(featureFlagsService) {
|
||||||
enableVoiceMessages = featureFlagsService.isFeatureEnabled(FeatureFlags.VoiceMessages)
|
enableVoiceMessages = featureFlagsService.isFeatureEnabled(FeatureFlags.VoiceMessages)
|
||||||
enableInRoomCalls = featureFlagsService.isFeatureEnabled(FeatureFlags.InRoomCalls)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleEvents(event: MessagesEvents) {
|
fun handleEvents(event: MessagesEvents) {
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,4 @@ package io.element.android.features.preferences.impl.advanced
|
||||||
sealed interface AdvancedSettingsEvents {
|
sealed interface AdvancedSettingsEvents {
|
||||||
data class SetRichTextEditorEnabled(val enabled: Boolean) : AdvancedSettingsEvents
|
data class SetRichTextEditorEnabled(val enabled: Boolean) : AdvancedSettingsEvents
|
||||||
data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents
|
data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents
|
||||||
data class SetCustomElementCallBaseUrl(val baseUrl: String?) : AdvancedSettingsEvents
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,25 +17,16 @@
|
||||||
package io.element.android.features.preferences.impl.advanced
|
package io.element.android.features.preferences.impl.advanced
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import io.element.android.appconfig.ElementCallConfig
|
|
||||||
import io.element.android.features.preferences.api.store.PreferencesStore
|
import io.element.android.features.preferences.api.store.PreferencesStore
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.net.URL
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AdvancedSettingsPresenter @Inject constructor(
|
class AdvancedSettingsPresenter @Inject constructor(
|
||||||
private val preferencesStore: PreferencesStore,
|
private val preferencesStore: PreferencesStore,
|
||||||
private val featureFlagService: FeatureFlagService,
|
|
||||||
) : Presenter<AdvancedSettingsState> {
|
) : Presenter<AdvancedSettingsState> {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -47,14 +38,6 @@ class AdvancedSettingsPresenter @Inject constructor(
|
||||||
val isDeveloperModeEnabled by preferencesStore
|
val isDeveloperModeEnabled by preferencesStore
|
||||||
.isDeveloperModeEnabledFlow()
|
.isDeveloperModeEnabledFlow()
|
||||||
.collectAsState(initial = false)
|
.collectAsState(initial = false)
|
||||||
val customElementCallBaseUrl by preferencesStore
|
|
||||||
.getCustomElementCallBaseUrlFlow()
|
|
||||||
.collectAsState(initial = null)
|
|
||||||
|
|
||||||
var canDisplayElementCallSettings by remember { mutableStateOf(false) }
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
canDisplayElementCallSettings = featureFlagService.isFeatureEnabled(FeatureFlags.InRoomCalls)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handleEvents(event: AdvancedSettingsEvents) {
|
fun handleEvents(event: AdvancedSettingsEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
|
|
@ -64,34 +47,13 @@ class AdvancedSettingsPresenter @Inject constructor(
|
||||||
is AdvancedSettingsEvents.SetDeveloperModeEnabled -> localCoroutineScope.launch {
|
is AdvancedSettingsEvents.SetDeveloperModeEnabled -> localCoroutineScope.launch {
|
||||||
preferencesStore.setDeveloperModeEnabled(event.enabled)
|
preferencesStore.setDeveloperModeEnabled(event.enabled)
|
||||||
}
|
}
|
||||||
is AdvancedSettingsEvents.SetCustomElementCallBaseUrl -> localCoroutineScope.launch {
|
|
||||||
// If the URL is either empty or the default one, we want to save 'null' to remove the custom URL
|
|
||||||
val urlToSave = event.baseUrl.takeIf { !it.isNullOrEmpty() && it != ElementCallConfig.DEFAULT_BASE_URL }
|
|
||||||
preferencesStore.setCustomElementCallBaseUrl(urlToSave)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return AdvancedSettingsState(
|
return AdvancedSettingsState(
|
||||||
isRichTextEditorEnabled = isRichTextEditorEnabled,
|
isRichTextEditorEnabled = isRichTextEditorEnabled,
|
||||||
isDeveloperModeEnabled = isDeveloperModeEnabled,
|
isDeveloperModeEnabled = isDeveloperModeEnabled,
|
||||||
customElementCallBaseUrlState = if (canDisplayElementCallSettings) {
|
|
||||||
CustomElementCallBaseUrlState(
|
|
||||||
baseUrl = customElementCallBaseUrl,
|
|
||||||
defaultUrl = ElementCallConfig.DEFAULT_BASE_URL,
|
|
||||||
validator = ::customElementCallUrlValidator,
|
|
||||||
)
|
|
||||||
} else null,
|
|
||||||
eventSink = { handleEvents(it) }
|
eventSink = { handleEvents(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun customElementCallUrlValidator(url: String?): Boolean {
|
|
||||||
return runCatching {
|
|
||||||
if (url.isNullOrEmpty()) return@runCatching
|
|
||||||
val parsedUrl = URL(url)
|
|
||||||
if (parsedUrl.protocol !in listOf("http", "https")) error("Incorrect protocol")
|
|
||||||
if (parsedUrl.host.isNullOrBlank()) error("Missing host")
|
|
||||||
}.isSuccess
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,5 @@ package io.element.android.features.preferences.impl.advanced
|
||||||
data class AdvancedSettingsState(
|
data class AdvancedSettingsState(
|
||||||
val isRichTextEditorEnabled: Boolean,
|
val isRichTextEditorEnabled: Boolean,
|
||||||
val isDeveloperModeEnabled: Boolean,
|
val isDeveloperModeEnabled: Boolean,
|
||||||
val customElementCallBaseUrlState: CustomElementCallBaseUrlState?,
|
|
||||||
val eventSink: (AdvancedSettingsEvents) -> Unit
|
val eventSink: (AdvancedSettingsEvents) -> Unit
|
||||||
)
|
)
|
||||||
|
|
||||||
data class CustomElementCallBaseUrlState(
|
|
||||||
val baseUrl: String?,
|
|
||||||
val defaultUrl: String,
|
|
||||||
val validator: (String?) -> Boolean,
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -24,17 +24,14 @@ open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSett
|
||||||
aAdvancedSettingsState(),
|
aAdvancedSettingsState(),
|
||||||
aAdvancedSettingsState(isRichTextEditorEnabled = true),
|
aAdvancedSettingsState(isRichTextEditorEnabled = true),
|
||||||
aAdvancedSettingsState(isDeveloperModeEnabled = true),
|
aAdvancedSettingsState(isDeveloperModeEnabled = true),
|
||||||
aAdvancedSettingsState(customElementCallBaseUrl = "https://call.element.io"),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aAdvancedSettingsState(
|
fun aAdvancedSettingsState(
|
||||||
isRichTextEditorEnabled: Boolean = false,
|
isRichTextEditorEnabled: Boolean = false,
|
||||||
isDeveloperModeEnabled: Boolean = false,
|
isDeveloperModeEnabled: Boolean = false,
|
||||||
customElementCallBaseUrl: String? = null,
|
|
||||||
) = AdvancedSettingsState(
|
) = AdvancedSettingsState(
|
||||||
isRichTextEditorEnabled = isRichTextEditorEnabled,
|
isRichTextEditorEnabled = isRichTextEditorEnabled,
|
||||||
isDeveloperModeEnabled = isDeveloperModeEnabled,
|
isDeveloperModeEnabled = isDeveloperModeEnabled,
|
||||||
customElementCallBaseUrlState = customElementCallBaseUrl?.let { CustomElementCallBaseUrlState(it, "https://call.element.io") { true } },
|
|
||||||
eventSink = {}
|
eventSink = {}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,13 @@
|
||||||
|
|
||||||
package io.element.android.features.preferences.impl.advanced
|
package io.element.android.features.preferences.impl.advanced
|
||||||
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import io.element.android.features.preferences.impl.R
|
import io.element.android.features.preferences.impl.R
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
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.PreferenceSwitch
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceTextField
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -36,11 +33,6 @@ fun AdvancedSettingsView(
|
||||||
onBackPressed: () -> Unit,
|
onBackPressed: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
fun isUsingDefaultUrl(value: String?): Boolean {
|
|
||||||
val defaultUrl = state.customElementCallBaseUrlState?.defaultUrl ?: return false
|
|
||||||
return value.isNullOrEmpty() || value == defaultUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
PreferencePage(
|
PreferencePage(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onBackPressed = onBackPressed,
|
onBackPressed = onBackPressed,
|
||||||
|
|
@ -58,23 +50,6 @@ fun AdvancedSettingsView(
|
||||||
isChecked = state.isDeveloperModeEnabled,
|
isChecked = state.isDeveloperModeEnabled,
|
||||||
onCheckedChange = { state.eventSink(AdvancedSettingsEvents.SetDeveloperModeEnabled(it)) },
|
onCheckedChange = { state.eventSink(AdvancedSettingsEvents.SetDeveloperModeEnabled(it)) },
|
||||||
)
|
)
|
||||||
state.customElementCallBaseUrlState?.let { callUrlState ->
|
|
||||||
val supportingText = if (isUsingDefaultUrl(callUrlState.baseUrl)) {
|
|
||||||
stringResource(R.string.screen_advanced_settings_element_call_base_url_description)
|
|
||||||
} else {
|
|
||||||
callUrlState.baseUrl
|
|
||||||
}
|
|
||||||
PreferenceTextField(
|
|
||||||
headline = stringResource(R.string.screen_advanced_settings_element_call_base_url),
|
|
||||||
value = callUrlState.baseUrl ?: callUrlState.defaultUrl,
|
|
||||||
supportingText = supportingText,
|
|
||||||
validation = callUrlState.validator,
|
|
||||||
onValidationErrorMessage = stringResource(R.string.screen_advanced_settings_element_call_base_url_validation_error),
|
|
||||||
displayValue = { value -> !isUsingDefaultUrl(value) },
|
|
||||||
keyboardOptions = KeyboardOptions.Default.copy(autoCorrect = false, keyboardType = KeyboardType.Uri),
|
|
||||||
onChange = { state.eventSink(AdvancedSettingsEvents.SetCustomElementCallBaseUrl(it)) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,5 +20,6 @@ import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
|
||||||
|
|
||||||
sealed interface DeveloperSettingsEvents {
|
sealed interface DeveloperSettingsEvents {
|
||||||
data class UpdateEnabledFeature(val feature: FeatureUiModel, val isEnabled: Boolean) : DeveloperSettingsEvents
|
data class UpdateEnabledFeature(val feature: FeatureUiModel, val isEnabled: Boolean) : DeveloperSettingsEvents
|
||||||
|
data class SetCustomElementCallBaseUrl(val baseUrl: String?) : DeveloperSettingsEvents
|
||||||
data object ClearCache: DeveloperSettingsEvents
|
data object ClearCache: DeveloperSettingsEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,16 @@ package io.element.android.features.preferences.impl.developer
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.key
|
import androidx.compose.runtime.key
|
||||||
import androidx.compose.runtime.mutableStateMapOf
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||||
|
import io.element.android.appconfig.ElementCallConfig
|
||||||
|
import io.element.android.features.preferences.api.store.PreferencesStore
|
||||||
import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
|
import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
|
||||||
import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase
|
import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase
|
||||||
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter
|
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter
|
||||||
|
|
@ -39,6 +43,7 @@ import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.net.URL
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class DeveloperSettingsPresenter @Inject constructor(
|
class DeveloperSettingsPresenter @Inject constructor(
|
||||||
|
|
@ -46,6 +51,7 @@ class DeveloperSettingsPresenter @Inject constructor(
|
||||||
private val computeCacheSizeUseCase: ComputeCacheSizeUseCase,
|
private val computeCacheSizeUseCase: ComputeCacheSizeUseCase,
|
||||||
private val clearCacheUseCase: ClearCacheUseCase,
|
private val clearCacheUseCase: ClearCacheUseCase,
|
||||||
private val rageshakePresenter: RageshakePreferencesPresenter,
|
private val rageshakePresenter: RageshakePreferencesPresenter,
|
||||||
|
private val preferencesStore: PreferencesStore,
|
||||||
) : Presenter<DeveloperSettingsState> {
|
) : Presenter<DeveloperSettingsState> {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -64,8 +70,12 @@ class DeveloperSettingsPresenter @Inject constructor(
|
||||||
val clearCacheAction = remember {
|
val clearCacheAction = remember {
|
||||||
mutableStateOf<Async<Unit>>(Async.Uninitialized)
|
mutableStateOf<Async<Unit>>(Async.Uninitialized)
|
||||||
}
|
}
|
||||||
|
val customElementCallBaseUrl by preferencesStore
|
||||||
|
.getCustomElementCallBaseUrlFlow()
|
||||||
|
.collectAsState(initial = null)
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
FeatureFlags.values().forEach { feature ->
|
FeatureFlags.entries.forEach { feature ->
|
||||||
features[feature.key] = feature
|
features[feature.key] = feature
|
||||||
enabledFeatures[feature.key] = featureFlagService.isFeatureEnabled(feature)
|
enabledFeatures[feature.key] = featureFlagService.isFeatureEnabled(feature)
|
||||||
}
|
}
|
||||||
|
|
@ -86,6 +96,11 @@ class DeveloperSettingsPresenter @Inject constructor(
|
||||||
event.isEnabled,
|
event.isEnabled,
|
||||||
triggerClearCache = { handleEvents(DeveloperSettingsEvents.ClearCache) }
|
triggerClearCache = { handleEvents(DeveloperSettingsEvents.ClearCache) }
|
||||||
)
|
)
|
||||||
|
is DeveloperSettingsEvents.SetCustomElementCallBaseUrl -> coroutineScope.launch {
|
||||||
|
// If the URL is either empty or the default one, we want to save 'null' to remove the custom URL
|
||||||
|
val urlToSave = event.baseUrl.takeIf { !it.isNullOrEmpty() && it != ElementCallConfig.DEFAULT_BASE_URL }
|
||||||
|
preferencesStore.setCustomElementCallBaseUrl(urlToSave)
|
||||||
|
}
|
||||||
DeveloperSettingsEvents.ClearCache -> coroutineScope.clearCache(clearCacheAction)
|
DeveloperSettingsEvents.ClearCache -> coroutineScope.clearCache(clearCacheAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -95,6 +110,11 @@ class DeveloperSettingsPresenter @Inject constructor(
|
||||||
cacheSize = cacheSize.value,
|
cacheSize = cacheSize.value,
|
||||||
clearCacheAction = clearCacheAction.value,
|
clearCacheAction = clearCacheAction.value,
|
||||||
rageshakeState = rageshakeState,
|
rageshakeState = rageshakeState,
|
||||||
|
customElementCallBaseUrlState = CustomElementCallBaseUrlState(
|
||||||
|
baseUrl = customElementCallBaseUrl,
|
||||||
|
defaultUrl = ElementCallConfig.DEFAULT_BASE_URL,
|
||||||
|
validator = ::customElementCallUrlValidator,
|
||||||
|
),
|
||||||
eventSink = ::handleEvents
|
eventSink = ::handleEvents
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -145,5 +165,14 @@ class DeveloperSettingsPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun customElementCallUrlValidator(url: String?): Boolean {
|
||||||
|
return runCatching {
|
||||||
|
if (url.isNullOrEmpty()) return@runCatching
|
||||||
|
val parsedUrl = URL(url)
|
||||||
|
if (parsedUrl.protocol !in listOf("http", "https")) error("Incorrect protocol")
|
||||||
|
if (parsedUrl.host.isNullOrBlank()) error("Missing host")
|
||||||
|
}.isSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,17 @@ import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
|
import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
data class DeveloperSettingsState constructor(
|
data class DeveloperSettingsState(
|
||||||
val features: ImmutableList<FeatureUiModel>,
|
val features: ImmutableList<FeatureUiModel>,
|
||||||
val cacheSize: Async<String>,
|
val cacheSize: Async<String>,
|
||||||
val rageshakeState: RageshakePreferencesState,
|
val rageshakeState: RageshakePreferencesState,
|
||||||
val clearCacheAction: Async<Unit>,
|
val clearCacheAction: Async<Unit>,
|
||||||
|
val customElementCallBaseUrlState: CustomElementCallBaseUrlState,
|
||||||
val eventSink: (DeveloperSettingsEvents) -> Unit
|
val eventSink: (DeveloperSettingsEvents) -> Unit
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class CustomElementCallBaseUrlState(
|
||||||
|
val baseUrl: String?,
|
||||||
|
val defaultUrl: String,
|
||||||
|
val validator: (String?) -> Boolean,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,13 @@ open class DeveloperSettingsStateProvider : PreviewParameterProvider<DeveloperSe
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aDeveloperSettingsState(),
|
aDeveloperSettingsState(),
|
||||||
aDeveloperSettingsState().copy(clearCacheAction = Async.Loading()),
|
aDeveloperSettingsState().copy(clearCacheAction = Async.Loading()),
|
||||||
|
aDeveloperSettingsState().copy(
|
||||||
|
customElementCallBaseUrlState = CustomElementCallBaseUrlState(
|
||||||
|
baseUrl = "https://call.element.ahoy",
|
||||||
|
defaultUrl = "https://call.element.io",
|
||||||
|
validator = { true }
|
||||||
|
)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,5 +41,6 @@ fun aDeveloperSettingsState() = DeveloperSettingsState(
|
||||||
rageshakeState = aRageshakePreferencesState(),
|
rageshakeState = aRageshakePreferencesState(),
|
||||||
cacheSize = Async.Success("1.2 MB"),
|
cacheSize = Async.Success("1.2 MB"),
|
||||||
clearCacheAction = Async.Uninitialized,
|
clearCacheAction = Async.Uninitialized,
|
||||||
|
customElementCallBaseUrlState = CustomElementCallBaseUrlState(baseUrl = null, defaultUrl = "https://call.element.io", validator = { true }),
|
||||||
eventSink = {}
|
eventSink = {}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,20 @@
|
||||||
|
|
||||||
package io.element.android.features.preferences.impl.developer
|
package io.element.android.features.preferences.impl.developer
|
||||||
|
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import io.element.android.features.preferences.impl.R
|
||||||
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesView
|
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.PreferenceCategory
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
|
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
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
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.featureflag.ui.FeatureListView
|
import io.element.android.libraries.featureflag.ui.FeatureListView
|
||||||
import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
|
import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -47,6 +51,7 @@ fun DeveloperSettingsView(
|
||||||
PreferenceCategory(title = "Feature flags") {
|
PreferenceCategory(title = "Feature flags") {
|
||||||
FeatureListContent(state)
|
FeatureListContent(state)
|
||||||
}
|
}
|
||||||
|
ElementCallCategory(state = state)
|
||||||
PreferenceCategory(title = "Rust SDK") {
|
PreferenceCategory(title = "Rust SDK") {
|
||||||
PreferenceText(
|
PreferenceText(
|
||||||
title = "Configure tracing",
|
title = "Configure tracing",
|
||||||
|
|
@ -84,6 +89,34 @@ fun DeveloperSettingsView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ElementCallCategory(
|
||||||
|
state: DeveloperSettingsState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
PreferenceCategory(modifier = modifier, title = "Element Call", showDivider = true) {
|
||||||
|
val callUrlState = state.customElementCallBaseUrlState
|
||||||
|
fun isUsingDefaultUrl(value: String?): Boolean {
|
||||||
|
return value.isNullOrEmpty() || value == callUrlState.defaultUrl
|
||||||
|
}
|
||||||
|
val supportingText = if (isUsingDefaultUrl(callUrlState.baseUrl)) {
|
||||||
|
stringResource(R.string.screen_advanced_settings_element_call_base_url_description)
|
||||||
|
} else {
|
||||||
|
callUrlState.baseUrl
|
||||||
|
}
|
||||||
|
PreferenceTextField(
|
||||||
|
headline = stringResource(R.string.screen_advanced_settings_element_call_base_url),
|
||||||
|
value = callUrlState.baseUrl ?: callUrlState.defaultUrl,
|
||||||
|
supportingText = supportingText,
|
||||||
|
validation = callUrlState.validator,
|
||||||
|
onValidationErrorMessage = stringResource(R.string.screen_advanced_settings_element_call_base_url_validation_error),
|
||||||
|
displayValue = { value -> !isUsingDefaultUrl(value) },
|
||||||
|
keyboardOptions = KeyboardOptions.Default.copy(autoCorrect = false, keyboardType = KeyboardType.Uri),
|
||||||
|
onChange = { state.eventSink(DeveloperSettingsEvents.SetCustomElementCallBaseUrl(it)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun FeatureListContent(
|
private fun FeatureListContent(
|
||||||
state: DeveloperSettingsState,
|
state: DeveloperSettingsState,
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,6 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
|
||||||
import io.element.android.libraries.featureflag.test.InMemoryPreferencesStore
|
import io.element.android.libraries.featureflag.test.InMemoryPreferencesStore
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.awaitLastSequentialItem
|
import io.element.android.tests.testutils.awaitLastSequentialItem
|
||||||
|
|
@ -37,23 +35,20 @@ class AdvancedSettingsPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state`() = runTest {
|
fun `present - initial state`() = runTest {
|
||||||
val store = InMemoryPreferencesStore()
|
val store = InMemoryPreferencesStore()
|
||||||
val featureFlagService = FakeFeatureFlagService()
|
val presenter = AdvancedSettingsPresenter(store)
|
||||||
val presenter = AdvancedSettingsPresenter(store, featureFlagService)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitLastSequentialItem()
|
val initialState = awaitLastSequentialItem()
|
||||||
assertThat(initialState.isDeveloperModeEnabled).isFalse()
|
assertThat(initialState.isDeveloperModeEnabled).isFalse()
|
||||||
assertThat(initialState.isRichTextEditorEnabled).isFalse()
|
assertThat(initialState.isRichTextEditorEnabled).isFalse()
|
||||||
assertThat(initialState.customElementCallBaseUrlState?.baseUrl).isNull()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - developer mode on off`() = runTest {
|
fun `present - developer mode on off`() = runTest {
|
||||||
val store = InMemoryPreferencesStore()
|
val store = InMemoryPreferencesStore()
|
||||||
val featureFlagService = FakeFeatureFlagService()
|
val presenter = AdvancedSettingsPresenter(store)
|
||||||
val presenter = AdvancedSettingsPresenter(store, featureFlagService)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -69,8 +64,7 @@ class AdvancedSettingsPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - rich text editor on off`() = runTest {
|
fun `present - rich text editor on off`() = runTest {
|
||||||
val store = InMemoryPreferencesStore()
|
val store = InMemoryPreferencesStore()
|
||||||
val featureFlagService = FakeFeatureFlagService()
|
val presenter = AdvancedSettingsPresenter(store)
|
||||||
val presenter = AdvancedSettingsPresenter(store, featureFlagService)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -82,56 +76,4 @@ class AdvancedSettingsPresenterTest {
|
||||||
assertThat(awaitItem().isRichTextEditorEnabled).isFalse()
|
assertThat(awaitItem().isRichTextEditorEnabled).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - custom element call url state is null if the feature flag is disabled`() = runTest {
|
|
||||||
val store = InMemoryPreferencesStore()
|
|
||||||
val featureFlagService = FakeFeatureFlagService().apply {
|
|
||||||
setFeatureEnabled(FeatureFlags.InRoomCalls, false)
|
|
||||||
}
|
|
||||||
val presenter = AdvancedSettingsPresenter(store, featureFlagService)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
val initialState = awaitLastSequentialItem()
|
|
||||||
assertThat(initialState.customElementCallBaseUrlState).isNull()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - custom element call base url`() = runTest {
|
|
||||||
val store = InMemoryPreferencesStore()
|
|
||||||
val featureFlagService = FakeFeatureFlagService(initialState = hashMapOf(FeatureFlags.InRoomCalls.key to true))
|
|
||||||
val presenter = AdvancedSettingsPresenter(store, featureFlagService)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
val initialState = awaitLastSequentialItem()
|
|
||||||
assertThat(initialState.customElementCallBaseUrlState).isNotNull()
|
|
||||||
assertThat(initialState.customElementCallBaseUrlState?.baseUrl).isNull()
|
|
||||||
|
|
||||||
initialState.eventSink(AdvancedSettingsEvents.SetCustomElementCallBaseUrl("https://call.element.ahoy"))
|
|
||||||
val updatedItem = awaitItem()
|
|
||||||
assertThat(updatedItem.customElementCallBaseUrlState?.baseUrl).isEqualTo("https://call.element.ahoy")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - custom element call base url validator needs at least an HTTP scheme and host`() = runTest {
|
|
||||||
val store = InMemoryPreferencesStore()
|
|
||||||
val featureFlagService = FakeFeatureFlagService().apply {
|
|
||||||
setFeatureEnabled(FeatureFlags.InRoomCalls, true)
|
|
||||||
}
|
|
||||||
val presenter = AdvancedSettingsPresenter(store, featureFlagService)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
val urlValidator = awaitLastSequentialItem().customElementCallBaseUrlState!!.validator
|
|
||||||
assertThat(urlValidator("")).isTrue() // We allow empty string to clear the value and use the default one
|
|
||||||
assertThat(urlValidator("test")).isFalse()
|
|
||||||
assertThat(urlValidator("http://")).isFalse()
|
|
||||||
assertThat(urlValidator("geo://test")).isFalse()
|
|
||||||
assertThat(urlValidator("https://call.element.io")).isTrue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.appconfig.ElementCallConfig
|
||||||
import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase
|
import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase
|
||||||
import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase
|
import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase
|
||||||
import io.element.android.features.rageshake.impl.preferences.DefaultRageshakePreferencesPresenter
|
import io.element.android.features.rageshake.impl.preferences.DefaultRageshakePreferencesPresenter
|
||||||
|
|
@ -28,7 +29,9 @@ import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataSto
|
||||||
import io.element.android.libraries.architecture.Async
|
import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
|
import io.element.android.libraries.featureflag.test.InMemoryPreferencesStore
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
|
import io.element.android.tests.testutils.awaitLastSequentialItem
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -40,13 +43,7 @@ class DeveloperSettingsPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - ensures initial state is correct`() = runTest {
|
fun `present - ensures initial state is correct`() = runTest {
|
||||||
val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore())
|
val presenter = createDeveloperSettingsPresenter()
|
||||||
val presenter = DeveloperSettingsPresenter(
|
|
||||||
FakeFeatureFlagService(),
|
|
||||||
FakeComputeCacheSizeUseCase(),
|
|
||||||
FakeClearCacheUseCase(),
|
|
||||||
rageshakePresenter
|
|
||||||
)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -54,6 +51,8 @@ class DeveloperSettingsPresenterTest {
|
||||||
assertThat(initialState.features).isEmpty()
|
assertThat(initialState.features).isEmpty()
|
||||||
assertThat(initialState.clearCacheAction).isEqualTo(Async.Uninitialized)
|
assertThat(initialState.clearCacheAction).isEqualTo(Async.Uninitialized)
|
||||||
assertThat(initialState.cacheSize).isEqualTo(Async.Uninitialized)
|
assertThat(initialState.cacheSize).isEqualTo(Async.Uninitialized)
|
||||||
|
assertThat(initialState.customElementCallBaseUrlState).isNotNull()
|
||||||
|
assertThat(initialState.customElementCallBaseUrlState.baseUrl).isNull()
|
||||||
val loadedState = awaitItem()
|
val loadedState = awaitItem()
|
||||||
assertThat(loadedState.rageshakeState.isEnabled).isFalse()
|
assertThat(loadedState.rageshakeState.isEnabled).isFalse()
|
||||||
assertThat(loadedState.rageshakeState.isSupported).isTrue()
|
assertThat(loadedState.rageshakeState.isSupported).isTrue()
|
||||||
|
|
@ -64,32 +63,20 @@ class DeveloperSettingsPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - ensures feature list is loaded`() = runTest {
|
fun `present - ensures feature list is loaded`() = runTest {
|
||||||
val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore())
|
val presenter = createDeveloperSettingsPresenter()
|
||||||
val presenter = DeveloperSettingsPresenter(
|
|
||||||
FakeFeatureFlagService(),
|
|
||||||
FakeComputeCacheSizeUseCase(),
|
|
||||||
FakeClearCacheUseCase(),
|
|
||||||
rageshakePresenter,
|
|
||||||
)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val state = awaitItem()
|
val state = awaitItem()
|
||||||
assertThat(state.features).hasSize(FeatureFlags.values().size)
|
assertThat(state.features).hasSize(FeatureFlags.entries.size)
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - ensures state is updated when enabled feature event is triggered`() = runTest {
|
fun `present - ensures state is updated when enabled feature event is triggered`() = runTest {
|
||||||
val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore())
|
val presenter = createDeveloperSettingsPresenter()
|
||||||
val presenter = DeveloperSettingsPresenter(
|
|
||||||
FakeFeatureFlagService(),
|
|
||||||
FakeComputeCacheSizeUseCase(),
|
|
||||||
FakeClearCacheUseCase(),
|
|
||||||
rageshakePresenter,
|
|
||||||
)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -109,12 +96,7 @@ class DeveloperSettingsPresenterTest {
|
||||||
fun `present - clear cache`() = runTest {
|
fun `present - clear cache`() = runTest {
|
||||||
val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore())
|
val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore())
|
||||||
val clearCacheUseCase = FakeClearCacheUseCase()
|
val clearCacheUseCase = FakeClearCacheUseCase()
|
||||||
val presenter = DeveloperSettingsPresenter(
|
val presenter = createDeveloperSettingsPresenter(clearCacheUseCase = clearCacheUseCase, rageshakePresenter = rageshakePresenter)
|
||||||
FakeFeatureFlagService(),
|
|
||||||
FakeComputeCacheSizeUseCase(),
|
|
||||||
clearCacheUseCase,
|
|
||||||
rageshakePresenter,
|
|
||||||
)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -130,4 +112,52 @@ class DeveloperSettingsPresenterTest {
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - custom element call base url`() = runTest {
|
||||||
|
val preferencesStore = InMemoryPreferencesStore()
|
||||||
|
val presenter = createDeveloperSettingsPresenter(preferencesStore = preferencesStore)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
skipItems(1)
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.customElementCallBaseUrlState.baseUrl).isNull()
|
||||||
|
initialState.eventSink(DeveloperSettingsEvents.SetCustomElementCallBaseUrl("https://call.element.ahoy"))
|
||||||
|
val updatedItem = awaitItem()
|
||||||
|
assertThat(updatedItem.customElementCallBaseUrlState.baseUrl).isEqualTo("https://call.element.ahoy")
|
||||||
|
assertThat(updatedItem.customElementCallBaseUrlState.defaultUrl).isEqualTo(ElementCallConfig.DEFAULT_BASE_URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - custom element call base url validator needs at least an HTTP scheme and host`() = runTest {
|
||||||
|
val presenter = createDeveloperSettingsPresenter()
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val urlValidator = awaitLastSequentialItem().customElementCallBaseUrlState.validator
|
||||||
|
assertThat(urlValidator("")).isTrue() // We allow empty string to clear the value and use the default one
|
||||||
|
assertThat(urlValidator("test")).isFalse()
|
||||||
|
assertThat(urlValidator("http://")).isFalse()
|
||||||
|
assertThat(urlValidator("geo://test")).isFalse()
|
||||||
|
assertThat(urlValidator("https://call.element.io")).isTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDeveloperSettingsPresenter(
|
||||||
|
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(),
|
||||||
|
cacheSizeUseCase: FakeComputeCacheSizeUseCase = FakeComputeCacheSizeUseCase(),
|
||||||
|
clearCacheUseCase: FakeClearCacheUseCase = FakeClearCacheUseCase(),
|
||||||
|
rageshakePresenter: DefaultRageshakePreferencesPresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore()),
|
||||||
|
preferencesStore: InMemoryPreferencesStore = InMemoryPreferencesStore(),
|
||||||
|
): DeveloperSettingsPresenter {
|
||||||
|
return DeveloperSettingsPresenter(
|
||||||
|
featureFlagService = featureFlagService,
|
||||||
|
computeCacheSizeUseCase = cacheSizeUseCase,
|
||||||
|
clearCacheUseCase = clearCacheUseCase,
|
||||||
|
rageshakePresenter = rageshakePresenter,
|
||||||
|
preferencesStore = preferencesStore,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,12 +55,6 @@ enum class FeatureFlags(
|
||||||
description = "Allow user to lock/unlock the app with a pin code or biometrics",
|
description = "Allow user to lock/unlock the app with a pin code or biometrics",
|
||||||
defaultValue = true,
|
defaultValue = true,
|
||||||
),
|
),
|
||||||
InRoomCalls(
|
|
||||||
key = "feature.elementcall",
|
|
||||||
title = "Element call in rooms",
|
|
||||||
description = "Allow user to start or join a call in a room",
|
|
||||||
defaultValue = true,
|
|
||||||
),
|
|
||||||
Mentions(
|
Mentions(
|
||||||
key = "feature.mentions",
|
key = "feature.mentions",
|
||||||
title = "Mentions",
|
title = "Mentions",
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ class StaticFeatureFlagProvider @Inject constructor() :
|
||||||
FeatureFlags.NotificationSettings -> true
|
FeatureFlags.NotificationSettings -> true
|
||||||
FeatureFlags.VoiceMessages -> true
|
FeatureFlags.VoiceMessages -> true
|
||||||
FeatureFlags.PinUnlock -> true
|
FeatureFlags.PinUnlock -> true
|
||||||
FeatureFlags.InRoomCalls -> true
|
|
||||||
FeatureFlags.Mentions -> false
|
FeatureFlags.Mentions -> false
|
||||||
FeatureFlags.SecureStorage -> false
|
FeatureFlags.SecureStorage -> false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:a4e1af15c571d1f087005849b627d79387f8f5557bbc4233768bb3c2d940d628
|
|
||||||
size 48510
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:aa25ebf20fe62af56a548c3e962ae2e76e6e8e1b7e685d021306b733613e49eb
|
|
||||||
size 45462
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c221b092112d787bdc0f9db9e9da3afe75d6b5754b98db119df3c63514da03b4
|
oid sha256:b040737c81fb04596312a207e58c75580e48fdd70c1f5ee6abd3c73a846c0337
|
||||||
size 53808
|
size 58489
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c221b092112d787bdc0f9db9e9da3afe75d6b5754b98db119df3c63514da03b4
|
oid sha256:b040737c81fb04596312a207e58c75580e48fdd70c1f5ee6abd3c73a846c0337
|
||||||
size 53808
|
size 58489
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:5b01d0d26eddb4b6edd900f0f17568d76108e4be97c979d63c5dcca1ca0ed83e
|
||||||
|
size 56942
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:419ba4098c8b77a6e6ea2af72eeb5a614a9982d04e003fedfcdee92772484eb1
|
oid sha256:491beebfede0316e804b818c65794bd0c8f8125fc94f4a89f8e1b3ded5afbf3a
|
||||||
size 48839
|
size 53597
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:419ba4098c8b77a6e6ea2af72eeb5a614a9982d04e003fedfcdee92772484eb1
|
oid sha256:491beebfede0316e804b818c65794bd0c8f8125fc94f4a89f8e1b3ded5afbf3a
|
||||||
size 48839
|
size 53597
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:16b24b31812e18fbf54568f2bae63c0827dc30e73d7924cdab6387df0c2ad41b
|
||||||
|
size 52062
|
||||||
Loading…
Add table
Add a link
Reference in a new issue