From 3af5406f60c4ad4120366bdaa74483bd2e5043d7 Mon Sep 17 00:00:00 2001 From: Tommy Date: Sat, 29 Apr 2023 11:34:28 -0400 Subject: [PATCH] Update linting and formatting rules (#261) * Add new linting rules * Apply new linting rules * Fix dart sdk version * Format code to 80 lines --- analysis_options.yaml | 7 + lib/apis/bttv_api.dart | 21 ++- lib/apis/ffz_api.dart | 19 ++- lib/apis/seventv_api.dart | 11 +- lib/apis/twitch_api.dart | 83 ++++++++---- lib/main.dart | 11 +- lib/models/badges.dart | 18 ++- lib/models/category.dart | 6 +- lib/models/channel.dart | 6 +- lib/models/chatters.dart | 6 +- lib/models/emotes.dart | 24 ++-- lib/models/irc.dart | 122 +++++++++++++----- lib/models/stream.dart | 6 +- lib/models/user.dart | 6 +- lib/screens/channel/channel.dart | 58 ++++++--- lib/screens/channel/chat/chat.dart | 27 ++-- .../channel/chat/details/chat_details.dart | 33 +++-- .../chat/details/chat_details_store.dart | 3 +- .../channel/chat/details/chat_modes.dart | 22 +++- .../channel/chat/details/chat_users_list.dart | 67 +++++++--- .../chat/emote_menu/emote_menu_panel.dart | 7 +- .../chat/emote_menu/emote_menu_section.dart | 11 +- .../chat/emote_menu/recent_emotes_panel.dart | 32 +++-- .../chat/stores/chat_assets_store.dart | 56 +++++--- .../channel/chat/stores/chat_store.dart | 64 ++++++--- .../channel/chat/widgets/chat_bottom_bar.dart | 55 +++++--- .../channel/chat/widgets/chat_message.dart | 50 ++++--- .../channel/chat/widgets/chat_user_modal.dart | 20 ++- lib/screens/channel/video/video.dart | 10 +- lib/screens/channel/video/video_bar.dart | 18 ++- lib/screens/channel/video/video_overlay.dart | 47 +++++-- lib/screens/channel/video/video_store.dart | 27 ++-- lib/screens/home/home.dart | 4 +- lib/screens/home/home_store.dart | 6 +- lib/screens/home/search/search.dart | 25 +++- .../search/search_results_categories.dart | 3 +- .../home/search/search_results_channels.dart | 14 +- lib/screens/home/search/search_store.dart | 12 +- .../home/stream_list/large_stream_card.dart | 17 ++- lib/screens/home/stream_list/stream_card.dart | 21 ++- .../home/stream_list/stream_list_store.dart | 8 +- .../home/stream_list/streams_list.dart | 42 +++--- .../home/top/categories/categories.dart | 13 +- .../home/top/categories/category_card.dart | 9 +- .../home/top/categories/category_streams.dart | 47 +++++-- lib/screens/home/top/top.dart | 12 +- lib/screens/onboarding/onboarding_intro.dart | 7 +- lib/screens/onboarding/onboarding_login.dart | 7 +- .../onboarding/onboarding_scaffold.dart | 11 +- lib/screens/onboarding/onboarding_setup.dart | 12 +- .../settings/account/blocked_users.dart | 6 +- .../account/widgets/profile_card.dart | 9 +- lib/screens/settings/chat_settings.dart | 91 ++++++++----- lib/screens/settings/general_settings.dart | 6 +- lib/screens/settings/other_settings.dart | 20 ++- lib/screens/settings/settings.dart | 14 +- lib/screens/settings/stores/auth_store.dart | 16 ++- .../settings/stores/settings_store.dart | 7 +- lib/screens/settings/stores/user_store.dart | 23 +++- lib/screens/settings/video_settings.dart | 15 ++- .../widgets/settings_list_select.dart | 7 +- .../widgets/settings_list_slider.dart | 7 +- lib/widgets/animate_scale.dart | 3 +- lib/widgets/block_report_modal.dart | 3 +- lib/widgets/button.dart | 31 +++-- lib/widgets/cached_image.dart | 6 +- lib/widgets/loading_indicator.dart | 3 +- lib/widgets/page_view.dart | 10 +- lib/widgets/profile_picture.dart | 8 +- lib/widgets/scroll_to_top_button.dart | 3 +- lib/widgets/uptime.dart | 11 +- pubspec.lock | 2 +- test/emote_test.dart | 3 +- test/stream_test.dart | 3 +- 74 files changed, 1049 insertions(+), 481 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 6b3bb043..e06ccaf3 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -27,6 +27,13 @@ linter: rules: - directives_ordering - prefer_single_quotes + - always_use_package_imports + - avoid_void_async + - prefer_final_locals + - prefer_final_in_for_each + - avoid_redundant_argument_values + - unnecessary_parenthesis + - always_declare_return_types # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/lib/apis/bttv_api.dart b/lib/apis/bttv_api.dart index 8cd628e7..e4507a1d 100644 --- a/lib/apis/bttv_api.dart +++ b/lib/apis/bttv_api.dart @@ -19,7 +19,9 @@ class BTTVApi { final decoded = jsonDecode(response.body) as List; final emotes = decoded.map((emote) => EmoteBTTV.fromJson(emote)).toList(); - return emotes.map((emote) => Emote.fromBTTV(emote, EmoteType.bttvGlobal)).toList(); + return emotes + .map((emote) => Emote.fromBTTV(emote, EmoteType.bttvGlobal)) + .toList(); } else { return Future.error('Failed to get BTTV global emotes'); } @@ -27,7 +29,8 @@ class BTTVApi { /// Returns a map of a channel's BTTV emotes to their URL. Future> getEmotesChannel({required String id}) async { - final url = Uri.parse('https://api.betterttv.net/3/cached/users/twitch/$id'); + final url = + Uri.parse('https://api.betterttv.net/3/cached/users/twitch/$id'); final response = await _client.get(url); if (response.statusCode == 200) { @@ -35,8 +38,10 @@ class BTTVApi { final result = EmoteBTTVChannel.fromJson(decoded); final emoteToUrl = []; - emoteToUrl.addAll(result.channelEmotes.map((emote) => Emote.fromBTTV(emote, EmoteType.bttvChannel))); - emoteToUrl.addAll(result.sharedEmotes.map((emote) => Emote.fromBTTV(emote, EmoteType.bttvShared))); + emoteToUrl.addAll(result.channelEmotes + .map((emote) => Emote.fromBTTV(emote, EmoteType.bttvChannel))); + emoteToUrl.addAll(result.sharedEmotes + .map((emote) => Emote.fromBTTV(emote, EmoteType.bttvShared))); return emoteToUrl; } else { @@ -52,9 +57,13 @@ class BTTVApi { if (response.statusCode == 200) { final badges = jsonDecode(response.body) as List; - final badgeObjects = badges.map((badge) => BadgeInfoBTTV.fromJson(badge)).toList(); + final badgeObjects = + badges.map((badge) => BadgeInfoBTTV.fromJson(badge)).toList(); - return {for (final badge in badgeObjects) badge.providerId: ChatBadge.fromBTTV(badge)}; + return { + for (final badge in badgeObjects) + badge.providerId: ChatBadge.fromBTTV(badge) + }; } else { return Future.error('Failed to get BTTV badges'); } diff --git a/lib/apis/ffz_api.dart b/lib/apis/ffz_api.dart index c405afce..e59a21e1 100644 --- a/lib/apis/ffz_api.dart +++ b/lib/apis/ffz_api.dart @@ -22,11 +22,14 @@ class FFZApi { final emotes = []; for (final setId in defaultSets) { - final emoticons = decoded['sets'][setId.toString()]['emoticons'] as List; + final emoticons = + decoded['sets'][setId.toString()]['emoticons'] as List; emotes.addAll(emoticons.map((emote) => EmoteFFZ.fromJson(emote))); } - return emotes.map((emote) => Emote.fromFFZ(emote, EmoteType.ffzGlobal)).toList(); + return emotes + .map((emote) => Emote.fromFFZ(emote, EmoteType.ffzGlobal)) + .toList(); } else { return Future.error('Failed to get FFZ global emotes'); } @@ -40,11 +43,16 @@ class FFZApi { if (response.statusCode == 200) { final decoded = jsonDecode(response.body); final roomInfo = RoomFFZ.fromJson(decoded['room']); - final emoticons = decoded['sets'][roomInfo.set.toString()]['emoticons'] as List; + final emoticons = + decoded['sets'][roomInfo.set.toString()]['emoticons'] as List; final emotes = emoticons.map((emote) => EmoteFFZ.fromJson(emote)); - return Tuple2(roomInfo, emotes.map((emote) => Emote.fromFFZ(emote, EmoteType.ffzChannel)).toList()); + return Tuple2( + roomInfo, + emotes + .map((emote) => Emote.fromFFZ(emote, EmoteType.ffzChannel)) + .toList()); } else { return Future.error('Failed to get FFZ room info'); } @@ -58,7 +66,8 @@ class FFZApi { if (response.statusCode == 200) { final decoded = jsonDecode(response.body); final badges = decoded['badges'] as List; - final badgeObjects = badges.map((badge) => BadgeInfoFFZ.fromJson(badge)).toList(); + final badgeObjects = + badges.map((badge) => BadgeInfoFFZ.fromJson(badge)).toList(); final result = >{}; for (final badge in badgeObjects.reversed) { diff --git a/lib/apis/seventv_api.dart b/lib/apis/seventv_api.dart index 56c912db..e772cee5 100644 --- a/lib/apis/seventv_api.dart +++ b/lib/apis/seventv_api.dart @@ -19,7 +19,9 @@ class SevenTVApi { final decoded = jsonDecode(response.body) as List; final emotes = decoded.map((emote) => Emote7TV.fromJson(emote)); - return emotes.map((emote) => Emote.from7TV(emote, EmoteType.sevenTVGlobal)).toList(); + return emotes + .map((emote) => Emote.from7TV(emote, EmoteType.sevenTVGlobal)) + .toList(); } else { return Future.error('Failed to get 7TV global emotes'); } @@ -34,7 +36,9 @@ class SevenTVApi { final decoded = jsonDecode(response.body) as List; final emotes = decoded.map((emote) => Emote7TV.fromJson(emote)); - return emotes.map((emote) => Emote.from7TV(emote, EmoteType.sevenTVChannel)).toList(); + return emotes + .map((emote) => Emote.from7TV(emote, EmoteType.sevenTVChannel)) + .toList(); } else { return Future.error('Failed to get 7TV channel emotes'); } @@ -42,7 +46,8 @@ class SevenTVApi { /// Returns a map of user IDS to a list of their 7TV badges. Future>> getBadges() async { - final url = Uri.parse('https://api.7tv.app/v2/badges?user_identifier=twitch_id'); + final url = + Uri.parse('https://api.7tv.app/v2/badges?user_identifier=twitch_id'); final response = await _client.get(url); if (response.statusCode == 200) { diff --git a/lib/apis/twitch_api.dart b/lib/apis/twitch_api.dart index 3703d186..41f78c50 100644 --- a/lib/apis/twitch_api.dart +++ b/lib/apis/twitch_api.dart @@ -18,15 +18,19 @@ class TwitchApi { const TwitchApi(this._client); /// Returns a list of all Twitch global emotes. - Future> getEmotesGlobal({required Map headers}) async { + Future> getEmotesGlobal( + {required Map headers}) async { final url = Uri.parse('https://api.twitch.tv/helix/chat/emotes/global'); final response = await _client.get(url, headers: headers); if (response.statusCode == 200) { final decoded = jsonDecode(response.body)['data'] as List; - final emotes = decoded.map((emote) => EmoteTwitch.fromJson(emote)).toList(); + final emotes = + decoded.map((emote) => EmoteTwitch.fromJson(emote)).toList(); - return emotes.map((emote) => Emote.fromTwitch(emote, EmoteType.twitchGlobal)).toList(); + return emotes + .map((emote) => Emote.fromTwitch(emote, EmoteType.twitchGlobal)) + .toList(); } else { return Future.error('Failed to get Twitch global emotes'); } @@ -37,12 +41,14 @@ class TwitchApi { required String id, required Map headers, }) async { - final url = Uri.parse('https://api.twitch.tv/helix/chat/emotes?broadcaster_id=$id'); + final url = + Uri.parse('https://api.twitch.tv/helix/chat/emotes?broadcaster_id=$id'); final response = await _client.get(url, headers: headers); if (response.statusCode == 200) { final decoded = jsonDecode(response.body)['data'] as List; - final emotes = decoded.map((emote) => EmoteTwitch.fromJson(emote)).toList(); + final emotes = + decoded.map((emote) => EmoteTwitch.fromJson(emote)).toList(); return emotes.map((emote) { switch (emote.emoteType) { @@ -66,12 +72,14 @@ class TwitchApi { required String setId, required Map headers, }) async { - final url = Uri.parse('https://api.twitch.tv/helix/chat/emotes/set?emote_set_id=$setId'); + final url = Uri.parse( + 'https://api.twitch.tv/helix/chat/emotes/set?emote_set_id=$setId'); final response = await _client.get(url, headers: headers); if (response.statusCode == 200) { final decoded = jsonDecode(response.body)['data'] as List; - final emotes = decoded.map((emote) => EmoteTwitch.fromJson(emote)).toList(); + final emotes = + decoded.map((emote) => EmoteTwitch.fromJson(emote)).toList(); return emotes.map((emote) { switch (emote.emoteType) { @@ -90,7 +98,8 @@ class TwitchApi { } /// Returns a map of global Twitch badges to their [Emote] object. - Future> getBadgesGlobal({required Map headers}) async { + Future> getBadgesGlobal( + {required Map headers}) async { final url = Uri.parse('https://api.twitch.tv/helix/chat/badges/global'); final response = await _client.get(url, headers: headers); @@ -119,7 +128,8 @@ class TwitchApi { required String id, required Map headers, }) async { - final url = Uri.parse('https://api.twitch.tv/helix/chat/badges?broadcaster_id=$id'); + final url = + Uri.parse('https://api.twitch.tv/helix/chat/badges?broadcaster_id=$id'); final response = await _client.get(url, headers: headers); if (response.statusCode == 200) { @@ -181,7 +191,8 @@ class TwitchApi { Future validateToken({required String token}) async { final url = Uri.parse('https://id.twitch.tv/oauth2/validate'); - final response = await _client.get(url, headers: {'Authorization': 'Bearer $token'}); + final response = + await _client.get(url, headers: {'Authorization': 'Bearer $token'}); if (response.statusCode == 200) { return true; } else { @@ -194,8 +205,9 @@ class TwitchApi { required Map headers, String? cursor, }) async { - final url = Uri.parse( - cursor == null ? 'https://api.twitch.tv/helix/streams' : 'https://api.twitch.tv/helix/streams?after=$cursor'); + final url = Uri.parse(cursor == null + ? 'https://api.twitch.tv/helix/streams' + : 'https://api.twitch.tv/helix/streams?after=$cursor'); final response = await _client.get(url, headers: headers); final decoded = jsonDecode(response.body); @@ -221,7 +233,8 @@ class TwitchApi { if (response.statusCode == 200) { return StreamsTwitch.fromJson(decoded); } else { - return Future.error('Failed to get followed streams: ${decoded['message']}'); + return Future.error( + 'Failed to get followed streams: ${decoded['message']}'); } } @@ -240,7 +253,8 @@ class TwitchApi { if (response.statusCode == 200) { return StreamsTwitch.fromJson(decoded); } else { - return Future.error('Failed to get streams under category: ${decoded['message']}'); + return Future.error( + 'Failed to get streams under category: ${decoded['message']}'); } } @@ -249,7 +263,8 @@ class TwitchApi { required String userLogin, required Map headers, }) async { - final uri = Uri.parse('https://api.twitch.tv/helix/streams?user_login=$userLogin'); + final uri = + Uri.parse('https://api.twitch.tv/helix/streams?user_login=$userLogin'); final response = await _client.get(uri, headers: headers); if (response.statusCode == 200) { @@ -272,8 +287,9 @@ class TwitchApi { String? id, required Map headers, }) async { - final url = Uri.parse( - id != null ? 'https://api.twitch.tv/helix/users?id=$id' : 'https://api.twitch.tv/helix/users?login=$userLogin'); + final url = Uri.parse(id != null + ? 'https://api.twitch.tv/helix/users?id=$id' + : 'https://api.twitch.tv/helix/users?login=$userLogin'); final response = await _client.get(url, headers: headers); final decoded = jsonDecode(response.body); @@ -296,7 +312,8 @@ class TwitchApi { required String userId, required Map headers, }) async { - final url = Uri.parse('https://api.twitch.tv/helix/channels?broadcaster_id=$userId'); + final url = Uri.parse( + 'https://api.twitch.tv/helix/channels?broadcaster_id=$userId'); final response = await _client.get(url, headers: headers); final decoded = jsonDecode(response.body); @@ -318,7 +335,8 @@ class TwitchApi { required String query, required Map headers, }) async { - final url = Uri.parse('https://api.twitch.tv/helix/search/channels?first=8&query=$query'); + final url = Uri.parse( + 'https://api.twitch.tv/helix/search/channels?first=8&query=$query'); final response = await _client.get(url, headers: headers); if (response.statusCode == 200) { @@ -344,7 +362,8 @@ class TwitchApi { if (response.statusCode == 200) { return CategoriesTwitch.fromJson(decoded); } else { - return Future.error('Failed to get top categories: ${decoded['message']}'); + return Future.error( + 'Failed to get top categories: ${decoded['message']}'); } } @@ -389,7 +408,8 @@ class TwitchApi { required String userId, required Map headers, }) async { - final uri = Uri.parse('https://api.twitch.tv/helix/subscriptions?broadcaster_id=$userId'); + final uri = Uri.parse( + 'https://api.twitch.tv/helix/subscriptions?broadcaster_id=$userId'); final response = await _client.get(uri, headers: headers); if (response.statusCode == 200) { @@ -403,7 +423,8 @@ class TwitchApi { /// Returns a [ChatUsers] object containing the names of chatters in the given [userLogin]'s chat. Future getChatters({required String userLogin}) async { - final uri = Uri.parse('https://tmi.twitch.tv/group/user/$userLogin/chatters'); + final uri = + Uri.parse('https://tmi.twitch.tv/group/user/$userLogin/chatters'); final response = await _client.get(uri); if (response.statusCode == 200) { @@ -432,7 +453,8 @@ class TwitchApi { final blockedList = decoded['data'] as List; if (blockedList.isNotEmpty) { - final result = blockedList.map((e) => UserBlockedTwitch.fromJson(e)).toList(); + final result = + blockedList.map((e) => UserBlockedTwitch.fromJson(e)).toList(); if (cursor != null) { // Wait a bit (150 milliseconds) before recursively calling. @@ -441,7 +463,8 @@ class TwitchApi { // With the Twitch API, we can make up to 800 requests per minute. // Waiting 150 milliseconds between requests will cap the rate here at 400 requests per minute. await Future.delayed(const Duration(milliseconds: 150)); - result.addAll(await getUserBlockedList(id: id, cursor: cursor, headers: headers)); + result.addAll(await getUserBlockedList( + id: id, cursor: cursor, headers: headers)); } return result; @@ -455,8 +478,10 @@ class TwitchApi { } // Blocks the user with the given ID and returns true on success or false on failure. - Future blockUser({required String userId, required Map headers}) async { - final url = Uri.parse('https://api.twitch.tv/helix/users/blocks?target_user_id=$userId'); + Future blockUser( + {required String userId, required Map headers}) async { + final url = Uri.parse( + 'https://api.twitch.tv/helix/users/blocks?target_user_id=$userId'); final response = await _client.put(url, headers: headers); if (response.statusCode == 204) { @@ -467,8 +492,10 @@ class TwitchApi { } // Unblocks the user with the given ID and returns true on success or false on failure. - Future unblockUser({required String userId, required Map headers}) async { - final url = Uri.parse('https://api.twitch.tv/helix/users/blocks?target_user_id=$userId'); + Future unblockUser( + {required String userId, required Map headers}) async { + final url = Uri.parse( + 'https://api.twitch.tv/helix/users/blocks?target_user_id=$userId'); final response = await _client.delete(url, headers: headers); if (response.statusCode == 204) { diff --git a/lib/main.dart b/lib/main.dart index 7756cd6f..29f2159b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -47,7 +47,10 @@ void main() async { autorun((_) => prefs.setString('settings', jsonEncode(settingsStore))); // Initialize Sentry for crash reporting if enabled. - if (settingsStore.sendCrashLogs) await SentryFlutter.init((options) => options.tracesSampleRate = sampleRate); + if (settingsStore.sendCrashLogs) { + await SentryFlutter.init( + (options) => options.tracesSampleRate = sampleRate); + } /// Initialize API services with a common client. /// This will prevent every request from creating a new client instance. @@ -165,7 +168,8 @@ final darkTheme = ThemeData( fontWeight: FontWeight.bold, ), ), - bottomNavigationBarTheme: const BottomNavigationBarThemeData(backgroundColor: gray), + bottomNavigationBarTheme: + const BottomNavigationBarThemeData(backgroundColor: gray), colorScheme: ColorScheme.fromSwatch( brightness: Brightness.dark, primarySwatch: Colors.deepPurple, @@ -220,7 +224,8 @@ class MyApp extends StatelessWidget { locale: DevicePreview.locale(context), title: 'Frosty', theme: lightTheme, - darkTheme: settingsStore.themeType == ThemeType.dark || settingsStore.themeType == ThemeType.system + darkTheme: settingsStore.themeType == ThemeType.dark || + settingsStore.themeType == ThemeType.system ? darkTheme : oledTheme, themeMode: settingsStore.themeType == ThemeType.system diff --git a/lib/models/badges.dart b/lib/models/badges.dart index 4d116564..70faf87c 100644 --- a/lib/models/badges.dart +++ b/lib/models/badges.dart @@ -24,7 +24,8 @@ class BadgeInfoTwitch { this.description, ); - factory BadgeInfoTwitch.fromJson(Map json) => _$BadgeInfoTwitchFromJson(json); + factory BadgeInfoTwitch.fromJson(Map json) => + _$BadgeInfoTwitchFromJson(json); } @JsonSerializable(createToJson: false, fieldRename: FieldRename.snake) @@ -41,7 +42,8 @@ class BadgeInfoFFZ { this.urls, ); - factory BadgeInfoFFZ.fromJson(Map json) => _$BadgeInfoFFZFromJson(json); + factory BadgeInfoFFZ.fromJson(Map json) => + _$BadgeInfoFFZFromJson(json); } @JsonSerializable(createToJson: false) @@ -59,7 +61,8 @@ class BadgeUrlsFFZ { this.url4x, ); - factory BadgeUrlsFFZ.fromJson(Map json) => _$BadgeUrlsFFZFromJson(json); + factory BadgeUrlsFFZ.fromJson(Map json) => + _$BadgeUrlsFFZFromJson(json); } @JsonSerializable(createToJson: false) @@ -74,7 +77,8 @@ class BadgeInfo7TV { this.users, ); - factory BadgeInfo7TV.fromJson(Map json) => _$BadgeInfo7TVFromJson(json); + factory BadgeInfo7TV.fromJson(Map json) => + _$BadgeInfo7TVFromJson(json); } @JsonSerializable(createToJson: false) @@ -87,7 +91,8 @@ class BadgeInfoBTTV { this.badge, ); - factory BadgeInfoBTTV.fromJson(Map json) => _$BadgeInfoBTTVFromJson(json); + factory BadgeInfoBTTV.fromJson(Map json) => + _$BadgeInfoBTTVFromJson(json); } @JsonSerializable(createToJson: false) @@ -100,7 +105,8 @@ class BadgeDetailsBTTV { this.svg, ); - factory BadgeDetailsBTTV.fromJson(Map json) => _$BadgeDetailsBTTVFromJson(json); + factory BadgeDetailsBTTV.fromJson(Map json) => + _$BadgeDetailsBTTVFromJson(json); } class ChatBadge { diff --git a/lib/models/category.dart b/lib/models/category.dart index 34cb2c9c..05dc473a 100644 --- a/lib/models/category.dart +++ b/lib/models/category.dart @@ -14,7 +14,8 @@ class CategoryTwitch { this.name, ); - factory CategoryTwitch.fromJson(Map json) => _$CategoryTwitchFromJson(json); + factory CategoryTwitch.fromJson(Map json) => + _$CategoryTwitchFromJson(json); } @JsonSerializable(createToJson: false, fieldRename: FieldRename.snake) @@ -27,5 +28,6 @@ class CategoriesTwitch { this.pagination, ); - factory CategoriesTwitch.fromJson(Map json) => _$CategoriesTwitchFromJson(json); + factory CategoriesTwitch.fromJson(Map json) => + _$CategoriesTwitchFromJson(json); } diff --git a/lib/models/channel.dart b/lib/models/channel.dart index 0be892fe..be13fa41 100644 --- a/lib/models/channel.dart +++ b/lib/models/channel.dart @@ -15,7 +15,8 @@ class Channel { required this.broadcasterName, }); - factory Channel.fromJson(Map json) => _$ChannelFromJson(json); + factory Channel.fromJson(Map json) => + _$ChannelFromJson(json); } // Object for Twitch channel search query. @@ -35,5 +36,6 @@ class ChannelQuery { required this.startedAt, }); - factory ChannelQuery.fromJson(Map json) => _$ChannelQueryFromJson(json); + factory ChannelQuery.fromJson(Map json) => + _$ChannelQueryFromJson(json); } diff --git a/lib/models/chatters.dart b/lib/models/chatters.dart index e2c4d8cc..c059807e 100644 --- a/lib/models/chatters.dart +++ b/lib/models/chatters.dart @@ -12,7 +12,8 @@ class ChatUsers { this.chatters, ); - factory ChatUsers.fromJson(Map json) => _$ChatUsersFromJson(json); + factory ChatUsers.fromJson(Map json) => + _$ChatUsersFromJson(json); } @JsonSerializable(createToJson: false, fieldRename: FieldRename.snake) @@ -35,5 +36,6 @@ class Chatters { this.viewers, ); - factory Chatters.fromJson(Map json) => _$ChattersFromJson(json); + factory Chatters.fromJson(Map json) => + _$ChattersFromJson(json); } diff --git a/lib/models/emotes.dart b/lib/models/emotes.dart index 2b307176..07118cd6 100644 --- a/lib/models/emotes.dart +++ b/lib/models/emotes.dart @@ -18,7 +18,8 @@ class EmoteTwitch { this.ownerId, ); - factory EmoteTwitch.fromJson(Map json) => _$EmoteTwitchFromJson(json); + factory EmoteTwitch.fromJson(Map json) => + _$EmoteTwitchFromJson(json); } // * BTTV Emotes * @@ -32,7 +33,8 @@ class EmoteBTTV { this.code, ); - factory EmoteBTTV.fromJson(Map json) => _$EmoteBTTVFromJson(json); + factory EmoteBTTV.fromJson(Map json) => + _$EmoteBTTVFromJson(json); } @JsonSerializable(createToJson: false) @@ -45,7 +47,8 @@ class EmoteBTTVChannel { this.sharedEmotes, ); - factory EmoteBTTVChannel.fromJson(Map json) => _$EmoteBTTVChannelFromJson(json); + factory EmoteBTTVChannel.fromJson(Map json) => + _$EmoteBTTVChannelFromJson(json); } // * FFZ Emotes * @@ -61,7 +64,8 @@ class RoomFFZ { this.modUrls, ); - factory RoomFFZ.fromJson(Map json) => _$RoomFFZFromJson(json); + factory RoomFFZ.fromJson(Map json) => + _$RoomFFZFromJson(json); } @JsonSerializable(createToJson: false) @@ -79,7 +83,8 @@ class ImagesFFZ { this.url4x, ); - factory ImagesFFZ.fromJson(Map json) => _$ImagesFFZFromJson(json); + factory ImagesFFZ.fromJson(Map json) => + _$ImagesFFZFromJson(json); } @JsonSerializable(createToJson: false) @@ -96,7 +101,8 @@ class EmoteFFZ { this.urls, ); - factory EmoteFFZ.fromJson(Map json) => _$EmoteFFZFromJson(json); + factory EmoteFFZ.fromJson(Map json) => + _$EmoteFFZFromJson(json); } @JsonSerializable(createToJson: false, fieldRename: FieldRename.snake) @@ -115,7 +121,8 @@ class Emote7TV { this.urls, ); - factory Emote7TV.fromJson(Map json) => _$Emote7TVFromJson(json); + factory Emote7TV.fromJson(Map json) => + _$Emote7TVFromJson(json); } /// The common emote class. @@ -142,7 +149,8 @@ class Emote { factory Emote.fromTwitch(EmoteTwitch emote, EmoteType type) => Emote( name: emote.name, zeroWidth: false, - url: 'https://static-cdn.jtvnw.net/emoticons/v2/${emote.id}/default/dark/3.0', + url: + 'https://static-cdn.jtvnw.net/emoticons/v2/${emote.id}/default/dark/3.0', type: type, ownerId: emote.ownerId, ); diff --git a/lib/models/irc.dart b/lib/models/irc.dart index d7f71cbd..b75c102d 100644 --- a/lib/models/irc.dart +++ b/lib/models/irc.dart @@ -45,7 +45,8 @@ class IRCMessage { if (ircMessage.message == null) { messages.clear(); bufferedMessages.clear(); - messages.add(IRCMessage.createNotice(message: 'Chat was cleared by a moderator')); + messages.add( + IRCMessage.createNotice(message: 'Chat was cleared by a moderator')); return; } @@ -130,7 +131,8 @@ class IRCMessage { final span = []; if (timestamp != TimestampType.disabled) { - final time = tags['tmi-sent-ts'] ?? DateTime.now().millisecondsSinceEpoch.toString(); + final time = tags['tmi-sent-ts'] ?? + DateTime.now().millisecondsSinceEpoch.toString(); final parsedTime = DateTime.fromMillisecondsSinceEpoch(int.parse(time)); if (timestamp == TimestampType.twentyFour) { @@ -165,16 +167,20 @@ class IRCMessage { var badgeUrl = badgeInfo.url; // Add custom FFZ mod badge if it exists. - if (badgeInfo.name == 'Moderator' && (ffzUserBadges != null || ffzRoomInfo?.modUrls != null)) { + if (badgeInfo.name == 'Moderator' && + (ffzUserBadges != null || ffzRoomInfo?.modUrls != null)) { // Check if mod is bot. - final botBadge = ffzUserBadges?.firstWhereOrNull((element) => element.name == 'Bot'); + final botBadge = ffzUserBadges + ?.firstWhereOrNull((element) => element.name == 'Bot'); // Check if user has bot badge or room has custom FFZ mod badges if (botBadge != null) { badgeUrl = botBadge.url; skipBot = true; } else if (ffzRoomInfo?.modUrls != null) { - badgeUrl = ffzRoomInfo!.modUrls?.url4x ?? ffzRoomInfo.modUrls?.url2x ?? ffzRoomInfo.modUrls!.url1x; + badgeUrl = ffzRoomInfo!.modUrls?.url4x ?? + ffzRoomInfo.modUrls?.url2x ?? + ffzRoomInfo.modUrls!.url1x; } final newBadge = ChatBadge( @@ -196,7 +202,9 @@ class IRCMessage { // Add custom FFZ vip badge if it exists if (badgeInfo.name == 'VIP' && ffzRoomInfo?.vipBadge != null) { - badgeUrl = ffzRoomInfo!.vipBadge?.url4x ?? ffzRoomInfo.vipBadge?.url2x ?? ffzRoomInfo.vipBadge!.url1x; + badgeUrl = ffzRoomInfo!.vipBadge?.url4x ?? + ffzRoomInfo.vipBadge?.url2x ?? + ffzRoomInfo.vipBadge!.url1x; } final newBadge = ChatBadge( @@ -273,14 +281,23 @@ class IRCMessage { } } - var color = Color(int.parse((tags['color'] ?? '#868686').replaceFirst('#', '0xFF'))); + var color = Color( + int.parse((tags['color'] ?? '#868686').replaceFirst('#', '0xFF'))); if (useReadableColors) { final hsl = HSLColor.fromColor(color); if (isLightTheme == true) { - if (hsl.lightness >= 0.5) color = hsl.withLightness(hsl.lightness + ((0 - hsl.lightness) * 0.5)).toColor(); + if (hsl.lightness >= 0.5) { + color = hsl + .withLightness(hsl.lightness + ((0 - hsl.lightness) * 0.5)) + .toColor(); + } } else { - if (hsl.lightness <= 0.5) color = hsl.withLightness(hsl.lightness + ((1 - hsl.lightness) * 0.5)).toColor(); + if (hsl.lightness <= 0.5) { + color = hsl + .withLightness(hsl.lightness + ((1 - hsl.lightness) * 0.5)) + .toColor(); + } } } @@ -288,7 +305,9 @@ class IRCMessage { final displayName = tags['display-name']!; span.add( TextSpan( - text: regexEnglish.hasMatch(displayName) ? displayName : '$displayName ($user)', + text: regexEnglish.hasMatch(displayName) + ? displayName + : '$displayName ($user)', style: TextStyle( color: color, fontWeight: FontWeight.bold, @@ -301,13 +320,16 @@ class IRCMessage { if (action == false) span.add(const TextSpan(text: ':')); // Italicize the text if it was called with an IRC Action (e.g., "/me"). - final textStyle = action == true ? const TextStyle(fontStyle: FontStyle.italic) : style; + final textStyle = + action == true ? const TextStyle(fontStyle: FontStyle.italic) : style; if (!showMessage) { span.add(const TextSpan(text: ' ')); } else { // Check if the message is a reply. If it is, remove the reply username (first word) from the message. - final words = tags.containsKey('reply-parent-display-name') ? split?.sublist(1) : split; + final words = tags.containsKey('reply-parent-display-name') + ? split?.sublist(1) + : split; // Add the message and any emotes to the span. if (words != null) { @@ -334,7 +356,8 @@ class IRCMessage { while (nextEmote != null && nextEmote.zeroWidth && index != 0) { emoteStack.add(nextEmote); index--; - nextEmote = emoteToObject[words[index]] ?? localEmotes?[words[index]]; + nextEmote = + emoteToObject[words[index]] ?? localEmotes?[words[index]]; } // If there's one more emote thats NOT zero-width, add it to the base of the stack. @@ -354,15 +377,20 @@ class IRCMessage { ...emoteStack.reversed.map( (emote) => FrostyCachedNetworkImage( imageUrl: emote.url, - height: emote.height != null ? emote.height! * emoteScale : emoteSize, - width: emote.width != null ? emote.width! * emoteScale : emoteSize, + height: emote.height != null + ? emote.height! * emoteScale + : emoteSize, + width: emote.width != null + ? emote.width! * emoteScale + : emoteSize, useFade: false, ), ) ]; // Create the message for the tooltip - final message = emoteStack.reversed.map((emote) => '${emote.name} - ${emoteType[emote.type.index]}'); + final message = emoteStack.reversed.map( + (emote) => '${emote.name} - ${emoteType[emote.type.index]}'); localSpan.add( WidgetSpan( alignment: PlaceholderAlignment.middle, @@ -399,7 +427,9 @@ class IRCMessage { style: const TextStyle(color: Colors.white), ), Text( - nextWordIsEmoji ? message.join(', ') : message.skip(1).join(', '), + nextWordIsEmoji + ? message.join(', ') + : message.skip(1).join(', '), style: const TextStyle(color: Colors.white), ), ], @@ -419,22 +449,32 @@ class IRCMessage { // If the next word is neither an emote or emoji, add it as a text span. if (nextEmote == null && !nextWordIsEmoji) { localSpan.add(const TextSpan(text: ' ')); - localSpan.add(_createTextSpan(text: words[index], style: textStyle, launchExternal: launchExternal)); + localSpan.add(_createTextSpan( + text: words[index], + style: textStyle, + launchExternal: launchExternal)); } } else { localSpan.add( _createEmoteSpan( emote: emote, - height: emote.height != null ? emote.height! * emoteScale : emoteSize, + height: emote.height != null + ? emote.height! * emoteScale + : emoteSize, width: emote.width != null ? emote.width! * emoteScale : null, ), ); } } else { if (regexEmoji.hasMatch(word)) { - localSpan.add(_createEmojiSpan(emoji: word, style: textStyle?.copyWith(fontSize: emoteSize - 5))); + localSpan.add(_createEmojiSpan( + emoji: word, + style: textStyle?.copyWith(fontSize: emoteSize - 5))); } else { - localSpan.add(_createTextSpan(text: word, style: textStyle, launchExternal: launchExternal)); + localSpan.add(_createTextSpan( + text: word, + style: textStyle, + launchExternal: launchExternal)); } } @@ -576,16 +616,20 @@ class IRCMessage { ); } - static TextSpan _createTextSpan({required String text, required bool launchExternal, TextStyle? style}) { + static TextSpan _createTextSpan( + {required String text, required bool launchExternal, TextStyle? style}) { if (text.startsWith('@')) { - return TextSpan(text: text, style: style?.copyWith(fontWeight: FontWeight.bold)); + return TextSpan( + text: text, style: style?.copyWith(fontWeight: FontWeight.bold)); } else if (RegExp(r'https?:\/\/').hasMatch(text)) { return TextSpan( text: text, style: style?.copyWith(color: Colors.blue), recognizer: TapGestureRecognizer() ..onTap = () => launchUrl(Uri.parse(text), - mode: launchExternal ? LaunchMode.externalApplication : LaunchMode.inAppWebView), + mode: launchExternal + ? LaunchMode.externalApplication + : LaunchMode.inAppWebView), ); } else { return TextSpan(text: text, style: style); @@ -607,7 +651,8 @@ class IRCMessage { // Get the tags substring and escape characters. // IRC messages escape spaces with \s. - final tags = whole.substring(1, tagAndIrcMessageDivider).replaceAll('\\s', ' '); + final tags = + whole.substring(1, tagAndIrcMessageDivider).replaceAll('\\s', ' '); // Next, parse and map the tags. final mappedTags = {}; @@ -634,15 +679,18 @@ class IRCMessage { // If the username exists, set it. // tmi.twitch.tv means the message was sent by Twitch rather than a user, so will be irrelevant. - final String? user = - splitMessage[0] == 'tmi.twitch.tv' ? null : splitMessage[0].substring(0, splitMessage[0].indexOf('!')); + final String? user = splitMessage[0] == 'tmi.twitch.tv' + ? null + : splitMessage[0].substring(0, splitMessage[0].indexOf('!')); // If there is an associated message, set it. // // Also remove any "INVALID/UNDEFINED" Unicode characters. // Rendering this character on iOS shows a question mark inside a square. // This character is used by some clients to bypass restrictions on repeating message. - var message = splitMessage.length > 3 ? splitMessage.sublist(3).join(' ').substring(1) : null; + var message = splitMessage.length > 3 + ? splitMessage.sublist(3).join(' ').substring(1) + : null; // Now process any Twitch emotes contained in the message tags. // The map containing emotes from the user's tags to their URL. @@ -656,14 +704,16 @@ class IRCMessage { for (final emoteIdAndPosition in emotes) { final indexBetweenIdAndPositions = emoteIdAndPosition.indexOf(':'); - final emoteId = emoteIdAndPosition.substring(0, indexBetweenIdAndPositions); + final emoteId = + emoteIdAndPosition.substring(0, indexBetweenIdAndPositions); // Parse the range in order to extract the associated word. // If there are more than one indices, use the first one. // Else, use the one provided indices. final String range; if (emoteIdAndPosition.contains(',')) { - range = emoteIdAndPosition.substring(indexBetweenIdAndPositions + 1, emoteIdAndPosition.indexOf(',')); + range = emoteIdAndPosition.substring( + indexBetweenIdAndPositions + 1, emoteIdAndPosition.indexOf(',')); } else { range = emoteIdAndPosition.substring(indexBetweenIdAndPositions + 1); } @@ -679,7 +729,8 @@ class IRCMessage { localEmotes[emoteWord] = Emote( name: emoteWord, zeroWidth: false, - url: 'https://static-cdn.jtvnw.net/emoticons/v2/$emoteId/default/dark/3.0', + url: + 'https://static-cdn.jtvnw.net/emoticons/v2/$emoteId/default/dark/3.0', type: EmoteType.twitchSub, ); } @@ -696,7 +747,9 @@ class IRCMessage { } // Check if the message mentions the logged-in user - if (userLogin != null) mention = message.toLowerCase().contains(userLogin); + if (userLogin != null) { + mention = message.toLowerCase().contains(userLogin); + } // Escape the message message = message @@ -725,7 +778,10 @@ class IRCMessage { } } - if (wordBuffer.isNotEmpty) split.addAll(wordBuffer.toString().split(' ').where((element) => element != '')); + if (wordBuffer.isNotEmpty) { + split.addAll( + wordBuffer.toString().split(' ').where((element) => element != '')); + } if (emojiBuffer.isNotEmpty) split.add(emojiBuffer.toString()); } diff --git a/lib/models/stream.dart b/lib/models/stream.dart index b68c9c4f..05edf653 100644 --- a/lib/models/stream.dart +++ b/lib/models/stream.dart @@ -27,7 +27,8 @@ class StreamTwitch { this.thumbnailUrl, ); - factory StreamTwitch.fromJson(Map json) => _$StreamTwitchFromJson(json); + factory StreamTwitch.fromJson(Map json) => + _$StreamTwitchFromJson(json); } @JsonSerializable(createToJson: false, fieldRename: FieldRename.snake) @@ -40,5 +41,6 @@ class StreamsTwitch { this.pagination, ); - factory StreamsTwitch.fromJson(Map json) => _$StreamsTwitchFromJson(json); + factory StreamsTwitch.fromJson(Map json) => + _$StreamsTwitchFromJson(json); } diff --git a/lib/models/user.dart b/lib/models/user.dart index c99723ce..59030217 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -17,7 +17,8 @@ class UserTwitch { this.profileImageUrl, ); - factory UserTwitch.fromJson(Map json) => _$UserTwitchFromJson(json); + factory UserTwitch.fromJson(Map json) => + _$UserTwitchFromJson(json); } @JsonSerializable(createToJson: false, fieldRename: FieldRename.snake) @@ -32,5 +33,6 @@ class UserBlockedTwitch { this.displayName, ); - factory UserBlockedTwitch.fromJson(Map json) => _$UserBlockedTwitchFromJson(json); + factory UserBlockedTwitch.fromJson(Map json) => + _$UserBlockedTwitchFromJson(json); } diff --git a/lib/screens/channel/channel.dart b/lib/screens/channel/channel.dart index 1d383893..335d643f 100644 --- a/lib/screens/channel/channel.dart +++ b/lib/screens/channel/channel.dart @@ -95,8 +95,9 @@ class _VideoChatState extends State { final overlay = GestureDetector( onLongPress: _videoStore.handleToggleOverlay, - onDoubleTap: - orientation == Orientation.landscape ? () => settingsStore.fullScreen = !settingsStore.fullScreen : null, + onDoubleTap: orientation == Orientation.landscape + ? () => settingsStore.fullScreen = !settingsStore.fullScreen + : null, onTap: () { if (_chatStore.assetsStore.showEmoteMenu) { _chatStore.assetsStore.showEmoteMenu = false; @@ -115,7 +116,9 @@ class _VideoChatState extends State { chatStore: _chatStore, ); - if (_videoStore.paused || _videoStore.streamInfo == null) return videoOverlay; + if (_videoStore.paused || _videoStore.streamInfo == null) { + return videoOverlay; + } return AnimatedOpacity( opacity: _videoStore.overlayVisible ? 1.0 : 0.0, @@ -189,16 +192,21 @@ class _VideoChatState extends State { switchOutCurve: Curves.easeIn, child: _chatStore.notification != null ? Align( - alignment: - _chatStore.settings.chatNotificationsOnBottom ? Alignment.bottomCenter : Alignment.topCenter, + alignment: _chatStore.settings.chatNotificationsOnBottom + ? Alignment.bottomCenter + : Alignment.topCenter, child: FrostyNotification( message: _chatStore.notification!, - showPasteButton: _chatStore.notification!.contains('copied'), + showPasteButton: + _chatStore.notification!.contains('copied'), onButtonPressed: () async { // Paste clipboard text into the text controller. - final data = await Clipboard.getData(Clipboard.kTextPlain); + final data = + await Clipboard.getData(Clipboard.kTextPlain); - if (data != null) _chatStore.textController.text = data.text!; + if (data != null) { + _chatStore.textController.text = data.text!; + } _chatStore.updateNotification(''); }, @@ -214,7 +222,8 @@ class _VideoChatState extends State { final videoChat = Scaffold( body: Observer( builder: (context) { - if (orientation == Orientation.landscape && !settingsStore.landscapeForceVerticalChat) { + if (orientation == Orientation.landscape && + !settingsStore.landscapeForceVerticalChat) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); final landscapeChat = AnimatedContainer( @@ -222,9 +231,11 @@ class _VideoChatState extends State { duration: const Duration(milliseconds: 200), width: _chatStore.expandChat ? MediaQuery.of(context).size.width / 2 - : MediaQuery.of(context).size.width * _chatStore.settings.chatWidth, + : MediaQuery.of(context).size.width * + _chatStore.settings.chatWidth, color: _chatStore.settings.fullScreen - ? Colors.black.withOpacity(_chatStore.settings.fullScreenChatOverlayOpacity) + ? Colors.black.withOpacity( + _chatStore.settings.fullScreenChatOverlayOpacity) : Theme.of(context).scaffoldBackgroundColor, child: chat, ); @@ -235,22 +246,30 @@ class _VideoChatState extends State { child: Theme( data: darkTheme, child: DefaultTextStyle( - style: DefaultTextStyle.of(context).style.copyWith(color: Colors.white), + style: DefaultTextStyle.of(context) + .style + .copyWith(color: Colors.white), child: landscapeChat, ), ), ); return ColoredBox( - color: settingsStore.showVideo ? Colors.black : Theme.of(context).scaffoldBackgroundColor, + color: settingsStore.showVideo + ? Colors.black + : Theme.of(context).scaffoldBackgroundColor, child: SafeArea( bottom: false, - left: (settingsStore.landscapeCutout == LandscapeCutoutType.both || - settingsStore.landscapeCutout == LandscapeCutoutType.left) + left: (settingsStore.landscapeCutout == + LandscapeCutoutType.both || + settingsStore.landscapeCutout == + LandscapeCutoutType.left) ? false : true, - right: (settingsStore.landscapeCutout == LandscapeCutoutType.both || - settingsStore.landscapeCutout == LandscapeCutoutType.right) + right: (settingsStore.landscapeCutout == + LandscapeCutoutType.both || + settingsStore.landscapeCutout == + LandscapeCutoutType.right) ? false : true, child: settingsStore.showVideo @@ -285,7 +304,10 @@ class _VideoChatState extends State { return SafeArea( child: Column( children: [ - if (!settingsStore.showVideo) appBar else AspectRatio(aspectRatio: 16 / 9, child: video), + if (!settingsStore.showVideo) + appBar + else + AspectRatio(aspectRatio: 16 / 9, child: video), Expanded(child: chat), ], ), diff --git a/lib/screens/channel/chat/chat.dart b/lib/screens/channel/chat/chat.dart index f302be8a..f3816682 100644 --- a/lib/screens/channel/chat/chat.dart +++ b/lib/screens/channel/chat/chat.dart @@ -34,9 +34,12 @@ class Chat extends StatelessWidget { alignment: AlignmentDirectional.bottomCenter, children: [ MediaQuery( - data: MediaQuery.of(context).copyWith(textScaleFactor: chatStore.settings.messageScale), + data: MediaQuery.of(context).copyWith( + textScaleFactor: chatStore.settings.messageScale), child: DefaultTextStyle( - style: DefaultTextStyle.of(context).style.copyWith(fontSize: chatStore.settings.fontSize), + style: DefaultTextStyle.of(context) + .style + .copyWith(fontSize: chatStore.settings.fontSize), child: Observer( builder: (context) { return ListView.builder( @@ -46,7 +49,8 @@ class Chat extends StatelessWidget { controller: chatStore.scrollController, itemCount: chatStore.renderMessages.length, itemBuilder: (context, index) => ChatMessage( - ircMessage: chatStore.renderMessages.reversed.toList()[index], + ircMessage: chatStore.renderMessages.reversed + .toList()[index], chatStore: chatStore, ), ); @@ -64,9 +68,11 @@ class Chat extends StatelessWidget { child: chatStore.autoScroll ? null : Button( - padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0), + padding: const EdgeInsets.symmetric( + horizontal: 15.0, vertical: 10.0), onPressed: chatStore.resumeScroll, - icon: const Icon(Icons.keyboard_double_arrow_down_rounded), + icon: const Icon( + Icons.keyboard_double_arrow_down_rounded), child: const Text('Resume scroll'), ), ), @@ -76,7 +82,8 @@ class Chat extends StatelessWidget { ), ), ), - if (chatStore.settings.showBottomBar) ChatBottomBar(chatStore: chatStore), + if (chatStore.settings.showBottomBar) + ChatBottomBar(chatStore: chatStore), WillPopScope( onWillPop: Platform.isAndroid ? () async { @@ -94,7 +101,10 @@ class Chat extends StatelessWidget { duration: const Duration(milliseconds: 200), height: chatStore.assetsStore.showEmoteMenu ? MediaQuery.of(context).size.height / - (MediaQuery.of(context).orientation == Orientation.portrait ? 3 : 2) + (MediaQuery.of(context).orientation == + Orientation.portrait + ? 3 + : 2) : 0, child: AnimatedSwitcher( duration: const Duration(milliseconds: 100), @@ -115,7 +125,8 @@ class Chat extends StatelessWidget { ), EmoteMenuPanel( chatStore: chatStore, - twitchEmotes: chatStore.assetsStore.userEmoteSectionToEmotes, + twitchEmotes: chatStore + .assetsStore.userEmoteSectionToEmotes, ), ...[ chatStore.assetsStore.sevenTVEmotes, diff --git a/lib/screens/channel/chat/details/chat_details.dart b/lib/screens/channel/chat/details/chat_details.dart index 42888b63..58cfbccf 100644 --- a/lib/screens/channel/chat/details/chat_details.dart +++ b/lib/screens/channel/chat/details/chat_details.dart @@ -40,13 +40,18 @@ class _ChatDetailsState extends State { mainAxisSize: MainAxisSize.min, children: [ Opacity( - opacity: widget.chatStore.sleepTimer != null && widget.chatStore.sleepTimer!.isActive ? 1.0 : 0.5, + opacity: widget.chatStore.sleepTimer != null && + widget.chatStore.sleepTimer!.isActive + ? 1.0 + : 0.5, child: Row( children: [ const Icon(Icons.timer_rounded), Text( ' ${widget.chatStore.timeRemaining.toString().split('.')[0]}', - style: const TextStyle(fontWeight: FontWeight.w600, fontFeatures: [FontFeature.tabularFigures()]), + style: const TextStyle( + fontWeight: FontWeight.w600, + fontFeatures: [FontFeature.tabularFigures()]), ), const Spacer(), IconButton( @@ -62,9 +67,11 @@ class _ChatDetailsState extends State { DropdownButton( value: widget.chatStore.sleepHours, items: List.generate(24, (index) => index) - .map((e) => DropdownMenuItem(value: e, child: Text(e.toString()))) + .map((e) => DropdownMenuItem( + value: e, child: Text(e.toString()))) .toList(), - onChanged: (int? hours) => widget.chatStore.sleepHours = hours!, + onChanged: (int? hours) => + widget.chatStore.sleepHours = hours!, menuMaxHeight: 200, ), const SizedBox(width: 10.0), @@ -76,9 +83,11 @@ class _ChatDetailsState extends State { DropdownButton( value: widget.chatStore.sleepMinutes, items: List.generate(60, (index) => index) - .map((e) => DropdownMenuItem(value: e, child: Text(e.toString()))) + .map((e) => DropdownMenuItem( + value: e, child: Text(e.toString()))) .toList(), - onChanged: (int? minutes) => widget.chatStore.sleepMinutes = minutes!, + onChanged: (int? minutes) => + widget.chatStore.sleepMinutes = minutes!, menuMaxHeight: 200, ), const SizedBox(width: 10.0), @@ -91,10 +100,12 @@ class _ChatDetailsState extends State { actions: [ Observer( builder: (context) => Button( - onPressed: widget.chatStore.sleepHours == 0 && widget.chatStore.sleepMinutes == 0 + onPressed: widget.chatStore.sleepHours == 0 && + widget.chatStore.sleepMinutes == 0 ? null : () => widget.chatStore.updateSleepTimer( - onTimerFinished: () => navigatorKey.currentState?.popUntil((route) => route.isFirst), + onTimerFinished: () => navigatorKey.currentState + ?.popUntil((route) => route.isFirst), ), child: const Text('Set timer'), ), @@ -201,7 +212,8 @@ class _ChatDetailsState extends State { onTap: () => Navigator.push( context, MaterialPageRoute( - builder: (context) => Settings(settingsStore: widget.chatStore.settings), + builder: (context) => + Settings(settingsStore: widget.chatStore.settings), ), ), ), @@ -215,7 +227,8 @@ class _ChatDetailsState extends State { children: children, ), ) - : Column(crossAxisAlignment: CrossAxisAlignment.start, children: children), + : Column( + crossAxisAlignment: CrossAxisAlignment.start, children: children), ); } } diff --git a/lib/screens/channel/chat/details/chat_details_store.dart b/lib/screens/channel/chat/details/chat_details_store.dart index cc0a81e8..f37716d3 100644 --- a/lib/screens/channel/chat/details/chat_details_store.dart +++ b/lib/screens/channel/chat/details/chat_details_store.dart @@ -67,7 +67,8 @@ abstract class ChatDetailsStoreBase with Store { ChatDetailsStoreBase({required this.twitchApi, required this.channelName}) { scrollController.addListener(() { - if (scrollController.position.atEdge || scrollController.position.outOfRange) { + if (scrollController.position.atEdge || + scrollController.position.outOfRange) { showJumpButton = false; } else { showJumpButton = true; diff --git a/lib/screens/channel/chat/details/chat_modes.dart b/lib/screens/channel/chat/details/chat_modes.dart index 150fd354..573c192f 100644 --- a/lib/screens/channel/chat/details/chat_modes.dart +++ b/lib/screens/channel/chat/details/chat_modes.dart @@ -17,9 +17,12 @@ class ChatModes extends StatelessWidget { preferBelow: false, triggerMode: TooltipTriggerMode.tap, showDuration: const Duration(seconds: 3), - message: 'Emote-only mode ${roomState.emoteOnly != '0' ? 'on' : 'off'}', + message: + 'Emote-only mode ${roomState.emoteOnly != '0' ? 'on' : 'off'}', child: Icon( - roomState.emoteOnly != '0' ? Icons.emoji_emotions_rounded : Icons.emoji_emotions_outlined, + roomState.emoteOnly != '0' + ? Icons.emoji_emotions_rounded + : Icons.emoji_emotions_outlined, color: roomState.emoteOnly != '0' ? Colors.yellow : Colors.grey, ), ), @@ -33,7 +36,9 @@ class ChatModes extends StatelessWidget { ? 'Followers-only mode on' : 'Followers-only mode on (${roomState.followersOnly} ${pluralize('minute', roomState.followersOnly)})', child: Icon( - roomState.followersOnly != '-1' ? Icons.favorite_rounded : Icons.favorite_outline_rounded, + roomState.followersOnly != '-1' + ? Icons.favorite_rounded + : Icons.favorite_outline_rounded, color: roomState.followersOnly != '-1' ? Colors.red : Colors.grey, ), ), @@ -41,7 +46,8 @@ class ChatModes extends StatelessWidget { preferBelow: false, triggerMode: TooltipTriggerMode.tap, showDuration: const Duration(seconds: 3), - message: 'R9K (unique-chat) mode ${roomState.r9k != '0' ? 'on' : 'off'}', + message: + 'R9K (unique-chat) mode ${roomState.r9k != '0' ? 'on' : 'off'}', child: Text( 'R9K', style: TextStyle( @@ -57,7 +63,9 @@ class ChatModes extends StatelessWidget { message: 'Slow mode ${roomState.slowMode != '0' ? 'on (${roomState.slowMode} ${pluralize('second', roomState.slowMode)})' : 'off'}', child: Icon( - roomState.slowMode != '0' ? Icons.hourglass_top_rounded : Icons.hourglass_empty_rounded, + roomState.slowMode != '0' + ? Icons.hourglass_top_rounded + : Icons.hourglass_empty_rounded, color: roomState.slowMode != '0' ? Colors.blue : Colors.grey, ), ), @@ -67,7 +75,9 @@ class ChatModes extends StatelessWidget { showDuration: const Duration(seconds: 3), message: 'Subs-only mode ${roomState.subMode != '0' ? 'on' : 'off'}', child: Icon( - roomState.subMode != '0' ? Icons.monetization_on_rounded : Icons.monetization_on_outlined, + roomState.subMode != '0' + ? Icons.monetization_on_rounded + : Icons.monetization_on_outlined, color: roomState.subMode != '0' ? Colors.green : Colors.grey, ), ), diff --git a/lib/screens/channel/chat/details/chat_users_list.dart b/lib/screens/channel/chat/details/chat_users_list.dart index d78737de..9ac3929e 100644 --- a/lib/screens/channel/chat/details/chat_users_list.dart +++ b/lib/screens/channel/chat/details/chat_users_list.dart @@ -81,13 +81,19 @@ class _ChattersListState extends State { contentPadding: EdgeInsets.zero, prefixIcon: const Icon(Icons.filter_list_rounded), hintText: 'Filter chatters', - suffixIcon: widget.chatDetailsStore.textFieldFocusNode.hasFocus || + suffixIcon: widget.chatDetailsStore.textFieldFocusNode + .hasFocus || widget.chatDetailsStore.filterText.isNotEmpty ? IconButton( - tooltip: widget.chatDetailsStore.filterText.isEmpty ? 'Cancel' : 'Clear', + tooltip: + widget.chatDetailsStore.filterText.isEmpty + ? 'Cancel' + : 'Clear', onPressed: () { - if (widget.chatDetailsStore.filterText.isEmpty) { - widget.chatDetailsStore.textFieldFocusNode.unfocus(); + if (widget + .chatDetailsStore.filterText.isEmpty) { + widget.chatDetailsStore.textFieldFocusNode + .unfocus(); } widget.chatDetailsStore.textController.clear(); }, @@ -117,7 +123,8 @@ class _ChattersListState extends State { slivers: [ if (widget.chatDetailsStore.filterText.isEmpty) SliverPadding( - padding: const EdgeInsets.only(left: 10.0, right: 10.0, top: 20.0), + padding: const EdgeInsets.only( + left: 10.0, right: 10.0, top: 20.0), sliver: SliverToBoxAdapter( child: Text( '${NumberFormat().format(widget.chatDetailsStore.chatUsers?.chatterCount)} in chat', @@ -128,30 +135,39 @@ class _ChattersListState extends State { ), ), ), - if (widget.chatDetailsStore.chatUsers?.chatterCount == 0) + if (widget + .chatDetailsStore.chatUsers?.chatterCount == + 0) const SliverFillRemaining( hasScrollBody: false, child: Center( - child: AlertMessage(message: 'No chatters found'), + child: AlertMessage( + message: 'No chatters found'), ), ) - else if (widget.chatDetailsStore.filteredUsers.expand((element) => element).isEmpty) + else if (widget.chatDetailsStore.filteredUsers + .expand((element) => element) + .isEmpty) const SliverFillRemaining( hasScrollBody: false, child: Center( - child: AlertMessage(message: 'No matching chatters'), + child: AlertMessage( + message: 'No matching chatters'), ), ) else - ...widget.chatDetailsStore.filteredUsers.expandIndexed( + ...widget.chatDetailsStore.filteredUsers + .expandIndexed( (index, users) => [ if (users.isNotEmpty) ...[ SliverPadding( - padding: const EdgeInsets.only(left: 10.0, right: 10.0, top: 20.0), + padding: const EdgeInsets.only( + left: 10.0, right: 10.0, top: 20.0), sliver: SliverToBoxAdapter( child: SectionHeader( headers[index], - padding: const EdgeInsets.symmetric(vertical: 5.0), + padding: const EdgeInsets.symmetric( + vertical: 5.0), ), ), ), @@ -159,27 +175,36 @@ class _ChattersListState extends State { delegate: SliverChildBuilderDelegate( (context, index) => InkWell( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0), + padding: const EdgeInsets.symmetric( + horizontal: 10.0, + vertical: 5.0), child: Text(users[index]), ), onLongPress: () async { HapticFeedback.lightImpact(); - final userInfo = await context.read().getUser( - headers: context.read().headersTwitch, - userLogin: users[index]); + final userInfo = await context + .read() + .getUser( + headers: context + .read() + .headersTwitch, + userLogin: users[index]); if (!mounted) return; showModalBottomSheet( - backgroundColor: Colors.transparent, + backgroundColor: + Colors.transparent, isScrollControlled: true, context: context, - builder: (context) => ChatUserModal( + builder: (context) => + ChatUserModal( chatStore: widget.chatStore, username: userInfo.login, userId: userInfo.id, - displayName: userInfo.displayName, + displayName: + userInfo.displayName, ), ); }, @@ -200,7 +225,9 @@ class _ChattersListState extends State { switchInCurve: Curves.easeOut, switchOutCurve: Curves.easeIn, child: widget.chatDetailsStore.showJumpButton - ? ScrollToTopButton(scrollController: widget.chatDetailsStore.scrollController) + ? ScrollToTopButton( + scrollController: + widget.chatDetailsStore.scrollController) : null, ), ), 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 50f19864..f6a18f93 100644 --- a/lib/screens/channel/chat/emote_menu/emote_menu_panel.dart +++ b/lib/screens/channel/chat/emote_menu/emote_menu_panel.dart @@ -50,8 +50,11 @@ class EmoteMenuPanel extends StatelessWidget { ); } else { return FrostyPageView( - headers: twitchEmotes!.keys.map((header) => header.split(' ')[0]).toList(), - children: twitchEmotes!.entries.map((e) => EmoteMenuSection(chatStore: chatStore, emotes: e.value)).toList(), + headers: + twitchEmotes!.keys.map((header) => header.split(' ')[0]).toList(), + children: twitchEmotes!.entries + .map((e) => EmoteMenuSection(chatStore: chatStore, emotes: e.value)) + .toList(), ); } } 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 1fc3bfb1..2bb49284 100644 --- a/lib/screens/channel/chat/emote_menu/emote_menu_section.dart +++ b/lib/screens/channel/chat/emote_menu/emote_menu_section.dart @@ -20,11 +20,12 @@ class EmoteMenuSection extends StatelessWidget { Widget build(BuildContext context) { return GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: MediaQuery.of(context).orientation == Orientation.portrait - ? 8 - : context.read().showVideo - ? 6 - : 16, + crossAxisCount: + MediaQuery.of(context).orientation == Orientation.portrait + ? 8 + : context.read().showVideo + ? 6 + : 16, ), itemBuilder: (context, index) => InkWell( onTap: () => chatStore.addEmote(emotes[index]), 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 92311ba9..16b13c9f 100644 --- a/lib/screens/channel/chat/emote_menu/recent_emotes_panel.dart +++ b/lib/screens/channel/chat/emote_menu/recent_emotes_panel.dart @@ -26,11 +26,12 @@ class RecentEmotesPanel extends StatelessWidget { else SliverGrid( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: MediaQuery.of(context).orientation == Orientation.portrait - ? 8 - : context.read().showVideo - ? 6 - : 16, + crossAxisCount: + MediaQuery.of(context).orientation == Orientation.portrait + ? 8 + : context.read().showVideo + ? 6 + : 16, ), delegate: SliverChildBuilderDelegate( (context, index) { @@ -39,11 +40,14 @@ class RecentEmotesPanel extends StatelessWidget { ...chatStore.assetsStore.emoteToObject.values, ...chatStore.assetsStore.userEmoteToObject.values ]; - final matchingEmotes = validEmotes - .where((existingEmote) => existingEmote.name == emote.name && existingEmote.type == emote.type); + final matchingEmotes = validEmotes.where((existingEmote) => + existingEmote.name == emote.name && + existingEmote.type == emote.type); return InkWell( - onTap: matchingEmotes.isNotEmpty ? () => chatStore.addEmote(emote) : null, + onTap: matchingEmotes.isNotEmpty + ? () => chatStore.addEmote(emote) + : null, child: Tooltip( message: emote.name, preferBelow: false, @@ -51,9 +55,15 @@ class RecentEmotesPanel extends StatelessWidget { padding: const EdgeInsets.all(10.0), child: Center( child: FrostyCachedNetworkImage( - imageUrl: matchingEmotes.isNotEmpty ? matchingEmotes.first.url : emote.url, - color: matchingEmotes.isNotEmpty ? null : const Color.fromRGBO(255, 255, 255, 0.5), - colorBlendMode: matchingEmotes.isNotEmpty ? null : BlendMode.modulate, + imageUrl: matchingEmotes.isNotEmpty + ? matchingEmotes.first.url + : emote.url, + color: matchingEmotes.isNotEmpty + ? null + : const Color.fromRGBO(255, 255, 255, 0.5), + colorBlendMode: matchingEmotes.isNotEmpty + ? null + : BlendMode.modulate, height: emote.height?.toDouble() ?? defaultEmoteSize, width: emote.width?.toDouble(), ), diff --git a/lib/screens/channel/chat/stores/chat_assets_store.dart b/lib/screens/channel/chat/stores/chat_assets_store.dart index 5bf0f2e5..dec1329b 100644 --- a/lib/screens/channel/chat/stores/chat_assets_store.dart +++ b/lib/screens/channel/chat/stores/chat_assets_store.dart @@ -23,13 +23,16 @@ abstract class ChatAssetsStoreBase with Store { RoomFFZ? ffzRoomInfo; @computed - List get bttvEmotes => _emoteToObject.values.where((emote) => isBTTV(emote)).toList(); + List get bttvEmotes => + _emoteToObject.values.where((emote) => isBTTV(emote)).toList(); @computed - List get ffzEmotes => _emoteToObject.values.where((emote) => isFFZ(emote)).toList(); + List get ffzEmotes => + _emoteToObject.values.where((emote) => isFFZ(emote)).toList(); @computed - List get sevenTVEmotes => _emoteToObject.values.where((emote) => is7TV(emote)).toList(); + List get sevenTVEmotes => + _emoteToObject.values.where((emote) => is7TV(emote)).toList(); /// The map of badges ids to their object representation. final twitchBadgesToObject = ObservableMap(); @@ -74,11 +77,13 @@ abstract class ChatAssetsStoreBase with Store { } bool isFFZ(Emote emote) { - return emote.type == EmoteType.ffzChannel || emote.type == EmoteType.ffzGlobal; + return emote.type == EmoteType.ffzChannel || + emote.type == EmoteType.ffzGlobal; } bool is7TV(Emote emote) { - return emote.type == EmoteType.sevenTVChannel || emote.type == EmoteType.sevenTVGlobal; + return emote.type == EmoteType.sevenTVChannel || + emote.type == EmoteType.sevenTVGlobal; } late final ReactionDisposer _disposeReaction; @@ -104,7 +109,8 @@ abstract class ChatAssetsStoreBase with Store { _disposeReaction = autorun((_) { if (_recentEmotes.length > 48) _recentEmotes.removeLast(); - prefs.setStringList('recent_emotes', _recentEmotes.map((emote) => jsonEncode(emote)).toList()); + prefs.setStringList('recent_emotes', + _recentEmotes.map((emote) => jsonEncode(emote)).toList()); }); } @@ -145,16 +151,17 @@ abstract class ChatAssetsStoreBase with Store { bttvApi.getEmotesGlobal().catchError(onError), bttvApi.getEmotesChannel(id: channelId).catchError(onError), twitchApi.getEmotesGlobal(headers: headers).catchError(onError), - twitchApi.getEmotesChannel(id: channelId, headers: headers).catchError(onError), + twitchApi + .getEmotesChannel(id: channelId, headers: headers) + .catchError(onError), sevenTVApi.getEmotesGlobal().catchError(onError), sevenTVApi.getEmotesChannel(id: channelId).catchError(onError), ffzApi.getRoomInfo(id: channelId).then((ffzRoom) { ffzRoomInfo = ffzRoom.item1; return ffzRoom.item2; }).catchError(onError), - ]) - .then((assets) => assets.expand((list) => list)) - .then((emotes) => _emoteToObject = {for (final emote in emotes) emote.name: emote}); + ]).then((assets) => assets.expand((list) => list)).then((emotes) => + _emoteToObject = {for (final emote in emotes) emote.name: emote}); @action Future badgesFuture({ @@ -165,14 +172,25 @@ abstract class ChatAssetsStoreBase with Store { Future.wait([ // Get global badges first, then channel badges to avoid badge conflicts. // We want the channel badges to override the global badges. - twitchApi.getBadgesGlobal(headers: headers).then((badges) => twitchBadgesToObject.addAll(badges)).then((_) => - twitchApi + twitchApi + .getBadgesGlobal(headers: headers) + .then((badges) => twitchBadgesToObject.addAll(badges)) + .then((_) => twitchApi .getBadgesChannel(id: channelId, headers: headers) .then((badges) => twitchBadgesToObject.addAll(badges)) .catchError(onError)), - ffzApi.getBadges().then((badges) => _userToFFZBadges = badges).catchError(onError), - sevenTVApi.getBadges().then((badges) => _userTo7TVBadges = badges).catchError(onError), - bttvApi.getBadges().then((badges) => _userToBTTVBadges = badges).catchError(onError), + ffzApi + .getBadges() + .then((badges) => _userToFFZBadges = badges) + .catchError(onError), + sevenTVApi + .getBadges() + .then((badges) => _userTo7TVBadges = badges) + .catchError(onError), + bttvApi + .getBadges() + .then((badges) => _userToBTTVBadges = badges) + .catchError(onError), ]); @action @@ -181,13 +199,15 @@ abstract class ChatAssetsStoreBase with Store { required Map headers, required Function onError, }) async { - final userEmotes = await Future.wait( - emoteSets.map((setId) => twitchApi.getEmotesSets(setId: setId, headers: headers).catchError(onError))); + final userEmotes = await Future.wait(emoteSets.map((setId) => twitchApi + .getEmotesSets(setId: setId, headers: headers) + .catchError(onError))); for (final emoteSet in userEmotes) { if (emoteSet.isNotEmpty) { if (emoteSet.first.type == EmoteType.twitchSub) { - final owner = await twitchApi.getUser(id: emoteSet.first.ownerId, headers: headers); + final owner = await twitchApi.getUser( + id: emoteSet.first.ownerId, headers: headers); _userEmoteSectionToEmotes.update( owner.displayName, (existingEmoteSet) => [...existingEmoteSet, ...emoteSet], diff --git a/lib/screens/channel/chat/stores/chat_store.dart b/lib/screens/channel/chat/stores/chat_store.dart index 4384ba59..926f4b2d 100644 --- a/lib/screens/channel/chat/stores/chat_store.dart +++ b/lib/screens/channel/chat/stores/chat_store.dart @@ -113,7 +113,9 @@ abstract class ChatStoreBase with Store { List get renderMessages { // If autoscroll is disabled, render ALL messages in chat. // The second condition is to prevent an out of index error with sublist. - if (!_autoScroll || _messages.length < _renderMessageLimit) return _messages; + if (!_autoScroll || _messages.length < _renderMessageLimit) { + return _messages; + } // When autoscroll is enabled, only show the first [_renderMessageLimit] messages. // This will improve performance by only rendering a limited amount of messages @@ -163,12 +165,14 @@ abstract class ChatStoreBase with Store { ); // Create a timer that will add messages from the buffer every 200 milliseconds. - _messageBufferTimer = Timer.periodic(const Duration(milliseconds: 200), (timer) => addMessages()); + _messageBufferTimer = Timer.periodic( + const Duration(milliseconds: 200), (timer) => addMessages()); assetsStore.init(); chatDetailsStore.updateChatters(); - _messageBuffer.add(IRCMessage.createNotice(message: 'Connecting to chat...')); + _messageBuffer + .add(IRCMessage.createNotice(message: 'Connecting to chat...')); if (settings.chatDelay > 0) { _messageBuffer.add(IRCMessage.createNotice( @@ -205,11 +209,14 @@ 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(() => _showEmoteAutocomplete = - !_showMentionAutocomplete && textFieldFocusNode.hasFocus && textController.text.split(' ').last.isNotEmpty); + !_showMentionAutocomplete && + textFieldFocusNode.hasFocus && + textController.text.split(' ').last.isNotEmpty); textController.addListener(() { _showSendButton = textController.text.isNotEmpty; - _showMentionAutocomplete = textFieldFocusNode.hasFocus && textController.text.split(' ').last.startsWith('@'); + _showMentionAutocomplete = textFieldFocusNode.hasFocus && + textController.text.split(' ').last.startsWith('@'); }); } @@ -224,12 +231,16 @@ abstract class ChatStoreBase with Store { for (final message in data.trimRight().split('\r\n')) { // debugPrint('$message\n'); if (message.startsWith('@')) { - final parsedIRCMessage = IRCMessage.fromString(message, userLogin: auth.user.details?.login); + final parsedIRCMessage = + IRCMessage.fromString(message, userLogin: auth.user.details?.login); // Filter messages from any blocked users if not a moderator or not the channel owner. if (!_userState.mod && channelName != auth.user.details?.login && - auth.user.blockedUsers.where((blockedUser) => blockedUser.userLogin == parsedIRCMessage.user).isNotEmpty) { + auth.user.blockedUsers + .where((blockedUser) => + blockedUser.userLogin == parsedIRCMessage.user) + .isNotEmpty) { continue; } @@ -254,7 +265,8 @@ abstract class ChatStoreBase with Store { ); break; case Command.roomState: - chatDetailsStore.roomState = chatDetailsStore.roomState.fromIRCMessage(parsedIRCMessage); + chatDetailsStore.roomState = + chatDetailsStore.roomState.fromIRCMessage(parsedIRCMessage); continue; case Command.userState: _userState = _userState.fromIRCMessage(parsedIRCMessage); @@ -292,7 +304,9 @@ abstract class ChatStoreBase with Store { } // If the message limit is reached, remove the oldest messages. - if (_messages.length >= _messageLimit) _messages = _messages.sublist(_messagesToRemove).asObservable(); + if (_messages.length >= _messageLimit) { + _messages = _messages.sublist(_messagesToRemove).asObservable(); + } } else if (message == 'PING :tmi.twitch.tv') { _channel?.sink.add('PONG :tmi.twitch.tv'); return; @@ -342,11 +356,13 @@ abstract class ChatStoreBase with Store { @action void connectToChat() { _channel?.sink.close(1001); - _channel = WebSocketChannel.connect(Uri.parse('wss://irc-ws.chat.twitch.tv:443')); + _channel = + WebSocketChannel.connect(Uri.parse('wss://irc-ws.chat.twitch.tv:443')); // Listen for new messages and forward them to the handler. _channelListener = _channel?.stream.listen( - (data) => Future.delayed(Duration(seconds: settings.chatDelay.toInt()), () => _handleIRCData(data.toString())), + (data) => Future.delayed(Duration(seconds: settings.chatDelay.toInt()), + () => _handleIRCData(data.toString())), onError: (error) => debugPrint('Chat error: ${error.toString()}'), onDone: () async { if (_channel == null) return; @@ -365,7 +381,8 @@ abstract class ChatStoreBase with Store { // Increment the retry count and attempt the reconnect. _retries++; - _messageBuffer.add(IRCMessage.createNotice(message: 'Reconnecting to chat (attempt $_retries)...')); + _messageBuffer.add(IRCMessage.createNotice( + message: 'Reconnecting to chat (attempt $_retries)...')); _channelListener?.cancel(); connectToChat(); }, @@ -408,7 +425,8 @@ abstract class ChatStoreBase with Store { if (message.isEmpty) return; if (_channel == null || _channel?.closeCode != null) { - _messageBuffer.add(IRCMessage.createNotice(message: 'Failed to send message: disconnected from chat.')); + _messageBuffer.add(IRCMessage.createNotice( + message: 'Failed to send message: disconnected from chat.')); } else { // Send the message to the IRC chat room. _channel?.sink.add('PRIVMSG #$channelName :$message'); @@ -417,14 +435,17 @@ abstract class ChatStoreBase with Store { var userStateString = _userState.raw; if (userStateString != null) { if (message.length > 3 && message.substring(0, 3) == '/me') { - userStateString += ' :\x01ACTION ${message.replaceRange(0, 3, '').trim()}\x01'; + userStateString += + ' :\x01ACTION ${message.replaceRange(0, 3, '').trim()}\x01'; } else { userStateString += ' :${message.trim()}'; } final userChatMessage = IRCMessage.fromString(userStateString); userChatMessage.localEmotes?.addAll(assetsStore.userEmoteToObject); - if (auth.isLoggedIn && auth.user.details != null) userChatMessage.tags['user-id'] = auth.user.details!.id; + if (auth.isLoggedIn && auth.user.details != null) { + userChatMessage.tags['user-id'] = auth.user.details!.id; + } toSend = userChatMessage; } @@ -436,7 +457,9 @@ abstract class ChatStoreBase with Store { void addEmote(Emote emote, {bool autocompleteMode = false}) { if (textController.text.isEmpty || textController.text.endsWith(' ')) { textController.text += '${emote.name} '; - } else if (autocompleteMode && _showEmoteAutocomplete && textController.text.endsWith('')) { + } else if (autocompleteMode && + _showEmoteAutocomplete && + textController.text.endsWith('')) { final split = textController.text.split(' ') ..removeLast() ..add('${emote.name} '); @@ -447,10 +470,12 @@ abstract class ChatStoreBase with Store { } assetsStore.recentEmotes - ..removeWhere((recentEmote) => recentEmote.name == emote.name && recentEmote.type == emote.type) + ..removeWhere((recentEmote) => + recentEmote.name == emote.name && recentEmote.type == emote.type) ..insert(0, emote); - textController.selection = TextSelection.fromPosition(TextPosition(offset: textController.text.length)); + textController.selection = TextSelection.fromPosition( + TextPosition(offset: textController.text.length)); } /// Cancels the previous notification/timer and creates a new one with the provided [notificationMessage]. @@ -468,7 +493,8 @@ abstract class ChatStoreBase with Store { // Set the new notification message and create a new timer that will dismiss it after 2 seconds. _notification = notificationMessage; - _notificationTimer = Timer(const Duration(seconds: 2), () => _notification = null); + _notificationTimer = + Timer(const Duration(seconds: 2), () => _notification = null); } /// Updates the sleep timer with [sleepHours] and [sleepMinutes]. diff --git a/lib/screens/channel/chat/widgets/chat_bottom_bar.dart b/lib/screens/channel/chat/widgets/chat_bottom_bar.dart index eb0cff6d..c1de24b1 100644 --- a/lib/screens/channel/chat/widgets/chat_bottom_bar.dart +++ b/lib/screens/channel/chat/widgets/chat_bottom_bar.dart @@ -17,11 +17,16 @@ class ChatBottomBar extends StatelessWidget { message: 'Emote menu', preferBelow: false, child: IconButton( - color: chatStore.assetsStore.showEmoteMenu ? Theme.of(context).colorScheme.secondary : null, - icon: Icon(chatStore.assetsStore.showEmoteMenu ? Icons.emoji_emotions_rounded : Icons.emoji_emotions_outlined), + color: chatStore.assetsStore.showEmoteMenu + ? Theme.of(context).colorScheme.secondary + : null, + icon: Icon(chatStore.assetsStore.showEmoteMenu + ? Icons.emoji_emotions_rounded + : Icons.emoji_emotions_outlined), onPressed: () { FocusScope.of(context).unfocus(); - chatStore.assetsStore.showEmoteMenu = !chatStore.assetsStore.showEmoteMenu; + chatStore.assetsStore.showEmoteMenu = + !chatStore.assetsStore.showEmoteMenu; }, ), ); @@ -34,18 +39,23 @@ class ChatBottomBar extends StatelessWidget { ...chatStore.assetsStore.ffzEmotes, ...chatStore.assetsStore.sevenTVEmotes ] - .where((emote) => - emote.name.toLowerCase().contains(chatStore.textController.text.split(' ').last.toLowerCase())) + .where((emote) => emote.name.toLowerCase().contains( + chatStore.textController.text.split(' ').last.toLowerCase())) .toList(); final matchingChatters = chatStore.chatDetailsStore.allChatters - .where((chatter) => - chatter.contains(chatStore.textController.text.split(' ').last.replaceFirst('@', '').toLowerCase())) + .where((chatter) => chatter.contains(chatStore.textController.text + .split(' ') + .last + .replaceFirst('@', '') + .toLowerCase())) .toList(); return Column( children: [ - if (chatStore.settings.autocomplete && chatStore.showEmoteAutocomplete && matchingEmotes.isNotEmpty) ...[ + if (chatStore.settings.autocomplete && + chatStore.showEmoteAutocomplete && + matchingEmotes.isNotEmpty) ...[ const Divider( height: 1.0, thickness: 1.0, @@ -57,7 +67,8 @@ class ChatBottomBar extends StatelessWidget { itemCount: matchingEmotes.length, scrollDirection: Axis.horizontal, itemBuilder: (context, index) => InkWell( - onTap: () => chatStore.addEmote(matchingEmotes[index], autocompleteMode: true), + onTap: () => chatStore.addEmote(matchingEmotes[index], + autocompleteMode: true), child: Tooltip( message: matchingEmotes[index].name, preferBelow: false, @@ -67,7 +78,8 @@ class ChatBottomBar extends StatelessWidget { child: FrostyCachedNetworkImage( imageUrl: matchingEmotes[index].url, useFade: false, - height: matchingEmotes[index].height?.toDouble() ?? defaultEmoteSize, + height: matchingEmotes[index].height?.toDouble() ?? + defaultEmoteSize, width: matchingEmotes[index].width?.toDouble(), ), ), @@ -99,7 +111,8 @@ class ChatBottomBar extends StatelessWidget { chatStore.textController.text = split.join(' '); chatStore.textController.selection = - TextSelection.fromPosition(TextPosition(offset: chatStore.textController.text.length)); + TextSelection.fromPosition(TextPosition( + offset: chatStore.textController.text.length)); }, child: Text(matchingChatters[index]), ), @@ -113,7 +126,8 @@ class ChatBottomBar extends StatelessWidget { if (!chatStore.expandChat && chatStore.settings.chatWidth < 0.3 && chatStore.settings.showVideo && - MediaQuery.of(context).orientation == Orientation.landscape) + MediaQuery.of(context).orientation == + Orientation.landscape) IconButton( tooltip: 'Enter a message', onPressed: () { @@ -132,8 +146,12 @@ class ChatBottomBar extends StatelessWidget { enabled: chatStore.auth.isLoggedIn ? true : false, decoration: InputDecoration( contentPadding: const EdgeInsets.only(left: 15.0), - prefixIcon: chatStore.settings.emoteMenuButtonOnLeft ? emoteMenuButton : null, - suffixIcon: chatStore.settings.emoteMenuButtonOnLeft ? null : emoteMenuButton, + prefixIcon: chatStore.settings.emoteMenuButtonOnLeft + ? emoteMenuButton + : null, + suffixIcon: chatStore.settings.emoteMenuButtonOnLeft + ? null + : emoteMenuButton, hintMaxLines: 1, hintText: chatStore.auth.isLoggedIn ? 'Send a message ${chatStore.settings.chatDelay == 0 ? '' : '(${chatStore.settings.chatDelay.toInt()}s delay)'}' @@ -146,12 +164,15 @@ class ChatBottomBar extends StatelessWidget { if (chatStore.showSendButton && (chatStore.settings.chatWidth >= 0.3 || chatStore.expandChat || - MediaQuery.of(context).orientation == Orientation.portrait)) + MediaQuery.of(context).orientation == + Orientation.portrait)) IconButton( tooltip: 'Send', icon: const Icon(Icons.send_rounded), - onPressed: - chatStore.auth.isLoggedIn ? () => chatStore.sendMessage(chatStore.textController.text) : null, + onPressed: chatStore.auth.isLoggedIn + ? () => chatStore + .sendMessage(chatStore.textController.text) + : null, ) else IconButton( diff --git a/lib/screens/channel/chat/widgets/chat_message.dart b/lib/screens/channel/chat/widgets/chat_message.dart index 2c544577..0ee4b72c 100644 --- a/lib/screens/channel/chat/widgets/chat_message.dart +++ b/lib/screens/channel/chat/widgets/chat_message.dart @@ -25,7 +25,8 @@ class ChatMessage extends StatelessWidget { if (isModal) return; // Ignore if long-pressing own username. - if (ircMessage.user == null || ircMessage.user == chatStore.auth.user.details?.login) return; + if (ircMessage.user == null || + ircMessage.user == chatStore.auth.user.details?.login) return; showModalBottomSheet( backgroundColor: Colors.transparent, @@ -60,7 +61,8 @@ class ChatMessage extends StatelessWidget { case Command.userState: // If user is being mentioned in the message, highlight it red. if (ircMessage.mention == true) color = Colors.red.withOpacity(0.2); - if (chatStore.settings.highlightFirstTimeChatter && ircMessage.tags['first-msg'] == '1') { + if (chatStore.settings.highlightFirstTimeChatter && + ircMessage.tags['first-msg'] == '1') { color = Colors.green.withOpacity(0.2); } @@ -73,7 +75,8 @@ class ChatMessage extends StatelessWidget { emoteScale: chatStore.settings.emoteScale, badgeScale: chatStore.settings.badgeScale, useReadableColors: chatStore.settings.useReadableColors, - isLightTheme: Theme.of(context).brightness == Brightness.light, + isLightTheme: + Theme.of(context).brightness == Brightness.light, launchExternal: chatStore.settings.launchUrlExternal, timestamp: chatStore.settings.timestampType, ), @@ -85,14 +88,17 @@ class ChatMessage extends StatelessWidget { final replyBody = ircMessage.tags['reply-parent-msg-body']; if ((replyUser != null && replyBody != null) || - (chatStore.settings.highlightFirstTimeChatter && ircMessage.tags['first-msg'] == '1')) { + (chatStore.settings.highlightFirstTimeChatter && + ircMessage.tags['first-msg'] == '1')) { renderMessage = Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( - replyUser != null && replyBody != null ? Icons.reply_rounded : Icons.new_releases_outlined, + replyUser != null && replyBody != null + ? Icons.reply_rounded + : Icons.new_releases_outlined, size: defaultBadgeSize * chatStore.settings.badgeScale, color: defaultTextStyle.color?.withOpacity(0.5), textDirection: TextDirection.rtl, @@ -110,7 +116,8 @@ class ChatMessage extends StatelessWidget { maxLines: 1, style: TextStyle( overflow: TextOverflow.ellipsis, - color: defaultTextStyle.color?.withOpacity(0.5), + color: defaultTextStyle.color + ?.withOpacity(0.5), ), ), ) @@ -118,7 +125,8 @@ class ChatMessage extends StatelessWidget { 'First time chatting', style: TextStyle( fontWeight: FontWeight.w600, - color: defaultTextStyle.color?.withOpacity(0.5), + color: + defaultTextStyle.color?.withOpacity(0.5), ), ), ), @@ -170,7 +178,8 @@ class ChatMessage extends StatelessWidget { badgeScale: chatStore.settings.badgeScale, showMessage: chatStore.settings.showDeletedMessages, useReadableColors: chatStore.settings.useReadableColors, - isLightTheme: Theme.of(context).brightness == Brightness.light, + isLightTheme: + Theme.of(context).brightness == Brightness.light, launchExternal: chatStore.settings.launchUrlExternal, timestamp: chatStore.settings.timestampType, ), @@ -196,14 +205,17 @@ class ChatMessage extends StatelessWidget { if (ircMessage.tags.containsKey('system-msg')) Text( ircMessage.tags['system-msg']!, - style: TextStyle(fontWeight: FontWeight.w600, color: defaultTextStyle.color?.withOpacity(0.5)), + style: TextStyle( + fontWeight: FontWeight.w600, + color: defaultTextStyle.color?.withOpacity(0.5)), ), if (ircMessage.tags['msg-id'] == 'announcement') Row( children: [ Icon( Icons.announcement_outlined, - size: defaultBadgeSize * chatStore.settings.badgeScale, + size: + defaultBadgeSize * chatStore.settings.badgeScale, ), const SizedBox(width: 5.0), const Text( @@ -222,8 +234,10 @@ class ChatMessage extends StatelessWidget { assetsStore: chatStore.assetsStore, emoteScale: chatStore.settings.emoteScale, badgeScale: chatStore.settings.badgeScale, - useReadableColors: chatStore.settings.useReadableColors, - isLightTheme: Theme.of(context).brightness == Brightness.light, + useReadableColors: + chatStore.settings.useReadableColors, + isLightTheme: + Theme.of(context).brightness == Brightness.light, launchExternal: chatStore.settings.launchUrlExternal, timestamp: chatStore.settings.timestampType, ), @@ -240,7 +254,9 @@ class ChatMessage extends StatelessWidget { } final paddedMessage = Padding( - padding: EdgeInsets.symmetric(vertical: chatStore.settings.messageSpacing / 2, horizontal: 10.0), + padding: EdgeInsets.symmetric( + vertical: chatStore.settings.messageSpacing / 2, + horizontal: 10.0), child: renderMessage, ); @@ -259,12 +275,16 @@ class ChatMessage extends StatelessWidget { : paddedMessage; // Color the message if the color has been set. - final coloredMessage = color == null ? dividedMessage : ColoredBox(color: color, child: dividedMessage); + final coloredMessage = color == null + ? dividedMessage + : ColoredBox(color: color, child: dividedMessage); return InkWell( onTap: () { FocusScope.of(context).unfocus(); - if (chatStore.assetsStore.showEmoteMenu) chatStore.assetsStore.showEmoteMenu = false; + if (chatStore.assetsStore.showEmoteMenu) { + chatStore.assetsStore.showEmoteMenu = false; + } }, onLongPress: onLongPressMessage, child: coloredMessage, diff --git a/lib/screens/channel/chat/widgets/chat_user_modal.dart b/lib/screens/channel/chat/widgets/chat_user_modal.dart index b510c882..ae7765ca 100644 --- a/lib/screens/channel/chat/widgets/chat_user_modal.dart +++ b/lib/screens/channel/chat/widgets/chat_user_modal.dart @@ -31,8 +31,9 @@ class ChatUserModal extends StatefulWidget { class _ChatUserModalState extends State { @override Widget build(BuildContext context) { - final name = - regexEnglish.hasMatch(widget.displayName) ? widget.displayName : '${widget.displayName} (${widget.username})'; + final name = regexEnglish.hasMatch(widget.displayName) + ? widget.displayName + : '${widget.displayName} (${widget.username})'; return FrostyBottomSheet( child: SizedBox( @@ -63,7 +64,8 @@ class _ChatUserModalState extends State { ? Button( padding: const EdgeInsets.symmetric(horizontal: 15.0), onPressed: () { - widget.chatStore.textController.text = '@${widget.username} '; + widget.chatStore.textController.text = + '@${widget.username} '; Navigator.pop(context); widget.chatStore.textFieldFocusNode.requestFocus(); }, @@ -88,17 +90,21 @@ class _ChatUserModalState extends State { Expanded( child: Observer( builder: (context) { - final userMessages = - widget.chatStore.messages.reversed.where((message) => message.user == widget.username).toList(); + final userMessages = widget.chatStore.messages.reversed + .where((message) => message.user == widget.username) + .toList(); if (userMessages.isEmpty) { return const AlertMessage(message: 'No recent messages'); } return MediaQuery( - data: MediaQuery.of(context).copyWith(textScaleFactor: widget.chatStore.settings.messageScale), + data: MediaQuery.of(context).copyWith( + textScaleFactor: + widget.chatStore.settings.messageScale), child: DefaultTextStyle( - style: DefaultTextStyle.of(context).style.copyWith(fontSize: widget.chatStore.settings.fontSize), + style: DefaultTextStyle.of(context).style.copyWith( + fontSize: widget.chatStore.settings.fontSize), child: ListView.builder( reverse: true, itemCount: userMessages.length, diff --git a/lib/screens/channel/video/video.dart b/lib/screens/channel/video/video.dart index 34998abf..60ff16ce 100644 --- a/lib/screens/channel/video/video.dart +++ b/lib/screens/channel/video/video.dart @@ -26,8 +26,11 @@ class _VideoState extends State