From a0a9ba45fab3438133e9b819d88be3fad929ecbb Mon Sep 17 00:00:00 2001 From: Tom Hempel Date: Mon, 23 Mar 2026 09:20:51 +0100 Subject: [PATCH] created hidden settings menu for ab-testing --- app/src/main/AndroidManifest.xml | 5 ++ .../com/dano/test1/ui/AbTestSettingsStore.kt | 46 ++++++++++++ .../com/dano/test1/ui/DevSettingsActivity.kt | 71 ++++++++++++++++++ .../com/dano/test1/ui/LoadButtonHandler.kt | 12 +++ .../main/res/layout/activity_dev_settings.xml | 75 +++++++++++++++++++ 5 files changed, 209 insertions(+) create mode 100644 app/src/main/java/com/dano/test1/ui/AbTestSettingsStore.kt create mode 100644 app/src/main/java/com/dano/test1/ui/DevSettingsActivity.kt create mode 100644 app/src/main/res/layout/activity_dev_settings.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 49acd6e..b429bec 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -41,6 +41,11 @@ + + diff --git a/app/src/main/java/com/dano/test1/ui/AbTestSettingsStore.kt b/app/src/main/java/com/dano/test1/ui/AbTestSettingsStore.kt new file mode 100644 index 0000000..eb51892 --- /dev/null +++ b/app/src/main/java/com/dano/test1/ui/AbTestSettingsStore.kt @@ -0,0 +1,46 @@ +package com.dano.test1.ui + +import android.content.Context + +/** + * Persists dev-only A/B overrides. Not a security boundary; values are for local QA. + * Use [effectiveVariant] when branching questionnaire or remote-config logic. + */ +object AbTestSettingsStore { + private const val PREF = "dev_ab_settings" + private const val KEY_OVERRIDE = "override_enabled" + private const val KEY_VARIANT = "variant" + + const val VARIANT_NONE = "NONE" + const val VARIANT_A = "A" + const val VARIANT_B = "B" + + fun isOverrideEnabled(context: Context): Boolean = + context.getSharedPreferences(PREF, Context.MODE_PRIVATE).getBoolean(KEY_OVERRIDE, false) + + fun setOverrideEnabled(context: Context, enabled: Boolean) { + context.getSharedPreferences(PREF, Context.MODE_PRIVATE).edit() + .putBoolean(KEY_OVERRIDE, enabled) + .apply() + } + + fun getVariant(context: Context): String = + context.getSharedPreferences(PREF, Context.MODE_PRIVATE).getString(KEY_VARIANT, VARIANT_NONE) + ?: VARIANT_NONE + + fun setVariant(context: Context, variant: String) { + context.getSharedPreferences(PREF, Context.MODE_PRIVATE).edit() + .putString(KEY_VARIANT, variant) + .apply() + } + + /** Returns "A" or "B" when override is on and a branch is selected; otherwise null. */ + fun effectiveVariant(context: Context): String? { + if (!isOverrideEnabled(context)) return null + return when (getVariant(context)) { + VARIANT_A -> "A" + VARIANT_B -> "B" + else -> null + } + } +} diff --git a/app/src/main/java/com/dano/test1/ui/DevSettingsActivity.kt b/app/src/main/java/com/dano/test1/ui/DevSettingsActivity.kt new file mode 100644 index 0000000..16f0904 --- /dev/null +++ b/app/src/main/java/com/dano/test1/ui/DevSettingsActivity.kt @@ -0,0 +1,71 @@ +package com.dano.test1.ui + +import android.os.Bundle +import android.widget.RadioButton +import android.widget.RadioGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.SwitchCompat +import androidx.appcompat.widget.Toolbar +import com.dano.test1.R + +/** + * Dev-only A/B settings. Entry: client code [_dev_settings_] on the opening screen Load action. + * Not a security boundary (secret string is in the APK). + */ +class DevSettingsActivity : AppCompatActivity() { + + private lateinit var switchOverride: SwitchCompat + private lateinit var radioGroup: RadioGroup + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_dev_settings) + + val toolbar = findViewById(R.id.devSettingsToolbar) + setSupportActionBar(toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + + switchOverride = findViewById(R.id.switchAbOverride) + radioGroup = findViewById(R.id.radioGroupVariant) + + applyPrefsToUi() + + switchOverride.setOnCheckedChangeListener { _, isChecked -> + AbTestSettingsStore.setOverrideEnabled(this, isChecked) + updateRadiosEnabled(isChecked) + } + + radioGroup.setOnCheckedChangeListener { _, checkedId -> + val variant = when (checkedId) { + R.id.radioVariantA -> AbTestSettingsStore.VARIANT_A + R.id.radioVariantB -> AbTestSettingsStore.VARIANT_B + else -> AbTestSettingsStore.VARIANT_NONE + } + AbTestSettingsStore.setVariant(this, variant) + } + } + + private fun applyPrefsToUi() { + val overrideOn = AbTestSettingsStore.isOverrideEnabled(this) + switchOverride.isChecked = overrideOn + updateRadiosEnabled(overrideOn) + + when (AbTestSettingsStore.getVariant(this)) { + AbTestSettingsStore.VARIANT_A -> radioGroup.check(R.id.radioVariantA) + AbTestSettingsStore.VARIANT_B -> radioGroup.check(R.id.radioVariantB) + else -> radioGroup.check(R.id.radioVariantDefault) + } + } + + private fun updateRadiosEnabled(enabled: Boolean) { + for (i in 0 until radioGroup.childCount) { + (radioGroup.getChildAt(i) as? RadioButton)?.isEnabled = enabled + } + radioGroup.alpha = if (enabled) 1f else 0.5f + } + + override fun onSupportNavigateUp(): Boolean { + finish() + return true + } +} diff --git a/app/src/main/java/com/dano/test1/ui/LoadButtonHandler.kt b/app/src/main/java/com/dano/test1/ui/LoadButtonHandler.kt index 8cde1c2..1ab68ac 100644 --- a/app/src/main/java/com/dano/test1/ui/LoadButtonHandler.kt +++ b/app/src/main/java/com/dano/test1/ui/LoadButtonHandler.kt @@ -1,5 +1,6 @@ package com.dano.test1.ui +import android.content.Intent import android.widget.Button import android.widget.EditText import android.widget.Toast @@ -24,6 +25,11 @@ class LoadButtonHandler( private val updateMainButtonsState: (Boolean) -> Unit, ) { + companion object { + /** Opening-screen client code that opens dev A/B settings (not a security boundary). */ + private const val DEV_SETTINGS_SECRET = "_dev_settings_" + } + fun setup() { loadButton.text = LanguageManager.getText(languageIDProvider(), "load") loadButton.setOnClickListener { handleLoadButton() } @@ -37,6 +43,12 @@ class LoadButtonHandler( return } + if (inputText == DEV_SETTINGS_SECRET) { + editText.text.clear() + activity.startActivity(Intent(activity, DevSettingsActivity::class.java)) + return + } + buttonPoints.clear() setButtonsEnabled(emptyList()) // temporär sperren updateButtonTexts() // Chips zeigen vorläufig „Gesperrt“ diff --git a/app/src/main/res/layout/activity_dev_settings.xml b/app/src/main/res/layout/activity_dev_settings.xml new file mode 100644 index 0000000..0cbf9f1 --- /dev/null +++ b/app/src/main/res/layout/activity_dev_settings.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +