format
This commit is contained in:
parent
54456f990e
commit
696b227497
105 changed files with 2194 additions and 2273 deletions
|
|
@ -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?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)',
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -36,4 +36,4 @@ class FollowedChannels {
|
|||
|
||||
factory FollowedChannels.fromJson(Map<String, dynamic> json) =>
|
||||
_$FollowedChannelsFromJson(json);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
...[
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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]),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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')),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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: () {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) ...[
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
|
|
|
|||
|
|
@ -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>()
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue