import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:xterm/xterm.dart'; import '../providers/auth_provider.dart'; import '../services/terminal_service.dart'; class TerminalScreen extends StatefulWidget { final String sessionName; const TerminalScreen({super.key, required this.sessionName}); @override State createState() => _TerminalScreenState(); } class _TerminalScreenState extends State { late Terminal _terminal; late TerminalService _terminalService; StreamSubscription? _outputSubscription; StreamSubscription? _stateSubscription; final _terminalController = TerminalController(); @override void initState() { super.initState(); _terminal = Terminal( maxLines: 10000, ); _terminalService = TerminalService(); _connect(); } void _connect() { final auth = context.read(); _terminalService.setToken(auth.token); _outputSubscription = _terminalService.output.listen((data) { _terminal.write(data); }); _stateSubscription = _terminalService.connectionState.listen((state) { setState(() {}); if (state == TerminalConnectionState.connected) { _terminal.write('Connected to ${widget.sessionName}\r\n'); } else if (state == TerminalConnectionState.error) { _terminal.write('\r\n[Connection Error]\r\n'); } }); _terminal.onOutput = (data) { _terminalService.sendInput(data); }; _terminal.onResize = (width, height, pixelWidth, pixelHeight) { _terminalService.resize(height, width); }; _terminalService.connect(widget.sessionName); } @override void dispose() { _outputSubscription?.cancel(); _stateSubscription?.cancel(); _terminalService.dispose(); _terminalController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.sessionName), actions: [ // Connection status indicator Padding( padding: const EdgeInsets.only(right: 8), child: Icon( Icons.circle, size: 12, color: switch (_terminalService.currentState) { TerminalConnectionState.connected => Colors.green, TerminalConnectionState.connecting => Colors.orange, _ => Colors.red, }, ), ), // Copy button IconButton( icon: const Icon(Icons.copy), onPressed: () { final selection = _terminalController.selection; if (selection != null) { final text = _terminal.buffer.getText(selection); Clipboard.setData(ClipboardData(text: text)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Copied to clipboard')), ); } }, tooltip: 'Copy Selection', ), // Paste button IconButton( icon: const Icon(Icons.paste), onPressed: () async { final data = await Clipboard.getData(Clipboard.kTextPlain); if (data?.text != null) { _terminalService.sendInput(data!.text!); } }, tooltip: 'Paste', ), // Reconnect button IconButton( icon: const Icon(Icons.refresh), onPressed: () { _terminalService.disconnect(); _terminal.write('\r\n[Reconnecting...]\r\n'); _connect(); }, tooltip: 'Reconnect', ), ], ), body: SafeArea( child: TerminalView( _terminal, controller: _terminalController, textStyle: const TerminalStyle( fontSize: 13, fontFamily: 'JetBrainsMono', ), theme: const TerminalTheme( cursor: Color(0xFFD97706), selection: Color(0x40D97706), foreground: Color(0xFFE5E5E5), background: Color(0xFF1A1A1A), black: Color(0xFF1A1A1A), red: Color(0xFFEF4444), green: Color(0xFF22C55E), yellow: Color(0xFFEAB308), blue: Color(0xFF3B82F6), magenta: Color(0xFFA855F7), cyan: Color(0xFF06B6D4), white: Color(0xFFE5E5E5), brightBlack: Color(0xFF6B7280), brightRed: Color(0xFFF87171), brightGreen: Color(0xFF4ADE80), brightYellow: Color(0xFFFDE047), brightBlue: Color(0xFF60A5FA), brightMagenta: Color(0xFFC084FC), brightCyan: Color(0xFF22D3EE), brightWhite: Color(0xFFFFFFFF), searchHitBackground: Color(0xFFD97706), searchHitBackgroundCurrent: Color(0xFFEF4444), searchHitForeground: Color(0xFF1A1A1A), ), autofocus: true, alwaysShowCursor: true, ), ), // Quick action buttons for mobile bottomNavigationBar: Container( height: 48, color: const Color(0xFF2D2D2D), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buildQuickButton('Ctrl+C', () => _terminalService.sendInput('\x03')), _buildQuickButton('Ctrl+D', () => _terminalService.sendInput('\x04')), _buildQuickButton('Tab', () => _terminalService.sendInput('\t')), _buildQuickButton('Esc', () => _terminalService.sendInput('\x1b')), _buildQuickButton('Up', () => _terminalService.sendInput('\x1b[A')), _buildQuickButton('Down', () => _terminalService.sendInput('\x1b[B')), ], ), ), ); } Widget _buildQuickButton(String label, VoidCallback onTap) { return InkWell( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Text( label, style: TextStyle( color: Colors.grey.shade400, fontSize: 12, fontWeight: FontWeight.w500, ), ), ), ); } }