Browse Source

体验优化,聊天的svga文件播放;

master
ZHR007 2 months ago
parent
commit
e660f78cf4
6 changed files with 347 additions and 192 deletions
  1. 70
      lib/controller/message/chat_controller.dart
  2. 65
      lib/extension/ex_fuction.dart
  3. 6
      lib/pages/discover/live_room_page.dart
  4. 338
      lib/pages/message/chat_page.dart
  5. 31
      lib/widget/live/live_room_action_bar.dart
  6. 29
      lib/widget/message/gift_item.dart

70
lib/controller/message/chat_controller.dart

@ -1,3 +1,4 @@
import 'package:dating_touchme_app/controller/discover/svga_player_manager.dart';
import 'package:get/get.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:flutter/material.dart';
@ -889,6 +890,18 @@ class ChatController extends GetxController {
return _cursor != null;
}
bool _isGiftMessage(EMMessage message) {
try {
if (message.body.type == MessageType.CUSTOM) {
final customBody = message.body as EMCustomMessageBody;
return customBody.event == 'gift';
}
} catch (e) {
//
}
return false;
}
///
void addReceivedMessage(EMMessage message) {
//
@ -898,13 +911,53 @@ class ChatController extends GetxController {
update();
//
_refreshConversationList();
if(_isGiftMessage(message) && Get.currentRoute == '/ChatPage'){
Get.log('message915: $message');
final giftInfo = _parseGiftInfo(message);
var svgaFile = '', giftProductId = '';
if (giftInfo != null) {
svgaFile = giftInfo['svgaFile']?.toString() ?? '';
giftProductId = giftInfo['giftProductId']?.toString() ?? '';
}
if(svgaFile.isNotEmpty){
final svgaManager = SvgaPlayerManager.instance;
svgaManager.addToQueue(
SvgaAnimationItem(
svgaFile: svgaFile,
giftProductId: giftProductId,
),
);
}
}
if (Get.isLogEnable) {
Get.log('收到新消息并添加到列表: ${message.msgId}');
}
}
}
Map<String, dynamic>? _parseGiftInfo(EMMessage message) {
try {
if (message.body.type == MessageType.CUSTOM) {
final customBody = message.body as EMCustomMessageBody;
if (customBody.event == 'gift' && customBody.params != null) {
// Map<String, String> Map<String, dynamic>
final params = customBody.params!;
return {
'giftProductId': params['giftProductId'] ?? '',
'giftProductTitle': params['giftProductTitle'] ?? '',
'giftMainPic': params['giftMainPic'] ?? '',
'svgaFile': params['svgaFile'] ?? '',
'giftPrice': params['giftPrice'] ?? '0',
'quantity': int.tryParse(params['quantity'] ?? '1') ?? 1,
};
}
}
} catch (e) {
print('解析礼物信息失败: $e');
}
return null;
}
///
Future<bool> recallMessage(EMMessage message) async {
try {
@ -1778,12 +1831,16 @@ class ChatController extends GetxController {
if (code == 'E0002') {
// toast
SmartDialog.showToast('玫瑰不足请充值');
setDialogDismiss(true);
SmartDialog.show(
alignment: Alignment.bottomCenter,
maskColor: Get.context != null
? TDTheme.of(Get.context!).fontGyColor2
: Colors.black.withOpacity(0.5),
builder: (_) => const LiveRechargePopup(),
onDismiss: () {
setDialogDismiss(false);
}
);
} else {
SmartDialog.showToast('发送礼物失败');
@ -1798,6 +1855,7 @@ class ChatController extends GetxController {
'giftMainPic': gift.mainPic,
'giftPrice': gift.unitSellingPrice.toString(),
'quantity': quantity.toString(),
'svgaFile': gift.svgaFile,
};
// 使
@ -1826,6 +1884,16 @@ class ChatController extends GetxController {
//
_refreshRoseBalance();
//
final svgaManager = SvgaPlayerManager.instance;
svgaManager.addToQueue(
SvgaAnimationItem(
svgaFile: gift.svgaFile,
giftProductId: gift.productId,
),
);
print('✅ 礼物已添加到播放队列: ${gift.productTitle}');
if (Get.isLogEnable) {
Get.log('✅ 礼物消息发送成功: ${gift.productTitle}');
}

65
lib/extension/ex_fuction.dart

@ -0,0 +1,65 @@
import 'dart:async';
import 'dart:ui';
extension FunctionExt on Function{
VoidCallback throttle(){
return FunctionProxy(this).throttle;
}
VoidCallback throttleWithTimeout({int? timeout}){
return FunctionProxy(this, timeout: timeout).throttleWithTimeout;
}
VoidCallback debounce({int? timeout}){
return FunctionProxy(this, timeout: timeout).debounce;
}
}
class FunctionProxy {
static final Map<String, bool> _funcThrottle = {};
static final Map<String, Timer> _funcDebounce = {};
final Function? target;
final int timeout;
FunctionProxy(this.target, {int? timeout}) : timeout = timeout ?? 500;
void throttle() async {
String key = hashCode.toString();
bool enable = _funcThrottle[key] ?? true;
if (enable) {
_funcThrottle[key] = false;
try {
await target?.call();
} catch (e) {
rethrow;
} finally {
_funcThrottle.remove(key);
}
}
}
void throttleWithTimeout() {
String key = hashCode.toString();
bool enable = _funcThrottle[key] ?? true;
if (enable) {
_funcThrottle[key] = false;
Timer(Duration(milliseconds: timeout), () {
_funcThrottle.remove(key);
});
target?.call();
}
}
void debounce() {
String key = hashCode.toString();
Timer? timer = _funcDebounce[key];
timer?.cancel();
timer = Timer(Duration(milliseconds: timeout), () {
Timer? t = _funcDebounce.remove(key);
t?.cancel();
target?.call();
});
_funcDebounce[key] = timer;
}
}

6
lib/pages/discover/live_room_page.dart

@ -1,6 +1,7 @@
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:dating_touchme_app/extension/ex_fuction.dart';
import 'package:dating_touchme_app/generated/assets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@ -180,6 +181,9 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
}
void _showGiftPopup() async {
if(_roomController.isDialogShowing.value){
return;
}
//
FocusScope.of(context).unfocus();
//
@ -355,7 +359,7 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
message = value;
},
onSendTap: _sendMessage,
onGiftTap: _showGiftPopup,
onGiftTap: _showGiftPopup.throttle(),
onChargeTap: _showRechargePopup,
onInputTap: _openInputDialog,
),

338
lib/pages/message/chat_page.dart

@ -1,4 +1,5 @@
import 'package:dating_touchme_app/extension/ex_widget.dart';
import 'package:dating_touchme_app/widget/live/svga_player_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
@ -76,13 +77,7 @@ class _ChatPageState extends State<ChatPage> {
if (Get.isRegistered<ChatController>(tag: tag)) {
Get.delete<ChatController>(tag: tag);
}
_controller = Get.put(
ChatController(
userId: widget.userId,
userData: widget.userData,
),
tag: tag,
);
_controller = Get.put(ChatController(userId: widget.userId, userData: widget.userData), tag: tag);
//
_scrollController.addListener(() {
@ -158,11 +153,7 @@ class _ChatPageState extends State<ChatPage> {
maskColor: TDTheme.of(context).fontGyColor2,
onDismiss: () {
_controller.setDialogDismiss(false);
},
maskWidget: GestureDetector(
onTap: () => SmartDialog.dismiss(),
child: Container(color: Colors.transparent),
),
}
);
}
@ -185,6 +176,9 @@ class _ChatPageState extends State<ChatPage> {
//
void _showCallTypeSelectionDialog(ChatController controller, {List<ChatAudioProductModel>? products}) {
//
if(_controller.isDialogShowing.value){
return;
}
FocusScope.of(context).unfocus();
_controller.setDialogDismiss(true);
SmartDialog.show(
@ -227,14 +221,10 @@ class _ChatPageState extends State<ChatPage> {
},
alignment: Alignment.bottomCenter,
animationType: SmartAnimationType.centerFade_otherSlide,
maskColor: TDTheme.of(context).fontGyColor2,
maskColor: TDTheme.of(context).fontGyColor3,
onDismiss: () {
_controller.setDialogDismiss(false);
},
maskWidget: GestureDetector(
onTap: () => SmartDialog.dismiss(),
child: Container(color: Colors.transparent),
),
}
);
}
@ -256,172 +246,178 @@ class _ChatPageState extends State<ChatPage> {
Get.back();
}
},
child: Scaffold(
backgroundColor: Color(0xffF5F5F5),
appBar: AppBar(
title: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
controller.userNickName ?? widget.userData?.nickName ?? '',
style: TextStyle(fontSize: 18.sp),
),
// 使 Obx 线
Obx(() {
final isOnline = controller.isUserOnline.value;
if (isOnline) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(width: 8.w),
Container(
width: 46.w,
height: 26.h,
decoration: BoxDecoration(
color: Color.fromRGBO(234, 255, 219, 0.5),
borderRadius: BorderRadius.circular(178),
),
child: Center(
child: Text(
'在线',
style: TextStyle(
fontSize: 12.sp,
color: Color.fromRGBO(38, 199, 124, 1),
child: Stack(
children: [
Scaffold(
backgroundColor: Color(0xffF5F5F5),
appBar: AppBar(
title: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
controller.userNickName ?? widget.userData?.nickName ?? '',
style: TextStyle(fontSize: 18.sp),
),
// 使 Obx 线
Obx(() {
final isOnline = controller.isUserOnline.value;
if (isOnline) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(width: 8.w),
Container(
width: 46.w,
height: 26.h,
decoration: BoxDecoration(
color: Color.fromRGBO(234, 255, 219, 0.5),
borderRadius: BorderRadius.circular(178),
),
child: Center(
child: Text(
'在线',
style: TextStyle(
fontSize: 12.sp,
color: Color.fromRGBO(38, 199, 124, 1),
),
),
),
),
],
);
}
return SizedBox.shrink();
}),
],
),
centerTitle: false,
actions: [
Container(
padding: EdgeInsets.only(right: 16.w),
child: Image.asset(Assets.imagesMore, width: 16.w),
).onTap(() {
//
Get.to(() => ChatSettingsPage(
userId: widget.userId,
userData: widget.userData ?? _controller.userData,
));
}),
],
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
Get.back();
},
),
),
body: GestureDetector(
onTap: () {
//
FocusManager.instance.primaryFocus?.unfocus();
//
ChatInputBar.closePanels(_chatInputBarKey);
},
behavior: HitTestBehavior.opaque,
child: Column(
children: [
//
Expanded(
child: Container(
color: Color(0xffF5F5F5),
child: ListView.builder(
controller: _scrollController,
reverse: false,
padding: EdgeInsets.only(
top: 16.w,
left: 16.w,
right: 16.w,
bottom: 16.w,
),
),
],
);
}
return SizedBox.shrink();
}),
],
),
centerTitle: false,
actions: [
Container(
padding: EdgeInsets.only(right: 16.w),
child: Image.asset(Assets.imagesMore, width: 16.w),
).onTap(() {
//
Get.to(() => ChatSettingsPage(
userId: widget.userId,
userData: widget.userData ?? _controller.userData,
));
}),
],
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
Get.back();
},
),
),
body: GestureDetector(
onTap: () {
//
FocusManager.instance.primaryFocus?.unfocus();
//
ChatInputBar.closePanels(_chatInputBarKey);
},
behavior: HitTestBehavior.opaque,
child: Column(
children: [
//
Expanded(
child: Container(
color: Color(0xffF5F5F5),
child: ListView.builder(
controller: _scrollController,
reverse: false,
padding: EdgeInsets.only(
top: 16.w,
left: 16.w,
right: 16.w,
bottom: 16.w,
),
itemCount: controller.messages.length + 1, //
// 🚀
cacheExtent: 500, // 500
itemBuilder: (context, index) {
//
if (index == 0) {
return _buildUserInfoCard(controller, widget.userData);
}
final message = controller.messages[index - 1];
Get.log('message: $message');
final isSentByMe =
message.direction == MessageDirection.SEND;
itemCount: controller.messages.length + 1, //
// 🚀
cacheExtent: 500, // 500
itemBuilder: (context, index) {
//
if (index == 0) {
return _buildUserInfoCard(controller, widget.userData);
}
final previousMessage = index > 1
? controller.messages[index - 2]
: null;
final message = controller.messages[index - 1];
Get.log('message347: $message');
final isSentByMe =
message.direction == MessageDirection.SEND;
// 508使
final showRoseError = isSentByMe &&
message.status == MessageStatus.FAIL &&
controller.shouldShowRoseError(message.msgId);
final previousMessage = index > 1
? controller.messages[index - 2]
: null;
// 使
final showSensitiveWordError = isSentByMe &&
message.status == MessageStatus.FAIL &&
controller.shouldShowSensitiveWordError(message.msgId);
// 508使
final showRoseError = isSentByMe &&
message.status == MessageStatus.FAIL &&
controller.shouldShowRoseError(message.msgId);
// 🚀 key
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
MessageItem(
key: ValueKey(message.msgId),
message: message,
isSentByMe: isSentByMe,
previousMessage: previousMessage,
chatController: controller, // controller 使 Get.find
),
// 508
if (showRoseError) _buildRoseErrorHint(context),
// E0001
if (showSensitiveWordError) _buildSensitiveWordErrorHint(context),
],
);
// 使
final showSensitiveWordError = isSentByMe &&
message.status == MessageStatus.FAIL &&
controller.shouldShowSensitiveWordError(message.msgId);
// 🚀 key
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
MessageItem(
key: ValueKey(message.msgId),
message: message,
isSentByMe: isSentByMe,
previousMessage: previousMessage,
chatController: controller, // controller 使 Get.find
),
// 508
if (showRoseError) _buildRoseErrorHint(context),
// E0001
if (showSensitiveWordError) _buildSensitiveWordErrorHint(context),
],
);
},
),
),
),
// 使
ChatInputBar(
key: _chatInputBarKey,
onSendMessage: (message) async {
await controller.sendMessage(message);
},
onImageSelected: (imagePaths) async {
//
for (var imagePath in imagePaths) {
await controller.sendImageMessage(imagePath);
}
},
onVoiceRecorded: (filePath, seconds) async {
//
await controller.sendVoiceMessage(filePath, seconds);
},
onGiftTap: () {
//
_showGiftPopup();
},
//
onVideoCall: () async {
//
final products = await CallController.instance.listChatAudioProduct(widget.userId);
//
_showCallTypeSelectionDialog(controller, products: products);
},
),
),
],
),
// 使
ChatInputBar(
key: _chatInputBarKey,
onSendMessage: (message) async {
await controller.sendMessage(message);
},
onImageSelected: (imagePaths) async {
//
for (var imagePath in imagePaths) {
await controller.sendImageMessage(imagePath);
}
},
onVoiceRecorded: (filePath, seconds) async {
//
await controller.sendVoiceMessage(filePath, seconds);
},
onGiftTap: () {
//
_showGiftPopup();
},
//
onVideoCall: () async {
//
final products = await CallController.instance.listChatAudioProduct(widget.userId);
//
_showCallTypeSelectionDialog(controller, products: products);
},
),
],
),
),
const SvgaPlayerWidget(),
],
),
);
},
);

31
lib/widget/live/live_room_action_bar.dart

@ -1,6 +1,7 @@
import 'package:dating_touchme_app/generated/assets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';
class LiveRoomActionBar extends StatelessWidget {
const LiveRoomActionBar({
@ -25,23 +26,21 @@ class LiveRoomActionBar extends StatelessWidget {
return Row(
children: [
SizedBox(width: 16.w),
InkWell(
onTap: onGiftTap,
child: Container(
width: 38.w,
height: 38.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(38.w)),
color: const Color.fromRGBO(0, 0, 0, .3),
),
child: Center(
child: Image.asset(
Assets.imagesGiftIcon,
width: 28.w,
height: 28.w,
),
),
TDButton(
width: 38.w,
height: 38.w,
iconWidget: Image.asset(Assets.imagesGiftIcon, width: 28.w, height: 28.w),
padding: EdgeInsets.all(5.w),
type: TDButtonType.fill,
shape: TDButtonShape.circle,
theme: TDButtonTheme.primary,
style: TDButtonStyle(
backgroundColor: const Color.fromRGBO(0, 0, 0, .3),
),
activeStyle: TDButtonStyle(
backgroundColor: const Color.fromRGBO(0, 0, 0, .2),
),
onTap: onGiftTap,
),
SizedBox(width: 9.w),
Expanded(

29
lib/widget/message/gift_item.dart

@ -1,4 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:dating_touchme_app/controller/discover/svga_player_manager.dart';
import 'package:dating_touchme_app/extension/ex_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:im_flutter_sdk/im_flutter_sdk.dart';
@ -36,6 +38,7 @@ class GiftItem extends StatelessWidget {
'giftProductId': params['giftProductId'] ?? '',
'giftProductTitle': params['giftProductTitle'] ?? '',
'giftMainPic': params['giftMainPic'] ?? '',
'svgaFile': params['svgaFile'] ?? '',
'giftPrice': params['giftPrice'] ?? '0',
'quantity': int.tryParse(params['quantity'] ?? '1') ?? 1,
};
@ -56,6 +59,14 @@ class GiftItem extends StatelessWidget {
return '礼物';
}
String _getGiftSvga() {
final giftInfo = _parseGiftInfo();
if (giftInfo != null) {
return giftInfo['svgaFile']?.toString() ?? '';
}
return '';
}
///
String _getGiftImage() {
final giftInfo = _parseGiftInfo();
@ -98,8 +109,7 @@ class GiftItem extends StatelessWidget {
Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
child: Row(
mainAxisAlignment:
isSentByMe ? MainAxisAlignment.end : MainAxisAlignment.start,
mainAxisAlignment: isSentByMe ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (!isSentByMe) _buildAvatar(),
@ -219,7 +229,20 @@ class GiftItem extends StatelessWidget {
),
],
),
),
).onTap((){
final svgaFile = _getGiftSvga();
Get.log(giftInfo.toString());
Get.log('${message.status}');
if ((message.status == MessageStatus.SUCCESS || message.status == MessageStatus.PROGRESS) && svgaFile.isNotEmpty) {
final svgaManager = SvgaPlayerManager.instance;
svgaManager.addToQueue(
SvgaAnimationItem(
svgaFile: svgaFile,
giftProductId: giftInfo['giftProductId']?.toString() ?? '',
),
);
}
}),
if (isSentByMe) SizedBox(width: 8.w),
if (isSentByMe) _buildAvatar(),
],

Loading…
Cancel
Save