From 414ec255aacf1f6001ba70e903f592a0e38591dc Mon Sep 17 00:00:00 2001 From: Jolie <412895109@qq.com> Date: Sat, 27 Dec 2025 16:41:04 +0800 Subject: [PATCH] =?UTF-8?q?feat(live):=20=E5=AE=9E=E7=8E=B0=E7=9B=B4?= =?UTF-8?q?=E6=92=AD=E6=88=BF=E9=97=B4=E8=BE=93=E5=85=A5=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E6=A1=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加输入对话框组件,支持消息输入和发送 - 在LiveRoomActionBar中添加输入点击事件回调 - 实现输入对话框的显示/隐藏逻辑和焦点管理 - 添加键盘状态监听,自动调整UI布局 - 在IMManager中添加在线状态检查过滤消息 - 集成flutter_local_notifications和app_badge_plus依赖 - 优化消息发送流程和界面交互体验 --- lib/im/im_manager.dart | 2 +- lib/pages/discover/live_room_page.dart | 244 ++++++++++++++++++++-- lib/widget/live/live_room_action_bar.dart | 49 ++--- pubspec.yaml | 2 + 4 files changed, 247 insertions(+), 50 deletions(-) diff --git a/lib/im/im_manager.dart b/lib/im/im_manager.dart index 581f43b..a8ff812 100644 --- a/lib/im/im_manager.dart +++ b/lib/im/im_manager.dart @@ -211,7 +211,7 @@ class IMManager { debugPrint('📩 收到消息数: ${messages.length}'); // 从消息扩展字段中解析用户信息并缓存 for (var message in messages) { - if (message.direction == MessageDirection.RECEIVE) { + if (message.direction == MessageDirection.RECEIVE && message.onlineState) { _parseUserInfoFromMessageExt(message); // 检查发送者是否是当前正在聊天的用户,如果不是则显示弹框 _checkAndShowNotificationDialog(message); diff --git a/lib/pages/discover/live_room_page.dart b/lib/pages/discover/live_room_page.dart index 162d275..ff7bc85 100644 --- a/lib/pages/discover/live_room_page.dart +++ b/lib/pages/discover/live_room_page.dart @@ -29,6 +29,10 @@ class _LiveRoomPageState extends State { late final OverlayController _overlayController; String message = ''; final TextEditingController _messageController = TextEditingController(); + final TextEditingController _inputDialogController = TextEditingController(); + final FocusNode _inputDialogFocusNode = FocusNode(); + bool _showInputDialog = false; + bool _isOpeningDialog = false; // 标记是否正在打开对话框 final activeGift = ValueNotifier(null); @@ -52,6 +56,8 @@ class _LiveRoomPageState extends State { _overlayController.hide(); } }); + // 监听输入对话框的焦点变化,当键盘收起时隐藏对话框 + _inputDialogFocusNode.addListener(_onInputDialogFocusChanged); // 启用屏幕常亮 WakelockPlus.enable(); // 如果当前用户是男性,请求连麦卡片信息 @@ -74,11 +80,32 @@ class _LiveRoomPageState extends State { await _roomController.getVirtualAccount(); } + /// 输入对话框焦点变化回调 + void _onInputDialogFocusChanged() { + if (!mounted) return; + // 如果焦点丢失且对话框仍显示,检查键盘状态 + if (!_inputDialogFocusNode.hasFocus && _showInputDialog) { + // 使用 addPostFrameCallback 确保在下一帧检查,此时键盘状态已更新 + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + // 如果键盘已收起,隐藏对话框 + final keyboardHeight = MediaQuery.of(context).viewInsets.bottom; + if (keyboardHeight == 0) { + _hideInputDialog(); + } + }); + } + } + @override void dispose() { + // 移除焦点监听器 + _inputDialogFocusNode.removeListener(_onInputDialogFocusChanged); // 禁用屏幕常亮 WakelockPlus.disable(); _messageController.dispose(); + _inputDialogController.dispose(); + _inputDialogFocusNode.dispose(); // 退出房间时清空RTM消息 if (Get.isRegistered()) { final roomController = Get.find(); @@ -87,6 +114,54 @@ class _LiveRoomPageState extends State { super.dispose(); } + /// 显示输入对话框 + void _openInputDialog() { + _isOpeningDialog = true; + setState(() { + _showInputDialog = true; + }); + // 延迟获取焦点,确保对话框已显示 + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted && _showInputDialog) { + _inputDialogFocusNode.requestFocus(); + // 键盘弹起后,重置标志 + Future.delayed(const Duration(milliseconds: 500), () { + if (mounted) { + _isOpeningDialog = false; + } + }); + } + }); + } + + /// 隐藏输入对话框 + void _hideInputDialog() { + _isOpeningDialog = false; + setState(() { + _showInputDialog = false; + }); + _inputDialogFocusNode.unfocus(); + _inputDialogController.clear(); + } + + /// 发送输入对话框中的消息 + void _sendInputDialogMessage() { + final content = _inputDialogController.text.trim(); + if (content.isEmpty) { + return; + } + + // 更新主 controller + _messageController.text = content; + message = content; + + // 发送消息 + _sendMessage(); + + // 关闭对话框 + _hideInputDialog(); + } + /// 发送消息 Future _sendMessage() async { final content = _messageController.text.trim(); @@ -140,8 +215,22 @@ class _LiveRoomPageState extends State { if (_overlayController.showOverlay.value) { _overlayController.hide(); } + // 检查键盘状态,如果键盘收起但对话框仍显示,且不是正在打开对话框,则隐藏对话框 + if (MediaQuery.of(context).viewInsets.bottom == 0 && + _showInputDialog && + !_isOpeningDialog) { + // 延迟一点时间再检查,避免在键盘弹起过程中误判 + Future.delayed(const Duration(milliseconds: 300), () { + if (mounted && + MediaQuery.of(context).viewInsets.bottom == 0 && + _showInputDialog && + !_isOpeningDialog) { + _hideInputDialog(); + } + }); + } }); - + return PopScope( onPopInvokedWithResult: (bool didPop, Object? result) async { SmartDialog.dismiss(); @@ -154,6 +243,7 @@ class _LiveRoomPageState extends State { Get.back(); }, child: Scaffold( + resizeToAvoidBottomInset: false, body: Stack( children: [ Container( @@ -206,7 +296,8 @@ class _LiveRoomPageState extends State { SmartDialog.dismiss(); // 退出房间时清空RTM消息 if (Get.isRegistered()) { - final roomController = Get.find(); + final roomController = + Get.find(); roomController.chatMessages.clear(); } _overlayController.show(); @@ -225,33 +316,148 @@ class _LiveRoomPageState extends State { ), ), ), - SafeArea( - top: false, - child: Padding( - padding: EdgeInsets.only( - bottom: 10.w, - left: 0, - right: 0, - ), - child: LiveRoomActionBar( - messageController: _messageController, - onMessageChanged: (value) { - message = value; - }, - onSendTap: _sendMessage, - onGiftTap: _showGiftPopup, - onChargeTap: _showRechargePopup, + // 根据键盘状态显示/隐藏 LiveRoomActionBar + if (MediaQuery.of(context).viewInsets.bottom == 0) + SafeArea( + top: false, + child: Padding( + padding: EdgeInsets.only( + bottom: 10.w, + left: 0, + right: 0, + ), + child: LiveRoomActionBar( + messageController: _messageController, + onMessageChanged: (value) { + message = value; + }, + onSendTap: _sendMessage, + onGiftTap: _showGiftPopup, + onChargeTap: _showRechargePopup, + onInputTap: _openInputDialog, + ), ), ), - ), ], ), ), // SVGA 动画播放组件 const SvgaPlayerWidget(), + // 输入对话框 + if (_showInputDialog) + AnimatedPositioned( + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + left: 0, + right: 0, + bottom: MediaQuery.of(context).viewInsets.bottom, + child: _InputDialogWidget( + controller: _inputDialogController, + focusNode: _inputDialogFocusNode, + onSend: _sendInputDialogMessage, + onClose: _hideInputDialog, + ), + ), ], ), ), ); } } + +class _InputDialogWidget extends StatelessWidget { + const _InputDialogWidget({ + required this.controller, + required this.focusNode, + required this.onSend, + required this.onClose, + }); + + final TextEditingController controller; + final FocusNode focusNode; + final VoidCallback onSend; + final VoidCallback onClose; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20.w), + topRight: Radius.circular(20.w), + ), + ), + child: SafeArea( + top: false, + child: Padding( + padding: EdgeInsets.all(16.w), + child: Row( + children: [ + Expanded( + child: Container( + height: 38.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(38.w)), + color: const Color.fromRGBO(245, 245, 245, 1), + ), + alignment: Alignment.centerLeft, + child: TextField( + controller: controller, + focusNode: focusNode, + keyboardType: TextInputType.text, + textAlignVertical: TextAlignVertical.center, + style: TextStyle( + fontSize: ScreenUtil().setWidth(14), + height: 1.2, + color: Colors.black, + ), + decoration: InputDecoration( + contentPadding: EdgeInsets.symmetric( + vertical: 0, + horizontal: 15.w, + ), + hintText: "说点什么", + hintStyle: TextStyle( + color: const Color.fromRGBO(144, 144, 144, 1), + height: 1.2, + fontSize: ScreenUtil().setWidth(14), + ), + border: InputBorder.none, + isDense: true, + ), + onSubmitted: (_) { + onSend(); + }, + ), + ), + ), + SizedBox(width: 12.w), + GestureDetector( + onTap: onSend, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 24.w, + vertical: 10.h, + ), + decoration: BoxDecoration( + color: const Color.fromRGBO(117, 98, 249, 1), + borderRadius: BorderRadius.circular(19.w), + ), + child: Text( + "发送", + style: TextStyle( + fontSize: ScreenUtil().setWidth(14), + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/widget/live/live_room_action_bar.dart b/lib/widget/live/live_room_action_bar.dart index 05d01c9..867d375 100644 --- a/lib/widget/live/live_room_action_bar.dart +++ b/lib/widget/live/live_room_action_bar.dart @@ -10,6 +10,7 @@ class LiveRoomActionBar extends StatelessWidget { required this.onSendTap, required this.onGiftTap, required this.onChargeTap, + required this.onInputTap, }); final TextEditingController messageController; @@ -17,6 +18,7 @@ class LiveRoomActionBar extends StatelessWidget { final VoidCallback onSendTap; final VoidCallback onGiftTap; final VoidCallback onChargeTap; + final VoidCallback onInputTap; @override Widget build(BuildContext context) { @@ -43,37 +45,25 @@ class LiveRoomActionBar extends StatelessWidget { ), SizedBox(width: 9.w), Expanded( - child: Container( - height: 38.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(38.w)), - color: const Color.fromRGBO(0, 0, 0, .3), - ), - child: Center( - child: TextField( - controller: messageController, - keyboardType: TextInputType.text, - textAlignVertical: TextAlignVertical.center, - style: TextStyle( - fontSize: ScreenUtil().setWidth(14), - height: 1.2, - color: Colors.white, - ), - decoration: InputDecoration( - contentPadding: EdgeInsets.symmetric( - vertical: 0, - horizontal: 15.w, - ), - hintText: "聊点什么吧~", - hintStyle: TextStyle( - color: const Color.fromRGBO(144, 144, 144, 1), + child: InkWell( + onTap: onInputTap, + child: Container( + height: 38.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(38.w)), + color: const Color.fromRGBO(0, 0, 0, .3), + ), + alignment: Alignment.centerLeft, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 15.w), + child: Text( + "聊点什么吧~", + style: TextStyle( + fontSize: ScreenUtil().setWidth(14), height: 1.2, + color: const Color.fromRGBO(144, 144, 144, 1), ), - border: InputBorder.none, - isDense: true, ), - onChanged: onMessageChanged, - onSubmitted: (_) => onSendTap(), ), ), ), @@ -127,5 +117,4 @@ class LiveRoomActionBar extends StatelessWidget { ], ); } -} - +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 4e65d18..f7b5ece 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -79,6 +79,8 @@ dependencies: im_flutter_sdk: 4.15.2 webview_flutter: ^4.13.0 ota_update: ^7.1.0 + flutter_local_notifications: ^19.5.0 + app_badge_plus: ^1.2.6 dev_dependencies: flutter_test: