import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import 'package:ju_rc_app/lib/boxes.dart'; import 'package:ju_rc_app/lib/serial.dart'; class SerialBox extends JuBox { const SerialBox({super.key}); @override State createState() => _SerialBoxState(); } class _SerialBoxState extends State { USerial serial = getSerial(); final ScrollController _controller = ScrollController(); @override void initState() { super.initState(); serial.listen(serialListen); } @override void dispose() { serial.removeListen(serialListen); super.dispose(); } void serialListen(String line, SerialCommand? _) { setState(() { //change serial lines _controller.jumpTo(_controller.position.maxScrollExtent); }); } @override Widget build(BuildContext context) => GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute(builder: (context) => const SerialDetailPage()), ); }, child: ListView( controller: _controller, shrinkWrap: false, physics: const NeverScrollableScrollPhysics(), children: serial.lines .last(20) .map( (e) => Row( children: [ Padding( padding: const EdgeInsets.all(0.0), child: Icon(e.$1 != SerialMessageType.received ? Icons.arrow_left : Icons.arrow_right), ), Expanded( child: Text( e.$3, overflow: TextOverflow.ellipsis, maxLines: 1, ), ), ], ), ) .toList(), )); } class SerialDetailPage extends StatefulWidget { const SerialDetailPage({super.key}); @override State createState() => _SerialDetailPageState(); } class _SerialDetailPageState extends State { final ScrollController _controller = ScrollController(); USerial serial = getSerial(); bool jump = true; final List _messages = []; int messageIndex = 0; final TextEditingController _textController = TextEditingController(); FocusNode fc = FocusNode(); FocusNode _textFocus = FocusNode(); @override void initState() { super.initState(); serial.listen(serialListen); fc.requestFocus(); } @override void dispose() { serial.removeListen(serialListen); _textController.dispose(); _controller.dispose(); super.dispose(); } void serialListen(String line, SerialCommand? _) { if (!jump) return; setState(() { //change serial lines _controller.jumpTo(_controller.position.maxScrollExtent); }); } void submit() { if (_textController.text.isNotEmpty) { setState(() { serial.sprintln(_textController.text); _messages.add(_textController.text); messageIndex = _messages.length; _textController.text = ""; }); _textFocus.requestFocus(); } } void _handleKeyEvent(RawKeyEvent event) { if (event.runtimeType.toString() == 'RawKeyDownEvent') { if (event.logicalKey == LogicalKeyboardKey.arrowUp) { if (messageIndex > 0) { messageIndex--; Future.delayed(const Duration(microseconds: 10), () { _textController.text = _messages[messageIndex]; _textController.selection = TextSelection.collapsed(offset: _textController.text.length); }); } } else if (event.logicalKey == LogicalKeyboardKey.arrowDown) { if (messageIndex >= _messages.length - 1) { setState(() { messageIndex = _messages.length; Future.delayed(const Duration(microseconds: 10), () { _textController.text = ""; _textController.selection = const TextSelection.collapsed(offset: 0); }); }); } else { messageIndex++; Future.delayed(const Duration(microseconds: 10), () { _textController.text = _messages[messageIndex]; _textController.selection = TextSelection.collapsed(offset: _textController.text.length); }); } } } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: const Text( "Serial Console", style: TextStyle( fontSize: 16, ), ), toolbarHeight: 40, ), body: Column( children: [ Expanded( child: ListView( controller: _controller, children: serial.lines.items .map( (e) => Row( children: [ Text(DateFormat('HH:mm:ss.S').format(e.$2)), Padding( padding: const EdgeInsets.all(0.0), child: Icon(e.$1 != SerialMessageType.received ? Icons.arrow_left : Icons.arrow_right), ), Expanded( child: Text( e.$3, overflow: TextOverflow.ellipsis, maxLines: 1, ), ), ], ), ) .toList(), )), RawKeyboardListener( focusNode: fc, autofocus: true, onKey: _handleKeyEvent, child: Padding( padding: const EdgeInsets.fromLTRB(8.0, 0, 8.0, 8.0), child: Row( children: [ Expanded( child: TextField( focusNode: _textFocus, controller: _textController, autofocus: true, decoration: const InputDecoration( hintText: 'Enter a Command', ), onSubmitted: (_) { submit(); }, ), ), IconButton( icon: const Icon(Icons.send), onPressed: submit, ), ], ), )) ], )); } }