import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../blocs/wall/wall_bloc.dart'; import '../../blocs/wall/wall_event.dart'; import '../../blocs/wall/wall_state.dart'; /// Camera number input widget with prefix selection. /// /// Legacy behavior: field starts empty, digits typed by user (up to 6), /// prefix applied on Enter via [WallState.fullCameraNumber]. class CameraInputWidget extends StatefulWidget { const CameraInputWidget({super.key}); @override State createState() => _CameraInputWidgetState(); } class _CameraInputWidgetState extends State { final FocusNode _focusNode = FocusNode(); @override void dispose() { _focusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { final hasSelection = state.selectedViewerId != null; return KeyboardListener( focusNode: _focusNode, autofocus: true, onKeyEvent: (event) => _handleKeyEvent(context, event, state), child: Row( mainAxisSize: MainAxisSize.min, children: [ // Prefix buttons _PrefixButton( prefix: 500, isSelected: state.cameraPrefix == 500, onTap: hasSelection ? () => context .read() .add(const SetCameraPrefix(500)) : null, ), const SizedBox(width: 4), _PrefixButton( prefix: 501, isSelected: state.cameraPrefix == 501, onTap: hasSelection ? () => context .read() .add(const SetCameraPrefix(501)) : null, ), const SizedBox(width: 4), _PrefixButton( prefix: 502, isSelected: state.cameraPrefix == 502, onTap: hasSelection ? () => context .read() .add(const SetCameraPrefix(502)) : null, ), const SizedBox(width: 16), // Camera number display (shows only typed digits) Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: state.isEditing ? const Color(0xFF1A3A5C) : (hasSelection ? const Color(0xFF2D3748) : const Color(0xFF1A202C)), borderRadius: BorderRadius.circular(4), border: Border.all( color: state.isEditing ? const Color(0xFF00D4FF) : (hasSelection ? const Color(0xFF4A5568) : const Color(0xFF2D3748)), width: state.isEditing ? 2 : 1, ), ), child: SizedBox( width: 100, child: Text( state.cameraInputDisplay, style: TextStyle( color: hasSelection ? Colors.white : const Color(0xFF4A5568), fontSize: 20, fontWeight: FontWeight.bold, fontFamily: 'monospace', letterSpacing: 2, ), ), ), ), const SizedBox(width: 8), // Backspace button IconButton( icon: const Icon(Icons.backspace), color: Colors.white70, onPressed: hasSelection && state.cameraNumberInput.isNotEmpty ? () => context .read() .add(const BackspaceCameraDigit()) : null, ), // Execute button ElevatedButton.icon( icon: const Icon(Icons.send), label: const Text('OK'), style: ElevatedButton.styleFrom( backgroundColor: state.canExecuteCrossSwitch ? Colors.green : Colors.grey, foregroundColor: Colors.white, ), onPressed: state.canExecuteCrossSwitch ? () => context .read() .add(const ExecuteCrossSwitch()) : null, ), ], ), ); }, ); } void _handleKeyEvent( BuildContext context, KeyEvent event, WallState state) { if (event is! KeyDownEvent) return; if (state.selectedViewerId == null) return; final key = event.logicalKey; // Handle digit keys if (key.keyId >= LogicalKeyboardKey.digit0.keyId && key.keyId <= LogicalKeyboardKey.digit9.keyId) { final digit = key.keyId - LogicalKeyboardKey.digit0.keyId; context.read().add(AddCameraDigit(digit)); return; } // Handle numpad digits if (key.keyId >= LogicalKeyboardKey.numpad0.keyId && key.keyId <= LogicalKeyboardKey.numpad9.keyId) { final digit = key.keyId - LogicalKeyboardKey.numpad0.keyId; context.read().add(AddCameraDigit(digit)); return; } // Handle Enter if (key == LogicalKeyboardKey.enter || key == LogicalKeyboardKey.numpadEnter) { if (state.canExecuteCrossSwitch) { context.read().add(const ExecuteCrossSwitch()); } return; } // Handle Backspace — remove last digit if (key == LogicalKeyboardKey.backspace) { context.read().add(const BackspaceCameraDigit()); return; } // Handle Delete — cancel edit if (key == LogicalKeyboardKey.delete) { context.read().add(const CancelCameraEdit()); return; } // Handle Escape if (key == LogicalKeyboardKey.escape) { if (state.isEditing) { context.read().add(const CancelCameraEdit()); } else { context.read().add(const DeselectViewer()); } return; } // Handle period / Tab — cycle prefix if (key == LogicalKeyboardKey.period || key == LogicalKeyboardKey.numpadDecimal) { context.read().add(const CycleCameraPrefix()); return; } } } class _PrefixButton extends StatelessWidget { final int prefix; final bool isSelected; final VoidCallback? onTap; const _PrefixButton({ required this.prefix, required this.isSelected, this.onTap, }); @override Widget build(BuildContext context) { return Material( color: isSelected ? const Color(0xFF3182CE) : const Color(0xFF2D3748), borderRadius: BorderRadius.circular(4), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(4), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(4), border: Border.all( color: isSelected ? const Color(0xFF63B3ED) : const Color(0xFF4A5568), width: isSelected ? 2.0 : 1.0, ), ), child: Text( '$prefix', style: TextStyle( color: onTap != null ? Colors.white : const Color(0xFF4A5568), fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ), ); } }