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