Add proper tooltips for badges
This commit is contained in:
parent
a95c3eb7ae
commit
0457d3e85e
3 changed files with 60 additions and 26 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:frosty/models/badges.dart';
|
||||
import 'package:frosty/models/irc.dart';
|
||||
|
||||
class IRC {
|
||||
|
|
@ -49,7 +50,12 @@ class IRC {
|
|||
}
|
||||
|
||||
/// Returns an [InlineSpan] list that corresponds to the badges, username, words, and emotes of the given [IRCMessage].
|
||||
static List<InlineSpan> generateSpan({required IRCMessage ircMessage, required Map<String, String> assetToUrl, bool hideMessage = false}) {
|
||||
static List<InlineSpan> generateSpan({
|
||||
required IRCMessage ircMessage,
|
||||
required Map<String, String> emoteToUrl,
|
||||
required Map<String, BadgeInfoTwitch> badgeToObject,
|
||||
bool hideMessage = false,
|
||||
}) {
|
||||
// The span list that will be used to render the chat message
|
||||
final span = <InlineSpan>[];
|
||||
|
||||
|
|
@ -92,16 +98,16 @@ class IRC {
|
|||
final badges = ircMessage.tags['badges'];
|
||||
if (badges != null) {
|
||||
for (final badge in badges.split(',')) {
|
||||
final badgeUrl = assetToUrl[badge];
|
||||
if (badgeUrl != null) {
|
||||
final badgeInfo = badgeToObject[badge];
|
||||
if (badgeInfo != null) {
|
||||
span.add(
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Tooltip(
|
||||
message: badge,
|
||||
message: badgeInfo.title,
|
||||
preferBelow: false,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: badgeUrl,
|
||||
imageUrl: badgeInfo.imageUrl4x,
|
||||
placeholder: (context, url) => const SizedBox(),
|
||||
fadeInDuration: const Duration(seconds: 0),
|
||||
height: 20,
|
||||
|
|
@ -148,7 +154,7 @@ class IRC {
|
|||
for (final word in words) {
|
||||
buffer.write(' ');
|
||||
|
||||
final emoteUrl = assetToUrl[word] ?? localEmoteToUrl[word];
|
||||
final emoteUrl = emoteToUrl[word] ?? localEmoteToUrl[word];
|
||||
if (emoteUrl != null) {
|
||||
span.add(TextSpan(text: buffer.toString()));
|
||||
|
||||
|
|
|
|||
|
|
@ -51,18 +51,18 @@ class Twitch {
|
|||
}
|
||||
|
||||
/// Returns a map of global Twitch badges to their URL.
|
||||
static Future<Map<String, String>?> getBadgesGlobal() async {
|
||||
static Future<Map<String, BadgeInfoTwitch>?> getBadgesGlobal() async {
|
||||
final url = Uri.parse('https://badges.twitch.tv/v1/badges/global/display');
|
||||
final response = await http.get(url);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final result = <String, String>{};
|
||||
final result = <String, BadgeInfoTwitch>{};
|
||||
|
||||
final decoded = jsonDecode(response.body)['badge_sets'] as Map;
|
||||
|
||||
// TODO: Figure out cleaner way to decode badge JSON.
|
||||
decoded.forEach((id, versions) =>
|
||||
(versions['versions'] as Map).forEach((version, badgeInfo) => result['$id/$version'] = BadgeInfoTwitch.fromJson(badgeInfo).imageUrl4x));
|
||||
decoded.forEach(
|
||||
(id, versions) => (versions['versions'] as Map).forEach((version, badgeInfo) => result['$id/$version'] = BadgeInfoTwitch.fromJson(badgeInfo)));
|
||||
|
||||
return result;
|
||||
} else {
|
||||
|
|
@ -71,17 +71,17 @@ class Twitch {
|
|||
}
|
||||
|
||||
/// Returns a map of a channel's Twitch badges to their URL.
|
||||
static Future<Map<String, String>?> getBadgesChannel({required String id}) async {
|
||||
static Future<Map<String, BadgeInfoTwitch>?> getBadgesChannel({required String id}) async {
|
||||
final url = Uri.parse('https://badges.twitch.tv/v1/badges/channels/$id/display');
|
||||
final response = await http.get(url);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final result = <String, String>{};
|
||||
final result = <String, BadgeInfoTwitch>{};
|
||||
|
||||
final decoded = jsonDecode(response.body)['badge_sets'] as Map;
|
||||
|
||||
decoded.forEach((id, versions) =>
|
||||
(versions['versions'] as Map).forEach((version, badgeInfo) => result['$id/$version'] = BadgeInfoTwitch.fromJson(badgeInfo).imageUrl4x));
|
||||
decoded.forEach(
|
||||
(id, versions) => (versions['versions'] as Map).forEach((version, badgeInfo) => result['$id/$version'] = BadgeInfoTwitch.fromJson(badgeInfo)));
|
||||
|
||||
return result;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ 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/irc.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
|
@ -20,8 +21,11 @@ abstract class _ChatStoreBase with Store {
|
|||
/// The Twitch IRC WebSocket channel.
|
||||
final _channel = WebSocketChannel.connect(Uri.parse('wss://irc-ws.chat.twitch.tv:443'));
|
||||
|
||||
/// The map of badges and emote words to their image or GIF URL.
|
||||
final _assetToUrl = <String, String>{};
|
||||
/// The map of emote words to their image or GIF URL.
|
||||
final _emoteToUrl = <String, String>{};
|
||||
|
||||
/// The map of badges ids to their object representation.
|
||||
final _badgesToObject = <String, BadgeInfoTwitch>{};
|
||||
|
||||
/// The list of chat messages to render and display.
|
||||
final messages = ObservableList<IRCMessage>();
|
||||
|
|
@ -213,7 +217,7 @@ abstract class _ChatStoreBase with Store {
|
|||
textController.clear();
|
||||
}
|
||||
|
||||
/// Fetches global and channel assets (badges and emotes) and stores them in [_assetToUrl]
|
||||
/// Fetches global and channel assets (badges and emotes) and stores them in [_emoteToUrl]
|
||||
Future<void> getAssets() async {
|
||||
// Fetch the desired channel/user's information.
|
||||
final channelInfo = await Twitch.getUser(userLogin: channelName, headers: auth.headersTwitch);
|
||||
|
|
@ -228,17 +232,24 @@ abstract class _ChatStoreBase with Store {
|
|||
await BTTV.getEmotesChannel(id: channelInfo.id),
|
||||
await Twitch.getEmotesGlobal(headers: auth.headersTwitch),
|
||||
await Twitch.getEmotesChannel(id: channelInfo.id, headers: auth.headersTwitch),
|
||||
await Twitch.getBadgesGlobal(),
|
||||
await Twitch.getBadgesChannel(id: channelInfo.id),
|
||||
await SevenTV.getEmotesGlobal(),
|
||||
await SevenTV.getEmotesChannel(user: channelInfo.login)
|
||||
];
|
||||
|
||||
// Add all the assets to the global word-to-asset map.
|
||||
// 'Global' meaning these assets can be used by any message when rendering.
|
||||
for (final map in assets) {
|
||||
if (map != null) {
|
||||
_assetToUrl.addAll(map);
|
||||
_emoteToUrl.addAll(map);
|
||||
}
|
||||
}
|
||||
|
||||
final badges = [
|
||||
await Twitch.getBadgesGlobal(),
|
||||
await Twitch.getBadgesChannel(id: channelInfo.id),
|
||||
];
|
||||
|
||||
for (final map in badges) {
|
||||
if (map != null) {
|
||||
_badgesToObject.addAll(map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -251,9 +262,18 @@ abstract class _ChatStoreBase with Store {
|
|||
final List<InlineSpan> span;
|
||||
|
||||
if (settings.hideBannedMessages) {
|
||||
span = IRC.generateSpan(ircMessage: ircMessage, assetToUrl: _assetToUrl, hideMessage: true);
|
||||
span = IRC.generateSpan(
|
||||
ircMessage: ircMessage,
|
||||
emoteToUrl: _emoteToUrl,
|
||||
badgeToObject: _badgesToObject,
|
||||
hideMessage: true,
|
||||
);
|
||||
} else {
|
||||
span = IRC.generateSpan(ircMessage: ircMessage, assetToUrl: _assetToUrl);
|
||||
span = IRC.generateSpan(
|
||||
ircMessage: ircMessage,
|
||||
badgeToObject: _badgesToObject,
|
||||
emoteToUrl: _emoteToUrl,
|
||||
);
|
||||
}
|
||||
|
||||
// Render timeouts and bans
|
||||
|
|
@ -298,7 +318,11 @@ abstract class _ChatStoreBase with Store {
|
|||
),
|
||||
);
|
||||
} else if (ircMessage.command == Command.userNotice) {
|
||||
final span = IRC.generateSpan(ircMessage: ircMessage, assetToUrl: _assetToUrl);
|
||||
final span = IRC.generateSpan(
|
||||
ircMessage: ircMessage,
|
||||
emoteToUrl: _emoteToUrl,
|
||||
badgeToObject: _badgesToObject,
|
||||
);
|
||||
|
||||
// Render sub alerts
|
||||
return Container(
|
||||
|
|
@ -319,7 +343,11 @@ abstract class _ChatStoreBase with Store {
|
|||
),
|
||||
);
|
||||
} else {
|
||||
final span = IRC.generateSpan(ircMessage: ircMessage, assetToUrl: _assetToUrl);
|
||||
final span = IRC.generateSpan(
|
||||
ircMessage: ircMessage,
|
||||
emoteToUrl: _emoteToUrl,
|
||||
badgeToObject: _badgesToObject,
|
||||
);
|
||||
|
||||
// Render normal chat message (PRIVMSG).
|
||||
return Padding(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue