diff --git a/lib/api/bttv_api.dart b/lib/api/bttv_api.dart index 68473044..91d2c6bc 100644 --- a/lib/api/bttv_api.dart +++ b/lib/api/bttv_api.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; +import 'package:frosty/models/badges.dart'; import 'package:frosty/models/emotes.dart'; import 'package:http/http.dart' as http; @@ -39,4 +40,18 @@ class BTTV { return []; } } + + static Future?> getBadges() async { + final url = Uri.parse('https://api.betterttv.net/3/cached/badges'); + final response = await http.get(url); + + if (response.statusCode == 200) { + final badges = jsonDecode(response.body) as List; + + final badgeObjects = badges.map((badge) => BadgeInfoBTTV.fromJson(badge)).toList(); + + return {for (final badge in badgeObjects) badge.providerId: badge}; + } + debugPrint('Failed to get FFZ badges'); + } } diff --git a/lib/models/badges.dart b/lib/models/badges.dart index 8656614a..088a39d0 100644 --- a/lib/models/badges.dart +++ b/lib/models/badges.dart @@ -91,3 +91,35 @@ class BadgeInfo7TV { factory BadgeInfo7TV.fromJson(Map json) => _$BadgeInfo7TVFromJson(json); } + +@JsonSerializable(createToJson: false) +class BadgeInfoBTTV { + final String id; + final String name; + final String displayName; + final String providerId; + final BadgeDetailsBTTV badge; + + BadgeInfoBTTV( + this.id, + this.name, + this.displayName, + this.providerId, + this.badge, + ); + + factory BadgeInfoBTTV.fromJson(Map json) => _$BadgeInfoBTTVFromJson(json); +} + +@JsonSerializable(createToJson: false) +class BadgeDetailsBTTV { + final String description; + final String svg; + + BadgeDetailsBTTV( + this.description, + this.svg, + ); + + factory BadgeDetailsBTTV.fromJson(Map json) => _$BadgeDetailsBTTVFromJson(json); +} diff --git a/lib/models/badges.g.dart b/lib/models/badges.g.dart index 69330720..de95c1bf 100644 --- a/lib/models/badges.g.dart +++ b/lib/models/badges.g.dart @@ -43,3 +43,18 @@ BadgeInfo7TV _$BadgeInfo7TVFromJson(Map json) => BadgeInfo7TV( .toList(), (json['users'] as List).map((e) => e as String).toList(), ); + +BadgeInfoBTTV _$BadgeInfoBTTVFromJson(Map json) => + BadgeInfoBTTV( + json['id'] as String, + json['name'] as String, + json['displayName'] as String, + json['providerId'] as String, + BadgeDetailsBTTV.fromJson(json['badge'] as Map), + ); + +BadgeDetailsBTTV _$BadgeDetailsBTTVFromJson(Map json) => + BadgeDetailsBTTV( + json['description'] as String, + json['svg'] as String, + ); diff --git a/lib/models/irc_message.dart b/lib/models/irc_message.dart index 455237ee..377912f6 100644 --- a/lib/models/irc_message.dart +++ b/lib/models/irc_message.dart @@ -1,6 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:frosty/models/badges.dart'; import 'package:frosty/models/emotes.dart'; import 'package:intl/intl.dart'; @@ -75,6 +76,7 @@ class IRCMessage { required Map twitchBadgeToObject, required Map> ffzUserToBadges, required Map> sevenTVUserToBadges, + required Map bttvUserToBadge, RoomFFZ? ffzRoomInfo, bool hideMessage = false, bool zeroWidthEnabled = false, @@ -293,6 +295,26 @@ class IRCMessage { } } + // Add BTTV badges to span + final userBTTVBadge = bttvUserToBadge[tags['user-id']]; + if (userBTTVBadge != null) { + span.add( + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Tooltip( + message: userBTTVBadge.badge.description, + preferBelow: false, + child: SvgPicture.network( + userBTTVBadge.badge.svg, + placeholderBuilder: (context) => const SizedBox(), + height: 20, + ), + ), + ), + ); + span.add(const TextSpan(text: ' ')); + } + // Add 7TV badges to end of badges span final user7TVBadges = sevenTVUserToBadges[tags['user-id']]; if (user7TVBadges != null) { diff --git a/lib/screens/channel/chat/chat_assets_store.dart b/lib/screens/channel/chat/chat_assets_store.dart index 58b10afb..b08d4fa9 100644 --- a/lib/screens/channel/chat/chat_assets_store.dart +++ b/lib/screens/channel/chat/chat_assets_store.dart @@ -43,6 +43,10 @@ abstract class _ChatAssetsStoreBase with Store { @readonly var _userTo7TVBadges = >{}; + /// The map of usernames to their BTTV badge. + @readonly + var _userToBTTVBadges = {}; + /// The current index of the emote menu stack. @observable var emoteMenuIndex = 0; @@ -100,6 +104,9 @@ abstract class _ChatAssetsStoreBase with Store { final sevenTVBadges = await SevenTV.getBadges(); if (sevenTVBadges != null) _userTo7TVBadges = sevenTVBadges; + + final bttvBadges = await BTTV.getBadges(); + if (bttvBadges != null) _userToBTTVBadges = bttvBadges; } } diff --git a/lib/screens/channel/chat/chat_assets_store.g.dart b/lib/screens/channel/chat/chat_assets_store.g.dart index 039e3e55..d041a297 100644 --- a/lib/screens/channel/chat/chat_assets_store.g.dart +++ b/lib/screens/channel/chat/chat_assets_store.g.dart @@ -123,6 +123,24 @@ mixin _$ChatAssetsStore on _ChatAssetsStoreBase, Store { }); } + final _$_userToBTTVBadgesAtom = + Atom(name: '_ChatAssetsStoreBase._userToBTTVBadges'); + + Map get userToBTTVBadges { + _$_userToBTTVBadgesAtom.reportRead(); + return super._userToBTTVBadges; + } + + @override + Map get _userToBTTVBadges => userToBTTVBadges; + + @override + set _userToBTTVBadges(Map value) { + _$_userToBTTVBadgesAtom.reportWrite(value, super._userToBTTVBadges, () { + super._userToBTTVBadges = value; + }); + } + final _$emoteMenuIndexAtom = Atom(name: '_ChatAssetsStoreBase.emoteMenuIndex'); diff --git a/lib/screens/channel/chat/chat_message.dart b/lib/screens/channel/chat/chat_message.dart index 29692df1..c482ee8c 100644 --- a/lib/screens/channel/chat/chat_message.dart +++ b/lib/screens/channel/chat/chat_message.dart @@ -37,6 +37,7 @@ class ChatMessage extends StatelessWidget { twitchBadgeToObject: assetsStore.twitchBadgesToObject, ffzUserToBadges: assetsStore.userToFFZBadges, sevenTVUserToBadges: assetsStore.userTo7TVBadges, + bttvUserToBadge: assetsStore.userToBTTVBadges, ffzRoomInfo: assetsStore.ffzRoomInfo, zeroWidthEnabled: settingsStore.zeroWidthEnabled, timestamp: timeStamps, @@ -63,6 +64,7 @@ class ChatMessage extends StatelessWidget { twitchBadgeToObject: assetsStore.twitchBadgesToObject, ffzUserToBadges: assetsStore.userToFFZBadges, sevenTVUserToBadges: assetsStore.userTo7TVBadges, + bttvUserToBadge: assetsStore.userToBTTVBadges, ffzRoomInfo: assetsStore.ffzRoomInfo, hideMessage: settingsStore.hideBannedMessages, zeroWidthEnabled: settingsStore.zeroWidthEnabled, @@ -115,6 +117,7 @@ class ChatMessage extends StatelessWidget { twitchBadgeToObject: assetsStore.twitchBadgesToObject, ffzUserToBadges: assetsStore.userToFFZBadges, sevenTVUserToBadges: assetsStore.userTo7TVBadges, + bttvUserToBadge: assetsStore.userToBTTVBadges, ffzRoomInfo: assetsStore.ffzRoomInfo, zeroWidthEnabled: settingsStore.zeroWidthEnabled, timestamp: timeStamps,