From bb991b0530f23a19b019214551573477379ffa0f Mon Sep 17 00:00:00 2001 From: Yan Wang Date: Mon, 3 Aug 2026 10:57:53 -0700 Subject: [PATCH] fix(chat): keep speech active across navigation --- .../com/yage/opencode_client/MainActivity.kt | 10 ++++++++++ .../yage/opencode_client/ui/chat/ChatScreen.kt | 17 ----------------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/yage/opencode_client/MainActivity.kt b/app/src/main/java/com/yage/opencode_client/MainActivity.kt index fd70de9..f5fca78 100644 --- a/app/src/main/java/com/yage/opencode_client/MainActivity.kt +++ b/app/src/main/java/com/yage/opencode_client/MainActivity.kt @@ -32,6 +32,7 @@ import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.repeatOnLifecycle import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -113,6 +114,15 @@ class MainActivity : AppCompatActivity() { mainViewModel.receiveDeepLink(rawUrl) } val lifecycleOwner = LocalLifecycleOwner.current + DisposableEffect(lifecycleOwner, mainViewModel) { + val observer = LifecycleEventObserver { _, event -> + if (event == Lifecycle.Event.ON_STOP) { + mainViewModel.stopSpeechForBackground() + } + } + lifecycleOwner.lifecycle.addObserver(observer) + onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } + } LaunchedEffect(lifecycleOwner) { // Debug-only credential injection: if the launch Intent carries // test credentials (passed via `am start --es test_server_url ...`), diff --git a/app/src/main/java/com/yage/opencode_client/ui/chat/ChatScreen.kt b/app/src/main/java/com/yage/opencode_client/ui/chat/ChatScreen.kt index f0176f5..3cdc6bf 100644 --- a/app/src/main/java/com/yage/opencode_client/ui/chat/ChatScreen.kt +++ b/app/src/main/java/com/yage/opencode_client/ui/chat/ChatScreen.kt @@ -15,7 +15,6 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -29,10 +28,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.core.content.ContextCompat -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.compose.LocalLifecycleOwner import com.yage.opencode_client.R import com.yage.opencode_client.data.model.MessageWithParts import com.yage.opencode_client.data.model.Part @@ -57,7 +53,6 @@ fun ChatScreen( ) { val state by viewModel.state.collectAsStateWithLifecycle() val context = LocalContext.current - val lifecycleOwner = LocalLifecycleOwner.current val scope = rememberCoroutineScope() val aiBuilderToken = sanitizeBearerToken(viewModel.getAIBuilderSettings().token) val audioPermissionLauncher = rememberLauncherForActivityResult( @@ -145,18 +140,6 @@ fun ChatScreen( ) } - DisposableEffect(lifecycleOwner, viewModel) { - val observer = LifecycleEventObserver { _, event -> - if (event == Lifecycle.Event.ON_STOP) { - viewModel.stopSpeechForBackground() - } - } - lifecycleOwner.lifecycle.addObserver(observer) - onDispose { - lifecycleOwner.lifecycle.removeObserver(observer) - } - } - Column(modifier = Modifier.fillMaxSize()) { ChatTopBar( state = ChatTopBarState(