From 4a9678bb152bb89f965a0f16f9bad938bb3eb374 Mon Sep 17 00:00:00 2001 From: Tommy Chow Date: Sun, 4 Jan 2026 20:36:53 -0500 Subject: [PATCH 001/158] fix autocomplete bar blocking bottom of chat --- lib/screens/channel/chat/chat.dart | 6 +-- .../channel/chat/stores/chat_store.dart | 47 +++++++++++++++++++ .../channel/chat/stores/chat_store.g.dart | 28 ++++++++++- .../channel/chat/widgets/chat_bottom_bar.dart | 27 +---------- 4 files changed, 79 insertions(+), 29 deletions(-) diff --git a/lib/screens/channel/chat/chat.dart b/lib/screens/channel/chat/chat.dart index 944ba3d7..18ec8e3e 100644 --- a/lib/screens/channel/chat/chat.dart +++ b/lib/screens/channel/chat/chat.dart @@ -58,7 +58,7 @@ class Chat extends StatelessWidget { padding: EdgeInsets.only( top: MediaQuery.of(context).padding.top, bottom: - 68 + + chatStore.bottomBarHeight + (chatStore.assetsStore.showEmoteMenu ? 0 : MediaQuery.of(context).padding.bottom), @@ -70,7 +70,7 @@ class Chat extends StatelessWidget { padding: (listPadding ?? EdgeInsets.zero).add( EdgeInsets.only( bottom: - 68 + + chatStore.bottomBarHeight + (chatStore.assetsStore.showEmoteMenu ? 0 : MediaQuery.of( @@ -126,7 +126,7 @@ class Chat extends StatelessWidget { top: 4, right: 4, bottom: - 68 + + chatStore.bottomBarHeight + (chatStore.assetsStore.showEmoteMenu ? 0 : MediaQuery.of(context).padding.bottom), diff --git a/lib/screens/channel/chat/stores/chat_store.dart b/lib/screens/channel/chat/stores/chat_store.dart index b46e6eb2..da6268fb 100644 --- a/lib/screens/channel/chat/stores/chat_store.dart +++ b/lib/screens/channel/chat/stores/chat_store.dart @@ -36,6 +36,12 @@ abstract class ChatStoreBase with Store { 'ROOMSTATE', }; + /// Base height of the bottom bar (input field area). + static const _baseBottomBarHeight = 68.0; + + /// Height of the autocomplete bar (SizedBox + Divider). + static const _autocompleteHeight = 51.0; + /// Checks if IRC data contains a command that should bypass the chat delay. /// Returns true if any message in the data contains a bypass command. bool _shouldBypassDelay(String data) { @@ -203,6 +209,47 @@ abstract class ChatStoreBase with Store { /// Public getter for whether the chat is in shared chat mode. bool get isInSharedChatMode => _isInSharedChatMode; + /// Emotes matching the current autocomplete search term. + @computed + List get matchingEmotes { + if (!_showEmoteAutocomplete) return const []; + final searchTerm = _inputText.split(' ').last.toLowerCase(); + if (searchTerm.isEmpty) return const []; + + return [ + ...assetsStore.userEmoteToObject.values, + ...assetsStore.bttvEmotes, + ...assetsStore.ffzEmotes, + ...assetsStore.sevenTVEmotes, + ].where((emote) => emote.name.toLowerCase().contains(searchTerm)).toList(); + } + + /// Chatters matching the current mention autocomplete search term. + @computed + List get matchingChatters { + if (!_showMentionAutocomplete) return const []; + final searchTerm = + _inputText.split(' ').last.replaceFirst('@', '').toLowerCase(); + return chatDetailsStore.chatUsers + .where((chatter) => chatter.contains(searchTerm)) + .toList(); + } + + /// Current bottom bar height based on visible overlays. + /// Used to dynamically adjust chat message list padding. + @computed + double get bottomBarHeight { + var height = _baseBottomBarHeight; + + if (settings.autocomplete && + ((_showEmoteAutocomplete && matchingEmotes.isNotEmpty) || + (_showMentionAutocomplete && matchingChatters.isNotEmpty))) { + height += _autocompleteHeight; + } + + return height; + } + ChatStoreBase({ required this.twitchApi, required this.auth, diff --git a/lib/screens/channel/chat/stores/chat_store.g.dart b/lib/screens/channel/chat/stores/chat_store.g.dart index 9292169e..7e956e78 100644 --- a/lib/screens/channel/chat/stores/chat_store.g.dart +++ b/lib/screens/channel/chat/stores/chat_store.g.dart @@ -17,6 +17,29 @@ mixin _$ChatStore on ChatStoreBase, Store { () => super.renderMessages, name: 'ChatStoreBase.renderMessages', )).value; + Computed>? _$matchingEmotesComputed; + + @override + List get matchingEmotes => + (_$matchingEmotesComputed ??= Computed>( + () => super.matchingEmotes, + name: 'ChatStoreBase.matchingEmotes', + )).value; + Computed>? _$matchingChattersComputed; + + @override + List get matchingChatters => + (_$matchingChattersComputed ??= Computed>( + () => super.matchingChatters, + name: 'ChatStoreBase.matchingChatters', + )).value; + Computed? _$bottomBarHeightComputed; + + @override + double get bottomBarHeight => (_$bottomBarHeightComputed ??= Computed( + () => super.bottomBarHeight, + name: 'ChatStoreBase.bottomBarHeight', + )).value; late final _$timeRemainingAtom = Atom( name: 'ChatStoreBase.timeRemaining', @@ -489,7 +512,10 @@ mixin _$ChatStore on ChatStoreBase, Store { timeRemaining: ${timeRemaining}, expandChat: ${expandChat}, replyingToMessage: ${replyingToMessage}, -renderMessages: ${renderMessages} +renderMessages: ${renderMessages}, +matchingEmotes: ${matchingEmotes}, +matchingChatters: ${matchingChatters}, +bottomBarHeight: ${bottomBarHeight} '''; } } diff --git a/lib/screens/channel/chat/widgets/chat_bottom_bar.dart b/lib/screens/channel/chat/widgets/chat_bottom_bar.dart index e74ccd02..dd0c9304 100644 --- a/lib/screens/channel/chat/widgets/chat_bottom_bar.dart +++ b/lib/screens/channel/chat/widgets/chat_bottom_bar.dart @@ -57,31 +57,8 @@ class ChatBottomBar extends StatelessWidget { return Observer( builder: (context) { - final matchingEmotes = - [ - ...chatStore.assetsStore.userEmoteToObject.values, - ...chatStore.assetsStore.bttvEmotes, - ...chatStore.assetsStore.ffzEmotes, - ...chatStore.assetsStore.sevenTVEmotes, - ] - .where( - (emote) => emote.name.toLowerCase().contains( - chatStore.inputText.split(' ').last.toLowerCase(), - ), - ) - .toList(); - - final matchingChatters = chatStore.chatDetailsStore.chatUsers - .where( - (chatter) => chatter.contains( - chatStore.inputText - .split(' ') - .last - .replaceFirst('@', '') - .toLowerCase(), - ), - ) - .toList(); + final matchingEmotes = chatStore.matchingEmotes; + final matchingChatters = chatStore.matchingChatters; final isFullscreenOverlay = chatStore.settings.fullScreen && context.isLandscape; From 08913c1e351ddd37c1ae76f2a6c686787a20f1dd Mon Sep 17 00:00:00 2001 From: Tommy Chow Date: Sun, 4 Jan 2026 21:36:00 -0500 Subject: [PATCH 002/158] fix reply bar blocking bottom of chat, redesign --- .../channel/chat/stores/chat_store.dart | 9 +++ .../channel/chat/widgets/chat_bottom_bar.dart | 76 ++++++++++++------- 2 files changed, 57 insertions(+), 28 deletions(-) diff --git a/lib/screens/channel/chat/stores/chat_store.dart b/lib/screens/channel/chat/stores/chat_store.dart index da6268fb..b28e77cc 100644 --- a/lib/screens/channel/chat/stores/chat_store.dart +++ b/lib/screens/channel/chat/stores/chat_store.dart @@ -42,6 +42,9 @@ abstract class ChatStoreBase with Store { /// Height of the autocomplete bar (SizedBox + Divider). static const _autocompleteHeight = 51.0; + /// Height of the reply bar (Container + Divider). + static const _replyBarHeight = 41.0; + /// Checks if IRC data contains a command that should bypass the chat delay. /// Returns true if any message in the data contains a bypass command. bool _shouldBypassDelay(String data) { @@ -241,6 +244,12 @@ abstract class ChatStoreBase with Store { double get bottomBarHeight { var height = _baseBottomBarHeight; + // Add reply bar height when replying to a message. + if (replyingToMessage != null) { + height += _replyBarHeight; + } + + // Add autocomplete bar height when showing matches. if (settings.autocomplete && ((_showEmoteAutocomplete && matchingEmotes.isNotEmpty) || (_showMentionAutocomplete && matchingChatters.isNotEmpty))) { diff --git a/lib/screens/channel/chat/widgets/chat_bottom_bar.dart b/lib/screens/channel/chat/widgets/chat_bottom_bar.dart index dd0c9304..c262d54e 100644 --- a/lib/screens/channel/chat/widgets/chat_bottom_bar.dart +++ b/lib/screens/channel/chat/widgets/chat_bottom_bar.dart @@ -73,37 +73,57 @@ class ChatBottomBar extends StatelessWidget { children: [ if (chatStore.replyingToMessage != null) ...[ const Divider(), - ListTile( - contentPadding: const EdgeInsets.only(left: 16), - leading: const Icon(Icons.reply), - title: Tooltip( - message: chatStore.replyingToMessage!.message, - preferBelow: false, - child: Text.rich( - TextSpan( - children: chatStore.replyingToMessage!.generateSpan( - context, - assetsStore: chatStore.assetsStore, - emoteScale: chatStore.settings.emoteScale, - badgeScale: chatStore.settings.badgeScale, - launchExternal: chatStore.settings.launchUrlExternal, - timestamp: chatStore.settings.timestampType, - currentChannelId: chatStore.channelId, + TextFieldTapRegion( + child: Container( + height: 40, + decoration: BoxDecoration( + border: Border( + left: BorderSide( + color: Theme.of(context).colorScheme.secondary, + width: 4, ), ), - maxLines: 3, - overflow: TextOverflow.ellipsis, - style: context.defaultTextStyle.copyWith( - fontSize: chatStore.settings.fontSize, - ), ), - ), - trailing: IconButton( - tooltip: 'Cancel reply', - onPressed: () { - chatStore.replyingToMessage = null; - }, - icon: const Icon(Icons.close), + // Left: 8px so text aligns at 12px (4px border + 8px padding) + // Right: 4px to give close button some breathing room + padding: const EdgeInsets.only(left: 8, right: 4), + alignment: Alignment.centerLeft, + child: Row( + children: [ + Expanded( + child: Tooltip( + message: chatStore.replyingToMessage!.message ?? '', + preferBelow: false, + child: Text.rich( + TextSpan( + children: + chatStore.replyingToMessage!.generateSpan( + context, + assetsStore: chatStore.assetsStore, + emoteScale: chatStore.settings.emoteScale, + badgeScale: chatStore.settings.badgeScale, + launchExternal: + chatStore.settings.launchUrlExternal, + timestamp: chatStore.settings.timestampType, + currentChannelId: chatStore.channelId, + ), + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: context.defaultTextStyle.copyWith( + fontSize: chatStore.settings.fontSize, + ), + ), + ), + ), + IconButton( + tooltip: 'Cancel reply', + visualDensity: VisualDensity.compact, + onPressed: () => chatStore.replyingToMessage = null, + icon: const Icon(Icons.close, size: 20), + ), + ], + ), ), ), ], From 04e076747b54821b87e9a88249a3cc09275dfdd1 Mon Sep 17 00:00:00 2001 From: Tommy Chow Date: Sun, 4 Jan 2026 22:18:07 -0500 Subject: [PATCH 003/158] fix error message --- lib/screens/home/search/search_results_channels.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/screens/home/search/search_results_channels.dart b/lib/screens/home/search/search_results_channels.dart index f7413995..4bde37a7 100644 --- a/lib/screens/home/search/search_results_channels.dart +++ b/lib/screens/home/search/search_results_channels.dart @@ -57,11 +57,11 @@ class _SearchResultsChannelsState extends State { debugPrint('Search channels error: $error'); final snackBar = SnackBar( content: AlertMessage( - message: 'Unable to follow channel', + message: 'Unable to find channel', centered: false, ), ); - + if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar(snackBar); } } From aaad50c10d8f2c789a997b890730169213cb3562 Mon Sep 17 00:00:00 2001 From: Tommy Chow Date: Sun, 4 Jan 2026 22:21:22 -0500 Subject: [PATCH 004/158] release v5.0.2 --- assets/release-notes.md | 20 ++++++++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/assets/release-notes.md b/assets/release-notes.md index 5d87fabe..26314658 100644 --- a/assets/release-notes.md +++ b/assets/release-notes.md @@ -1,3 +1,23 @@ +## v5.0.2 + +January 4, 2026 + +### Improvements + +- Inline emote support in the chat text field. +- Search page now uses type-to-search for faster navigation. +- Restored ability to send messages while chat delay is active. +- Restored chat delay input indicator. +- Redesigned the reply bar. + +### Bug Fixes + +- Fixed reply bar and autocomplete bar blocking the bottom of chat. +- Fixed clear recent emotes button sizing. +- Fixed black screen when logging in. + +--- + ## v5.0.1 January 2, 2026 diff --git a/pubspec.yaml b/pubspec.yaml index 661a57d3..c9c20fed 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 5.0.1+64 +version: 5.0.2+65 environment: sdk: ^3.9.0 From 4d0b75a1c55b732ae1128d9d49651df08b8bfe6c Mon Sep 17 00:00:00 2001 From: Tommy Chow Date: Mon, 5 Jan 2026 23:40:31 -0500 Subject: [PATCH 005/158] rename enhanced rendering setting to reset for all users --- lib/screens/channel/video/video.dart | 2 +- .../settings/stores/settings_store.dart | 8 +++--- .../settings/stores/settings_store.g.dart | 28 ++++++++----------- lib/screens/settings/video_settings.dart | 6 ++-- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/lib/screens/channel/video/video.dart b/lib/screens/channel/video/video.dart index 8d99146b..fa9cc703 100644 --- a/lib/screens/channel/video/video.dart +++ b/lib/screens/channel/video/video.dart @@ -47,7 +47,7 @@ class _VideoState extends State