Add phone-side test UI for voice loop testing without Android Auto
ClippyAuto CI / build (push) Failing after 5s
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:
@@ -1,30 +1,150 @@
|
||||
package farm.hobbs.clippyauto.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
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 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.
|
||||
*
|
||||
* v1 MVP:
|
||||
* - 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.)
|
||||
* Phone-side activity for setup and standalone voice testing.
|
||||
* The real UI is in Android Auto, but this lets you test the voice
|
||||
* pipeline without needing a car or DHU.
|
||||
*/
|
||||
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?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// TODO: Build setup UI
|
||||
// - Matrix homeserver URL (pre-filled: mess.hobbs.farm)
|
||||
// - Username / password fields
|
||||
// - Login button
|
||||
// - Status: connected / disconnected
|
||||
// - "Session active" indicator
|
||||
|
||||
val layout = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setPadding(48, 48, 48, 48)
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user