diff --git a/app/src/main/java/farm/hobbs/clippyauto/ui/SetupActivity.kt b/app/src/main/java/farm/hobbs/clippyauto/ui/SetupActivity.kt index 4d40282..725b48c 100644 --- a/app/src/main/java/farm/hobbs/clippyauto/ui/SetupActivity.kt +++ b/app/src/main/java/farm/hobbs/clippyauto/ui/SetupActivity.kt @@ -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() } }