Add chat users/modes, refactor chat store
- Added ability to view all chat users in channel - Updated chat modes to icons for better appearence - Refactored chat into individual chat stores for better organization - Fixed ROOMSTATE changes not updating properly
This commit is contained in:
parent
0185b80454
commit
8c77eac1e4
14 changed files with 554 additions and 297 deletions
|
|
@ -53,7 +53,6 @@ class IRCMessage {
|
|||
|
||||
/// Returns a list of messages where the gievn CLEARMSG message is applied.
|
||||
static List<IRCMessage> clearMessage({required List<IRCMessage> messages, required IRCMessage ircMessage}) {
|
||||
// final targetUser = ircMessage.tags['login'];
|
||||
final targetId = ircMessage.tags['target-msg-id'];
|
||||
|
||||
// Search for the message associated with the ID and mark the message for deletion.
|
||||
|
|
@ -402,27 +401,27 @@ class IRCMessage {
|
|||
|
||||
/// The object representation of the IRC ROOMSTATE message.
|
||||
class ROOMSTATE {
|
||||
final bool emoteOnly;
|
||||
final String emoteOnly;
|
||||
final String followersOnly;
|
||||
final bool r9k;
|
||||
final String r9k;
|
||||
final String slowMode;
|
||||
final bool subMode;
|
||||
final String subMode;
|
||||
|
||||
const ROOMSTATE({
|
||||
this.emoteOnly = false,
|
||||
this.emoteOnly = '0',
|
||||
this.followersOnly = '-1',
|
||||
this.r9k = false,
|
||||
this.r9k = '0',
|
||||
this.slowMode = '0',
|
||||
this.subMode = false,
|
||||
this.subMode = '0',
|
||||
});
|
||||
|
||||
/// Create a new copy with the parameters from the provided [IRCMessage]
|
||||
ROOMSTATE fromIRCMessage(IRCMessage ircMessage) => ROOMSTATE(
|
||||
emoteOnly: ircMessage.tags['emote-only'] == '0' ? false : true,
|
||||
emoteOnly: ircMessage.tags['emote-only'] ?? emoteOnly,
|
||||
followersOnly: ircMessage.tags['followers-only'] ?? followersOnly,
|
||||
r9k: ircMessage.tags['r9k'] == '0' ? false : true,
|
||||
r9k: ircMessage.tags['r9k'] ?? r9k,
|
||||
slowMode: ircMessage.tags['slow'] ?? slowMode,
|
||||
subMode: ircMessage.tags['subs-only'] == '0' ? false : true,
|
||||
subMode: ircMessage.tags['subs-only'] ?? subMode,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class _ChatState extends State<Chat> with WidgetsBindingObserver {
|
|||
onTap: () {
|
||||
// If tapping chat, hide the keyboard and emote menu.
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
if (chatStore.showEmoteMenu) chatStore.showEmoteMenu = false;
|
||||
if (chatStore.assetsStore.showEmoteMenu) chatStore.assetsStore.showEmoteMenu = false;
|
||||
},
|
||||
child: Observer(
|
||||
builder: (context) => ListView.separated(
|
||||
|
|
@ -58,7 +58,7 @@ class _ChatState extends State<Chat> with WidgetsBindingObserver {
|
|||
itemBuilder: (context, index) => Observer(
|
||||
builder: (context) => ChatMessage(
|
||||
ircMessage: chatStore.messages[index],
|
||||
chatStore: chatStore,
|
||||
assetsStore: chatStore.assetsStore,
|
||||
hideMessageIfBanned: context.read<SettingsStore>().hideBannedMessages,
|
||||
zeroWidth: context.read<SettingsStore>().zeroWidthEnabled,
|
||||
),
|
||||
|
|
@ -83,7 +83,13 @@ class _ChatState extends State<Chat> with WidgetsBindingObserver {
|
|||
),
|
||||
),
|
||||
if (context.read<AuthStore>().isLoggedIn) ChatBottomBar(chatStore: chatStore),
|
||||
if (chatStore.showEmoteMenu) Expanded(child: EmoteMenu(chatStore: chatStore)),
|
||||
if (chatStore.assetsStore.showEmoteMenu)
|
||||
Expanded(
|
||||
child: EmoteMenu(
|
||||
assetsStore: chatStore.assetsStore,
|
||||
textController: chatStore.textController,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
101
lib/screens/channel/chat/chat_assets_store.dart
Normal file
101
lib/screens/channel/chat/chat_assets_store.dart
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import 'package:frosty/api/bttv_api.dart';
|
||||
import 'package:frosty/api/ffz_api.dart';
|
||||
import 'package:frosty/api/seventv_api.dart';
|
||||
import 'package:frosty/api/twitch_api.dart';
|
||||
import 'package:frosty/models/badges.dart';
|
||||
import 'package:frosty/models/emotes.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'chat_assets_store.g.dart';
|
||||
|
||||
class ChatAssetsStore = _ChatAssetsStoreBase with _$ChatAssetsStore;
|
||||
|
||||
abstract class _ChatAssetsStoreBase with Store {
|
||||
/// The map of emote words to their image or GIF URL. May be used by anyone in the chat.
|
||||
final emoteToObject = ObservableMap<String, Emote>();
|
||||
|
||||
@computed
|
||||
List<Emote> get bttvEmotes => emoteToObject.values.where((emote) => isBTTV(emote)).toList();
|
||||
|
||||
@computed
|
||||
List<Emote> get ffzEmotes => emoteToObject.values.where((emote) => isFFZ(emote)).toList();
|
||||
|
||||
@computed
|
||||
List<Emote> get sevenTvEmotes => emoteToObject.values.where((emote) => is7TV(emote)).toList();
|
||||
|
||||
/// The emotes that are "owned" and may be used by the current user.
|
||||
@readonly
|
||||
var _userEmoteToObject = ObservableMap<String, Emote>();
|
||||
|
||||
/// The map of badges ids to their object representation.
|
||||
final badgesToObject = <String, BadgeInfoTwitch>{};
|
||||
|
||||
/// The current index of the emote menu stack.
|
||||
@observable
|
||||
var emoteMenuIndex = 0;
|
||||
|
||||
/// Whether or not the emote menu is visible.
|
||||
@observable
|
||||
var showEmoteMenu = false;
|
||||
|
||||
bool isBTTV(Emote emote) {
|
||||
return emote.type == EmoteType.bttvChannel || emote.type == EmoteType.bttvGlobal || emote.type == EmoteType.bttvShared;
|
||||
}
|
||||
|
||||
bool isFFZ(Emote emote) {
|
||||
return emote.type == EmoteType.ffzChannel || emote.type == EmoteType.ffzGlobal;
|
||||
}
|
||||
|
||||
bool is7TV(Emote emote) {
|
||||
return emote.type == EmoteType.sevenTvChannel || emote.type == EmoteType.sevenTvGlobal;
|
||||
}
|
||||
|
||||
/// Fetches global and channel assets (badges and emotes) and stores them in [_emoteToUrl]
|
||||
@action
|
||||
Future<void> getAssets({required String channelName, required Map<String, String> headers}) async {
|
||||
// Fetch the desired channel/user's information.
|
||||
final channelInfo = await Twitch.getUser(userLogin: channelName, headers: headers);
|
||||
|
||||
if (channelInfo != null) {
|
||||
// Fetch the global and channel's assets (emotes & badges).
|
||||
// Async awaits are placed in a list so they are performed in parallel.
|
||||
final assets = [
|
||||
...await FFZ.getEmotesGlobal(),
|
||||
...await FFZ.getEmotesChannel(id: channelInfo.id),
|
||||
...await BTTV.getEmotesGlobal(),
|
||||
...await BTTV.getEmotesChannel(id: channelInfo.id),
|
||||
...await Twitch.getEmotesGlobal(headers: headers),
|
||||
...await Twitch.getEmotesChannel(id: channelInfo.id, headers: headers),
|
||||
...await SevenTV.getEmotesGlobal(),
|
||||
...await SevenTV.getEmotesChannel(user: channelInfo.login)
|
||||
];
|
||||
|
||||
for (final emote in assets) {
|
||||
emoteToObject[emote.name] = emote;
|
||||
}
|
||||
|
||||
final badges = [
|
||||
await Twitch.getBadgesGlobal(),
|
||||
await Twitch.getBadgesChannel(id: channelInfo.id),
|
||||
];
|
||||
|
||||
for (final map in badges) {
|
||||
if (map != null) {
|
||||
badgesToObject.addAll(map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> getUserEmotes({List<String>? emoteSets, required Map<String, String> headers}) async {
|
||||
if (emoteSets != null) {
|
||||
final userEmotes = <Emote>[];
|
||||
for (final setId in emoteSets) {
|
||||
userEmotes.addAll(await Twitch.getEmotesSets(setId: setId, headers: headers));
|
||||
}
|
||||
|
||||
_userEmoteToObject = {for (final emote in userEmotes) emote.name: emote}.asObservable();
|
||||
}
|
||||
}
|
||||
}
|
||||
112
lib/screens/channel/chat/chat_assets_store.g.dart
Normal file
112
lib/screens/channel/chat/chat_assets_store.g.dart
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'chat_assets_store.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// StoreGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
|
||||
|
||||
mixin _$ChatAssetsStore on _ChatAssetsStoreBase, Store {
|
||||
Computed<List<Emote>>? _$bttvEmotesComputed;
|
||||
|
||||
@override
|
||||
List<Emote> get bttvEmotes =>
|
||||
(_$bttvEmotesComputed ??= Computed<List<Emote>>(() => super.bttvEmotes,
|
||||
name: '_ChatAssetsStoreBase.bttvEmotes'))
|
||||
.value;
|
||||
Computed<List<Emote>>? _$ffzEmotesComputed;
|
||||
|
||||
@override
|
||||
List<Emote> get ffzEmotes =>
|
||||
(_$ffzEmotesComputed ??= Computed<List<Emote>>(() => super.ffzEmotes,
|
||||
name: '_ChatAssetsStoreBase.ffzEmotes'))
|
||||
.value;
|
||||
Computed<List<Emote>>? _$sevenTvEmotesComputed;
|
||||
|
||||
@override
|
||||
List<Emote> get sevenTvEmotes => (_$sevenTvEmotesComputed ??=
|
||||
Computed<List<Emote>>(() => super.sevenTvEmotes,
|
||||
name: '_ChatAssetsStoreBase.sevenTvEmotes'))
|
||||
.value;
|
||||
|
||||
final _$_userEmoteToObjectAtom =
|
||||
Atom(name: '_ChatAssetsStoreBase._userEmoteToObject');
|
||||
|
||||
ObservableMap<String, Emote> get userEmoteToObject {
|
||||
_$_userEmoteToObjectAtom.reportRead();
|
||||
return super._userEmoteToObject;
|
||||
}
|
||||
|
||||
@override
|
||||
ObservableMap<String, Emote> get _userEmoteToObject => userEmoteToObject;
|
||||
|
||||
@override
|
||||
set _userEmoteToObject(ObservableMap<String, Emote> value) {
|
||||
_$_userEmoteToObjectAtom.reportWrite(value, super._userEmoteToObject, () {
|
||||
super._userEmoteToObject = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$emoteMenuIndexAtom =
|
||||
Atom(name: '_ChatAssetsStoreBase.emoteMenuIndex');
|
||||
|
||||
@override
|
||||
int get emoteMenuIndex {
|
||||
_$emoteMenuIndexAtom.reportRead();
|
||||
return super.emoteMenuIndex;
|
||||
}
|
||||
|
||||
@override
|
||||
set emoteMenuIndex(int value) {
|
||||
_$emoteMenuIndexAtom.reportWrite(value, super.emoteMenuIndex, () {
|
||||
super.emoteMenuIndex = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$showEmoteMenuAtom = Atom(name: '_ChatAssetsStoreBase.showEmoteMenu');
|
||||
|
||||
@override
|
||||
bool get showEmoteMenu {
|
||||
_$showEmoteMenuAtom.reportRead();
|
||||
return super.showEmoteMenu;
|
||||
}
|
||||
|
||||
@override
|
||||
set showEmoteMenu(bool value) {
|
||||
_$showEmoteMenuAtom.reportWrite(value, super.showEmoteMenu, () {
|
||||
super.showEmoteMenu = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$getAssetsAsyncAction = AsyncAction('_ChatAssetsStoreBase.getAssets');
|
||||
|
||||
@override
|
||||
Future<void> getAssets(
|
||||
{required String channelName, required Map<String, String> headers}) {
|
||||
return _$getAssetsAsyncAction
|
||||
.run(() => super.getAssets(channelName: channelName, headers: headers));
|
||||
}
|
||||
|
||||
final _$getUserEmotesAsyncAction =
|
||||
AsyncAction('_ChatAssetsStoreBase.getUserEmotes');
|
||||
|
||||
@override
|
||||
Future<void> getUserEmotes(
|
||||
{List<String>? emoteSets, required Map<String, String> headers}) {
|
||||
return _$getUserEmotesAsyncAction
|
||||
.run(() => super.getUserEmotes(emoteSets: emoteSets, headers: headers));
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''
|
||||
emoteMenuIndex: ${emoteMenuIndex},
|
||||
showEmoteMenu: ${showEmoteMenu},
|
||||
bttvEmotes: ${bttvEmotes},
|
||||
ffzEmotes: ${ffzEmotes},
|
||||
sevenTvEmotes: ${sevenTvEmotes}
|
||||
''';
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,10 @@ class ChatBottomBar extends StatelessWidget {
|
|||
icon: Icon(Icons.adaptive.more),
|
||||
onPressed: () => showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (_) => ChatDetails(chatStore: chatStore),
|
||||
builder: (_) => ChatDetails(
|
||||
chatDetails: chatStore.chatDetailsStore,
|
||||
userLogin: chatStore.channelName,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
|
|
@ -24,13 +27,13 @@ class ChatBottomBar extends StatelessWidget {
|
|||
child: TextField(
|
||||
minLines: 1,
|
||||
maxLines: 5,
|
||||
onTap: () => chatStore.showEmoteMenu = false,
|
||||
onTap: () => chatStore.assetsStore.showEmoteMenu = false,
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.emoji_emotions_outlined),
|
||||
onPressed: () {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
chatStore.showEmoteMenu = !chatStore.showEmoteMenu;
|
||||
chatStore.assetsStore.showEmoteMenu = !chatStore.assetsStore.showEmoteMenu;
|
||||
},
|
||||
),
|
||||
isDense: true,
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:frosty/models/irc_message.dart';
|
||||
import 'package:frosty/screens/channel/chat/chat_store.dart';
|
||||
import 'package:frosty/screens/channel/chat/chat_assets_store.dart';
|
||||
|
||||
class ChatMessage extends StatelessWidget {
|
||||
final IRCMessage ircMessage;
|
||||
final ChatStore chatStore;
|
||||
final ChatAssetsStore assetsStore;
|
||||
final bool hideMessageIfBanned;
|
||||
final bool zeroWidth;
|
||||
|
||||
const ChatMessage({
|
||||
Key? key,
|
||||
required this.ircMessage,
|
||||
required this.chatStore,
|
||||
required this.assetsStore,
|
||||
this.hideMessageIfBanned = true,
|
||||
this.zeroWidth = false,
|
||||
}) : super(key: key);
|
||||
|
|
@ -25,8 +25,8 @@ class ChatMessage extends StatelessWidget {
|
|||
return Text.rich(
|
||||
TextSpan(
|
||||
children: ircMessage.generateSpan(
|
||||
emoteToObject: chatStore.emoteToObject,
|
||||
badgeToObject: chatStore.badgesToObject,
|
||||
emoteToObject: assetsStore.emoteToObject,
|
||||
badgeToObject: assetsStore.badgesToObject,
|
||||
zeroWidthEnabled: zeroWidth,
|
||||
),
|
||||
),
|
||||
|
|
@ -43,8 +43,8 @@ class ChatMessage extends StatelessWidget {
|
|||
Text.rich(
|
||||
TextSpan(
|
||||
children: ircMessage.generateSpan(
|
||||
emoteToObject: chatStore.emoteToObject,
|
||||
badgeToObject: chatStore.badgesToObject,
|
||||
emoteToObject: assetsStore.emoteToObject,
|
||||
badgeToObject: assetsStore.badgesToObject,
|
||||
hideMessage: hideMessageIfBanned,
|
||||
zeroWidthEnabled: zeroWidth,
|
||||
),
|
||||
|
|
@ -85,8 +85,8 @@ class ChatMessage extends StatelessWidget {
|
|||
Text.rich(
|
||||
TextSpan(
|
||||
children: ircMessage.generateSpan(
|
||||
emoteToObject: chatStore.emoteToObject,
|
||||
badgeToObject: chatStore.badgesToObject,
|
||||
emoteToObject: assetsStore.emoteToObject,
|
||||
badgeToObject: assetsStore.badgesToObject,
|
||||
zeroWidthEnabled: zeroWidth,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -2,15 +2,11 @@ import 'dart:async';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:frosty/api/bttv_api.dart';
|
||||
import 'package:frosty/api/ffz_api.dart';
|
||||
import 'package:frosty/api/seventv_api.dart';
|
||||
import 'package:frosty/api/twitch_api.dart';
|
||||
import 'package:frosty/core/auth/auth_store.dart';
|
||||
import 'package:frosty/core/settings/settings_store.dart';
|
||||
import 'package:frosty/models/badges.dart';
|
||||
import 'package:frosty/models/emotes.dart';
|
||||
import 'package:frosty/models/irc_message.dart';
|
||||
import 'package:frosty/screens/channel/chat/chat_assets_store.dart';
|
||||
import 'package:frosty/screens/channel/chat/details/chat_details_store.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
|
|
@ -20,54 +16,33 @@ part 'chat_store.g.dart';
|
|||
class ChatStore = _ChatStoreBase with _$ChatStore;
|
||||
|
||||
abstract class _ChatStoreBase with Store {
|
||||
/// The provided auth store to determine login status, get the token, and use the headers for requests.
|
||||
final AuthStore auth;
|
||||
|
||||
/// The provided setting store to account for any user-defined behaviors.
|
||||
final SettingsStore settings;
|
||||
|
||||
/// The name of the channel to connect to.
|
||||
final String channelName;
|
||||
|
||||
/// The Twitch IRC WebSocket channel.
|
||||
final _channel = WebSocketChannel.connect(Uri.parse('wss://irc-ws.chat.twitch.tv:443'));
|
||||
|
||||
/// The map of emote words to their image or GIF URL. May be used by anyone in the chat.
|
||||
final emoteToObject = ObservableMap<String, Emote>();
|
||||
|
||||
@computed
|
||||
List<Emote> get bttvEmotes => emoteToObject.values.where((emote) => isBTTV(emote)).toList();
|
||||
|
||||
@computed
|
||||
List<Emote> get ffzEmotes => emoteToObject.values.where((emote) => isFFZ(emote)).toList();
|
||||
|
||||
@computed
|
||||
List<Emote> get sevenTvEmotes => emoteToObject.values.where((emote) => is7TV(emote)).toList();
|
||||
|
||||
/// The emotes that are "owned" and may be used by the current user.
|
||||
@readonly
|
||||
var _userEmoteToObject = ObservableMap<String, Emote>();
|
||||
|
||||
/// The map of badges ids to their object representation.
|
||||
final badgesToObject = <String, BadgeInfoTwitch>{};
|
||||
|
||||
/// The scroll controller that controls auto-scroll and resume-scroll behavior.
|
||||
final scrollController = ScrollController();
|
||||
|
||||
/// The text controller that handles the TextField inputs and sending of messages.
|
||||
final textController = TextEditingController();
|
||||
|
||||
/// The name of the channel to connect to.
|
||||
final String channelName;
|
||||
/// The chat details store responsible for the chat modes and users in chat.
|
||||
final chatDetailsStore = ChatDetailsStore();
|
||||
|
||||
/// The provided auth store to determine login status, get the token, and use the headers for requests.
|
||||
final AuthStore auth;
|
||||
|
||||
/// The provided setting store to account for any user-defined behaviors.
|
||||
final SettingsStore settings;
|
||||
/// The assets store responsible for badges, emotes, and the emote menu.
|
||||
final assetsStore = ChatAssetsStore();
|
||||
|
||||
/// Requested message to be sent by the user. Will only be sent on receival of a USERNOTICE command.
|
||||
IRCMessage? toSend;
|
||||
|
||||
/// The current index of the emote menu stack.
|
||||
@observable
|
||||
var emoteMenuIndex = 0;
|
||||
|
||||
/// Whether or not the emote menu is visible.
|
||||
@observable
|
||||
var showEmoteMenu = false;
|
||||
|
||||
/// The list of chat messages to render and display.
|
||||
@readonly
|
||||
var _messages = ObservableList<IRCMessage>();
|
||||
|
|
@ -80,10 +55,6 @@ abstract class _ChatStoreBase with Store {
|
|||
@readonly
|
||||
var _userState = const USERSTATE();
|
||||
|
||||
/// The rules and modes being used in the chat.
|
||||
@readonly
|
||||
var _roomState = const ROOMSTATE();
|
||||
|
||||
_ChatStoreBase({
|
||||
required this.auth,
|
||||
required this.settings,
|
||||
|
|
@ -92,7 +63,7 @@ abstract class _ChatStoreBase with Store {
|
|||
// Create a reaction where anytime the emote menu is shown or hidden,
|
||||
// scroll to the bottom of the list. This will prevent the emote menu
|
||||
// from covering the latest messages when summoned.
|
||||
final disposeEmoteMenuReaction = reaction((_) => showEmoteMenu, (_) {
|
||||
final disposeEmoteMenuReaction = reaction((_) => assetsStore.showEmoteMenu, (_) {
|
||||
SchedulerBinding.instance?.addPostFrameCallback((_) {
|
||||
if (scrollController.hasClients) scrollController.jumpTo(scrollController.position.maxScrollExtent);
|
||||
});
|
||||
|
|
@ -115,8 +86,16 @@ abstract class _ChatStoreBase with Store {
|
|||
},
|
||||
);
|
||||
|
||||
// Fetch the users in chat.
|
||||
chatDetailsStore.updateChatters(channelName);
|
||||
|
||||
// Fetch the assets used in chat including badges and emotes.
|
||||
getAssets();
|
||||
_messages.add(IRCMessage.createNotice(message: 'Fetching channel assets...'));
|
||||
|
||||
assetsStore
|
||||
.getAssets(channelName: channelName, headers: auth.headersTwitch)
|
||||
.then((_) => _messages.add(IRCMessage.createNotice(message: 'Channel assets fetched!')))
|
||||
.onError((error, stackTrace) => _messages.add(IRCMessage.createNotice(message: 'Failed to fetch assets: ${error.toString()}')));
|
||||
|
||||
// The list of messages sent to the IRC WebSocket channel to connect and join.
|
||||
final commands = [
|
||||
|
|
@ -185,7 +164,7 @@ abstract class _ChatStoreBase with Store {
|
|||
_messages.add(parsedIRCMessage);
|
||||
break;
|
||||
case Command.roomState:
|
||||
_roomState = _roomState.fromIRCMessage(parsedIRCMessage);
|
||||
chatDetailsStore.roomState = chatDetailsStore.roomState.fromIRCMessage(parsedIRCMessage);
|
||||
continue;
|
||||
case Command.userState:
|
||||
_userState = _userState.fromIRCMessage(parsedIRCMessage);
|
||||
|
|
@ -196,7 +175,11 @@ abstract class _ChatStoreBase with Store {
|
|||
break;
|
||||
case Command.globalUserState:
|
||||
final setIds = parsedIRCMessage.tags['emote-sets']?.split(',');
|
||||
getUserEmotes(emoteSets: setIds);
|
||||
_messages.add(IRCMessage.createNotice(message: 'Fetching user emotes...'));
|
||||
assetsStore
|
||||
.getUserEmotes(emoteSets: setIds, headers: auth.headersTwitch)
|
||||
.then((_) => _messages.add(IRCMessage.createNotice(message: 'User emotes fetched!')))
|
||||
.onError((error, stackTrace) => _messages.add(IRCMessage.createNotice(message: 'Failed to fetch user emotes: ${error.toString()}')));
|
||||
continue;
|
||||
case Command.none:
|
||||
debugPrint('Unknown command: ${parsedIRCMessage.command}');
|
||||
|
|
@ -262,7 +245,7 @@ abstract class _ChatStoreBase with Store {
|
|||
// }
|
||||
|
||||
final userChatMessage = IRCMessage.fromString(userStateString);
|
||||
userChatMessage.localEmotes.addAll(_userEmoteToObject);
|
||||
userChatMessage.localEmotes.addAll(assetsStore.userEmoteToObject);
|
||||
userChatMessage.message = message.trim();
|
||||
toSend = userChatMessage;
|
||||
}
|
||||
|
|
@ -271,65 +254,6 @@ abstract class _ChatStoreBase with Store {
|
|||
textController.clear();
|
||||
}
|
||||
|
||||
/// Fetches global and channel assets (badges and emotes) and stores them in [_emoteToUrl]
|
||||
@action
|
||||
Future<void> getAssets() async {
|
||||
_messages.add(IRCMessage.createNotice(message: 'Fetching channel assets...'));
|
||||
|
||||
// Fetch the desired channel/user's information.
|
||||
final channelInfo = await Twitch.getUser(userLogin: channelName, headers: auth.headersTwitch);
|
||||
|
||||
if (channelInfo != null) {
|
||||
// Fetch the global and channel's assets (emotes & badges).
|
||||
// Async awaits are placed in a list so they are performed in parallel.
|
||||
final assets = [
|
||||
...await FFZ.getEmotesGlobal(),
|
||||
...await FFZ.getEmotesChannel(id: channelInfo.id),
|
||||
...await BTTV.getEmotesGlobal(),
|
||||
...await BTTV.getEmotesChannel(id: channelInfo.id),
|
||||
...await Twitch.getEmotesGlobal(headers: auth.headersTwitch),
|
||||
...await Twitch.getEmotesChannel(id: channelInfo.id, headers: auth.headersTwitch),
|
||||
...await SevenTV.getEmotesGlobal(),
|
||||
...await SevenTV.getEmotesChannel(user: channelInfo.login)
|
||||
];
|
||||
|
||||
// assets.sort((a, b) => a.id.compareTo(b.id));
|
||||
|
||||
for (final emote in assets) {
|
||||
emoteToObject[emote.name] = emote;
|
||||
}
|
||||
|
||||
final badges = [
|
||||
await Twitch.getBadgesGlobal(),
|
||||
await Twitch.getBadgesChannel(id: channelInfo.id),
|
||||
];
|
||||
|
||||
for (final map in badges) {
|
||||
if (map != null) {
|
||||
badgesToObject.addAll(map);
|
||||
}
|
||||
}
|
||||
|
||||
_messages.add(IRCMessage.createNotice(message: 'Channel assets fetched!'));
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> getUserEmotes({List<String>? emoteSets}) async {
|
||||
_messages.add(IRCMessage.createNotice(message: 'Fetching user emotes...'));
|
||||
|
||||
if (emoteSets != null) {
|
||||
final userEmotes = <Emote>[];
|
||||
for (final setId in emoteSets) {
|
||||
userEmotes.addAll(await Twitch.getEmotesSets(setId: setId, headers: auth.headersTwitch));
|
||||
}
|
||||
|
||||
_userEmoteToObject = {for (final emote in userEmotes) emote.name: emote}.asObservable();
|
||||
}
|
||||
|
||||
_messages.add(IRCMessage.createNotice(message: 'User emotes fetched!'));
|
||||
}
|
||||
|
||||
/// Pauses or resumes the chat subscription depending on the provided state.
|
||||
void handleAppStateChange(AppLifecycleState state) {
|
||||
switch (state) {
|
||||
|
|
@ -344,18 +268,6 @@ abstract class _ChatStoreBase with Store {
|
|||
}
|
||||
}
|
||||
|
||||
bool isBTTV(Emote emote) {
|
||||
return emote.type == EmoteType.bttvChannel || emote.type == EmoteType.bttvGlobal || emote.type == EmoteType.bttvShared;
|
||||
}
|
||||
|
||||
bool isFFZ(Emote emote) {
|
||||
return emote.type == EmoteType.ffzChannel || emote.type == EmoteType.ffzGlobal;
|
||||
}
|
||||
|
||||
bool is7TV(Emote emote) {
|
||||
return emote.type == EmoteType.sevenTvChannel || emote.type == EmoteType.sevenTvGlobal;
|
||||
}
|
||||
|
||||
/// Closes and disposes all the channels and controllers used by the store.
|
||||
void dispose() {
|
||||
_channel.sink.close();
|
||||
|
|
|
|||
|
|
@ -9,76 +9,6 @@ part of 'chat_store.dart';
|
|||
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
|
||||
|
||||
mixin _$ChatStore on _ChatStoreBase, Store {
|
||||
Computed<List<Emote>>? _$bttvEmotesComputed;
|
||||
|
||||
@override
|
||||
List<Emote> get bttvEmotes =>
|
||||
(_$bttvEmotesComputed ??= Computed<List<Emote>>(() => super.bttvEmotes,
|
||||
name: '_ChatStoreBase.bttvEmotes'))
|
||||
.value;
|
||||
Computed<List<Emote>>? _$ffzEmotesComputed;
|
||||
|
||||
@override
|
||||
List<Emote> get ffzEmotes =>
|
||||
(_$ffzEmotesComputed ??= Computed<List<Emote>>(() => super.ffzEmotes,
|
||||
name: '_ChatStoreBase.ffzEmotes'))
|
||||
.value;
|
||||
Computed<List<Emote>>? _$sevenTvEmotesComputed;
|
||||
|
||||
@override
|
||||
List<Emote> get sevenTvEmotes => (_$sevenTvEmotesComputed ??=
|
||||
Computed<List<Emote>>(() => super.sevenTvEmotes,
|
||||
name: '_ChatStoreBase.sevenTvEmotes'))
|
||||
.value;
|
||||
|
||||
final _$_userEmoteToObjectAtom =
|
||||
Atom(name: '_ChatStoreBase._userEmoteToObject');
|
||||
|
||||
ObservableMap<String, Emote> get userEmoteToObject {
|
||||
_$_userEmoteToObjectAtom.reportRead();
|
||||
return super._userEmoteToObject;
|
||||
}
|
||||
|
||||
@override
|
||||
ObservableMap<String, Emote> get _userEmoteToObject => userEmoteToObject;
|
||||
|
||||
@override
|
||||
set _userEmoteToObject(ObservableMap<String, Emote> value) {
|
||||
_$_userEmoteToObjectAtom.reportWrite(value, super._userEmoteToObject, () {
|
||||
super._userEmoteToObject = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$emoteMenuIndexAtom = Atom(name: '_ChatStoreBase.emoteMenuIndex');
|
||||
|
||||
@override
|
||||
int get emoteMenuIndex {
|
||||
_$emoteMenuIndexAtom.reportRead();
|
||||
return super.emoteMenuIndex;
|
||||
}
|
||||
|
||||
@override
|
||||
set emoteMenuIndex(int value) {
|
||||
_$emoteMenuIndexAtom.reportWrite(value, super.emoteMenuIndex, () {
|
||||
super.emoteMenuIndex = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$showEmoteMenuAtom = Atom(name: '_ChatStoreBase.showEmoteMenu');
|
||||
|
||||
@override
|
||||
bool get showEmoteMenu {
|
||||
_$showEmoteMenuAtom.reportRead();
|
||||
return super.showEmoteMenu;
|
||||
}
|
||||
|
||||
@override
|
||||
set showEmoteMenu(bool value) {
|
||||
_$showEmoteMenuAtom.reportWrite(value, super.showEmoteMenu, () {
|
||||
super.showEmoteMenu = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$_messagesAtom = Atom(name: '_ChatStoreBase._messages');
|
||||
|
||||
ObservableList<IRCMessage> get messages {
|
||||
|
|
@ -130,39 +60,6 @@ mixin _$ChatStore on _ChatStoreBase, Store {
|
|||
});
|
||||
}
|
||||
|
||||
final _$_roomStateAtom = Atom(name: '_ChatStoreBase._roomState');
|
||||
|
||||
ROOMSTATE get roomState {
|
||||
_$_roomStateAtom.reportRead();
|
||||
return super._roomState;
|
||||
}
|
||||
|
||||
@override
|
||||
ROOMSTATE get _roomState => roomState;
|
||||
|
||||
@override
|
||||
set _roomState(ROOMSTATE value) {
|
||||
_$_roomStateAtom.reportWrite(value, super._roomState, () {
|
||||
super._roomState = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$getAssetsAsyncAction = AsyncAction('_ChatStoreBase.getAssets');
|
||||
|
||||
@override
|
||||
Future<void> getAssets() {
|
||||
return _$getAssetsAsyncAction.run(() => super.getAssets());
|
||||
}
|
||||
|
||||
final _$getUserEmotesAsyncAction =
|
||||
AsyncAction('_ChatStoreBase.getUserEmotes');
|
||||
|
||||
@override
|
||||
Future<void> getUserEmotes({List<String>? emoteSets}) {
|
||||
return _$getUserEmotesAsyncAction
|
||||
.run(() => super.getUserEmotes(emoteSets: emoteSets));
|
||||
}
|
||||
|
||||
final _$_ChatStoreBaseActionController =
|
||||
ActionController(name: '_ChatStoreBase');
|
||||
|
||||
|
|
@ -202,11 +99,7 @@ mixin _$ChatStore on _ChatStoreBase, Store {
|
|||
@override
|
||||
String toString() {
|
||||
return '''
|
||||
emoteMenuIndex: ${emoteMenuIndex},
|
||||
showEmoteMenu: ${showEmoteMenu},
|
||||
bttvEmotes: ${bttvEmotes},
|
||||
ffzEmotes: ${ffzEmotes},
|
||||
sevenTvEmotes: ${sevenTvEmotes}
|
||||
|
||||
''';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,18 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/screens/channel/chat/chat_store.dart';
|
||||
import 'package:frosty/screens/channel/chat/details/chat_details_store.dart';
|
||||
import 'package:frosty/screens/channel/chat/details/chat_modes.dart';
|
||||
import 'package:frosty/screens/channel/chat/details/chat_users_list.dart';
|
||||
|
||||
class ChatDetails extends StatelessWidget {
|
||||
final ChatStore chatStore;
|
||||
final ChatDetailsStore chatDetails;
|
||||
final String userLogin;
|
||||
|
||||
const ChatDetails({Key? key, required this.chatStore}) : super(key: key);
|
||||
const ChatDetails({
|
||||
Key? key,
|
||||
required this.chatDetails,
|
||||
required this.userLogin,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -14,35 +21,20 @@ class ChatDetails extends StatelessWidget {
|
|||
child: Center(
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
return ListView(
|
||||
return Column(
|
||||
children: [
|
||||
const ListTile(
|
||||
title: Text('Modes'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Text('Emote-only'),
|
||||
trailing: Text(chatStore.roomState.emoteOnly ? 'Enabled' : 'Disabled'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Text('Followers-only'),
|
||||
trailing: Text(chatStore.roomState.followersOnly == '-1'
|
||||
? 'Disabled'
|
||||
: chatStore.roomState.followersOnly == '0'
|
||||
? 'Enabled'
|
||||
: '${chatStore.roomState.followersOnly} minute(s)'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Text('R9K'),
|
||||
trailing: Text(chatStore.roomState.r9k ? 'Enabled' : 'Disabled'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Text('Slow'),
|
||||
trailing: Text(chatStore.roomState.slowMode == '0' ? 'Disabled' : '${chatStore.roomState.slowMode} seconds'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Text('Sub-only'),
|
||||
trailing: Text(chatStore.roomState.subMode ? 'Enabled' : 'Disabled'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: ChatModes(roomState: chatDetails.roomState),
|
||||
),
|
||||
Expanded(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () => chatDetails.updateChatters(userLogin),
|
||||
child: ChattersList(
|
||||
chatUsers: chatDetails.chatters,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
|
|
|
|||
23
lib/screens/channel/chat/details/chat_details_store.dart
Normal file
23
lib/screens/channel/chat/details/chat_details_store.dart
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import 'package:frosty/api/twitch_api.dart';
|
||||
import 'package:frosty/models/chatters.dart';
|
||||
import 'package:frosty/models/irc_message.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'chat_details_store.g.dart';
|
||||
|
||||
class ChatDetailsStore = _ChatDetailsStoreBase with _$ChatDetailsStore;
|
||||
|
||||
abstract class _ChatDetailsStoreBase with Store {
|
||||
/// The rules and modes being used in the chat.
|
||||
@observable
|
||||
var roomState = const ROOMSTATE();
|
||||
|
||||
/// The list and types of chatters in the chat room.
|
||||
@readonly
|
||||
ChatUsers? _chatters;
|
||||
|
||||
@action
|
||||
Future<void> updateChatters(String userLogin) async {
|
||||
_chatters = await Twitch.getChatters(userLogin: userLogin);
|
||||
}
|
||||
}
|
||||
59
lib/screens/channel/chat/details/chat_details_store.g.dart
Normal file
59
lib/screens/channel/chat/details/chat_details_store.g.dart
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'chat_details_store.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// StoreGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
|
||||
|
||||
mixin _$ChatDetailsStore on _ChatDetailsStoreBase, Store {
|
||||
final _$roomStateAtom = Atom(name: '_ChatDetailsStoreBase.roomState');
|
||||
|
||||
@override
|
||||
ROOMSTATE get roomState {
|
||||
_$roomStateAtom.reportRead();
|
||||
return super.roomState;
|
||||
}
|
||||
|
||||
@override
|
||||
set roomState(ROOMSTATE value) {
|
||||
_$roomStateAtom.reportWrite(value, super.roomState, () {
|
||||
super.roomState = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$_chattersAtom = Atom(name: '_ChatDetailsStoreBase._chatters');
|
||||
|
||||
ChatUsers? get chatters {
|
||||
_$_chattersAtom.reportRead();
|
||||
return super._chatters;
|
||||
}
|
||||
|
||||
@override
|
||||
ChatUsers? get _chatters => chatters;
|
||||
|
||||
@override
|
||||
set _chatters(ChatUsers? value) {
|
||||
_$_chattersAtom.reportWrite(value, super._chatters, () {
|
||||
super._chatters = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$updateChattersAsyncAction =
|
||||
AsyncAction('_ChatDetailsStoreBase.updateChatters');
|
||||
|
||||
@override
|
||||
Future<void> updateChatters(String userLogin) {
|
||||
return _$updateChattersAsyncAction
|
||||
.run(() => super.updateChatters(userLogin));
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''
|
||||
roomState: ${roomState}
|
||||
''';
|
||||
}
|
||||
}
|
||||
63
lib/screens/channel/chat/details/chat_modes.dart
Normal file
63
lib/screens/channel/chat/details/chat_modes.dart
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:frosty/models/irc_message.dart';
|
||||
|
||||
class ChatModes extends StatelessWidget {
|
||||
final ROOMSTATE roomState;
|
||||
|
||||
const ChatModes({Key? key, required this.roomState}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Tooltip(
|
||||
preferBelow: false,
|
||||
message: 'Emote-Only: ${roomState.emoteOnly != '0' ? 'Enabled' : 'Disabled'}',
|
||||
child: Icon(
|
||||
Icons.emoji_emotions_outlined,
|
||||
color: roomState.emoteOnly != '0' ? Colors.green : Colors.grey,
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
preferBelow: false,
|
||||
message: roomState.followersOnly == '-1'
|
||||
? 'Followers-Only: Disabled'
|
||||
: roomState.followersOnly == '0'
|
||||
? 'Followers-Only: Only followed users can chat.'
|
||||
: roomState.followersOnly == '1'
|
||||
? 'Followers-Only: Users followed for at least ${roomState.followersOnly} minute can chat.'
|
||||
: 'Followers-Only: Users followed for at least ${roomState.followersOnly} minutes can chat',
|
||||
child: Icon(
|
||||
Icons.favorite,
|
||||
color: roomState.followersOnly != '-1' ? Colors.green : Colors.grey,
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
preferBelow: false,
|
||||
message: 'Unique Chat Messages: ${roomState.r9k != '0' ? 'Enabled' : 'Disabled'}',
|
||||
child: Text(
|
||||
'R9K',
|
||||
style: TextStyle(color: roomState.r9k != '0' ? Colors.green : Colors.grey),
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
preferBelow: false,
|
||||
message: 'Slow: ${roomState.slowMode == '0' ? 'Disabled' : '${roomState.slowMode} seconds'}',
|
||||
child: Icon(
|
||||
Icons.history_toggle_off,
|
||||
color: roomState.slowMode != '0' ? Colors.green : Colors.grey,
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
preferBelow: false,
|
||||
message: 'Subs-Only: ${roomState.subMode != '0' ? 'Enabled' : 'Disabled'}',
|
||||
child: Icon(
|
||||
Icons.attach_money,
|
||||
color: roomState.subMode != '0' ? Colors.green : Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
93
lib/screens/channel/chat/details/chat_users_list.dart
Normal file
93
lib/screens/channel/chat/details/chat_users_list.dart
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:frosty/models/chatters.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class ChattersList extends StatelessWidget {
|
||||
final ChatUsers? chatUsers;
|
||||
|
||||
const ChattersList({Key? key, required this.chatUsers}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final chatters = chatUsers?.chatters;
|
||||
if (chatters != null) {
|
||||
const headers = [
|
||||
'Moderators',
|
||||
'VIPs',
|
||||
'Staff',
|
||||
'Admins',
|
||||
'Global Moderators',
|
||||
'Users',
|
||||
];
|
||||
|
||||
final userTypes = [
|
||||
chatters.moderators,
|
||||
chatters.vips,
|
||||
chatters.staff,
|
||||
chatters.admins,
|
||||
chatters.globalMods,
|
||||
chatters.viewers,
|
||||
];
|
||||
|
||||
const textStyle = TextStyle(fontWeight: FontWeight.bold);
|
||||
|
||||
return CustomScrollView(slivers: [
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('Users', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
||||
Text(
|
||||
'${NumberFormat().format(chatUsers?.chatterCount)} users in chat',
|
||||
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.only(left: 10.0, right: 10.0, top: 20.0),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Broadcaster',
|
||||
style: textStyle,
|
||||
),
|
||||
const SizedBox(height: 5.0),
|
||||
if (chatters.broadcaster.isNotEmpty) Text(chatters.broadcaster.first),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
...userTypes.expandIndexed((index, type) => [
|
||||
if (type.isNotEmpty) ...[
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.only(left: 10.0, right: 10.0, top: 20.0),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Text(
|
||||
headers[index],
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => Text(type[index]),
|
||||
childCount: type.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
])
|
||||
]);
|
||||
}
|
||||
return const Center(child: Text('Failed to get chatters :('));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/screens/channel/chat/chat_store.dart';
|
||||
import 'package:frosty/screens/channel/chat/chat_assets_store.dart';
|
||||
import 'package:frosty/screens/channel/chat/emote_menu/emote_menu_panel.dart';
|
||||
|
||||
class EmoteMenu extends StatelessWidget {
|
||||
final ChatStore chatStore;
|
||||
final ChatAssetsStore assetsStore;
|
||||
final TextEditingController textController;
|
||||
|
||||
const EmoteMenu({Key? key, required this.chatStore}) : super(key: key);
|
||||
const EmoteMenu({Key? key, required this.assetsStore, required this.textController}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -24,31 +25,31 @@ class EmoteMenu extends StatelessWidget {
|
|||
children: [
|
||||
Expanded(
|
||||
child: PageView(
|
||||
onPageChanged: (index) => chatStore.emoteMenuIndex = index,
|
||||
onPageChanged: (index) => assetsStore.emoteMenuIndex = index,
|
||||
controller: pageContoller,
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) => EmoteMenuPanel(
|
||||
textController: chatStore.textController,
|
||||
emotes: chatStore.userEmoteToObject.values.toList(),
|
||||
textController: textController,
|
||||
emotes: assetsStore.userEmoteToObject.values.toList(),
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => EmoteMenuPanel(
|
||||
textController: chatStore.textController,
|
||||
emotes: chatStore.bttvEmotes,
|
||||
textController: textController,
|
||||
emotes: assetsStore.bttvEmotes,
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => EmoteMenuPanel(
|
||||
textController: chatStore.textController,
|
||||
emotes: chatStore.ffzEmotes,
|
||||
textController: textController,
|
||||
emotes: assetsStore.ffzEmotes,
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => EmoteMenuPanel(
|
||||
textController: chatStore.textController,
|
||||
emotes: chatStore.sevenTvEmotes,
|
||||
textController: textController,
|
||||
emotes: assetsStore.sevenTvEmotes,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -66,9 +67,9 @@ class EmoteMenu extends StatelessWidget {
|
|||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
chatStore.emoteMenuIndex = index;
|
||||
assetsStore.emoteMenuIndex = index;
|
||||
},
|
||||
style: index == chatStore.emoteMenuIndex ? null : TextButton.styleFrom(primary: Colors.grey),
|
||||
style: index == assetsStore.emoteMenuIndex ? null : TextButton.styleFrom(primary: Colors.grey),
|
||||
child: Text(section),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue