Browse Source

feat(video-call): 添加视频通话小窗功能

- 在OverlayController中添加视频通话overlay显示控制
- 实现VideoCallOverlayWidget组件用于视频通话小窗显示
- 在视频通话页面添加最小化到小窗功能
- 添加PopScope防止手势返回中断通话
- 优化IMManager中的空值检查和日志输出
- 改进直播房间邀请消息的头像URL处理
- 更新房间项目的封面图片获取逻辑和占位符样式
master
Jolie 3 months ago
parent
commit
adf838e773
7 changed files with 131 additions and 73 deletions
  1. 37
      lib/controller/overlay_controller.dart
  2. 14
      lib/im/im_manager.dart
  3. 31
      lib/main.dart
  4. 58
      lib/pages/message/video_call_page.dart
  5. 10
      lib/widget/live/live_room_guest_list_dialog.dart
  6. 51
      lib/widget/message/room_item.dart
  7. 3
      pubspec.yaml

37
lib/controller/overlay_controller.dart

@ -2,22 +2,49 @@ import 'package:get/get.dart';
/// Overlay
class OverlayController extends GetxController {
/// overlay
/// overlay
final showOverlay = false.obs;
/// overlay
/// overlay
final showVideoCallOverlay = false.obs;
///
String? videoCallTargetUserId;
String? videoCallTargetUserName;
String? videoCallTargetAvatarUrl;
/// overlay
void show() {
showOverlay.value = true;
}
/// overlay
/// overlay
void hide() {
showOverlay.value = false;
}
/// overlay
/// overlay
void toggle() {
showOverlay.value = !showOverlay.value;
}
}
/// overlay
void showVideoCall({
required String targetUserId,
String? targetUserName,
String? targetAvatarUrl,
}) {
videoCallTargetUserId = targetUserId;
videoCallTargetUserName = targetUserName;
videoCallTargetAvatarUrl = targetAvatarUrl;
showVideoCallOverlay.value = true;
}
/// overlay
void hideVideoCall() {
showVideoCallOverlay.value = false;
videoCallTargetUserId = null;
videoCallTargetUserName = null;
videoCallTargetAvatarUrl = null;
}
}

14
lib/im/im_manager.dart

@ -243,9 +243,7 @@ class IMManager {
// revenueInfo attributes UI显示
if (revenueInfo != null && revenueInfo.isNotEmpty) {
if (message.attributes == null) {
message.attributes = {};
}
message.attributes ??= {};
// revenueInfo coin_value 便UI组件可以直接使用
message.attributes!['coin_value'] = revenueInfo;
@ -1478,10 +1476,6 @@ class IMManager {
final notification = _notificationQueue.removeAt(0);
_isShowingNotification = true;
if (Get.isLogEnable) {
Get.log('✅ [IMManager] 显示消息通知弹框: fromId=${notification.fromId}, nickName=${notification.nickName}, 剩余队列长度=${_notificationQueue.length}');
}
//
SmartDialog.show(
builder: (context) {
@ -1531,10 +1525,6 @@ class IMManager {
_isShowingNotification = false;
if (Get.isLogEnable) {
Get.log('✅ [IMManager] 消息通知弹框已关闭,剩余队列长度=${_notificationQueue.length}');
}
//
Future.delayed(Duration(milliseconds: 300), () {
if (!_isShowingNotification) {
@ -1570,7 +1560,7 @@ class IMManager {
}
// GIFT消息
if (content != null && content.startsWith('[GIFT:]')) {
if (content.startsWith('[GIFT:]')) {
return '[礼物]';
}

31
lib/main.dart

@ -10,6 +10,7 @@ import 'package:dating_touchme_app/pages/mine/login_page.dart';
import 'package:dating_touchme_app/pages/setting/teenager_mode_page.dart';
import 'package:dating_touchme_app/rtc/rtc_manager.dart';
import 'package:dating_touchme_app/widget/live/draggable_overlay_widget.dart';
import 'package:dating_touchme_app/widget/message/video_call_overlay_widget.dart';
import 'package:dating_touchme_app/widget/user_agreement_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -125,15 +126,27 @@ void main() async {
try {
if (Get.isRegistered<OverlayController>()) {
final overlayController = Get.find<OverlayController>();
return overlayController.showOverlay.value
? DraggableOverlayWidget(
size: 60,
backgroundColor: const Color.fromRGBO(0, 0, 0, 0.6),
onClose: () {
overlayController.hide();
},
)
: const SizedBox.shrink();
//
if (overlayController.showVideoCallOverlay.value) {
return VideoCallOverlayWidget(
targetUserId: overlayController.videoCallTargetUserId ?? '',
targetUserName: overlayController.videoCallTargetUserName,
targetAvatarUrl: overlayController.videoCallTargetAvatarUrl,
onClose: () {
overlayController.hideVideoCall();
},
);
}
//
if (overlayController.showOverlay.value) {
return DraggableOverlayWidget(
size: 60,
backgroundColor: const Color.fromRGBO(0, 0, 0, 0.6),
onClose: () {
overlayController.hide();
},
);
}
}
} catch (e) {
print('获取OverlayController失败: $e');

58
lib/pages/message/video_call_page.dart

@ -8,6 +8,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../../controller/message/call_manager.dart';
import '../../controller/message/conversation_controller.dart';
import '../../controller/overlay_controller.dart';
import '../../model/home/marriage_data.dart';
///
@ -237,25 +238,32 @@ class _VideoCallPageState extends State<VideoCallPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Stack(
children: [
// /
_buildBackground(),
//
_buildMinimizeButton(),
//
_buildUserInfo(),
//
_buildCallDuration(),
//
_buildControlButtons(),
],
return PopScope(
canPop: false, //
onPopInvoked: (didPop) {
//
//
},
child: Scaffold(
backgroundColor: Colors.black,
body: Stack(
children: [
// /
_buildBackground(),
//
_buildMinimizeButton(),
//
_buildUserInfo(),
//
_buildCallDuration(),
//
_buildControlButtons(),
],
),
),
);
}
@ -274,8 +282,16 @@ class _VideoCallPageState extends State<VideoCallPage> {
///
void _minimizeCall() {
// TODO:
//
//
if (Get.isRegistered<OverlayController>()) {
final overlayController = Get.find<OverlayController>();
overlayController.showVideoCall(
targetUserId: widget.targetUserId,
targetUserName: _targetUserName,
targetAvatarUrl: _targetAvatarUrl,
);
}
//
Get.back();
}

10
lib/widget/live/live_room_guest_list_dialog.dart

@ -627,15 +627,21 @@ class _LiveRoomGuestListDialogState extends State<LiveRoomGuestListDialog> {
final channelDetail = roomController.rtcChannelDetail.value;
final anchorName = channelDetail?.anchorInfo?.nickName ?? '主持人';
final anchorAvatar = channelDetail?.anchorInfo?.profilePhoto ?? '';
// URL不为空且格式正确
final cleanedAvatar = anchorAvatar.trim().replaceAll('`', '');
final cleanedCover = cleanedAvatar; // 使
//
final messageData = <String, String>{
'type': 'live_room_invite',
'channelId': channelId,
'anchorAvatar': anchorAvatar,
'anchorAvatar': cleanedAvatar,
'anchorName': anchorName,
'coverImage': anchorAvatar, // 使
'coverImage': cleanedCover, // 使
};
print('📤 [LiveRoomGuestListDialog] 发送房间邀请消息: anchorAvatar=$cleanedAvatar, coverImage=$cleanedCover');
//
final result = await IMManager.instance.sendCustomMessage(

51
lib/widget/message/room_item.dart

@ -70,8 +70,10 @@ class RoomItem extends StatelessWidget {
String _getCoverImage() {
final roomInfo = _parseRoomInfo();
// 使使
final coverImage = roomInfo?['anchorAvatar'] ?? '';
return coverImage.trim().replaceAll('`', '');
final coverImage = roomInfo?['coverImage'] ?? roomInfo?['anchorAvatar'] ?? '';
final cleanedUrl = coverImage.trim().replaceAll('`', '');
print('📸 [RoomItem] 封面图片URL: $cleanedUrl');
return cleanedUrl;
}
///
@ -124,7 +126,7 @@ class RoomItem extends StatelessWidget {
final anchorName = _getAnchorName();
final anchorAvatar = _getAnchorAvatar();
final coverImage = _getAnchorAvatar();
final coverImage = _getCoverImage();
return Column(
children: [
@ -209,6 +211,8 @@ class RoomItem extends StatelessWidget {
},
)
: Container(
width: 150.w,
height: 150.w,
color: Colors.grey[200],
child: Icon(
Icons.live_tv,
@ -269,27 +273,30 @@ class RoomItem extends StatelessWidget {
width: 24.w,
height: 24.w,
fit: BoxFit.cover,
placeholder: (context, url) =>
Container(
color: Colors.grey[300],
child: Center(
child: SizedBox(
width: 12.w,
height: 12.w,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.grey[600],
),
),
placeholder: (context, url) => Container(
width: 24.w,
height: 24.w,
color: Colors.grey[300],
child: Center(
child: SizedBox(
width: 12.w,
height: 12.w,
child: CircularProgressIndicator(
strokeWidth: 1.5,
color: Colors.grey[600],
),
),
errorWidget: (context, url, error) =>
Image.asset(
Assets.imagesUserAvatar,
width: 24.w,
height: 24.w,
fit: BoxFit.cover,
),
),
),
errorWidget: (context, url, error) {
print('❌ [RoomItem] 头像加载失败: $url, error: $error');
return Image.asset(
Assets.imagesUserAvatar,
width: 24.w,
height: 24.w,
fit: BoxFit.cover,
);
},
)
: Image.asset(
Assets.imagesUserAvatar,

3
pubspec.yaml

@ -108,8 +108,7 @@ flutter:
# To add assets to your application, add an assets section, like this:
assets:
- assets/images/
- assets/images/emoji/
- build/app/outputs/flutter-apk/
# - images/a_dot_ham.jpeg
# An images asset can refer to one or more resolution-specific "variants", see

Loading…
Cancel
Save