mirror of
https://github.com/MeowLynxSea/vissh.git
synced 2025-07-09 19:44:34 +00:00
343 lines
10 KiB
Dart
343 lines
10 KiB
Dart
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<WindowManager> createState() => _WindowManagerState();
|
|
}
|
|
|
|
class _WindowManagerState extends State<WindowManager> {
|
|
final List<WindowData> _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<void> _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),
|
|
(isActive, onSessionEnd) => const Center(
|
|
child: Text('View your files...', style: TextStyle(color: Colors.white)),
|
|
),
|
|
Icons.folder_open,
|
|
);
|
|
_addWindow(
|
|
'Terminal',
|
|
const Offset(150, 150),
|
|
(isActive, onSessionEnd) => TerminalPage(
|
|
credentials: widget.credentials,
|
|
isActive: isActive,
|
|
onSessionEnd: onSessionEnd,
|
|
),
|
|
Icons.terminal,
|
|
);
|
|
}
|
|
|
|
void _addWindow(
|
|
String title, Offset position, Widget Function(bool, VoidCallback) child, IconData icon) {
|
|
setState(() {
|
|
final id = 'window_${_nextWindowId++}';
|
|
_windows.add(
|
|
WindowData(
|
|
id: id,
|
|
title: title,
|
|
position: position,
|
|
size: const Size(700, 500),
|
|
child: child,
|
|
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<Color>(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(
|
|
'Back',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
final sortedWindowsForTaskbar = List<WindowData>.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),
|
|
(isActive, onSessionEnd) => TerminalPage(
|
|
credentials: widget.credentials,
|
|
isActive: isActive,
|
|
onSessionEnd: onSessionEnd,
|
|
),
|
|
Icons.terminal,
|
|
),
|
|
tooltip: 'Add New Window',
|
|
child: const Icon(Icons.add),
|
|
),
|
|
const SizedBox(height: 32),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |