Add the ability to reconnect to chat with notification (#180)

* Add ability to refresh chat & new settings menu

* Improve chat notifications
This commit is contained in:
Tommy Chow 2022-08-07 18:46:05 -04:00 • committed by GitHub
parent 51a4ae8235
commit a5560ee9cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 89 additions and 71 deletions

View file

@ -73,18 +73,19 @@ class Chat extends StatelessWidget {
color: Colors.white,
),
),
Button(
onPressed: () async {
// Paste clipboard text into the text controller.
final data = await Clipboard.getData(Clipboard.kTextPlain);
if (chatStore.notification!.contains('copied'))
Button(
onPressed: () async {
// Paste clipboard text into the text controller.
final data = await Clipboard.getData(Clipboard.kTextPlain);
if (data != null) chatStore.textController.text = data.text!;
if (data != null) chatStore.textController.text = data.text!;
chatStore.updateNotification('');
},
fill: false,
child: const Text('Paste'),
),
chatStore.updateNotification('');
},
fill: false,
child: const Text('Paste'),
),
],
),
),

View file

@ -4,7 +4,6 @@ import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/screens/channel/chat/emote_menu/emote_menu_panel.dart';
import 'package:frosty/screens/channel/chat/emote_menu/recent_emotes_panel.dart';
import 'package:frosty/screens/channel/stores/chat_store.dart';
import 'package:frosty/widgets/alert_message.dart';
import 'package:frosty/widgets/button.dart';
class EmoteMenu extends StatefulWidget {
@ -68,28 +67,6 @@ class _EmoteMenuState extends State<EmoteMenu> {
),
),
),
Button(
onPressed: () async {
await widget.chatStore.getAssets();
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: AlertMessage(message: 'Emotes refreshed'),
behavior: SnackBarBehavior.floating,
),
);
},
color: Theme.of(context).colorScheme.secondary,
child: const Text(
'REFRESH EMOTES',
style: TextStyle(
letterSpacing: 0.8,
fontWeight: FontWeight.w600,
),
),
),
],
),
),

View file

@ -49,6 +49,9 @@ abstract class ChatStoreBase with Store {
/// The Twitch IRC WebSocket channel.
WebSocketChannel? _channel;
/// The subscription that handles the WebSocket connection.
StreamSubscription? _channelListener;
// The retry counter for exponential backoff.
var _retries = 0;
@ -322,7 +325,7 @@ abstract class ChatStoreBase with Store {
_channel = WebSocketChannel.connect(Uri.parse('wss://irc-ws.chat.twitch.tv:443'));
// Listen for new messages and forward them to the handler.
_channel?.stream.listen(
_channelListener = _channel?.stream.listen(
(data) => Future.delayed(Duration(seconds: settings.chatDelay.toInt()), () => _handleIRCData(data.toString())),
onError: (error) => debugPrint('Chat error: ${error.toString()}'),
onDone: () async {
@ -342,6 +345,7 @@ abstract class ChatStoreBase with Store {
// Increment the retry count and attempt the reconnect.
_retries++;
_messageBuffer.add(IRCMessage.createNotice(message: 'Reconnecting to chat (attempt $_retries)...'));
_channelListener?.cancel();
connectToChat();
},
);
@ -453,6 +457,7 @@ abstract class ChatStoreBase with Store {
_channel?.sink.close(1001);
_channel = null;
_channelListener?.cancel();
for (final reactionDisposer in reactions) {
reactionDisposer();

View file

@ -77,6 +77,67 @@ class _VideoChatState extends State<VideoChat> {
Widget build(BuildContext context) {
final settingsStore = _chatStore.settings;
void showSettings() {
showModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
builder: (context) => FrostyModal(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.app_settings_alt),
title: const Text('App settings'),
onTap: () => showModalBottomSheet(
backgroundColor: Colors.transparent,
isScrollControlled: true,
context: context,
builder: (context) => FrostyModal(
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.8,
child: Settings(settingsStore: settingsStore),
),
),
),
),
ListTile(
leading: const Icon(Icons.refresh),
title: const Text('Reconnect to chat'),
onTap: () {
_chatStore.updateNotification('Reconnecting to chat...');
_chatStore.connectToChat();
},
),
ListTile(
leading: const Icon(Icons.refresh),
title: const Text('Refresh badges and emotes'),
onTap: () async {
await _chatStore.getAssets();
_chatStore.updateNotification('Badges and emotes refreshed');
},
),
],
),
),
);
}
final appBar = AppBar(
title: Text(
regexEnglish.hasMatch(_chatStore.displayName) ? _chatStore.displayName : '${_chatStore.displayName} (${_chatStore.channelName})',
style: const TextStyle(fontSize: 20),
),
actions: [
IconButton(
tooltip: 'Settings',
icon: const Icon(Icons.settings),
onPressed: showSettings,
),
],
);
final player = GestureDetector(
onLongPress: _videoStore.handleToggleOverlay,
child: _Video(
@ -85,7 +146,10 @@ class _VideoChatState extends State<VideoChat> {
),
);
final videoOverlay = _VideoOverlay(videoStore: _videoStore);
final videoOverlay = _VideoOverlay(
videoStore: _videoStore,
onSettingsPressed: showSettings,
);
final overlay = GestureDetector(
onLongPress: _videoStore.handleToggleOverlay,
@ -138,30 +202,6 @@ class _VideoChatState extends State<VideoChat> {
chatStore: _chatStore,
);
final appBar = AppBar(
title: Text(
regexEnglish.hasMatch(_chatStore.displayName) ? _chatStore.displayName : '${_chatStore.displayName} (${_chatStore.channelName})',
style: const TextStyle(fontSize: 20),
),
actions: [
IconButton(
tooltip: 'Settings',
icon: const Icon(Icons.settings),
onPressed: () => showModalBottomSheet(
backgroundColor: Colors.transparent,
isScrollControlled: true,
context: context,
builder: (context) => FrostyModal(
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.8,
child: Settings(settingsStore: settingsStore),
),
),
),
),
],
);
final videoChat = Scaffold(
body: OrientationBuilder(
builder: (context, orientation) {
@ -306,8 +346,13 @@ class _Video extends StatelessWidget {
/// Creates a widget containing controls which enable interactions with an underlying [_Video] widget.
class _VideoOverlay extends StatelessWidget {
final VideoStore videoStore;
final void Function() onSettingsPressed;
const _VideoOverlay({Key? key, required this.videoStore}) : super(key: key);
const _VideoOverlay({
Key? key,
required this.videoStore,
required this.onSettingsPressed,
}) : super(key: key);
Future<void> _showSleepTimerDialog(BuildContext context) {
return showDialog(
@ -401,17 +446,7 @@ class _VideoOverlay extends StatelessWidget {
Icons.settings,
color: Colors.white,
),
onPressed: () => showModalBottomSheet(
backgroundColor: Colors.transparent,
isScrollControlled: true,
context: context,
builder: (context) => FrostyModal(
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.8,
child: Settings(settingsStore: videoStore.settingsStore),
),
),
),
onPressed: onSettingsPressed,
);
final chatOverlayButton = Observer(