feat: MVP implementation - Kiosk Browser

- Fullscreen WebView with immersive mode
- URL whitelist system
- Admin PIN-protected settings
- Boot receiver for auto-launch
- Idle timeout with homepage reset
- Lock Task Mode support
- Material Design 3 UI

Team: Pluto (coordinator), Margaret, Spirit
This commit is contained in:
2026-02-03 09:09:20 -06:00
parent 43d91e89ca
commit 6dc1a73d2e
17 changed files with 727 additions and 13 deletions
@@ -0,0 +1,106 @@
package farm.hobbs.kioskbrowser
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import farm.hobbs.kioskbrowser.databinding.ActivityAdminBinding
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import android.text.InputType
import android.widget.EditText
class AdminActivity : AppCompatActivity() {
private lateinit var binding: ActivityAdminBinding
private val adminPin = "1234" // Default PIN - should be configurable
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Require PIN before showing settings
showPinDialog()
}
private fun showPinDialog() {
val input = EditText(this).apply {
inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
hint = "Enter Admin PIN"
}
AlertDialog.Builder(this)
.setTitle("Admin Access")
.setMessage("Enter PIN to access settings")
.setView(input)
.setPositiveButton("OK") { _, _ ->
if (input.text.toString() == getStoredPin()) {
setupUI()
} else {
Toast.makeText(this, "Invalid PIN", Toast.LENGTH_SHORT).show()
finish()
}
}
.setNegativeButton("Cancel") { _, _ -> finish() }
.setCancelable(false)
.show()
}
private fun getStoredPin(): String {
return runBlocking {
dataStore.data.first()[stringPreferencesKey("admin_pin")] ?: adminPin
}
}
private fun setupUI() {
binding = ActivityAdminBinding.inflate(layoutInflater)
setContentView(binding.root)
loadCurrentSettings()
binding.btnSave.setOnClickListener {
saveSettings()
}
binding.btnExit.setOnClickListener {
finish()
}
binding.btnExitKiosk.setOnClickListener {
// Exit kiosk mode completely
finishAffinity()
}
}
private fun loadCurrentSettings() {
runBlocking {
dataStore.data.first().let { prefs ->
binding.etHomepage.setText(
prefs[stringPreferencesKey("homepage")] ?: "https://hobbs.farm"
)
binding.etWhitelist.setText(
prefs[stringPreferencesKey("whitelist")] ?: "hobbs.farm"
)
binding.etIdleTimeout.setText(
prefs[stringPreferencesKey("idle_timeout")] ?: "300"
)
binding.etAdminPin.setText(
prefs[stringPreferencesKey("admin_pin")] ?: "1234"
)
}
}
}
private fun saveSettings() {
runBlocking {
dataStore.edit { prefs ->
prefs[stringPreferencesKey("homepage")] = binding.etHomepage.text.toString()
prefs[stringPreferencesKey("whitelist")] = binding.etWhitelist.text.toString()
prefs[stringPreferencesKey("idle_timeout")] = binding.etIdleTimeout.text.toString()
prefs[stringPreferencesKey("admin_pin")] = binding.etAdminPin.text.toString()
}
}
Toast.makeText(this, "Settings saved", Toast.LENGTH_SHORT).show()
}
}