feat(chat): add bubbles for shared chat participant channels (#581)
* feat(chat): add bubbles for shared chat participant channels Twitch's native shared chat previously had no way to interact with participant channels the way manually-added chats work. Adds per-participant bubbles alongside manual tabs: tapping one spotlights that channel (fading the rest), hiding one filters its messages from the feed entirely. Defaults to unfaded, resets on rejoin (fresh ChatStore per connect). Manual "merged mode" fading is untouched and unaffected. * refactor(chat): dedup shared-chat tag lookup and extract fade policy - add IRCMessage.sourceChannelId, replacing three copies of the same source-room-id/room-id tag lookup - move the shared-chat spotlight/merged-mode fade decision out of ChatMessage's build into ChatStore.shouldFadeMessage, matching the extract-into-store convention already used for SharedChatBubbleState - factor the tab-bar item's Padding/Center wrapper into a shared _wrapItem helper instead of duplicating it per branch * fix(chat): don't spotlight a hidden shared-chat bubble toggleFocus didn't check hiddenChannelIds, so tapping a hidden bubble's chip added it to the spotlight set — fading every other participant to showcase a channel whose own messages were already filtered out of the feed. * fix(chat): stop shared-chat detection flapping on system messages _isInSharedChatMode was recomputed from every PRIVMSG/NOTICE/USERNOTICE, but only PRIVMSG reliably carries source-room-id for a session's duration. A sub/raid/announcement without the tag would flip the flag off and back on within a single live session. Previously this only risked a redundant asset refetch (already guarded); now the shared chat bubble bar reads the flag directly, so a flap would visibly collapse and reopen it. Scope detection to PRIVMSG only.
This commit is contained in:
parent
98c3a031bc
commit
abb6cbc446
7 changed files with 321 additions and 60 deletions
57
test/screens/channel/chat/shared_chat_bubble_state_test.dart
Normal file
57
test/screens/channel/chat/shared_chat_bubble_state_test.dart
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:frosty/screens/channel/chat/stores/shared_chat_bubble_state.dart';
|
||||
|
||||
void main() {
|
||||
late SharedChatBubbleState state;
|
||||
setUp(() => state = SharedChatBubbleState());
|
||||
|
||||
test('a channel is neither focused nor hidden by default', () {
|
||||
expect(state.isFocused('1'), isFalse);
|
||||
expect(state.isHidden('1'), isFalse);
|
||||
});
|
||||
|
||||
test('toggleFocus spotlights an unfocused channel', () {
|
||||
state.toggleFocus('1');
|
||||
expect(state.isFocused('1'), isTrue);
|
||||
});
|
||||
|
||||
test('toggleFocus un-spotlights an already-focused channel', () {
|
||||
state.toggleFocus('1');
|
||||
state.toggleFocus('1');
|
||||
expect(state.isFocused('1'), isFalse);
|
||||
});
|
||||
|
||||
test('multiple channels can be spotlighted at once', () {
|
||||
state.toggleFocus('1');
|
||||
state.toggleFocus('2');
|
||||
expect(state.isFocused('1'), isTrue);
|
||||
expect(state.isFocused('2'), isTrue);
|
||||
});
|
||||
|
||||
test('hide filters a channel and drops it from the spotlight', () {
|
||||
state.toggleFocus('1');
|
||||
state.hide('1');
|
||||
expect(state.isHidden('1'), isTrue);
|
||||
expect(state.isFocused('1'), isFalse);
|
||||
});
|
||||
|
||||
test('toggleFocus is a no-op for a hidden channel', () {
|
||||
state.hide('1');
|
||||
state.toggleFocus('1');
|
||||
expect(state.isFocused('1'), isFalse);
|
||||
});
|
||||
|
||||
test('show lifts a hide', () {
|
||||
state.hide('1');
|
||||
state.show('1');
|
||||
expect(state.isHidden('1'), isFalse);
|
||||
});
|
||||
|
||||
test('reset clears both focused and hidden channels', () {
|
||||
state.toggleFocus('1');
|
||||
state.hide('2');
|
||||
state.reset();
|
||||
expect(state.isFocused('1'), isFalse);
|
||||
expect(state.isHidden('2'), isFalse);
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue