新增了连接质量监测

This commit is contained in:
梦凌汐 2025-07-09 20:19:37 +08:00
parent 98ca5c46d0
commit 6589a3013d
3 changed files with 128 additions and 22 deletions

View File

@ -1,3 +1,4 @@
import 'dart:async';
import 'package:dartssh2/dartssh2.dart';
import 'package:flutter/material.dart';
import 'package:vissh/pages/terminal_page.dart';
@ -46,11 +47,28 @@ class _WindowManagerState extends State<WindowManager> {
String _verificationMessage = '连接到服务器...';
String _verificationFailedMessage = '';
String _connectionQuality = '';
Timer? _connectionQualityTimer;
@override
void initState() {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom]);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
overlays: [SystemUiOverlay.bottom]);
super.initState();
_verifyConnection();
widget.sshClient.done.then((_) {
if (mounted) {
_connectionQualityTimer?.cancel();
Navigator.of(context).pop('disconnected');
}
});
}
@override
void dispose() {
_connectionQualityTimer?.cancel();
super.dispose();
}
Future<void> _verifyConnection() async {
@ -61,6 +79,7 @@ class _WindowManagerState extends State<WindowManager> {
_isVerified = true;
_setupInitialWindows();
});
_startConnectionQualityChecks();
} catch (e) {
if (!mounted) return;
setState(() {
@ -70,21 +89,57 @@ class _WindowManagerState extends State<WindowManager> {
}
}
void _startConnectionQualityChecks() {
_connectionQualityTimer =
Timer.periodic(const Duration(seconds: 5), (_) {
_checkConnectionQuality();
});
}
Future<void> _checkConnectionQuality() async {
if (!mounted) return;
try {
final stopwatch = Stopwatch()..start();
await widget.sshClient.run('echo');
stopwatch.stop();
final latency = stopwatch.elapsedMilliseconds;
String quality;
if (latency < 150) {
quality = '良好';
} else if (latency < 500) {
quality = '一般';
} else {
quality = '';
}
if (mounted) {
setState(() {
_connectionQuality = '$quality (${latency}ms)';
});
}
} catch (e) {
if (mounted) {
setState(() {
_connectionQuality = '断开连接';
});
}
}
}
void _setupInitialWindows() {
_addWindow(
'File Explorer',
const Offset(100, 100),
Icons.folder_open,
// 使
(id) => const Center(
child: Text('View your files...', style: TextStyle(color: Colors.white)),
child:
Text('View your files...', style: TextStyle(color: Colors.white)),
),
);
_addWindow(
'Terminal',
const Offset(150, 150),
Icons.terminal,
// 使 id
(id) => TerminalPage(
credentials: widget.credentials,
onSessionEnd: () => _removeWindow(id),
@ -95,7 +150,6 @@ class _WindowManagerState extends State<WindowManager> {
void _addWindow(String title, Offset position, IconData icon,
Widget Function(String id) childBuilder) {
final id = 'window_${_nextWindowId++}';
// Widget
final windowChild = childBuilder(id);
setState(() {
_windows.add(
@ -104,7 +158,7 @@ class _WindowManagerState extends State<WindowManager> {
title: title,
position: position,
size: const Size(700, 500),
child: windowChild, // Widget
child: windowChild,
icon: icon,
),
);
@ -209,13 +263,15 @@ class _WindowManagerState extends State<WindowManager> {
children: [
const SizedBox(height: 120),
hasError
? const Icon(Icons.error_outline, color: Colors.redAccent, size: 48)
? const Icon(Icons.error_outline,
color: Colors.redAccent, size: 48)
: SizedBox(
width: 48,
height: 48,
child: CircularProgressIndicator(
strokeWidth: 3.5,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white.withValues(alpha: 0.8)),
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white.withValues(alpha: 0.8)),
),
),
const SizedBox(height: 40),
@ -249,9 +305,11 @@ class _WindowManagerState extends State<WindowManager> {
}
},
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 12),
backgroundColor: Colors.white.withValues(alpha: 0.1),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4)),
),
child: const Text(
'返回',
@ -269,7 +327,8 @@ class _WindowManagerState extends State<WindowManager> {
}
final topMostIndex = _windows.lastIndexWhere((w) => !w.isMinimized);
final activeWindowId = topMostIndex != -1 ? _windows[topMostIndex].id : null;
final activeWindowId =
topMostIndex != -1 ? _windows[topMostIndex].id : null;
final sortedWindowsForTaskbar = List<WindowData>.from(_windows);
sortedWindowsForTaskbar.sort((a, b) {
@ -291,12 +350,9 @@ class _WindowManagerState extends State<WindowManager> {
children: [
Expanded(
child: Stack(
// 使 collection for
children: [
for (final data in _windows)
Offstage(
// 使 Offstage
// Offstage (state)使
offstage: data.isMinimized,
child: DraggableWindow(
key: data.key,
@ -324,6 +380,7 @@ class _WindowManagerState extends State<WindowManager> {
windows: sortedWindowsForTaskbar,
activeWindowId: activeWindowId,
onWindowIconTap: _onWindowIconTap,
connectionQuality: _connectionQuality,
),
],
),

View File

@ -36,7 +36,7 @@ class _LoginPageState extends State<LoginPage> {
if (!mounted) return;
Navigator.push(
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => WindowManager(
@ -45,6 +45,12 @@ class _LoginPageState extends State<LoginPage> {
),
),
);
if (result == 'disconnected' && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('连接断开')),
);
}
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(

View File

@ -7,6 +7,7 @@ class Taskbar extends StatefulWidget {
final String? activeWindowId;
final Function(String) onWindowIconTap;
final double height;
final String? connectionQuality;
const Taskbar({
super.key,
@ -14,6 +15,7 @@ class Taskbar extends StatefulWidget {
required this.onWindowIconTap,
this.activeWindowId,
this.height = 48.0,
this.connectionQuality,
});
@override
@ -28,7 +30,8 @@ class _TaskbarState extends State<Taskbar> {
void initState() {
super.initState();
_updateTime();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) => _updateTime());
_timer =
Timer.periodic(const Duration(seconds: 1), (timer) => _updateTime());
}
@override
@ -49,28 +52,65 @@ class _TaskbarState extends State<Taskbar> {
@override
Widget build(BuildContext context) {
IconData connectionQualityIcon;
if(widget.connectionQuality == null || widget.connectionQuality!.isEmpty) {
connectionQualityIcon = Icons.network_check;
} else {
if(widget.connectionQuality!.startsWith('良好')) {
connectionQualityIcon = Icons.network_wifi_3_bar;
} else if(widget.connectionQuality!.startsWith('一般')) {
connectionQualityIcon = Icons.network_wifi_2_bar;
} else {
connectionQualityIcon = Icons.network_wifi_1_bar;
}
}
return Container(
height: widget.height,
color: Colors.black.withValues(alpha: 0.8),
child: Row(
children: [
if (widget.connectionQuality != null &&
widget.connectionQuality!.isNotEmpty)
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: Row(
children: [
Icon(connectionQualityIcon,
size: 16, color: Colors.white),
const SizedBox(width: 8),
Text(
'连接质量: ${widget.connectionQuality}',
style: const TextStyle(color: Colors.white, fontSize: 12),
),
],
),
),
const SizedBox(width: 16),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: widget.windows.map((window) {
final isActive = widget.activeWindowId == window.id && !window.isMinimized;
final isActive =
widget.activeWindowId == window.id && !window.isMinimized;
return InkWell(
onTap: () => widget.onWindowIconTap(window.id),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0),
decoration: BoxDecoration(
color: isActive ? Colors.white.withValues(alpha: 0.08) : Colors.transparent,
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 8.0),
decoration: BoxDecoration(
color: isActive
? Colors.white.withValues(alpha: 0.08)
: Colors.transparent,
borderRadius: BorderRadius.circular(4.0),
),
child: Tooltip(
message: window.title,
child: Icon(window.icon, color: window.isMinimized ? Colors.white.withAlpha(150) : Colors.white.withAlpha(220), size: 24),
child: Icon(
window.icon,
color: window.isMinimized
? Colors.white.withAlpha(150)
: Colors.white.withAlpha(220),
size: 24),
),
),
);
@ -81,7 +121,10 @@ class _TaskbarState extends State<Taskbar> {
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
_currentTime,
style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500),
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w500),
),
),
],