From d945b103ca4bdda1f40d1b7dae26ac942ea84c28 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 15 Nov 2024 00:52:31 +0800 Subject: [PATCH] :sparkles: Websocket connection status indicator --- assets/translations/en.json | 4 +- assets/translations/zh.json | 4 +- lib/providers/websocket.dart | 1 + lib/widgets/connection_indicator.dart | 55 ++++++++++++++++++++++++ lib/widgets/navigation/app_scaffold.dart | 8 +++- 5 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 lib/widgets/connection_indicator.dart diff --git a/assets/translations/en.json b/assets/translations/en.json index 0e1710d..254d946 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -131,5 +131,7 @@ "sensitiveContent": "Sensitive Content", "sensitiveContentCollapsed": "Sensitive content has been collapsed.", "sensitiveContentDescription": "This content has been marked as sensitive, and may not be suitable for all viewers.", - "sensitiveContentReveal": "Reveal" + "sensitiveContentReveal": "Reveal", + "serverConnecting": "Connecting to server...", + "serverDisconnected": "Lost connection from server" } diff --git a/assets/translations/zh.json b/assets/translations/zh.json index c6c4202..46acd89 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -131,5 +131,7 @@ "sensitiveContent": "敏感内容", "sensitiveContentCollapsed": "敏感内容已折叠。", "sensitiveContentDescription": "此内容已被标记,可能不适合所有人查看。", - "sensitiveContentReveal": "显示内容" + "sensitiveContentReveal": "显示内容", + "serverConnecting": "正在连接服务器…", + "serverDisconnected": "已与服务器断开连接" } diff --git a/lib/providers/websocket.dart b/lib/providers/websocket.dart index 07b1d05..03a336b 100644 --- a/lib/providers/websocket.dart +++ b/lib/providers/websocket.dart @@ -53,6 +53,7 @@ class WebSocketProvider extends ChangeNotifier { conn = WebSocketChannel.connect(uri); await conn!.ready; log('[WebSocket] Connected to server!'); + isConnected = true; } catch (err) { if (err is WebSocketChannelException) { log('Failed to connect to websocket: ${(err.inner as dynamic).message}'); diff --git a/lib/widgets/connection_indicator.dart b/lib/widgets/connection_indicator.dart new file mode 100644 index 0000000..8fcef67 --- /dev/null +++ b/lib/widgets/connection_indicator.dart @@ -0,0 +1,55 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:styled_widget/styled_widget.dart'; +import 'package:surface/providers/userinfo.dart'; +import 'package:surface/providers/websocket.dart'; + +class ConnectionIndicator extends StatelessWidget { + const ConnectionIndicator({super.key}); + + @override + Widget build(BuildContext context) { + final ws = context.watch(); + + return ListenableBuilder( + listenable: ws, + builder: (context, _) { + final ua = context.read(); + + return Container( + padding: EdgeInsets.only( + bottom: 8, + top: MediaQuery.of(context).padding.top + 8, + left: 24, + right: 24, + ), + color: Theme.of(context).colorScheme.secondaryContainer, + child: ua.isAuthorized + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (ws.isBusy) + Text('serverConnecting').tr().textColor( + Theme.of(context).colorScheme.onSecondaryContainer) + else if (!ws.isConnected) + Text('serverDisconnected').tr().textColor( + Theme.of(context).colorScheme.onSecondaryContainer), + ], + ) + : const SizedBox.shrink(), + ) + .height( + (ws.isBusy || !ws.isConnected) && ua.isAuthorized + ? MediaQuery.of(context).padding.top + 30 + : 0, + animate: true) + .animate( + const Duration(milliseconds: 300), + Curves.easeInOut, + ); + }, + ); + } +} diff --git a/lib/widgets/navigation/app_scaffold.dart b/lib/widgets/navigation/app_scaffold.dart index 1e93c0c..2a238ff 100644 --- a/lib/widgets/navigation/app_scaffold.dart +++ b/lib/widgets/navigation/app_scaffold.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:responsive_framework/responsive_framework.dart'; +import 'package:surface/widgets/connection_indicator.dart'; import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/navigation/app_background.dart'; import 'package:surface/widgets/navigation/app_bottom_navigation.dart'; @@ -80,7 +81,12 @@ class AppRootScaffold extends StatelessWidget { return AppBackground( child: Scaffold( - body: innerWidget, + body: Column( + children: [ + ConnectionIndicator(), + Expanded(child: innerWidget), + ], + ), drawer: !isExpandDrawer ? AppNavigationDrawer() : null, ), );