diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt index 4394f5436e..b2206b81c9 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorOverrides.kt @@ -22,13 +22,17 @@ import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.preferencesDataStore +import im.vector.app.features.HomeserverCapabilitiesOverride import im.vector.app.features.VectorOverrides +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.extensions.orFalse private val Context.dataStore: DataStore by preferencesDataStore(name = "vector_overrides") private val keyForceDialPadDisplay = booleanPreferencesKey("force_dial_pad_display") private val keyForceLoginFallback = booleanPreferencesKey("force_login_fallback") +private val forceCanChangeDisplayName = booleanPreferencesKey("force_can_change_display_name") +private val forceCanChangeAvatar = booleanPreferencesKey("force_can_change_avatar") class DebugVectorOverrides(private val context: Context) : VectorOverrides { @@ -40,6 +44,13 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides { preferences[keyForceLoginFallback].orFalse() } + override val forceHomeserverCapabilities = context.dataStore.data.map { preferences -> + HomeserverCapabilitiesOverride( + canChangeDisplayName = preferences[forceCanChangeDisplayName], + canChangeAvatar = preferences[forceCanChangeAvatar] + ) + } + suspend fun setForceDialPadDisplay(force: Boolean) { context.dataStore.edit { settings -> settings[keyForceDialPadDisplay] = force @@ -51,4 +62,18 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides { settings[keyForceLoginFallback] = force } } + + suspend fun updateHomeserverCapabilities(block: HomeserverCapabilitiesOverride.() -> HomeserverCapabilitiesOverride) { + val capabilitiesOverride = block(forceHomeserverCapabilities.firstOrNull() ?: HomeserverCapabilitiesOverride(null, null)) + context.dataStore.edit { settings -> + when (capabilitiesOverride.canChangeDisplayName) { + null -> settings.remove(forceCanChangeDisplayName) + else -> settings[forceCanChangeDisplayName] = capabilitiesOverride.canChangeDisplayName + } + when (capabilitiesOverride.canChangeAvatar) { + null -> settings.remove(forceCanChangeAvatar) + else -> settings[forceCanChangeAvatar] = capabilitiesOverride.canChangeAvatar + } + } + } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt index b54d776901..38253fe7c2 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsFragment.kt @@ -50,6 +50,12 @@ class DebugPrivateSettingsFragment : VectorBaseFragment + viewModel.handle(DebugPrivateSettingsViewActions.SetDisplayNameCapabilityOverride(option)) + } + views.forceChangeAvatarCapability.bind(it.homeserverCapabilityOverrides.avatar) { option -> + viewModel.handle(DebugPrivateSettingsViewActions.SetAvatarCapabilityOverride(option)) + } views.forceLoginFallback.isChecked = it.forceLoginFallback } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt index 1c76cf6fb2..5dea3dce64 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewActions.kt @@ -18,7 +18,9 @@ package im.vector.app.features.debug.settings import im.vector.app.core.platform.VectorViewModelAction -sealed class DebugPrivateSettingsViewActions : VectorViewModelAction { - data class SetDialPadVisibility(val force: Boolean) : DebugPrivateSettingsViewActions() - data class SetForceLoginFallbackEnabled(val force: Boolean) : DebugPrivateSettingsViewActions() +sealed interface DebugPrivateSettingsViewActions : VectorViewModelAction { + data class SetDialPadVisibility(val force: Boolean) : DebugPrivateSettingsViewActions + data class SetForceLoginFallbackEnabled(val force: Boolean) : DebugPrivateSettingsViewActions + data class SetDisplayNameCapabilityOverride(val option: BooleanHomeserverCapabilitiesOverride?) : DebugPrivateSettingsViewActions + data class SetAvatarCapabilityOverride(val option: BooleanHomeserverCapabilitiesOverride?) : DebugPrivateSettingsViewActions } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt index 8d040d4773..4d9c72168c 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt @@ -22,9 +22,12 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.debug.features.DebugVectorOverrides +import im.vector.app.features.debug.settings.DebugPrivateSettingsViewActions.SetAvatarCapabilityOverride +import im.vector.app.features.debug.settings.DebugPrivateSettingsViewActions.SetDisplayNameCapabilityOverride import kotlinx.coroutines.launch class DebugPrivateSettingsViewModel @AssistedInject constructor( @@ -40,10 +43,10 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() init { - observeVectorDataStore() + observeVectorOverrides() } - private fun observeVectorDataStore() { + private fun observeVectorOverrides() { debugVectorOverrides.forceDialPad.setOnEach { copy( dialPadVisible = it @@ -52,13 +55,23 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( debugVectorOverrides.forceLoginFallback.setOnEach { copy(forceLoginFallback = it) } + debugVectorOverrides.forceHomeserverCapabilities.setOnEach { + val activeDisplayNameOption = BooleanHomeserverCapabilitiesOverride.from(it.canChangeDisplayName) + val activeAvatarOption = BooleanHomeserverCapabilitiesOverride.from(it.canChangeAvatar) + copy(homeserverCapabilityOverrides = homeserverCapabilityOverrides.copy( + displayName = homeserverCapabilityOverrides.displayName.copy(activeOption = activeDisplayNameOption), + avatar = homeserverCapabilityOverrides.displayName.copy(activeOption = activeAvatarOption), + )) + } } override fun handle(action: DebugPrivateSettingsViewActions) { when (action) { is DebugPrivateSettingsViewActions.SetDialPadVisibility -> handleSetDialPadVisibility(action) is DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled -> handleSetForceLoginFallbackEnabled(action) - } + is SetDisplayNameCapabilityOverride -> handSetDisplayNameCapabilityOverride(action) + is SetAvatarCapabilityOverride -> handSetAvatarCapabilityOverride(action) + }.exhaustive } private fun handleSetDialPadVisibility(action: DebugPrivateSettingsViewActions.SetDialPadVisibility) { @@ -72,4 +85,18 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor( debugVectorOverrides.setForceLoginFallback(action.force) } } + + private fun handSetDisplayNameCapabilityOverride(action: SetDisplayNameCapabilityOverride) { + viewModelScope.launch { + val forceDisplayName = action.option.toBoolean() + debugVectorOverrides.updateHomeserverCapabilities { copy(canChangeDisplayName = forceDisplayName) } + } + } + + private fun handSetAvatarCapabilityOverride(action: SetAvatarCapabilityOverride) { + viewModelScope.launch { + val forceAvatar = action.option.toBoolean() + debugVectorOverrides.updateHomeserverCapabilities { copy(canChangeAvatar = forceAvatar) } + } + } } diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt index 7fca29af8c..749b11a744 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewState.kt @@ -17,8 +17,23 @@ package im.vector.app.features.debug.settings import com.airbnb.mvrx.MavericksState +import im.vector.app.features.debug.settings.OverrideDropdownView.OverrideDropdown data class DebugPrivateSettingsViewState( val dialPadVisible: Boolean = false, val forceLoginFallback: Boolean = false, + val homeserverCapabilityOverrides: HomeserverCapabilityOverrides = HomeserverCapabilityOverrides() ) : MavericksState + +data class HomeserverCapabilityOverrides( + val displayName: OverrideDropdown = OverrideDropdown( + label = "Override display name capability", + activeOption = null, + options = listOf(BooleanHomeserverCapabilitiesOverride.ForceEnabled, BooleanHomeserverCapabilitiesOverride.ForceDisabled) + ), + val avatar: OverrideDropdown = OverrideDropdown( + label = "Override avatar capability", + activeOption = null, + options = listOf(BooleanHomeserverCapabilitiesOverride.ForceEnabled, BooleanHomeserverCapabilitiesOverride.ForceDisabled) + ) +) diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt new file mode 100644 index 0000000000..546c9df9b7 --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.debug.settings + +import android.content.Context +import android.util.AttributeSet +import android.view.Gravity +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.widget.AppCompatSpinner +import im.vector.app.R + +class OverrideDropdownView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : LinearLayout(context, attrs) { + + private val labelView: TextView + private val optionsSpinner: AppCompatSpinner + + init { + orientation = HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + inflate(context, R.layout.view_boolean_dropdown, this) + labelView = findViewById(R.id.feature_label) + optionsSpinner = findViewById(R.id.feature_options) + } + + fun bind(feature: OverrideDropdown, listener: Listener) { + labelView.text = feature.label + + optionsSpinner.apply { + val arrayAdapter = ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item) + val options = listOf("Inactive") + feature.options.map { it.label } + arrayAdapter.addAll(options) + adapter = arrayAdapter + + feature.activeOption?.let { + setSelection(options.indexOf(it.label), false) + } + + onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + when (position) { + 0 -> listener.onOverrideSelected(option = null) + else -> listener.onOverrideSelected(feature.options[position - 1]) + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // do nothing + } + } + } + } + + fun interface Listener { + fun onOverrideSelected(option: T?) + } + + data class OverrideDropdown( + val label: String, + val options: List, + val activeOption: T?, + ) +} + +interface OverrideOption { + val label: String +} diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/PrivateSettingOverrides.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/PrivateSettingOverrides.kt new file mode 100644 index 0000000000..316e8fb901 --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/settings/PrivateSettingOverrides.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.debug.settings + +sealed interface BooleanHomeserverCapabilitiesOverride : OverrideOption { + + companion object { + fun from(value: Boolean?) = when (value) { + null -> null + true -> ForceEnabled + false -> ForceDisabled + } + } + + object ForceEnabled : BooleanHomeserverCapabilitiesOverride { + override val label = "Force enabled" + } + + object ForceDisabled : BooleanHomeserverCapabilitiesOverride { + override val label = "Force disabled" + } +} + +fun BooleanHomeserverCapabilitiesOverride?.toBoolean() = when (this) { + null -> null + BooleanHomeserverCapabilitiesOverride.ForceDisabled -> false + BooleanHomeserverCapabilitiesOverride.ForceEnabled -> true +} diff --git a/vector/src/debug/res/layout/fragment_debug_private_settings.xml b/vector/src/debug/res/layout/fragment_debug_private_settings.xml index 6760c68169..c42ad68dce 100644 --- a/vector/src/debug/res/layout/fragment_debug_private_settings.xml +++ b/vector/src/debug/res/layout/fragment_debug_private_settings.xml @@ -31,6 +31,24 @@ android:layout_height="wrap_content" android:text="Force login and registration fallback" /> + + + + diff --git a/vector/src/debug/res/layout/view_boolean_dropdown.xml b/vector/src/debug/res/layout/view_boolean_dropdown.xml new file mode 100644 index 0000000000..a231980797 --- /dev/null +++ b/vector/src/debug/res/layout/view_boolean_dropdown.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt b/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt index 4128fdbe3c..daa0d9e0bd 100644 --- a/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt +++ b/vector/src/main/java/im/vector/app/features/DefaultVectorOverrides.kt @@ -22,9 +22,16 @@ import kotlinx.coroutines.flow.flowOf interface VectorOverrides { val forceDialPad: Flow val forceLoginFallback: Flow + val forceHomeserverCapabilities: Flow? } +data class HomeserverCapabilitiesOverride( + val canChangeDisplayName: Boolean?, + val canChangeAvatar: Boolean? +) + class DefaultVectorOverrides : VectorOverrides { override val forceDialPad = flowOf(false) override val forceLoginFallback = flowOf(false) + override val forceHomeserverCapabilities: Flow? = null } diff --git a/vector/src/main/res/layout/fragment_ftue_profile_picture.xml b/vector/src/main/res/layout/fragment_ftue_profile_picture.xml index dd3b954191..0def088062 100644 --- a/vector/src/main/res/layout/fragment_ftue_profile_picture.xml +++ b/vector/src/main/res/layout/fragment_ftue_profile_picture.xml @@ -90,25 +90,6 @@ - -