created hidden settings menu for ab-testing
This commit is contained in:
@ -41,6 +41,11 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.DevSettingsActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:parentActivityName=".MainActivity" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
46
app/src/main/java/com/dano/test1/ui/AbTestSettingsStore.kt
Normal file
46
app/src/main/java/com/dano/test1/ui/AbTestSettingsStore.kt
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
app/src/main/java/com/dano/test1/ui/DevSettingsActivity.kt
Normal file
71
app/src/main/java/com/dano/test1/ui/DevSettingsActivity.kt
Normal file
@ -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<Toolbar>(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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package com.dano.test1.ui
|
package com.dano.test1.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
@ -24,6 +25,11 @@ class LoadButtonHandler(
|
|||||||
private val updateMainButtonsState: (Boolean) -> Unit,
|
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() {
|
fun setup() {
|
||||||
loadButton.text = LanguageManager.getText(languageIDProvider(), "load")
|
loadButton.text = LanguageManager.getText(languageIDProvider(), "load")
|
||||||
loadButton.setOnClickListener { handleLoadButton() }
|
loadButton.setOnClickListener { handleLoadButton() }
|
||||||
@ -37,6 +43,12 @@ class LoadButtonHandler(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inputText == DEV_SETTINGS_SECRET) {
|
||||||
|
editText.text.clear()
|
||||||
|
activity.startActivity(Intent(activity, DevSettingsActivity::class.java))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
buttonPoints.clear()
|
buttonPoints.clear()
|
||||||
setButtonsEnabled(emptyList()) // temporär sperren
|
setButtonsEnabled(emptyList()) // temporär sperren
|
||||||
updateButtonTexts() // Chips zeigen vorläufig „Gesperrt“
|
updateButtonTexts() // Chips zeigen vorläufig „Gesperrt“
|
||||||
|
|||||||
75
app/src/main/res/layout/activity_dev_settings.xml
Normal file
75
app/src/main/res/layout/activity_dev_settings.xml
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/devSettingsToolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
app:title="A/B testing (dev)"
|
||||||
|
app:titleTextColor="@android:color/white" />
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:text="Manual A/B override for local testing. Not a security boundary."
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
|
android:id="@+id/switchAbOverride"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:text="Manual A/B override" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:text="Variant"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<RadioGroup
|
||||||
|
android:id="@+id/radioGroupVariant"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/radioVariantDefault"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Default (no override)" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/radioVariantA"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Variant A" />
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/radioVariantB"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Variant B" />
|
||||||
|
</RadioGroup>
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
</LinearLayout>
|
||||||
Reference in New Issue
Block a user