From b561a7f61247f8826a7423bea1b4a7c86816fb6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=A6=E5=87=8C=E6=B1=90?= Date: Wed, 9 Jul 2025 13:28:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BA=86SSH=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- analysis_options.yaml | 3 + lib/main.dart | 140 +++++++++++++++++++++++++++++++++++--- lib/pages/login_page.dart | 127 ++++++++++++++++++++++++++++++++++ pubspec.yaml | 1 + 4 files changed, 260 insertions(+), 11 deletions(-) create mode 100644 lib/pages/login_page.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d29021..fc8e8a3 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -7,6 +7,9 @@ # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. +analyzer: + errors: + use_build_context_synchronously: ignore include: package:flutter_lints/flutter.yaml linter: diff --git a/lib/main.dart b/lib/main.dart index 6f9bdde..fa2821c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,9 @@ +import 'package:dartssh2/dartssh2.dart'; import 'package:flutter/material.dart'; import 'widgets/draggable_window.dart'; import 'models/window_data.dart'; import 'widgets/taskbar.dart'; +import 'pages/login_page.dart'; void main() { runApp(const MyApp()); @@ -14,13 +16,15 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, - home: const WindowManager(), + home: const LoginPage(), ); } } class WindowManager extends StatefulWidget { - const WindowManager({super.key}); + final SSHClient sshClient; + final String host; + const WindowManager({super.key, required this.sshClient, required this.host}); @override State createState() => _WindowManagerState(); @@ -30,9 +34,34 @@ class _WindowManagerState extends State { final List _windows = []; int _nextWindowId = 0; + bool _isVerified = false; + String _verificationMessage = '连接到服务器...'; + String _verificationFailedMessage = ''; + @override void initState() { super.initState(); + _verifyConnection(); + } + + Future _verifyConnection() async { + try { + await widget.sshClient.run('echo Vissh SSH Connection Verified'); + if (!mounted) return; + setState(() { + _isVerified = true; + _setupInitialWindows(); + }); + } catch (e) { + if (!mounted) return; + setState(() { + _verificationMessage = '无法登录'; + _verificationFailedMessage = '请检查服务器地址和凭据,然后重试。\n错误: $e'; + }); + } + } + + void _setupInitialWindows() { _addWindow( 'File Explorer', const Offset(100, 100), @@ -44,8 +73,9 @@ class _WindowManagerState extends State { _addWindow( 'Terminal', const Offset(150, 150), - const Center( - child: Text('Run your commands...', style: TextStyle(color: Colors.white)), + Center( + child: Text('SSH connected to: ${widget.host}', + style: const TextStyle(color: Colors.white)), ), Icons.web, ); @@ -72,10 +102,10 @@ class _WindowManagerState extends State { final windowIndex = _windows.indexWhere((w) => w.id == id); if (windowIndex != -1) { final window = _windows[windowIndex]; - if(window.isMinimized) { + if (window.isMinimized) { window.isMinimized = false; } - + if (windowIndex != _windows.length - 1) { final window = _windows.removeAt(windowIndex); _windows.add(window); @@ -92,7 +122,7 @@ class _WindowManagerState extends State { } }); } - + void _onWindowIconTap(String id) { final windowIndex = _windows.indexWhere((w) => w.id == id); if (windowIndex == -1) return; @@ -132,7 +162,7 @@ class _WindowManagerState extends State { void _updateWindowSize(String id, Size size) { final windowIndex = _windows.indexWhere((w) => w.id == id); if (windowIndex != -1) { - setState(() { + setState(() { _windows[windowIndex].size = size; }); } @@ -149,15 +179,100 @@ class _WindowManagerState extends State { @override Widget build(BuildContext context) { + if (!_isVerified) { + // --- Win11 风格的验证界面 --- + bool hasError = _verificationFailedMessage.isNotEmpty; + + return Scaffold( + backgroundColor: const Color(0xff0078D4), // Win11 蓝色背景 + body: Container( + decoration: const BoxDecoration( + image: DecorationImage( + image: NetworkImage('https://www.meowdream.cn/background.jpg'), + fit: BoxFit.cover, + ), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 120), + + hasError + ? Icon(Icons.error_outline, color: Colors.redAccent, size: 48) + : SizedBox( + width: 48, + height: 48, + child: CircularProgressIndicator( + strokeWidth: 3.5, + valueColor: AlwaysStoppedAnimation(Colors.white.withValues(alpha: 0.8)), + ), + ), + const SizedBox(height: 40), + + Text( + _verificationMessage, + style: const TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.w300, + ), + ), + const SizedBox(height: 10), + + if (hasError) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 40.0), + child: Text( + _verificationFailedMessage, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white.withValues(alpha: 0.8), + fontSize: 14, + ), + ), + ), + + if (hasError) + SizedBox(height: 16), + + if (hasError) + TextButton( + onPressed: () { + if (Navigator.canPop(context)) { + Navigator.of(context).pop(); + } + }, + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + backgroundColor: Colors.white.withValues(alpha: 0.1), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + ), + child: const Text( + '返回', + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + ), + ], + ), + ), + ), + ); + } + final sortedWindowsForTaskbar = List.from(_windows); sortedWindowsForTaskbar.sort((a, b) { final aId = int.tryParse(a.id.split('_').last) ?? 0; final bId = int.tryParse(b.id.split('_').last) ?? 0; return aId.compareTo(bId); }); - + final topMostIndex = _windows.lastIndexWhere((w) => !w.isMinimized); - final activeWindowId = topMostIndex != -1 ? _windows[topMostIndex].id : null; + final activeWindowId = + topMostIndex != -1 ? _windows[topMostIndex].id : null; return Scaffold( backgroundColor: Colors.blueGrey[900], @@ -209,7 +324,10 @@ class _WindowManagerState extends State { onPressed: () => _addWindow( 'New Window', const Offset(200, 200), - const Center(child: Text('This is the new window content area', style: TextStyle(color: Colors.white))), + const Center( + child: Text('This is the new window content area', + style: TextStyle(color: Colors.white)) + ), Icons.add_circle_outline, ), tooltip: 'Add New Window', diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart new file mode 100644 index 0000000..535096a --- /dev/null +++ b/lib/pages/login_page.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:dartssh2/dartssh2.dart'; +import '../main.dart'; + +class LoginPage extends StatefulWidget { + const LoginPage({super.key}); + + @override + State createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + final TextEditingController _hostController = TextEditingController(); + final TextEditingController _usernameController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + bool _isLoading = false; + + Future _login() async { + setState(() { + _isLoading = true; + }); + + try { + final client = SSHClient( + await SSHSocket.connect(_hostController.text, 22), + username: _usernameController.text, + onPasswordRequest: () => _passwordController.text, + ); + + // ignore: use_build_context_synchronously + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + WindowManager(sshClient: client, host: _hostController.text)), + ); + } catch (e) { + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Login Failed: $e')), + ); + } finally { + setState(() { + _isLoading = false; + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: const BoxDecoration( + image: DecorationImage( + image: NetworkImage('https://www.meowdream.cn/background.jpg'), + fit: BoxFit.cover, + ), + ), + child: Center( + child: Container( + padding: const EdgeInsets.all(20.0), + width: 300, + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.7), + borderRadius: BorderRadius.circular(10), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('登录', + style: TextStyle(color: Colors.white, fontSize: 24)), + const SizedBox(height: 20), + TextField( + controller: _hostController, + style: const TextStyle(color: Colors.white), + decoration: const InputDecoration( + labelText: '地址', + labelStyle: TextStyle(color: Colors.white70), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.white70)), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.white)), + ), + ), + const SizedBox(height: 10), + TextField( + controller: _usernameController, + style: const TextStyle(color: Colors.white), + decoration: const InputDecoration( + labelText: '用户名', + labelStyle: TextStyle(color: Colors.white70), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.white70)), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.white)), + ), + ), + const SizedBox(height: 10), + TextField( + controller: _passwordController, + obscureText: true, + style: const TextStyle(color: Colors.white), + decoration: const InputDecoration( + labelText: '密码', + labelStyle: TextStyle(color: Colors.white70), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.white70)), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.white)), + ), + ), + const SizedBox(height: 20), + _isLoading + ? const CircularProgressIndicator() + : ElevatedButton.icon( + onPressed: _login, + icon: const Icon(Icons.login), + label: const Text('连接'), + ), + ], + ), + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index bd6f260..e8660c3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.8 + dartssh2: ^2.13.0 dev_dependencies: flutter_test: