From d1450cab7eb7dc490c80a3b80da439646dede188 Mon Sep 17 00:00:00 2001 From: Spirit Date: Thu, 5 Mar 2026 23:09:48 +0000 Subject: [PATCH] Wire VoiceLoop into ClippySession lifecycle and ClippyScreen talk button --- .../hobbs/clippyauto/auto/ClippyScreen.kt | 12 +++--- .../hobbs/clippyauto/auto/ClippySession.kt | 43 ++++++++++++++++--- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/farm/hobbs/clippyauto/auto/ClippyScreen.kt b/app/src/main/java/farm/hobbs/clippyauto/auto/ClippyScreen.kt index ad6ef32..c47b70b 100644 --- a/app/src/main/java/farm/hobbs/clippyauto/auto/ClippyScreen.kt +++ b/app/src/main/java/farm/hobbs/clippyauto/auto/ClippyScreen.kt @@ -3,6 +3,7 @@ package farm.hobbs.clippyauto.auto import androidx.car.app.CarContext import androidx.car.app.Screen import androidx.car.app.model.* +import farm.hobbs.clippyauto.VoiceLoop /** * The Android Auto screen for ClippyAuto. @@ -15,6 +16,11 @@ import androidx.car.app.model.* class ClippyScreen(carContext: CarContext) : Screen(carContext) { private var statusText = "Tap to talk to Clippy" + private var voiceLoop: VoiceLoop? = null + + fun attachVoiceLoop(loop: VoiceLoop) { + voiceLoop = loop + } override fun onGetTemplate(): Template { val action = Action.Builder() @@ -51,11 +57,7 @@ class ClippyScreen(carContext: CarContext) : Screen(carContext) { } private fun onTalkPressed() { - // TODO: Wire to VoiceStateMachine - // IDLE → start listening - // SPEAKING → interrupt (fade TTS, activate STT) - statusText = "Listening..." - invalidate() // Refresh the screen + voiceLoop?.onTalkPressed() } fun updateStatus(status: String) { diff --git a/app/src/main/java/farm/hobbs/clippyauto/auto/ClippySession.kt b/app/src/main/java/farm/hobbs/clippyauto/auto/ClippySession.kt index 5cf6328..5d74fb8 100644 --- a/app/src/main/java/farm/hobbs/clippyauto/auto/ClippySession.kt +++ b/app/src/main/java/farm/hobbs/clippyauto/auto/ClippySession.kt @@ -5,26 +5,55 @@ import androidx.car.app.Screen import androidx.car.app.Session import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner +import farm.hobbs.clippyauto.VoiceLoop +import farm.hobbs.clippyauto.matrix.MatrixRestClient /** * Android Auto session — manages the lifecycle of a Clippy voice interaction. - * Sends session_start on create, session_end on destroy. + * Owns the VoiceLoop and passes it to the screen. */ class ClippySession : Session(), DefaultLifecycleObserver { + private var voiceLoop: VoiceLoop? = null + private var screen: ClippyScreen? = null + + // TODO: Move to config / SharedPreferences / SetupActivity + companion object { + const val HOMESERVER = "https://mess.hobbs.farm" + const val ROOM_ID = "" // Set after #clippy-voice room is created + const val CLIPPY_USER = "@clippy:hobbs.farm" + } + override fun onCreateScreen(intent: Intent): Screen { lifecycle.addObserver(this) - return ClippyScreen(carContext) + val clippyScreen = ClippyScreen(carContext) + screen = clippyScreen + return clippyScreen } override fun onStart(owner: LifecycleOwner) { - // TODO: Send session_start signal to Matrix #clippy-voice room - // TODO: Start Matrix sync (fallback path) - // TODO: Subscribe to FCM response bus + val matrixClient = MatrixRestClient(HOMESERVER) + + voiceLoop = VoiceLoop( + context = carContext, + matrixClient = matrixClient, + roomId = ROOM_ID, + clippyUserId = CLIPPY_USER, + onStateChange = { state, status -> + screen?.updateStatus(status) + } + ) + + voiceLoop?.initialize() + voiceLoop?.startSession() + + // Give the screen a reference so the talk button works + screen?.attachVoiceLoop(voiceLoop!!) } override fun onStop(owner: LifecycleOwner) { - // TODO: Send session_end signal to Matrix - // TODO: Clean up voice pipeline + voiceLoop?.endSession() + voiceLoop?.destroy() + voiceLoop = null } }