You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

478 lines
16 KiB

import 'package:dating_touchme_app/controller/discover/room_controller.dart';
import 'package:dating_touchme_app/controller/global.dart';
import 'package:dating_touchme_app/controller/overlay_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:dating_touchme_app/widget/live/live_room_user_header.dart';
import 'package:dating_touchme_app/widget/live/live_room_anchor_showcase.dart';
import 'package:dating_touchme_app/widget/live/live_room_active_speaker.dart';
import 'package:dating_touchme_app/widget/live/live_room_notice_chat_panel.dart';
import 'package:dating_touchme_app/widget/live/live_room_action_bar.dart';
import 'package:dating_touchme_app/widget/live/live_gift_popup.dart';
import 'package:dating_touchme_app/widget/live/live_recharge_popup.dart';
import 'package:dating_touchme_app/widget/live/svga_player_widget.dart';
class LiveRoomPage extends StatefulWidget {
final int id;
const LiveRoomPage({super.key, required this.id});
@override
State<LiveRoomPage> createState() => _LiveRoomPageState();
}
class _LiveRoomPageState extends State<LiveRoomPage> {
late final RoomController _roomController;
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);
final giftNum = ValueNotifier<int>(1);
changeActive(int index) {
activeGift.value = index;
setState(() {});
}
@override
void initState() {
super.initState();
_roomController = Get.isRegistered<RoomController>()
? Get.find<RoomController>()
: Get.put(RoomController());
_overlayController = Get.find<OverlayController>();
// 进入直播间时,确保隐藏小窗口(延迟到 build 完成后执行,避免在 build 过程中触发 setState)
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_overlayController.showOverlay.value) {
_overlayController.hide();
}
});
// 监听输入对话框的焦点变化,当键盘收起时隐藏对话框
_inputDialogFocusNode.addListener(_onInputDialogFocusChanged);
// 启用屏幕常亮
WakelockPlus.enable();
// 如果当前用户是男性,请求连麦卡片信息
_loadLinkMicCard();
// 请求玫瑰数量
_loadRoseCount();
}
/// 加载连麦卡片信息(仅男性用户)
Future<void> _loadLinkMicCard() async {
final userData = GlobalData().userData;
if (userData?.genderCode == 0) {
// 男性用户才请求
await _roomController.getUserPropLinkMicCard();
}
}
/// 加载玫瑰数量
Future<void> _loadRoseCount() async {
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>();
roomController.chatMessages.clear();
}
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();
if (content.isEmpty) {
return;
}
// 发送消息
await _roomController.sendChatMessage(content);
// 清空输入框
_messageController.clear();
message = '';
}
void _showGiftPopup() {
// 隐藏键盘
FocusScope.of(context).unfocus();
SmartDialog.show(
alignment: Alignment.bottomCenter,
maskColor: TDTheme.of(context).fontGyColor2,
builder: (_) => Obx(() {
// 优先使用 API 返回的 giftProducts,如果为空则使用后备列表
final giftProducts = _roomController.giftProducts;
final giftList = giftProducts.toList();
return LiveGiftPopup(
activeGift: activeGift,
giftNum: giftNum,
giftList: giftList,
changeActive: changeActive,
);
}),
);
}
void _showRechargePopup() {
// 隐藏键盘
FocusScope.of(context).unfocus();
SmartDialog.show(
alignment: Alignment.bottomCenter,
maskColor: TDTheme.of(context).fontGyColor2,
builder: (_) => const LiveRechargePopup(),
);
}
@override
Widget build(BuildContext context) {
// 在 build 方法开始时立即隐藏小窗口(使用 addPostFrameCallback 避免在 build 过程中触发 setState)
WidgetsBinding.instance.addPostFrameCallback((_) {
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();
// 退出房间时清空RTM消息
if (Get.isRegistered<RoomController>()) {
final roomController = Get.find<RoomController>();
roomController.chatMessages.clear();
}
_overlayController.show();
Get.back();
},
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Stack(
children: [
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color.fromRGBO(248, 242, 255, 1),
Color.fromRGBO(247, 247, 247, 1),
],
),
),
),
Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Color.fromRGBO(19, 16, 47, 1),
Color.fromRGBO(19, 16, 47, 1),
],
),
),
),
Container(
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 10.w),
Obx(() {
final detail =
_roomController.rtcChannelDetail.value;
final anchorInfo = detail?.anchorInfo;
final userName = anchorInfo!.nickName;
final avatarAsset = anchorInfo.profilePhoto;
const popularityText = '0'; // TODO: 使用真实数据
return LiveRoomUserHeader(
userName: userName,
popularityText: popularityText,
avatarAsset: avatarAsset,
onCloseTap: () {
SmartDialog.dismiss();
// 退出房间时清空RTM消息
if (Get.isRegistered<RoomController>()) {
final roomController =
Get.find<RoomController>();
roomController.chatMessages.clear();
}
_overlayController.show();
Get.back();
},
);
}),
SizedBox(height: 7.w),
LiveRoomAnchorShowcase(),
SizedBox(height: 5.w),
const LiveRoomActiveSpeaker(),
SizedBox(height: 9.w),
const LiveRoomNoticeChatPanel(),
SizedBox(height: 10.w),
],
),
),
),
// 根据键盘状态显示/隐藏 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) ...[
// 遮罩层,点击时隐藏对话框和键盘(放在对话框下方,对话框会在上面拦截点击)
Positioned.fill(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
// 隐藏键盘
FocusScope.of(context).unfocus();
// 隐藏对话框
_hideInputDialog();
},
child: Container(color: Colors.transparent),
),
),
// 输入对话框(放在遮罩层上面,会自动拦截点击事件)
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,
),
),
),
),
],
),
),
),
);
}
}