This commit is contained in:
Tommy Chow 2025-09-05 23:33:12 -07:00
parent 54456f990e
commit 696b227497
105 changed files with 2194 additions and 2273 deletions

View file

@ -20,27 +20,27 @@ class ApiException implements Exception {
class NetworkException extends ApiException {
const NetworkException(String message)
: super(message, null, DioExceptionType.connectionError);
: super(message, null, DioExceptionType.connectionError);
}
class TimeoutException extends ApiException {
const TimeoutException(String message)
: super(message, null, DioExceptionType.connectionTimeout);
: super(message, null, DioExceptionType.connectionTimeout);
}
class ServerException extends ApiException {
const ServerException(String message, int statusCode)
: super(message, statusCode, DioExceptionType.badResponse);
: super(message, statusCode, DioExceptionType.badResponse);
}
class NotFoundException extends ApiException {
const NotFoundException(String message)
: super(message, 404, DioExceptionType.badResponse);
: super(message, 404, DioExceptionType.badResponse);
}
class UnauthorizedException extends ApiException {
const UnauthorizedException(String message)
: super(message, 401, DioExceptionType.badResponse);
: super(message, 401, DioExceptionType.badResponse);
}
/// Base API client to eliminate code duplication across API services
@ -204,7 +204,8 @@ abstract class BaseApiClient {
// Extract error message from server response
String? serverMessage;
if (responseData is JsonMap) {
serverMessage = responseData['message'] as String? ??
serverMessage =
responseData['message'] as String? ??
responseData['error'] as String? ??
responseData['error_description'] as String?;
}

View file

@ -26,12 +26,14 @@ class BTTVApi extends BaseApiClient {
final emoteToUrl = <Emote>[];
emoteToUrl.addAll(
result.channelEmotes
.map((emote) => Emote.fromBTTV(emote, EmoteType.bttvChannel)),
result.channelEmotes.map(
(emote) => Emote.fromBTTV(emote, EmoteType.bttvChannel),
),
);
emoteToUrl.addAll(
result.sharedEmotes
.map((emote) => Emote.fromBTTV(emote, EmoteType.bttvShared)),
result.sharedEmotes.map(
(emote) => Emote.fromBTTV(emote, EmoteType.bttvShared),
),
);
return emoteToUrl;
@ -41,8 +43,9 @@ class BTTVApi extends BaseApiClient {
Future<Map<String, ChatBadge>> getBadges() async {
final data = await get<JsonList>('/badges');
final badgeObjects =
data.map((badge) => BadgeInfoBTTV.fromJson(badge)).toList();
final badgeObjects = data
.map((badge) => BadgeInfoBTTV.fromJson(badge))
.toList();
return {
for (final badge in badgeObjects)

View file

@ -8,10 +8,12 @@ class DioClient {
// Optimized configuration based on 2024-2025 best practices
dio.options = BaseOptions(
connectTimeout:
const Duration(seconds: 8), // Balanced for various networks
receiveTimeout:
const Duration(seconds: 15), // Allow for larger API responses
connectTimeout: const Duration(
seconds: 8,
), // Balanced for various networks
receiveTimeout: const Duration(
seconds: 15,
), // Allow for larger API responses
sendTimeout: const Duration(seconds: 10), // Reasonable for uploads
headers: {
'User-Agent': 'Frosty (Flutter Twitch Client)',

View file

@ -36,7 +36,9 @@ class FFZApi extends BaseApiClient {
return (
roomInfo,
emotes.map((emote) => Emote.fromFFZ(emote, EmoteType.ffzChannel)).toList()
emotes
.map((emote) => Emote.fromFFZ(emote, EmoteType.ffzChannel))
.toList(),
);
}
@ -45,8 +47,9 @@ class FFZApi extends BaseApiClient {
final data = await get<JsonMap>('/badges/ids');
final badges = data['badges'] as JsonList;
final badgeObjects =
badges.map((badge) => BadgeInfoFFZ.fromJson(badge)).toList();
final badgeObjects = badges
.map((badge) => BadgeInfoFFZ.fromJson(badge))
.toList();
final result = <String, List<ChatBadge>>{};
for (final badge in badgeObjects.reversed) {

View file

@ -24,15 +24,16 @@ class SevenTVApi extends BaseApiClient {
final data = await get<JsonMap>('/users/twitch/$id');
final emoteSetId = data['emote_set']['id'] as String;
final emotes = (data['emote_set']['emotes'] as JsonList)
.map((emote) => Emote7TV.fromJson(emote));
final emotes = (data['emote_set']['emotes'] as JsonList).map(
(emote) => Emote7TV.fromJson(emote),
);
return (
emoteSetId,
emotes
.map((emote) => Emote.from7TV(emote, EmoteType.sevenTVChannel))
.where((emote) => emote.url.isNotEmpty)
.toList()
.toList(),
);
}
}

View file

@ -33,9 +33,7 @@ class TwitchApi extends BaseApiClient {
}
/// Returns a list of a channel's Twitch emotes given their [id].
Future<List<Emote>> getEmotesChannel({
required String id,
}) async {
Future<List<Emote>> getEmotesChannel({required String id}) async {
final data = await get<JsonMap>(
'/chat/emotes',
queryParameters: {'broadcaster_id': id},
@ -59,9 +57,7 @@ class TwitchApi extends BaseApiClient {
}
/// Returns a list of Twitch emotes under the provided [setId].
Future<List<Emote>> getEmotesSets({
required String setId,
}) async {
Future<List<Emote>> getEmotesSets({required String setId}) async {
final data = await get<JsonMap>(
'/chat/emotes/set',
queryParameters: {'emote_set_id': setId},
@ -104,9 +100,7 @@ class TwitchApi extends BaseApiClient {
}
/// Returns a map of a channel's Twitch badges to their [Emote] object.
Future<Map<String, ChatBadge>> getBadgesChannel({
required String id,
}) async {
Future<Map<String, ChatBadge>> getBadgesChannel({required String id}) async {
final data = await get<JsonMap>(
'/chat/badges',
queryParameters: {'broadcaster_id': id},
@ -165,9 +159,7 @@ class TwitchApi extends BaseApiClient {
}
/// Returns a [StreamsTwitch] object that contains the top 20 streams and a cursor for further requests.
Future<StreamsTwitch> getTopStreams({
String? cursor,
}) async {
Future<StreamsTwitch> getTopStreams({String? cursor}) async {
final data = await get<JsonMap>(
'/streams',
queryParameters: cursor != null ? {'after': cursor} : null,
@ -216,18 +208,13 @@ class TwitchApi extends BaseApiClient {
final queryParams = {'game_id': gameId};
if (cursor != null) queryParams['after'] = cursor;
final data = await get<JsonMap>(
'/streams',
queryParameters: queryParams,
);
final data = await get<JsonMap>('/streams', queryParameters: queryParams);
return StreamsTwitch.fromJson(data);
}
/// Returns a [StreamTwitch] object containing the stream info associated with the given [userLogin].
Future<StreamTwitch> getStream({
required String userLogin,
}) async {
Future<StreamTwitch> getStream({required String userLogin}) async {
final data = await get<JsonMap>(
'/streams',
queryParameters: {'user_login': userLogin},
@ -241,9 +228,7 @@ class TwitchApi extends BaseApiClient {
}
}
Future<StreamsTwitch> getStreamsByIds({
required List<String> userIds,
}) async {
Future<StreamsTwitch> getStreamsByIds({required List<String> userIds}) async {
// Create query string manually for multiple user_id parameters
final userIdParams = userIds.map((id) => 'user_id=$id').join('&');
final url = '/streams?$userIdParams&first=100';
@ -254,10 +239,7 @@ class TwitchApi extends BaseApiClient {
}
/// Returns a [UserTwitch] object containing the user info associated with the given [userLogin].
Future<UserTwitch> getUser({
String? userLogin,
String? id,
}) async {
Future<UserTwitch> getUser({String? userLogin, String? id}) async {
final queryParams = <String, String>{};
if (id != null) {
queryParams['id'] = id;
@ -265,10 +247,7 @@ class TwitchApi extends BaseApiClient {
queryParams['login'] = userLogin;
}
final data = await get<JsonMap>(
'/users',
queryParameters: queryParams,
);
final data = await get<JsonMap>('/users', queryParameters: queryParams);
final userData = data['data'] as JsonList;
if (userData.isNotEmpty) {
@ -279,9 +258,7 @@ class TwitchApi extends BaseApiClient {
}
/// Returns a [Channel] object containing a channels's info associated with the given [userId].
Future<Channel> getChannel({
required String userId,
}) async {
Future<Channel> getChannel({required String userId}) async {
final data = await get<JsonMap>(
'/channels',
queryParameters: {'broadcaster_id': userId},
@ -296,9 +273,7 @@ class TwitchApi extends BaseApiClient {
}
/// Returns a list of [ChannelQuery] objects closest matching the given [query].
Future<List<ChannelQuery>> searchChannels({
required String query,
}) async {
Future<List<ChannelQuery>> searchChannels({required String query}) async {
final data = await get<JsonMap>(
'/search/channels',
queryParameters: {'first': '8', 'query': query},
@ -309,9 +284,7 @@ class TwitchApi extends BaseApiClient {
}
/// Returns a [CategoriesTwitch] object containing the next top 20 categories/games and a cursor for further requests.
Future<CategoriesTwitch> getTopCategories({
String? cursor,
}) async {
Future<CategoriesTwitch> getTopCategories({String? cursor}) async {
final data = await get<JsonMap>(
'/games/top',
queryParameters: cursor != null ? {'after': cursor} : null,
@ -321,13 +294,8 @@ class TwitchApi extends BaseApiClient {
}
/// Returns a [CategoriesTwitch] object containing the category info corresponding to the provided [gameId].
Future<CategoriesTwitch> getCategory({
required String gameId,
}) async {
final data = await get<JsonMap>(
'/games',
queryParameters: {'id': gameId},
);
Future<CategoriesTwitch> getCategory({required String gameId}) async {
final data = await get<JsonMap>('/games', queryParameters: {'id': gameId});
return CategoriesTwitch.fromJson(data);
}
@ -349,9 +317,7 @@ class TwitchApi extends BaseApiClient {
}
/// Returns the sub count associated with the given [userId].
Future<int> getSubscriberCount({
required String userId,
}) async {
Future<int> getSubscriberCount({required String userId}) async {
final data = await get<JsonMap>(
'/subscriptions',
queryParameters: {'broadcaster_id': userId},
@ -377,8 +343,9 @@ class TwitchApi extends BaseApiClient {
final blockedList = data['data'] as JsonList;
if (blockedList.isNotEmpty) {
final result =
blockedList.map((e) => UserBlockedTwitch.fromJson(e)).toList();
final result = blockedList
.map((e) => UserBlockedTwitch.fromJson(e))
.toList();
if (paginationCursor != null) {
// Wait a bit (150 milliseconds) before recursively calling.
@ -388,10 +355,7 @@ class TwitchApi extends BaseApiClient {
// 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: paginationCursor,
),
await getUserBlockedList(id: id, cursor: paginationCursor),
);
}
@ -403,9 +367,7 @@ class TwitchApi extends BaseApiClient {
}
// Blocks the user with the given ID and returns true on success or false on failure.
Future<bool> blockUser({
required String userId,
}) async {
Future<bool> blockUser({required String userId}) async {
try {
await put<dynamic>(
'/users/blocks',
@ -418,9 +380,7 @@ class TwitchApi extends BaseApiClient {
}
// Unblocks the user with the given ID and returns true on success or false on failure.
Future<bool> unblockUser({
required String userId,
}) async {
Future<bool> unblockUser({required String userId}) async {
try {
await delete<dynamic>(
'/users/blocks',
@ -451,15 +411,11 @@ class TwitchApi extends BaseApiClient {
/// Gets the color used for the user's name in chat.
/// [userId] - The ID of the user whose chat color to get
/// Returns the color as a hex string or empty string if no color is set.
Future<String> getUserChatColor({
required String userId,
}) async {
Future<String> getUserChatColor({required String userId}) async {
try {
final data = await get<JsonMap>(
'/chat/color',
queryParameters: {
'user_id': userId,
},
queryParameters: {'user_id': userId},
);
final users = data['data'] as JsonList;
@ -485,10 +441,7 @@ class TwitchApi extends BaseApiClient {
try {
await put<dynamic>(
'/chat/color',
queryParameters: {
'user_id': userId,
'color': color,
},
queryParameters: {'user_id': userId, 'color': color},
);
return true; // If no exception, operation succeeded
} on ApiException catch (e) {
@ -499,9 +452,7 @@ class TwitchApi extends BaseApiClient {
}
// Gets recent messages from a third-party service.
Future<JsonList> getRecentMessages({
required String userLogin,
}) async {
Future<JsonList> getRecentMessages({required String userLogin}) async {
// Use custom base URL for third-party service
final data = await get<JsonMap>(
'$_recentMessagesUrl/recent-messages/$userLogin',

View file

@ -31,9 +31,7 @@ void main() async {
CustomCacheManager.removeOrphanedCacheFiles();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
// Pass all uncaught "fatal" errors from the framework to Crashlytics
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
@ -111,10 +109,7 @@ final navigatorKey = GlobalKey<NavigatorState>();
class MyApp extends StatefulWidget {
final bool firstRun;
const MyApp({
super.key,
this.firstRun = false,
});
const MyApp({super.key, this.firstRun = false});
@override
State<MyApp> createState() => _MyAppState();
@ -138,8 +133,9 @@ class _MyAppState extends State<MyApp> {
return Observer(
builder: (context) {
final settingsStore = context.read<SettingsStore>();
final themes =
FrostyThemes(colorSchemeSeed: Color(settingsStore.accentColor));
final themes = FrostyThemes(
colorSchemeSeed: Color(settingsStore.accentColor),
);
return Provider<FrostyThemes>(
create: (_) => themes,
@ -150,8 +146,8 @@ class _MyAppState extends State<MyApp> {
themeMode: settingsStore.themeType == ThemeType.system
? ThemeMode.system
: settingsStore.themeType == ThemeType.light
? ThemeMode.light
: ThemeMode.dark,
? ThemeMode.light
: ThemeMode.dark,
home: widget.firstRun ? const OnboardingIntro() : const Home(),
navigatorKey: navigatorKey,
),

View file

@ -35,12 +35,7 @@ class BadgeInfoFFZ {
final String color;
final BadgeUrlsFFZ urls;
const BadgeInfoFFZ(
this.id,
this.title,
this.color,
this.urls,
);
const BadgeInfoFFZ(this.id, this.title, this.color, this.urls);
factory BadgeInfoFFZ.fromJson(Map<String, dynamic> json) =>
_$BadgeInfoFFZFromJson(json);
@ -55,11 +50,7 @@ class BadgeUrlsFFZ {
@JsonKey(name: '4')
final String url4x;
const BadgeUrlsFFZ(
this.url1x,
this.url2x,
this.url4x,
);
const BadgeUrlsFFZ(this.url1x, this.url2x, this.url4x);
factory BadgeUrlsFFZ.fromJson(Map<String, dynamic> json) =>
_$BadgeUrlsFFZFromJson(json);
@ -71,11 +62,7 @@ class BadgeInfo7TV {
final List<List<String>> urls;
final List<String> users;
BadgeInfo7TV(
this.tooltip,
this.urls,
this.users,
);
BadgeInfo7TV(this.tooltip, this.urls, this.users);
factory BadgeInfo7TV.fromJson(Map<String, dynamic> json) =>
_$BadgeInfo7TVFromJson(json);
@ -86,10 +73,7 @@ class BadgeInfoBTTV {
final String providerId;
final BadgeDetailsBTTV badge;
BadgeInfoBTTV(
this.providerId,
this.badge,
);
BadgeInfoBTTV(this.providerId, this.badge);
factory BadgeInfoBTTV.fromJson(Map<String, dynamic> json) =>
_$BadgeInfoBTTVFromJson(json);
@ -100,10 +84,7 @@ class BadgeDetailsBTTV {
final String description;
final String svg;
BadgeDetailsBTTV(
this.description,
this.svg,
);
BadgeDetailsBTTV(this.description, this.svg);
factory BadgeDetailsBTTV.fromJson(Map<String, dynamic> json) =>
_$BadgeDetailsBTTVFromJson(json);
@ -123,29 +104,29 @@ class ChatBadge {
});
factory ChatBadge.fromTwitch(BadgeInfoTwitch badge) => ChatBadge(
name: badge.title,
url: badge.imageUrl4x,
type: BadgeType.twitch,
);
name: badge.title,
url: badge.imageUrl4x,
type: BadgeType.twitch,
);
factory ChatBadge.fromBTTV(BadgeInfoBTTV badge) => ChatBadge(
name: badge.badge.description,
url: badge.badge.svg,
type: BadgeType.bttv,
);
name: badge.badge.description,
url: badge.badge.svg,
type: BadgeType.bttv,
);
factory ChatBadge.fromFFZ(BadgeInfoFFZ badge) => ChatBadge(
name: badge.title,
url: badge.urls.url4x,
color: badge.color,
type: BadgeType.ffz,
);
name: badge.title,
url: badge.urls.url4x,
color: badge.color,
type: BadgeType.ffz,
);
factory ChatBadge.from7TV(BadgeInfo7TV badge) => ChatBadge(
name: badge.tooltip,
url: badge.urls[2][1],
type: BadgeType.sevenTV,
);
name: badge.tooltip,
url: badge.urls[2][1],
type: BadgeType.sevenTV,
);
}
enum BadgeType {

View file

@ -17,25 +17,22 @@ BadgeInfoTwitch _$BadgeInfoTwitchFromJson(Map<String, dynamic> json) =>
);
BadgeInfoFFZ _$BadgeInfoFFZFromJson(Map<String, dynamic> json) => BadgeInfoFFZ(
(json['id'] as num).toInt(),
json['title'] as String,
json['color'] as String,
BadgeUrlsFFZ.fromJson(json['urls'] as Map<String, dynamic>),
);
(json['id'] as num).toInt(),
json['title'] as String,
json['color'] as String,
BadgeUrlsFFZ.fromJson(json['urls'] as Map<String, dynamic>),
);
BadgeUrlsFFZ _$BadgeUrlsFFZFromJson(Map<String, dynamic> json) => BadgeUrlsFFZ(
json['1'] as String,
json['2'] as String,
json['4'] as String,
);
BadgeUrlsFFZ _$BadgeUrlsFFZFromJson(Map<String, dynamic> json) =>
BadgeUrlsFFZ(json['1'] as String, json['2'] as String, json['4'] as String);
BadgeInfo7TV _$BadgeInfo7TVFromJson(Map<String, dynamic> json) => BadgeInfo7TV(
json['tooltip'] as String,
(json['urls'] as List<dynamic>)
.map((e) => (e as List<dynamic>).map((e) => e as String).toList())
.toList(),
(json['users'] as List<dynamic>).map((e) => e as String).toList(),
);
json['tooltip'] as String,
(json['urls'] as List<dynamic>)
.map((e) => (e as List<dynamic>).map((e) => e as String).toList())
.toList(),
(json['users'] as List<dynamic>).map((e) => e as String).toList(),
);
BadgeInfoBTTV _$BadgeInfoBTTVFromJson(Map<String, dynamic> json) =>
BadgeInfoBTTV(
@ -44,7 +41,4 @@ BadgeInfoBTTV _$BadgeInfoBTTVFromJson(Map<String, dynamic> json) =>
);
BadgeDetailsBTTV _$BadgeDetailsBTTVFromJson(Map<String, dynamic> json) =>
BadgeDetailsBTTV(
json['description'] as String,
json['svg'] as String,
);
BadgeDetailsBTTV(json['description'] as String, json['svg'] as String);

View file

@ -8,11 +8,7 @@ class CategoryTwitch {
final String id;
final String name;
const CategoryTwitch(
this.boxArtUrl,
this.id,
this.name,
);
const CategoryTwitch(this.boxArtUrl, this.id, this.name);
factory CategoryTwitch.fromJson(Map<String, dynamic> json) =>
_$CategoryTwitchFromJson(json);
@ -23,10 +19,7 @@ class CategoriesTwitch {
final List<CategoryTwitch> data;
final Map<String, String>? pagination;
const CategoriesTwitch(
this.data,
this.pagination,
);
const CategoriesTwitch(this.data, this.pagination);
factory CategoriesTwitch.fromJson(Map<String, dynamic> json) =>
_$CategoriesTwitchFromJson(json);

View file

@ -7,15 +7,15 @@ part of 'channel.dart';
// **************************************************************************
Channel _$ChannelFromJson(Map<String, dynamic> json) => Channel(
broadcasterId: json['broadcaster_id'] as String,
broadcasterLogin: json['broadcaster_login'] as String,
broadcasterName: json['broadcaster_name'] as String,
);
broadcasterId: json['broadcaster_id'] as String,
broadcasterLogin: json['broadcaster_login'] as String,
broadcasterName: json['broadcaster_name'] as String,
);
ChannelQuery _$ChannelQueryFromJson(Map<String, dynamic> json) => ChannelQuery(
broadcasterLogin: json['broadcaster_login'] as String,
displayName: json['display_name'] as String,
id: json['id'] as String,
isLive: json['is_live'] as bool,
startedAt: json['started_at'] as String,
);
broadcasterLogin: json['broadcaster_login'] as String,
displayName: json['display_name'] as String,
id: json['id'] as String,
isLive: json['is_live'] as bool,
startedAt: json['started_at'] as String,
);

View file

@ -7,10 +7,7 @@ class ChatUsers {
final int chatterCount;
final Chatters chatters;
ChatUsers(
this.chatterCount,
this.chatters,
);
ChatUsers(this.chatterCount, this.chatters);
factory ChatUsers.fromJson(Map<String, dynamic> json) =>
_$ChatUsersFromJson(json);

View file

@ -7,16 +7,16 @@ part of 'chatters.dart';
// **************************************************************************
ChatUsers _$ChatUsersFromJson(Map<String, dynamic> json) => ChatUsers(
(json['chatter_count'] as num).toInt(),
Chatters.fromJson(json['chatters'] as Map<String, dynamic>),
);
(json['chatter_count'] as num).toInt(),
Chatters.fromJson(json['chatters'] as Map<String, dynamic>),
);
Chatters _$ChattersFromJson(Map<String, dynamic> json) => Chatters(
(json['broadcaster'] as List<dynamic>).map((e) => e as String).toList(),
(json['vips'] as List<dynamic>).map((e) => e as String).toList(),
(json['moderators'] as List<dynamic>).map((e) => e as String).toList(),
(json['staff'] as List<dynamic>).map((e) => e as String).toList(),
(json['admins'] as List<dynamic>).map((e) => e as String).toList(),
(json['global_mods'] as List<dynamic>).map((e) => e as String).toList(),
(json['viewers'] as List<dynamic>).map((e) => e as String).toList(),
);
(json['broadcaster'] as List<dynamic>).map((e) => e as String).toList(),
(json['vips'] as List<dynamic>).map((e) => e as String).toList(),
(json['moderators'] as List<dynamic>).map((e) => e as String).toList(),
(json['staff'] as List<dynamic>).map((e) => e as String).toList(),
(json['admins'] as List<dynamic>).map((e) => e as String).toList(),
(json['global_mods'] as List<dynamic>).map((e) => e as String).toList(),
(json['viewers'] as List<dynamic>).map((e) => e as String).toList(),
);

View file

@ -12,12 +12,7 @@ class EmoteTwitch {
final String? emoteType;
final String? ownerId;
const EmoteTwitch(
this.id,
this.name,
this.emoteType,
this.ownerId,
);
const EmoteTwitch(this.id, this.name, this.emoteType, this.ownerId);
factory EmoteTwitch.fromJson(Map<String, dynamic> json) =>
_$EmoteTwitchFromJson(json);
@ -29,10 +24,7 @@ class EmoteBTTV {
final String id;
final String code;
const EmoteBTTV(
this.id,
this.code,
);
const EmoteBTTV(this.id, this.code);
factory EmoteBTTV.fromJson(Map<String, dynamic> json) =>
_$EmoteBTTVFromJson(json);
@ -43,10 +35,7 @@ class EmoteBTTVChannel {
final List<EmoteBTTV> channelEmotes;
final List<EmoteBTTV> sharedEmotes;
const EmoteBTTVChannel(
this.channelEmotes,
this.sharedEmotes,
);
const EmoteBTTVChannel(this.channelEmotes, this.sharedEmotes);
factory EmoteBTTVChannel.fromJson(Map<String, dynamic> json) =>
_$EmoteBTTVChannelFromJson(json);
@ -59,11 +48,7 @@ class RoomFFZ {
final ImagesFFZ? vipBadge;
final ImagesFFZ? modUrls;
const RoomFFZ(
this.set,
this.vipBadge,
this.modUrls,
);
const RoomFFZ(this.set, this.vipBadge, this.modUrls);
factory RoomFFZ.fromJson(Map<String, dynamic> json) =>
_$RoomFFZFromJson(json);
@ -78,11 +63,7 @@ class ImagesFFZ {
@JsonKey(name: '4')
final String? url4x;
const ImagesFFZ(
this.url1x,
this.url2x,
this.url4x,
);
const ImagesFFZ(this.url1x, this.url2x, this.url4x);
factory ImagesFFZ.fromJson(Map<String, dynamic> json) =>
_$ImagesFFZFromJson(json);
@ -93,10 +74,7 @@ class OwnerFFZ {
final String displayName;
final String name;
const OwnerFFZ({
required this.displayName,
required this.name,
});
const OwnerFFZ({required this.displayName, required this.name});
factory OwnerFFZ.fromJson(Map<String, dynamic> json) =>
_$OwnerFFZFromJson(json);
@ -130,11 +108,7 @@ class Emote7TV {
final String name;
final Emote7TVData data;
const Emote7TV(
this.id,
this.name,
this.data,
);
const Emote7TV(this.id, this.name, this.data);
factory Emote7TV.fromJson(Map<String, dynamic> json) =>
_$Emote7TVFromJson(json);
@ -145,10 +119,7 @@ class Owner7TV {
final String username;
final String displayName;
const Owner7TV({
required this.username,
required this.displayName,
});
const Owner7TV({required this.username, required this.displayName});
factory Owner7TV.fromJson(Map<String, dynamic> json) =>
_$Owner7TVFromJson(json);
@ -162,13 +133,7 @@ class Emote7TVData {
final Owner7TV? owner;
final Emote7TVHost host;
const Emote7TVData(
this.id,
this.name,
this.flags,
this.owner,
this.host,
);
const Emote7TVData(this.id, this.name, this.flags, this.owner, this.host);
factory Emote7TVData.fromJson(Map<String, dynamic> json) =>
_$Emote7TVDataFromJson(json);
@ -179,10 +144,7 @@ class Emote7TVHost {
final String url;
final List<Emote7TVFile> files;
Emote7TVHost(
this.url,
this.files,
);
Emote7TVHost(this.url, this.files);
factory Emote7TVHost.fromJson(Map<String, dynamic> json) =>
_$Emote7TVHostFromJson(json);
@ -195,12 +157,7 @@ class Emote7TVFile {
final int height;
final String format;
Emote7TVFile(
this.name,
this.width,
this.height,
this.format,
);
Emote7TVFile(this.name, this.width, this.height, this.format);
factory Emote7TVFile.fromJson(Map<String, dynamic> json) =>
_$Emote7TVFileFromJson(json);
@ -234,36 +191,37 @@ 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',
type: type,
ownerId: emote.ownerId,
);
name: emote.name,
zeroWidth: false,
url:
'https://static-cdn.jtvnw.net/emoticons/v2/${emote.id}/default/dark/3.0',
type: type,
ownerId: emote.ownerId,
);
factory Emote.fromBTTV(EmoteBTTV emote, EmoteType type) => Emote(
name: emote.code,
zeroWidth: zeroWidthEmotes.contains(emote.code),
url: 'https://cdn.betterttv.net/emote/${emote.id}/3x',
type: type,
);
name: emote.code,
zeroWidth: zeroWidthEmotes.contains(emote.code),
url: 'https://cdn.betterttv.net/emote/${emote.id}/3x',
type: type,
);
factory Emote.fromFFZ(EmoteFFZ emote, EmoteType type) => Emote(
name: emote.name,
zeroWidth: false,
width: emote.width,
height: emote.height,
url: emote.animated?.url4x ??
emote.animated?.url2x ??
emote.animated?.url1x ??
emote.urls.url4x ??
emote.urls.url2x ??
emote.urls.url1x,
type: type,
ownerDisplayName: emote.owner.displayName,
ownerUsername: emote.owner.name,
);
name: emote.name,
zeroWidth: false,
width: emote.width,
height: emote.height,
url:
emote.animated?.url4x ??
emote.animated?.url2x ??
emote.animated?.url1x ??
emote.urls.url4x ??
emote.urls.url2x ??
emote.urls.url1x,
type: type,
ownerDisplayName: emote.owner.displayName,
ownerUsername: emote.owner.name,
);
factory Emote.from7TV(Emote7TV emote, EmoteType type) {
final emoteData = emote.data;

View file

@ -7,16 +7,14 @@ part of 'emotes.dart';
// **************************************************************************
EmoteTwitch _$EmoteTwitchFromJson(Map<String, dynamic> json) => EmoteTwitch(
json['id'] as String,
json['name'] as String,
json['emote_type'] as String?,
json['owner_id'] as String?,
);
json['id'] as String,
json['name'] as String,
json['emote_type'] as String?,
json['owner_id'] as String?,
);
EmoteBTTV _$EmoteBTTVFromJson(Map<String, dynamic> json) => EmoteBTTV(
json['id'] as String,
json['code'] as String,
);
EmoteBTTV _$EmoteBTTVFromJson(Map<String, dynamic> json) =>
EmoteBTTV(json['id'] as String, json['code'] as String);
EmoteBTTVChannel _$EmoteBTTVChannelFromJson(Map<String, dynamic> json) =>
EmoteBTTVChannel(
@ -29,97 +27,94 @@ EmoteBTTVChannel _$EmoteBTTVChannelFromJson(Map<String, dynamic> json) =>
);
RoomFFZ _$RoomFFZFromJson(Map<String, dynamic> json) => RoomFFZ(
(json['set'] as num).toInt(),
json['vip_badge'] == null
? null
: ImagesFFZ.fromJson(json['vip_badge'] as Map<String, dynamic>),
json['mod_urls'] == null
? null
: ImagesFFZ.fromJson(json['mod_urls'] as Map<String, dynamic>),
);
(json['set'] as num).toInt(),
json['vip_badge'] == null
? null
: ImagesFFZ.fromJson(json['vip_badge'] as Map<String, dynamic>),
json['mod_urls'] == null
? null
: ImagesFFZ.fromJson(json['mod_urls'] as Map<String, dynamic>),
);
ImagesFFZ _$ImagesFFZFromJson(Map<String, dynamic> json) => ImagesFFZ(
json['1'] as String,
json['2'] as String?,
json['4'] as String?,
);
ImagesFFZ _$ImagesFFZFromJson(Map<String, dynamic> json) =>
ImagesFFZ(json['1'] as String, json['2'] as String?, json['4'] as String?);
OwnerFFZ _$OwnerFFZFromJson(Map<String, dynamic> json) => OwnerFFZ(
displayName: json['display_name'] as String,
name: json['name'] as String,
);
displayName: json['display_name'] as String,
name: json['name'] as String,
);
EmoteFFZ _$EmoteFFZFromJson(Map<String, dynamic> json) => EmoteFFZ(
json['name'] as String,
(json['height'] as num).toInt(),
(json['width'] as num).toInt(),
OwnerFFZ.fromJson(json['owner'] as Map<String, dynamic>),
ImagesFFZ.fromJson(json['urls'] as Map<String, dynamic>),
json['animated'] == null
? null
: ImagesFFZ.fromJson(json['animated'] as Map<String, dynamic>),
);
json['name'] as String,
(json['height'] as num).toInt(),
(json['width'] as num).toInt(),
OwnerFFZ.fromJson(json['owner'] as Map<String, dynamic>),
ImagesFFZ.fromJson(json['urls'] as Map<String, dynamic>),
json['animated'] == null
? null
: ImagesFFZ.fromJson(json['animated'] as Map<String, dynamic>),
);
Emote7TV _$Emote7TVFromJson(Map<String, dynamic> json) => Emote7TV(
json['id'] as String,
json['name'] as String,
Emote7TVData.fromJson(json['data'] as Map<String, dynamic>),
);
json['id'] as String,
json['name'] as String,
Emote7TVData.fromJson(json['data'] as Map<String, dynamic>),
);
Owner7TV _$Owner7TVFromJson(Map<String, dynamic> json) => Owner7TV(
username: json['username'] as String,
displayName: json['display_name'] as String,
);
username: json['username'] as String,
displayName: json['display_name'] as String,
);
Emote7TVData _$Emote7TVDataFromJson(Map<String, dynamic> json) => Emote7TVData(
json['id'] as String,
json['name'] as String,
(json['flags'] as num).toInt(),
json['owner'] == null
? null
: Owner7TV.fromJson(json['owner'] as Map<String, dynamic>),
Emote7TVHost.fromJson(json['host'] as Map<String, dynamic>),
);
json['id'] as String,
json['name'] as String,
(json['flags'] as num).toInt(),
json['owner'] == null
? null
: Owner7TV.fromJson(json['owner'] as Map<String, dynamic>),
Emote7TVHost.fromJson(json['host'] as Map<String, dynamic>),
);
Emote7TVHost _$Emote7TVHostFromJson(Map<String, dynamic> json) => Emote7TVHost(
json['url'] as String,
(json['files'] as List<dynamic>)
.map((e) => Emote7TVFile.fromJson(e as Map<String, dynamic>))
.toList(),
);
json['url'] as String,
(json['files'] as List<dynamic>)
.map((e) => Emote7TVFile.fromJson(e as Map<String, dynamic>))
.toList(),
);
Emote7TVFile _$Emote7TVFileFromJson(Map<String, dynamic> json) => Emote7TVFile(
json['name'] as String,
(json['width'] as num).toInt(),
(json['height'] as num).toInt(),
json['format'] as String,
);
json['name'] as String,
(json['width'] as num).toInt(),
(json['height'] as num).toInt(),
json['format'] as String,
);
Emote _$EmoteFromJson(Map<String, dynamic> json) => Emote(
name: json['name'] as String,
realName: json['realName'] as String?,
width: (json['width'] as num?)?.toInt(),
height: (json['height'] as num?)?.toInt(),
zeroWidth: json['zeroWidth'] as bool,
url: json['url'] as String,
type: $enumDecode(_$EmoteTypeEnumMap, json['type']),
ownerDisplayName: json['ownerDisplayName'] as String?,
ownerUsername: json['ownerUsername'] as String?,
ownerId: json['ownerId'] as String?,
);
name: json['name'] as String,
realName: json['realName'] as String?,
width: (json['width'] as num?)?.toInt(),
height: (json['height'] as num?)?.toInt(),
zeroWidth: json['zeroWidth'] as bool,
url: json['url'] as String,
type: $enumDecode(_$EmoteTypeEnumMap, json['type']),
ownerDisplayName: json['ownerDisplayName'] as String?,
ownerUsername: json['ownerUsername'] as String?,
ownerId: json['ownerId'] as String?,
);
Map<String, dynamic> _$EmoteToJson(Emote instance) => <String, dynamic>{
'name': instance.name,
'realName': instance.realName,
'width': instance.width,
'height': instance.height,
'zeroWidth': instance.zeroWidth,
'url': instance.url,
'type': _$EmoteTypeEnumMap[instance.type]!,
'ownerDisplayName': instance.ownerDisplayName,
'ownerUsername': instance.ownerUsername,
'ownerId': instance.ownerId,
};
'name': instance.name,
'realName': instance.realName,
'width': instance.width,
'height': instance.height,
'zeroWidth': instance.zeroWidth,
'url': instance.url,
'type': _$EmoteTypeEnumMap[instance.type]!,
'ownerDisplayName': instance.ownerDisplayName,
'ownerUsername': instance.ownerUsername,
'ownerId': instance.ownerId,
};
const _$EmoteTypeEnumMap = {
EmoteType.twitchBits: 'twitchBits',

View file

@ -10,11 +10,7 @@ class SevenTVEvent {
final int? t;
final SevenTVEventData d;
const SevenTVEvent({
required this.op,
this.t,
required this.d,
});
const SevenTVEvent({required this.op, this.t, required this.d});
factory SevenTVEvent.fromJson(Map<String, dynamic> json) =>
_$SevenTVEventFromJson(json);
@ -29,11 +25,7 @@ class SevenTVEventData {
@JsonKey(includeToJson: false)
final SevenTVEventEmoteSetBody? body;
const SevenTVEventData({
required this.type,
this.condition,
this.body,
});
const SevenTVEventData({required this.type, this.condition, this.body});
factory SevenTVEventData.fromJson(Map<String, dynamic> json) =>
_$SevenTVEventDataFromJson(json);
@ -46,10 +38,7 @@ class SevenTVEventUpdatedEmote {
final Emote7TV? value;
final Emote7TV? oldValue;
const SevenTVEventUpdatedEmote({
this.value,
this.oldValue,
});
const SevenTVEventUpdatedEmote({this.value, this.oldValue});
factory SevenTVEventUpdatedEmote.fromJson(Map<String, dynamic> json) =>
_$SevenTVEventUpdatedEmoteFromJson(json);

View file

@ -7,15 +7,13 @@ part of 'events.dart';
// **************************************************************************
SevenTVEvent _$SevenTVEventFromJson(Map<String, dynamic> json) => SevenTVEvent(
op: (json['op'] as num).toInt(),
t: (json['t'] as num?)?.toInt(),
d: SevenTVEventData.fromJson(json['d'] as Map<String, dynamic>),
);
op: (json['op'] as num).toInt(),
t: (json['t'] as num?)?.toInt(),
d: SevenTVEventData.fromJson(json['d'] as Map<String, dynamic>),
);
Map<String, dynamic> _$SevenTVEventToJson(SevenTVEvent instance) {
final val = <String, dynamic>{
'op': instance.op,
};
final val = <String, dynamic>{'op': instance.op};
void writeNotNull(String key, dynamic value) {
if (value != null) {
@ -37,36 +35,32 @@ SevenTVEventData _$SevenTVEventDataFromJson(Map<String, dynamic> json) =>
body: json['body'] == null
? null
: SevenTVEventEmoteSetBody.fromJson(
json['body'] as Map<String, dynamic>),
json['body'] as Map<String, dynamic>,
),
);
Map<String, dynamic> _$SevenTVEventDataToJson(SevenTVEventData instance) =>
<String, dynamic>{
'type': instance.type,
'condition': instance.condition,
};
<String, dynamic>{'type': instance.type, 'condition': instance.condition};
SevenTVEventUpdatedEmote _$SevenTVEventUpdatedEmoteFromJson(
Map<String, dynamic> json) =>
SevenTVEventUpdatedEmote(
value: json['value'] == null
? null
: Emote7TV.fromJson(json['value'] as Map<String, dynamic>),
oldValue: json['old_value'] == null
? null
: Emote7TV.fromJson(json['old_value'] as Map<String, dynamic>),
);
Map<String, dynamic> json,
) => SevenTVEventUpdatedEmote(
value: json['value'] == null
? null
: Emote7TV.fromJson(json['value'] as Map<String, dynamic>),
oldValue: json['old_value'] == null
? null
: Emote7TV.fromJson(json['old_value'] as Map<String, dynamic>),
);
SevenTVEventEmoteSetBody _$SevenTVEventEmoteSetBodyFromJson(
Map<String, dynamic> json) =>
SevenTVEventEmoteSetBody(
actor: Owner7TV.fromJson(json['actor'] as Map<String, dynamic>),
pushed: (json['pushed'] as List<dynamic>?)
?.map((e) =>
SevenTVEventUpdatedEmote.fromJson(e as Map<String, dynamic>))
.toList(),
pulled: (json['pulled'] as List<dynamic>?)
?.map((e) =>
SevenTVEventUpdatedEmote.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> json,
) => SevenTVEventEmoteSetBody(
actor: Owner7TV.fromJson(json['actor'] as Map<String, dynamic>),
pushed: (json['pushed'] as List<dynamic>?)
?.map((e) => SevenTVEventUpdatedEmote.fromJson(e as Map<String, dynamic>))
.toList(),
pulled: (json['pulled'] as List<dynamic>?)
?.map((e) => SevenTVEventUpdatedEmote.fromJson(e as Map<String, dynamic>))
.toList(),
);

View file

@ -36,4 +36,4 @@ class FollowedChannels {
factory FollowedChannels.fromJson(Map<String, dynamic> json) =>
_$FollowedChannelsFromJson(json);
}
}

View file

@ -172,7 +172,7 @@ class IRCMessage {
size: badgeSize,
color:
Theme.of(context).iconTheme.color?.withValues(alpha: 0.5) ??
Colors.grey.withValues(alpha: 0.5),
Colors.grey.withValues(alpha: 0.5),
),
),
),
@ -270,14 +270,16 @@ class IRCMessage {
// Handle custom FFZ mod badge
if (badgeInfo.name == 'Moderator' &&
(ffzUserBadges != null || ffzRoomInfo?.modUrls != null)) {
final botBadge = ffzUserBadges
?.firstWhereOrNull((element) => element.name == 'Bot');
final botBadge = ffzUserBadges?.firstWhereOrNull(
(element) => element.name == 'Bot',
);
if (botBadge != null) {
badgeUrl = botBadge.url;
skipBot = true;
} else if (ffzRoomInfo?.modUrls != null) {
badgeUrl = ffzRoomInfo!.modUrls?.url4x ??
badgeUrl =
ffzRoomInfo!.modUrls?.url4x ??
ffzRoomInfo.modUrls?.url2x ??
ffzRoomInfo.modUrls!.url1x;
}
@ -303,7 +305,8 @@ class IRCMessage {
// Handle custom FFZ VIP badge
if (badgeInfo.name == 'VIP' && ffzRoomInfo?.vipBadge != null) {
badgeUrl = ffzRoomInfo!.vipBadge?.url4x ??
badgeUrl =
ffzRoomInfo!.vipBadge?.url4x ??
ffzRoomInfo.vipBadge?.url2x ??
ffzRoomInfo.vipBadge!.url1x;
}
@ -415,10 +418,7 @@ class IRCMessage {
text: user != null
? utils.getReadableName(displayName, user!)
: displayName,
style: TextStyle(
color: color,
fontWeight: FontWeight.bold,
),
style: TextStyle(color: color, fontWeight: FontWeight.bold),
recognizer: TapGestureRecognizer()..onTap = onTapName,
),
);
@ -496,8 +496,9 @@ class IRCMessage {
localSpan.add(
_createEmojiSpan(
emoji: word,
style:
textStyle?.copyWith(fontSize: emoteSize - _emojiSizeOffset),
style: textStyle?.copyWith(
fontSize: emoteSize - _emojiSizeOffset,
),
),
);
} else {
@ -583,10 +584,8 @@ class IRCMessage {
if (nextWordIsEmoji)
Text(emoji, style: textStyle?.copyWith(fontSize: 40)),
...emoteStack.reversed.map(
(emote) => FrostyCachedNetworkImage(
imageUrl: emote.url,
width: 56,
),
(emote) =>
FrostyCachedNetworkImage(imageUrl: emote.url, width: 56),
),
],
),
@ -669,8 +668,9 @@ class IRCMessage {
_addUsername(span, context, onTapName);
// 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;
_addMessageContent(
context,
@ -693,10 +693,7 @@ class IRCMessage {
}) {
return WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Text(
emoji,
style: style,
),
child: Text(emoji, style: style),
);
}
@ -717,11 +714,7 @@ class IRCMessage {
),
);
} else if (isSvg == true) {
return SvgPicture.network(
badge.url,
height: size,
width: size,
);
return SvgPicture.network(badge.url, height: size, width: size);
} else {
return FrostyCachedNetworkImage(
imageUrl: badge.url,
@ -820,11 +813,11 @@ class IRCMessage {
),
recognizer: TapGestureRecognizer()
..onTap = () => launchUrl(
Uri.parse(text),
mode: launchExternal
? LaunchMode.externalApplication
: LaunchMode.inAppBrowserView,
),
Uri.parse(text),
mode: launchExternal
? LaunchMode.externalApplication
: LaunchMode.inAppBrowserView,
),
);
} else {
return TextSpan(text: text, style: style);
@ -838,10 +831,7 @@ class IRCMessage {
}) {
_showAssetDetailsBottomSheet(
context,
leading: FrostyCachedNetworkImage(
imageUrl: emote.url,
width: 56,
),
leading: FrostyCachedNetworkImage(imageUrl: emote.url, width: 56),
url: emote.url,
title: emote.realName != null
? '${emote.name} (${emote.realName})'
@ -885,9 +875,7 @@ class IRCMessage {
),
title: Text(
title,
style: const TextStyle(
fontWeight: FontWeight.w600,
),
style: const TextStyle(fontWeight: FontWeight.w600),
),
subtitle: subtitle,
),
@ -944,8 +932,9 @@ 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 = <String, String>{};
@ -1000,8 +989,10 @@ 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.
@ -1133,12 +1124,8 @@ class IRCMessage {
);
}
factory IRCMessage.createNotice({required String message}) => IRCMessage(
raw: '',
tags: {},
command: Command.notice,
message: message,
);
factory IRCMessage.createNotice({required String message}) =>
IRCMessage(raw: '', tags: {}, command: Command.notice, message: message);
}
/// The object representation of the IRC ROOMSTATE message.
@ -1159,12 +1146,12 @@ class ROOMSTATE {
/// Create a new copy with the parameters from the provided [IRCMessage]
ROOMSTATE fromIRCMessage(IRCMessage ircMessage) => ROOMSTATE(
emoteOnly: ircMessage.tags['emote-only'] ?? emoteOnly,
followersOnly: ircMessage.tags['followers-only'] ?? followersOnly,
r9k: ircMessage.tags['r9k'] ?? r9k,
slowMode: ircMessage.tags['slow'] ?? slowMode,
subMode: ircMessage.tags['subs-only'] ?? subMode,
);
emoteOnly: ircMessage.tags['emote-only'] ?? emoteOnly,
followersOnly: ircMessage.tags['followers-only'] ?? followersOnly,
r9k: ircMessage.tags['r9k'] ?? r9k,
slowMode: ircMessage.tags['slow'] ?? slowMode,
subMode: ircMessage.tags['subs-only'] ?? subMode,
);
}
class USERSTATE {
@ -1183,12 +1170,12 @@ class USERSTATE {
});
USERSTATE fromIRCMessage(IRCMessage ircMessage) => USERSTATE(
raw: ircMessage.raw,
color: ircMessage.tags['color'] ?? color,
displayName: ircMessage.tags['display-name'] ?? displayName,
mod: ircMessage.tags['mod'] == '0' ? false : true,
subscriber: ircMessage.tags['subscriber'] == '0' ? false : true,
);
raw: ircMessage.raw,
color: ircMessage.tags['color'] ?? color,
displayName: ircMessage.tags['display-name'] ?? displayName,
mod: ircMessage.tags['mod'] == '0' ? false : true,
subscriber: ircMessage.tags['subscriber'] == '0' ? false : true,
);
}
/// The possible types of Twitch IRC commands.

View file

@ -17,6 +17,5 @@ SharedChatSession _$SharedChatSessionFromJson(Map<String, dynamic> json) =>
updatedAt: json['updated_at'] as String,
);
Participant _$ParticipantFromJson(Map<String, dynamic> json) => Participant(
broadcasterId: json['broadcaster_id'] as String,
);
Participant _$ParticipantFromJson(Map<String, dynamic> json) =>
Participant(broadcasterId: json['broadcaster_id'] as String);

View file

@ -36,10 +36,7 @@ class StreamsTwitch {
final List<StreamTwitch> data;
final Map<String, String> pagination;
const StreamsTwitch(
this.data,
this.pagination,
);
const StreamsTwitch(this.data, this.pagination);
factory StreamsTwitch.fromJson(Map<String, dynamic> json) =>
_$StreamsTwitchFromJson(json);

View file

@ -7,16 +7,16 @@ part of 'stream.dart';
// **************************************************************************
StreamTwitch _$StreamTwitchFromJson(Map<String, dynamic> json) => StreamTwitch(
json['user_id'] as String,
json['user_login'] as String,
json['user_name'] as String,
json['game_id'] as String,
json['game_name'] as String,
json['title'] as String,
(json['viewer_count'] as num).toInt(),
json['started_at'] as String,
json['thumbnail_url'] as String,
);
json['user_id'] as String,
json['user_login'] as String,
json['user_name'] as String,
json['game_id'] as String,
json['game_name'] as String,
json['title'] as String,
(json['viewer_count'] as num).toInt(),
json['started_at'] as String,
json['thumbnail_url'] as String,
);
StreamsTwitch _$StreamsTwitchFromJson(Map<String, dynamic> json) =>
StreamsTwitch(

View file

@ -10,12 +10,7 @@ class UserTwitch {
final String displayName;
final String profileImageUrl;
const UserTwitch(
this.id,
this.login,
this.displayName,
this.profileImageUrl,
);
const UserTwitch(this.id, this.login, this.displayName, this.profileImageUrl);
factory UserTwitch.fromJson(Map<String, dynamic> json) =>
_$UserTwitchFromJson(json);
@ -27,11 +22,7 @@ class UserBlockedTwitch {
final String userLogin;
final String displayName;
const UserBlockedTwitch(
this.userId,
this.userLogin,
this.displayName,
);
const UserBlockedTwitch(this.userId, this.userLogin, this.displayName);
factory UserBlockedTwitch.fromJson(Map<String, dynamic> json) =>
_$UserBlockedTwitchFromJson(json);

View file

@ -7,11 +7,11 @@ part of 'user.dart';
// **************************************************************************
UserTwitch _$UserTwitchFromJson(Map<String, dynamic> json) => UserTwitch(
json['id'] as String,
json['login'] as String,
json['display_name'] as String,
json['profile_image_url'] as String,
);
json['id'] as String,
json['login'] as String,
json['display_name'] as String,
json['profile_image_url'] as String,
);
UserBlockedTwitch _$UserBlockedTwitchFromJson(Map<String, dynamic> json) =>
UserBlockedTwitch(

View file

@ -94,19 +94,17 @@ class _VideoChatState extends State<VideoChat>
);
// Spring-back animation with smooth easing
_springBackAnimation = Tween<double>(
begin: 0,
end: 0,
).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.elasticOut,
),
)..addListener(() {
setState(() {
_pipDragDistance = _springBackAnimation.value;
_springBackAnimation =
Tween<double>(begin: 0, end: 0).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.elasticOut,
),
)..addListener(() {
setState(() {
_pipDragDistance = _springBackAnimation.value;
});
});
});
}
void _handlePipDragStart(DragStartDetails details) {
@ -151,7 +149,8 @@ class _VideoChatState extends State<VideoChat>
}
final velocity = details.velocity.pixelsPerSecond.dy;
final shouldTriggerPip = _pipDragDistance >= _pipTriggerDistance ||
final shouldTriggerPip =
_pipDragDistance >= _pipTriggerDistance ||
velocity > 600; // Simple velocity threshold
if (shouldTriggerPip) {
@ -179,15 +178,13 @@ class _VideoChatState extends State<VideoChat>
}
void _animateSpringBack() {
_springBackAnimation = Tween<double>(
begin: _pipDragDistance,
end: 0,
).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.elasticOut,
),
);
_springBackAnimation = Tween<double>(begin: _pipDragDistance, end: 0)
.animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.elasticOut,
),
);
_animationController.reset();
_animationController.forward().then((_) {
@ -201,10 +198,7 @@ class _VideoChatState extends State<VideoChat>
final player = GestureDetector(
onLongPress: _videoStore.handleToggleOverlay,
child: Video(
key: _videoKey,
videoStore: _videoStore,
),
child: Video(key: _videoKey, videoStore: _videoStore),
);
final overlay = GestureDetector(
@ -241,8 +235,9 @@ class _VideoChatState extends State<VideoChat>
duration: const Duration(milliseconds: 200),
child: Container(
decoration: BoxDecoration(
color: Colors.black
.withValues(alpha: settingsStore.overlayOpacity),
color: Colors.black.withValues(
alpha: settingsStore.overlayOpacity,
),
),
child: IgnorePointer(
ignoring: !_videoStore.overlayVisible,
@ -258,12 +253,7 @@ class _VideoChatState extends State<VideoChat>
builder: (context) {
if (!_videoStore.settingsStore.showOverlay) return player;
return Stack(
children: [
player,
overlay,
],
);
return Stack(children: [player, overlay]);
},
);
@ -277,9 +267,7 @@ class _VideoChatState extends State<VideoChat>
key: _chatKey,
chatStore: _chatStore,
listPadding: chatOnly
? EdgeInsets.only(
top: context.safePaddingTop,
)
? EdgeInsets.only(top: context.safePaddingTop)
: null,
),
Observer(
@ -324,8 +312,8 @@ class _VideoChatState extends State<VideoChat>
statusBarColor: Colors.transparent,
statusBarIconBrightness:
context.theme.brightness == Brightness.dark
? Brightness.light
: Brightness.dark,
? Brightness.light
: Brightness.dark,
),
title: StreamInfoBar(
streamInfo: streamInfo,
@ -402,79 +390,80 @@ class _VideoChatState extends State<VideoChat>
: context.scaffoldColor,
child: settingsStore.showVideo
? settingsStore.fullScreen
? Stack(
children: [
player,
if (settingsStore.showOverlay)
Row(
children:
settingsStore.landscapeChatLeftSide
? [
overlayChat,
Expanded(child: overlay),
]
: [
Expanded(child: overlay),
overlayChat,
],
),
],
)
: LayoutBuilder(
builder: (context, constraints) {
final totalWidth = constraints.maxWidth;
final chatWidth = _chatStore.expandChat
? 0.5
: _chatStore.settings.chatWidth;
? Stack(
children: [
player,
if (settingsStore.showOverlay)
Row(
children:
settingsStore.landscapeChatLeftSide
? [
overlayChat,
Expanded(child: overlay),
]
: [
Expanded(child: overlay),
overlayChat,
],
),
],
)
: LayoutBuilder(
builder: (context, constraints) {
final totalWidth = constraints.maxWidth;
final chatWidth = _chatStore.expandChat
? 0.5
: _chatStore.settings.chatWidth;
// Create the landscape chat container with proper styling
final chatContainer = AnimatedContainer(
curve: Curves.ease,
duration: const Duration(milliseconds: 200),
width: totalWidth * chatWidth,
color: _chatStore.settings.fullScreen
? Colors.black.withValues(
alpha: _chatStore.settings
.fullScreenChatOverlayOpacity,
)
: context.scaffoldColor,
child: chat,
);
// Create the landscape chat container with proper styling
final chatContainer = AnimatedContainer(
curve: Curves.ease,
duration: const Duration(milliseconds: 200),
width: totalWidth * chatWidth,
color: _chatStore.settings.fullScreen
? Colors.black.withValues(
alpha: _chatStore
.settings
.fullScreenChatOverlayOpacity,
)
: context.scaffoldColor,
child: chat,
);
final draggableDivider = Observer(
builder: (_) => DraggableDivider(
currentWidth: chatWidth,
maxWidth: 0.6,
isResizableOnLeft:
settingsStore.landscapeChatLeftSide,
showHandle: _videoStore.overlayVisible,
onDrag: (newWidth) {
if (!_chatStore.expandChat) {
_chatStore.settings.chatWidth =
newWidth;
}
},
),
);
final draggableDivider = Observer(
builder: (_) => DraggableDivider(
currentWidth: chatWidth,
maxWidth: 0.6,
isResizableOnLeft:
settingsStore.landscapeChatLeftSide,
showHandle: _videoStore.overlayVisible,
onDrag: (newWidth) {
if (!_chatStore.expandChat) {
_chatStore.settings.chatWidth =
newWidth;
}
},
),
);
return SafeArea(
child: Row(
children:
settingsStore.landscapeChatLeftSide
? [
chatContainer,
draggableDivider,
Expanded(child: video),
]
: [
Expanded(child: video),
draggableDivider,
chatContainer,
],
),
);
},
)
return SafeArea(
child: Row(
children:
settingsStore.landscapeChatLeftSide
? [
chatContainer,
draggableDivider,
Expanded(child: video),
]
: [
Expanded(child: video),
draggableDivider,
chatContainer,
],
),
);
},
)
: chat,
);
}
@ -508,9 +497,10 @@ class _VideoChatState extends State<VideoChat>
left: 0,
right: 0,
child: AnimatedBuilder(
animation: Listenable.merge(
[_animationController, _springBackAnimation],
),
animation: Listenable.merge([
_animationController,
_springBackAnimation,
]),
builder: (context, child) {
// Calculate current drag distance from either manual drag or animation
final currentDragDistance = _isPipDragging
@ -518,7 +508,8 @@ class _VideoChatState extends State<VideoChat>
: (_springBackAnimation.value);
// Simple scale effect for visual feedback
final scaleFactor = 1.0 -
final scaleFactor =
1.0 -
(currentDragDistance /
_pipMaxDragDistance *
0.1);
@ -554,8 +545,9 @@ class _VideoChatState extends State<VideoChat>
!_videoStore.isInPipMode)
Positioned.fill(
child: Container(
color: Colors.black
.withValues(alpha: 0.4),
color: Colors.black.withValues(
alpha: 0.4,
),
child: Center(
child: AnimatedOpacity(
opacity: _pipDragDistance > 20

View file

@ -39,8 +39,9 @@ class Chat extends StatelessWidget {
textScaler: chatStore.settings.messageScale.textScaler,
),
child: DefaultTextStyle(
style: context.defaultTextStyle
.copyWith(fontSize: chatStore.settings.fontSize),
style: context.defaultTextStyle.copyWith(
fontSize: chatStore.settings.fontSize,
),
child: Scrollbar(
controller: chatStore.scrollController,
child: Observer(
@ -49,20 +50,23 @@ class Chat extends StatelessWidget {
reverse: true,
padding: (listPadding ?? EdgeInsets.zero).add(
EdgeInsets.only(
bottom: 68 +
bottom:
68 +
(chatStore.assetsStore.showEmoteMenu
? 0
: MediaQuery.of(context)
.padding
.bottom),
: MediaQuery.of(
context,
).padding.bottom),
),
),
addAutomaticKeepAlives: false,
controller: chatStore.scrollController,
itemCount: chatStore.renderMessages.length,
itemBuilder: (context, index) => ChatMessage(
ircMessage: chatStore.renderMessages[
chatStore.renderMessages.length -
ircMessage:
chatStore.renderMessages[chatStore
.renderMessages
.length -
1 -
index],
chatStore: chatStore,
@ -85,7 +89,8 @@ class Chat extends StatelessWidget {
left: 4,
top: 4,
right: 4,
bottom: 68 +
bottom:
68 +
(chatStore.assetsStore.showEmoteMenu
? 0
: MediaQuery.of(context).padding.bottom),
@ -99,8 +104,9 @@ class Chat extends StatelessWidget {
? null
: ElevatedButton.icon(
onPressed: chatStore.resumeScroll,
icon:
const Icon(Icons.arrow_downward_rounded),
icon: const Icon(
Icons.arrow_downward_rounded,
),
label: Text(
chatStore.messageBuffer.isNotEmpty
? '${chatStore.messageBuffer.length} new ${chatStore.messageBuffer.length == 1 ? 'message' : 'messages'}'
@ -159,13 +165,12 @@ class Chat extends StatelessWidget {
if (chatStore.settings.showFFZEmotes) 'FFZ',
],
children: [
RecentEmotesPanel(
chatStore: chatStore,
),
RecentEmotesPanel(chatStore: chatStore),
if (chatStore.settings.showTwitchEmotes)
EmoteMenuPanel(
chatStore: chatStore,
twitchEmotes: chatStore.assetsStore
twitchEmotes: chatStore
.assetsStore
.userEmoteSectionToEmotes,
),
...[

View file

@ -150,8 +150,9 @@ class _ChatDetailsState extends State<ChatDetails> {
/// Returns null if no match is found.
String? _hexToColorName(String hexColor) {
// Remove # if present
final cleanHex =
hexColor.startsWith('#') ? hexColor.substring(1) : hexColor;
final cleanHex = hexColor.startsWith('#')
? hexColor.substring(1)
: hexColor;
// Convert to uppercase for comparison
final upperHex = cleanHex.toUpperCase();
@ -220,9 +221,9 @@ class _ChatDetailsState extends State<ChatDetails> {
}
void _showMessage(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(message)));
}
bool _hasActiveModes() {
@ -251,11 +252,7 @@ class _ChatDetailsState extends State<ChatDetails> {
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ChatModes(roomState: widget.chatDetailsStore.roomState),
),
const Divider(
height: 16,
indent: 16,
endIndent: 16,
),
const Divider(height: 16, indent: 16, endIndent: 16),
],
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
@ -314,8 +311,9 @@ class _ChatDetailsState extends State<ChatDetails> {
icon: Icons.refresh_rounded,
label: 'Reconnect to chat',
onTap: () {
widget.chatStore
.updateNotification('Reconnecting to chat...');
widget.chatStore.updateNotification(
'Reconnecting to chat...',
);
widget.chatStore.connectToChat();
},
),
@ -348,9 +346,8 @@ class _ChatDetailsState extends State<ChatDetails> {
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Settings(
settingsStore: widget.chatStore.settings,
),
builder: (context) =>
Settings(settingsStore: widget.chatStore.settings),
),
),
),
@ -359,11 +356,7 @@ class _ChatDetailsState extends State<ChatDetails> {
),
];
return ListView(
shrinkWrap: true,
primary: false,
children: children,
);
return ListView(shrinkWrap: true, primary: false, children: children);
},
);
}
@ -397,11 +390,7 @@ extension on _ChatDetailsState {
}
class _CardContents extends StatelessWidget {
const _CardContents({
this.icon,
this.label,
this.labelStyle,
});
const _CardContents({this.icon, this.label, this.labelStyle});
final IconData? icon;
final String? label;
@ -417,11 +406,7 @@ class _CardContents extends StatelessWidget {
if (icon != null) Icon(icon),
if (label != null) ...[
const SizedBox(height: 8),
Text(
label!,
textAlign: TextAlign.center,
style: labelStyle,
),
Text(label!, textAlign: TextAlign.center, style: labelStyle),
],
],
),
@ -450,7 +435,7 @@ class _ChatColorPickerModal extends StatefulWidget {
final String? initialColor;
final ScrollController scrollController;
final Future<void> Function(BuildContext context, String color)
onColorSelected;
onColorSelected;
final ChatStore chatStore;
@override
@ -481,10 +466,7 @@ class _ChatColorPickerModalState extends State<_ChatColorPickerModal> {
}
Widget _buildHeader() {
return const SectionHeader(
'Username color',
isFirst: true,
);
return const SectionHeader('Username color', isFirst: true);
}
Widget _buildPreview() {
@ -530,14 +512,7 @@ class _ChatColorPickerModalState extends State<_ChatColorPickerModal> {
tags: mockTags,
user: userDetails.login,
message: 'How it will look in chat',
split: [
'How',
'it',
'will',
'look',
'in',
'chat',
],
split: ['How', 'it', 'will', 'look', 'in', 'chat'],
action: false,
mention: false,
);
@ -587,17 +562,11 @@ class _ChatColorPickerModalState extends State<_ChatColorPickerModal> {
leading: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: adjustedColor,
shape: BoxShape.circle,
),
decoration: BoxDecoration(color: adjustedColor, shape: BoxShape.circle),
),
title: Text(displayName),
trailing: isSelected
? Icon(
Icons.check,
color: Theme.of(context).colorScheme.primary,
)
? Icon(Icons.check, color: Theme.of(context).colorScheme.primary)
: null,
onTap: () => setState(() => selectedColor = colorName),
);

View file

@ -12,13 +12,16 @@ mixin _$ChatDetailsStore on ChatDetailsStoreBase, Store {
Computed<Iterable<String>>? _$filteredUsersComputed;
@override
Iterable<String> get filteredUsers => (_$filteredUsersComputed ??=
Computed<Iterable<String>>(() => super.filteredUsers,
name: 'ChatDetailsStoreBase.filteredUsers'))
.value;
Iterable<String> get filteredUsers =>
(_$filteredUsersComputed ??= Computed<Iterable<String>>(
() => super.filteredUsers,
name: 'ChatDetailsStoreBase.filteredUsers',
)).value;
late final _$roomStateAtom =
Atom(name: 'ChatDetailsStoreBase.roomState', context: context);
late final _$roomStateAtom = Atom(
name: 'ChatDetailsStoreBase.roomState',
context: context,
);
@override
ROOMSTATE get roomState {
@ -33,8 +36,10 @@ mixin _$ChatDetailsStore on ChatDetailsStoreBase, Store {
});
}
late final _$showJumpButtonAtom =
Atom(name: 'ChatDetailsStoreBase.showJumpButton', context: context);
late final _$showJumpButtonAtom = Atom(
name: 'ChatDetailsStoreBase.showJumpButton',
context: context,
);
@override
bool get showJumpButton {
@ -49,8 +54,10 @@ mixin _$ChatDetailsStore on ChatDetailsStoreBase, Store {
});
}
late final _$_filterTextAtom =
Atom(name: 'ChatDetailsStoreBase._filterText', context: context);
late final _$_filterTextAtom = Atom(
name: 'ChatDetailsStoreBase._filterText',
context: context,
);
String get filterText {
_$_filterTextAtom.reportRead();

View file

@ -107,11 +107,7 @@ class ChatModes extends StatelessWidget {
activeModes.sort((a, b) => a.key.compareTo(b.key));
final activeChips = activeModes.map((entry) => entry.value).toList();
return Wrap(
spacing: 8,
runSpacing: -4,
children: activeChips,
);
return Wrap(spacing: 8, runSpacing: -4, children: activeChips);
},
);
}
@ -172,10 +168,9 @@ class ChatModes extends StatelessWidget {
Text(
duration,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.6),
color: Theme.of(
context,
).colorScheme.onSurface.withValues(alpha: 0.6),
),
),
],

View file

@ -46,8 +46,8 @@ class _ChattersListState extends State<ChattersList> {
decoration: InputDecoration(
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
@ -108,11 +108,14 @@ class _ChattersListState extends State<ChattersList> {
if (widget.chatDetailsStore.chatUsers.isEmpty)
const SliverFillRemaining(
hasScrollBody: false,
child:
AlertMessage(message: 'No chatters found'),
child: AlertMessage(
message: 'No chatters found',
),
)
else if (widget
.chatDetailsStore.filteredUsers.isEmpty)
.chatDetailsStore
.filteredUsers
.isEmpty)
const SliverFillRemaining(
hasScrollBody: false,
child: AlertMessage(
@ -133,12 +136,14 @@ class _ChattersListState extends State<ChattersList> {
),
),
onTap: () async {
final userInfo =
await context.read<TwitchApi>().getUser(
userLogin: widget.chatDetailsStore
.filteredUsers
.elementAt(index),
);
final userInfo = await context
.read<TwitchApi>()
.getUser(
userLogin: widget
.chatDetailsStore
.filteredUsers
.elementAt(index),
);
if (!context.mounted) return;
@ -155,7 +160,9 @@ class _ChattersListState extends State<ChattersList> {
},
),
itemCount: widget
.chatDetailsStore.filteredUsers.length,
.chatDetailsStore
.filteredUsers
.length,
),
],
);

View file

@ -42,14 +42,8 @@ class EmoteMenuPanel extends StatelessWidget {
headers: [if (channelEmotes.isNotEmpty) 'Channel', 'Global'],
children: [
if (channelEmotes.isNotEmpty)
EmoteMenuSection(
chatStore: chatStore,
emotes: channelEmotes,
),
EmoteMenuSection(
chatStore: chatStore,
emotes: globalEmotes,
),
EmoteMenuSection(chatStore: chatStore, emotes: channelEmotes),
EmoteMenuSection(chatStore: chatStore, emotes: globalEmotes),
],
);
} else {
@ -60,8 +54,9 @@ class EmoteMenuPanel extends StatelessWidget {
);
return FrostyPageView(
headers:
twitchEmotes!.keys.map((header) => header.split(' ')[0]).toList(),
headers: twitchEmotes!.keys
.map((header) => header.split(' ')[0])
.toList(),
children: twitchEmotes!.entries
.map(
(e) => EmoteMenuSection(

View file

@ -37,8 +37,8 @@ class _EmoteMenuSectionState extends State<EmoteMenuSection>
crossAxisCount: context.isPortrait
? 8
: context.settingsStore.showVideo
? 6
: 16,
? 6
: 16,
),
padding: EdgeInsets.zero,
itemBuilder: (context, index) => InkWell(

View file

@ -13,18 +13,16 @@ import 'package:provider/provider.dart';
class RecentEmotesPanel extends StatelessWidget {
final ChatStore chatStore;
const RecentEmotesPanel({
super.key,
required this.chatStore,
});
const RecentEmotesPanel({super.key, required this.chatStore});
Future<void> _showClearDialog(BuildContext context) {
return showDialog(
context: context,
builder: (context) => AlertDialog.adaptive(
title: const Text('Clear recent emotes'),
content:
const Text('Are you sure you want to clear your recent emotes?'),
content: const Text(
'Are you sure you want to clear your recent emotes?',
),
actions: [
TextButton(
onPressed: Navigator.of(context).pop,
@ -64,8 +62,8 @@ class RecentEmotesPanel extends StatelessWidget {
crossAxisCount: context.isPortrait
? 8
: context.read<SettingsStore>().showVideo
? 6
: 16,
? 6
: 16,
),
itemBuilder: (context, index) {
final emote = chatStore.assetsStore.recentEmotes[index];

View file

@ -105,7 +105,8 @@ abstract class ChatAssetsStoreBase with Store {
// Retrieve the instance that will allow us to retrieve local search history.
final prefs = await SharedPreferences.getInstance();
_recentEmotes = prefs
_recentEmotes =
prefs
.getStringList('recent_emotes')
?.map((emoteJson) => Emote.fromJson(jsonDecode(emoteJson)))
.toList()
@ -142,40 +143,53 @@ abstract class ChatAssetsStoreBase with Store {
// Emotes
Future.wait([
Future.wait([
if (showTwitchEmotes) ...[
twitchApi.getEmotesGlobal().catchError(onEmoteError),
twitchApi.getEmotesChannel(id: channelId).then((emotes) {
_userEmoteSectionToEmotes.update(
'Channel Emotes',
(existingEmoteSet) => [...existingEmoteSet, ...emotes],
ifAbsent: () => emotes.toList(),
);
if (showTwitchEmotes) ...[
twitchApi.getEmotesGlobal().catchError(onEmoteError),
twitchApi
.getEmotesChannel(id: channelId)
.then((emotes) {
_userEmoteSectionToEmotes.update(
'Channel Emotes',
(existingEmoteSet) => [...existingEmoteSet, ...emotes],
ifAbsent: () => emotes.toList(),
);
return emotes;
}).catchError(onEmoteError),
],
if (show7TVEmotes) ...[
sevenTVApi.getEmotesGlobal().catchError(onEmoteError),
sevenTVApi.getEmotesChannel(id: channelId).then((data) {
final (setId, emotes) = data;
sevenTvEmoteSetId = setId;
return emotes;
}).catchError(onEmoteError),
],
if (showBTTVEmotes) ...[
bttvApi.getEmotesGlobal().catchError(onEmoteError),
bttvApi.getEmotesChannel(id: channelId).catchError(onEmoteError),
],
if (showFFZEmotes) ...[
ffzApi.getEmotesGlobal().catchError(onEmoteError),
ffzApi.getRoomInfo(id: channelId).then((ffzRoom) {
final (roomInfo, emotes) = ffzRoom;
return emotes;
})
.catchError(onEmoteError),
],
if (show7TVEmotes) ...[
sevenTVApi.getEmotesGlobal().catchError(onEmoteError),
sevenTVApi
.getEmotesChannel(id: channelId)
.then((data) {
final (setId, emotes) = data;
sevenTvEmoteSetId = setId;
return emotes;
})
.catchError(onEmoteError),
],
if (showBTTVEmotes) ...[
bttvApi.getEmotesGlobal().catchError(onEmoteError),
bttvApi
.getEmotesChannel(id: channelId)
.catchError(onEmoteError),
],
if (showFFZEmotes) ...[
ffzApi.getEmotesGlobal().catchError(onEmoteError),
ffzApi
.getRoomInfo(id: channelId)
.then((ffzRoom) {
final (roomInfo, emotes) = ffzRoom;
ffzRoomInfo = roomInfo;
return emotes;
}).catchError(onEmoteError),
],
]).then((assets) => assets.expand((list) => list)).then(
ffzRoomInfo = roomInfo;
return emotes;
})
.catchError(onEmoteError),
],
])
.then((assets) => assets.expand((list) => list))
.then(
(emotes) => _emoteToObject = {
for (final emote in emotes) emote.name: emote,
}.asObservable(),
@ -183,18 +197,17 @@ abstract class ChatAssetsStoreBase with Store {
// Badges
Future.wait([
twitchApi
.getSharedChatSession(
broadcasterId: channelId,
)
.getSharedChatSession(broadcasterId: channelId)
.then((sharedChatSession) {
if (sharedChatSession == null) return;
if (sharedChatSession == null) return;
for (final participant in sharedChatSession.participants) {
twitchApi.getUser(id: participant.broadcasterId).then((user) {
channelIdToUserTwitch[participant.broadcasterId] = user;
});
}
}).catchError(onBadgeError),
for (final participant in sharedChatSession.participants) {
twitchApi.getUser(id: participant.broadcasterId).then((user) {
channelIdToUserTwitch[participant.broadcasterId] = user;
});
}
})
.catchError(onBadgeError),
// Get global badges first, then channel badges to avoid badge conflicts.
// We want the channel badges to override the global badges.
if (showTwitchBadges)
@ -245,9 +258,7 @@ abstract class ChatAssetsStoreBase with Store {
ifAbsent: () => emoteSet,
);
} else {
final owner = await twitchApi.getUser(
id: ownerId,
);
final owner = await twitchApi.getUser(id: ownerId);
_userEmoteSectionToEmotes.update(
owner.displayName,
(existingEmoteSet) => [...existingEmoteSet, ...emoteSet],

View file

@ -12,27 +12,30 @@ mixin _$ChatAssetsStore on ChatAssetsStoreBase, Store {
Computed<List<Emote>>? _$bttvEmotesComputed;
@override
List<Emote> get bttvEmotes =>
(_$bttvEmotesComputed ??= Computed<List<Emote>>(() => super.bttvEmotes,
name: 'ChatAssetsStoreBase.bttvEmotes'))
.value;
List<Emote> get bttvEmotes => (_$bttvEmotesComputed ??= Computed<List<Emote>>(
() => super.bttvEmotes,
name: 'ChatAssetsStoreBase.bttvEmotes',
)).value;
Computed<List<Emote>>? _$ffzEmotesComputed;
@override
List<Emote> get ffzEmotes =>
(_$ffzEmotesComputed ??= Computed<List<Emote>>(() => super.ffzEmotes,
name: 'ChatAssetsStoreBase.ffzEmotes'))
.value;
List<Emote> get ffzEmotes => (_$ffzEmotesComputed ??= Computed<List<Emote>>(
() => super.ffzEmotes,
name: 'ChatAssetsStoreBase.ffzEmotes',
)).value;
Computed<List<Emote>>? _$sevenTVEmotesComputed;
@override
List<Emote> get sevenTVEmotes => (_$sevenTVEmotesComputed ??=
Computed<List<Emote>>(() => super.sevenTVEmotes,
name: 'ChatAssetsStoreBase.sevenTVEmotes'))
.value;
List<Emote> get sevenTVEmotes =>
(_$sevenTVEmotesComputed ??= Computed<List<Emote>>(
() => super.sevenTVEmotes,
name: 'ChatAssetsStoreBase.sevenTVEmotes',
)).value;
late final _$_recentEmotesAtom =
Atom(name: 'ChatAssetsStoreBase._recentEmotes', context: context);
late final _$_recentEmotesAtom = Atom(
name: 'ChatAssetsStoreBase._recentEmotes',
context: context,
);
ObservableList<Emote> get recentEmotes {
_$_recentEmotesAtom.reportRead();
@ -49,8 +52,10 @@ mixin _$ChatAssetsStore on ChatAssetsStoreBase, Store {
});
}
late final _$_emoteToObjectAtom =
Atom(name: 'ChatAssetsStoreBase._emoteToObject', context: context);
late final _$_emoteToObjectAtom = Atom(
name: 'ChatAssetsStoreBase._emoteToObject',
context: context,
);
ObservableMap<String, Emote> get emoteToObject {
_$_emoteToObjectAtom.reportRead();
@ -67,8 +72,10 @@ mixin _$ChatAssetsStore on ChatAssetsStoreBase, Store {
});
}
late final _$_userEmoteToObjectAtom =
Atom(name: 'ChatAssetsStoreBase._userEmoteToObject', context: context);
late final _$_userEmoteToObjectAtom = Atom(
name: 'ChatAssetsStoreBase._userEmoteToObject',
context: context,
);
Map<String, Emote> get userEmoteToObject {
_$_userEmoteToObjectAtom.reportRead();
@ -86,7 +93,9 @@ mixin _$ChatAssetsStore on ChatAssetsStoreBase, Store {
}
late final _$_userEmoteSectionToEmotesAtom = Atom(
name: 'ChatAssetsStoreBase._userEmoteSectionToEmotes', context: context);
name: 'ChatAssetsStoreBase._userEmoteSectionToEmotes',
context: context,
);
Map<String, List<Emote>> get userEmoteSectionToEmotes {
_$_userEmoteSectionToEmotesAtom.reportRead();
@ -99,14 +108,19 @@ mixin _$ChatAssetsStore on ChatAssetsStoreBase, Store {
@override
set _userEmoteSectionToEmotes(Map<String, List<Emote>> value) {
_$_userEmoteSectionToEmotesAtom
.reportWrite(value, super._userEmoteSectionToEmotes, () {
super._userEmoteSectionToEmotes = value;
});
_$_userEmoteSectionToEmotesAtom.reportWrite(
value,
super._userEmoteSectionToEmotes,
() {
super._userEmoteSectionToEmotes = value;
},
);
}
late final _$_userToFFZBadgesAtom =
Atom(name: 'ChatAssetsStoreBase._userToFFZBadges', context: context);
late final _$_userToFFZBadgesAtom = Atom(
name: 'ChatAssetsStoreBase._userToFFZBadges',
context: context,
);
Map<String, List<ChatBadge>> get userToFFZBadges {
_$_userToFFZBadgesAtom.reportRead();
@ -123,8 +137,10 @@ mixin _$ChatAssetsStore on ChatAssetsStoreBase, Store {
});
}
late final _$_userTo7TVBadgesAtom =
Atom(name: 'ChatAssetsStoreBase._userTo7TVBadges', context: context);
late final _$_userTo7TVBadgesAtom = Atom(
name: 'ChatAssetsStoreBase._userTo7TVBadges',
context: context,
);
Map<String, List<ChatBadge>> get userTo7TVBadges {
_$_userTo7TVBadgesAtom.reportRead();
@ -141,8 +157,10 @@ mixin _$ChatAssetsStore on ChatAssetsStoreBase, Store {
});
}
late final _$_userToBTTVBadgesAtom =
Atom(name: 'ChatAssetsStoreBase._userToBTTVBadges', context: context);
late final _$_userToBTTVBadgesAtom = Atom(
name: 'ChatAssetsStoreBase._userToBTTVBadges',
context: context,
);
Map<String, ChatBadge> get userToBTTVBadges {
_$_userToBTTVBadgesAtom.reportRead();
@ -159,8 +177,10 @@ mixin _$ChatAssetsStore on ChatAssetsStoreBase, Store {
});
}
late final _$showEmoteMenuAtom =
Atom(name: 'ChatAssetsStoreBase.showEmoteMenu', context: context);
late final _$showEmoteMenuAtom = Atom(
name: 'ChatAssetsStoreBase.showEmoteMenu',
context: context,
);
@override
bool get showEmoteMenu {
@ -175,57 +195,72 @@ mixin _$ChatAssetsStore on ChatAssetsStoreBase, Store {
});
}
late final _$initAsyncAction =
AsyncAction('ChatAssetsStoreBase.init', context: context);
late final _$initAsyncAction = AsyncAction(
'ChatAssetsStoreBase.init',
context: context,
);
@override
Future<void> init() {
return _$initAsyncAction.run(() => super.init());
}
late final _$userEmotesFutureAsyncAction =
AsyncAction('ChatAssetsStoreBase.userEmotesFuture', context: context);
late final _$userEmotesFutureAsyncAction = AsyncAction(
'ChatAssetsStoreBase.userEmotesFuture',
context: context,
);
@override
Future<void> userEmotesFuture(
{required List<String> emoteSets,
required Map<String, String> headers,
required Function onError}) {
return _$userEmotesFutureAsyncAction.run(() => super.userEmotesFuture(
emoteSets: emoteSets, headers: headers, onError: onError));
Future<void> userEmotesFuture({
required List<String> emoteSets,
required Map<String, String> headers,
required Function onError,
}) {
return _$userEmotesFutureAsyncAction.run(
() => super.userEmotesFuture(
emoteSets: emoteSets,
headers: headers,
onError: onError,
),
);
}
late final _$ChatAssetsStoreBaseActionController =
ActionController(name: 'ChatAssetsStoreBase', context: context);
late final _$ChatAssetsStoreBaseActionController = ActionController(
name: 'ChatAssetsStoreBase',
context: context,
);
@override
Future<void> assetsFuture(
{required String channelId,
required Map<String, String> headers,
required Function onEmoteError,
required Function onBadgeError,
bool showTwitchEmotes = true,
bool showTwitchBadges = true,
bool show7TVEmotes = true,
bool showBTTVEmotes = true,
bool showBTTVBadges = true,
bool showFFZEmotes = true,
bool showFFZBadges = true}) {
Future<void> assetsFuture({
required String channelId,
required Map<String, String> headers,
required Function onEmoteError,
required Function onBadgeError,
bool showTwitchEmotes = true,
bool showTwitchBadges = true,
bool show7TVEmotes = true,
bool showBTTVEmotes = true,
bool showBTTVBadges = true,
bool showFFZEmotes = true,
bool showFFZBadges = true,
}) {
final _$actionInfo = _$ChatAssetsStoreBaseActionController.startAction(
name: 'ChatAssetsStoreBase.assetsFuture');
name: 'ChatAssetsStoreBase.assetsFuture',
);
try {
return super.assetsFuture(
channelId: channelId,
headers: headers,
onEmoteError: onEmoteError,
onBadgeError: onBadgeError,
showTwitchEmotes: showTwitchEmotes,
showTwitchBadges: showTwitchBadges,
show7TVEmotes: show7TVEmotes,
showBTTVEmotes: showBTTVEmotes,
showBTTVBadges: showBTTVBadges,
showFFZEmotes: showFFZEmotes,
showFFZBadges: showFFZBadges);
channelId: channelId,
headers: headers,
onEmoteError: onEmoteError,
onBadgeError: onBadgeError,
showTwitchEmotes: showTwitchEmotes,
showTwitchBadges: showTwitchBadges,
show7TVEmotes: show7TVEmotes,
showBTTVEmotes: showBTTVEmotes,
showBTTVBadges: showBTTVBadges,
showFFZEmotes: showFFZEmotes,
showFFZBadges: showFFZBadges,
);
} finally {
_$ChatAssetsStoreBaseActionController.endAction(_$actionInfo);
}

View file

@ -190,10 +190,7 @@ abstract class ChatStoreBase with Store {
// Create a reaction that will reconnect to chat when logging in or out.
// Closing the channel will trigger a reconnect with the new credentials.
reactions.add(
reaction(
(_) => auth.isLoggedIn,
(_) => _channel?.sink.close(1000),
),
reaction((_) => auth.isLoggedIn, (_) => _channel?.sink.close(1000)),
);
reactions.add(
@ -238,27 +235,22 @@ abstract class ChatStoreBase with Store {
}
reactions.add(
reaction(
(_) => settings.showVideo,
(showVideo) {
if (settings.chatDelay > 0) {
if (showVideo) {
_messages.add(
IRCMessage.createNotice(
message:
'Waiting ${settings.chatDelay.toInt()} ${settings.chatDelay == 1.0 ? 'second' : 'seconds'} due to message delay setting...',
),
);
} else {
_messages.add(
IRCMessage.createNotice(
message: 'Removing message delay...',
),
);
}
reaction((_) => settings.showVideo, (showVideo) {
if (settings.chatDelay > 0) {
if (showVideo) {
_messages.add(
IRCMessage.createNotice(
message:
'Waiting ${settings.chatDelay.toInt()} ${settings.chatDelay == 1.0 ? 'second' : 'seconds'} due to message delay setting...',
),
);
} else {
_messages.add(
IRCMessage.createNotice(message: 'Removing message delay...'),
);
}
},
),
}
}),
);
if (settings.showRecentMessages) {
@ -293,12 +285,14 @@ abstract class ChatStoreBase with Store {
textController.addListener(() {
_inputText = textController.text;
_showEmoteAutocomplete = !_showMentionAutocomplete &&
_showEmoteAutocomplete =
!_showMentionAutocomplete &&
textFieldFocusNode.hasFocus &&
textController.text.split(' ').last.isNotEmpty;
_showSendButton = textController.text.isNotEmpty;
_showMentionAutocomplete = textFieldFocusNode.hasFocus &&
_showMentionAutocomplete =
textFieldFocusNode.hasFocus &&
textController.text.split(' ').last.startsWith('@');
});
}
@ -314,8 +308,10 @@ 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,
);
if (parsedIRCMessage.user != null) {
chatDetailsStore.chatUsers.add(parsedIRCMessage.user!);
@ -353,8 +349,9 @@ abstract class ChatStoreBase with Store {
case Command.notice:
case Command.userNotice:
// Update shared chat mode based on source-room-id tag presence
_isInSharedChatMode =
parsedIRCMessage.tags.containsKey('source-room-id');
_isInSharedChatMode = parsedIRCMessage.tags.containsKey(
'source-room-id',
);
messageBuffer.add(parsedIRCMessage);
break;
case Command.clearChat:
@ -372,8 +369,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);
@ -455,24 +452,24 @@ abstract class ChatStoreBase with Store {
// Fetch the assets used in chat including badges and emotes.
@action
Future<void> getAssets() async => assetsStore.assetsFuture(
channelId: channelId,
headers: auth.headersTwitch,
onEmoteError: (error) {
debugPrint(error.toString());
return <Emote>[];
},
onBadgeError: (error) {
debugPrint(error.toString());
return <ChatBadge>[];
},
showTwitchEmotes: settings.showTwitchEmotes,
showTwitchBadges: settings.showTwitchBadges,
show7TVEmotes: settings.show7TVEmotes,
showBTTVEmotes: settings.showBTTVEmotes,
showBTTVBadges: settings.showBTTVBadges,
showFFZEmotes: settings.showFFZEmotes,
showFFZBadges: settings.showFFZBadges,
);
channelId: channelId,
headers: auth.headersTwitch,
onEmoteError: (error) {
debugPrint(error.toString());
return <Emote>[];
},
onBadgeError: (error) {
debugPrint(error.toString());
return <ChatBadge>[];
},
showTwitchEmotes: settings.showTwitchEmotes,
showTwitchBadges: settings.showTwitchBadges,
show7TVEmotes: settings.show7TVEmotes,
showBTTVEmotes: settings.showBTTVEmotes,
showBTTVBadges: settings.showBTTVBadges,
showFFZEmotes: settings.showFFZEmotes,
showFFZBadges: settings.showFFZBadges,
);
/// Re-enables [_autoScroll] and jumps to the latest message.
@action
@ -499,8 +496,9 @@ abstract class ChatStoreBase with Store {
);
_sevenTVChannel?.sink.close(1000);
_sevenTVChannel =
WebSocketChannel.connect(Uri.parse('wss://events.7tv.io/v3'));
_sevenTVChannel = WebSocketChannel.connect(
Uri.parse('wss://events.7tv.io/v3'),
);
void listener(dynamic data) {
// debugPrint(data);
@ -567,8 +565,9 @@ abstract class ChatStoreBase with Store {
@action
void connectToChat() {
_channel?.sink.close(1000);
_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(
@ -591,9 +590,7 @@ abstract class ChatStoreBase with Store {
if (_retries >= _maxRetries) {
messageBuffer.add(
IRCMessage.createNotice(
message: 'Disconnected from chat',
),
IRCMessage.createNotice(message: 'Disconnected from chat'),
);
return;
}
@ -762,8 +759,10 @@ abstract class ChatStoreBase with Store {
// Set the new notification message and create a new timer that will dismiss it after 3 seconds.
_notification = notificationMessage;
_notificationTimer =
Timer(const Duration(seconds: 5), () => _notification = null);
_notificationTimer = Timer(
const Duration(seconds: 5),
() => _notification = null,
);
}
/// Clears the current notification immediately.
@ -787,20 +786,17 @@ abstract class ChatStoreBase with Store {
timeRemaining = duration;
// Set a periodic timer that will update the time remaining every second.
sleepTimer = Timer.periodic(
const Duration(seconds: 1),
(timer) {
// If the timer is up, cancel the timer and exit the app.
if (timeRemaining.inSeconds == 0) {
timer.cancel();
onTimerFinished();
return;
}
sleepTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
// If the timer is up, cancel the timer and exit the app.
if (timeRemaining.inSeconds == 0) {
timer.cancel();
onTimerFinished();
return;
}
// Decrement the time remaining.
timeRemaining = Duration(seconds: timeRemaining.inSeconds - 1);
},
);
// Decrement the time remaining.
timeRemaining = Duration(seconds: timeRemaining.inSeconds - 1);
});
}
/// Cancels the sleep timer and resets the time remaining.
@ -812,8 +808,9 @@ abstract class ChatStoreBase with Store {
@action
Future<void> getRecentMessage() async {
final recentMessages =
await twitchApi.getRecentMessages(userLogin: channelName);
final recentMessages = await twitchApi.getRecentMessages(
userLogin: channelName,
);
for (final message in recentMessages) {
_handleIRCData(message);

View file

@ -12,13 +12,16 @@ mixin _$ChatStore on ChatStoreBase, Store {
Computed<List<IRCMessage>>? _$renderMessagesComputed;
@override
List<IRCMessage> get renderMessages => (_$renderMessagesComputed ??=
Computed<List<IRCMessage>>(() => super.renderMessages,
name: 'ChatStoreBase.renderMessages'))
.value;
List<IRCMessage> get renderMessages =>
(_$renderMessagesComputed ??= Computed<List<IRCMessage>>(
() => super.renderMessages,
name: 'ChatStoreBase.renderMessages',
)).value;
late final _$timeRemainingAtom =
Atom(name: 'ChatStoreBase.timeRemaining', context: context);
late final _$timeRemainingAtom = Atom(
name: 'ChatStoreBase.timeRemaining',
context: context,
);
@override
Duration get timeRemaining {
@ -33,8 +36,10 @@ mixin _$ChatStore on ChatStoreBase, Store {
});
}
late final _$_notificationAtom =
Atom(name: 'ChatStoreBase._notification', context: context);
late final _$_notificationAtom = Atom(
name: 'ChatStoreBase._notification',
context: context,
);
String? get notification {
_$_notificationAtom.reportRead();
@ -51,8 +56,10 @@ mixin _$ChatStore on ChatStoreBase, Store {
});
}
late final _$_messagesAtom =
Atom(name: 'ChatStoreBase._messages', context: context);
late final _$_messagesAtom = Atom(
name: 'ChatStoreBase._messages',
context: context,
);
ObservableList<IRCMessage> get messages {
_$_messagesAtom.reportRead();
@ -69,8 +76,10 @@ mixin _$ChatStore on ChatStoreBase, Store {
});
}
late final _$_autoScrollAtom =
Atom(name: 'ChatStoreBase._autoScroll', context: context);
late final _$_autoScrollAtom = Atom(
name: 'ChatStoreBase._autoScroll',
context: context,
);
bool get autoScroll {
_$_autoScrollAtom.reportRead();
@ -87,8 +96,10 @@ mixin _$ChatStore on ChatStoreBase, Store {
});
}
late final _$_inputTextAtom =
Atom(name: 'ChatStoreBase._inputText', context: context);
late final _$_inputTextAtom = Atom(
name: 'ChatStoreBase._inputText',
context: context,
);
String get inputText {
_$_inputTextAtom.reportRead();
@ -105,8 +116,10 @@ mixin _$ChatStore on ChatStoreBase, Store {
});
}
late final _$_showSendButtonAtom =
Atom(name: 'ChatStoreBase._showSendButton', context: context);
late final _$_showSendButtonAtom = Atom(
name: 'ChatStoreBase._showSendButton',
context: context,
);
bool get showSendButton {
_$_showSendButtonAtom.reportRead();
@ -123,8 +136,10 @@ mixin _$ChatStore on ChatStoreBase, Store {
});
}
late final _$_showEmoteAutocompleteAtom =
Atom(name: 'ChatStoreBase._showEmoteAutocomplete', context: context);
late final _$_showEmoteAutocompleteAtom = Atom(
name: 'ChatStoreBase._showEmoteAutocomplete',
context: context,
);
bool get showEmoteAutocomplete {
_$_showEmoteAutocompleteAtom.reportRead();
@ -136,14 +151,19 @@ mixin _$ChatStore on ChatStoreBase, Store {
@override
set _showEmoteAutocomplete(bool value) {
_$_showEmoteAutocompleteAtom
.reportWrite(value, super._showEmoteAutocomplete, () {
super._showEmoteAutocomplete = value;
});
_$_showEmoteAutocompleteAtom.reportWrite(
value,
super._showEmoteAutocomplete,
() {
super._showEmoteAutocomplete = value;
},
);
}
late final _$_showMentionAutocompleteAtom =
Atom(name: 'ChatStoreBase._showMentionAutocomplete', context: context);
late final _$_showMentionAutocompleteAtom = Atom(
name: 'ChatStoreBase._showMentionAutocomplete',
context: context,
);
bool get showMentionAutocomplete {
_$_showMentionAutocompleteAtom.reportRead();
@ -155,14 +175,19 @@ mixin _$ChatStore on ChatStoreBase, Store {
@override
set _showMentionAutocomplete(bool value) {
_$_showMentionAutocompleteAtom
.reportWrite(value, super._showMentionAutocomplete, () {
super._showMentionAutocomplete = value;
});
_$_showMentionAutocompleteAtom.reportWrite(
value,
super._showMentionAutocomplete,
() {
super._showMentionAutocomplete = value;
},
);
}
late final _$_isSendingMessageAtom =
Atom(name: 'ChatStoreBase._isSendingMessage', context: context);
late final _$_isSendingMessageAtom = Atom(
name: 'ChatStoreBase._isSendingMessage',
context: context,
);
bool get isSendingMessage {
_$_isSendingMessageAtom.reportRead();
@ -179,8 +204,10 @@ mixin _$ChatStore on ChatStoreBase, Store {
});
}
late final _$_isInSharedChatModeAtom =
Atom(name: 'ChatStoreBase._isInSharedChatMode', context: context);
late final _$_isInSharedChatModeAtom = Atom(
name: 'ChatStoreBase._isInSharedChatMode',
context: context,
);
bool get isInSharedChatMode {
_$_isInSharedChatModeAtom.reportRead();
@ -197,8 +224,10 @@ mixin _$ChatStore on ChatStoreBase, Store {
});
}
late final _$_userStateAtom =
Atom(name: 'ChatStoreBase._userState', context: context);
late final _$_userStateAtom = Atom(
name: 'ChatStoreBase._userState',
context: context,
);
USERSTATE get userState {
_$_userStateAtom.reportRead();
@ -215,8 +244,10 @@ mixin _$ChatStore on ChatStoreBase, Store {
});
}
late final _$expandChatAtom =
Atom(name: 'ChatStoreBase.expandChat', context: context);
late final _$expandChatAtom = Atom(
name: 'ChatStoreBase.expandChat',
context: context,
);
@override
bool get expandChat {
@ -231,8 +262,10 @@ mixin _$ChatStore on ChatStoreBase, Store {
});
}
late final _$replyingToMessageAtom =
Atom(name: 'ChatStoreBase.replyingToMessage', context: context);
late final _$replyingToMessageAtom = Atom(
name: 'ChatStoreBase.replyingToMessage',
context: context,
);
@override
IRCMessage? get replyingToMessage {
@ -247,29 +280,36 @@ mixin _$ChatStore on ChatStoreBase, Store {
});
}
late final _$getAssetsAsyncAction =
AsyncAction('ChatStoreBase.getAssets', context: context);
late final _$getAssetsAsyncAction = AsyncAction(
'ChatStoreBase.getAssets',
context: context,
);
@override
Future<void> getAssets() {
return _$getAssetsAsyncAction.run(() => super.getAssets());
}
late final _$getRecentMessageAsyncAction =
AsyncAction('ChatStoreBase.getRecentMessage', context: context);
late final _$getRecentMessageAsyncAction = AsyncAction(
'ChatStoreBase.getRecentMessage',
context: context,
);
@override
Future<void> getRecentMessage() {
return _$getRecentMessageAsyncAction.run(() => super.getRecentMessage());
}
late final _$ChatStoreBaseActionController =
ActionController(name: 'ChatStoreBase', context: context);
late final _$ChatStoreBaseActionController = ActionController(
name: 'ChatStoreBase',
context: context,
);
@override
void _handleIRCData(String data) {
final _$actionInfo = _$ChatStoreBaseActionController.startAction(
name: 'ChatStoreBase._handleIRCData');
name: 'ChatStoreBase._handleIRCData',
);
try {
return super._handleIRCData(data);
} finally {
@ -280,7 +320,8 @@ mixin _$ChatStore on ChatStoreBase, Store {
@override
void resumeScroll() {
final _$actionInfo = _$ChatStoreBaseActionController.startAction(
name: 'ChatStoreBase.resumeScroll');
name: 'ChatStoreBase.resumeScroll',
);
try {
return super.resumeScroll();
} finally {
@ -291,7 +332,8 @@ mixin _$ChatStore on ChatStoreBase, Store {
@override
void listenToSevenTVEmoteSet({required String emoteSetId}) {
final _$actionInfo = _$ChatStoreBaseActionController.startAction(
name: 'ChatStoreBase.listenToSevenTVEmoteSet');
name: 'ChatStoreBase.listenToSevenTVEmoteSet',
);
try {
return super.listenToSevenTVEmoteSet(emoteSetId: emoteSetId);
} finally {
@ -302,7 +344,8 @@ mixin _$ChatStore on ChatStoreBase, Store {
@override
void connectToChat() {
final _$actionInfo = _$ChatStoreBaseActionController.startAction(
name: 'ChatStoreBase.connectToChat');
name: 'ChatStoreBase.connectToChat',
);
try {
return super.connectToChat();
} finally {
@ -313,7 +356,8 @@ mixin _$ChatStore on ChatStoreBase, Store {
@override
void addMessages() {
final _$actionInfo = _$ChatStoreBaseActionController.startAction(
name: 'ChatStoreBase.addMessages');
name: 'ChatStoreBase.addMessages',
);
try {
return super.addMessages();
} finally {
@ -324,7 +368,8 @@ mixin _$ChatStore on ChatStoreBase, Store {
@override
void sendMessage(String message) {
final _$actionInfo = _$ChatStoreBaseActionController.startAction(
name: 'ChatStoreBase.sendMessage');
name: 'ChatStoreBase.sendMessage',
);
try {
return super.sendMessage(message);
} finally {
@ -335,7 +380,8 @@ mixin _$ChatStore on ChatStoreBase, Store {
@override
void addEmote(Emote emote, {bool autocompleteMode = false}) {
final _$actionInfo = _$ChatStoreBaseActionController.startAction(
name: 'ChatStoreBase.addEmote');
name: 'ChatStoreBase.addEmote',
);
try {
return super.addEmote(emote, autocompleteMode: autocompleteMode);
} finally {
@ -346,7 +392,8 @@ mixin _$ChatStore on ChatStoreBase, Store {
@override
void updateNotification(String notificationMessage) {
final _$actionInfo = _$ChatStoreBaseActionController.startAction(
name: 'ChatStoreBase.updateNotification');
name: 'ChatStoreBase.updateNotification',
);
try {
return super.updateNotification(notificationMessage);
} finally {
@ -357,7 +404,8 @@ mixin _$ChatStore on ChatStoreBase, Store {
@override
void clearNotification() {
final _$actionInfo = _$ChatStoreBaseActionController.startAction(
name: 'ChatStoreBase.clearNotification');
name: 'ChatStoreBase.clearNotification',
);
try {
return super.clearNotification();
} finally {
@ -366,13 +414,18 @@ mixin _$ChatStore on ChatStoreBase, Store {
}
@override
void updateSleepTimer(
{required Duration duration, required VoidCallback onTimerFinished}) {
void updateSleepTimer({
required Duration duration,
required VoidCallback onTimerFinished,
}) {
final _$actionInfo = _$ChatStoreBaseActionController.startAction(
name: 'ChatStoreBase.updateSleepTimer');
name: 'ChatStoreBase.updateSleepTimer',
);
try {
return super.updateSleepTimer(
duration: duration, onTimerFinished: onTimerFinished);
duration: duration,
onTimerFinished: onTimerFinished,
);
} finally {
_$ChatStoreBaseActionController.endAction(_$actionInfo);
}
@ -381,7 +434,8 @@ mixin _$ChatStore on ChatStoreBase, Store {
@override
void cancelSleepTimer() {
final _$actionInfo = _$ChatStoreBaseActionController.startAction(
name: 'ChatStoreBase.cancelSleepTimer');
name: 'ChatStoreBase.cancelSleepTimer',
);
try {
return super.cancelSleepTimer();
} finally {
@ -392,7 +446,8 @@ mixin _$ChatStore on ChatStoreBase, Store {
@override
void unfocusInput() {
final _$actionInfo = _$ChatStoreBaseActionController.startAction(
name: 'ChatStoreBase.unfocusInput');
name: 'ChatStoreBase.unfocusInput',
);
try {
return super.unfocusInput();
} finally {
@ -403,7 +458,8 @@ mixin _$ChatStore on ChatStoreBase, Store {
@override
void safeRequestFocus() {
final _$actionInfo = _$ChatStoreBaseActionController.startAction(
name: 'ChatStoreBase.safeRequestFocus');
name: 'ChatStoreBase.safeRequestFocus',
);
try {
return super.safeRequestFocus();
} finally {

View file

@ -18,7 +18,8 @@ class ChatBottomBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isEmotesEnabled = chatStore.settings.showTwitchEmotes ||
final isEmotesEnabled =
chatStore.settings.showTwitchEmotes ||
chatStore.settings.show7TVEmotes ||
chatStore.settings.showBTTVEmotes ||
chatStore.settings.showFFZEmotes;
@ -47,18 +48,19 @@ class ChatBottomBar extends StatelessWidget {
return Observer(
builder: (context) {
final matchingEmotes = [
...chatStore.assetsStore.userEmoteToObject.values,
...chatStore.assetsStore.bttvEmotes,
...chatStore.assetsStore.ffzEmotes,
...chatStore.assetsStore.sevenTVEmotes,
]
.where(
(emote) => emote.name.toLowerCase().contains(
final matchingEmotes =
[
...chatStore.assetsStore.userEmoteToObject.values,
...chatStore.assetsStore.bttvEmotes,
...chatStore.assetsStore.ffzEmotes,
...chatStore.assetsStore.sevenTVEmotes,
]
.where(
(emote) => emote.name.toLowerCase().contains(
chatStore.inputText.split(' ').last.toLowerCase(),
),
)
.toList();
)
.toList();
final matchingChatters = chatStore.chatDetailsStore.chatUsers
.where(
@ -106,8 +108,9 @@ class ChatBottomBar extends StatelessWidget {
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: context.defaultTextStyle
.copyWith(fontSize: chatStore.settings.fontSize),
style: context.defaultTextStyle.copyWith(
fontSize: chatStore.settings.fontSize,
),
),
),
trailing: IconButton(
@ -149,7 +152,8 @@ class ChatBottomBar extends StatelessWidget {
child: FrostyCachedNetworkImage(
imageUrl: matchingEmotes[index].url,
useFade: false,
height: matchingEmotes[index].height?.toDouble() ??
height:
matchingEmotes[index].height?.toDouble() ??
defaultEmoteSize,
width: matchingEmotes[index].width?.toDouble(),
),
@ -181,10 +185,10 @@ class ChatBottomBar extends StatelessWidget {
chatStore.textController.text = split.join(' ');
chatStore.textController.selection =
TextSelection.fromPosition(
TextPosition(
offset: chatStore.textController.text.length,
),
);
TextPosition(
offset: chatStore.textController.text.length,
),
);
},
child: Text(matchingChatters[index]),
),
@ -213,8 +217,9 @@ class ChatBottomBar extends StatelessWidget {
builder: (context) {
final isDisabledDueToDelay =
chatStore.settings.showVideo &&
chatStore.settings.chatDelay > 0;
final isDisabled = !chatStore.auth.isLoggedIn ||
chatStore.settings.chatDelay > 0;
final isDisabled =
!chatStore.auth.isLoggedIn ||
chatStore.isSendingMessage ||
isDisabledDueToDelay;
@ -237,14 +242,15 @@ class ChatBottomBar extends StatelessWidget {
decoration: InputDecoration(
prefixIcon:
chatStore.settings.emoteMenuButtonOnLeft
? emoteMenuButton
: null,
? emoteMenuButton
: null,
suffixIcon: Row(
mainAxisSize: MainAxisSize.min,
spacing: 8,
children: [
if (!chatStore
.settings.emoteMenuButtonOnLeft &&
.settings
.emoteMenuButtonOnLeft &&
emoteMenuButton != null)
emoteMenuButton,
],
@ -252,10 +258,11 @@ class ChatBottomBar extends StatelessWidget {
hintMaxLines: 1,
hintText: chatStore.auth.isLoggedIn
? chatStore.isSendingMessage
? 'Sending...'
: chatStore.replyingToMessage != null
? 'Reply'
: 'Chat'
? 'Sending...'
: chatStore.replyingToMessage !=
null
? 'Reply'
: 'Chat'
: 'Log in',
),
controller: chatStore.textController,
@ -308,12 +315,14 @@ class ChatBottomBar extends StatelessWidget {
),
)
: const Icon(Icons.send_rounded),
onPressed: chatStore.auth.isLoggedIn &&
onPressed:
chatStore.auth.isLoggedIn &&
!chatStore.isSendingMessage &&
!(chatStore.settings.showVideo &&
chatStore.settings.chatDelay > 0)
? () => chatStore
.sendMessage(chatStore.textController.text)
? () => chatStore.sendMessage(
chatStore.textController.text,
)
: null,
);
},

View file

@ -48,19 +48,12 @@ class ChatMessage extends StatelessWidget {
);
}
void onTapPingedUser(
BuildContext context, {
required String nickname,
}) {
void onTapPingedUser(BuildContext context, {required String nickname}) {
// Ignore if the message is a recent message in the modal bottom sheet.
if (isModal) return;
final twitchApi = context.read<TwitchApi>();
twitchApi
.getUser(
userLogin: nickname,
)
.then((user) {
twitchApi.getUser(userLogin: nickname).then((user) {
if (context.mounted) {
showModalBottomSheetWithProperFocus(
isScrollControlled: true,
@ -156,8 +149,9 @@ class ChatMessage extends StatelessWidget {
final defaultTextStyle = DefaultTextStyle.of(context).style;
final messageHeaderIconSize =
defaultBadgeSize * chatStore.settings.badgeScale;
final messageHeaderTextColor =
defaultTextStyle.color?.withValues(alpha: 0.5);
final messageHeaderTextColor = defaultTextStyle.color?.withValues(
alpha: 0.5,
);
const messageHeaderFontWeight = FontWeight.w600;
return Observer(
@ -170,8 +164,9 @@ class ChatMessage extends StatelessWidget {
case Command.userState:
final shouldHighlightFirstMessage =
chatStore.settings.highlightFirstTimeChatter &&
ircMessage.tags['first-msg'] == '1';
final shouldHighlightMessage = chatStore.settings.showUserNotices &&
ircMessage.tags['first-msg'] == '1';
final shouldHighlightMessage =
chatStore.settings.showUserNotices &&
ircMessage.tags['msg-id'] == 'highlighted-message';
final messageSpan = Text.rich(
@ -210,14 +205,14 @@ class ChatMessage extends StatelessWidget {
onTap: isModal
? null
: () => showModalBottomSheetWithProperFocus(
context: context,
builder: (context) {
return ReplyThread(
selectedMessage: ircMessage,
chatStore: chatStore,
);
},
),
context: context,
builder: (context) {
return ReplyThread(
selectedMessage: ircMessage,
chatStore: chatStore,
);
},
),
child: Text(
'Replying to @$replyUser: $replyBody',
maxLines: 1,
@ -265,9 +260,7 @@ class ChatMessage extends StatelessWidget {
spacing: 4,
children: [
messageHeaderIcon,
Flexible(
child: messageHeader,
),
Flexible(child: messageHeader),
],
)
else
@ -305,8 +298,9 @@ class ChatMessage extends StatelessWidget {
else
Text(
'Timed out for $banDuration ${int.parse(banDuration) > 1 ? 'seconds' : 'second'}',
style:
const TextStyle(fontWeight: messageHeaderFontWeight),
style: const TextStyle(
fontWeight: messageHeaderFontWeight,
),
),
Text.rich(
TextSpan(
@ -394,12 +388,8 @@ class ChatMessage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4,
children: [
if (messageHeaderIcon != null) ...[
messageHeaderIcon,
],
Expanded(
child: messageHeader,
),
if (messageHeaderIcon != null) ...[messageHeaderIcon],
Expanded(child: messageHeader),
],
),
if (ircMessage.message != null) ...[
@ -432,7 +422,8 @@ class ChatMessage extends StatelessWidget {
}
// Check if this is a reply message in reply thread context for indentation
final isReplyInThread = isInReplyThread &&
final isReplyInThread =
isInReplyThread &&
ircMessage.tags['reply-parent-display-name'] != null &&
ircMessage.tags['reply-parent-msg-body'] != null;
@ -468,10 +459,7 @@ class ChatMessage extends StatelessWidget {
final dividedMessage = chatStore.settings.showChatMessageDividers
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
paddedMessage,
const Divider(),
],
children: [paddedMessage, const Divider()],
)
: paddedMessage;

View file

@ -38,9 +38,7 @@ class _ChatUserModalState extends State<ChatUserModal> {
children: [
ListTile(
contentPadding: const EdgeInsets.all(12),
leading: ProfilePicture(
userLogin: widget.username,
),
leading: ProfilePicture(userLogin: widget.username),
title: Row(
children: [
Flexible(
@ -104,9 +102,9 @@ class _ChatUserModalState extends State<ChatUserModal> {
),
),
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,
primary: false,

View file

@ -41,9 +41,9 @@ class ReplyThread extends StatelessWidget {
textScaler: TextScaler.linear(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: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
@ -54,18 +54,13 @@ class ReplyThread extends StatelessWidget {
padding: EdgeInsets.fromLTRB(12, 0, 12, 8),
),
if (replyParent != null)
ChatMessage(
ircMessage: replyParent,
chatStore: chatStore,
)
ChatMessage(ircMessage: replyParent, chatStore: chatStore)
else
Padding(
padding: const EdgeInsets.fromLTRB(12, 0, 12, 8),
child: Text(
'$replyName: $replyBody',
style: const TextStyle(
fontWeight: FontWeight.w500,
),
style: const TextStyle(fontWeight: FontWeight.w500),
),
),
Flexible(

View file

@ -10,10 +10,7 @@ import 'package:webview_flutter_android/webview_flutter_android.dart';
class Video extends StatefulWidget {
final VideoStore videoStore;
const Video({
super.key,
required this.videoStore,
});
const Video({super.key, required this.videoStore});
@override
State<Video> createState() => _VideoState();
@ -24,8 +21,9 @@ class _VideoState extends State<Video> with WidgetsBindingObserver {
void initState() {
super.initState();
if (widget.videoStore.settingsStore.showVideo) {
widget.videoStore.videoWebViewController
.loadRequest(Uri.parse(widget.videoStore.videoUrl));
widget.videoStore.videoWebViewController.loadRequest(
Uri.parse(widget.videoStore.videoUrl),
);
}
WidgetsBinding.instance.addObserver(this);
}
@ -62,8 +60,9 @@ class _VideoState extends State<Video> with WidgetsBindingObserver {
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
widget.videoStore.videoWebViewController
.loadRequest(Uri.parse('about:blank'));
widget.videoStore.videoWebViewController.loadRequest(
Uri.parse('about:blank'),
);
super.dispose();
}

View file

@ -50,8 +50,11 @@ class VideoOverlay extends StatelessWidget {
@override
Widget build(BuildContext context) {
final surfaceColor =
context.watch<FrostyThemes>().dark.colorScheme.onSurface;
final surfaceColor = context
.watch<FrostyThemes>()
.dark
.colorScheme
.onSurface;
final backButton = IconButton(
tooltip: 'Back',
@ -78,10 +81,7 @@ class VideoOverlay extends StatelessWidget {
);
final videoSettingsButton = IconButton(
icon: Icon(
Icons.settings,
shadows: _iconShadow,
),
icon: Icon(Icons.settings, shadows: _iconShadow),
color: surfaceColor,
onPressed: () {
videoStore.updateStreamQualities();
@ -190,8 +190,9 @@ class VideoOverlay extends StatelessWidget {
);
final rotateButton = Tooltip(
message:
context.isPortrait ? 'Enter landscape mode' : 'Exit landscape mode',
message: context.isPortrait
? 'Enter landscape mode'
: 'Exit landscape mode',
preferBelow: false,
child: IconButton(
icon: Icon(
@ -206,9 +207,9 @@ class VideoOverlay extends StatelessWidget {
DeviceOrientation.landscapeRight,
]);
} else {
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp],
);
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
SystemChrome.setPreferredOrientations([]);
}
},
@ -423,18 +424,18 @@ class VideoOverlay extends StatelessWidget {
child: GestureDetector(
onTap: () =>
showModalBottomSheetWithProperFocus(
isScrollControlled: true,
context: context,
builder: (context) => GestureDetector(
onTap: FocusScope.of(context).unfocus,
child: ChattersList(
chatDetailsStore:
chatStore.chatDetailsStore,
chatStore: chatStore,
userLogin: streamInfo.userLogin,
isScrollControlled: true,
context: context,
builder: (context) => GestureDetector(
onTap: FocusScope.of(context).unfocus,
child: ChattersList(
chatDetailsStore:
chatStore.chatDetailsStore,
chatStore: chatStore,
userLogin: streamInfo.userLogin,
),
),
),
),
),
child: Row(
spacing: 4,
children: [

View file

@ -62,8 +62,9 @@ abstract class VideoStoreBase with Store {
'StreamQualities',
onMessageReceived: (message) async {
final data = jsonDecode(message.message) as List;
_availableStreamQualities =
data.map((item) => item as String).toList();
_availableStreamQualities = data
.map((item) => item as String)
.toList();
if (_firstTimeSettingQuality) {
_firstTimeSettingQuality = false;
if (settingsStore.defaultToHighestQuality) {
@ -89,8 +90,7 @@ abstract class VideoStoreBase with Store {
onMessageReceived: (message) {
_paused = false;
if (Platform.isAndroid) pip.setIsPlaying(true);
videoWebViewController.runJavaScript(
'''
videoWebViewController.runJavaScript('''
(function() {
const video = document.getElementsByTagName("video")[0];
if (video) {
@ -101,8 +101,7 @@ abstract class VideoStoreBase with Store {
}
}
})();
''',
);
''');
},
)
..addJavaScriptChannel(
@ -122,16 +121,17 @@ abstract class VideoStoreBase with Store {
onPageFinished: (url) async {
if (url != videoUrl) return;
// Safe evaluation of JavaScript boolean result
final result =
await videoWebViewController.runJavaScriptReturningResult(
'window._injected ? true : false',
);
final result = await videoWebViewController
.runJavaScriptReturningResult(
'window._injected ? true : false',
);
final injected = result is bool
? result
: (result.toString().toLowerCase() == 'true');
if (injected) return;
await videoWebViewController
.runJavaScript('window._injected = true;');
await videoWebViewController.runJavaScript(
'window._injected = true;',
);
await initVideo();
_acceptContentWarning();
},
@ -216,8 +216,10 @@ abstract class VideoStoreBase with Store {
}
// Initialize the [_overlayTimer] to hide the overlay automatically after 5 seconds.
_overlayTimer =
Timer(const Duration(seconds: 5), () => _overlayVisible = false);
_overlayTimer = Timer(
const Duration(seconds: 5),
() => _overlayVisible = false,
);
// Initialize a reaction that will reload the webview whenever the overlay is toggled.
_disposeOverlayReaction = reaction(
@ -226,42 +228,43 @@ abstract class VideoStoreBase with Store {
);
// Initialize a reaction to manage stream info timer based on video mode
_disposeVideoModeReaction = reaction(
(_) => settingsStore.showVideo,
(showVideo) {
if (showVideo) {
// In video mode, stop the timer since overlay taps handle refreshing
_stopStreamInfoTimer();
} else {
// In chat-only mode, start the timer for automatic updates
_startStreamInfoTimer();
// Ensure overlay timer is active for clean UI
_overlayTimer.cancel();
_overlayTimer =
Timer(const Duration(seconds: 5), () => _overlayVisible = false);
}
},
);
_disposeVideoModeReaction = reaction((_) => settingsStore.showVideo, (
showVideo,
) {
if (showVideo) {
// In video mode, stop the timer since overlay taps handle refreshing
_stopStreamInfoTimer();
} else {
// In chat-only mode, start the timer for automatic updates
_startStreamInfoTimer();
// Ensure overlay timer is active for clean UI
_overlayTimer.cancel();
_overlayTimer = Timer(
const Duration(seconds: 5),
() => _overlayVisible = false,
);
}
});
// Check initial state and start timer if already in chat-only mode
if (!settingsStore.showVideo) {
_startStreamInfoTimer();
_overlayTimer.cancel();
_overlayTimer =
Timer(const Duration(seconds: 5), () => _overlayVisible = false);
_overlayTimer = Timer(
const Duration(seconds: 5),
() => _overlayVisible = false,
);
}
// On Android, enable auto PiP mode (setAutoEnterEnabled) if the device supports it.
if (Platform.isAndroid) {
_disposeAndroidAutoPipReaction = autorun(
(_) async {
if (settingsStore.showVideo && await SimplePip.isAutoPipAvailable) {
pip.setAutoPipMode();
} else {
pip.setAutoPipMode(autoEnter: false);
}
},
);
_disposeAndroidAutoPipReaction = autorun((_) async {
if (settingsStore.showVideo && await SimplePip.isAutoPipAvailable) {
pip.setAutoPipMode();
} else {
pip.setAutoPipMode(autoEnter: false);
}
});
}
updateStreamInfo();
@ -303,8 +306,9 @@ abstract class VideoStoreBase with Store {
@action
Future<void> setStreamQuality(String newStreamQuality) async {
final indexOfStreamQuality =
_availableStreamQualities.indexOf(newStreamQuality);
final indexOfStreamQuality = _availableStreamQualities.indexOf(
newStreamQuality,
);
if (indexOfStreamQuality == -1) return;
await _setStreamQualityIndex(indexOfStreamQuality);
}
@ -484,8 +488,10 @@ abstract class VideoStoreBase with Store {
updateStreamInfo();
_overlayVisible = true;
_overlayTimer =
Timer(const Duration(seconds: 5), () => _overlayVisible = false);
_overlayTimer = Timer(
const Duration(seconds: 5),
() => _overlayVisible = false,
);
}
}
@ -513,9 +519,7 @@ abstract class VideoStoreBase with Store {
@action
Future<void> updateStreamInfo() async {
try {
_streamInfo = await twitchApi.getStream(
userLogin: userLogin,
);
_streamInfo = await twitchApi.getStream(userLogin: userLogin);
} catch (e) {
debugPrint(e.toString());
@ -525,8 +529,10 @@ abstract class VideoStoreBase with Store {
// Restart overlay timer in chat-only mode even on error
if (!settingsStore.showVideo) {
_overlayTimer =
Timer(const Duration(seconds: 5), () => _overlayVisible = false);
_overlayTimer = Timer(
const Duration(seconds: 5),
() => _overlayVisible = false,
);
}
}
}
@ -545,8 +551,10 @@ abstract class VideoStoreBase with Store {
_overlayVisible = true;
_overlayTimer.cancel();
_overlayTimer =
Timer(const Duration(seconds: 3), () => _overlayVisible = false);
_overlayTimer = Timer(
const Duration(seconds: 3),
() => _overlayVisible = false,
);
}
}
@ -568,8 +576,9 @@ abstract class VideoStoreBase with Store {
void handlePausePlay() {
try {
if (_paused) {
videoWebViewController
.runJavaScript('document.getElementsByTagName("video")[0].play();');
videoWebViewController.runJavaScript(
'document.getElementsByTagName("video")[0].play();',
);
} else {
videoWebViewController.runJavaScript(
'document.getElementsByTagName("video")[0].pause();',
@ -608,15 +617,13 @@ abstract class VideoStoreBase with Store {
try {
if (Platform.isIOS && _isInPipMode) {
// Exit PiP mode on iOS
videoWebViewController.runJavaScript(
'''
videoWebViewController.runJavaScript('''
(function() {
if (document.pictureInPictureElement) {
document.exitPictureInPicture();
}
})();
''',
);
''');
} else {
// Enter PiP mode (both iOS and Android)
requestPictureInPicture();

View file

@ -9,8 +9,10 @@ part of 'video_store.dart';
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers
mixin _$VideoStore on VideoStoreBase, Store {
late final _$_pausedAtom =
Atom(name: 'VideoStoreBase._paused', context: context);
late final _$_pausedAtom = Atom(
name: 'VideoStoreBase._paused',
context: context,
);
bool get paused {
_$_pausedAtom.reportRead();
@ -27,8 +29,10 @@ mixin _$VideoStore on VideoStoreBase, Store {
});
}
late final _$_overlayVisibleAtom =
Atom(name: 'VideoStoreBase._overlayVisible', context: context);
late final _$_overlayVisibleAtom = Atom(
name: 'VideoStoreBase._overlayVisible',
context: context,
);
bool get overlayVisible {
_$_overlayVisibleAtom.reportRead();
@ -45,8 +49,10 @@ mixin _$VideoStore on VideoStoreBase, Store {
});
}
late final _$_streamInfoAtom =
Atom(name: 'VideoStoreBase._streamInfo', context: context);
late final _$_streamInfoAtom = Atom(
name: 'VideoStoreBase._streamInfo',
context: context,
);
StreamTwitch? get streamInfo {
_$_streamInfoAtom.reportRead();
@ -63,8 +69,10 @@ mixin _$VideoStore on VideoStoreBase, Store {
});
}
late final _$_availableStreamQualitiesAtom =
Atom(name: 'VideoStoreBase._availableStreamQualities', context: context);
late final _$_availableStreamQualitiesAtom = Atom(
name: 'VideoStoreBase._availableStreamQualities',
context: context,
);
List<String> get availableStreamQualities {
_$_availableStreamQualitiesAtom.reportRead();
@ -76,14 +84,19 @@ mixin _$VideoStore on VideoStoreBase, Store {
@override
set _availableStreamQualities(List<String> value) {
_$_availableStreamQualitiesAtom
.reportWrite(value, super._availableStreamQualities, () {
super._availableStreamQualities = value;
});
_$_availableStreamQualitiesAtom.reportWrite(
value,
super._availableStreamQualities,
() {
super._availableStreamQualities = value;
},
);
}
late final _$_streamQualityIndexAtom =
Atom(name: 'VideoStoreBase._streamQualityIndex', context: context);
late final _$_streamQualityIndexAtom = Atom(
name: 'VideoStoreBase._streamQualityIndex',
context: context,
);
int get streamQualityIndex {
_$_streamQualityIndexAtom.reportRead();
@ -100,8 +113,10 @@ mixin _$VideoStore on VideoStoreBase, Store {
});
}
late final _$_latencyAtom =
Atom(name: 'VideoStoreBase._latency', context: context);
late final _$_latencyAtom = Atom(
name: 'VideoStoreBase._latency',
context: context,
);
String? get latency {
_$_latencyAtom.reportRead();
@ -118,8 +133,10 @@ mixin _$VideoStore on VideoStoreBase, Store {
});
}
late final _$_isInPipModeAtom =
Atom(name: 'VideoStoreBase._isInPipMode', context: context);
late final _$_isInPipModeAtom = Atom(
name: 'VideoStoreBase._isInPipMode',
context: context,
);
bool get isInPipMode {
_$_isInPipModeAtom.reportRead();
@ -136,56 +153,72 @@ mixin _$VideoStore on VideoStoreBase, Store {
});
}
late final _$updateStreamQualitiesAsyncAction =
AsyncAction('VideoStoreBase.updateStreamQualities', context: context);
late final _$updateStreamQualitiesAsyncAction = AsyncAction(
'VideoStoreBase.updateStreamQualities',
context: context,
);
@override
Future<void> updateStreamQualities() {
return _$updateStreamQualitiesAsyncAction
.run(() => super.updateStreamQualities());
return _$updateStreamQualitiesAsyncAction.run(
() => super.updateStreamQualities(),
);
}
late final _$setStreamQualityAsyncAction =
AsyncAction('VideoStoreBase.setStreamQuality', context: context);
late final _$setStreamQualityAsyncAction = AsyncAction(
'VideoStoreBase.setStreamQuality',
context: context,
);
@override
Future<void> setStreamQuality(String newStreamQuality) {
return _$setStreamQualityAsyncAction
.run(() => super.setStreamQuality(newStreamQuality));
return _$setStreamQualityAsyncAction.run(
() => super.setStreamQuality(newStreamQuality),
);
}
late final _$_setStreamQualityIndexAsyncAction =
AsyncAction('VideoStoreBase._setStreamQualityIndex', context: context);
late final _$_setStreamQualityIndexAsyncAction = AsyncAction(
'VideoStoreBase._setStreamQualityIndex',
context: context,
);
@override
Future<void> _setStreamQualityIndex(int newStreamQualityIndex) {
return _$_setStreamQualityIndexAsyncAction
.run(() => super._setStreamQualityIndex(newStreamQualityIndex));
return _$_setStreamQualityIndexAsyncAction.run(
() => super._setStreamQualityIndex(newStreamQualityIndex),
);
}
late final _$initVideoAsyncAction =
AsyncAction('VideoStoreBase.initVideo', context: context);
late final _$initVideoAsyncAction = AsyncAction(
'VideoStoreBase.initVideo',
context: context,
);
@override
Future<void> initVideo() {
return _$initVideoAsyncAction.run(() => super.initVideo());
}
late final _$updateStreamInfoAsyncAction =
AsyncAction('VideoStoreBase.updateStreamInfo', context: context);
late final _$updateStreamInfoAsyncAction = AsyncAction(
'VideoStoreBase.updateStreamInfo',
context: context,
);
@override
Future<void> updateStreamInfo() {
return _$updateStreamInfoAsyncAction.run(() => super.updateStreamInfo());
}
late final _$VideoStoreBaseActionController =
ActionController(name: 'VideoStoreBase', context: context);
late final _$VideoStoreBaseActionController = ActionController(
name: 'VideoStoreBase',
context: context,
);
@override
void handleVideoTap() {
final _$actionInfo = _$VideoStoreBaseActionController.startAction(
name: 'VideoStoreBase.handleVideoTap');
name: 'VideoStoreBase.handleVideoTap',
);
try {
return super.handleVideoTap();
} finally {
@ -196,7 +229,8 @@ mixin _$VideoStore on VideoStoreBase, Store {
@override
void handleToggleOverlay() {
final _$actionInfo = _$VideoStoreBaseActionController.startAction(
name: 'VideoStoreBase.handleToggleOverlay');
name: 'VideoStoreBase.handleToggleOverlay',
);
try {
return super.handleToggleOverlay();
} finally {
@ -207,7 +241,8 @@ mixin _$VideoStore on VideoStoreBase, Store {
@override
void handleRefresh() {
final _$actionInfo = _$VideoStoreBaseActionController.startAction(
name: 'VideoStoreBase.handleRefresh');
name: 'VideoStoreBase.handleRefresh',
);
try {
return super.handleRefresh();
} finally {
@ -218,7 +253,8 @@ mixin _$VideoStore on VideoStoreBase, Store {
@override
void togglePictureInPicture() {
final _$actionInfo = _$VideoStoreBaseActionController.startAction(
name: 'VideoStoreBase.togglePictureInPicture');
name: 'VideoStoreBase.togglePictureInPicture',
);
try {
return super.togglePictureInPicture();
} finally {
@ -229,7 +265,8 @@ mixin _$VideoStore on VideoStoreBase, Store {
@override
void dispose() {
final _$actionInfo = _$VideoStoreBaseActionController.startAction(
name: 'VideoStoreBase.dispose');
name: 'VideoStoreBase.dispose',
);
try {
return super.dispose();
} finally {

View file

@ -145,12 +145,8 @@ class _HomeState extends State<Home> {
listType: ListType.followed,
scrollController: _homeStore.followedScrollController,
),
TopSection(
homeStore: _homeStore,
),
Search(
scrollController: _homeStore.searchScrollController,
),
TopSection(homeStore: _homeStore),
Search(scrollController: _homeStore.searchScrollController),
],
),
),
@ -167,8 +163,9 @@ class _HomeState extends State<Home> {
scrollController = _homeStore.followedScrollController;
} else if (_homeStore.selectedIndex == (isLoggedIn ? 1 : 0)) {
// Top tab
scrollController = _homeStore.topSectionScrollControllers[
_homeStore.topSectionCurrentIndex];
scrollController =
_homeStore.topSectionScrollControllers[_homeStore
.topSectionCurrentIndex];
} else {
// Search tab
scrollController = _homeStore.searchScrollController;
@ -182,9 +179,9 @@ class _HomeState extends State<Home> {
),
Observer(
builder: (_) => Theme(
data: Theme.of(context).copyWith(
splashFactory: NoSplash.splashFactory,
),
data: Theme.of(
context,
).copyWith(splashFactory: NoSplash.splashFactory),
child: NavigationBar(
backgroundColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
@ -196,8 +193,9 @@ class _HomeState extends State<Home> {
Icons.favorite_border_rounded,
color: _homeStore.selectedIndex == 0
? theme.colorScheme.onSurface
: theme.colorScheme.onSurfaceVariant
.withValues(alpha: 0.6),
: theme.colorScheme.onSurfaceVariant.withValues(
alpha: 0.6,
),
),
selectedIcon: Icon(
Icons.favorite_rounded,
@ -211,9 +209,10 @@ class _HomeState extends State<Home> {
Icons.arrow_upward_rounded,
color:
_homeStore.selectedIndex == (isLoggedIn ? 1 : 0)
? theme.colorScheme.onSurface
: theme.colorScheme.onSurfaceVariant
.withValues(alpha: 0.6),
? theme.colorScheme.onSurface
: theme.colorScheme.onSurfaceVariant.withValues(
alpha: 0.6,
),
),
selectedIcon: Icon(
Icons.arrow_upward_rounded,
@ -227,9 +226,10 @@ class _HomeState extends State<Home> {
Icons.search_rounded,
color:
_homeStore.selectedIndex == (isLoggedIn ? 2 : 1)
? theme.colorScheme.onSurface
: theme.colorScheme.onSurfaceVariant
.withValues(alpha: 0.6),
? theme.colorScheme.onSurface
: theme.colorScheme.onSurfaceVariant.withValues(
alpha: 0.6,
),
),
selectedIcon: Icon(
Icons.search_rounded,

View file

@ -16,10 +16,7 @@ abstract class HomeStoreBase with Store {
/// The scroll controllers for controlling the scroll to top on the top section.
/// One controller is for the top streams tab and the other is for the top categories tab.
final topSectionScrollControllers = [
ScrollController(),
ScrollController(),
];
final topSectionScrollControllers = [ScrollController(), ScrollController()];
/// The scroll controller for controlling the scroll to top on the search section.
final searchScrollController = ScrollController();

View file

@ -9,8 +9,10 @@ part of 'home_store.dart';
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers
mixin _$HomeStore on HomeStoreBase, Store {
late final _$topSectionCurrentIndexAtom =
Atom(name: 'HomeStoreBase.topSectionCurrentIndex', context: context);
late final _$topSectionCurrentIndexAtom = Atom(
name: 'HomeStoreBase.topSectionCurrentIndex',
context: context,
);
@override
int get topSectionCurrentIndex {
@ -20,14 +22,19 @@ mixin _$HomeStore on HomeStoreBase, Store {
@override
set topSectionCurrentIndex(int value) {
_$topSectionCurrentIndexAtom
.reportWrite(value, super.topSectionCurrentIndex, () {
super.topSectionCurrentIndex = value;
});
_$topSectionCurrentIndexAtom.reportWrite(
value,
super.topSectionCurrentIndex,
() {
super.topSectionCurrentIndex = value;
},
);
}
late final _$_selectedIndexAtom =
Atom(name: 'HomeStoreBase._selectedIndex', context: context);
late final _$_selectedIndexAtom = Atom(
name: 'HomeStoreBase._selectedIndex',
context: context,
);
int get selectedIndex {
_$_selectedIndexAtom.reportRead();
@ -44,13 +51,16 @@ mixin _$HomeStore on HomeStoreBase, Store {
});
}
late final _$HomeStoreBaseActionController =
ActionController(name: 'HomeStoreBase', context: context);
late final _$HomeStoreBaseActionController = ActionController(
name: 'HomeStoreBase',
context: context,
);
@override
void handleTap(int index) {
final _$actionInfo = _$HomeStoreBaseActionController.startAction(
name: 'HomeStoreBase.handleTap');
name: 'HomeStoreBase.handleTap',
);
try {
return super.handleTap(index);
} finally {

View file

@ -18,10 +18,7 @@ class Search extends StatefulWidget {
// The scroll controller for handling scroll to top functionality.
final ScrollController scrollController;
const Search({
super.key,
required this.scrollController,
});
const Search({super.key, required this.scrollController});
@override
State<Search> createState() => _SearchState();
@ -52,7 +49,8 @@ class _SearchState extends State<Search> {
slivers: [
SliverToBoxAdapter(
child: SizedBox(
height: MediaQuery.of(context).padding.top +
height:
MediaQuery.of(context).padding.top +
_kSearchBarHeight,
),
),
@ -77,7 +75,8 @@ class _SearchState extends State<Search> {
child: ListView(
controller: widget.scrollController,
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top +
top:
MediaQuery.of(context).padding.top +
_kSearchBarHeight,
bottom: MediaQuery.of(context).padding.bottom,
),
@ -112,11 +111,13 @@ class _SearchState extends State<Search> {
_searchStore.handleQuery(searchTerm);
_searchStore.textEditingController.selection =
TextSelection.fromPosition(
TextPosition(
offset: _searchStore
.textEditingController.text.length,
),
);
TextPosition(
offset: _searchStore
.textEditingController
.text
.length,
),
);
},
),
),
@ -193,8 +194,8 @@ class _SearchState extends State<Search> {
decoration: InputDecoration(
prefixIcon: const Icon(Icons.search_rounded),
hintText: 'Search channels or categories',
suffixIcon: _searchStore
.textFieldFocusNode.hasFocus ||
suffixIcon:
_searchStore.textFieldFocusNode.hasFocus ||
_searchStore.searchText.isNotEmpty
? IconButton(
icon: const Icon(Icons.close_rounded),
@ -210,20 +211,23 @@ class _SearchState extends State<Search> {
if (widget.scrollController.hasClients) {
widget.scrollController.animateTo(
0,
duration:
const Duration(milliseconds: 150),
duration: const Duration(
milliseconds: 150,
),
curve: Curves.easeOut,
);
}
// After the frame (when content swaps), re-evaluate border state
WidgetsBinding.instance
.addPostFrameCallback((_) {
WidgetsBinding.instance.addPostFrameCallback((
_,
) {
if (widget.scrollController.hasClients) {
// Nudge listeners even if already at zero
final offset =
widget.scrollController.offset;
final target =
(offset == 0) ? 0.01 : 0.0;
final target = (offset == 0)
? 0.01
: 0.0;
widget.scrollController.jumpTo(
(offset == 0) ? target : 0.0,
);

View file

@ -19,9 +19,7 @@ class SearchResultsCategories extends StatelessWidget {
final future = searchStore.categoryFuture;
if (future == null) {
return const SliverToBoxAdapter(
child: SizedBox.shrink(),
);
return const SliverToBoxAdapter(child: SizedBox.shrink());
}
switch (future.status) {
@ -69,9 +67,8 @@ class SearchResultsCategories extends StatelessWidget {
return SliverList.builder(
itemCount: categories.data.length,
itemBuilder: (context, index) => CategoryCard(
category: categories.data[index],
),
itemBuilder: (context, index) =>
CategoryCard(category: categories.data[index]),
);
}
},

View file

@ -49,10 +49,7 @@ class _SearchResultsChannelsState extends State<SearchResultsChannels> {
} on ApiException catch (e) {
debugPrint('Search channels ApiException: $e');
final snackBar = SnackBar(
content: AlertMessage(
message: e.message,
centered: false,
),
content: AlertMessage(message: e.message, centered: false),
);
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(snackBar);
@ -76,18 +73,15 @@ class _SearchResultsChannelsState extends State<SearchResultsChannels> {
final future = widget.searchStore.channelFuture;
if (future == null) {
return const SliverToBoxAdapter(
child: SizedBox.shrink(),
);
return const SliverToBoxAdapter(child: SizedBox.shrink());
}
switch (future.status) {
case FutureStatus.pending:
return SliverList.builder(
itemCount: 8,
itemBuilder: (context, index) => ChannelSkeletonLoader(
index: index,
),
itemBuilder: (context, index) =>
ChannelSkeletonLoader(index: index),
);
case FutureStatus.rejected:
return const SliverToBoxAdapter(
@ -108,56 +102,54 @@ class _SearchResultsChannelsState extends State<SearchResultsChannels> {
return SliverList.list(
children: [
...results.map(
(channel) {
final displayName = getReadableName(
channel.displayName,
channel.broadcasterLogin,
);
...results.map((channel) {
final displayName = getReadableName(
channel.displayName,
channel.broadcasterLogin,
);
return InkWell(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => VideoChat(
userId: channel.id,
userName: channel.displayName,
userLogin: channel.broadcasterLogin,
),
),
),
onLongPress: () {
HapticFeedback.lightImpact();
showModalBottomSheetWithProperFocus(
context: context,
builder: (context) => UserActionsModal(
authStore: widget.searchStore.authStore,
name: displayName,
userLogin: channel.broadcasterLogin,
userId: channel.id,
),
);
},
child: ListTile(
title: Text(displayName),
leading: ProfilePicture(
return InkWell(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => VideoChat(
userId: channel.id,
userName: channel.displayName,
userLogin: channel.broadcasterLogin,
radius: 16,
),
subtitle: channel.isLive
? Row(
spacing: 6,
children: [
const LiveIndicator(),
Uptime(startTime: channel.startedAt),
],
)
: null,
),
);
},
),
),
onLongPress: () {
HapticFeedback.lightImpact();
showModalBottomSheetWithProperFocus(
context: context,
builder: (context) => UserActionsModal(
authStore: widget.searchStore.authStore,
name: displayName,
userLogin: channel.broadcasterLogin,
userId: channel.id,
),
);
},
child: ListTile(
title: Text(displayName),
leading: ProfilePicture(
userLogin: channel.broadcasterLogin,
radius: 16,
),
subtitle: channel.isLive
? Row(
spacing: 6,
children: [
const LiveIndicator(),
Uptime(startTime: channel.startedAt),
],
)
: null,
),
);
}),
ListTile(
title: Text('Go to channel "${widget.query}"'),
onTap: () => _handleSearch(context, widget.query),

View file

@ -41,7 +41,8 @@ abstract class SearchStoreBase with Store {
final prefs = await SharedPreferences.getInstance();
// Retrieve the search history from local storage. If it doesn't exist, use an empty list.
_searchHistory = prefs.getStringList('search_history')?.asObservable() ??
_searchHistory =
prefs.getStringList('search_history')?.asObservable() ??
ObservableList<String>();
// Create a reaction that will limit the history to 8 entries and update it to local storage automatically.
@ -51,8 +52,9 @@ abstract class SearchStoreBase with Store {
});
// Add a listener to update the cancel button visibility whenever the text changes.
textEditingController
.addListener(() => _searchText = textEditingController.text);
textEditingController.addListener(
() => _searchText = textEditingController.text,
);
}
/// Obtain the channels and categories that match the provided [query].
@ -65,26 +67,19 @@ abstract class SearchStoreBase with Store {
_searchHistory.insert(0, query);
// Fetch the matching channels, sort it by live status, and then set it.
_channelFuture = twitchApi
.searchChannels(
query: query,
)
.then(
(channels) {
channels.sort((c1, c2) => c2.isLive ? 1 : -1);
return channels;
},
).asObservable();
_channelFuture = twitchApi.searchChannels(query: query).then((channels) {
channels.sort((c1, c2) => c2.isLive ? 1 : -1);
return channels;
}).asObservable();
// Fetch and set the categories that match the query.
_categoryFuture = twitchApi
.searchCategories(
query: query,
)
.then((categories) {
_categoryFuture = twitchApi.searchCategories(query: query).then((
categories,
) {
// Move exact matches to the first result
final matchingIndex = categories.data
.indexWhere((c) => c.name.toLowerCase() == query.toLowerCase());
final matchingIndex = categories.data.indexWhere(
(c) => c.name.toLowerCase() == query.toLowerCase(),
);
if (matchingIndex >= 1) {
final matchingCategory = categories.data.removeAt(matchingIndex);
categories.data.insert(0, matchingCategory);
@ -96,12 +91,8 @@ abstract class SearchStoreBase with Store {
/// Find a specific channel provided the [query].
/// This is used for channels that may not show up in the search results.
Future<Channel> searchChannel(String query) async {
final user = await twitchApi.getUser(
userLogin: query,
);
return await twitchApi.getChannel(
userId: user.id,
);
final user = await twitchApi.getUser(userLogin: query);
return await twitchApi.getChannel(userId: user.id);
}
void dispose() {

View file

@ -9,8 +9,10 @@ part of 'search_store.dart';
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers
mixin _$SearchStore on SearchStoreBase, Store {
late final _$_searchTextAtom =
Atom(name: 'SearchStoreBase._searchText', context: context);
late final _$_searchTextAtom = Atom(
name: 'SearchStoreBase._searchText',
context: context,
);
String get searchText {
_$_searchTextAtom.reportRead();
@ -27,8 +29,10 @@ mixin _$SearchStore on SearchStoreBase, Store {
});
}
late final _$_searchHistoryAtom =
Atom(name: 'SearchStoreBase._searchHistory', context: context);
late final _$_searchHistoryAtom = Atom(
name: 'SearchStoreBase._searchHistory',
context: context,
);
ObservableList<String> get searchHistory {
_$_searchHistoryAtom.reportRead();
@ -45,8 +49,10 @@ mixin _$SearchStore on SearchStoreBase, Store {
});
}
late final _$_channelFutureAtom =
Atom(name: 'SearchStoreBase._channelFuture', context: context);
late final _$_channelFutureAtom = Atom(
name: 'SearchStoreBase._channelFuture',
context: context,
);
ObservableFuture<List<ChannelQuery>>? get channelFuture {
_$_channelFutureAtom.reportRead();
@ -63,8 +69,10 @@ mixin _$SearchStore on SearchStoreBase, Store {
});
}
late final _$_categoryFutureAtom =
Atom(name: 'SearchStoreBase._categoryFuture', context: context);
late final _$_categoryFutureAtom = Atom(
name: 'SearchStoreBase._categoryFuture',
context: context,
);
ObservableFuture<CategoriesTwitch?>? get categoryFuture {
_$_categoryFutureAtom.reportRead();
@ -81,21 +89,26 @@ mixin _$SearchStore on SearchStoreBase, Store {
});
}
late final _$initAsyncAction =
AsyncAction('SearchStoreBase.init', context: context);
late final _$initAsyncAction = AsyncAction(
'SearchStoreBase.init',
context: context,
);
@override
Future<void> init() {
return _$initAsyncAction.run(() => super.init());
}
late final _$SearchStoreBaseActionController =
ActionController(name: 'SearchStoreBase', context: context);
late final _$SearchStoreBaseActionController = ActionController(
name: 'SearchStoreBase',
context: context,
);
@override
void handleQuery(String query) {
final _$actionInfo = _$SearchStoreBaseActionController.startAction(
name: 'SearchStoreBase.handleQuery');
name: 'SearchStoreBase.handleQuery',
);
try {
return super.handleQuery(query);
} finally {

View file

@ -65,8 +65,10 @@ class LargeStreamCard extends StatelessWidget {
),
);
final streamerName =
getReadableName(streamInfo.userName, streamInfo.userLogin);
final streamerName = getReadableName(
streamInfo.userName,
streamInfo.userLogin,
);
return InkWell(
onTap: () => Navigator.push(

View file

@ -88,9 +88,7 @@ class OfflineChannelCard extends StatelessWidget {
spacing: 12,
children: [
// Profile picture
ProfilePicture(
userLogin: channelInfo.broadcasterLogin,
),
ProfilePicture(userLogin: channelInfo.broadcasterLogin),
// Channel info
Expanded(
child: Column(

View file

@ -66,12 +66,15 @@ class StreamCard extends StatelessWidget {
),
);
final streamerName =
getReadableName(streamInfo.userName, streamInfo.userLogin);
final streamerName = getReadableName(
streamInfo.userName,
streamInfo.userLogin,
);
final streamTitle = streamInfo.title.trim();
final category =
streamInfo.gameName.isNotEmpty ? streamInfo.gameName : 'No Category';
final category = streamInfo.gameName.isNotEmpty
? streamInfo.gameName
: 'No Category';
const subFontSize = 14.0;
@ -95,9 +98,7 @@ class StreamCard extends StatelessWidget {
),
Container(
margin: const EdgeInsets.all(3),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(6)),
clipBehavior: Clip.antiAlias,
child: BlurredContainer(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
@ -109,8 +110,11 @@ class StreamCard extends StatelessWidget {
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color:
context.watch<FrostyThemes>().dark.colorScheme.onSurface,
color: context
.watch<FrostyThemes>()
.dark
.colorScheme
.onSurface,
),
),
),
@ -128,10 +132,7 @@ class StreamCard extends StatelessWidget {
Row(
spacing: 4,
children: [
ProfilePicture(
userLogin: streamInfo.userLogin,
radius: 10,
),
ProfilePicture(userLogin: streamInfo.userLogin, radius: 10),
Flexible(
child: Tooltip(
message: streamerName,
@ -165,13 +166,12 @@ class StreamCard extends StatelessWidget {
InkWell(
onTap: streamInfo.gameName.isNotEmpty
? () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CategoryStreams(
categoryId: streamInfo.gameId,
),
),
)
context,
MaterialPageRoute(
builder: (context) =>
CategoryStreams(categoryId: streamInfo.gameId),
),
)
: null,
child: Tooltip(
message: category,
@ -238,14 +238,8 @@ class StreamCard extends StatelessWidget {
),
child: Row(
children: [
if (showThumbnail)
Flexible(
child: imageSection,
),
Flexible(
flex: 2,
child: streamInfoSection,
),
if (showThumbnail) Flexible(child: imageSection),
Flexible(flex: 2, child: streamInfoSection),
],
),
),

View file

@ -213,9 +213,7 @@ abstract class ListStoreBase with Store {
);
break;
case ListType.top:
newStreams = await twitchApi.getTopStreams(
cursor: _streamsCursor,
);
newStreams = await twitchApi.getTopStreams(cursor: _streamsCursor);
break;
case ListType.category:
newStreams = await twitchApi.getStreamsUnderCategory(
@ -259,9 +257,7 @@ abstract class ListStoreBase with Store {
try {
_pinnedStreams = (await twitchApi.getStreamsByIds(
userIds: settingsStore.pinnedChannelIds,
))
.data
.asObservable();
)).data.asObservable();
_error = null;
} on SocketException {
@ -333,9 +329,7 @@ abstract class ListStoreBase with Store {
_isCategoryDetailsLoading = true;
final categoryDetails = await twitchApi.getCategory(
gameId: categoryId!,
);
final categoryDetails = await twitchApi.getCategory(gameId: categoryId!);
_categoryDetails = categoryDetails.data.first;
@ -365,8 +359,4 @@ abstract class ListStoreBase with Store {
/// [ListType.followed] is the list of streams that the user is following.
/// [ListType.top] is the list of top streams.
/// [ListType.category] is the list of streams under a category.
enum ListType {
followed,
top,
category,
}
enum ListType { followed, top, category }

View file

@ -12,48 +12,54 @@ mixin _$ListStore on ListStoreBase, Store {
Computed<bool>? _$hasMoreComputed;
@override
bool get hasMore => (_$hasMoreComputed ??=
Computed<bool>(() => super.hasMore, name: 'ListStoreBase.hasMore'))
.value;
bool get hasMore => (_$hasMoreComputed ??= Computed<bool>(
() => super.hasMore,
name: 'ListStoreBase.hasMore',
)).value;
Computed<bool>? _$isLoadingComputed;
@override
bool get isLoading =>
(_$isLoadingComputed ??= Computed<bool>(() => super.isLoading,
name: 'ListStoreBase.isLoading'))
.value;
bool get isLoading => (_$isLoadingComputed ??= Computed<bool>(
() => super.isLoading,
name: 'ListStoreBase.isLoading',
)).value;
Computed<bool>? _$hasMoreOfflineChannelsComputed;
@override
bool get hasMoreOfflineChannels => (_$hasMoreOfflineChannelsComputed ??=
Computed<bool>(() => super.hasMoreOfflineChannels,
name: 'ListStoreBase.hasMoreOfflineChannels'))
.value;
bool get hasMoreOfflineChannels =>
(_$hasMoreOfflineChannelsComputed ??= Computed<bool>(
() => super.hasMoreOfflineChannels,
name: 'ListStoreBase.hasMoreOfflineChannels',
)).value;
Computed<ObservableList<StreamTwitch>>? _$streamsComputed;
@override
ObservableList<StreamTwitch> get streams => (_$streamsComputed ??=
Computed<ObservableList<StreamTwitch>>(() => super.streams,
name: 'ListStoreBase.streams'))
.value;
ObservableList<StreamTwitch> get streams =>
(_$streamsComputed ??= Computed<ObservableList<StreamTwitch>>(
() => super.streams,
name: 'ListStoreBase.streams',
)).value;
Computed<List<dynamic>>? _$allPinnedChannelsComputed;
@override
List<dynamic> get allPinnedChannels => (_$allPinnedChannelsComputed ??=
Computed<List<dynamic>>(() => super.allPinnedChannels,
name: 'ListStoreBase.allPinnedChannels'))
.value;
List<dynamic> get allPinnedChannels =>
(_$allPinnedChannelsComputed ??= Computed<List<dynamic>>(
() => super.allPinnedChannels,
name: 'ListStoreBase.allPinnedChannels',
)).value;
Computed<ObservableList<FollowedChannel>>? _$offlineChannelsComputed;
@override
ObservableList<FollowedChannel> get offlineChannels =>
(_$offlineChannelsComputed ??= Computed<ObservableList<FollowedChannel>>(
() => super.offlineChannels,
name: 'ListStoreBase.offlineChannels'))
.value;
() => super.offlineChannels,
name: 'ListStoreBase.offlineChannels',
)).value;
late final _$_allStreamsAtom =
Atom(name: 'ListStoreBase._allStreams', context: context);
late final _$_allStreamsAtom = Atom(
name: 'ListStoreBase._allStreams',
context: context,
);
ObservableList<StreamTwitch> get allStreams {
_$_allStreamsAtom.reportRead();
@ -70,8 +76,10 @@ mixin _$ListStore on ListStoreBase, Store {
});
}
late final _$_isAllStreamsLoadingAtom =
Atom(name: 'ListStoreBase._isAllStreamsLoading', context: context);
late final _$_isAllStreamsLoadingAtom = Atom(
name: 'ListStoreBase._isAllStreamsLoading',
context: context,
);
bool get isAllStreamsLoading {
_$_isAllStreamsLoadingAtom.reportRead();
@ -83,14 +91,19 @@ mixin _$ListStore on ListStoreBase, Store {
@override
set _isAllStreamsLoading(bool value) {
_$_isAllStreamsLoadingAtom.reportWrite(value, super._isAllStreamsLoading,
() {
super._isAllStreamsLoading = value;
});
_$_isAllStreamsLoadingAtom.reportWrite(
value,
super._isAllStreamsLoading,
() {
super._isAllStreamsLoading = value;
},
);
}
late final _$_pinnedStreamsAtom =
Atom(name: 'ListStoreBase._pinnedStreams', context: context);
late final _$_pinnedStreamsAtom = Atom(
name: 'ListStoreBase._pinnedStreams',
context: context,
);
ObservableList<StreamTwitch> get pinnedStreams {
_$_pinnedStreamsAtom.reportRead();
@ -107,8 +120,10 @@ mixin _$ListStore on ListStoreBase, Store {
});
}
late final _$_isPinnedStreamsLoadingAtom =
Atom(name: 'ListStoreBase._isPinnedStreamsLoading', context: context);
late final _$_isPinnedStreamsLoadingAtom = Atom(
name: 'ListStoreBase._isPinnedStreamsLoading',
context: context,
);
bool get isPinnedStreamsLoading {
_$_isPinnedStreamsLoadingAtom.reportRead();
@ -120,14 +135,19 @@ mixin _$ListStore on ListStoreBase, Store {
@override
set _isPinnedStreamsLoading(bool value) {
_$_isPinnedStreamsLoadingAtom
.reportWrite(value, super._isPinnedStreamsLoading, () {
super._isPinnedStreamsLoading = value;
});
_$_isPinnedStreamsLoadingAtom.reportWrite(
value,
super._isPinnedStreamsLoading,
() {
super._isPinnedStreamsLoading = value;
},
);
}
late final _$_categoryDetailsAtom =
Atom(name: 'ListStoreBase._categoryDetails', context: context);
late final _$_categoryDetailsAtom = Atom(
name: 'ListStoreBase._categoryDetails',
context: context,
);
CategoryTwitch? get categoryDetails {
_$_categoryDetailsAtom.reportRead();
@ -144,8 +164,10 @@ mixin _$ListStore on ListStoreBase, Store {
});
}
late final _$_isCategoryDetailsLoadingAtom =
Atom(name: 'ListStoreBase._isCategoryDetailsLoading', context: context);
late final _$_isCategoryDetailsLoadingAtom = Atom(
name: 'ListStoreBase._isCategoryDetailsLoading',
context: context,
);
bool get isCategoryDetailsLoading {
_$_isCategoryDetailsLoadingAtom.reportRead();
@ -157,14 +179,19 @@ mixin _$ListStore on ListStoreBase, Store {
@override
set _isCategoryDetailsLoading(bool value) {
_$_isCategoryDetailsLoadingAtom
.reportWrite(value, super._isCategoryDetailsLoading, () {
super._isCategoryDetailsLoading = value;
});
_$_isCategoryDetailsLoadingAtom.reportWrite(
value,
super._isCategoryDetailsLoading,
() {
super._isCategoryDetailsLoading = value;
},
);
}
late final _$_allOfflineChannelsAtom =
Atom(name: 'ListStoreBase._allOfflineChannels', context: context);
late final _$_allOfflineChannelsAtom = Atom(
name: 'ListStoreBase._allOfflineChannels',
context: context,
);
ObservableList<FollowedChannel> get allOfflineChannels {
_$_allOfflineChannelsAtom.reportRead();
@ -181,8 +208,10 @@ mixin _$ListStore on ListStoreBase, Store {
});
}
late final _$_isOfflineChannelsLoadingAtom =
Atom(name: 'ListStoreBase._isOfflineChannelsLoading', context: context);
late final _$_isOfflineChannelsLoadingAtom = Atom(
name: 'ListStoreBase._isOfflineChannelsLoading',
context: context,
);
bool get isOfflineChannelsLoading {
_$_isOfflineChannelsLoadingAtom.reportRead();
@ -194,14 +223,19 @@ mixin _$ListStore on ListStoreBase, Store {
@override
set _isOfflineChannelsLoading(bool value) {
_$_isOfflineChannelsLoadingAtom
.reportWrite(value, super._isOfflineChannelsLoading, () {
super._isOfflineChannelsLoading = value;
});
_$_isOfflineChannelsLoadingAtom.reportWrite(
value,
super._isOfflineChannelsLoading,
() {
super._isOfflineChannelsLoading = value;
},
);
}
late final _$showJumpButtonAtom =
Atom(name: 'ListStoreBase.showJumpButton', context: context);
late final _$showJumpButtonAtom = Atom(
name: 'ListStoreBase.showJumpButton',
context: context,
);
@override
bool get showJumpButton {
@ -216,8 +250,10 @@ mixin _$ListStore on ListStoreBase, Store {
});
}
late final _$isOfflineChannelsExpandedAtom =
Atom(name: 'ListStoreBase.isOfflineChannelsExpanded', context: context);
late final _$isOfflineChannelsExpandedAtom = Atom(
name: 'ListStoreBase.isOfflineChannelsExpanded',
context: context,
);
@override
bool get isOfflineChannelsExpanded {
@ -227,14 +263,19 @@ mixin _$ListStore on ListStoreBase, Store {
@override
set isOfflineChannelsExpanded(bool value) {
_$isOfflineChannelsExpandedAtom
.reportWrite(value, super.isOfflineChannelsExpanded, () {
super.isOfflineChannelsExpanded = value;
});
_$isOfflineChannelsExpandedAtom.reportWrite(
value,
super.isOfflineChannelsExpanded,
() {
super.isOfflineChannelsExpanded = value;
},
);
}
late final _$_errorAtom =
Atom(name: 'ListStoreBase._error', context: context);
late final _$_errorAtom = Atom(
name: 'ListStoreBase._error',
context: context,
);
String? get error {
_$_errorAtom.reportRead();
@ -251,46 +292,58 @@ mixin _$ListStore on ListStoreBase, Store {
});
}
late final _$getStreamsAsyncAction =
AsyncAction('ListStoreBase.getStreams', context: context);
late final _$getStreamsAsyncAction = AsyncAction(
'ListStoreBase.getStreams',
context: context,
);
@override
Future<void> getStreams() {
return _$getStreamsAsyncAction.run(() => super.getStreams());
}
late final _$getPinnedStreamsAsyncAction =
AsyncAction('ListStoreBase.getPinnedStreams', context: context);
late final _$getPinnedStreamsAsyncAction = AsyncAction(
'ListStoreBase.getPinnedStreams',
context: context,
);
@override
Future<void> getPinnedStreams() {
return _$getPinnedStreamsAsyncAction.run(() => super.getPinnedStreams());
}
late final _$getOfflineChannelsAsyncAction =
AsyncAction('ListStoreBase.getOfflineChannels', context: context);
late final _$getOfflineChannelsAsyncAction = AsyncAction(
'ListStoreBase.getOfflineChannels',
context: context,
);
@override
Future<void> getOfflineChannels() {
return _$getOfflineChannelsAsyncAction
.run(() => super.getOfflineChannels());
return _$getOfflineChannelsAsyncAction.run(
() => super.getOfflineChannels(),
);
}
late final _$refreshStreamsAsyncAction =
AsyncAction('ListStoreBase.refreshStreams', context: context);
late final _$refreshStreamsAsyncAction = AsyncAction(
'ListStoreBase.refreshStreams',
context: context,
);
@override
Future<void> refreshStreams() {
return _$refreshStreamsAsyncAction.run(() => super.refreshStreams());
}
late final _$_getCategoryDetailsAsyncAction =
AsyncAction('ListStoreBase._getCategoryDetails', context: context);
late final _$_getCategoryDetailsAsyncAction = AsyncAction(
'ListStoreBase._getCategoryDetails',
context: context,
);
@override
Future<void> _getCategoryDetails() {
return _$_getCategoryDetailsAsyncAction
.run(() => super._getCategoryDetails());
return _$_getCategoryDetailsAsyncAction.run(
() => super._getCategoryDetails(),
);
}
@override

View file

@ -93,8 +93,8 @@ class _StreamsListState extends State<StreamsList>
final extraTopPadding = widget.listType == ListType.top
? kToolbarHeight
: widget.listType == ListType.category
? kToolbarHeight + 122
: 0.0;
? kToolbarHeight + 122
: 0.0;
final topPadding = MediaQuery.of(context).padding.top + extraTopPadding;
@ -107,10 +107,7 @@ class _StreamsListState extends State<StreamsList>
if (_listStore.error != null) {
final snackBar = SnackBar(
content: AlertMessage(
message: _listStore.error!,
centered: false,
),
content: AlertMessage(message: _listStore.error!, centered: false),
);
if (!context.mounted) return;
@ -222,11 +219,7 @@ class _StreamsListState extends State<StreamsList>
if (statusWidget != null) {
return CustomScrollView(
slivers: [
SliverFillRemaining(
child: Center(
child: statusWidget,
),
),
SliverFillRemaining(child: Center(child: statusWidget)),
],
);
}
@ -235,11 +228,12 @@ class _StreamsListState extends State<StreamsList>
final unpinnedStreams = widget.listType == ListType.followed
? _listStore.streams
.where(
(stream) =>
!settingsStore.pinnedChannelIds.contains(stream.userId),
)
.toList()
.where(
(stream) => !settingsStore.pinnedChannelIds.contains(
stream.userId,
),
)
.toList()
: _listStore.streams;
final isFollowingTab = widget.listType == ListType.followed;
@ -256,9 +250,7 @@ class _StreamsListState extends State<StreamsList>
physics: const AlwaysScrollableScrollPhysics(),
controller: _listStore.scrollController,
slivers: [
SliverTopPadding(
extraTopPadding: extraTopPadding,
),
SliverTopPadding(extraTopPadding: extraTopPadding),
if (isFollowingTab &&
_listStore.allPinnedChannels.isNotEmpty) ...[
SliverToBoxAdapter(
@ -340,8 +332,8 @@ class _StreamsListState extends State<StreamsList>
_listStore.getStreams();
}
return Observer(
builder: (context) => settingsStore
.largeStreamCard
builder: (context) =>
settingsStore.largeStreamCard
? LargeStreamCard(
key: ValueKey(
unpinnedStreams[index].userId,
@ -403,7 +395,8 @@ class _StreamsListState extends State<StreamsList>
return Observer(
builder: (context) => OfflineChannelCard(
key: ValueKey(
_listStore.offlineChannels[index]
_listStore
.offlineChannels[index]
.broadcasterId,
),
channelInfo:
@ -448,10 +441,7 @@ class _StreamsListState extends State<StreamsList>
class SliverTopPadding extends StatelessWidget {
final double extraTopPadding;
const SliverTopPadding({
super.key,
this.extraTopPadding = 0.0,
});
const SliverTopPadding({super.key, this.extraTopPadding = 0.0});
@override
Widget build(BuildContext context) {
@ -465,16 +455,12 @@ class SliverTopPadding extends StatelessWidget {
/// Helper widget for consistent bottom padding in sliver lists
class SliverBottomPadding extends StatelessWidget {
const SliverBottomPadding({
super.key,
});
const SliverBottomPadding({super.key});
@override
Widget build(BuildContext context) {
return SliverToBoxAdapter(
child: SizedBox(
height: MediaQuery.of(context).padding.bottom,
),
child: SizedBox(height: MediaQuery.of(context).padding.bottom),
);
}
}

View file

@ -13,10 +13,7 @@ import 'package:provider/provider.dart';
class Categories extends StatefulWidget {
final ScrollController scrollController;
const Categories({
super.key,
required this.scrollController,
});
const Categories({super.key, required this.scrollController});
@override
State<Categories> createState() => _CategoriesState();
@ -111,11 +108,7 @@ class _CategoriesState extends State<Categories>
if (statusWidget != null) {
return CustomScrollView(
slivers: [
SliverFillRemaining(
child: Center(
child: statusWidget,
),
),
SliverFillRemaining(child: Center(child: statusWidget)),
],
);
}

View file

@ -12,12 +12,15 @@ mixin _$CategoriesStore on CategoriesStoreBase, Store {
Computed<bool>? _$hasMoreComputed;
@override
bool get hasMore => (_$hasMoreComputed ??= Computed<bool>(() => super.hasMore,
name: 'CategoriesStoreBase.hasMore'))
.value;
bool get hasMore => (_$hasMoreComputed ??= Computed<bool>(
() => super.hasMore,
name: 'CategoriesStoreBase.hasMore',
)).value;
late final _$_isLoadingAtom =
Atom(name: 'CategoriesStoreBase._isLoading', context: context);
late final _$_isLoadingAtom = Atom(
name: 'CategoriesStoreBase._isLoading',
context: context,
);
bool get isLoading {
_$_isLoadingAtom.reportRead();
@ -34,8 +37,10 @@ mixin _$CategoriesStore on CategoriesStoreBase, Store {
});
}
late final _$_categoriesAtom =
Atom(name: 'CategoriesStoreBase._categories', context: context);
late final _$_categoriesAtom = Atom(
name: 'CategoriesStoreBase._categories',
context: context,
);
ObservableList<CategoryTwitch> get categories {
_$_categoriesAtom.reportRead();
@ -52,8 +57,10 @@ mixin _$CategoriesStore on CategoriesStoreBase, Store {
});
}
late final _$_errorAtom =
Atom(name: 'CategoriesStoreBase._error', context: context);
late final _$_errorAtom = Atom(
name: 'CategoriesStoreBase._error',
context: context,
);
String? get error {
_$_errorAtom.reportRead();
@ -70,21 +77,26 @@ mixin _$CategoriesStore on CategoriesStoreBase, Store {
});
}
late final _$getCategoriesAsyncAction =
AsyncAction('CategoriesStoreBase.getCategories', context: context);
late final _$getCategoriesAsyncAction = AsyncAction(
'CategoriesStoreBase.getCategories',
context: context,
);
@override
Future<void> getCategories() {
return _$getCategoriesAsyncAction.run(() => super.getCategories());
}
late final _$CategoriesStoreBaseActionController =
ActionController(name: 'CategoriesStoreBase', context: context);
late final _$CategoriesStoreBaseActionController = ActionController(
name: 'CategoriesStoreBase',
context: context,
);
@override
Future<void> refreshCategories() {
final _$actionInfo = _$CategoriesStoreBaseActionController.startAction(
name: 'CategoriesStoreBase.refreshCategories');
name: 'CategoriesStoreBase.refreshCategories',
);
try {
return super.refreshCategories();
} finally {

View file

@ -26,13 +26,11 @@ class CategoryCard extends StatelessWidget {
return InkWell(
onTap: isTappable
? () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CategoryStreams(
categoryId: category.id,
),
),
)
context,
MaterialPageRoute(
builder: (context) => CategoryStreams(categoryId: category.id),
),
)
: null,
child: Padding(
padding: EdgeInsets.only(
@ -66,10 +64,9 @@ class CategoryCard extends StatelessWidget {
Flexible(
child: Text(
category.name,
style: Theme.of(context)
.textTheme
.bodyLarge
?.copyWith(fontWeight: FontWeight.w600),
style: Theme.of(
context,
).textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w600),
),
),
],

View file

@ -15,10 +15,7 @@ class CategoryStreams extends StatefulWidget {
/// The category id, used for fetching the relevant streams in the [ListStore].
final String categoryId;
const CategoryStreams({
super.key,
required this.categoryId,
});
const CategoryStreams({super.key, required this.categoryId});
@override
State<CategoryStreams> createState() => _CategoryStreamsState();
@ -67,8 +64,9 @@ class _CategoryStreamsState extends State<CategoryStreams> {
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: theme.colorScheme.outlineVariant
.withValues(alpha: 0.3),
color: theme.colorScheme.outlineVariant.withValues(
alpha: 0.3,
),
width: 0.5,
),
),
@ -164,9 +162,7 @@ class _CategoryStreamsState extends State<CategoryStreams> {
class _TransparentCategoryCard extends StatelessWidget {
final dynamic category;
const _TransparentCategoryCard({
required this.category,
});
const _TransparentCategoryCard({required this.category});
@override
Widget build(BuildContext context) {
@ -177,11 +173,7 @@ class _TransparentCategoryCard extends StatelessWidget {
final artHeight = (artWidth * (4 / 3)).toInt();
return Padding(
padding: const EdgeInsets.only(
bottom: 16,
left: 16,
right: 16,
),
padding: const EdgeInsets.only(bottom: 16, left: 16, right: 16),
child: Row(
spacing: 12,
children: [

View file

@ -10,10 +10,7 @@ import 'package:frosty/widgets/blurred_container.dart';
class TopSection extends StatefulWidget {
final HomeStore homeStore;
const TopSection({
super.key,
required this.homeStore,
});
const TopSection({super.key, required this.homeStore});
@override
State<TopSection> createState() => _TopSectionState();
@ -68,17 +65,16 @@ class _TopSectionState extends State<TopSection>
isScrollable: true,
controller: _tabBarController,
tabs: const [
Tab(
child: Text('Streams'),
),
Tab(child: Text('Streams')),
Tab(child: Text('Categories')),
],
),
Observer(
builder: (_) => AnimatedScrollBorder(
scrollController:
widget.homeStore.topSectionScrollControllers[
widget.homeStore.topSectionCurrentIndex],
widget.homeStore.topSectionScrollControllers[widget
.homeStore
.topSectionCurrentIndex],
),
),
],

View file

@ -9,10 +9,7 @@ class LoginWebView extends StatelessWidget {
/// Optional widget to navigate to after successful login
final Widget? routeAfter;
const LoginWebView({
super.key,
this.routeAfter,
});
const LoginWebView({super.key, this.routeAfter});
@override
Widget build(BuildContext context) {

View file

@ -18,9 +18,7 @@ class OnboardingLogin extends StatelessWidget {
buttonText: 'Connect with Twitch',
buttonIcon: const Icon(SimpleIcons.twitch),
skipRoute: const OnboardingSetup(),
route: LoginWebView(
routeAfter: const OnboardingSetup(),
),
route: LoginWebView(routeAfter: const OnboardingSetup()),
);
}
}

View file

@ -37,14 +37,13 @@ class OnboardingScaffold extends StatelessWidget {
]);
if (isLast) {
SharedPreferences.getInstance()
.then((prefs) => prefs.setBool('first_run', false));
SharedPreferences.getInstance().then(
(prefs) => prefs.setBool('first_run', false),
);
}
return Scaffold(
appBar: AppBar(
shape: const Border(),
),
appBar: AppBar(shape: const Border()),
body: SafeArea(
child: SizedBox(
width: double.infinity,
@ -60,10 +59,7 @@ class OnboardingScaffold extends StatelessWidget {
spacing: 12,
children: [
if (showLogo) ...[
SvgPicture.asset(
'assets/icons/logo.svg',
height: 80,
),
SvgPicture.asset('assets/icons/logo.svg', height: 80),
],
Text(
header,
@ -97,8 +93,10 @@ class OnboardingScaffold extends StatelessWidget {
),
if (disclaimer != null)
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
child: Opacity(
opacity: 0.5,
child: Text(
@ -120,9 +118,7 @@ class OnboardingScaffold extends StatelessWidget {
)
: Navigator.push(
context,
MaterialPageRoute(
builder: (context) => route,
),
MaterialPageRoute(builder: (context) => route),
),
icon: buttonIcon ?? const SizedBox(),
label: Text(buttonText ?? 'Next'),
@ -135,9 +131,7 @@ class OnboardingScaffold extends StatelessWidget {
child: TextButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => skipRoute!,
),
MaterialPageRoute(builder: (context) => skipRoute!),
),
child: const Text('Skip'),
),

View file

@ -67,8 +67,9 @@ class OnboardingSetup extends StatelessWidget {
// ignore: deprecated_member_use
settingsStore.accentColor = newColor.value,
enableAlpha: false,
pickerAreaBorderRadius:
const BorderRadius.all(Radius.circular(8)),
pickerAreaBorderRadius: const BorderRadius.all(
Radius.circular(8),
),
labelTypes: const [],
),
),
@ -99,13 +100,11 @@ class OnboardingSetup extends StatelessWidget {
),
recognizer: TapGestureRecognizer()
..onTap = () => launchUrl(
Uri.parse(
'https://recent-messages.robotty.de/',
),
mode: settingsStore.launchUrlExternal
? LaunchMode.externalApplication
: LaunchMode.inAppBrowserView,
),
Uri.parse('https://recent-messages.robotty.de/'),
mode: settingsStore.launchUrlExternal
? LaunchMode.externalApplication
: LaunchMode.inAppBrowserView,
),
),
],
),
@ -123,12 +122,15 @@ class OnboardingSetup extends StatelessWidget {
onChanged: (newValue) {
settingsStore.shareCrashLogsAndAnalytics = newValue;
FirebaseCrashlytics.instance
.setCrashlyticsCollectionEnabled(newValue);
FirebaseAnalytics.instance
.setAnalyticsCollectionEnabled(newValue);
FirebasePerformance.instance
.setPerformanceCollectionEnabled(newValue);
FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(
newValue,
);
FirebaseAnalytics.instance.setAnalyticsCollectionEnabled(
newValue,
);
FirebasePerformance.instance.setPerformanceCollectionEnabled(
newValue,
);
},
),
],

View file

@ -52,9 +52,7 @@ class AccountOptions extends StatelessWidget {
SettingsTileRoute(
leading: const Icon(Icons.block_rounded),
title: 'Blocked users',
child: BlockedUsers(
authStore: authStore,
),
child: BlockedUsers(authStore: authStore),
),
ListTile(
leading: const Icon(Icons.logout_rounded),

View file

@ -9,10 +9,7 @@ import 'package:frosty/widgets/settings_page_layout.dart';
class BlockedUsers extends StatelessWidget {
final AuthStore authStore;
const BlockedUsers({
super.key,
required this.authStore,
});
const BlockedUsers({super.key, required this.authStore});
@override
Widget build(BuildContext context) {
@ -25,10 +22,7 @@ class BlockedUsers extends StatelessWidget {
await authStore.user.refreshBlockedUsers();
},
child: const Center(
child: AlertMessage(
message: 'No blocked users',
vertical: true,
),
child: AlertMessage(message: 'No blocked users', vertical: true),
),
);
}
@ -39,29 +33,25 @@ class BlockedUsers extends StatelessWidget {
HapticFeedback.lightImpact();
await authStore.user.refreshBlockedUsers();
},
children: authStore.user.blockedUsers.map(
(blockedUser) {
final displayName = getReadableName(
blockedUser.displayName,
blockedUser.userLogin,
);
children: authStore.user.blockedUsers.map((blockedUser) {
final displayName = getReadableName(
blockedUser.displayName,
blockedUser.userLogin,
);
return ListTile(
title: Text(displayName),
trailing: TextButton(
style: TextButton.styleFrom(
foregroundColor: Colors.red,
),
onPressed: () => authStore.showBlockDialog(
context,
targetUser: displayName,
targetUserId: blockedUser.userId,
),
child: const Text('Unblock'),
return ListTile(
title: Text(displayName),
trailing: TextButton(
style: TextButton.styleFrom(foregroundColor: Colors.red),
onPressed: () => authStore.showBlockDialog(
context,
targetUser: displayName,
targetUserId: blockedUser.userId,
),
);
},
).toList(),
child: const Text('Unblock'),
),
);
}).toList(),
);
},
);

View file

@ -61,13 +61,12 @@ class ProfileCard extends StatelessWidget {
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoginWebView(),
),
MaterialPageRoute(builder: (context) => LoginWebView()),
),
onLongPress: () async {
final clipboardText =
(await Clipboard.getData(Clipboard.kTextPlain))?.text;
final clipboardText = (await Clipboard.getData(
Clipboard.kTextPlain,
))?.text;
if (clipboardText == null) return;

View file

@ -34,22 +34,17 @@ class _ChatSettingsState extends State<ChatSettings> {
return Observer(
builder: (context) => SettingsPageLayout(
children: [
const SectionHeader(
'Message sizing',
isFirst: true,
),
const SectionHeader('Message sizing', isFirst: true),
ExpansionTile(
title: const Text(
'Preview',
),
title: const Text('Preview'),
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
child: DefaultTextStyle(
style: DefaultTextStyle.of(context)
.style
.copyWith(fontSize: settingsStore.fontSize),
style: DefaultTextStyle.of(
context,
).style.copyWith(fontSize: settingsStore.fontSize),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -67,9 +62,7 @@ class _ChatSettingsState extends State<ChatSettings> {
defaultBadgeSize * settingsStore.badgeScale,
),
),
const TextSpan(
text: ' Badge and emote preview. ',
),
const TextSpan(text: ' Badge and emote preview. '),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: FrostyCachedNetworkImage(
@ -234,8 +227,9 @@ class _ChatSettingsState extends State<ChatSettings> {
landscapeCutoutNames[settingsStore.landscapeCutout.index],
options: landscapeCutoutNames,
onChanged: (newValue) => settingsStore.landscapeCutout =
LandscapeCutoutType
.values[landscapeCutoutNames.indexOf(newValue)],
LandscapeCutoutType.values[landscapeCutoutNames.indexOf(
newValue,
)],
),
SettingsListSlider(
title: 'Chat overlay opacity',
@ -320,13 +314,11 @@ class _ChatSettingsState extends State<ChatSettings> {
),
recognizer: TapGestureRecognizer()
..onTap = () => launchUrl(
Uri.parse(
'https://recent-messages.robotty.de/',
),
mode: settingsStore.launchUrlExternal
? LaunchMode.externalApplication
: LaunchMode.inAppBrowserView,
),
Uri.parse('https://recent-messages.robotty.de/'),
mode: settingsStore.launchUrlExternal
? LaunchMode.externalApplication
: LaunchMode.inAppBrowserView,
),
),
],
),

View file

@ -21,10 +21,7 @@ class GeneralSettings extends StatelessWidget {
return Observer(
builder: (context) => SettingsPageLayout(
children: [
const SectionHeader(
'Theme',
isFirst: true,
),
const SectionHeader('Theme', isFirst: true),
SettingsListSelect(
selectedOption: themeNames[settingsStore.themeType.index],
options: themeNames,

View file

@ -16,10 +16,7 @@ import 'package:url_launcher/url_launcher.dart';
class OtherSettings extends StatefulWidget {
final SettingsStore settingsStore;
const OtherSettings({
super.key,
required this.settingsStore,
});
const OtherSettings({super.key, required this.settingsStore});
@override
State<OtherSettings> createState() => _OtherSettingsState();
@ -89,11 +86,9 @@ class _OtherSettingsState extends State<OtherSettings> {
ListTile(
leading: const Icon(Icons.notes_rounded),
title: const Text('Release notes'),
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const ReleaseNotes(),
),
),
onTap: () => Navigator.of(
context,
).push(MaterialPageRoute(builder: (context) => const ReleaseNotes())),
),
ListTile(
leading: const Icon(Icons.launch_rounded),
@ -141,12 +136,15 @@ class _OtherSettingsState extends State<OtherSettings> {
onChanged: (newValue) {
widget.settingsStore.shareCrashLogsAndAnalytics = newValue;
FirebaseCrashlytics.instance
.setCrashlyticsCollectionEnabled(newValue);
FirebaseAnalytics.instance
.setAnalyticsCollectionEnabled(newValue);
FirebasePerformance.instance
.setPerformanceCollectionEnabled(newValue);
FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(
newValue,
);
FirebaseAnalytics.instance.setAnalyticsCollectionEnabled(
newValue,
);
FirebasePerformance.instance.setPerformanceCollectionEnabled(
newValue,
);
},
),
),

View file

@ -81,10 +81,7 @@ class Settings extends StatelessWidget {
top: MediaQuery.of(context).padding.top + kToolbarHeight + 8,
),
children: [
const SectionHeader(
'Account',
isFirst: true,
),
const SectionHeader('Account', isFirst: true),
ProfileCard(authStore: context.read<AuthStore>()),
const SectionHeader('Customize'),
SettingsTileRoute(

View file

@ -42,8 +42,10 @@ abstract class AuthBase with Store {
/// Authentication headers for Twitch API requests.
@computed
Map<String, String> get headersTwitch =>
{'Authorization': 'Bearer $_token', 'Client-Id': clientId};
Map<String, String> get headersTwitch => {
'Authorization': 'Bearer $_token',
'Client-Id': clientId,
};
/// Error flag that will be non-null and contain an error message if login failed.
@readonly
@ -70,8 +72,9 @@ abstract class AuthBase with Store {
if (request.url == 'https://www.twitch.tv/?no-reload=true') {
if (routeAfter != null) {
navigatorKey.currentState?.pop();
navigatorKey.currentState
?.push(MaterialPageRoute(builder: (context) => routeAfter));
navigatorKey.currentState?.push(
MaterialPageRoute(builder: (context) => routeAfter),
);
} else {
// Pop twice, once to dismiss the WebView and again to dismiss the Login dialog.
navigatorKey.currentState?.pop();
@ -97,8 +100,7 @@ abstract class AuthBase with Store {
},
onPageFinished: (_) async {
try {
await webViewController.runJavaScript(
'''
await webViewController.runJavaScript('''
{
function modifyElement(element) {
element.style.maxHeight = '20vh';
@ -123,8 +125,7 @@ abstract class AuthBase with Store {
subtree: true
});
}
''',
);
''');
} catch (e) {
debugPrint('Auth WebView JavaScript error: $e');
}
@ -167,10 +168,7 @@ abstract class AuthBase with Store {
if (isBlocked) {
user.unblock(targetId: targetUserId);
} else {
user.block(
targetId: targetUserId,
displayName: targetUser,
);
user.block(targetId: targetUserId, displayName: targetUser);
}
Navigator.pop(context);
}
@ -185,10 +183,7 @@ abstract class AuthBase with Store {
onPressed: Navigator.of(context).pop,
child: const Text('Cancel'),
),
TextButton(
onPressed: onPressed,
child: const Text('Yes'),
),
TextButton(onPressed: onPressed, child: const Text('Yes')),
],
),
);

View file

@ -12,10 +12,11 @@ mixin _$AuthStore on AuthBase, Store {
Computed<Map<String, String>>? _$headersTwitchComputed;
@override
Map<String, String> get headersTwitch => (_$headersTwitchComputed ??=
Computed<Map<String, String>>(() => super.headersTwitch,
name: 'AuthBase.headersTwitch'))
.value;
Map<String, String> get headersTwitch =>
(_$headersTwitchComputed ??= Computed<Map<String, String>>(
() => super.headersTwitch,
name: 'AuthBase.headersTwitch',
)).value;
late final _$_tokenAtom = Atom(name: 'AuthBase._token', context: context);
@ -34,8 +35,10 @@ mixin _$AuthStore on AuthBase, Store {
});
}
late final _$_isLoggedInAtom =
Atom(name: 'AuthBase._isLoggedIn', context: context);
late final _$_isLoggedInAtom = Atom(
name: 'AuthBase._isLoggedIn',
context: context,
);
bool get isLoggedIn {
_$_isLoggedInAtom.reportRead();
@ -76,16 +79,20 @@ mixin _$AuthStore on AuthBase, Store {
return _$initAsyncAction.run(() => super.init());
}
late final _$loginAsyncAction =
AsyncAction('AuthBase.login', context: context);
late final _$loginAsyncAction = AsyncAction(
'AuthBase.login',
context: context,
);
@override
Future<void> login({required String token}) {
return _$loginAsyncAction.run(() => super.login(token: token));
}
late final _$logoutAsyncAction =
AsyncAction('AuthBase.logout', context: context);
late final _$logoutAsyncAction = AsyncAction(
'AuthBase.logout',
context: context,
);
@override
Future<void> logout() {

View file

@ -217,7 +217,6 @@ abstract class _SettingsStoreBase with Store {
@observable
var emoteMenuButtonOnLeft = defaultEmoteMenuButtonOnLeft;
// Landscape mode options
@JsonKey(defaultValue: defaultLandscapeChatLeftSide)
@observable
@ -376,25 +375,12 @@ abstract class _SettingsStoreBase with Store {
const themeNames = ['System', 'Light', 'Dark'];
enum ThemeType {
system,
light,
dark,
}
enum ThemeType { system, light, dark }
const timestampNames = ['Disabled', '12-hour', '24-hour'];
enum TimestampType {
disabled,
twelve,
twentyFour,
}
enum TimestampType { disabled, twelve, twentyFour }
const landscapeCutoutNames = ['None', 'Left', 'Right', 'Both'];
enum LandscapeCutoutType {
none,
left,
right,
both,
}
enum LandscapeCutoutType { none, left, right, both }

View file

@ -6,120 +6,131 @@ part of 'settings_store.dart';
// JsonSerializableGenerator
// **************************************************************************
SettingsStore _$SettingsStoreFromJson(Map<String, dynamic> json) =>
SettingsStore()
..themeType = $enumDecodeNullable(_$ThemeTypeEnumMap, json['themeType'],
unknownValue: ThemeType.system) ??
ThemeType.system
..accentColor = (json['accentColor'] as num?)?.toInt() ?? 4287710975
..showThumbnails = json['showThumbnails'] as bool? ?? true
..largeStreamCard = json['largeStreamCard'] as bool? ?? false
..launchUrlExternal = json['launchUrlExternal'] as bool? ?? false
..showVideo = json['showVideo'] as bool? ?? true
..defaultToHighestQuality =
json['defaultToHighestQuality'] as bool? ?? false
..useEnhancedRendering = json['useEnhancedRendering'] as bool? ?? false
..showOverlay = json['showOverlay'] as bool? ?? true
..toggleableOverlay = json['toggleableOverlay'] as bool? ?? false
..overlayOpacity = (json['overlayOpacity'] as num?)?.toDouble() ?? 0.0
..badgeScale = (json['badgeScale'] as num?)?.toDouble() ?? 1.0
..emoteScale = (json['emoteScale'] as num?)?.toDouble() ?? 1.0
..messageScale = (json['messageScale'] as num?)?.toDouble() ?? 1.0
..messageSpacing = (json['messageSpacing'] as num?)?.toDouble() ?? 8.0
..fontSize = (json['fontSize'] as num?)?.toDouble() ?? 12.0
..showDeletedMessages = json['showDeletedMessages'] as bool? ?? false
..showChatMessageDividers =
json['showChatMessageDividers'] as bool? ?? false
..timestampType = $enumDecodeNullable(
_$TimestampTypeEnumMap, json['timestampType'],
unknownValue: TimestampType.disabled) ??
TimestampType.disabled
..autoSyncChatDelay = json['autoSyncChatDelay'] as bool? ?? false
..chatDelay = (json['chatDelay'] as num?)?.toDouble() ?? 0.0
..highlightFirstTimeChatter =
json['highlightFirstTimeChatter'] as bool? ?? true
..showUserNotices = json['showUserNotices'] as bool? ?? true
..emoteMenuButtonOnLeft = json['emoteMenuButtonOnLeft'] as bool? ?? false
..landscapeChatLeftSide = json['landscapeChatLeftSide'] as bool? ?? false
..landscapeForceVerticalChat =
json['landscapeForceVerticalChat'] as bool? ?? false
..landscapeCutout = $enumDecodeNullable(
_$LandscapeCutoutTypeEnumMap, json['landscapeCutout']) ??
LandscapeCutoutType.none
..chatWidth = (json['chatWidth'] as num?)?.toDouble() ?? 0.2
..fullScreenChatOverlayOpacity =
(json['fullScreenChatOverlayOpacity'] as num?)?.toDouble() ?? 0.5
..autocomplete = json['autocomplete'] as bool? ?? true
..showTwitchEmotes = json['showTwitchEmotes'] as bool? ?? true
..showTwitchBadges = json['showTwitchBadges'] as bool? ?? true
..show7TVEmotes = json['show7TVEmotes'] as bool? ?? true
..showBTTVEmotes = json['showBTTVEmotes'] as bool? ?? true
..showBTTVBadges = json['showBTTVBadges'] as bool? ?? true
..showFFZEmotes = json['showFFZEmotes'] as bool? ?? true
..showFFZBadges = json['showFFZBadges'] as bool? ?? true
..showRecentMessages = json['showRecentMessages'] as bool? ?? false
..mutedWords = (json['mutedWords'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[]
..matchWholeWord = json['matchWholeWord'] as bool? ?? true
..shareCrashLogsAndAnalytics =
json['shareCrashLogsAndAnalytics'] as bool? ?? true
..fullScreen = json['fullScreen'] as bool? ?? false
..fullScreenChatOverlay = json['fullScreenChatOverlay'] as bool? ?? false
..pinnedChannelIds = (json['pinnedChannelIds'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[];
SettingsStore _$SettingsStoreFromJson(
Map<String, dynamic> json,
) => SettingsStore()
..themeType =
$enumDecodeNullable(
_$ThemeTypeEnumMap,
json['themeType'],
unknownValue: ThemeType.system,
) ??
ThemeType.system
..accentColor = (json['accentColor'] as num?)?.toInt() ?? 4287710975
..showThumbnails = json['showThumbnails'] as bool? ?? true
..largeStreamCard = json['largeStreamCard'] as bool? ?? false
..launchUrlExternal = json['launchUrlExternal'] as bool? ?? false
..showVideo = json['showVideo'] as bool? ?? true
..defaultToHighestQuality = json['defaultToHighestQuality'] as bool? ?? false
..useEnhancedRendering = json['useEnhancedRendering'] as bool? ?? false
..showOverlay = json['showOverlay'] as bool? ?? true
..toggleableOverlay = json['toggleableOverlay'] as bool? ?? false
..overlayOpacity = (json['overlayOpacity'] as num?)?.toDouble() ?? 0.0
..badgeScale = (json['badgeScale'] as num?)?.toDouble() ?? 1.0
..emoteScale = (json['emoteScale'] as num?)?.toDouble() ?? 1.0
..messageScale = (json['messageScale'] as num?)?.toDouble() ?? 1.0
..messageSpacing = (json['messageSpacing'] as num?)?.toDouble() ?? 8.0
..fontSize = (json['fontSize'] as num?)?.toDouble() ?? 12.0
..showDeletedMessages = json['showDeletedMessages'] as bool? ?? false
..showChatMessageDividers = json['showChatMessageDividers'] as bool? ?? false
..timestampType =
$enumDecodeNullable(
_$TimestampTypeEnumMap,
json['timestampType'],
unknownValue: TimestampType.disabled,
) ??
TimestampType.disabled
..autoSyncChatDelay = json['autoSyncChatDelay'] as bool? ?? false
..chatDelay = (json['chatDelay'] as num?)?.toDouble() ?? 0.0
..highlightFirstTimeChatter =
json['highlightFirstTimeChatter'] as bool? ?? true
..showUserNotices = json['showUserNotices'] as bool? ?? true
..emoteMenuButtonOnLeft = json['emoteMenuButtonOnLeft'] as bool? ?? false
..landscapeChatLeftSide = json['landscapeChatLeftSide'] as bool? ?? false
..landscapeForceVerticalChat =
json['landscapeForceVerticalChat'] as bool? ?? false
..landscapeCutout =
$enumDecodeNullable(
_$LandscapeCutoutTypeEnumMap,
json['landscapeCutout'],
) ??
LandscapeCutoutType.none
..chatWidth = (json['chatWidth'] as num?)?.toDouble() ?? 0.2
..fullScreenChatOverlayOpacity =
(json['fullScreenChatOverlayOpacity'] as num?)?.toDouble() ?? 0.5
..autocomplete = json['autocomplete'] as bool? ?? true
..showTwitchEmotes = json['showTwitchEmotes'] as bool? ?? true
..showTwitchBadges = json['showTwitchBadges'] as bool? ?? true
..show7TVEmotes = json['show7TVEmotes'] as bool? ?? true
..showBTTVEmotes = json['showBTTVEmotes'] as bool? ?? true
..showBTTVBadges = json['showBTTVBadges'] as bool? ?? true
..showFFZEmotes = json['showFFZEmotes'] as bool? ?? true
..showFFZBadges = json['showFFZBadges'] as bool? ?? true
..showRecentMessages = json['showRecentMessages'] as bool? ?? false
..mutedWords =
(json['mutedWords'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[]
..matchWholeWord = json['matchWholeWord'] as bool? ?? true
..shareCrashLogsAndAnalytics =
json['shareCrashLogsAndAnalytics'] as bool? ?? true
..fullScreen = json['fullScreen'] as bool? ?? false
..fullScreenChatOverlay = json['fullScreenChatOverlay'] as bool? ?? false
..pinnedChannelIds =
(json['pinnedChannelIds'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[];
Map<String, dynamic> _$SettingsStoreToJson(SettingsStore instance) =>
<String, dynamic>{
'themeType': _$ThemeTypeEnumMap[instance.themeType]!,
'accentColor': instance.accentColor,
'showThumbnails': instance.showThumbnails,
'largeStreamCard': instance.largeStreamCard,
'launchUrlExternal': instance.launchUrlExternal,
'showVideo': instance.showVideo,
'defaultToHighestQuality': instance.defaultToHighestQuality,
'useEnhancedRendering': instance.useEnhancedRendering,
'showOverlay': instance.showOverlay,
'toggleableOverlay': instance.toggleableOverlay,
'overlayOpacity': instance.overlayOpacity,
'badgeScale': instance.badgeScale,
'emoteScale': instance.emoteScale,
'messageScale': instance.messageScale,
'messageSpacing': instance.messageSpacing,
'fontSize': instance.fontSize,
'showDeletedMessages': instance.showDeletedMessages,
'showChatMessageDividers': instance.showChatMessageDividers,
'timestampType': _$TimestampTypeEnumMap[instance.timestampType]!,
'autoSyncChatDelay': instance.autoSyncChatDelay,
'chatDelay': instance.chatDelay,
'highlightFirstTimeChatter': instance.highlightFirstTimeChatter,
'showUserNotices': instance.showUserNotices,
'emoteMenuButtonOnLeft': instance.emoteMenuButtonOnLeft,
'landscapeChatLeftSide': instance.landscapeChatLeftSide,
'landscapeForceVerticalChat': instance.landscapeForceVerticalChat,
'landscapeCutout':
_$LandscapeCutoutTypeEnumMap[instance.landscapeCutout]!,
'chatWidth': instance.chatWidth,
'fullScreenChatOverlayOpacity': instance.fullScreenChatOverlayOpacity,
'autocomplete': instance.autocomplete,
'showTwitchEmotes': instance.showTwitchEmotes,
'showTwitchBadges': instance.showTwitchBadges,
'show7TVEmotes': instance.show7TVEmotes,
'showBTTVEmotes': instance.showBTTVEmotes,
'showBTTVBadges': instance.showBTTVBadges,
'showFFZEmotes': instance.showFFZEmotes,
'showFFZBadges': instance.showFFZBadges,
'showRecentMessages': instance.showRecentMessages,
'mutedWords': instance.mutedWords,
'matchWholeWord': instance.matchWholeWord,
'shareCrashLogsAndAnalytics': instance.shareCrashLogsAndAnalytics,
'fullScreen': instance.fullScreen,
'fullScreenChatOverlay': instance.fullScreenChatOverlay,
'pinnedChannelIds': instance.pinnedChannelIds,
};
Map<String, dynamic> _$SettingsStoreToJson(
SettingsStore instance,
) => <String, dynamic>{
'themeType': _$ThemeTypeEnumMap[instance.themeType]!,
'accentColor': instance.accentColor,
'showThumbnails': instance.showThumbnails,
'largeStreamCard': instance.largeStreamCard,
'launchUrlExternal': instance.launchUrlExternal,
'showVideo': instance.showVideo,
'defaultToHighestQuality': instance.defaultToHighestQuality,
'useEnhancedRendering': instance.useEnhancedRendering,
'showOverlay': instance.showOverlay,
'toggleableOverlay': instance.toggleableOverlay,
'overlayOpacity': instance.overlayOpacity,
'badgeScale': instance.badgeScale,
'emoteScale': instance.emoteScale,
'messageScale': instance.messageScale,
'messageSpacing': instance.messageSpacing,
'fontSize': instance.fontSize,
'showDeletedMessages': instance.showDeletedMessages,
'showChatMessageDividers': instance.showChatMessageDividers,
'timestampType': _$TimestampTypeEnumMap[instance.timestampType]!,
'autoSyncChatDelay': instance.autoSyncChatDelay,
'chatDelay': instance.chatDelay,
'highlightFirstTimeChatter': instance.highlightFirstTimeChatter,
'showUserNotices': instance.showUserNotices,
'emoteMenuButtonOnLeft': instance.emoteMenuButtonOnLeft,
'landscapeChatLeftSide': instance.landscapeChatLeftSide,
'landscapeForceVerticalChat': instance.landscapeForceVerticalChat,
'landscapeCutout': _$LandscapeCutoutTypeEnumMap[instance.landscapeCutout]!,
'chatWidth': instance.chatWidth,
'fullScreenChatOverlayOpacity': instance.fullScreenChatOverlayOpacity,
'autocomplete': instance.autocomplete,
'showTwitchEmotes': instance.showTwitchEmotes,
'showTwitchBadges': instance.showTwitchBadges,
'show7TVEmotes': instance.show7TVEmotes,
'showBTTVEmotes': instance.showBTTVEmotes,
'showBTTVBadges': instance.showBTTVBadges,
'showFFZEmotes': instance.showFFZEmotes,
'showFFZBadges': instance.showFFZBadges,
'showRecentMessages': instance.showRecentMessages,
'mutedWords': instance.mutedWords,
'matchWholeWord': instance.matchWholeWord,
'shareCrashLogsAndAnalytics': instance.shareCrashLogsAndAnalytics,
'fullScreen': instance.fullScreen,
'fullScreenChatOverlay': instance.fullScreenChatOverlay,
'pinnedChannelIds': instance.pinnedChannelIds,
};
const _$ThemeTypeEnumMap = {
ThemeType.system: 'system',
@ -147,8 +158,10 @@ const _$LandscapeCutoutTypeEnumMap = {
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers
mixin _$SettingsStore on _SettingsStoreBase, Store {
late final _$themeTypeAtom =
Atom(name: '_SettingsStoreBase.themeType', context: context);
late final _$themeTypeAtom = Atom(
name: '_SettingsStoreBase.themeType',
context: context,
);
@override
ThemeType get themeType {
@ -163,8 +176,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$accentColorAtom =
Atom(name: '_SettingsStoreBase.accentColor', context: context);
late final _$accentColorAtom = Atom(
name: '_SettingsStoreBase.accentColor',
context: context,
);
@override
int get accentColor {
@ -179,8 +194,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$showThumbnailsAtom =
Atom(name: '_SettingsStoreBase.showThumbnails', context: context);
late final _$showThumbnailsAtom = Atom(
name: '_SettingsStoreBase.showThumbnails',
context: context,
);
@override
bool get showThumbnails {
@ -195,8 +212,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$largeStreamCardAtom =
Atom(name: '_SettingsStoreBase.largeStreamCard', context: context);
late final _$largeStreamCardAtom = Atom(
name: '_SettingsStoreBase.largeStreamCard',
context: context,
);
@override
bool get largeStreamCard {
@ -211,8 +230,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$launchUrlExternalAtom =
Atom(name: '_SettingsStoreBase.launchUrlExternal', context: context);
late final _$launchUrlExternalAtom = Atom(
name: '_SettingsStoreBase.launchUrlExternal',
context: context,
);
@override
bool get launchUrlExternal {
@ -227,8 +248,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$showVideoAtom =
Atom(name: '_SettingsStoreBase.showVideo', context: context);
late final _$showVideoAtom = Atom(
name: '_SettingsStoreBase.showVideo',
context: context,
);
@override
bool get showVideo {
@ -244,7 +267,9 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
}
late final _$defaultToHighestQualityAtom = Atom(
name: '_SettingsStoreBase.defaultToHighestQuality', context: context);
name: '_SettingsStoreBase.defaultToHighestQuality',
context: context,
);
@override
bool get defaultToHighestQuality {
@ -254,14 +279,19 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
@override
set defaultToHighestQuality(bool value) {
_$defaultToHighestQualityAtom
.reportWrite(value, super.defaultToHighestQuality, () {
super.defaultToHighestQuality = value;
});
_$defaultToHighestQualityAtom.reportWrite(
value,
super.defaultToHighestQuality,
() {
super.defaultToHighestQuality = value;
},
);
}
late final _$useEnhancedRenderingAtom =
Atom(name: '_SettingsStoreBase.useEnhancedRendering', context: context);
late final _$useEnhancedRenderingAtom = Atom(
name: '_SettingsStoreBase.useEnhancedRendering',
context: context,
);
@override
bool get useEnhancedRendering {
@ -271,14 +301,19 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
@override
set useEnhancedRendering(bool value) {
_$useEnhancedRenderingAtom.reportWrite(value, super.useEnhancedRendering,
() {
super.useEnhancedRendering = value;
});
_$useEnhancedRenderingAtom.reportWrite(
value,
super.useEnhancedRendering,
() {
super.useEnhancedRendering = value;
},
);
}
late final _$showOverlayAtom =
Atom(name: '_SettingsStoreBase.showOverlay', context: context);
late final _$showOverlayAtom = Atom(
name: '_SettingsStoreBase.showOverlay',
context: context,
);
@override
bool get showOverlay {
@ -293,8 +328,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$toggleableOverlayAtom =
Atom(name: '_SettingsStoreBase.toggleableOverlay', context: context);
late final _$toggleableOverlayAtom = Atom(
name: '_SettingsStoreBase.toggleableOverlay',
context: context,
);
@override
bool get toggleableOverlay {
@ -309,8 +346,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$overlayOpacityAtom =
Atom(name: '_SettingsStoreBase.overlayOpacity', context: context);
late final _$overlayOpacityAtom = Atom(
name: '_SettingsStoreBase.overlayOpacity',
context: context,
);
@override
double get overlayOpacity {
@ -325,8 +364,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$badgeScaleAtom =
Atom(name: '_SettingsStoreBase.badgeScale', context: context);
late final _$badgeScaleAtom = Atom(
name: '_SettingsStoreBase.badgeScale',
context: context,
);
@override
double get badgeScale {
@ -341,8 +382,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$emoteScaleAtom =
Atom(name: '_SettingsStoreBase.emoteScale', context: context);
late final _$emoteScaleAtom = Atom(
name: '_SettingsStoreBase.emoteScale',
context: context,
);
@override
double get emoteScale {
@ -357,8 +400,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$messageScaleAtom =
Atom(name: '_SettingsStoreBase.messageScale', context: context);
late final _$messageScaleAtom = Atom(
name: '_SettingsStoreBase.messageScale',
context: context,
);
@override
double get messageScale {
@ -373,8 +418,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$messageSpacingAtom =
Atom(name: '_SettingsStoreBase.messageSpacing', context: context);
late final _$messageSpacingAtom = Atom(
name: '_SettingsStoreBase.messageSpacing',
context: context,
);
@override
double get messageSpacing {
@ -389,8 +436,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$fontSizeAtom =
Atom(name: '_SettingsStoreBase.fontSize', context: context);
late final _$fontSizeAtom = Atom(
name: '_SettingsStoreBase.fontSize',
context: context,
);
@override
double get fontSize {
@ -405,8 +454,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$showDeletedMessagesAtom =
Atom(name: '_SettingsStoreBase.showDeletedMessages', context: context);
late final _$showDeletedMessagesAtom = Atom(
name: '_SettingsStoreBase.showDeletedMessages',
context: context,
);
@override
bool get showDeletedMessages {
@ -422,7 +473,9 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
}
late final _$showChatMessageDividersAtom = Atom(
name: '_SettingsStoreBase.showChatMessageDividers', context: context);
name: '_SettingsStoreBase.showChatMessageDividers',
context: context,
);
@override
bool get showChatMessageDividers {
@ -432,14 +485,19 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
@override
set showChatMessageDividers(bool value) {
_$showChatMessageDividersAtom
.reportWrite(value, super.showChatMessageDividers, () {
super.showChatMessageDividers = value;
});
_$showChatMessageDividersAtom.reportWrite(
value,
super.showChatMessageDividers,
() {
super.showChatMessageDividers = value;
},
);
}
late final _$timestampTypeAtom =
Atom(name: '_SettingsStoreBase.timestampType', context: context);
late final _$timestampTypeAtom = Atom(
name: '_SettingsStoreBase.timestampType',
context: context,
);
@override
TimestampType get timestampType {
@ -454,8 +512,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$autoSyncChatDelayAtom =
Atom(name: '_SettingsStoreBase.autoSyncChatDelay', context: context);
late final _$autoSyncChatDelayAtom = Atom(
name: '_SettingsStoreBase.autoSyncChatDelay',
context: context,
);
@override
bool get autoSyncChatDelay {
@ -470,8 +530,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$chatDelayAtom =
Atom(name: '_SettingsStoreBase.chatDelay', context: context);
late final _$chatDelayAtom = Atom(
name: '_SettingsStoreBase.chatDelay',
context: context,
);
@override
double get chatDelay {
@ -487,7 +549,9 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
}
late final _$highlightFirstTimeChatterAtom = Atom(
name: '_SettingsStoreBase.highlightFirstTimeChatter', context: context);
name: '_SettingsStoreBase.highlightFirstTimeChatter',
context: context,
);
@override
bool get highlightFirstTimeChatter {
@ -497,14 +561,19 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
@override
set highlightFirstTimeChatter(bool value) {
_$highlightFirstTimeChatterAtom
.reportWrite(value, super.highlightFirstTimeChatter, () {
super.highlightFirstTimeChatter = value;
});
_$highlightFirstTimeChatterAtom.reportWrite(
value,
super.highlightFirstTimeChatter,
() {
super.highlightFirstTimeChatter = value;
},
);
}
late final _$showUserNoticesAtom =
Atom(name: '_SettingsStoreBase.showUserNotices', context: context);
late final _$showUserNoticesAtom = Atom(
name: '_SettingsStoreBase.showUserNotices',
context: context,
);
@override
bool get showUserNotices {
@ -519,8 +588,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$emoteMenuButtonOnLeftAtom =
Atom(name: '_SettingsStoreBase.emoteMenuButtonOnLeft', context: context);
late final _$emoteMenuButtonOnLeftAtom = Atom(
name: '_SettingsStoreBase.emoteMenuButtonOnLeft',
context: context,
);
@override
bool get emoteMenuButtonOnLeft {
@ -530,14 +601,19 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
@override
set emoteMenuButtonOnLeft(bool value) {
_$emoteMenuButtonOnLeftAtom.reportWrite(value, super.emoteMenuButtonOnLeft,
() {
super.emoteMenuButtonOnLeft = value;
});
_$emoteMenuButtonOnLeftAtom.reportWrite(
value,
super.emoteMenuButtonOnLeft,
() {
super.emoteMenuButtonOnLeft = value;
},
);
}
late final _$landscapeChatLeftSideAtom =
Atom(name: '_SettingsStoreBase.landscapeChatLeftSide', context: context);
late final _$landscapeChatLeftSideAtom = Atom(
name: '_SettingsStoreBase.landscapeChatLeftSide',
context: context,
);
@override
bool get landscapeChatLeftSide {
@ -547,14 +623,19 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
@override
set landscapeChatLeftSide(bool value) {
_$landscapeChatLeftSideAtom.reportWrite(value, super.landscapeChatLeftSide,
() {
super.landscapeChatLeftSide = value;
});
_$landscapeChatLeftSideAtom.reportWrite(
value,
super.landscapeChatLeftSide,
() {
super.landscapeChatLeftSide = value;
},
);
}
late final _$landscapeForceVerticalChatAtom = Atom(
name: '_SettingsStoreBase.landscapeForceVerticalChat', context: context);
name: '_SettingsStoreBase.landscapeForceVerticalChat',
context: context,
);
@override
bool get landscapeForceVerticalChat {
@ -564,14 +645,19 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
@override
set landscapeForceVerticalChat(bool value) {
_$landscapeForceVerticalChatAtom
.reportWrite(value, super.landscapeForceVerticalChat, () {
super.landscapeForceVerticalChat = value;
});
_$landscapeForceVerticalChatAtom.reportWrite(
value,
super.landscapeForceVerticalChat,
() {
super.landscapeForceVerticalChat = value;
},
);
}
late final _$landscapeCutoutAtom =
Atom(name: '_SettingsStoreBase.landscapeCutout', context: context);
late final _$landscapeCutoutAtom = Atom(
name: '_SettingsStoreBase.landscapeCutout',
context: context,
);
@override
LandscapeCutoutType get landscapeCutout {
@ -586,8 +672,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$chatWidthAtom =
Atom(name: '_SettingsStoreBase.chatWidth', context: context);
late final _$chatWidthAtom = Atom(
name: '_SettingsStoreBase.chatWidth',
context: context,
);
@override
double get chatWidth {
@ -603,8 +691,9 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
}
late final _$fullScreenChatOverlayOpacityAtom = Atom(
name: '_SettingsStoreBase.fullScreenChatOverlayOpacity',
context: context);
name: '_SettingsStoreBase.fullScreenChatOverlayOpacity',
context: context,
);
@override
double get fullScreenChatOverlayOpacity {
@ -614,14 +703,19 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
@override
set fullScreenChatOverlayOpacity(double value) {
_$fullScreenChatOverlayOpacityAtom
.reportWrite(value, super.fullScreenChatOverlayOpacity, () {
super.fullScreenChatOverlayOpacity = value;
});
_$fullScreenChatOverlayOpacityAtom.reportWrite(
value,
super.fullScreenChatOverlayOpacity,
() {
super.fullScreenChatOverlayOpacity = value;
},
);
}
late final _$autocompleteAtom =
Atom(name: '_SettingsStoreBase.autocomplete', context: context);
late final _$autocompleteAtom = Atom(
name: '_SettingsStoreBase.autocomplete',
context: context,
);
@override
bool get autocomplete {
@ -636,8 +730,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$showTwitchEmotesAtom =
Atom(name: '_SettingsStoreBase.showTwitchEmotes', context: context);
late final _$showTwitchEmotesAtom = Atom(
name: '_SettingsStoreBase.showTwitchEmotes',
context: context,
);
@override
bool get showTwitchEmotes {
@ -652,8 +748,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$showTwitchBadgesAtom =
Atom(name: '_SettingsStoreBase.showTwitchBadges', context: context);
late final _$showTwitchBadgesAtom = Atom(
name: '_SettingsStoreBase.showTwitchBadges',
context: context,
);
@override
bool get showTwitchBadges {
@ -668,8 +766,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$show7TVEmotesAtom =
Atom(name: '_SettingsStoreBase.show7TVEmotes', context: context);
late final _$show7TVEmotesAtom = Atom(
name: '_SettingsStoreBase.show7TVEmotes',
context: context,
);
@override
bool get show7TVEmotes {
@ -684,8 +784,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$showBTTVEmotesAtom =
Atom(name: '_SettingsStoreBase.showBTTVEmotes', context: context);
late final _$showBTTVEmotesAtom = Atom(
name: '_SettingsStoreBase.showBTTVEmotes',
context: context,
);
@override
bool get showBTTVEmotes {
@ -700,8 +802,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$showBTTVBadgesAtom =
Atom(name: '_SettingsStoreBase.showBTTVBadges', context: context);
late final _$showBTTVBadgesAtom = Atom(
name: '_SettingsStoreBase.showBTTVBadges',
context: context,
);
@override
bool get showBTTVBadges {
@ -716,8 +820,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$showFFZEmotesAtom =
Atom(name: '_SettingsStoreBase.showFFZEmotes', context: context);
late final _$showFFZEmotesAtom = Atom(
name: '_SettingsStoreBase.showFFZEmotes',
context: context,
);
@override
bool get showFFZEmotes {
@ -732,8 +838,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$showFFZBadgesAtom =
Atom(name: '_SettingsStoreBase.showFFZBadges', context: context);
late final _$showFFZBadgesAtom = Atom(
name: '_SettingsStoreBase.showFFZBadges',
context: context,
);
@override
bool get showFFZBadges {
@ -748,8 +856,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$showRecentMessagesAtom =
Atom(name: '_SettingsStoreBase.showRecentMessages', context: context);
late final _$showRecentMessagesAtom = Atom(
name: '_SettingsStoreBase.showRecentMessages',
context: context,
);
@override
bool get showRecentMessages {
@ -764,8 +874,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$mutedWordsAtom =
Atom(name: '_SettingsStoreBase.mutedWords', context: context);
late final _$mutedWordsAtom = Atom(
name: '_SettingsStoreBase.mutedWords',
context: context,
);
@override
List<String> get mutedWords {
@ -780,8 +892,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$matchWholeWordAtom =
Atom(name: '_SettingsStoreBase.matchWholeWord', context: context);
late final _$matchWholeWordAtom = Atom(
name: '_SettingsStoreBase.matchWholeWord',
context: context,
);
@override
bool get matchWholeWord {
@ -797,7 +911,9 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
}
late final _$shareCrashLogsAndAnalyticsAtom = Atom(
name: '_SettingsStoreBase.shareCrashLogsAndAnalytics', context: context);
name: '_SettingsStoreBase.shareCrashLogsAndAnalytics',
context: context,
);
@override
bool get shareCrashLogsAndAnalytics {
@ -807,14 +923,19 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
@override
set shareCrashLogsAndAnalytics(bool value) {
_$shareCrashLogsAndAnalyticsAtom
.reportWrite(value, super.shareCrashLogsAndAnalytics, () {
super.shareCrashLogsAndAnalytics = value;
});
_$shareCrashLogsAndAnalyticsAtom.reportWrite(
value,
super.shareCrashLogsAndAnalytics,
() {
super.shareCrashLogsAndAnalytics = value;
},
);
}
late final _$fullScreenAtom =
Atom(name: '_SettingsStoreBase.fullScreen', context: context);
late final _$fullScreenAtom = Atom(
name: '_SettingsStoreBase.fullScreen',
context: context,
);
@override
bool get fullScreen {
@ -829,8 +950,10 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$fullScreenChatOverlayAtom =
Atom(name: '_SettingsStoreBase.fullScreenChatOverlay', context: context);
late final _$fullScreenChatOverlayAtom = Atom(
name: '_SettingsStoreBase.fullScreenChatOverlay',
context: context,
);
@override
bool get fullScreenChatOverlay {
@ -840,14 +963,19 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
@override
set fullScreenChatOverlay(bool value) {
_$fullScreenChatOverlayAtom.reportWrite(value, super.fullScreenChatOverlay,
() {
super.fullScreenChatOverlay = value;
});
_$fullScreenChatOverlayAtom.reportWrite(
value,
super.fullScreenChatOverlay,
() {
super.fullScreenChatOverlay = value;
},
);
}
late final _$pinnedChannelIdsAtom =
Atom(name: '_SettingsStoreBase.pinnedChannelIds', context: context);
late final _$pinnedChannelIdsAtom = Atom(
name: '_SettingsStoreBase.pinnedChannelIds',
context: context,
);
@override
List<String> get pinnedChannelIds {
@ -862,13 +990,16 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
});
}
late final _$_SettingsStoreBaseActionController =
ActionController(name: '_SettingsStoreBase', context: context);
late final _$_SettingsStoreBaseActionController = ActionController(
name: '_SettingsStoreBase',
context: context,
);
@override
void resetGeneralSettings() {
final _$actionInfo = _$_SettingsStoreBaseActionController.startAction(
name: '_SettingsStoreBase.resetGeneralSettings');
name: '_SettingsStoreBase.resetGeneralSettings',
);
try {
return super.resetGeneralSettings();
} finally {
@ -879,7 +1010,8 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
@override
void resetVideoSettings() {
final _$actionInfo = _$_SettingsStoreBaseActionController.startAction(
name: '_SettingsStoreBase.resetVideoSettings');
name: '_SettingsStoreBase.resetVideoSettings',
);
try {
return super.resetVideoSettings();
} finally {
@ -890,7 +1022,8 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
@override
void resetChatSettings() {
final _$actionInfo = _$_SettingsStoreBaseActionController.startAction(
name: '_SettingsStoreBase.resetChatSettings');
name: '_SettingsStoreBase.resetChatSettings',
);
try {
return super.resetChatSettings();
} finally {
@ -901,7 +1034,8 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
@override
void resetOtherSettings() {
final _$actionInfo = _$_SettingsStoreBaseActionController.startAction(
name: '_SettingsStoreBase.resetOtherSettings');
name: '_SettingsStoreBase.resetOtherSettings',
);
try {
return super.resetOtherSettings();
} finally {
@ -912,7 +1046,8 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
@override
void resetGlobalConfigs() {
final _$actionInfo = _$_SettingsStoreBaseActionController.startAction(
name: '_SettingsStoreBase.resetGlobalConfigs');
name: '_SettingsStoreBase.resetGlobalConfigs',
);
try {
return super.resetGlobalConfigs();
} finally {
@ -923,7 +1058,8 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
@override
void resetAllSettings() {
final _$actionInfo = _$_SettingsStoreBaseActionController.startAction(
name: '_SettingsStoreBase.resetAllSettings');
name: '_SettingsStoreBase.resetAllSettings',
);
try {
return super.resetAllSettings();
} finally {

View file

@ -52,19 +52,14 @@ abstract class UserStoreBase with Store {
}
@action
Future<void> unblock({
required String targetId,
}) async {
Future<void> unblock({required String targetId}) async {
final success = await twitchApi.unblockUser(userId: targetId);
if (success) await refreshBlockedUsers();
}
@action
Future<void> refreshBlockedUsers() async =>
_blockedUsers = (await twitchApi.getUserBlockedList(
id: _details!.id,
))
.asObservable();
Future<void> refreshBlockedUsers() async => _blockedUsers =
(await twitchApi.getUserBlockedList(id: _details!.id)).asObservable();
@action
void dispose() {

View file

@ -9,8 +9,10 @@ part of 'user_store.dart';
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers
mixin _$UserStore on UserStoreBase, Store {
late final _$_detailsAtom =
Atom(name: 'UserStoreBase._details', context: context);
late final _$_detailsAtom = Atom(
name: 'UserStoreBase._details',
context: context,
);
UserTwitch? get details {
_$_detailsAtom.reportRead();
@ -27,8 +29,10 @@ mixin _$UserStore on UserStoreBase, Store {
});
}
late final _$_blockedUsersAtom =
Atom(name: 'UserStoreBase._blockedUsers', context: context);
late final _$_blockedUsersAtom = Atom(
name: 'UserStoreBase._blockedUsers',
context: context,
);
ObservableList<UserBlockedTwitch> get blockedUsers {
_$_blockedUsersAtom.reportRead();
@ -45,47 +49,60 @@ mixin _$UserStore on UserStoreBase, Store {
});
}
late final _$initAsyncAction =
AsyncAction('UserStoreBase.init', context: context);
late final _$initAsyncAction = AsyncAction(
'UserStoreBase.init',
context: context,
);
@override
Future<void> init() {
return _$initAsyncAction.run(() => super.init());
}
late final _$blockAsyncAction =
AsyncAction('UserStoreBase.block', context: context);
late final _$blockAsyncAction = AsyncAction(
'UserStoreBase.block',
context: context,
);
@override
Future<void> block({required String targetId, required String displayName}) {
return _$blockAsyncAction
.run(() => super.block(targetId: targetId, displayName: displayName));
return _$blockAsyncAction.run(
() => super.block(targetId: targetId, displayName: displayName),
);
}
late final _$unblockAsyncAction =
AsyncAction('UserStoreBase.unblock', context: context);
late final _$unblockAsyncAction = AsyncAction(
'UserStoreBase.unblock',
context: context,
);
@override
Future<void> unblock({required String targetId}) {
return _$unblockAsyncAction.run(() => super.unblock(targetId: targetId));
}
late final _$refreshBlockedUsersAsyncAction =
AsyncAction('UserStoreBase.refreshBlockedUsers', context: context);
late final _$refreshBlockedUsersAsyncAction = AsyncAction(
'UserStoreBase.refreshBlockedUsers',
context: context,
);
@override
Future<void> refreshBlockedUsers() {
return _$refreshBlockedUsersAsyncAction
.run(() => super.refreshBlockedUsers());
return _$refreshBlockedUsersAsyncAction.run(
() => super.refreshBlockedUsers(),
);
}
late final _$UserStoreBaseActionController =
ActionController(name: 'UserStoreBase', context: context);
late final _$UserStoreBaseActionController = ActionController(
name: 'UserStoreBase',
context: context,
);
@override
void dispose() {
final _$actionInfo = _$UserStoreBaseActionController.startAction(
name: 'UserStoreBase.dispose');
name: 'UserStoreBase.dispose',
);
try {
return super.dispose();
} finally {

View file

@ -19,10 +19,7 @@ class VideoSettings extends StatelessWidget {
return Observer(
builder: (context) => SettingsPageLayout(
children: [
const SectionHeader(
'Player',
isFirst: true,
),
const SectionHeader('Player', isFirst: true),
SettingsListSwitch(
title: 'Enable video',
value: settingsStore.showVideo,

View file

@ -28,13 +28,11 @@ class _ReleaseNotesState extends State<ReleaseNotes> {
void initState() {
super.initState();
rootBundle.loadString('assets/release-notes.md').then(
(changelog) {
setState(() {
releaseNotes = changelog;
});
},
);
rootBundle.loadString('assets/release-notes.md').then((changelog) {
setState(() {
releaseNotes = changelog;
});
});
}
@override
@ -78,20 +76,14 @@ class _ReleaseNotesState extends State<ReleaseNotes> {
data: releaseNotes,
styleSheet: MarkdownStyleSheet(
h2: const TextStyle(fontSize: 20),
h3: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
h3: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
h3Padding: const EdgeInsets.only(top: 16),
h4: const TextStyle(fontSize: 14),
h4Padding: const EdgeInsets.only(top: 16),
p: const TextStyle(fontSize: 14),
horizontalRuleDecoration: const BoxDecoration(
border: Border(
top: BorderSide(
color: Colors.transparent,
width: 32,
),
top: BorderSide(color: Colors.transparent, width: 32),
),
),
),
@ -127,9 +119,7 @@ class _ReleaseNotesState extends State<ReleaseNotes> {
top: MediaQuery.of(context).padding.top + kToolbarHeight,
left: 0,
right: 0,
child: AnimatedScrollBorder(
scrollController: _scrollController,
),
child: AnimatedScrollBorder(scrollController: _scrollController),
),
],
),

View file

@ -31,10 +31,7 @@ class SettingsListSelect extends StatelessWidget {
.map(
(option) => ButtonSegment(
value: option,
label: Text(
option,
overflow: TextOverflow.ellipsis,
),
label: Text(option, overflow: TextOverflow.ellipsis),
),
)
.toList(),

View file

@ -24,19 +24,14 @@ class _SettingsMutedWordsState extends State<SettingsMutedWords> {
}
void addMutedWord(String text) {
settingsStore.mutedWords = [
...settingsStore.mutedWords,
text,
];
settingsStore.mutedWords = [...settingsStore.mutedWords, text];
textController.clear();
textFieldFocusNode.unfocus();
}
void removeMutedWord(int index) {
settingsStore.mutedWords = [
...settingsStore.mutedWords..removeAt(index),
];
settingsStore.mutedWords = [...settingsStore.mutedWords..removeAt(index)];
}
@override
@ -75,9 +70,7 @@ class _SettingsMutedWordsState extends State<SettingsMutedWords> {
if (textController.text.isEmpty) {
textFieldFocusNode.unfocus();
} else {
addMutedWord(
textController.text,
);
addMutedWord(textController.text);
}
},
icon: const Icon(Icons.check),
@ -97,8 +90,9 @@ class _SettingsMutedWordsState extends State<SettingsMutedWords> {
itemCount: settingsStore.mutedWords.length,
itemBuilder: (context, index) {
return ListTile(
title:
Text(settingsStore.mutedWords.elementAt(index)),
title: Text(
settingsStore.mutedWords.elementAt(index),
),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {

View file

@ -37,10 +37,7 @@ class _BlurredSettingsPage extends StatelessWidget {
final String title;
final Widget child;
const _BlurredSettingsPage({
required this.title,
required this.child,
});
const _BlurredSettingsPage({required this.title, required this.child});
@override
Widget build(BuildContext context) {

View file

@ -8,17 +8,17 @@ class FrostyThemes {
const FrostyThemes({required this.colorSchemeSeed});
ThemeData get light => createBaseTheme(
colorScheme: ColorScheme.fromSeed(seedColor: colorSchemeSeed),
backgroundColor: const Color.fromRGBO(248, 248, 248, 1),
);
colorScheme: ColorScheme.fromSeed(seedColor: colorSchemeSeed),
backgroundColor: const Color.fromRGBO(248, 248, 248, 1),
);
ThemeData get dark => createBaseTheme(
colorScheme: ColorScheme.fromSeed(
seedColor: colorSchemeSeed,
brightness: Brightness.dark,
),
backgroundColor: Colors.black,
);
colorScheme: ColorScheme.fromSeed(
seedColor: colorSchemeSeed,
brightness: Brightness.dark,
),
backgroundColor: Colors.black,
);
ThemeData createBaseTheme({
required ColorScheme colorScheme,
@ -93,9 +93,7 @@ class FrostyThemes {
borderRadius: const BorderRadius.all(Radius.circular(8)),
border: Border.all(color: borderColor, width: borderWidth),
),
textStyle: TextStyle(
color: colorScheme.onSurface,
),
textStyle: TextStyle(color: colorScheme.onSurface),
),
snackBarTheme: SnackBarThemeData(
showCloseIcon: true,
@ -143,25 +141,16 @@ class FrostyThemes {
fontWeight: FontWeight.w500,
letterSpacing: -0.006,
),
labelMedium: TextStyle(
fontWeight: FontWeight.w500,
letterSpacing: 0,
),
labelMedium: TextStyle(fontWeight: FontWeight.w500, letterSpacing: 0),
labelSmall: TextStyle(
fontWeight: FontWeight.w500,
letterSpacing: 0.005,
),
// Body text
bodyLarge: TextStyle(
letterSpacing: -0.011,
),
bodyMedium: TextStyle(
letterSpacing: -0.006,
),
bodySmall: TextStyle(
letterSpacing: 0,
),
bodyLarge: TextStyle(letterSpacing: -0.011),
bodyMedium: TextStyle(letterSpacing: -0.006),
bodySmall: TextStyle(letterSpacing: 0),
),
);
}

View file

@ -58,8 +58,10 @@ Color adjustChatNameColor(
final mid = (lo + hi) / 2.0;
final t = (mid - 0.5).abs() * 2; // 0..1 near ends
final easedSat = (hsl0.saturation * (1 - 0.25 * t * t)).clamp(0.0, 1.0);
final candidate =
hsl0.withLightness(mid).withSaturation(easedSat).toColor();
final candidate = hsl0
.withLightness(mid)
.withSaturation(easedSat)
.toColor();
if (_contrastRatio(candidate, bg) >= targetContrast) {
bestL = mid;

View file

@ -19,8 +19,9 @@ class AlertMessage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final defaultColor =
Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.5);
final defaultColor = Theme.of(
context,
).colorScheme.onSurface.withValues(alpha: 0.5);
final Widget widget;
@ -30,49 +31,38 @@ class AlertMessage extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
spacing: 8,
children: [
Icon(
Icons.info_outline_rounded,
color: color ?? defaultColor,
),
Icon(Icons.info_outline_rounded, color: color ?? defaultColor),
Text(
message,
textAlign: centered ? TextAlign.center : TextAlign.start,
style: TextStyle(
color: color ?? defaultColor,
),
style: TextStyle(color: color ?? defaultColor),
),
],
);
} else {
widget = Row(
mainAxisAlignment:
centered ? MainAxisAlignment.center : MainAxisAlignment.start,
mainAxisAlignment: centered
? MainAxisAlignment.center
: MainAxisAlignment.start,
spacing: 8,
children: [
Icon(
Icons.info_outline_rounded,
color: color ?? defaultColor,
),
Icon(Icons.info_outline_rounded, color: color ?? defaultColor),
Flexible(
child: Text(
message,
style: TextStyle(
color: color ?? defaultColor,
),
style: TextStyle(color: color ?? defaultColor),
),
),
],
);
}
final effectivePadding = padding ??
final effectivePadding =
padding ??
(vertical ? const EdgeInsets.symmetric(horizontal: 24) : null);
return effectivePadding != null
? Padding(
padding: effectivePadding,
child: widget,
)
? Padding(padding: effectivePadding, child: widget)
: widget;
}
}

View file

@ -48,21 +48,21 @@ class BlurredContainer extends StatelessWidget {
final isDark = forceDarkMode ?? (theme.brightness == Brightness.dark);
// Adaptive alpha based on theme for optimal visibility
final adaptiveAlpha = backgroundAlpha ??
final adaptiveAlpha =
backgroundAlpha ??
(isDark ? BlurConfig.darkModeAlpha : BlurConfig.lightModeAlpha);
// Use dark background color if forced, otherwise use theme color
final backgroundColor =
forceDarkMode == true ? Colors.black : theme.scaffoldBackgroundColor;
final backgroundColor = forceDarkMode == true
? Colors.black
: theme.scaffoldBackgroundColor;
// Create decoration based on gradient direction
Decoration decoration;
if (gradientDirection == GradientDirection.none) {
// Solid color (original behavior)
decoration = BoxDecoration(
color: backgroundColor.withValues(
alpha: adaptiveAlpha,
),
color: backgroundColor.withValues(alpha: adaptiveAlpha),
);
} else {
List<Color> colors;

View file

@ -40,10 +40,12 @@ class FrostyCachedNetworkImage extends StatelessWidget {
colorBlendMode: colorBlendMode,
placeholder: placeholder,
useOldImageOnUrlChange: useOldImageOnUrlChange,
fadeOutDuration:
useFade ? const Duration(milliseconds: 500) : Duration.zero,
fadeInDuration:
useFade ? const Duration(milliseconds: 500) : Duration.zero,
fadeOutDuration: useFade
? const Duration(milliseconds: 500)
: Duration.zero,
fadeInDuration: useFade
? const Duration(milliseconds: 500)
: Duration.zero,
fadeInCurve: Curves.easeOut,
fadeOutCurve: Curves.easeIn,
fit: fit,

View file

@ -23,11 +23,7 @@ class FrostyDialog extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SectionHeader(
title,
isFirst: true,
padding: EdgeInsets.zero,
),
SectionHeader(title, isFirst: true, padding: EdgeInsets.zero),
const SizedBox(height: 16),
content ?? Text(message!, textAlign: TextAlign.center),
if (actions != null) ...[

View file

@ -146,8 +146,8 @@ class _DraggableDividerState extends State<DraggableDivider> {
color: _isDragging
? colorScheme.primary
: (_isHovered
? colorScheme.onSurface.withValues(alpha: 0.3)
: colorScheme.onSurface.withValues(alpha: 0.1)),
? colorScheme.onSurface.withValues(alpha: 0.3)
: colorScheme.onSurface.withValues(alpha: 0.1)),
borderRadius: BorderRadius.circular(2),
),
),

View file

@ -26,7 +26,8 @@ class ExpandableSectionHeader extends StatelessWidget {
return InkWell(
onTap: onToggle,
child: Padding(
padding: padding ??
padding:
padding ??
EdgeInsets.fromLTRB(
isLandscape ? MediaQuery.of(context).padding.left : 16,
0,
@ -37,9 +38,7 @@ class ExpandableSectionHeader extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (!isFirst) ...[
const SizedBox(height: 32),
],
if (!isFirst) ...[const SizedBox(height: 32)],
Row(
children: [
Expanded(
@ -49,8 +48,9 @@ class ExpandableSectionHeader extends StatelessWidget {
fontSize: fontSize ?? 13,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
color:
context.colorScheme.onSurface.withValues(alpha: 0.6),
color: context.colorScheme.onSurface.withValues(
alpha: 0.6,
),
),
),
),

View file

@ -75,22 +75,12 @@ class _LiveIndicatorState extends State<LiveIndicator>
_pingScale = Tween<double>(
begin: _pingScaleStart,
end: _pingScaleEnd,
).animate(
CurvedAnimation(
parent: _pingController!,
curve: Curves.easeOut,
),
);
).animate(CurvedAnimation(parent: _pingController!, curve: Curves.easeOut));
_pingOpacity = Tween<double>(
begin: _pingOpacityStart,
end: _pingOpacityEnd,
).animate(
CurvedAnimation(
parent: _pingController!,
curve: Curves.easeOut,
),
);
).animate(CurvedAnimation(parent: _pingController!, curve: Curves.easeOut));
}
void _disposeAnimations() {
@ -144,10 +134,7 @@ class _LiveIndicatorState extends State<LiveIndicator>
return Container(
width: widget.size,
height: widget.size,
decoration: BoxDecoration(
color: widget.color,
shape: BoxShape.circle,
),
decoration: BoxDecoration(color: widget.color, shape: BoxShape.circle),
);
}
@ -177,8 +164,9 @@ class _LiveIndicatorState extends State<LiveIndicator>
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: widget.color
.withValues(alpha: _borderOpacity),
color: widget.color.withValues(
alpha: _borderOpacity,
),
width: _borderWidth,
),
),

View file

@ -19,8 +19,9 @@ class LoadingIndicator extends StatelessWidget {
Text(
subtitle!,
style: TextStyle(
color:
Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.5),
color: Theme.of(
context,
).colorScheme.onSurface.withValues(alpha: 0.5),
),
),
],

View file

@ -16,8 +16,9 @@ class FrostyNotification extends StatelessWidget {
@override
Widget build(BuildContext context) {
final Widget notificationContent = BlurredContainer(
gradientDirection:
showGradient ? GradientDirection.up : GradientDirection.none,
gradientDirection: showGradient
? GradientDirection.up
: GradientDirection.none,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
children: [
@ -25,18 +26,13 @@ class FrostyNotification extends StatelessWidget {
child: Row(
spacing: 8,
children: [
Icon(
Icons.info_outline_rounded,
size: 20,
),
Icon(Icons.info_outline_rounded, size: 20),
Expanded(
child: Text(
message,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: FontWeight.w500,
),
style: TextStyle(fontWeight: FontWeight.w500),
),
),
],
@ -48,10 +44,9 @@ class FrostyNotification extends StatelessWidget {
icon: const Icon(Icons.close_rounded, size: 20),
visualDensity: VisualDensity.compact,
tooltip: 'Dismiss',
color: Theme.of(context)
.colorScheme
.onSurfaceVariant
.withValues(alpha: 0.6),
color: Theme.of(
context,
).colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
),
],
),

View file

@ -28,16 +28,8 @@ class _FrostyPageViewState extends State<FrostyPageView> {
fontSize: 14,
fontWeight: FontWeight.w600,
),
labelPadding: const EdgeInsets.symmetric(
horizontal: 12,
),
tabs: widget.headers
.map(
(header) => Tab(
text: header,
),
)
.toList(),
labelPadding: const EdgeInsets.symmetric(horizontal: 12),
tabs: widget.headers.map((header) => Tab(text: header)).toList(),
),
const Divider(),
Expanded(child: TabBarView(children: widget.children)),

View file

@ -52,14 +52,13 @@ class _FrostyPhotoViewDialogState extends State<FrostyPhotoViewDialog>
);
_exitTranslateAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _exitController, curve: Curves.easeOut),
)..addListener(
() => setState(() {}),
);
_exitOpacityAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(parent: _exitController, curve: Curves.easeOut),
)..addListener(
() => setState(() => _imageOpacity = _exitOpacityAnimation.value),
);
)..addListener(() => setState(() {}));
_exitOpacityAnimation =
Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(parent: _exitController, curve: Curves.easeOut),
)..addListener(
() => setState(() => _imageOpacity = _exitOpacityAnimation.value),
);
}
void _toggleResolution() {
@ -86,8 +85,10 @@ class _FrostyPhotoViewDialogState extends State<FrostyPhotoViewDialog>
final screenHeight = MediaQuery.of(context).size.height;
final fadeDistance = screenHeight * 0.20; // 20% of screen height
final buttonsOpacity =
(1.0 - (_dragOffset.abs() / fadeDistance)).clamp(0.0, 1.0);
final buttonsOpacity = (1.0 - (_dragOffset.abs() / fadeDistance)).clamp(
0.0,
1.0,
);
return Stack(
alignment: Alignment.topCenter,
@ -109,25 +110,29 @@ class _FrostyPhotoViewDialogState extends State<FrostyPhotoViewDialog>
},
onVerticalDragUpdate:
photoViewScaleState == PhotoViewScaleState.initial
? (details) => setState(() => _dragOffset += details.delta.dy)
: null,
? (details) => setState(() => _dragOffset += details.delta.dy)
: null,
onVerticalDragEnd: photoViewScaleState == PhotoViewScaleState.initial
? (details) {
final velocity = details.velocity.pixelsPerSecond.dy;
final shouldDismiss = (_dragOffset.abs() > fadeDistance) ||
final shouldDismiss =
(_dragOffset.abs() > fadeDistance) ||
velocity.abs() > 700;
if (shouldDismiss) {
// run exit animation: translate down by 30% of screen and fade out
_exitController
..value = 0.0
..forward()
.whenComplete(() => Navigator.of(context).pop());
..forward().whenComplete(
() => Navigator.of(context).pop(),
);
return;
}
// animate back to zero
_resetAnimation = Tween<double>(begin: _dragOffset, end: 0.0)
.animate(_resetController);
_resetAnimation = Tween<double>(
begin: _dragOffset,
end: 0.0,
).animate(_resetController);
_resetController
..value = 0.0
..forward();
@ -149,8 +154,9 @@ class _FrostyPhotoViewDialogState extends State<FrostyPhotoViewDialog>
),
scaleStateChangedCallback: (value) =>
setState(() => photoViewScaleState = value),
backgroundDecoration:
const BoxDecoration(color: Colors.transparent),
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
),
),
),
@ -185,9 +191,7 @@ class _FrostyPhotoViewDialogState extends State<FrostyPhotoViewDialog>
child: ElevatedButton(
onPressed: _toggleResolution,
child: Text(
_isFullResolution
? 'View thumbnail'
: 'View original',
_isFullResolution ? 'View thumbnail' : 'View original',
style: TextStyle(
color: context
.watch<FrostyThemes>()

View file

@ -7,11 +7,7 @@ class ProfilePicture extends StatefulWidget {
final String userLogin;
final double radius;
const ProfilePicture({
super.key,
required this.userLogin,
this.radius = 20,
});
const ProfilePicture({super.key, required this.userLogin, this.radius = 20});
@override
State<ProfilePicture> createState() => _ProfilePictureState();

Some files were not shown because too many files have changed in this diff Show more