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_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_emoji_asset_image.dart'; import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart'; ///聊天输入框 class RoomMsgInput extends StatefulWidget { final String? atTextContent; final bool initialShowEmoji; const RoomMsgInput({ super.key, this.atTextContent, this.initialShowEmoji = false, }); @override State createState() => _RoomMsgInputState(); } class _RoomMsgInputState extends State { static const List _roomEmojiAssets = [ "sc_images/room/emoji/fluent_emoji_01.webp", "sc_images/room/emoji/fluent_emoji_02.webp", "sc_images/room/emoji/fluent_emoji_03.webp", "sc_images/room/emoji/fluent_emoji_04.webp", "sc_images/room/emoji/fluent_emoji_05.webp", "sc_images/room/emoji/fluent_emoji_06.webp", "sc_images/room/emoji/fluent_emoji_07.webp", "sc_images/room/emoji/fluent_emoji_08.webp", "sc_images/room/emoji/fluent_emoji_09.webp", "sc_images/room/emoji/fluent_emoji_10.webp", "sc_images/room/emoji/monkey_1.gif", "sc_images/room/emoji/monkey_2.gif", "sc_images/room/emoji/monkey_3.gif", "sc_images/room/emoji/monkey_4.gif", "sc_images/room/emoji/monkey_5.gif", "sc_images/room/emoji/monkey_6.gif", "sc_images/room/emoji/monkey_7.gif", "sc_images/room/emoji/monkey_8.gif", "sc_images/room/emoji/monkey_9.gif", "sc_images/room/emoji/monkey_10.gif", ]; bool showSend = false; bool showEmoji = false; bool _emojiAssetsPrecached = false; final FocusNode msgNode = FocusNode(); final TextEditingController controller = TextEditingController(); BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy; Color get _panelBackgroundColor => Colors.white24; @override void initState() { super.initState(); showEmoji = widget.initialShowEmoji; 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 void didChangeDependencies() { super.didChangeDependencies(); if (showEmoji) { _precacheRoomEmojis(); } } @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() : "sc_images/room/sc_icon_room_bottom_emoji.png", color: showEmoji ? Colors.white : null, 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: !widget.initialShowEmoji, 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: _roomEmojiAssets.length, itemBuilder: (context, index) { final emojiAsset = _roomEmojiAssets[index]; return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { _sendRoomEmoji(emojiAsset); }, child: Center( child: RoomEmojiAssetImage( key: ValueKey(emojiAsset), asset: emojiAsset, width: 30.w, height: 30.w, ), ), ); }, ), ); } void _toggleEmojiPanel() { if (showEmoji) { setState(() { showEmoji = false; }); FocusScope.of(context).requestFocus(msgNode); return; } SCKeybordUtil.hide(context); _precacheRoomEmojis(); setState(() { showEmoji = true; }); } void _precacheRoomEmojis() { if (_emojiAssetsPrecached) { return; } _emojiAssetsPrecached = true; for (final asset in _roomEmojiAssets) { precacheImage(AssetImage(asset), context); } } void _sendRoomEmoji(String emojiAsset) { final rtcProvider = Provider.of(context, listen: false); final currenRoom = rtcProvider.currenRoom; final currentUser = AccountStorage().getCurrentUser()?.userProfile; if (currenRoom == null || currentUser == null) { return; } final seatIndex = rtcProvider.userOnMaiInIndex(currentUser.id ?? ""); final msg = Msg( groupId: currenRoom.roomProfile?.roomProfile?.roomAccount ?? "", role: rtcProvider.currenRoom?.entrants?.roles ?? "", msg: emojiAsset, type: SCRoomMsgType.emoticons, user: currentUser, number: seatIndex, ); if (seatIndex > -1) { rtcProvider.starPlayEmoji(msg); } Provider.of( context, listen: false, ).dispatchMessage(msg, addLocal: seatIndex < 0); } 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; }