Browse Source

feat(live): 实现直播房间输入对话框功能

- 添加输入对话框组件,支持消息输入和发送
- 在LiveRoomActionBar中添加输入点击事件回调
- 实现输入对话框的显示/隐藏逻辑和焦点管理
- 添加键盘状态监听,自动调整UI布局
- 在IMManager中添加在线状态检查过滤消息
- 集成flutter_local_notifications和app_badge_plus依赖
- 优化消息发送流程和界面交互体验
master
Jolie 3 months ago
parent
commit
414ec255aa
4 changed files with 247 additions and 50 deletions
  1. 2
      lib/im/im_manager.dart
  2. 244
      lib/pages/discover/live_room_page.dart
  3. 49
      lib/widget/live/live_room_action_bar.dart
  4. 2
      pubspec.yaml

2
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);

244
lib/pages/discover/live_room_page.dart

@ -29,6 +29,10 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
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<int?>(null);
@ -52,6 +56,8 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
_overlayController.hide();
}
});
//
_inputDialogFocusNode.addListener(_onInputDialogFocusChanged);
//
WakelockPlus.enable();
//
@ -74,11 +80,32 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
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<RoomController>()) {
final roomController = Get.find<RoomController>();
@ -87,6 +114,54 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
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<void> _sendMessage() async {
final content = _messageController.text.trim();
@ -140,8 +215,22 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
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<LiveRoomPage> {
Get.back();
},
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Stack(
children: [
Container(
@ -206,7 +296,8 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
SmartDialog.dismiss();
// 退RTM消息
if (Get.isRegistered<RoomController>()) {
final roomController = Get.find<RoomController>();
final roomController =
Get.find<RoomController>();
roomController.chatMessages.clear();
}
_overlayController.show();
@ -225,33 +316,148 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
),
),
),
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,
),
),
),
),
],
),
),
),
);
}
}

49
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 {
],
);
}
}
}

2
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:

Loading…
Cancel
Save