Add phone-side test UI for voice loop testing without Android Auto
ClippyAuto CI / build (push) Failing after 5s

SetupActivity now has Connect + Talk buttons, status display, and log output.
Allows testing the full STT → Matrix → Clippy → TTS pipeline from the phone.
This commit is contained in:
2026-03-06 18:34:07 +00:00
parent 899cad0700
commit dbbf2b76ab
@@ -1,30 +1,150 @@
package farm.hobbs.clippyauto.ui package farm.hobbs.clippyauto.ui
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
import android.widget.Button
import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import farm.hobbs.clippyauto.R import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import farm.hobbs.clippyauto.VoiceLoop
import farm.hobbs.clippyauto.matrix.MatrixRestClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/** /**
* Phone-side activity for initial setup and session monitoring. * Phone-side activity for setup and standalone voice testing.
* * The real UI is in Android Auto, but this lets you test the voice
* v1 MVP: * pipeline without needing a car or DHU.
* - Login to Matrix homeserver (first run)
* - Show "session active" indicator during Android Auto use
* - Display debrief summary notification after session ends
*
* v2:
* - Full conversation transcript review
* - Settings (silence thresholds, TTS voice, etc.)
*/ */
class SetupActivity : AppCompatActivity() { class SetupActivity : AppCompatActivity() {
companion object {
private const val HOMESERVER = "https://mess.hobbs.farm"
private const val ROOM_ID = "!KSovCtghshGUfVDbQg:hobbs.farm"
private const val CLIPPY_USER = "@clippy:hobbs.farm"
private const val MATRIX_USER = "clippyauto"
private const val MATRIX_PASS = "ClippyAuto2026!"
private const val MIC_PERMISSION_CODE = 100
}
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
private var voiceLoop: VoiceLoop? = null
private lateinit var statusText: TextView
private lateinit var logText: TextView
private lateinit var talkButton: Button
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// TODO: Build setup UI
// - Matrix homeserver URL (pre-filled: mess.hobbs.farm) val layout = LinearLayout(this).apply {
// - Username / password fields orientation = LinearLayout.VERTICAL
// - Login button setPadding(48, 48, 48, 48)
// - Status: connected / disconnected }
// - "Session active" indicator
// Title
layout.addView(TextView(this).apply {
text = "📎 ClippyAuto"
textSize = 28f
setPadding(0, 0, 0, 16)
})
// Status
statusText = TextView(this).apply {
text = "Tap Connect to start"
textSize = 18f
setPadding(0, 0, 0, 24)
}
layout.addView(statusText)
// Connect button
val connectButton = Button(this).apply {
text = "🔌 Connect to Matrix"
setOnClickListener { connectToMatrix() }
}
layout.addView(connectButton)
// Talk button
talkButton = Button(this).apply {
text = "🎙️ Talk to Clippy"
isEnabled = false
setOnClickListener { voiceLoop?.onTalkPressed() }
}
layout.addView(talkButton)
// Log output
logText = TextView(this).apply {
text = "Log:\n"
textSize = 12f
setPadding(0, 24, 0, 0)
}
val scroll = ScrollView(this).apply {
addView(logText)
}
layout.addView(scroll)
setContentView(layout)
// Request mic permission
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this, arrayOf(Manifest.permission.RECORD_AUDIO), MIC_PERMISSION_CODE
)
}
}
private fun connectToMatrix() {
statusText.text = "Connecting to Matrix..."
log("Connecting to $HOMESERVER...")
val matrixClient = MatrixRestClient(HOMESERVER)
voiceLoop = VoiceLoop(
context = this,
matrixClient = matrixClient,
roomId = ROOM_ID,
clippyUserId = CLIPPY_USER,
onStateChange = { state, status ->
runOnUiThread {
statusText.text = status
log("State: $state → $status")
}
}
)
scope.launch {
val loggedIn = withContext(Dispatchers.IO) {
matrixClient.login(MATRIX_USER, MATRIX_PASS)
}
if (loggedIn) {
log("✅ Logged in as @clippyauto:hobbs.farm")
voiceLoop?.initialize()
voiceLoop?.startSession()
talkButton.isEnabled = true
statusText.text = "Connected! Tap Talk to Clippy"
} else {
log("❌ Login failed")
statusText.text = "⚠️ Matrix login failed"
}
}
}
private fun log(msg: String) {
runOnUiThread {
logText.append("$msg\n")
}
}
override fun onDestroy() {
voiceLoop?.endSession()
voiceLoop?.destroy()
super.onDestroy()
} }
} }