diff --git a/app/src/main/java/com/example/jnicpp/bowling/AdminAuth.kt b/app/src/main/java/com/example/jnicpp/bowling/AdminAuth.kt
new file mode 100644
index 0000000..025f97c
--- /dev/null
+++ b/app/src/main/java/com/example/jnicpp/bowling/AdminAuth.kt
@@ -0,0 +1,31 @@
+/**
+ * @file AdminAuth.kt
+ * @brief Minimal local admin/normal-user distinction gating access to the tuning screen.
+ */
+package com.example.jnicpp.bowling
+
+/**
+ * @brief Recognizes an admin login vs. a normal user for this local tool.
+ *
+ * Deliberately not a real multi-user account system -- there's no backend
+ * and no per-user data anywhere in this app. Just a single admin password
+ * checked locally: entering it correctly (see
+ * [BowlingCameraActivity]'s Tuning button) recognizes that login attempt
+ * as admin and unlocks [ParameterEditorActivity] for it; anyone who
+ * doesn't enter it stays a normal user with no access. Not persisted
+ * across app restarts or Tuning taps -- each attempt is checked fresh.
+ */
+object AdminAuth {
+ // Change this to your own value if you want a different admin
+ // password. Plain-text on purpose: this only gates a local tuning
+ // screen, not anything sensitive, so hashing/storage machinery would
+ // be complexity this tool doesn't need.
+ private const val ADMIN_PASSWORD = "admin123"
+
+ /**
+ * @brief Whether [password] matches the admin password.
+ * @param password The password entered at the login prompt.
+ * @return true if this login attempt should be recognized as admin.
+ */
+ fun isAdminPassword(password: String): Boolean = password == ADMIN_PASSWORD
+}
diff --git a/app/src/main/java/com/example/jnicpp/bowling/AdminLoginPrompt.kt b/app/src/main/java/com/example/jnicpp/bowling/AdminLoginPrompt.kt
new file mode 100644
index 0000000..4f44f61
--- /dev/null
+++ b/app/src/main/java/com/example/jnicpp/bowling/AdminLoginPrompt.kt
@@ -0,0 +1,53 @@
+/**
+ * @file AdminLoginPrompt.kt
+ * @brief Dialog that gates admin-only actions behind AdminAuth's password check.
+ */
+package com.example.jnicpp.bowling
+
+import android.content.Context
+import android.text.InputType
+import android.widget.EditText
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
+import com.example.jnicpp.R
+
+/**
+ * @brief Shows the admin-login dialog used to gate [ParameterEditorActivity].
+ *
+ * Pulled out of [BowlingCameraActivity] so that Activity stays limited to
+ * wiring user actions into this rather than holding dialog-building and
+ * password-checking logic itself -- same reasoning as
+ * [StepCounterUiController].
+ */
+object AdminLoginPrompt {
+
+ /**
+ * @brief Prompts for the admin password and invokes [onSuccess] only
+ * if it matches [AdminAuth]'s admin password; otherwise shows a
+ * "staying in normal user mode" toast and does nothing further.
+ * @param context Used to build the dialog and its toast.
+ * @param onSuccess Invoked once the entered password is confirmed correct.
+ */
+ fun show(context: Context, onSuccess: () -> Unit) {
+ val passwordInput = EditText(context).apply {
+ inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
+ hint = context.getString(R.string.admin_password_hint)
+ }
+ val paddingPx = (16 * context.resources.displayMetrics.density).toInt()
+ passwordInput.setPadding(paddingPx, paddingPx, paddingPx, paddingPx)
+
+ AlertDialog.Builder(context)
+ .setTitle(R.string.admin_login_title)
+ .setMessage(R.string.admin_login_message)
+ .setView(passwordInput)
+ .setPositiveButton(R.string.admin_login_confirm) { _, _ ->
+ if (AdminAuth.isAdminPassword(passwordInput.text.toString())) {
+ onSuccess()
+ } else {
+ Toast.makeText(context, R.string.admin_login_failed, Toast.LENGTH_SHORT).show()
+ }
+ }
+ .setNegativeButton(R.string.admin_login_cancel, null)
+ .show()
+ }
+}
diff --git a/app/src/main/java/com/example/jnicpp/bowling/BowlingCameraActivity.kt b/app/src/main/java/com/example/jnicpp/bowling/BowlingCameraActivity.kt
index fa47629..fa4e873 100644
--- a/app/src/main/java/com/example/jnicpp/bowling/BowlingCameraActivity.kt
+++ b/app/src/main/java/com/example/jnicpp/bowling/BowlingCameraActivity.kt
@@ -109,7 +109,11 @@ class BowlingCameraActivity : AppCompatActivity(), CameraXController.Callback {
binding.switchPose.setOnCheckedChangeListener { _, isChecked -> viewModel.onPoseToggled(isChecked) }
binding.btnSwitchCamera.setOnClickListener { onSwitchCameraClicked() }
binding.btnBack.setOnClickListener { finish() }
- binding.btnEditor.setOnClickListener { startActivity(Intent(this, ParameterEditorActivity::class.java)) }
+ binding.btnEditor.setOnClickListener {
+ AdminLoginPrompt.show(this) {
+ startActivity(Intent(this, ParameterEditorActivity::class.java))
+ }
+ }
observeViewModel()
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2dfb0a5..33f3b42 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -20,6 +20,12 @@
Recording failed: %1$s
Pose detector error: %1$s
Tuning
+ Admin login
+ Enter the admin password to access tuning settings.
+ Password
+ Log in
+ Cancel
+ Incorrect password — staying in normal user mode.
Step Detection Tuning