add focus current channel toggle to fade non-active shared chat messages

Replaces the merge button in the chat tab bar with a more-actions menu
that exposes both "Merge chats" and "Focus current channel" toggles.
When focus mode is on, messages whose source channel differs from the
active channel render at reduced opacity in both Twitch native shared
chat and merged-tabs views.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tommy Chow 2026-04-18 15:18:08 -07:00
parent 12c2fed7da
commit f2de6c653d
5 changed files with 95 additions and 18 deletions

View file

@ -609,6 +609,19 @@ class ChatMessage extends StatelessWidget {
child: dividedMessage,
);
final sourceChannelId =
ircMessage.tags['source-room-id'] ?? ircMessage.tags['room-id'];
final currentChannelId =
overrideCurrentChannelId ?? chatStore.channelId;
final shouldFade =
chatStore.settings.focusCurrentChannel &&
sourceChannelId != null &&
sourceChannelId != currentChannelId;
final fadedMessage = shouldFade
? Opacity(opacity: 0.4, child: coloredMessage)
: coloredMessage;
final finalMessage = InkWell(
onTap: () {
FocusScope.of(context).unfocus();
@ -617,7 +630,7 @@ class ChatMessage extends StatelessWidget {
}
},
onLongPress: () => onLongPressMessage(context, defaultTextStyle),
child: coloredMessage,
child: fadedMessage,
);
return finalMessage;

View file

@ -171,7 +171,9 @@ class ChatTabs extends StatelessWidget {
right: 12,
top: 0,
bottom: 0,
child: Center(child: _buildMergeToggle(context)),
child: Center(
child: _buildMoreActionsMenu(context),
),
),
],
),
@ -184,22 +186,47 @@ class ChatTabs extends StatelessWidget {
);
}
Widget _buildMergeToggle(BuildContext context) {
final isMerged = chatTabsStore.mergedMode;
return IconButton.filledTonal(
icon: const Icon(Icons.call_merge, size: 18),
tooltip: isMerged ? 'Split chats' : 'Merge loaded chats',
visualDensity: VisualDensity.compact,
isSelected: isMerged,
style: IconButton.styleFrom(
minimumSize: const Size(42, 42),
backgroundColor: isMerged
? null
: Theme.of(context).colorScheme.surfaceContainerHighest,
),
onPressed: () {
HapticFeedback.selectionClick();
chatTabsStore.toggleMergedMode();
Widget _buildMoreActionsMenu(BuildContext context) {
final settings = chatTabsStore.activeChatStore.settings;
return MenuAnchor(
menuChildren: [
Observer(
builder: (_) => CheckboxMenuButton(
value: chatTabsStore.mergedMode,
onChanged: (_) {
HapticFeedback.selectionClick();
chatTabsStore.toggleMergedMode();
},
child: const Text('Merge chats'),
),
),
Observer(
builder: (_) => CheckboxMenuButton(
value: settings.focusCurrentChannel,
onChanged: (newValue) {
HapticFeedback.selectionClick();
settings.focusCurrentChannel = newValue ?? false;
},
child: const Text('Focus current channel'),
),
),
],
builder: (context, controller, child) {
return IconButton.filledTonal(
icon: const Icon(Icons.more_vert, size: 18),
tooltip: 'Chat options',
visualDensity: VisualDensity.compact,
style: IconButton.styleFrom(
minimumSize: const Size(42, 42),
backgroundColor:
Theme.of(context).colorScheme.surfaceContainerHighest,
),
onPressed: () {
HapticFeedback.selectionClick();
controller.isOpen ? controller.close() : controller.open();
},
);
},
);
}

View file

@ -221,6 +221,16 @@ class ChatSettings extends StatelessWidget {
onChanged: (newValue) =>
settingsStore.showHistoricalTimestamps = newValue,
),
SettingsListSwitch(
title: 'Focus current channel',
subtitle: const Text(
'Fades messages from other channels in shared chat and '
'merged views, so the current channel stands out.',
),
value: settingsStore.focusCurrentChannel,
onChanged: (newValue) =>
settingsStore.focusCurrentChannel = newValue,
),
const SectionHeader('Delay'),
SettingsListSwitch(
title: 'Auto-sync chat delay',

View file

@ -128,6 +128,7 @@ abstract class _SettingsStoreBase with Store {
static const defaultShowChatMessageDividers = false;
static const defaultTimestampType = TimestampType.disabled;
static const defaultShowHistoricalTimestamps = false;
static const defaultFocusCurrentChannel = false;
// Alert defaults
static const defaultHighlightFirstTimeChatter = true;
@ -201,6 +202,10 @@ abstract class _SettingsStoreBase with Store {
@observable
var showHistoricalTimestamps = defaultShowHistoricalTimestamps;
@JsonKey(defaultValue: defaultFocusCurrentChannel)
@observable
var focusCurrentChannel = defaultFocusCurrentChannel;
@JsonKey(defaultValue: defaultHighlightFirstTimeChatter)
@observable
var highlightFirstTimeChatter = defaultHighlightFirstTimeChatter;
@ -320,6 +325,7 @@ abstract class _SettingsStoreBase with Store {
showChatMessageDividers = defaultShowChatMessageDividers;
timestampType = defaultTimestampType;
showHistoricalTimestamps = defaultShowHistoricalTimestamps;
focusCurrentChannel = defaultFocusCurrentChannel;
highlightFirstTimeChatter = defaultHighlightFirstTimeChatter;
showUserNotices = defaultShowUserNotices;

View file

@ -43,6 +43,7 @@ SettingsStore _$SettingsStoreFromJson(
TimestampType.disabled
..showHistoricalTimestamps =
json['showHistoricalTimestamps'] as bool? ?? false
..focusCurrentChannel = json['focusCurrentChannel'] as bool? ?? false
..highlightFirstTimeChatter =
json['highlightFirstTimeChatter'] as bool? ?? true
..showUserNotices = json['showUserNotices'] as bool? ?? true
@ -117,6 +118,7 @@ Map<String, dynamic> _$SettingsStoreToJson(
'showChatMessageDividers': instance.showChatMessageDividers,
'timestampType': _$TimestampTypeEnumMap[instance.timestampType]!,
'showHistoricalTimestamps': instance.showHistoricalTimestamps,
'focusCurrentChannel': instance.focusCurrentChannel,
'highlightFirstTimeChatter': instance.highlightFirstTimeChatter,
'showUserNotices': instance.showUserNotices,
'showTwitchEmotes': instance.showTwitchEmotes,
@ -572,6 +574,24 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
);
}
late final _$focusCurrentChannelAtom = Atom(
name: '_SettingsStoreBase.focusCurrentChannel',
context: context,
);
@override
bool get focusCurrentChannel {
_$focusCurrentChannelAtom.reportRead();
return super.focusCurrentChannel;
}
@override
set focusCurrentChannel(bool value) {
_$focusCurrentChannelAtom.reportWrite(value, super.focusCurrentChannel, () {
super.focusCurrentChannel = value;
});
}
late final _$highlightFirstTimeChatterAtom = Atom(
name: '_SettingsStoreBase.highlightFirstTimeChatter',
context: context,
@ -1223,6 +1243,7 @@ showDeletedMessages: ${showDeletedMessages},
showChatMessageDividers: ${showChatMessageDividers},
timestampType: ${timestampType},
showHistoricalTimestamps: ${showHistoricalTimestamps},
focusCurrentChannel: ${focusCurrentChannel},
highlightFirstTimeChatter: ${highlightFirstTimeChatter},
showUserNotices: ${showUserNotices},
showTwitchEmotes: ${showTwitchEmotes},