Tweak emote menu (#47, #56, #57, #58, #59)

The emote menu will now have disabled buttons for whenever the emote
category is empty. Sub emotes for the channel will also be disabled if
the user is not subbed.

Closes #47, closes #56, closes #57, closes #58, closes #59
This commit is contained in:
tommyxchow 2021-12-12 18:51:18 -05:00
parent 60cdf87219
commit f288f0d566
5 changed files with 117 additions and 95 deletions

View file

@ -19,51 +19,50 @@ class Chat extends StatefulWidget {
class _ChatState extends State<Chat> {
@override
Widget build(BuildContext context) {
final chatStore = widget.chatStore;
return FutureBuilder(
future: widget.chatStore.getAssets(),
future: chatStore.getAssets(),
builder: (_, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
child: Observer(
builder: (context) {
return Column(
children: [
Expanded(
child: Stack(
alignment: AlignmentDirectional.bottomCenter,
children: [
Observer(
builder: (_) {
return ListView.builder(
addAutomaticKeepAlives: false,
addRepaintBoundaries: false,
itemCount: widget.chatStore.messages.length,
controller: widget.chatStore.scrollController,
itemBuilder: (context, index) {
return widget.chatStore.renderChatMessage(widget.chatStore.messages[index], context);
},
);
builder: (context) => Column(
children: [
Expanded(
child: Stack(
alignment: AlignmentDirectional.bottomCenter,
children: [
Observer(
builder: (_) => ListView.builder(
addAutomaticKeepAlives: false,
addRepaintBoundaries: false,
itemCount: chatStore.messages.length,
controller: chatStore.scrollController,
itemBuilder: (context, index) {
return chatStore.renderChatMessage(chatStore.messages[index], context);
},
),
Observer(
builder: (_) {
return Visibility(
visible: !widget.chatStore.autoScroll,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
width: double.infinity,
child: ElevatedButton(
onPressed: widget.chatStore.resumeScroll,
child: const Text('Resume Scroll'),
),
),
Observer(
builder: (_) {
return Visibility(
visible: !chatStore.autoScroll,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
width: double.infinity,
child: ElevatedButton(
onPressed: chatStore.resumeScroll,
child: const Text('Resume Scroll'),
),
);
},
),
],
),
),
);
},
),
],
),
),
if (context.read<AuthStore>().isLoggedIn)
Row(
children: [
IconButton(
@ -73,69 +72,66 @@ class _ChatState extends State<Chat> {
context: context,
builder: (context) {
return ChatStats(
chatStore: widget.chatStore,
chatStore: chatStore,
);
},
);
},
),
if (context.read<AuthStore>().isLoggedIn) ...[
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0),
child: TextField(
minLines: 1,
maxLines: 5,
onTap: () => widget.chatStore.showEmoteMenu = false,
decoration: InputDecoration(
suffixIcon: IconButton(
icon: const Icon(Icons.emoji_emotions_outlined),
onPressed: () {
FocusManager.instance.primaryFocus?.unfocus();
widget.chatStore.showEmoteMenu = !widget.chatStore.showEmoteMenu;
},
),
isDense: true,
contentPadding: const EdgeInsets.all(10.0),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
hintText: 'Send a message',
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0),
child: TextField(
minLines: 1,
maxLines: 5,
onTap: () => chatStore.showEmoteMenu = false,
decoration: InputDecoration(
suffixIcon: IconButton(
icon: const Icon(Icons.emoji_emotions_outlined),
onPressed: () {
FocusManager.instance.primaryFocus?.unfocus();
chatStore.showEmoteMenu = !chatStore.showEmoteMenu;
},
),
controller: widget.chatStore.textController,
onSubmitted: (string) => widget.chatStore.sendMessage(string),
isDense: true,
contentPadding: const EdgeInsets.all(10.0),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
hintText: 'Send a message',
),
controller: chatStore.textController,
onSubmitted: chatStore.sendMessage,
),
),
IconButton(
icon: const Icon(Icons.send),
onPressed: () => widget.chatStore.sendMessage(widget.chatStore.textController.text),
)
],
),
IconButton(
icon: const Icon(Icons.send),
onPressed: () => chatStore.sendMessage(chatStore.textController.text),
)
],
),
if (widget.chatStore.showEmoteMenu) ...[
IndexedStack(
index: widget.chatStore.emoteMenuIndex,
children: EmoteType.values.map((type) => EmoteMenu(chatStore: widget.chatStore, emoteType: type)).toList(),
),
SingleChildScrollView(
if (chatStore.showEmoteMenu) ...[
Observer(builder: (_) => EmoteMenu(chatStore: chatStore, emoteType: EmoteType.values[chatStore.emoteMenuIndex])),
Observer(
builder: (_) => SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: EmoteType.values
.asMap()
.entries
.map((e) => TextButton(
style: e.key == widget.chatStore.emoteMenuIndex ? null : TextButton.styleFrom(primary: Colors.grey),
onPressed: () => widget.chatStore.emoteMenuIndex = e.key,
child: Text(widget.chatStore.emoteMenuTitle(e.value)),
))
.toList()),
)
]
],
);
},
children: EmoteType.values.asMap().entries.map((e) {
final emotes = chatStore.emoteToObject.values.toList().where((emote) => emote.type == EmoteType.values[e.key]).toList();
return TextButton(
style: e.key == chatStore.emoteMenuIndex ? null : TextButton.styleFrom(primary: Colors.grey),
onPressed: emotes.isEmpty ? null : () => chatStore.emoteMenuIndex = e.key,
child: Text(chatStore.emoteMenuTitle(e.value)),
);
}).toList(),
),
),
)
]
],
),
),
);
}

View file

@ -47,9 +47,6 @@ abstract class _ChatStoreBase with Store {
/// The provided setting store to account for any user-defined behaviors.
final SettingsStore settings;
/// The logged-in user's appearance in chat.
var _userState = const USERSTATE();
/// Requested message to be sent by the user. Will only be sent on receival of a USERNOTICE command.
IRCMessage? toSend;
@ -65,6 +62,10 @@ abstract class _ChatStoreBase with Store {
@readonly
var _autoScroll = true;
/// The logged-in user's appearance in chat.
@readonly
var _userState = const USERSTATE();
/// The rules and modes being used in the chat.
@readonly
var _roomState = const ROOMSTATE();
@ -378,7 +379,7 @@ abstract class _ChatStoreBase with Store {
case EmoteType.twitchChannel:
return 'Twitch Channel';
case EmoteType.ffzGlobal:
return 'Twitch Global';
return 'FFZ Global';
case EmoteType.ffzChannel:
return 'FFZ Channel';
case EmoteType.bttvGlobal:

View file

@ -56,6 +56,23 @@ mixin _$ChatStore on _ChatStoreBase, Store {
});
}
final _$_userStateAtom = Atom(name: '_ChatStoreBase._userState');
USERSTATE get userState {
_$_userStateAtom.reportRead();
return super._userState;
}
@override
USERSTATE get _userState => userState;
@override
set _userState(USERSTATE value) {
_$_userStateAtom.reportWrite(value, super._userState, () {
super._userState = value;
});
}
final _$_roomStateAtom = Atom(name: '_ChatStoreBase._roomState');
ROOMSTATE get roomState {

View file

@ -21,15 +21,20 @@ class EmoteMenu extends StatelessWidget {
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
),
padding: const EdgeInsets.all(10.0),
itemCount: emotes.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () => chatStore.textController.text += ' ' + emotes[index].name,
onTap: emoteType == EmoteType.twitchChannel && !chatStore.userState.subscriber
? null
: () => chatStore.textController.text += ' ' + emotes[index].name,
child: Tooltip(
message: emotes[index].name,
preferBelow: false,
child: CachedNetworkImage(
imageUrl: emotes[index].url,
color: emoteType == EmoteType.twitchChannel && !chatStore.userState.subscriber ? const Color.fromRGBO(255, 255, 255, 0.5) : null,
colorBlendMode: BlendMode.modulate,
),
),
);

View file

@ -52,11 +52,14 @@ class VideoChat extends StatelessWidget {
IconButton(
icon: const Icon(Icons.settings),
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) {
return Settings(settingsStore: context.read<SettingsStore>());
},
Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) {
return Settings(settingsStore: context.read<SettingsStore>());
},
),
);
},
),