From 2d74eb060c6689848d41716ff623d1dd5c75f8d7 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 6 Dec 2021 12:08:34 +0000 Subject: [PATCH] adding debug screen to override features - adds enum support with persistence via class names --- vector/src/debug/AndroidManifest.xml | 1 + .../app/features/debug/DebugMenuActivity.kt | 2 + .../app/features/debug/di/FeaturesModule.kt | 48 ++++++++++++ .../features/DebugFeaturesSettingsActivity.kt | 78 +++++++++++++++++++ .../debug/features/DebugVectorFeatures.kt | 66 ++++++++++++++++ .../debug/features/EnumFeatureItem.kt | 78 +++++++++++++++++++ .../debug/features/FeaturesController.kt | 52 +++++++++++++ .../debug/res/layout/activity_debug_menu.xml | 6 ++ vector/src/debug/res/layout/item_feature.xml | 30 +++++++ .../im/vector/app/core/di/SingletonModule.kt | 7 -- .../im/vector/app/core/di/FeaturesModule.kt | 34 ++++++++ 11 files changed, 395 insertions(+), 7 deletions(-) create mode 100644 vector/src/debug/java/im/vector/app/features/debug/di/FeaturesModule.kt create mode 100644 vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesSettingsActivity.kt create mode 100644 vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt create mode 100644 vector/src/debug/java/im/vector/app/features/debug/features/EnumFeatureItem.kt create mode 100644 vector/src/debug/java/im/vector/app/features/debug/features/FeaturesController.kt create mode 100644 vector/src/debug/res/layout/item_feature.xml create mode 100644 vector/src/release/java/im/vector/app/core/di/FeaturesModule.kt diff --git a/vector/src/debug/AndroidManifest.xml b/vector/src/debug/AndroidManifest.xml index dba8440602..ceeb0353db 100644 --- a/vector/src/debug/AndroidManifest.xml +++ b/vector/src/debug/AndroidManifest.xml @@ -7,6 +7,7 @@ + diff --git a/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt index 64de648a23..4916ab1e5d 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt @@ -34,6 +34,7 @@ import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.toast import im.vector.app.databinding.ActivityDebugMenuBinding +import im.vector.app.features.debug.features.DebugFeaturesSettingsActivity import im.vector.app.features.debug.sas.DebugSasEmojiActivity import im.vector.app.features.debug.settings.DebugPrivateSettingsActivity import im.vector.app.features.qrcode.QrCodeScannerActivity @@ -76,6 +77,7 @@ class DebugMenuActivity : VectorBaseActivity() { } private fun setupViews() { + views.debugFeatures.setOnClickListener { startActivity(Intent(this, DebugFeaturesSettingsActivity::class.java)) } views.debugPrivateSetting.setOnClickListener { openPrivateSettings() } views.debugTestTextViewLink.setOnClickListener { testTextViewLink() } views.debugOpenButtonStylesLight.setOnClickListener { diff --git a/vector/src/debug/java/im/vector/app/features/debug/di/FeaturesModule.kt b/vector/src/debug/java/im/vector/app/features/debug/di/FeaturesModule.kt new file mode 100644 index 0000000000..57c39d03d8 --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/di/FeaturesModule.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 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.di + +import android.content.Context +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import im.vector.app.features.DefaultVectorFeatures +import im.vector.app.features.VectorFeatures +import im.vector.app.features.debug.features.DebugVectorFeatures + +@InstallIn(SingletonComponent::class) +@Module +interface FeaturesModule { + + @Binds + fun bindNavigator(navigator: DebugVectorFeatures): VectorFeatures + + companion object { + + @Provides + fun providesDefaultVectorFeatures(): DefaultVectorFeatures { + return DefaultVectorFeatures() + } + + @Provides + fun providesDebugVectorFeatures(context: Context, defaultVectorFeatures: DefaultVectorFeatures): DebugVectorFeatures { + return DebugVectorFeatures(context, defaultVectorFeatures) + } + } +} diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesSettingsActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesSettingsActivity.kt new file mode 100644 index 0000000000..03d065c982 --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesSettingsActivity.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2021 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.features + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.extensions.cleanup +import im.vector.app.core.extensions.configureWith +import im.vector.app.databinding.FragmentGenericRecyclerBinding +import im.vector.app.features.DefaultVectorFeatures +import im.vector.app.features.themes.ActivityOtherThemes +import im.vector.app.features.themes.ThemeUtils +import javax.inject.Inject + +@AndroidEntryPoint +class DebugFeaturesSettingsActivity : AppCompatActivity() { + + @Inject lateinit var debugFeatures: DebugVectorFeatures + @Inject lateinit var defaultFeatures: DefaultVectorFeatures + + private lateinit var views: FragmentGenericRecyclerBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + ThemeUtils.setActivityTheme(this, ActivityOtherThemes.Default) + views = FragmentGenericRecyclerBinding.inflate(layoutInflater) + setContentView(views.root) + val controller = FeaturesController(object : EnumFeatureItem.Listener { + + @Suppress("UNCHECKED_CAST") + override fun > onOptionSelected(option: Any?, feature: Feature.EnumFeature) { + debugFeatures.overrideEnum(option as? T, feature.type) + } + }) + views.genericRecyclerView.configureWith(controller) + controller.setData(createState()) + } + + private fun createState(): FeaturesState { + return FeaturesState(listOf( + createEnumFeature( + label = "Login version", + selection = debugFeatures.loginVersion(), + default = defaultFeatures.loginVersion() + ) + )) + } + + private inline fun > createEnumFeature(label: String, selection: T, default: T): Feature { + return Feature.EnumFeature( + label = label, + selection = selection.takeIf { debugFeatures.hasEnumOverride(T::class) }, + default = default, + options = enumValues().toList(), + type = T::class + ) + } + + override fun onDestroy() { + views.genericRecyclerView.cleanup() + super.onDestroy() + } +} diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt new file mode 100644 index 0000000000..822adcb61f --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 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.features + +import android.content.Context +import android.content.SharedPreferences +import im.vector.app.features.DefaultVectorFeatures +import im.vector.app.features.VectorFeatures +import kotlin.reflect.KClass + +class DebugVectorFeatures( + context: Context, + private val vectorFeatures: DefaultVectorFeatures +) : VectorFeatures { + + private val featurePrefs = context.getSharedPreferences("debug-features", Context.MODE_PRIVATE) + + override fun loginVersion(): VectorFeatures.LoginVersion { + return featurePrefs.readEnum() ?: vectorFeatures.loginVersion() + } + + fun > hasEnumOverride(type: KClass): Boolean { + return featurePrefs.containsEnum(type) + } + + fun > overrideEnum(value: T?, type: KClass) { + if (value == null) { + featurePrefs.removeEnum(type) + } else { + featurePrefs.putEnum(value, type) + } + } +} + +private fun > SharedPreferences.removeEnum(type: KClass) { + edit().remove("enum-${type.simpleName}").apply() +} + +private fun > SharedPreferences.containsEnum(type: KClass): Boolean { + return contains("enum-${type.simpleName}") +} + +private inline fun > SharedPreferences.readEnum(): T? { + val value = T::class.simpleName + return getString("enum-$value", null)?.let { enumValueOf(it) } +} + +private fun > SharedPreferences.putEnum(value: T, type: KClass) { + edit() + .putString("enum-${type.simpleName}", value.name) + .apply() +} diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/EnumFeatureItem.kt b/vector/src/debug/java/im/vector/app/features/debug/features/EnumFeatureItem.kt new file mode 100644 index 0000000000..1e2ec56ea8 --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/features/EnumFeatureItem.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2019 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.features + +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Spinner +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel + +@EpoxyModelClass(layout = im.vector.app.R.layout.item_feature) +abstract class EnumFeatureItem : VectorEpoxyModel() { + + @EpoxyAttribute + lateinit var feature: Feature.EnumFeature<*> + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var listener: Listener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.label.text = feature.label + + holder.optionsSpinner.apply { + val arrayAdapter = ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item) + arrayAdapter.add("DEFAULT - ${feature.default.name}") + arrayAdapter.addAll(feature.options.map { it.name }) + adapter = arrayAdapter + + feature.selection?.let { + setSelection(feature.options.indexOf(it) + 1, false) + } + + onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + when (position) { + 0 -> listener?.onOptionSelected(option = null, feature) + else -> { + val option: Any = feature.options[position - 1] + listener?.onOptionSelected(option, feature) + } + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // do nothing + } + } + } + } + + class Holder : VectorEpoxyHolder() { + val label by bind(im.vector.app.R.id.feature_label) + val optionsSpinner by bind(im.vector.app.R.id.feature_options) + } + + interface Listener { + fun > onOptionSelected(option: Any?, feature: Feature.EnumFeature) + } +} diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/FeaturesController.kt b/vector/src/debug/java/im/vector/app/features/debug/features/FeaturesController.kt new file mode 100644 index 0000000000..50897e6bba --- /dev/null +++ b/vector/src/debug/java/im/vector/app/features/debug/features/FeaturesController.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2019 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.features + +import com.airbnb.epoxy.TypedEpoxyController +import kotlin.reflect.KClass + +data class FeaturesState( + val features: List +) + +sealed interface Feature { + + data class EnumFeature>( + val label: String, + val selection: T?, + val default: T, + val options: List, + val type: KClass + ) : Feature +} + +class FeaturesController(private val listener: EnumFeatureItem.Listener) : TypedEpoxyController() { + + override fun buildModels(data: FeaturesState?) { + if (data == null) return + + data.features.forEachIndexed { index, feature -> + when (feature) { + is Feature.EnumFeature<*> -> enumFeatureItem { + id(index) + feature(feature) + listener(this@FeaturesController.listener) + } + } + } + } +} diff --git a/vector/src/debug/res/layout/activity_debug_menu.xml b/vector/src/debug/res/layout/activity_debug_menu.xml index ac70e4ef0e..7aa69becde 100644 --- a/vector/src/debug/res/layout/activity_debug_menu.xml +++ b/vector/src/debug/res/layout/activity_debug_menu.xml @@ -20,6 +20,12 @@ android:padding="@dimen/layout_horizontal_margin" android:showDividers="middle"> +