import 'package:dartssh2/dartssh2.dart'; import 'package:flutter/material.dart'; import 'package:vissh/pages/terminal_page.dart'; import 'package:vissh/widgets/draggable_window.dart'; import 'package:vissh/models/window_data.dart'; import 'package:vissh/widgets/taskbar.dart'; import 'package:vissh/pages/login_page.dart'; import 'package:vissh/models/credentials.dart'; import 'package:flutter/services.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: const LoginPage(), ); } } class WindowManager extends StatefulWidget { final SSHClient sshClient; final SSHCredentials credentials; const WindowManager({ super.key, required this.sshClient, required this.credentials, }); @override State createState() => _WindowManagerState(); } class _WindowManagerState extends State { final List _windows = []; int _nextWindowId = 0; bool _isVerified = false; String _verificationMessage = '连接到服务器...'; String _verificationFailedMessage = ''; @override void initState() { SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom]); 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), Icons.folder_open, // 使用新的构建器语法 (id) => const Center( 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), ), ); } 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( WindowData( id: id, title: title, position: position, size: const Size(700, 500), child: windowChild, // 存储创建好的 Widget 实例 icon: icon, ), ); _bringToFront(id); }); } void _bringToFront(String id) { final windowIndex = _windows.indexWhere((w) => w.id == id); if (windowIndex != -1) { final window = _windows[windowIndex]; if (window.isMinimized) { window.isMinimized = false; } if (windowIndex != _windows.length - 1) { final window = _windows.removeAt(windowIndex); _windows.add(window); } setState(() {}); } } void _minimizeWindow(String id) { setState(() { final windowIndex = _windows.indexWhere((w) => w.id == id); if (windowIndex != -1) { _windows[windowIndex].isMinimized = true; } }); } void _onWindowIconTap(String id) { final windowIndex = _windows.indexWhere((w) => w.id == id); if (windowIndex == -1) return; final window = _windows[windowIndex]; final topMostIndex = _windows.lastIndexWhere((w) => !w.isMinimized); setState(() { if (window.isMinimized) { window.isMinimized = false; _bringToFront(id); } else { if (topMostIndex != -1 && _windows[topMostIndex].id == id) { _minimizeWindow(id); } else { _bringToFront(id); } } }); } void _removeWindow(String id) { setState(() { _windows.removeWhere((w) => w.id == id); }); } void _updateWindowPosition(String id, Offset position) { final windowIndex = _windows.indexWhere((w) => w.id == id); if (windowIndex != -1) { setState(() { _windows[windowIndex].position = position; }); } } void _updateWindowSize(String id, Size size) { final windowIndex = _windows.indexWhere((w) => w.id == id); if (windowIndex != -1) { setState(() { _windows[windowIndex].size = size; }); } } void _updateWindowMaximizeState(String id, bool isMaximized) { final windowIndex = _windows.indexWhere((w) => w.id == id); if (windowIndex != -1) { setState(() { _windows[windowIndex].isMaximized = isMaximized; }); } } @override Widget build(BuildContext context) { if (!_isVerified) { bool hasError = _verificationFailedMessage.isNotEmpty; return Scaffold( backgroundColor: const Color(0xff0078D4), 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 ? const 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) const 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; return Scaffold( backgroundColor: Colors.blueGrey[900], body: Container( decoration: const BoxDecoration( image: DecorationImage( image: NetworkImage('https://www.meowdream.cn/background.jpg'), fit: BoxFit.cover, ), ), child: Column( children: [ Expanded( child: Stack( children: _windows.where((w) => !w.isMinimized).map((data) { final bool isActive = data.id == activeWindowId; return DraggableWindow( key: ValueKey(data.id), id: data.id, initialPosition: data.position, initialSize: data.size, title: data.title, icon: data.icon, isActive: isActive, isMaximized: data.isMaximized, onBringToFront: _bringToFront, onMinimize: _minimizeWindow, onClose: _removeWindow, onMove: _updateWindowPosition, onResize: _updateWindowSize, onMaximizeChanged: _updateWindowMaximizeState, child: data.child, ); }).toList(), ), ), Taskbar( windows: sortedWindowsForTaskbar, activeWindowId: activeWindowId, onWindowIconTap: _onWindowIconTap, ), ], ), ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton( onPressed: () => _addWindow( 'Terminal', const Offset(150, 150), Icons.terminal, (id) => TerminalPage( credentials: widget.credentials, onSessionEnd: () => _removeWindow(id), ), ), tooltip: 'Start Terminal', child: const Icon(Icons.add), ), const SizedBox(height: 32), ], ), ); } }