* fix(chat): surface all disconnected states in reconnect banner (issue #491) The connection-status banner only appeared for RECONNECTING/DISCONNECTED, so NO_NETWORK and AUTH_EXPIRED left the user staring at a silent chat with no feedback and no way to recover — the core of the 'reconnecting is not user-friendly' complaint. ChatTopBanner now shows for every non-connected, non-connecting state: - RECONNECTING: spinner + 'Reconnecting to Hermes…' (auto, no button) - DISCONNECTED: 'Disconnected from Hermes' + Reconnect - NO_NETWORK: 'No network connection' + Reconnect (force a retry) - AUTH_EXPIRED: 'Session expired' + 'Sign in again' -> AuthLoginScreen The auto-reconnect engine (backoff, network-return trigger, in-flight message queue in HermesWsClient) is untouched — this only makes the stuck states visible and actionable. * fix(chat): use LocalHermesStatusColors for reconnect banner (review fix) Per AGENTS.md, semantic status colors must come from LocalHermesStatusColors, never MaterialTheme.colorScheme. The reconnect banner used errorContainer/onErrorContainer directly, which is both a semantic-color violation and inconsistent with the rest of the app (Navigation.kt maps DISCONNECTED/NO_NETWORK/AUTH_EXPIRED -> .error; TelegramOnboardingDialog uses .errorContainer for error surfaces). Switch Surface bg to errorContainer and foreground (spinner, text, button content) to error via LocalHermesStatusColors.current.
This commit is contained in:
2 files changed
+58
-23
No files matched your search
@@ -109,12 +109,14 @@ import androidx.core.content.FileProvider
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil.compose.AsyncImage
|
||||
import com.m57.hermescontrol.AuthLoginScreen
|
||||
import com.m57.hermescontrol.NavigationController
|
||||
import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.data.model.Attachment
|
||||
import com.m57.hermescontrol.data.ws.CommandCatalog
|
||||
import com.m57.hermescontrol.data.ws.ConnectionStatus
|
||||
import com.m57.hermescontrol.notification.NotificationHelper
|
||||
import com.m57.hermescontrol.theme.LocalHermesStatusColors
|
||||
import com.m57.hermescontrol.theme.StatusRed
|
||||
import com.m57.hermescontrol.ui.common.EmptyState
|
||||
import com.m57.hermescontrol.ui.common.HermesScaffold
|
||||
@@ -360,6 +362,7 @@ fun ChatScreen(
|
||||
ChatTopBanner(
|
||||
connectionStatus = state.connectionStatus,
|
||||
onReconnect = viewModel::reconnect,
|
||||
onAuthExpired = { NavigationController.navigateTo(AuthLoginScreen) },
|
||||
)
|
||||
|
||||
Box(
|
||||
@@ -1337,18 +1340,20 @@ private fun ChatLifecycleEffects(
|
||||
private fun ChatTopBanner(
|
||||
connectionStatus: ConnectionStatus,
|
||||
onReconnect: () -> Unit,
|
||||
onAuthExpired: () -> Unit,
|
||||
) {
|
||||
val isShown =
|
||||
connectionStatus != ConnectionStatus.CONNECTED &&
|
||||
connectionStatus != ConnectionStatus.CONNECTING
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible =
|
||||
connectionStatus == ConnectionStatus.RECONNECTING ||
|
||||
connectionStatus == ConnectionStatus.DISCONNECTED,
|
||||
visible = isShown,
|
||||
enter = androidx.compose.animation.expandVertically() + androidx.compose.animation.fadeIn(),
|
||||
exit = androidx.compose.animation.shrinkVertically() + androidx.compose.animation.fadeOut(),
|
||||
) {
|
||||
androidx.compose.material3.Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = MaterialTheme.colorScheme.errorContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onErrorContainer,
|
||||
color = LocalHermesStatusColors.current.errorContainer,
|
||||
contentColor = LocalHermesStatusColors.current.error,
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
@@ -1358,25 +1363,52 @@ private fun ChatTopBanner(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
text =
|
||||
if (connectionStatus == ConnectionStatus.RECONNECTING) {
|
||||
stringResource(R.string.chat_status_reconnecting)
|
||||
} else {
|
||||
stringResource(R.string.chat_status_disconnected)
|
||||
},
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
if (connectionStatus == ConnectionStatus.DISCONNECTED) {
|
||||
TextButton(
|
||||
onClick = onReconnect,
|
||||
colors =
|
||||
androidx.compose.material3.ButtonDefaults.textButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.onErrorContainer,
|
||||
),
|
||||
) {
|
||||
Text(stringResource(R.string.chat_action_reconnect))
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (connectionStatus == ConnectionStatus.RECONNECTING) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(16.dp).padding(end = 8.dp),
|
||||
strokeWidth = 2.dp,
|
||||
color = LocalHermesStatusColors.current.error,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text =
|
||||
when (connectionStatus) {
|
||||
ConnectionStatus.RECONNECTING -> stringResource(R.string.chat_status_reconnecting)
|
||||
ConnectionStatus.DISCONNECTED -> stringResource(R.string.chat_status_disconnected)
|
||||
ConnectionStatus.NO_NETWORK -> stringResource(R.string.chat_status_no_network)
|
||||
ConnectionStatus.AUTH_EXPIRED -> stringResource(R.string.chat_status_auth_expired)
|
||||
else -> ""
|
||||
},
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
when (connectionStatus) {
|
||||
ConnectionStatus.DISCONNECTED,
|
||||
ConnectionStatus.NO_NETWORK,
|
||||
-> {
|
||||
TextButton(
|
||||
onClick = onReconnect,
|
||||
colors =
|
||||
androidx.compose.material3.ButtonDefaults.textButtonColors(
|
||||
contentColor = LocalHermesStatusColors.current.error,
|
||||
),
|
||||
) {
|
||||
Text(stringResource(R.string.chat_action_reconnect))
|
||||
}
|
||||
}
|
||||
ConnectionStatus.AUTH_EXPIRED -> {
|
||||
TextButton(
|
||||
onClick = onAuthExpired,
|
||||
colors =
|
||||
androidx.compose.material3.ButtonDefaults.textButtonColors(
|
||||
contentColor = LocalHermesStatusColors.current.error,
|
||||
),
|
||||
) {
|
||||
Text(stringResource(R.string.chat_action_sign_in))
|
||||
}
|
||||
}
|
||||
else -> { /* RECONNECTING: auto, no button */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user