diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt index 9e4397cb..9e3b054c 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt @@ -66,7 +66,10 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.core.content.FileProvider +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.m57.hermescontrol.NavigationController import com.m57.hermescontrol.R @@ -102,7 +105,7 @@ import java.text.SimpleDateFormat import java.util.Date import java.util.Locale -private const val SESSION_SYNC_INTERVAL_MS = 5_000L +private const val SESSION_SYNC_INTERVAL_MS = 30_000L /** * Chat screen — the primary conversation surface of Hermes Control. @@ -126,6 +129,7 @@ fun ChatScreen( val state by viewModel.uiState.collectAsStateWithLifecycle() val streamingState by viewModel.streamingState.collectAsStateWithLifecycle() val credentialWarning by HermesWsClient.credentialWarning.collectAsStateWithLifecycle() + val lifecycleOwner = LocalLifecycleOwner.current val listState = rememberLazyListState() val scrollScope = rememberCoroutineScope() val scrollController = rememberChatScrollController(listState, scrollScope) @@ -133,15 +137,17 @@ fun ChatScreen( var showContextSheet by remember { mutableStateOf(false) } // Periodic session sync while connected. - LaunchedEffect(state.currentSessionId, state.connectionStatus) { - if (state.currentSessionId != null && state.connectionStatus == ConnectionStatus.CONNECTED) { - viewModel.syncCurrentSession() - viewModel.fetchContextUsage() - } - while (state.currentSessionId != null && state.connectionStatus == ConnectionStatus.CONNECTED) { - delay(SESSION_SYNC_INTERVAL_MS) - viewModel.syncCurrentSession() - viewModel.fetchContextUsage() + LaunchedEffect(lifecycleOwner, state.currentSessionId, state.connectionStatus) { + lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + if (state.currentSessionId != null && state.connectionStatus == ConnectionStatus.CONNECTED) { + viewModel.syncCurrentSession() + viewModel.fetchContextUsage() + } + while (state.currentSessionId != null && state.connectionStatus == ConnectionStatus.CONNECTED) { + delay(SESSION_SYNC_INTERVAL_MS) + viewModel.syncCurrentSession() + viewModel.fetchContextUsage() + } } } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt index 091c0e1f..2c9ffb48 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt @@ -2238,7 +2238,7 @@ class ChatViewModel( * Both calls are independent and best-effort: a failure on one must not * wipe the other's already-shown value, and neither blocks the chat. The * two fetches are launched separately so a slow/erroring one can't starve - * the other. Polled from [syncCurrentSession] via the 5s loop and re-fired + * the other. Polled from [syncCurrentSession] via the 30s loop and re-fired * on model switch (the denominator changes). */ fun fetchContextUsage() { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/logs/LogsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/logs/LogsScreen.kt index 32ca974f..4d0f8be6 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/logs/LogsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/logs/LogsScreen.kt @@ -31,7 +31,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text 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 @@ -45,6 +44,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.LifecycleStartEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.m57.hermescontrol.R @@ -120,9 +120,9 @@ fun LogsScreen( } // Auto-refresh logs via polling while the screen is visible - DisposableEffect(Unit) { + LifecycleStartEffect(viewModel) { viewModel.startAutoRefresh() - onDispose { viewModel.stopAutoRefresh() } + onStopOrDispose { viewModel.stopAutoRefresh() } } ToastEffect(toastMessage = state.toastMessage, onClearToast = viewModel::clearToast)