💄 Redesign the network status sheet

This commit is contained in:
2025-12-24 22:43:52 +08:00
parent 1716afd66c
commit 2474c7f97c
4 changed files with 96 additions and 64 deletions

View File

@@ -143,6 +143,7 @@
"connectionConnected": "Connected", "connectionConnected": "Connected",
"connectionDisconnected": "Disconnected", "connectionDisconnected": "Disconnected",
"connectionReconnecting": "Reconnecting", "connectionReconnecting": "Reconnecting",
"connectionServerDown": "Unable to Connect",
"accountConnections": "Account Connections", "accountConnections": "Account Connections",
"accountConnectionsDescription": "Manage your external account connections", "accountConnectionsDescription": "Manage your external account connections",
"accountConnectionAdd": "Add Connection", "accountConnectionAdd": "Add Connection",

View File

@@ -33,7 +33,6 @@ class AppWrapper extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final networkStateShowing = useState(false); final networkStateShowing = useState(false);
final wsNotifier = ref.watch(websocketStateProvider.notifier);
final websocketState = ref.watch(websocketStateProvider); final websocketState = ref.watch(websocketStateProvider);
final isShowSnow = useState(false); final isShowSnow = useState(false);
final isSnowGone = useState(false); final isSnowGone = useState(false);
@@ -47,8 +46,7 @@ class AppWrapper extends HookConsumerWidget {
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
isDismissible: false, isDismissible: false,
builder: (context) => builder: (context) => NetworkStatusSheet(autoClose: true),
NetworkStatusSheet(onReconnect: () => wsNotifier.connect()),
).then((_) => networkStateShowing.value = false); ).then((_) => networkStateShowing.value = false);
}); });
} }

View File

@@ -1,78 +1,117 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/pods/websocket.dart'; import 'package:island/pods/websocket.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:island/widgets/content/sheet.dart'; import 'package:island/widgets/content/sheet.dart';
import 'package:styled_widget/styled_widget.dart';
class NetworkStatusSheet extends HookConsumerWidget { class NetworkStatusSheet extends HookConsumerWidget {
final VoidCallback onReconnect; final bool autoClose;
const NetworkStatusSheet({super.key, this.autoClose = false});
const NetworkStatusSheet({super.key, required this.onReconnect});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final ws = ref.watch(websocketProvider); final ws = ref.watch(websocketProvider);
final wsState = ref.watch(websocketStateProvider); final wsState = ref.watch(websocketStateProvider);
final wsNotifier = ref.watch(websocketStateProvider.notifier);
useEffect(() {
if (!autoClose) return;
final checks = [wsState == WebSocketState.connected()];
if (!checks.any((e) => !e)) {
Future.delayed(Duration(seconds: 3), () {
if (context.mounted) Navigator.of(context).pop();
});
}
return null;
}, [wsState]);
return SheetScaffold( return SheetScaffold(
heightFactor: 0.4, heightFactor: 0.6,
titleText: titleText: wsState == WebSocketState.connected()
wsState == WebSocketState.connected()
? 'Connection Status' ? 'Connection Status'
: 'Connection Issue', : 'Connection Issue',
child: Padding( child: Padding(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Row(
spacing: 8,
children: [
Text('WebSocket').bold(),
wsState.when( wsState.when(
connected: connected: () => Text('connectionConnected').tr(),
() => Text( connecting: () => Text('connectionReconnecting').tr(),
'Connected to server', disconnected: () => Text('connectionDisconnected').tr(),
style: Theme.of(context).textTheme.bodyLarge, serverDown: () => Text('connectionServerDown').tr(),
), duplicateDevice: () => Text(
connecting:
() => Text(
'Connecting to server...',
style: Theme.of(context).textTheme.bodyLarge,
),
disconnected:
() => Text(
'Disconnected from server',
style: Theme.of(context).textTheme.bodyLarge,
),
serverDown:
() => Text(
'The server is not available right now... Please try again later...',
style: Theme.of(context).textTheme.bodyLarge,
),
duplicateDevice:
() => Text(
'Another device has connected with the same account.', 'Another device has connected with the same account.',
style: Theme.of(context).textTheme.bodyLarge,
), ),
error: error: (message) => Text('Connection error: $message'),
(message) => Text(
'Connection error: $message',
style: Theme.of(context).textTheme.bodyLarge,
), ),
),
const SizedBox(height: 16),
if (ws.heartbeatDelay != null) if (ws.heartbeatDelay != null)
Text( Text('${ws.heartbeatDelay!.inMilliseconds}ms'),
'Last heartbeat: ${ws.heartbeatDelay!.inMilliseconds}ms', AnimatedSwitcher(
style: Theme.of(context).textTheme.bodyMedium, duration: const Duration(milliseconds: 300),
child: wsState.when(
connected: () => Icon(
Symbols.check_circle,
key: ValueKey(WebSocketState.connected),
color: Colors.green,
size: 16,
), ),
const SizedBox(height: 24), connecting: () => Icon(
Center( Symbols.sync,
child: FilledButton.icon( key: ValueKey(WebSocketState.connecting),
color: Colors.orange,
size: 16,
),
disconnected: () => Icon(
Symbols.wifi_off,
key: ValueKey(WebSocketState.disconnected),
color: Colors.grey,
size: 16,
),
serverDown: () => Icon(
Symbols.cloud_off,
key: ValueKey(WebSocketState.serverDown),
color: Colors.red,
size: 16,
),
duplicateDevice: () => Icon(
Symbols.devices,
key: ValueKey(WebSocketState.duplicateDevice),
color: Colors.orange,
size: 16,
),
error: (message) => Icon(
Symbols.error,
key: ValueKey(WebSocketState.error),
color: Colors.red,
size: 16,
),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
spacing: 8,
children: [
FilledButton.icon(
icon: const Icon(Symbols.wifi), icon: const Icon(Symbols.wifi),
label: const Text('Reconnect'), label: const Text('Reconnect'),
onPressed: () { onPressed: () {
onReconnect(); wsNotifier.manualReconnect();
Navigator.pop(context);
}, },
), ),
],
), ),
], ],
), ),

View File

@@ -5,7 +5,6 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/pods/message.dart'; import 'package:island/pods/message.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/websocket.dart';
import 'package:island/services/update_service.dart'; import 'package:island/services/update_service.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/content/network_status_sheet.dart'; import 'package:island/widgets/content/network_status_sheet.dart';
@@ -67,8 +66,6 @@ class DebugSheet extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final wsNotifier = ref.watch(websocketStateProvider.notifier);
return SheetScaffold( return SheetScaffold(
titleText: 'Debug', titleText: 'Debug',
heightFactor: 0.6, heightFactor: 0.6,
@@ -111,10 +108,7 @@ class DebugSheet extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => NetworkStatusSheet(),
(context) => NetworkStatusSheet(
onReconnect: () => wsNotifier.connect(),
),
); );
}, },
), ),