Add option to enable historical recent messages in chat (#374)
* Add initial support for recent messages * Add setting for recent messages * Add setting to onboarding * Activate buffer after connecting * Update copy * Remove unnecessay future delayed * Remove old line * Prefer message buffer
This commit is contained in:
parent
a0c945a8d4
commit
1594d40f48
10 changed files with 245 additions and 85 deletions
|
|
@ -522,4 +522,20 @@ class TwitchApi {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Unblocks the user with the given ID and returns true on success or false on failure.
|
||||
Future<List<dynamic>> getRecentMessages({
|
||||
required String userLogin,
|
||||
}) async {
|
||||
final url = Uri.parse(
|
||||
'https://recent-messages.robotty.de/api/v2/recent-messages/$userLogin',
|
||||
);
|
||||
|
||||
final response = await _client.get(url);
|
||||
if (response.statusCode == 200) {
|
||||
return jsonDecode(response.body)['messages'] as List;
|
||||
} else {
|
||||
return Future.error('Failed to get recent messages for $userLogin');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -797,13 +797,16 @@ class IRCMessage {
|
|||
: splitMessage[0].substring(0, splitMessage[0].indexOf('!'));
|
||||
|
||||
// If there is an associated message, set it.
|
||||
//
|
||||
// Also remove any "INVALID/UNDEFINED" Unicode characters.
|
||||
// Rendering this character on iOS shows a question mark inside a square.
|
||||
// This character is used by some clients to bypass restrictions on repeating message.
|
||||
var message = splitMessage.length > 3
|
||||
? splitMessage.sublist(3).join(' ').substring(1)
|
||||
: null;
|
||||
String? message;
|
||||
if (splitMessage.length > 3) {
|
||||
final combinedMessage = splitMessage.sublist(3).join(' ');
|
||||
|
||||
if (combinedMessage.startsWith(':')) {
|
||||
message = combinedMessage.substring(1);
|
||||
} else {
|
||||
message = combinedMessage;
|
||||
}
|
||||
}
|
||||
|
||||
// Now process any Twitch emotes contained in the message tags.
|
||||
// The map containing emotes from the user's tags to their URL.
|
||||
|
|
@ -811,7 +814,7 @@ class IRCMessage {
|
|||
final localEmotes = <String, Emote>{};
|
||||
|
||||
final emoteTags = mappedTags['emotes'];
|
||||
if (emoteTags != null) {
|
||||
if (emoteTags != null && message != null) {
|
||||
// Emotes and their indices are separated by '/' so split them there.
|
||||
final emotes = emoteTags.split('/');
|
||||
|
||||
|
|
@ -838,7 +841,7 @@ class IRCMessage {
|
|||
final startIndex = int.parse(indexSplit[0]);
|
||||
final endIndex = int.parse(indexSplit[1]);
|
||||
|
||||
final emoteWord = message!.substring(startIndex, endIndex + 1);
|
||||
final emoteWord = message.substring(startIndex, endIndex + 1);
|
||||
|
||||
// Store the emote word and its associated URL for later use.
|
||||
localEmotes[emoteWord] = Emote(
|
||||
|
|
@ -869,6 +872,9 @@ class IRCMessage {
|
|||
// Escape the message
|
||||
message = message
|
||||
.split(' ')
|
||||
// Also remove any "INVALID/UNDEFINED" Unicode characters.
|
||||
// Rendering this character on iOS shows a question mark inside a square.
|
||||
// This character is used by some clients to bypass restrictions on repeating message.
|
||||
.map((word) => word.replaceAll('\u{E0000}', '').trim())
|
||||
.where((element) => element != '')
|
||||
.join(' ');
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ class _VideoChatState extends State<VideoChat> {
|
|||
final _chatKey = GlobalKey();
|
||||
|
||||
late final ChatStore _chatStore = ChatStore(
|
||||
twitchApi: context.read<TwitchApi>(),
|
||||
channelName: widget.userLogin,
|
||||
channelId: widget.userId,
|
||||
displayName: widget.userName,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:frosty/apis/twitch_api.dart';
|
||||
import 'package:frosty/models/emotes.dart';
|
||||
import 'package:frosty/models/events.dart';
|
||||
import 'package:frosty/models/irc.dart';
|
||||
|
|
@ -26,6 +27,8 @@ abstract class ChatStoreBase with Store {
|
|||
/// The maximum ammount of messages to render when autoscroll is enabled.
|
||||
static const _renderMessageLimit = 100;
|
||||
|
||||
final TwitchApi twitchApi;
|
||||
|
||||
/// The amount of messages to free (remove) when the [_messageLimit] is reached.
|
||||
final _messagesToRemove = (_messageLimit * 0.2).toInt();
|
||||
|
||||
|
|
@ -84,7 +87,7 @@ abstract class ChatStoreBase with Store {
|
|||
final reactions = <ReactionDisposer>[];
|
||||
|
||||
/// The periodic timer used for batching chat message re-renders.
|
||||
late final Timer _messageBufferTimer;
|
||||
Timer? _messageBufferTimer;
|
||||
|
||||
/// The list of chat messages to add once autoscroll is resumed.
|
||||
/// This is used as an optimization to prevent the list from being updated/shifted while the user is scrolling.
|
||||
|
|
@ -150,6 +153,7 @@ abstract class ChatStoreBase with Store {
|
|||
IRCMessage? replyingToMessage;
|
||||
|
||||
ChatStoreBase({
|
||||
required this.twitchApi,
|
||||
required this.auth,
|
||||
required this.chatDetailsStore,
|
||||
required this.assetsStore,
|
||||
|
|
@ -198,19 +202,12 @@ abstract class ChatStoreBase with Store {
|
|||
),
|
||||
);
|
||||
|
||||
// Create a timer that will add messages from the buffer every 200 milliseconds.
|
||||
_messageBufferTimer = Timer.periodic(
|
||||
const Duration(milliseconds: 200),
|
||||
(timer) => addMessages(),
|
||||
);
|
||||
|
||||
assetsStore.init();
|
||||
|
||||
messageBuffer
|
||||
.add(IRCMessage.createNotice(message: 'Connecting to chat...'));
|
||||
_messages.add(IRCMessage.createNotice(message: 'Connecting to chat...'));
|
||||
|
||||
if (settings.showVideo && settings.chatDelay > 0) {
|
||||
messageBuffer.add(
|
||||
_messages.add(
|
||||
IRCMessage.createNotice(
|
||||
message:
|
||||
'Waiting ${settings.chatDelay.toInt()} ${settings.chatDelay == 1.0 ? 'second' : 'seconds'} due to message delay setting...',
|
||||
|
|
@ -221,14 +218,14 @@ abstract class ChatStoreBase with Store {
|
|||
reactions.add(
|
||||
reaction((_) => settings.showVideo, (showVideo) {
|
||||
if (showVideo && settings.chatDelay > 0) {
|
||||
messageBuffer.add(
|
||||
_messages.add(
|
||||
IRCMessage.createNotice(
|
||||
message:
|
||||
'Waiting ${settings.chatDelay.toInt()} ${settings.chatDelay == 1.0 ? 'second' : 'seconds'} due to message delay setting...',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
messageBuffer.add(
|
||||
_messages.add(
|
||||
IRCMessage.createNotice(
|
||||
message: 'Removing message delay...',
|
||||
),
|
||||
|
|
@ -237,7 +234,11 @@ abstract class ChatStoreBase with Store {
|
|||
}),
|
||||
);
|
||||
|
||||
connectToChat();
|
||||
if (settings.showRecentMessages) {
|
||||
getRecentMessage().then((_) => connectToChat());
|
||||
} else {
|
||||
connectToChat();
|
||||
}
|
||||
|
||||
// Tell the scrollController to determine when auto-scroll should be enabled or disabled.
|
||||
scrollController.addListener(() {
|
||||
|
|
@ -376,10 +377,17 @@ abstract class ChatStoreBase with Store {
|
|||
messageBuffer.add(
|
||||
IRCMessage.createNotice(
|
||||
message:
|
||||
"Connected to ${getReadableName(displayName, channelName)}'s chat!",
|
||||
"Welcome to ${getReadableName(displayName, channelName)}'s chat!",
|
||||
),
|
||||
);
|
||||
|
||||
// Activate the message buffer.
|
||||
// Create a timer that will add messages from the buffer every 200 milliseconds.
|
||||
_messageBufferTimer = Timer.periodic(
|
||||
const Duration(milliseconds: 200),
|
||||
(timer) => addMessages(),
|
||||
);
|
||||
|
||||
getAssets().then((_) {
|
||||
if (!settings.show7TVEmotes) return;
|
||||
|
||||
|
|
@ -446,53 +454,59 @@ abstract class ChatStoreBase with Store {
|
|||
_sevenTVChannel =
|
||||
WebSocketChannel.connect(Uri.parse('wss://events.7tv.io/v3'));
|
||||
|
||||
void listener(dynamic data) {
|
||||
// debugPrint(data);
|
||||
final decoded = jsonDecode(data);
|
||||
|
||||
final event = SevenTVEvent.fromJson(decoded);
|
||||
|
||||
final body = event.d.body;
|
||||
if (event.d.type != 'emote_set.update' || body == null) return;
|
||||
|
||||
if (body.pushed != null) {
|
||||
final pushedEmote = body.pushed?.first.value;
|
||||
|
||||
if (pushedEmote == null) return;
|
||||
|
||||
final emote = Emote.from7TV(pushedEmote, EmoteType.sevenTVChannel);
|
||||
|
||||
assetsStore.emoteToObject[emote.name] = emote;
|
||||
|
||||
messageBuffer.add(
|
||||
IRCMessage.createNotice(
|
||||
message:
|
||||
'${getReadableName(body.actor.displayName, body.actor.username)} added 7TV emote "${emote.name}" to chat',
|
||||
),
|
||||
);
|
||||
} else if (body.pulled != null) {
|
||||
final pulledEmote = body.pulled?.first.oldValue;
|
||||
|
||||
if (pulledEmote == null) return;
|
||||
|
||||
assetsStore.emoteToObject.removeWhere(
|
||||
(name, _) => name == pulledEmote.name,
|
||||
);
|
||||
|
||||
messageBuffer.add(
|
||||
IRCMessage.createNotice(
|
||||
message:
|
||||
'${getReadableName(body.actor.displayName, body.actor.username)} removed 7TV emote "${pulledEmote.name}" from chat',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_sevenTVChannel?.stream.listen(
|
||||
(data) => Future.delayed(
|
||||
settings.showVideo
|
||||
? Duration(seconds: settings.chatDelay.toInt())
|
||||
: Duration.zero,
|
||||
() {
|
||||
// debugPrint(data);
|
||||
final decoded = jsonDecode(data);
|
||||
|
||||
final event = SevenTVEvent.fromJson(decoded);
|
||||
|
||||
final body = event.d.body;
|
||||
if (event.d.type != 'emote_set.update' || body == null) return;
|
||||
|
||||
if (body.pushed != null) {
|
||||
final pushedEmote = body.pushed?.first.value;
|
||||
|
||||
if (pushedEmote == null) return;
|
||||
|
||||
final emote = Emote.from7TV(pushedEmote, EmoteType.sevenTVChannel);
|
||||
|
||||
assetsStore.emoteToObject[emote.name] = emote;
|
||||
|
||||
messageBuffer.add(
|
||||
IRCMessage.createNotice(
|
||||
message:
|
||||
'${getReadableName(body.actor.displayName, body.actor.username)} added 7TV emote "${emote.name}" to chat',
|
||||
),
|
||||
);
|
||||
} else if (body.pulled != null) {
|
||||
final pulledEmote = body.pulled?.first.oldValue;
|
||||
|
||||
if (pulledEmote == null) return;
|
||||
|
||||
assetsStore.emoteToObject.removeWhere(
|
||||
(name, _) => name == pulledEmote.name,
|
||||
);
|
||||
|
||||
messageBuffer.add(
|
||||
IRCMessage.createNotice(
|
||||
message:
|
||||
'${getReadableName(body.actor.displayName, body.actor.username)} removed 7TV emote "${pulledEmote.name}" from chat',
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
(data) {
|
||||
if (!settings.showVideo || settings.chatDelay == 0) {
|
||||
listener(data);
|
||||
} else {
|
||||
Future.delayed(
|
||||
Duration(seconds: settings.chatDelay.toInt()),
|
||||
() => listener(data),
|
||||
);
|
||||
}
|
||||
},
|
||||
onError: (error) => debugPrint('7TV events error: ${error.toString()}'),
|
||||
onDone: () => debugPrint('7TV events done'),
|
||||
);
|
||||
|
|
@ -508,12 +522,16 @@ abstract class ChatStoreBase with Store {
|
|||
|
||||
// Listen for new messages and forward them to the handler.
|
||||
_channelListener = _channel?.stream.listen(
|
||||
(data) => Future.delayed(
|
||||
settings.showVideo
|
||||
? Duration(seconds: settings.chatDelay.toInt())
|
||||
: Duration.zero,
|
||||
() => _handleIRCData(data.toString()),
|
||||
),
|
||||
(data) {
|
||||
if (!settings.showVideo || settings.chatDelay == 0) {
|
||||
_handleIRCData(data.toString());
|
||||
} else {
|
||||
Future.delayed(
|
||||
Duration(seconds: settings.chatDelay.toInt()),
|
||||
() => _handleIRCData(data.toString()),
|
||||
);
|
||||
}
|
||||
},
|
||||
onError: (error) => debugPrint('Chat error: ${error.toString()}'),
|
||||
onDone: () async {
|
||||
if (_shouldDisconnect) {
|
||||
|
|
@ -720,11 +738,21 @@ abstract class ChatStoreBase with Store {
|
|||
timeRemaining = const Duration();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> getRecentMessage() async {
|
||||
final recentMessages =
|
||||
await twitchApi.getRecentMessages(userLogin: channelName);
|
||||
|
||||
for (final message in recentMessages) {
|
||||
_handleIRCData(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes and disposes all the channels and controllers used by the store.
|
||||
void dispose() {
|
||||
_shouldDisconnect = true;
|
||||
|
||||
_messageBufferTimer.cancel();
|
||||
_messageBufferTimer?.cancel();
|
||||
_notificationTimer?.cancel();
|
||||
sleepTimer?.cancel();
|
||||
|
||||
|
|
|
|||
|
|
@ -219,6 +219,14 @@ mixin _$ChatStore on ChatStoreBase, Store {
|
|||
return _$getAssetsAsyncAction.run(() => super.getAssets());
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -440,15 +440,20 @@ class ChatMessage extends StatelessWidget {
|
|||
child: dividedMessage,
|
||||
);
|
||||
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
if (chatStore.assetsStore.showEmoteMenu) {
|
||||
chatStore.assetsStore.showEmoteMenu = false;
|
||||
}
|
||||
},
|
||||
onLongPress: () => onLongPressMessage(context, defaultTextStyle),
|
||||
child: coloredMessage,
|
||||
final isHistorical = ircMessage.tags['historical'] == '1';
|
||||
|
||||
return Opacity(
|
||||
opacity: isHistorical ? 0.5 : 1,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
if (chatStore.assetsStore.showEmoteMenu) {
|
||||
chatStore.assetsStore.showEmoteMenu = false;
|
||||
}
|
||||
},
|
||||
onLongPress: () => onLongPressMessage(context, defaultTextStyle),
|
||||
child: coloredMessage,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:firebase_analytics/firebase_analytics.dart';
|
||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||
import 'package:firebase_performance/firebase_performance.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/screens/onboarding/onboarding_scaffold.dart';
|
||||
|
|
@ -9,6 +10,7 @@ import 'package:frosty/screens/settings/stores/settings_store.dart';
|
|||
import 'package:frosty/screens/settings/widgets/settings_list_select.dart';
|
||||
import 'package:frosty/screens/settings/widgets/settings_list_switch.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class OnboardingSetup extends StatelessWidget {
|
||||
const OnboardingSetup({super.key});
|
||||
|
|
@ -32,6 +34,37 @@ class OnboardingSetup extends StatelessWidget {
|
|||
onChanged: (newTheme) => settingsStore.themeType =
|
||||
ThemeType.values[themeNames.indexOf(newTheme)],
|
||||
),
|
||||
SettingsListSwitch(
|
||||
title: 'Show historical recent messages',
|
||||
subtitle: Text.rich(
|
||||
TextSpan(
|
||||
text:
|
||||
'Loads historical recent messages in chat through a third-party API service at ',
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'https://recent-messages.robotty.de/',
|
||||
style: const TextStyle(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: Colors.blue,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => launchUrl(
|
||||
Uri.parse(
|
||||
'https://recent-messages.robotty.de/',
|
||||
),
|
||||
mode: settingsStore.launchUrlExternal
|
||||
? LaunchMode.externalApplication
|
||||
: LaunchMode.inAppWebView,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
value: settingsStore.showRecentMessages,
|
||||
onChanged: (newValue) =>
|
||||
settingsStore.showRecentMessages = newValue,
|
||||
),
|
||||
SettingsListSwitch(
|
||||
title: 'Share crash logs and analytics',
|
||||
subtitle: const Text(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/constants.dart';
|
||||
|
|
@ -9,6 +10,7 @@ import 'package:frosty/screens/settings/widgets/settings_list_slider.dart';
|
|||
import 'package:frosty/screens/settings/widgets/settings_list_switch.dart';
|
||||
import 'package:frosty/widgets/cached_image.dart';
|
||||
import 'package:frosty/widgets/section_header.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class ChatSettings extends StatefulWidget {
|
||||
final SettingsStore settingsStore;
|
||||
|
|
@ -324,6 +326,38 @@ class _ChatSettingsState extends State<ChatSettings> {
|
|||
value: settingsStore.showFFZBadges,
|
||||
onChanged: (newValue) => settingsStore.showFFZBadges = newValue,
|
||||
),
|
||||
const SectionHeader('Recent messages', showDivider: true),
|
||||
SettingsListSwitch(
|
||||
title: 'Show historical recent messages',
|
||||
subtitle: Text.rich(
|
||||
TextSpan(
|
||||
text:
|
||||
'Loads historical recent messages in chat through a third-party API service at ',
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'https://recent-messages.robotty.de/',
|
||||
style: const TextStyle(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: Colors.blue,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => launchUrl(
|
||||
Uri.parse(
|
||||
'https://recent-messages.robotty.de/',
|
||||
),
|
||||
mode: settingsStore.launchUrlExternal
|
||||
? LaunchMode.externalApplication
|
||||
: LaunchMode.inAppWebView,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
value: settingsStore.showRecentMessages,
|
||||
onChanged: (newValue) =>
|
||||
settingsStore.showRecentMessages = newValue,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -150,6 +150,9 @@ abstract class _SettingsStoreBase with Store {
|
|||
static const defaultShowFFZEmotes = true;
|
||||
static const defaultShowFFZBadges = true;
|
||||
|
||||
// Recent messages defaults
|
||||
static const defaultShowRecentMessages = false;
|
||||
|
||||
// Message sizing options
|
||||
@JsonKey(defaultValue: defaultBadgeScale)
|
||||
@observable
|
||||
|
|
@ -282,6 +285,11 @@ abstract class _SettingsStoreBase with Store {
|
|||
@observable
|
||||
var showFFZBadges = defaultShowFFZBadges;
|
||||
|
||||
// Recent messages
|
||||
@JsonKey(defaultValue: defaultShowRecentMessages)
|
||||
@observable
|
||||
var showRecentMessages = defaultShowRecentMessages;
|
||||
|
||||
@action
|
||||
void resetChatSettings() {
|
||||
badgeScale = defaultBadgeScale;
|
||||
|
|
@ -321,6 +329,8 @@ abstract class _SettingsStoreBase with Store {
|
|||
showBTTVBadges = defaultShowBTTVBadges;
|
||||
showFFZEmotes = defaultShowFFZEmotes;
|
||||
showFFZBadges = defaultShowFFZBadges;
|
||||
|
||||
showRecentMessages = defaultShowRecentMessages;
|
||||
}
|
||||
|
||||
// * Other settings
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ SettingsStore _$SettingsStoreFromJson(Map<String, dynamic> json) =>
|
|||
..showBTTVBadges = json['showBTTVBadges'] as bool? ?? true
|
||||
..showFFZEmotes = json['showFFZEmotes'] as bool? ?? true
|
||||
..showFFZBadges = json['showFFZBadges'] as bool? ?? true
|
||||
..showRecentMessages = json['showRecentMessages'] as bool? ?? false
|
||||
..shareCrashLogsAndAnalytics =
|
||||
json['shareCrashLogsAndAnalytics'] as bool? ?? true
|
||||
..fullScreen = json['fullScreen'] as bool? ?? false
|
||||
|
|
@ -109,6 +110,7 @@ Map<String, dynamic> _$SettingsStoreToJson(SettingsStore instance) =>
|
|||
'showBTTVBadges': instance.showBTTVBadges,
|
||||
'showFFZEmotes': instance.showFFZEmotes,
|
||||
'showFFZBadges': instance.showFFZBadges,
|
||||
'showRecentMessages': instance.showRecentMessages,
|
||||
'shareCrashLogsAndAnalytics': instance.shareCrashLogsAndAnalytics,
|
||||
'fullScreen': instance.fullScreen,
|
||||
'fullScreenChatOverlay': instance.fullScreenChatOverlay,
|
||||
|
|
@ -791,6 +793,22 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
|
|||
});
|
||||
}
|
||||
|
||||
late final _$showRecentMessagesAtom =
|
||||
Atom(name: '_SettingsStoreBase.showRecentMessages', context: context);
|
||||
|
||||
@override
|
||||
bool get showRecentMessages {
|
||||
_$showRecentMessagesAtom.reportRead();
|
||||
return super.showRecentMessages;
|
||||
}
|
||||
|
||||
@override
|
||||
set showRecentMessages(bool value) {
|
||||
_$showRecentMessagesAtom.reportWrite(value, super.showRecentMessages, () {
|
||||
super.showRecentMessages = value;
|
||||
});
|
||||
}
|
||||
|
||||
late final _$shareCrashLogsAndAnalyticsAtom = Atom(
|
||||
name: '_SettingsStoreBase.shareCrashLogsAndAnalytics', context: context);
|
||||
|
||||
|
|
@ -953,6 +971,7 @@ showBTTVEmotes: ${showBTTVEmotes},
|
|||
showBTTVBadges: ${showBTTVBadges},
|
||||
showFFZEmotes: ${showFFZEmotes},
|
||||
showFFZBadges: ${showFFZBadges},
|
||||
showRecentMessages: ${showRecentMessages},
|
||||
shareCrashLogsAndAnalytics: ${shareCrashLogsAndAnalytics},
|
||||
fullScreen: ${fullScreen},
|
||||
fullScreenChatOverlay: ${fullScreenChatOverlay}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue