diff --git a/lib/screens/channel/chat/chat.dart b/lib/screens/channel/chat/chat.dart index 059c59d2..1c6293ad 100644 --- a/lib/screens/channel/chat/chat.dart +++ b/lib/screens/channel/chat/chat.dart @@ -107,10 +107,7 @@ class Chat extends StatelessWidget { if (chatStore.assetsStore.showEmoteMenu) SizedBox( height: MediaQuery.of(context).size.height / 3, - child: EmoteMenu( - assetsStore: chatStore.assetsStore, - textController: chatStore.textController, - ), + child: EmoteMenu(chatStore: chatStore), ), ], ), diff --git a/lib/screens/channel/chat/emote_menu/emote_menu.dart b/lib/screens/channel/chat/emote_menu/emote_menu.dart index 4eb43e51..f1ae1bf9 100644 --- a/lib/screens/channel/chat/emote_menu/emote_menu.dart +++ b/lib/screens/channel/chat/emote_menu/emote_menu.dart @@ -3,16 +3,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:frosty/screens/channel/chat/emote_menu/emote_menu_panel.dart'; import 'package:frosty/screens/channel/chat/emote_menu/recent_emotes_panel.dart'; -import 'package:frosty/screens/channel/stores/chat_assets_store.dart'; +import 'package:frosty/screens/channel/stores/chat_store.dart'; class EmoteMenu extends StatefulWidget { - final ChatAssetsStore assetsStore; - final TextEditingController textController; + final ChatStore chatStore; const EmoteMenu({ Key? key, - required this.assetsStore, - required this.textController, + required this.chatStore, }) : super(key: key); @override @@ -20,7 +18,7 @@ class EmoteMenu extends StatefulWidget { } class _EmoteMenuState extends State { - late final PageController _pageContoller = PageController(initialPage: widget.assetsStore.emoteMenuIndex); + late final PageController _pageContoller = PageController(initialPage: widget.chatStore.assetsStore.emoteMenuIndex); @override Widget build(BuildContext context) { @@ -47,9 +45,9 @@ class _EmoteMenuState extends State { duration: const Duration(milliseconds: 200), curve: Curves.ease, ); - widget.assetsStore.emoteMenuIndex = index; + widget.chatStore.assetsStore.emoteMenuIndex = index; }, - style: index == widget.assetsStore.emoteMenuIndex ? null : TextButton.styleFrom(primary: Colors.grey), + style: index == widget.chatStore.assetsStore.emoteMenuIndex ? null : TextButton.styleFrom(primary: Colors.grey), child: Text(section), ), ), @@ -59,39 +57,34 @@ class _EmoteMenuState extends State { ), Expanded( child: PageView( - onPageChanged: (index) => widget.assetsStore.emoteMenuIndex = index, + onPageChanged: (index) => widget.chatStore.assetsStore.emoteMenuIndex = index, controller: _pageContoller, children: [ RecentEmotesPanel( - assetsStore: widget.assetsStore, - textController: widget.textController, + chatStore: widget.chatStore, ), Observer( builder: (_) => EmoteMenuPanel( - assetsStore: widget.assetsStore, - textController: widget.textController, - emotes: widget.assetsStore.userEmoteToObject.values.toList(), + chatStore: widget.chatStore, + emotes: widget.chatStore.assetsStore.userEmoteToObject.values.toList(), ), ), Observer( builder: (_) => EmoteMenuPanel( - assetsStore: widget.assetsStore, - textController: widget.textController, - emotes: widget.assetsStore.bttvEmotes, + chatStore: widget.chatStore, + emotes: widget.chatStore.assetsStore.bttvEmotes, ), ), Observer( builder: (_) => EmoteMenuPanel( - assetsStore: widget.assetsStore, - textController: widget.textController, - emotes: widget.assetsStore.ffzEmotes, + chatStore: widget.chatStore, + emotes: widget.chatStore.assetsStore.ffzEmotes, ), ), Observer( builder: (_) => EmoteMenuPanel( - assetsStore: widget.assetsStore, - textController: widget.textController, - emotes: widget.assetsStore.sevenTVEmotes, + chatStore: widget.chatStore, + emotes: widget.chatStore.assetsStore.sevenTVEmotes, ), ), ], diff --git a/lib/screens/channel/chat/emote_menu/emote_menu_panel.dart b/lib/screens/channel/chat/emote_menu/emote_menu_panel.dart index 88424081..2b4625cb 100644 --- a/lib/screens/channel/chat/emote_menu/emote_menu_panel.dart +++ b/lib/screens/channel/chat/emote_menu/emote_menu_panel.dart @@ -1,18 +1,16 @@ import 'package:flutter/material.dart'; import 'package:frosty/models/emotes.dart'; import 'package:frosty/screens/channel/chat/emote_menu/emote_menu_section.dart'; -import 'package:frosty/screens/channel/stores/chat_assets_store.dart'; +import 'package:frosty/screens/channel/stores/chat_store.dart'; import 'package:frosty/widgets/section_header.dart'; class EmoteMenuPanel extends StatelessWidget { - final ChatAssetsStore assetsStore; - final TextEditingController textController; + final ChatStore chatStore; final List emotes; const EmoteMenuPanel({ Key? key, - required this.assetsStore, - required this.textController, + required this.chatStore, required this.emotes, }) : super(key: key); @@ -48,8 +46,7 @@ class EmoteMenuPanel extends StatelessWidget { ), ), EmoteMenuSection( - assetsStore: assetsStore, - textController: textController, + chatStore: chatStore, emotes: globalEmotes, ), ], @@ -58,8 +55,7 @@ class EmoteMenuPanel extends StatelessWidget { child: SectionHeader('Channel Emotes'), ), EmoteMenuSection( - assetsStore: assetsStore, - textController: textController, + chatStore: chatStore, emotes: channelEmotes, ), ], @@ -68,8 +64,7 @@ class EmoteMenuPanel extends StatelessWidget { child: SectionHeader('Subscribed Emotes'), ), EmoteMenuSection( - assetsStore: assetsStore, - textController: textController, + chatStore: chatStore, emotes: subEmotes, ), ], @@ -78,8 +73,7 @@ class EmoteMenuPanel extends StatelessWidget { child: SectionHeader('Unlocked Emotes'), ), EmoteMenuSection( - assetsStore: assetsStore, - textController: textController, + chatStore: chatStore, emotes: miscEmotes, ), ], diff --git a/lib/screens/channel/chat/emote_menu/emote_menu_section.dart b/lib/screens/channel/chat/emote_menu/emote_menu_section.dart index c40e92a9..769c2505 100644 --- a/lib/screens/channel/chat/emote_menu/emote_menu_section.dart +++ b/lib/screens/channel/chat/emote_menu/emote_menu_section.dart @@ -2,46 +2,38 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:frosty/constants/constants.dart'; import 'package:frosty/models/emotes.dart'; -import 'package:frosty/screens/channel/stores/chat_assets_store.dart'; +import 'package:frosty/screens/channel/stores/chat_store.dart'; import 'package:frosty/screens/settings/stores/settings_store.dart'; import 'package:provider/provider.dart'; class EmoteMenuSection extends StatelessWidget { - final ChatAssetsStore assetsStore; - final TextEditingController textController; + final ChatStore chatStore; final List emotes; const EmoteMenuSection({ Key? key, - required this.assetsStore, - required this.textController, + required this.chatStore, required this.emotes, }) : super(key: key); @override Widget build(BuildContext context) { - return SliverPadding( - padding: const EdgeInsets.all(10.0), - sliver: SliverGrid( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: MediaQuery.of(context).orientation == Orientation.portrait - ? 8 - : context.read().showVideo - ? 6 - : 16, - mainAxisSpacing: 10.0, - crossAxisSpacing: 10.0, - ), - delegate: SliverChildBuilderDelegate( - (context, index) => GestureDetector( - onTap: () { - textController.text += ' ' + emotes[index].name; - assetsStore.recentEmotes.remove(emotes[index]); - assetsStore.recentEmotes.insert(0, emotes[index]); - }, - child: Tooltip( - message: emotes[index].name, - preferBelow: false, + return SliverGrid( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: MediaQuery.of(context).orientation == Orientation.portrait + ? 8 + : context.read().showVideo + ? 6 + : 16, + ), + delegate: SliverChildBuilderDelegate( + (context, index) => InkWell( + onTap: () => chatStore.addEmote(emotes[index]), + child: Tooltip( + message: emotes[index].name, + preferBelow: false, + child: Padding( + padding: const EdgeInsets.all(10.0), child: Center( child: CachedNetworkImage( imageUrl: emotes[index].url, @@ -51,8 +43,8 @@ class EmoteMenuSection extends StatelessWidget { ), ), ), - childCount: emotes.length, ), + childCount: emotes.length, ), ); } diff --git a/lib/screens/channel/chat/emote_menu/recent_emotes_panel.dart b/lib/screens/channel/chat/emote_menu/recent_emotes_panel.dart index a976971b..383ba160 100644 --- a/lib/screens/channel/chat/emote_menu/recent_emotes_panel.dart +++ b/lib/screens/channel/chat/emote_menu/recent_emotes_panel.dart @@ -1,60 +1,91 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:frosty/constants/constants.dart'; -import 'package:frosty/screens/channel/stores/chat_assets_store.dart'; +import 'package:frosty/screens/channel/stores/chat_store.dart'; import 'package:frosty/screens/settings/stores/settings_store.dart'; import 'package:frosty/widgets/section_header.dart'; import 'package:provider/provider.dart'; -class RecentEmotesPanel extends StatelessWidget { - final ChatAssetsStore assetsStore; - final TextEditingController textController; +class RecentEmotesPanel extends StatefulWidget { + final ChatStore chatStore; const RecentEmotesPanel({ Key? key, - required this.assetsStore, - required this.textController, + required this.chatStore, }) : super(key: key); + @override + State createState() => _RecentEmotesPanelState(); +} + +class _RecentEmotesPanelState extends State { + Future _showClearDialog() { + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Clear Recent Emotes'), + content: const Text('Are you sure you want to clear your recent emotes?'), + actions: [ + TextButton( + onPressed: Navigator.of(context).pop, + child: const Text('Cancel'), + style: TextButton.styleFrom(primary: Colors.red), + ), + ElevatedButton( + onPressed: () { + setState(widget.chatStore.assetsStore.recentEmotes.clear); + Navigator.pop(context); + }, + child: const Text('Yes'), + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { return CustomScrollView( slivers: [ - const SliverToBoxAdapter( - child: SectionHeader( - 'Recent Emotes', - padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), + SliverToBoxAdapter( + child: Row( + children: [ + const SectionHeader( + 'Recent Emotes', + padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), + ), + const Spacer(), + SizedBox( + height: 30, + child: TextButton( + onPressed: widget.chatStore.assetsStore.recentEmotes.isEmpty ? null : _showClearDialog, + child: const Text('Clear'), + ), + ), + ], ), ), - SliverPadding( - padding: const EdgeInsets.all(10.0), - sliver: SliverGrid( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: MediaQuery.of(context).orientation == Orientation.portrait - ? 8 - : context.read().showVideo - ? 6 - : 16, - mainAxisSpacing: 10.0, - crossAxisSpacing: 10.0, - ), - delegate: SliverChildBuilderDelegate( - (context, index) { - final emote = assetsStore.recentEmotes[index]; - final validEmotes = [...assetsStore.emoteToObject.values, ...assetsStore.userEmoteToObject.values]; - final matchingEmotes = validEmotes.where((existingEmote) => existingEmote.name == emote.name && existingEmote.type == emote.type); + SliverGrid( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: MediaQuery.of(context).orientation == Orientation.portrait + ? 8 + : context.read().showVideo + ? 6 + : 16, + ), + delegate: SliverChildBuilderDelegate( + (context, index) { + final emote = widget.chatStore.assetsStore.recentEmotes[index]; + final validEmotes = [...widget.chatStore.assetsStore.emoteToObject.values, ...widget.chatStore.assetsStore.userEmoteToObject.values]; + final matchingEmotes = validEmotes.where((existingEmote) => existingEmote.name == emote.name && existingEmote.type == emote.type); - return GestureDetector( - onTap: matchingEmotes.isNotEmpty - ? () { - textController.text += ' ' + emote.name; - assetsStore.recentEmotes.removeWhere((recentEmote) => recentEmote.name == matchingEmotes.first.name); - assetsStore.recentEmotes.insert(0, matchingEmotes.first); - } - : null, - child: Tooltip( - message: emote.name, - preferBelow: false, + return InkWell( + onTap: matchingEmotes.isNotEmpty ? () => widget.chatStore.addEmote(emote) : null, + child: Tooltip( + message: emote.name, + preferBelow: false, + child: Padding( + padding: const EdgeInsets.all(10.0), child: Center( child: CachedNetworkImage( imageUrl: matchingEmotes.isNotEmpty ? matchingEmotes.first.url : emote.url, @@ -65,10 +96,10 @@ class RecentEmotesPanel extends StatelessWidget { ), ), ), - ); - }, - childCount: assetsStore.recentEmotes.length, - ), + ), + ); + }, + childCount: widget.chatStore.assetsStore.recentEmotes.length, ), ), ], diff --git a/lib/screens/channel/chat/widgets/chat_bottom_bar.dart b/lib/screens/channel/chat/widgets/chat_bottom_bar.dart index 57a7911d..cc1e4cbd 100644 --- a/lib/screens/channel/chat/widgets/chat_bottom_bar.dart +++ b/lib/screens/channel/chat/widgets/chat_bottom_bar.dart @@ -1,5 +1,7 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:frosty/constants/constants.dart'; import 'package:frosty/screens/channel/chat/details/chat_details.dart'; import 'package:frosty/screens/channel/stores/chat_store.dart'; @@ -11,61 +13,105 @@ class ChatBottomBar extends StatelessWidget { @override Widget build(BuildContext context) { return Observer( - builder: (context) => Row( - children: [ - IconButton( - icon: Icon(Icons.adaptive.more), - tooltip: 'Chat Details', - onPressed: () => showModalBottomSheet( - isScrollControlled: true, - context: context, - builder: (_) => SizedBox( - height: MediaQuery.of(context).size.height * 0.8, - child: ChatDetails( - chatDetails: chatStore.chatDetailsStore, - chatStore: chatStore, - userLogin: chatStore.channelName, - ), + builder: (context) { + final emotes = [ + ...chatStore.assetsStore.userEmoteToObject.values, + ...chatStore.assetsStore.bttvEmotes, + ...chatStore.assetsStore.ffzEmotes, + ...chatStore.assetsStore.sevenTVEmotes + ].where((emote) => emote.name.toLowerCase().contains(chatStore.textController.text.split(' ').last.toLowerCase())).toList(); + + return Column( + children: [ + if (chatStore.settings.emoteAutocomplete && chatStore.showAutocomplete && emotes.isNotEmpty) ...[ + const Divider( + height: 1.0, + thickness: 1.0, ), - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), - child: TextField( - focusNode: chatStore.textFieldFocusNode, - minLines: 1, - maxLines: 5, - enabled: chatStore.auth.isLoggedIn ? true : false, - decoration: InputDecoration( - suffixIcon: IconButton( - tooltip: 'Emote Menu', - icon: const Icon(Icons.emoji_emotions_outlined), - onPressed: () { - FocusScope.of(context).unfocus(); - chatStore.assetsStore.showEmoteMenu = !chatStore.assetsStore.showEmoteMenu; - }, + SizedBox( + height: 50, + child: ListView.builder( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + itemCount: emotes.length, + scrollDirection: Axis.horizontal, + itemBuilder: (context, index) => InkWell( + onTap: () => chatStore.addEmote(emotes[index], autocompleteMode: true), + child: Tooltip( + message: emotes[index].name, + preferBelow: false, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: Center( + child: CachedNetworkImage( + imageUrl: emotes[index].url, + fadeInDuration: const Duration(), + height: emotes[index].height?.toDouble() ?? defaultEmoteSize, + width: emotes[index].width?.toDouble(), + ), + ), + ), + ), ), - isDense: true, - hintMaxLines: 1, - contentPadding: const EdgeInsets.all(10.0), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(15.0)), - ), - hintText: chatStore.auth.isLoggedIn ? 'Send a message' : 'Log in to chat', ), - controller: chatStore.textController, - onSubmitted: chatStore.sendMessage, - ), + ) + ], + Row( + children: [ + IconButton( + icon: Icon(Icons.adaptive.more), + tooltip: 'Chat Details', + onPressed: () => showModalBottomSheet( + isScrollControlled: true, + context: context, + builder: (_) => SizedBox( + height: MediaQuery.of(context).size.height * 0.8, + child: ChatDetails( + chatDetails: chatStore.chatDetailsStore, + chatStore: chatStore, + userLogin: chatStore.channelName, + ), + ), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), + child: TextField( + focusNode: chatStore.textFieldFocusNode, + minLines: 1, + maxLines: 5, + enabled: chatStore.auth.isLoggedIn ? true : false, + decoration: InputDecoration( + suffixIcon: IconButton( + tooltip: 'Emote Menu', + icon: const Icon(Icons.emoji_emotions_outlined), + onPressed: () { + FocusScope.of(context).unfocus(); + chatStore.assetsStore.showEmoteMenu = !chatStore.assetsStore.showEmoteMenu; + }, + ), + hintMaxLines: 1, + contentPadding: const EdgeInsets.all(10.0), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(15.0)), + ), + hintText: chatStore.auth.isLoggedIn ? 'Send a message' : 'Log in to chat', + ), + controller: chatStore.textController, + onSubmitted: chatStore.sendMessage, + ), + ), + ), + IconButton( + tooltip: 'Send', + icon: const Icon(Icons.send), + onPressed: chatStore.auth.isLoggedIn ? () => chatStore.sendMessage(chatStore.textController.text) : null, + ) + ], ), - ), - IconButton( - tooltip: 'Send', - icon: const Icon(Icons.send), - onPressed: chatStore.auth.isLoggedIn ? () => chatStore.sendMessage(chatStore.textController.text) : null, - ) - ], - ), + ], + ); + }, ); } } diff --git a/lib/screens/channel/stores/chat_store.dart b/lib/screens/channel/stores/chat_store.dart index 68b33cff..63a5712e 100644 --- a/lib/screens/channel/stores/chat_store.dart +++ b/lib/screens/channel/stores/chat_store.dart @@ -72,6 +72,9 @@ abstract class _ChatStoreBase with Store { @readonly var _autoScroll = true; + @readonly + var _showAutocomplete = false; + /// The logged-in user's appearance in chat. @readonly var _userState = const USERSTATE(); @@ -129,7 +132,7 @@ abstract class _ChatStoreBase with Store { } }); - // Add a listener to the textfield that will do the following when focused: + // Add a listener to the textfield focus that will do the following when focused: // 1. Hide the emote menu if it is currently shown // 2. Scroll to the latest message (after a brief delay) textFieldFocusNode.addListener(() { @@ -142,6 +145,11 @@ abstract class _ChatStoreBase with Store { ); } }); + + // Add a listener to the textfield that will show/hide the autocomplete bar if focused. + // Will also rebuild the autocomplete bar when typing, refreshing the results as the user types. + textController + .addListener(() => _showAutocomplete = textFieldFocusNode.hasFocus && textController.text.split(' ').last.isNotEmpty ? true : false); } /// Handle and process the provided string-representation of the IRC data. @@ -344,6 +352,28 @@ abstract class _ChatStoreBase with Store { textController.clear(); } + /// Adds the given [emote] to the chat textfield. + @action + void addEmote(Emote emote, {bool autocompleteMode = false}) { + if (textController.text.isEmpty || textController.text.endsWith(' ')) { + textController.text += emote.name + ' '; + } else if (autocompleteMode && _showAutocomplete && textController.text.endsWith('')) { + final split = textController.text.split(' ') + ..removeLast() + ..add(emote.name + ' '); + + textController.text = split.join(' '); + } else { + textController.text += ' ' + emote.name + ' '; + } + + assetsStore.recentEmotes + ..removeWhere((recentEmote) => recentEmote.name == emote.name && recentEmote.type == emote.type) + ..insert(0, emote); + + textController.selection = TextSelection.fromPosition(TextPosition(offset: textController.text.length)); + } + /// Closes and disposes all the channels and controllers used by the store. void dispose() { _channel?.sink.close(1001); diff --git a/lib/screens/channel/stores/chat_store.g.dart b/lib/screens/channel/stores/chat_store.g.dart index ef4b9577..a1895d0f 100644 --- a/lib/screens/channel/stores/chat_store.g.dart +++ b/lib/screens/channel/stores/chat_store.g.dart @@ -43,6 +43,24 @@ mixin _$ChatStore on _ChatStoreBase, Store { }); } + final _$_showAutocompleteAtom = + Atom(name: '_ChatStoreBase._showAutocomplete'); + + bool get showAutocomplete { + _$_showAutocompleteAtom.reportRead(); + return super._showAutocomplete; + } + + @override + bool get _showAutocomplete => showAutocomplete; + + @override + set _showAutocomplete(bool value) { + _$_showAutocompleteAtom.reportWrite(value, super._showAutocomplete, () { + super._showAutocomplete = value; + }); + } + final _$_userStateAtom = Atom(name: '_ChatStoreBase._userState'); USERSTATE get userState { @@ -107,6 +125,17 @@ mixin _$ChatStore on _ChatStoreBase, Store { } } + @override + void addEmote(Emote emote, {bool autocompleteMode = false}) { + final _$actionInfo = _$_ChatStoreBaseActionController.startAction( + name: '_ChatStoreBase.addEmote'); + try { + return super.addEmote(emote, autocompleteMode: autocompleteMode); + } finally { + _$_ChatStoreBaseActionController.endAction(_$actionInfo); + } + } + @override String toString() { return ''' diff --git a/lib/screens/settings/sections/chat_settings.dart b/lib/screens/settings/sections/chat_settings.dart index 540b81e9..29ac6223 100644 --- a/lib/screens/settings/sections/chat_settings.dart +++ b/lib/screens/settings/sections/chat_settings.dart @@ -36,10 +36,24 @@ class _ChatSettingsState extends State { children: [ SwitchListTile.adaptive( title: const Text('Bottom Bar'), - subtitle: const Text('Displays the bottom text field bar.'), + subtitle: const Text('Shows the bottom text field bar.'), value: settingsStore.showBottomBar, onChanged: (newValue) => settingsStore.showBottomBar = newValue, ), + SwitchListTile.adaptive( + isThreeLine: true, + title: const Text('Emote Autocomplete'), + subtitle: const Text('Shows a bar that suggests matching emotes when typing.'), + value: settingsStore.emoteAutocomplete, + onChanged: settingsStore.showBottomBar ? (newValue) => settingsStore.emoteAutocomplete = newValue : null, + ), + SwitchListTile.adaptive( + isThreeLine: true, + title: const Text('Zero-Width Emotes'), + subtitle: const Text('Shows "stacked" emotes from BetterTTV and 7TV.'), + value: settingsStore.showZeroWidth, + onChanged: (newValue) => settingsStore.showZeroWidth = newValue, + ), SwitchListTile.adaptive( isThreeLine: true, title: const Text('Readable Name Colors'), @@ -47,13 +61,6 @@ class _ChatSettingsState extends State { value: settingsStore.useReadableColors, onChanged: (newValue) => settingsStore.useReadableColors = newValue, ), - SwitchListTile.adaptive( - isThreeLine: true, - title: const Text('Zero-Width Emotes'), - subtitle: const Text('Displays "stacked" emotes from BetterTTV and 7TV.'), - value: settingsStore.showZeroWidth, - onChanged: (newValue) => settingsStore.showZeroWidth = newValue, - ), SwitchListTile.adaptive( isThreeLine: true, title: const Text('Show Deleted Messages'), @@ -64,14 +71,14 @@ class _ChatSettingsState extends State { SwitchListTile.adaptive( isThreeLine: true, title: const Text('Message Dividers'), - subtitle: const Text('Displays a subtle divider between each message.'), + subtitle: const Text('Shows a subtle divider between each message.'), value: settingsStore.showChatMessageDividers, onChanged: (newValue) => settingsStore.showChatMessageDividers = newValue, ), ListTile( isThreeLine: true, title: const Text('Message Timestamps'), - subtitle: const Text('Displays timestamps for when a message was sent.'), + subtitle: const Text('Shows timestamps for when a message was sent.'), trailing: DropdownButton( value: settingsStore.timestampType, onChanged: (TimestampType? newTimestamp) => settingsStore.timestampType = newTimestamp!, diff --git a/lib/screens/settings/sections/general_settings.dart b/lib/screens/settings/sections/general_settings.dart index d7d8c344..bcac6cc5 100644 --- a/lib/screens/settings/sections/general_settings.dart +++ b/lib/screens/settings/sections/general_settings.dart @@ -43,7 +43,7 @@ class GeneralSettings extends StatelessWidget { SwitchListTile.adaptive( isThreeLine: true, title: const Text('Stream Uptime on Thumbnails'), - subtitle: const Text('Displays the uptime of the stream in the HH:MM:SS format.'), + subtitle: const Text('Shows the uptime of the stream in the HH:MM:SS format.'), value: settingsStore.showThumbnailUptime, onChanged: settingsStore.showThumbnails ? (newValue) => settingsStore.showThumbnailUptime = newValue : null, ), diff --git a/lib/screens/settings/sections/other_settings.dart b/lib/screens/settings/sections/other_settings.dart index 0df8f177..7d8278b2 100644 --- a/lib/screens/settings/sections/other_settings.dart +++ b/lib/screens/settings/sections/other_settings.dart @@ -95,7 +95,7 @@ class OtherSettings extends StatelessWidget { ), ), Container( - padding: const EdgeInsets.symmetric(horizontal: 10.0), + padding: const EdgeInsets.symmetric(horizontal: 15.0), width: double.infinity, child: OutlinedButton.icon( icon: const Icon(Icons.delete_sweep), @@ -112,7 +112,7 @@ class OtherSettings extends StatelessWidget { ), ), Container( - padding: const EdgeInsets.symmetric(horizontal: 10.0), + padding: const EdgeInsets.symmetric(horizontal: 15.0), width: double.infinity, child: OutlinedButton.icon( icon: const Icon(Icons.restore), diff --git a/lib/screens/settings/stores/settings_store.dart b/lib/screens/settings/stores/settings_store.dart index 8c32b454..f26d76dd 100644 --- a/lib/screens/settings/stores/settings_store.dart +++ b/lib/screens/settings/stores/settings_store.dart @@ -79,6 +79,8 @@ abstract class _SettingsStoreBase with Store { static const defaultEmoteScale = 1.0; + static const defaultEmoteAutocomplete = true; + @JsonKey(defaultValue: defaultShowBottomBar) @observable var showBottomBar = defaultShowBottomBar; @@ -123,6 +125,10 @@ abstract class _SettingsStoreBase with Store { @observable var emoteScale = defaultEmoteScale; + @JsonKey(defaultValue: defaultEmoteAutocomplete) + @observable + var emoteAutocomplete = defaultEmoteAutocomplete; + // * Other settings static const defaultSendCrashLogs = true; @@ -168,6 +174,7 @@ abstract class _SettingsStoreBase with Store { messageSpacing = defaultMessageSpacing; badgeScale = defaultBadgeScale; emoteScale = defaultEmoteScale; + emoteAutocomplete = defaultEmoteAutocomplete; // * Other settings sendCrashLogs = defaultSendCrashLogs; diff --git a/lib/screens/settings/stores/settings_store.g.dart b/lib/screens/settings/stores/settings_store.g.dart index 1de63102..06fcbc08 100644 --- a/lib/screens/settings/stores/settings_store.g.dart +++ b/lib/screens/settings/stores/settings_store.g.dart @@ -32,6 +32,7 @@ SettingsStore _$SettingsStoreFromJson(Map json) => ..messageScale = (json['messageScale'] as num?)?.toDouble() ?? 1.0 ..badgeScale = (json['badgeScale'] as num?)?.toDouble() ?? 1.0 ..emoteScale = (json['emoteScale'] as num?)?.toDouble() ?? 1.0 + ..emoteAutocomplete = json['emoteAutocomplete'] as bool? ?? true ..sendCrashLogs = json['sendCrashLogs'] as bool? ?? true ..fullScreen = json['fullScreen'] as bool? ?? false ..expandInfo = json['expandInfo'] as bool? ?? true; @@ -56,6 +57,7 @@ Map _$SettingsStoreToJson(SettingsStore instance) => 'messageScale': instance.messageScale, 'badgeScale': instance.badgeScale, 'emoteScale': instance.emoteScale, + 'emoteAutocomplete': instance.emoteAutocomplete, 'sendCrashLogs': instance.sendCrashLogs, 'fullScreen': instance.fullScreen, 'expandInfo': instance.expandInfo, @@ -358,6 +360,22 @@ mixin _$SettingsStore on _SettingsStoreBase, Store { }); } + final _$emoteAutocompleteAtom = + Atom(name: '_SettingsStoreBase.emoteAutocomplete'); + + @override + bool get emoteAutocomplete { + _$emoteAutocompleteAtom.reportRead(); + return super.emoteAutocomplete; + } + + @override + set emoteAutocomplete(bool value) { + _$emoteAutocompleteAtom.reportWrite(value, super.emoteAutocomplete, () { + super.emoteAutocomplete = value; + }); + } + final _$sendCrashLogsAtom = Atom(name: '_SettingsStoreBase.sendCrashLogs'); @override @@ -438,6 +456,7 @@ messageSpacing: ${messageSpacing}, messageScale: ${messageScale}, badgeScale: ${badgeScale}, emoteScale: ${emoteScale}, +emoteAutocomplete: ${emoteAutocomplete}, sendCrashLogs: ${sendCrashLogs}, fullScreen: ${fullScreen}, expandInfo: ${expandInfo}