import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:matrix/matrix.dart'; class ChatInput extends StatefulWidget { final Room room; final Event? replyTo; final VoidCallback? onClearReply; const ChatInput({ super.key, required this.room, this.replyTo, this.onClearReply, }); @override State createState() => _ChatInputState(); } class _ChatInputState extends State { final _ctrl = TextEditingController(); bool _canSend = false; @override void initState() { super.initState(); _ctrl.addListener(() { final hasText = _ctrl.text.trim().isNotEmpty; if (hasText != _canSend) setState(() => _canSend = hasText); }); } @override void dispose() { _ctrl.dispose(); super.dispose(); } Future _send() async { final text = _ctrl.text.trim(); if (text.isEmpty) return; _ctrl.clear(); setState(() => _canSend = false); await widget.room.sendTextEvent( text, inReplyTo: widget.replyTo, ); widget.onClearReply?.call(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ if (widget.replyTo != null) _ReplyBar( event: widget.replyTo!, onClear: widget.onClearReply ?? () {}, ), Padding( padding: const EdgeInsets.fromLTRB(8, 4, 8, 8), child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded( child: TextField( controller: _ctrl, minLines: 1, maxLines: 6, textCapitalization: TextCapitalization.sentences, decoration: InputDecoration( hintText: 'Nachricht...', contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 10, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(24), borderSide: BorderSide.none, ), filled: true, fillColor: theme.colorScheme.surfaceContainerHigh, ), onSubmitted: (_) => _send(), keyboardType: TextInputType.multiline, inputFormatters: [ // Shift+Enter = newline, Enter = send _SendOnEnterFormatter(onSend: _send), ], ), ), const SizedBox(width: 8), AnimatedScale( scale: _canSend ? 1.0 : 0.8, duration: const Duration(milliseconds: 150), child: FloatingActionButton.small( onPressed: _canSend ? _send : null, elevation: _canSend ? 2 : 0, child: const Icon(Icons.send_rounded), ), ), ], ), ), ], ), ); } } class _ReplyBar extends StatelessWidget { final Event event; final VoidCallback onClear; const _ReplyBar({required this.event, required this.onClear}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Container( padding: const EdgeInsets.fromLTRB(16, 8, 8, 4), decoration: BoxDecoration( border: Border( top: BorderSide(color: theme.dividerColor), left: BorderSide(color: theme.colorScheme.primary, width: 3), ), ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( event.senderFromMemoryOrFallback.calcDisplayname(), style: theme.textTheme.labelSmall?.copyWith( color: theme.colorScheme.primary, fontWeight: FontWeight.bold, ), ), Text( event.body, maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodySmall, ), ], ), ), IconButton( icon: const Icon(Icons.close, size: 18), onPressed: onClear, ), ], ), ); } } class _SendOnEnterFormatter extends TextInputFormatter { final VoidCallback onSend; _SendOnEnterFormatter({required this.onSend}); @override TextEditingValue formatEditUpdate( TextEditingValue oldValue, TextEditingValue newValue, ) { return newValue; } }