import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:extended_text_field/extended_text_field.dart'; import '../../generated/assets.dart'; import '../../config/emoji_config.dart'; import '../emoji_panel.dart'; import 'more_options_view.dart'; import 'voice_input_view.dart'; /// 表情特殊文本构建器 - 用于在 ExtendedTextField 中显示表情 class EmojiSpecialTextSpanBuilder extends SpecialTextSpanBuilder { @override SpecialText? createSpecialText( String flag, { TextStyle? textStyle, SpecialTextGestureTapCallback? onTap, required int index, }) { // 匹配 [emoji:xxx] 格式 if (flag.startsWith('[emoji:')) { return EmojiSpecialText( textStyle: textStyle, onTap: onTap, ); } return null; } } /// 表情特殊文本类 class EmojiSpecialText extends SpecialText { EmojiSpecialText({ TextStyle? textStyle, SpecialTextGestureTapCallback? onTap, }) : super( '[emoji:', ']', textStyle, onTap: onTap, ); @override InlineSpan finishText() { // 提取表情ID final emojiId = toString().replaceAll('[emoji:', '').replaceAll(']', ''); final emoji = EmojiConfig.getEmojiById(emojiId); if (emoji != null) { // 返回包含表情图片的 WidgetSpan return WidgetSpan( child: Padding( padding: EdgeInsets.symmetric(horizontal: 2.w), child: Image.asset( emoji.path, width: 24.w, height: 24.w, fit: BoxFit.contain, ), ), ); } // 如果表情不存在,返回普通文本 return TextSpan( text: toString(), style: textStyle, ); } } /// 表情文本输入格式化器 - 处理删除时一次性删除整个表情标记 class EmojiTextInputFormatter extends TextInputFormatter { @override TextEditingValue formatEditUpdate( TextEditingValue oldValue, TextEditingValue newValue, ) { // 如果文本长度减少(删除操作) if (newValue.text.length < oldValue.text.length) { final oldSelection = oldValue.selection; final cursorOffset = oldSelection.baseOffset; final deletedLength = oldValue.text.length - newValue.text.length; // 在整个文本中查找所有表情标记 final emojiRegex = RegExp(r'\[emoji:\d+\]'); final allMatches = emojiRegex.allMatches(oldValue.text); // 查找光标所在位置或光标前最近的表情标记 for (final match in allMatches) { final emojiStart = match.start; final emojiEnd = match.end; // 如果光标在表情标记内部(不包括开始位置,包括结束位置) if (cursorOffset > emojiStart && cursorOffset <= emojiEnd) { // 删除整个表情标记 final beforeEmoji = oldValue.text.substring(0, emojiStart); final afterEmoji = oldValue.text.substring(emojiEnd); final newText = beforeEmoji + afterEmoji; // 删除表情后,光标保持在删除操作发生的位置 // 跟删除普通文本一样:删除后光标停留在删除位置 // 光标位置 = 删除位置 - 表情长度 final emojiLength = emojiEnd - emojiStart; int newCursorOffset = (cursorOffset - emojiLength).clamp(0, newText.length); return TextEditingValue( text: newText, selection: TextSelection.collapsed(offset: newCursorOffset), ); } // 如果光标刚好在表情标记的开始位置,且删除的是表情标记内部的字符 if (cursorOffset == emojiStart) { // 检查删除的字符是否在表情标记范围内 if (cursorOffset + deletedLength <= emojiEnd) { // 删除整个表情标记 final beforeEmoji = oldValue.text.substring(0, emojiStart); final afterEmoji = oldValue.text.substring(emojiEnd); final newText = beforeEmoji + afterEmoji; // 删除表情后,光标保持在删除位置 // 光标位置 = 删除位置 - 表情长度 final emojiLength = emojiEnd - emojiStart; int newCursorOffset = (cursorOffset - emojiLength).clamp(0, newText.length); return TextEditingValue( text: newText, selection: TextSelection.collapsed(offset: newCursorOffset), ); } } } } return newValue; } } class ChatInputBar extends StatefulWidget { final ValueChanged onSendMessage; final ValueChanged>? onImageSelected; final Function(String filePath, int seconds)? onVoiceRecorded; final VoidCallback? onVoiceCall; // 语音通话回调 final Future Function()? onVideoCall; // 视频通话回调 final VoidCallback? onGiftTap; // 礼物按钮回调 const ChatInputBar({ required this.onSendMessage, this.onImageSelected, this.onVoiceRecorded, this.onVoiceCall, this.onVideoCall, this.onGiftTap, super.key, }); @override State createState() => _ChatInputBarState(); } class _ChatInputBarState extends State { final TextEditingController _textController = TextEditingController(); final FocusNode _focusNode = FocusNode(); bool _isMoreOptionsVisible = false; bool _isVoiceVisible = false; bool _isEmojiVisible = false; void _handleSendMessage() { if (_textController.text.isNotEmpty) { widget.onSendMessage(_textController.text); _textController.clear(); } } // 切换更多选项的显示状态 void _toggleMoreOptions() { print('📷 [ChatInputBar] 更多选项(图片)按钮被点击'); setState(() { _isMoreOptionsVisible = !_isMoreOptionsVisible; if (_isMoreOptionsVisible) { _isVoiceVisible = false; _isEmojiVisible = false; } // 收起键盘 FocusManager.instance.primaryFocus?.unfocus(); }); } void _handleImageTap(List imagePaths) { // 将图片路径列表传递给父组件 if (widget.onImageSelected != null) { widget.onImageSelected!(imagePaths); } } void _handleCameraTap(String imagePath) { // 将单个图片路径包装成列表传递给父组件 if (widget.onImageSelected != null) { widget.onImageSelected!([imagePath]); } } void _toggleVoiceOptions() { setState(() { _isVoiceVisible = !_isVoiceVisible; if (_isVoiceVisible) { _isMoreOptionsVisible = false; _isEmojiVisible = false; } FocusManager.instance.primaryFocus?.unfocus(); }); } void _toggleEmojiPanel() { setState(() { _isEmojiVisible = !_isEmojiVisible; if (_isEmojiVisible) { _isMoreOptionsVisible = false; _isVoiceVisible = false; } FocusManager.instance.primaryFocus?.unfocus(); }); } // 关闭所有控制面板 void _closeAllPanels() { if (!mounted) return; if (_isMoreOptionsVisible || _isVoiceVisible || _isEmojiVisible) { setState(() { _isMoreOptionsVisible = false; _isVoiceVisible = false; _isEmojiVisible = false; }); } } @override void initState() { super.initState(); // 监听输入框焦点变化 _focusNode.addListener(() { if (_focusNode.hasFocus && mounted) { // 输入框获得焦点(键盘弹起),关闭所有控制面板 _closeAllPanels(); } }); } @override void dispose() { _focusNode.dispose(); _textController.dispose(); super.dispose(); } void _handleEmojiSelected(EmojiItem emoji) { // 将表情添加到输入框 final currentText = _textController.text; final emojiText = '[emoji:${emoji.id}]'; final newText = currentText + emojiText; _textController.text = newText; // 将光标移到末尾 _textController.selection = TextSelection.fromPosition( TextPosition(offset: newText.length), ); setState(() {}); // 刷新显示 } @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 16.w), color: Colors.white, child: Column( children: [ SizedBox(height: 10.h), Row( children: [ Expanded( child: Container( height: 40.h, decoration: BoxDecoration( color: Color(0xffF5F5F5), borderRadius: BorderRadius.circular(5.h), ), padding: EdgeInsets.symmetric(horizontal: 16.w), child: ExtendedTextField( controller: _textController, focusNode: _focusNode, specialTextSpanBuilder: EmojiSpecialTextSpanBuilder(), inputFormatters: [ EmojiTextInputFormatter(), ], decoration: InputDecoration( border: InputBorder.none, hintText: "请输入聊天内容~", hintStyle: TextStyle( fontSize: 14.sp, color: Colors.grey, ), ), style: TextStyle( fontSize: 14.sp, color: Colors.black, ), onChanged: (value) { setState(() {}); // 刷新以更新表情显示 }, ), ), ), SizedBox(width: 12.w), // 发送按钮 Container( padding: EdgeInsets.symmetric( horizontal: 16.w, vertical: 8.h, ), decoration: BoxDecoration( color: Color.fromRGBO(117, 98, 249, 1), borderRadius: BorderRadius.circular(5.h), ), child: Text( "发送", style: TextStyle( fontSize: 14.sp, color: Colors.white, fontWeight: FontWeight.w500, ), ), ).onTap(_handleSendMessage), ], ), SizedBox(height: 12.h), // 底部工具栏 Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ // 语音消息按钮 Image.asset( Assets.imagesAudio, width: 24.w, height: 24.w, ).onTap(_toggleVoiceOptions), // 语音通话按钮(暂时隐藏) // Image.asset( // Assets.imagesSendCall, // width: 24.w, // height: 24.w, // ).onTap(() { // widget.onVoiceCall?.call(); // }), // 视频通话按钮(暂时隐藏) Image.asset( Assets.imagesVideoCall, width: 24.w, height: 24.w, ).onTap(() async { await widget.onVideoCall?.call(); }), // 礼物按钮 Image.asset(Assets.imagesGift, width: 24.w, height: 24.w).onTap(() { widget.onGiftTap?.call(); }), // 表情按钮 Image.asset(Assets.imagesEmoji, width: 24.w, height: 24.w).onTap(_toggleEmojiPanel), // 更多按钮 Image.asset( Assets.imagesAdd, width: 24.w, height: 24.w, ).onTap(_toggleMoreOptions), ], ), ], ), ), // 更多选项展开视图(支持图片) MoreOptionsView( isVisible: _isMoreOptionsVisible, onImageSelected: _handleImageTap, onCameraSelected: _handleCameraTap, ), // 语音输入展开视图(与 MoreOptionsView 相同的展开方式) VoiceInputView( isVisible: _isVoiceVisible, onVoiceRecorded: widget.onVoiceRecorded, ), // 表情面板 EmojiPanel( isVisible: _isEmojiVisible, onEmojiSelected: _handleEmojiSelected, ), ], ); } }