adding entry points for injecting and overriding the homeserver capabilities

This commit is contained in:
Adam Brown 2022-02-24 16:41:35 +00:00
parent 50740b1449
commit 3df4f1e099
11 changed files with 260 additions and 25 deletions

View File

@ -22,13 +22,17 @@ import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore import androidx.datastore.preferences.preferencesDataStore
import im.vector.app.features.HomeserverCapabilitiesOverride
import im.vector.app.features.VectorOverrides import im.vector.app.features.VectorOverrides
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "vector_overrides") private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "vector_overrides")
private val keyForceDialPadDisplay = booleanPreferencesKey("force_dial_pad_display") private val keyForceDialPadDisplay = booleanPreferencesKey("force_dial_pad_display")
private val keyForceLoginFallback = booleanPreferencesKey("force_login_fallback") 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 { class DebugVectorOverrides(private val context: Context) : VectorOverrides {
@ -40,6 +44,13 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides {
preferences[keyForceLoginFallback].orFalse() preferences[keyForceLoginFallback].orFalse()
} }
override val forceHomeserverCapabilities = context.dataStore.data.map { preferences ->
HomeserverCapabilitiesOverride(
canChangeDisplayName = preferences[forceCanChangeDisplayName],
canChangeAvatar = preferences[forceCanChangeAvatar]
)
}
suspend fun setForceDialPadDisplay(force: Boolean) { suspend fun setForceDialPadDisplay(force: Boolean) {
context.dataStore.edit { settings -> context.dataStore.edit { settings ->
settings[keyForceDialPadDisplay] = force settings[keyForceDialPadDisplay] = force
@ -51,4 +62,18 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides {
settings[keyForceLoginFallback] = force 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
}
}
}
} }

View File

@ -50,6 +50,12 @@ class DebugPrivateSettingsFragment : VectorBaseFragment<FragmentDebugPrivateSett
override fun invalidate() = withState(viewModel) { override fun invalidate() = withState(viewModel) {
views.forceDialPadTabDisplay.isChecked = it.dialPadVisible views.forceDialPadTabDisplay.isChecked = it.dialPadVisible
views.forceChangeDisplayNameCapability.bind(it.homeserverCapabilityOverrides.displayName) { option ->
viewModel.handle(DebugPrivateSettingsViewActions.SetDisplayNameCapabilityOverride(option))
}
views.forceChangeAvatarCapability.bind(it.homeserverCapabilityOverrides.avatar) { option ->
viewModel.handle(DebugPrivateSettingsViewActions.SetAvatarCapabilityOverride(option))
}
views.forceLoginFallback.isChecked = it.forceLoginFallback views.forceLoginFallback.isChecked = it.forceLoginFallback
} }
} }

View File

@ -18,7 +18,9 @@ package im.vector.app.features.debug.settings
import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.core.platform.VectorViewModelAction
sealed class DebugPrivateSettingsViewActions : VectorViewModelAction { sealed interface DebugPrivateSettingsViewActions : VectorViewModelAction {
data class SetDialPadVisibility(val force: Boolean) : DebugPrivateSettingsViewActions() data class SetDialPadVisibility(val force: Boolean) : DebugPrivateSettingsViewActions
data class SetForceLoginFallbackEnabled(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
} }

View File

@ -22,9 +22,12 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory 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.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.debug.features.DebugVectorOverrides 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 import kotlinx.coroutines.launch
class DebugPrivateSettingsViewModel @AssistedInject constructor( class DebugPrivateSettingsViewModel @AssistedInject constructor(
@ -40,10 +43,10 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory<DebugPrivateSettingsViewModel, DebugPrivateSettingsViewState> by hiltMavericksViewModelFactory() companion object : MavericksViewModelFactory<DebugPrivateSettingsViewModel, DebugPrivateSettingsViewState> by hiltMavericksViewModelFactory()
init { init {
observeVectorDataStore() observeVectorOverrides()
} }
private fun observeVectorDataStore() { private fun observeVectorOverrides() {
debugVectorOverrides.forceDialPad.setOnEach { debugVectorOverrides.forceDialPad.setOnEach {
copy( copy(
dialPadVisible = it dialPadVisible = it
@ -52,13 +55,23 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor(
debugVectorOverrides.forceLoginFallback.setOnEach { debugVectorOverrides.forceLoginFallback.setOnEach {
copy(forceLoginFallback = it) 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) { override fun handle(action: DebugPrivateSettingsViewActions) {
when (action) { when (action) {
is DebugPrivateSettingsViewActions.SetDialPadVisibility -> handleSetDialPadVisibility(action) is DebugPrivateSettingsViewActions.SetDialPadVisibility -> handleSetDialPadVisibility(action)
is DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled -> handleSetForceLoginFallbackEnabled(action) is DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled -> handleSetForceLoginFallbackEnabled(action)
} is SetDisplayNameCapabilityOverride -> handSetDisplayNameCapabilityOverride(action)
is SetAvatarCapabilityOverride -> handSetAvatarCapabilityOverride(action)
}.exhaustive
} }
private fun handleSetDialPadVisibility(action: DebugPrivateSettingsViewActions.SetDialPadVisibility) { private fun handleSetDialPadVisibility(action: DebugPrivateSettingsViewActions.SetDialPadVisibility) {
@ -72,4 +85,18 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor(
debugVectorOverrides.setForceLoginFallback(action.force) 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) }
}
}
} }

View File

@ -17,8 +17,23 @@
package im.vector.app.features.debug.settings package im.vector.app.features.debug.settings
import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksState
import im.vector.app.features.debug.settings.OverrideDropdownView.OverrideDropdown
data class DebugPrivateSettingsViewState( data class DebugPrivateSettingsViewState(
val dialPadVisible: Boolean = false, val dialPadVisible: Boolean = false,
val forceLoginFallback: Boolean = false, val forceLoginFallback: Boolean = false,
val homeserverCapabilityOverrides: HomeserverCapabilityOverrides = HomeserverCapabilityOverrides()
) : MavericksState ) : MavericksState
data class HomeserverCapabilityOverrides(
val displayName: OverrideDropdown<BooleanHomeserverCapabilitiesOverride> = OverrideDropdown(
label = "Override display name capability",
activeOption = null,
options = listOf(BooleanHomeserverCapabilitiesOverride.ForceEnabled, BooleanHomeserverCapabilitiesOverride.ForceDisabled)
),
val avatar: OverrideDropdown<BooleanHomeserverCapabilitiesOverride> = OverrideDropdown(
label = "Override avatar capability",
activeOption = null,
options = listOf(BooleanHomeserverCapabilitiesOverride.ForceEnabled, BooleanHomeserverCapabilitiesOverride.ForceDisabled)
)
)

View File

@ -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 <T: OverrideOption> bind(feature: OverrideDropdown<T>, listener: Listener<T>) {
labelView.text = feature.label
optionsSpinner.apply {
val arrayAdapter = ArrayAdapter<String>(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<T> {
fun onOverrideSelected(option: T?)
}
data class OverrideDropdown<T: OverrideOption>(
val label: String,
val options: List<T>,
val activeOption: T?,
)
}
interface OverrideOption {
val label: String
}

View File

@ -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
}

View File

@ -31,6 +31,24 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Force login and registration fallback" /> android:text="Force login and registration fallback" />
<im.vector.app.features.debug.settings.OverrideDropdownView
android:id="@+id/forceChangeDisplayNameCapability"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp" />
<im.vector.app.features.debug.settings.OverrideDropdownView
android:id="@+id/forceChangeAvatarCapability"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:parentTag="android.widget.LinearLayout">
<TextView
android:id="@+id/feature_label"
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:gravity="center"
android:textColor="?vctr_content_primary"
tools:text="Login version" />
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/feature_options"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</merge>

View File

@ -22,9 +22,16 @@ import kotlinx.coroutines.flow.flowOf
interface VectorOverrides { interface VectorOverrides {
val forceDialPad: Flow<Boolean> val forceDialPad: Flow<Boolean>
val forceLoginFallback: Flow<Boolean> val forceLoginFallback: Flow<Boolean>
val forceHomeserverCapabilities: Flow<HomeserverCapabilitiesOverride>?
} }
data class HomeserverCapabilitiesOverride(
val canChangeDisplayName: Boolean?,
val canChangeAvatar: Boolean?
)
class DefaultVectorOverrides : VectorOverrides { class DefaultVectorOverrides : VectorOverrides {
override val forceDialPad = flowOf(false) override val forceDialPad = flowOf(false)
override val forceLoginFallback = flowOf(false) override val forceLoginFallback = flowOf(false)
override val forceHomeserverCapabilities: Flow<HomeserverCapabilitiesOverride>? = null
} }

View File

@ -90,25 +90,6 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:id="@+id/changeProfilePictureButton"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:adjustViewBounds="true"
android:background="@drawable/bg_rounded_button"
android:backgroundTint="?vctr_system"
android:contentDescription="@string/ftue_profile_picture_title"
android:padding="10dp"
android:src="@drawable/ic_camera_plain"
app:layout_constraintBottom_toBottomOf="@id/profilePictureView"
app:layout_constraintEnd_toEndOf="@id/profilePictureView"
app:layout_constraintHeight_percent="0.08"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintStart_toStartOf="@id/profilePictureView"
app:layout_constraintTop_toTopOf="@id/profilePictureView"
app:layout_constraintVertical_bias="1"
app:tint="?vctr_content_secondary" />
<Space <Space
android:id="@+id/avatarTitleSpacing" android:id="@+id/avatarTitleSpacing"
android:layout_width="match_parent" android:layout_width="match_parent"