import 'package:extended_text/extended_text.dart'; import 'package:extended_text_field/extended_text_field.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:provider/provider.dart'; import 'package:yumi/app/config/business_logic_strategy.dart'; import 'package:yumi/app/constants/sc_emoji_datas.dart'; import 'package:yumi/app/constants/sc_global_config.dart'; import 'package:yumi/app/constants/sc_room_msg_type.dart'; import 'package:yumi/app/constants/sc_screen.dart'; import 'package:yumi/app_localizations.dart'; import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/services/audio/rtm_manager.dart'; import 'package:yumi/shared/business_logic/usecases/sc_case.dart'; import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; import 'package:yumi/shared/tools/sc_keybord_util.dart'; import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart'; ///聊天输入框 class RoomMsgInput extends StatefulWidget { final String? atTextContent; const RoomMsgInput({super.key, this.atTextContent}); @override State createState() => _RoomMsgInputState(); } class _RoomMsgInputState extends State { bool showSend = false; bool showEmoji = false; final FocusNode msgNode = FocusNode(); final TextEditingController controller = TextEditingController(); BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy; Color get _panelBackgroundColor => Colors.white24; @override void initState() { super.initState(); if (widget.atTextContent != null) { controller.value = TextEditingValue(text: widget.atTextContent ?? ""); showSend = controller.text.trim().isNotEmpty; } } @override void dispose() { controller.dispose(); msgNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final keyboardHeight = MediaQuery.of(context).viewInsets.bottom; return Scaffold( backgroundColor: Colors.transparent, resizeToAvoidBottomInset: false, body: Column( children: [ Expanded( child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { Navigator.pop(context); }, ), ), AnimatedPadding( duration: const Duration(milliseconds: 220), curve: Curves.easeOut, padding: EdgeInsets.only(bottom: keyboardHeight), child: _buildBottomPanel(context), ), ], ), ); } Widget _buildBottomPanel(BuildContext context) { return Container( color: Colors.black, child: SafeArea( top: false, child: Padding( padding: EdgeInsets.fromLTRB(25.w, 12.w, 25.w, 12.w), child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ GestureDetector( behavior: HitTestBehavior.opaque, onTap: _toggleEmojiPanel, child: SizedBox( width: 24.w, height: 24.w, child: Image.asset( showEmoji ? _strategy.getSCMessageChatPageChatKeyboardIcon() : _strategy.getSCMessageChatPageEmojiIcon(), color: Colors.white, fit: BoxFit.contain, ), ), ), SizedBox(width: 15.w), Expanded(child: _buildInputBar(context)), ], ), if (showEmoji) SizedBox(height: 12.w), if (showEmoji) _buildEmojiPanel(), ], ), ), ), ); } Widget _buildInputBar(BuildContext context) { return Container( decoration: BoxDecoration( color: _panelBackgroundColor, borderRadius: BorderRadius.circular(height(5)), ), child: Row( children: [ Expanded( child: ExtendedTextField( controller: controller, textDirection: widget.atTextContent != null ? TextDirection.ltr : null, specialTextSpanBuilder: AtTextSpanBuilder(), focusNode: msgNode, autofocus: true, textInputAction: TextInputAction.send, onTap: () { if (showEmoji) { setState(() { showEmoji = false; }); } }, onSubmitted: (_) { _sendMessage(); }, onChanged: (s) { setState(() { showSend = controller.text.trim().isNotEmpty; }); }, decoration: InputDecoration( hintText: SCAppLocalizations.of(context)!.pleaseChatFfriendly, fillColor: Colors.transparent, hintStyle: TextStyle(color: Colors.white60, fontSize: sp(14)), contentPadding: EdgeInsets.symmetric(horizontal: 15.w), counterText: '', isDense: true, filled: false, focusColor: Colors.transparent, hoverColor: Colors.transparent, border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, disabledBorder: InputBorder.none, errorBorder: InputBorder.none, focusedErrorBorder: InputBorder.none, ), style: TextStyle( fontSize: ScreenUtil().setSp(14), color: Colors.white, ), ), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: _sendMessage, child: Opacity( opacity: showSend ? 1 : 0.5, child: Container( padding: EdgeInsets.all(4.w), width: 30.w, height: 30.w, child: Image.asset( "sc_images/room/sc_icon_room_message_send.png", ), ), ), ), SizedBox(width: 3.w), ], ), ); } Widget _buildEmojiPanel() { return Container( height: 150.w, decoration: BoxDecoration( color: _panelBackgroundColor, borderRadius: BorderRadius.circular(height(5)), ), child: GridView.builder( padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 10.w), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 8, crossAxisSpacing: 8.w, mainAxisSpacing: 8.w, ), itemCount: SCEmojiDatas.smileys.length, itemBuilder: (context, index) { final emoji = SCEmojiDatas.smileys[index]; return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { _insertEmoji(emoji); }, child: Center( child: ExtendedText(emoji, style: TextStyle(fontSize: sp(15))), ), ); }, ), ); } void _toggleEmojiPanel() { if (showEmoji) { setState(() { showEmoji = false; }); FocusScope.of(context).requestFocus(msgNode); return; } SCKeybordUtil.hide(context); setState(() { showEmoji = true; }); } void _insertEmoji(String emoji) { final value = controller.value; final selection = value.selection; final fallbackOffset = value.text.length; final start = selection.isValid ? selection.start : fallbackOffset; final end = selection.isValid ? selection.end : fallbackOffset; final safeStart = start < 0 ? fallbackOffset : start; final safeEnd = end < 0 ? fallbackOffset : end; final nextText = value.text.replaceRange(safeStart, safeEnd, emoji); controller.value = value.copyWith( text: nextText, selection: TextSelection.collapsed(offset: safeStart + emoji.length), composing: TextRange.empty, ); setState(() { showSend = controller.text.trim().isNotEmpty; }); } void _sendMessage() { if (controller.text.trim().isEmpty) { return; } final rtcProvider = Provider.of(context, listen: false); final currenRoom = rtcProvider.currenRoom; if (currenRoom == null) { return; } Provider.of(context, listen: false).dispatchMessage( Msg( groupId: currenRoom.roomProfile?.roomProfile?.roomAccount ?? "", role: rtcProvider.currenRoom?.entrants?.roles ?? "", msg: controller.text, type: SCRoomMsgType.text, user: AccountStorage().getCurrentUser()?.userProfile, ), ); Navigator.pop(context); } } class PopRoute extends PopupRoute { final Duration _duration = Duration(milliseconds: 350); Widget child; PopRoute({required this.child}); @override Color? get barrierColor => null; @override bool get barrierDismissible => true; @override String? get barrierLabel => null; @override Widget buildPage( BuildContext context, Animation animation, Animation secondaryAnimation, ) { return child; } @override Duration get transitionDuration => _duration; }