import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../blocs/geviserver/geviserver_bloc.dart'; import '../../blocs/geviserver/geviserver_event.dart'; import '../../blocs/geviserver/geviserver_state.dart'; import '../../widgets/app_drawer.dart'; class GeViServerScreen extends StatefulWidget { const GeViServerScreen({super.key}); @override State createState() => _GeViServerScreenState(); } class _GeViServerScreenState extends State { // Connection form controllers final _addressController = TextEditingController(text: 'localhost'); final _usernameController = TextEditingController(text: 'sysadmin'); final _passwordController = TextEditingController(text: 'masterkey'); // CrossSwitch form controllers final _videoInputController = TextEditingController(text: '1'); final _videoOutputController = TextEditingController(text: '1'); final _switchModeController = TextEditingController(text: '0'); // Digital I/O controller final _contactIdController = TextEditingController(text: '1'); // Custom action controllers final _customActionTypeIdController = TextEditingController(text: '1'); final _customActionTextController = TextEditingController(text: 'Test message'); // Raw message controller final _rawMessageController = TextEditingController(); bool _didCheckStatus = false; @override void didChangeDependencies() { super.didChangeDependencies(); // Check status on init (only once) if (!_didCheckStatus) { _didCheckStatus = true; context.read().add(const CheckStatusEvent()); } } @override void dispose() { _addressController.dispose(); _usernameController.dispose(); _passwordController.dispose(); _videoInputController.dispose(); _videoOutputController.dispose(); _switchModeController.dispose(); _contactIdController.dispose(); _customActionTypeIdController.dispose(); _customActionTextController.dispose(); _rawMessageController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('GeViServer Control'), actions: [ BlocBuilder( builder: (context, state) { return Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: _getStatusColor(state.connectionStatus).withOpacity(0.2), borderRadius: BorderRadius.circular(16), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( _getStatusIcon(state.connectionStatus), size: 16, color: _getStatusColor(state.connectionStatus), ), const SizedBox(width: 6), Text( _getStatusText(state.connectionStatus), style: TextStyle( color: _getStatusColor(state.connectionStatus), fontWeight: FontWeight.bold, ), ), ], ), ), const SizedBox(width: 16), ], ); }, ), ], ), drawer: const AppDrawer(currentRoute: '/geviserver'), body: BlocConsumer( listener: (context, state) { if (state.lastActionResult != null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.lastActionResult!), backgroundColor: state.lastActionSuccess ? Colors.green : Colors.red, duration: const Duration(seconds: 2), ), ); context.read().add(const ClearActionResultEvent()); } }, builder: (context, state) { return Row( children: [ // Left panel - Controls Expanded( flex: 2, child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildConnectionCard(context, state), const SizedBox(height: 16), if (state.isConnected) ...[ _buildVideoControlCard(context, state), const SizedBox(height: 16), _buildDigitalIOCard(context, state), const SizedBox(height: 16), _buildCustomActionCard(context, state), const SizedBox(height: 16), _buildRawMessageCard(context, state), ], ], ), ), ), // Right panel - Message log Expanded( flex: 1, child: _buildMessageLogPanel(context, state), ), ], ); }, ), ); } Widget _buildConnectionCard(BuildContext context, GeViServerState state) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.power_settings_new, color: state.isConnected ? Colors.green : Colors.grey, ), const SizedBox(width: 8), Text( 'Connection', style: Theme.of(context).textTheme.titleLarge, ), ], ), const Divider(), if (!state.isConnected) ...[ Row( children: [ Expanded( child: TextField( controller: _addressController, decoration: const InputDecoration( labelText: 'Server Address', hintText: 'localhost', border: OutlineInputBorder(), isDense: true, ), ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: _usernameController, decoration: const InputDecoration( labelText: 'Username', hintText: 'sysadmin', border: OutlineInputBorder(), isDense: true, ), ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: _passwordController, obscureText: true, decoration: const InputDecoration( labelText: 'Password', border: OutlineInputBorder(), isDense: true, ), ), ), ], ), const SizedBox(height: 16), SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: state.isLoading ? null : () { context.read().add( ConnectGeViServerEvent( address: _addressController.text, username: _usernameController.text, password: _passwordController.text, ), ); }, icon: state.isLoading ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.link), label: Text(state.isLoading ? 'Connecting...' : 'Connect'), style: ElevatedButton.styleFrom( backgroundColor: Colors.green, foregroundColor: Colors.white, ), ), ), ] else ...[ ListTile( leading: const Icon(Icons.computer, color: Colors.green), title: Text('Connected to ${state.serverAddress}'), subtitle: Text('User: ${state.username}'), contentPadding: EdgeInsets.zero, ), const SizedBox(height: 8), SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: state.isLoading ? null : () { context.read().add( const DisconnectGeViServerEvent(), ); }, icon: const Icon(Icons.link_off), label: const Text('Disconnect'), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, ), ), ), ], ], ), ), ); } Widget _buildVideoControlCard(BuildContext context, GeViServerState state) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.videocam, color: Colors.blue), const SizedBox(width: 8), Text( 'Video Control', style: Theme.of(context).textTheme.titleLarge, ), ], ), const Divider(), Row( children: [ Expanded( child: TextField( controller: _videoInputController, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: 'Video Input', border: OutlineInputBorder(), isDense: true, ), ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: _videoOutputController, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: 'Video Output', border: OutlineInputBorder(), isDense: true, ), ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: _switchModeController, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: 'Switch Mode', border: OutlineInputBorder(), isDense: true, ), ), ), ], ), const SizedBox(height: 16), Row( children: [ Expanded( child: ElevatedButton.icon( onPressed: state.isLoading ? null : () { context.read().add( SendCrossSwitchEvent( videoInput: int.tryParse(_videoInputController.text) ?? 1, videoOutput: int.tryParse(_videoOutputController.text) ?? 1, switchMode: int.tryParse(_switchModeController.text) ?? 0, ), ); }, icon: const Icon(Icons.swap_horiz), label: const Text('CrossSwitch'), ), ), const SizedBox(width: 8), Expanded( child: ElevatedButton.icon( onPressed: state.isLoading ? null : () { context.read().add( ClearVideoOutputEvent( videoOutput: int.tryParse(_videoOutputController.text) ?? 1, ), ); }, icon: const Icon(Icons.clear), label: const Text('Clear Output'), style: ElevatedButton.styleFrom( backgroundColor: Colors.orange, foregroundColor: Colors.white, ), ), ), ], ), ], ), ), ); } Widget _buildDigitalIOCard(BuildContext context, GeViServerState state) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.toggle_on, color: Colors.purple), const SizedBox(width: 8), Text( 'Digital I/O', style: Theme.of(context).textTheme.titleLarge, ), ], ), const Divider(), Row( children: [ Expanded( flex: 2, child: TextField( controller: _contactIdController, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: 'Contact ID', border: OutlineInputBorder(), isDense: true, ), ), ), const SizedBox(width: 8), Expanded( child: ElevatedButton.icon( onPressed: state.isLoading ? null : () { context.read().add( CloseContactEvent( contactId: int.tryParse(_contactIdController.text) ?? 1, ), ); }, icon: const Icon(Icons.lock), label: const Text('Close'), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, ), ), ), const SizedBox(width: 8), Expanded( child: ElevatedButton.icon( onPressed: state.isLoading ? null : () { context.read().add( OpenContactEvent( contactId: int.tryParse(_contactIdController.text) ?? 1, ), ); }, icon: const Icon(Icons.lock_open), label: const Text('Open'), style: ElevatedButton.styleFrom( backgroundColor: Colors.green, foregroundColor: Colors.white, ), ), ), ], ), ], ), ), ); } Widget _buildCustomActionCard(BuildContext context, GeViServerState state) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.send, color: Colors.teal), const SizedBox(width: 8), Text( 'Custom Action', style: Theme.of(context).textTheme.titleLarge, ), ], ), const Divider(), Row( children: [ Expanded( child: TextField( controller: _customActionTypeIdController, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: 'Type ID', border: OutlineInputBorder(), isDense: true, ), ), ), const SizedBox(width: 8), Expanded( flex: 3, child: TextField( controller: _customActionTextController, decoration: const InputDecoration( labelText: 'Text', border: OutlineInputBorder(), isDense: true, ), ), ), const SizedBox(width: 8), ElevatedButton.icon( onPressed: state.isLoading ? null : () { context.read().add( SendCustomActionEvent( typeId: int.tryParse(_customActionTypeIdController.text) ?? 1, text: _customActionTextController.text, ), ); }, icon: const Icon(Icons.send), label: const Text('Send'), ), ], ), ], ), ), ); } Widget _buildRawMessageCard(BuildContext context, GeViServerState state) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.code, color: Colors.indigo), const SizedBox(width: 8), Text( 'Raw Message', style: Theme.of(context).textTheme.titleLarge, ), ], ), const Divider(), Row( children: [ Expanded( child: TextField( controller: _rawMessageController, decoration: const InputDecoration( labelText: 'Action Message', hintText: 'e.g., CrossSwitch(7, 3, 0)', border: OutlineInputBorder(), isDense: true, ), ), ), const SizedBox(width: 8), ElevatedButton.icon( onPressed: state.isLoading || _rawMessageController.text.isEmpty ? null : () { context.read().add( SendMessageEvent( message: _rawMessageController.text, ), ); _rawMessageController.clear(); }, icon: const Icon(Icons.send), label: const Text('Send'), ), ], ), ], ), ), ); } Widget _buildMessageLogPanel(BuildContext context, GeViServerState state) { return Container( decoration: BoxDecoration( border: Border( left: BorderSide(color: Colors.grey.shade300), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.all(16), color: Colors.grey.shade100, child: Row( children: [ const Icon(Icons.list_alt, size: 20), const SizedBox(width: 8), const Text( 'Message Log', style: TextStyle(fontWeight: FontWeight.bold), ), const Spacer(), Text( '${state.messageLog.length} messages', style: TextStyle(color: Colors.grey.shade600, fontSize: 12), ), ], ), ), Expanded( child: state.messageLog.isEmpty ? const Center( child: Text( 'No messages yet', style: TextStyle(color: Colors.grey), ), ) : ListView.builder( padding: const EdgeInsets.all(8), itemCount: state.messageLog.length, itemBuilder: (context, index) { final reversedIndex = state.messageLog.length - 1 - index; return Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), margin: const EdgeInsets.only(bottom: 4), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(4), ), child: Text( state.messageLog[reversedIndex], style: const TextStyle( fontFamily: 'monospace', fontSize: 12, ), ), ); }, ), ), ], ), ); } Color _getStatusColor(ConnectionStatus status) { switch (status) { case ConnectionStatus.connected: return Colors.green; case ConnectionStatus.connecting: return Colors.orange; case ConnectionStatus.error: return Colors.red; case ConnectionStatus.disconnected: return Colors.grey; } } IconData _getStatusIcon(ConnectionStatus status) { switch (status) { case ConnectionStatus.connected: return Icons.check_circle; case ConnectionStatus.connecting: return Icons.sync; case ConnectionStatus.error: return Icons.error; case ConnectionStatus.disconnected: return Icons.cancel; } } String _getStatusText(ConnectionStatus status) { switch (status) { case ConnectionStatus.connected: return 'Connected'; case ConnectionStatus.connecting: return 'Connecting...'; case ConnectionStatus.error: return 'Error'; case ConnectionStatus.disconnected: return 'Disconnected'; } } }