add connection state management to ChatStore and update UI feedback in chat

This commit is contained in:
Tommy Chow 2026-01-06 19:42:09 -05:00
parent d296a20aef
commit c378b9b2fd
4 changed files with 76 additions and 18 deletions

View file

@ -241,6 +241,15 @@ abstract class ChatStoreBase with Store {
@readonly
var _isWaitingForAck = false;
/// Whether the chat WebSocket is currently connected.
@readonly
var _isConnected = false;
/// Whether we've successfully connected at least once (to distinguish
/// "connecting" from "disconnected" in the UI).
@readonly
var _hasConnected = false;
/// The logged-in user's appearance in chat.
@readonly
var _userState = const USERSTATE();
@ -251,12 +260,6 @@ abstract class ChatStoreBase with Store {
@observable
IRCMessage? replyingToMessage;
/// Public getter for whether the chat is in shared chat mode.
bool get isInSharedChatMode => _isInSharedChatMode;
/// Public getter for whether we're waiting for message acknowledgment.
bool get isWaitingForAck => _isWaitingForAck;
/// Emotes matching the current autocomplete search term.
@computed
List<Emote> get matchingEmotes {
@ -637,6 +640,10 @@ abstract class ChatStoreBase with Store {
// Reset exponential backoff if successfully connected.
_retries = 0;
_backoffTime = 0;
// Mark connection as established for UI
_isConnected = true;
_hasConnected = true;
}
}
}
@ -839,6 +846,9 @@ abstract class ChatStoreBase with Store {
// Invalidate the current connection to cancel pending delayed callbacks
currentConnectionId = 0;
// Mark connection as disconnected for UI
_isConnected = false;
// Cancel chat delay countdown when disconnecting
_cancelChatDelayCountdown();
_chatDelaySyncCompleted = false;

View file

@ -247,6 +247,46 @@ mixin _$ChatStore on ChatStoreBase, Store {
});
}
late final _$_isConnectedAtom = Atom(
name: 'ChatStoreBase._isConnected',
context: context,
);
bool get isConnected {
_$_isConnectedAtom.reportRead();
return super._isConnected;
}
@override
bool get _isConnected => isConnected;
@override
set _isConnected(bool value) {
_$_isConnectedAtom.reportWrite(value, super._isConnected, () {
super._isConnected = value;
});
}
late final _$_hasConnectedAtom = Atom(
name: 'ChatStoreBase._hasConnected',
context: context,
);
bool get hasConnected {
_$_hasConnectedAtom.reportRead();
return super._hasConnected;
}
@override
bool get _hasConnected => hasConnected;
@override
set _hasConnected(bool value) {
_$_hasConnectedAtom.reportWrite(value, super._hasConnected, () {
super._hasConnected = value;
});
}
late final _$_userStateAtom = Atom(
name: 'ChatStoreBase._userState',
context: context,

View file

@ -245,7 +245,10 @@ class ChatBottomBar extends StatelessWidget {
builder: (context) {
final isLoggedIn = chatStore.auth.isLoggedIn;
final isWaitingForAck = chatStore.isWaitingForAck;
final isEnabled = isLoggedIn && !isWaitingForAck;
final isConnected = chatStore.isConnected;
final hasConnected = chatStore.hasConnected;
final isEnabled =
isLoggedIn && !isWaitingForAck && isConnected;
return GestureDetector(
onTap: !isLoggedIn
@ -285,15 +288,19 @@ class ChatBottomBar extends StatelessWidget {
],
),
hintMaxLines: 1,
hintText: isLoggedIn
? isWaitingForAck
? 'Sending...'
: chatStore.replyingToMessage != null
? 'Reply'
: hasChatDelay
? 'Chat (${chatStore.settings.chatDelay.toInt()}s delay)'
: 'Chat'
: loginTooltipMessage,
hintText: !isLoggedIn
? loginTooltipMessage
: !isConnected
? (hasConnected
? 'Chat disconnected'
: 'Connecting...')
: isWaitingForAck
? 'Sending...'
: chatStore.replyingToMessage != null
? 'Reply'
: hasChatDelay
? 'Chat (${chatStore.settings.chatDelay.toInt()}s delay)'
: 'Chat',
),
controller: chatStore.textController,
onSubmitted: chatStore.sendMessage,
@ -326,7 +333,8 @@ class ChatBottomBar extends StatelessWidget {
: const Icon(Icons.send_rounded),
onPressed:
chatStore.auth.isLoggedIn &&
!chatStore.isWaitingForAck
!chatStore.isWaitingForAck &&
chatStore.isConnected
? () => chatStore.sendMessage(
chatStore.textController.text,
)

View file

@ -215,7 +215,7 @@ abstract class _SettingsStoreBase with Store {
@JsonKey(defaultValue: defaultShowUserNotices)
@observable
var showUserNotices = defaultHighlightFirstTimeChatter;
var showUserNotices = defaultShowUserNotices;
// Layout options
@JsonKey(defaultValue: defaultEmoteMenuButtonOnLeft)