Browse Source
Merge branch 'master' of http://git.qniao.cn/dating-agency/dating_touchme_app
Merge branch 'master' of http://git.qniao.cn/dating-agency/dating_touchme_app
* 'master' of http://git.qniao.cn/dating-agency/dating_touchme_app: feat(live): 实现实名认证匹配功能并优化直播间动效播放 添加用户信息页面跳转到聊天页面 no message feat(discover): 实现SVGA动画播放管理器和播放组件 feat(live): 实现礼物弹窗用户选择与礼物展示功能 增加红娘等级,增加昵称最大宽度 修改信息上传头像增加示例,增加跳转支付宝逻辑 修改信息上传头像增加示例,增加跳转支付宝逻辑 # Conflicts: # lib/controller/discover/room_controller.dartios
15 changed files with 602 additions and 197 deletions
Split View
Diff Options
-
9ios/Podfile
-
103lib/controller/discover/room_controller.dart
-
86lib/controller/discover/svga_player_manager.dart
-
1lib/controller/mine/mine_controller.dart
-
1lib/controller/mine/rose_controller.dart
-
28lib/model/home/marriage_data.dart
-
2lib/network/api_urls.dart
-
4lib/network/rtc_api.dart
-
31lib/network/rtc_api.g.dart
-
4lib/pages/discover/live_room_page.dart
-
14lib/pages/home/user_information_page.dart
-
19lib/pages/mine/mine_page.dart
-
264lib/widget/live/live_gift_popup.dart
-
6lib/widget/live/live_room_anchor_showcase.dart
-
227lib/widget/live/svga_player_widget.dart
@ -0,0 +1,86 @@ |
|||
import 'dart:collection'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter_svga/flutter_svga.dart'; |
|||
import 'package:get/get.dart'; |
|||
|
|||
/// SVGA 动画项 |
|||
class SvgaAnimationItem { |
|||
final String svgaFile; |
|||
final String? targetUserId; // 接收礼物的用户ID |
|||
final String? senderUserId; // 发送礼物的用户ID |
|||
final String? giftProductId; // 礼物产品ID |
|||
|
|||
SvgaAnimationItem({ |
|||
required this.svgaFile, |
|||
this.targetUserId, |
|||
this.senderUserId, |
|||
this.giftProductId, |
|||
}); |
|||
} |
|||
|
|||
/// SVGA 动画播放队列管理器 |
|||
/// 注意:由于 SVGAAnimationController 需要 vsync,实际播放需要在 Widget 中完成 |
|||
/// 这个管理器只负责管理队列,实际的播放需要通过回调通知外部 Widget |
|||
class SvgaPlayerManager extends GetxController { |
|||
static SvgaPlayerManager? _instance; |
|||
static SvgaPlayerManager get instance { |
|||
_instance ??= Get.put(SvgaPlayerManager()); |
|||
return _instance!; |
|||
} |
|||
|
|||
final Queue<SvgaAnimationItem> _animationQueue = Queue<SvgaAnimationItem>(); |
|||
final Rx<SvgaAnimationItem?> currentItem = Rx<SvgaAnimationItem?>(null); |
|||
final RxBool isPlaying = false.obs; |
|||
|
|||
/// 添加动画到队列 |
|||
void addToQueue(SvgaAnimationItem item) { |
|||
_animationQueue.add(item); |
|||
_playNext(); |
|||
} |
|||
|
|||
/// 播放下一个动画 |
|||
void _playNext() { |
|||
if (isPlaying.value || _animationQueue.isEmpty) { |
|||
return; |
|||
} |
|||
|
|||
final item = _animationQueue.removeFirst(); |
|||
currentItem.value = item; |
|||
isPlaying.value = true; |
|||
print('✅ SVGA 动画已添加到播放队列: ${item.svgaFile}'); |
|||
} |
|||
|
|||
/// 标记当前动画播放完成 |
|||
void onAnimationFinished() { |
|||
print('✅ SVGA 动画播放完成'); |
|||
isPlaying.value = false; |
|||
currentItem.value = null; |
|||
// 播放下一个 |
|||
_playNext(); |
|||
} |
|||
|
|||
/// 标记当前动画播放失败 |
|||
void onAnimationError(String error) { |
|||
print('❌ SVGA 动画播放失败: $error'); |
|||
isPlaying.value = false; |
|||
currentItem.value = null; |
|||
// 继续播放下一个 |
|||
_playNext(); |
|||
} |
|||
|
|||
/// 停止当前播放 |
|||
void stop() { |
|||
isPlaying.value = false; |
|||
currentItem.value = null; |
|||
} |
|||
|
|||
/// 清空队列 |
|||
void clearQueue() { |
|||
stop(); |
|||
_animationQueue.clear(); |
|||
} |
|||
|
|||
/// 获取队列长度 |
|||
int get queueLength => _animationQueue.length; |
|||
} |
|||
|
|||
@ -0,0 +1,227 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter_svga/flutter_svga.dart'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:dating_touchme_app/controller/discover/svga_player_manager.dart'; |
|||
|
|||
/// SVGA 动画播放 Widget |
|||
/// 监听 SvgaPlayerManager 的队列,自动播放 SVGA 动画 |
|||
class SvgaPlayerWidget extends StatefulWidget { |
|||
const SvgaPlayerWidget({super.key}); |
|||
|
|||
@override |
|||
State<SvgaPlayerWidget> createState() => _SvgaPlayerWidgetState(); |
|||
} |
|||
|
|||
class _SvgaPlayerWidgetState extends State<SvgaPlayerWidget> |
|||
with TickerProviderStateMixin { |
|||
final SvgaPlayerManager _manager = SvgaPlayerManager.instance; |
|||
SVGAAnimationController? _controller; |
|||
SvgaAnimationItem? _currentItem; |
|||
|
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
// 监听队列变化 |
|||
ever(_manager.currentItem, (item) { |
|||
print( |
|||
'📢 currentItem 变化: ${item?.svgaFile ?? "null"}, 当前 _currentItem: ${_currentItem?.svgaFile ?? "null"}', |
|||
); |
|||
if (item != null) { |
|||
// 如果当前没有播放,或者新的 item 与当前不同,则播放 |
|||
if (_currentItem == null || item.svgaFile != _currentItem!.svgaFile) { |
|||
print('🎯 准备播放新动画: ${item.svgaFile}'); |
|||
_playAnimation(item); |
|||
} else { |
|||
print('⚠️ 相同的动画,跳过播放'); |
|||
} |
|||
} else { |
|||
// currentItem 变为 null,但不立即清理,等待播放完成回调 |
|||
print('📢 currentItem 变为 null,等待播放完成回调'); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
void dispose() { |
|||
_controller?.dispose(); |
|||
super.dispose(); |
|||
} |
|||
|
|||
/// 播放动画 |
|||
Future<void> _playAnimation(SvgaAnimationItem item) async { |
|||
print( |
|||
'🎬 开始播放动画: ${item.svgaFile}, 当前状态: _controller=${_controller != null}, _currentItem=${_currentItem?.svgaFile ?? "null"}', |
|||
); |
|||
|
|||
// 如果正在播放,先停止并清理 |
|||
if (_controller != null) { |
|||
print('🛑 停止当前播放'); |
|||
_controller!.stop(); |
|||
_controller!.dispose(); |
|||
_controller = null; |
|||
} |
|||
|
|||
// 设置当前项并创建新的 controller |
|||
_currentItem = item; |
|||
_controller = SVGAAnimationController(vsync: this); |
|||
print('✅ 创建新的 controller,准备加载动画'); |
|||
|
|||
try { |
|||
// 判断是网络 URL 还是本地资源 |
|||
if (item.svgaFile.startsWith('http://') || |
|||
item.svgaFile.startsWith('https://')) { |
|||
// 网络 URL |
|||
SVGAParser.shared |
|||
.decodeFromURL(item.svgaFile) |
|||
.then((video) { |
|||
if (!mounted) return; |
|||
|
|||
// 检查是否还是当前要播放的动画 |
|||
if (_currentItem != item || _controller == null) { |
|||
print('⚠️ 动画已变更,取消播放: ${item.svgaFile}'); |
|||
return; |
|||
} |
|||
|
|||
_controller!.videoItem = video; |
|||
// 播放动画(repeat 会循环播放,我们需要监听完成) |
|||
_controller!.repeat(); |
|||
|
|||
// 获取动画时长,如果为 null 则使用默认值 3 秒 |
|||
final duration = _controller!.duration; |
|||
final playDuration = duration != null && duration > Duration.zero |
|||
? duration |
|||
: const Duration(seconds: 3); |
|||
|
|||
print( |
|||
'✅ SVGA 动画加载成功(网络): ${item.svgaFile}, 播放时长: ${playDuration.inMilliseconds}ms', |
|||
); |
|||
|
|||
// 在动画时长后停止并通知完成 |
|||
Future.delayed(playDuration, () { |
|||
if (!mounted) { |
|||
print('⚠️ Widget 已卸载,取消完成回调'); |
|||
return; |
|||
} |
|||
|
|||
// 再次检查是否还是当前动画 |
|||
if (_currentItem == item && _controller != null) { |
|||
print('✅ SVGA 动画播放完成(网络): ${item.svgaFile}'); |
|||
|
|||
// 先停止动画 |
|||
_controller!.stop(); |
|||
|
|||
// 清理当前项和 controller |
|||
final wasCurrentItem = _currentItem; |
|||
_currentItem = null; |
|||
_controller?.dispose(); |
|||
_controller = null; |
|||
|
|||
// 通知管理器播放完成(这会触发下一个动画) |
|||
if (wasCurrentItem == item) { |
|||
_manager.onAnimationFinished(); |
|||
} |
|||
} else { |
|||
print( |
|||
'⚠️ 动画已变更,跳过完成回调: _currentItem=${_currentItem?.svgaFile ?? "null"}, item=${item.svgaFile}', |
|||
); |
|||
} |
|||
}); |
|||
}) |
|||
.catchError((error) { |
|||
print('❌ SVGA 动画加载失败(网络): $error'); |
|||
_currentItem = null; |
|||
_controller?.dispose(); |
|||
_controller = null; |
|||
_manager.onAnimationError(error.toString()); |
|||
}); |
|||
} else { |
|||
// 本地资源(assets) |
|||
SVGAParser.shared |
|||
.decodeFromAssets(item.svgaFile) |
|||
.then((video) { |
|||
if (!mounted) return; |
|||
|
|||
// 检查是否还是当前要播放的动画 |
|||
if (_currentItem != item || _controller == null) { |
|||
print('⚠️ 动画已变更,取消播放: ${item.svgaFile}'); |
|||
return; |
|||
} |
|||
|
|||
_controller!.videoItem = video; |
|||
// 播放动画(repeat 会循环播放,我们需要监听完成) |
|||
_controller!.repeat(); |
|||
|
|||
// 获取动画时长,如果为 null 则使用默认值 3 秒 |
|||
final duration = _controller!.duration; |
|||
final playDuration = duration != null && duration > Duration.zero |
|||
? duration |
|||
: const Duration(seconds: 3); |
|||
|
|||
print( |
|||
'✅ SVGA 动画加载成功(本地): ${item.svgaFile}, 播放时长: ${playDuration.inMilliseconds}ms', |
|||
); |
|||
|
|||
// 在动画时长后停止并通知完成 |
|||
Future.delayed(playDuration, () { |
|||
if (!mounted) { |
|||
print('⚠️ Widget 已卸载,取消完成回调'); |
|||
return; |
|||
} |
|||
|
|||
// 再次检查是否还是当前动画 |
|||
if (_currentItem == item && _controller != null) { |
|||
print('✅ SVGA 动画播放完成(本地): ${item.svgaFile}'); |
|||
|
|||
// 先停止动画 |
|||
_controller!.stop(); |
|||
|
|||
// 清理当前项和 controller |
|||
final wasCurrentItem = _currentItem; |
|||
_currentItem = null; |
|||
_controller?.dispose(); |
|||
_controller = null; |
|||
|
|||
// 通知管理器播放完成(这会触发下一个动画) |
|||
if (wasCurrentItem == item) { |
|||
_manager.onAnimationFinished(); |
|||
} |
|||
} else { |
|||
print( |
|||
'⚠️ 动画已变更,跳过完成回调: _currentItem=${_currentItem?.svgaFile ?? "null"}, item=${item.svgaFile}', |
|||
); |
|||
} |
|||
}); |
|||
}) |
|||
.catchError((error) { |
|||
print('❌ SVGA 动画加载失败(本地): $error'); |
|||
_currentItem = null; |
|||
_controller?.dispose(); |
|||
_controller = null; |
|||
_manager.onAnimationError(error.toString()); |
|||
}); |
|||
} |
|||
} catch (e) { |
|||
print('❌ SVGA 播放异常: $e'); |
|||
_currentItem = null; |
|||
_controller?.dispose(); |
|||
_controller = null; |
|||
_manager.onAnimationError(e.toString()); |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Obx(() { |
|||
final currentItem = _manager.currentItem.value; |
|||
final isPlaying = _manager.isPlaying.value; |
|||
|
|||
if (!isPlaying || currentItem == null || _controller == null) { |
|||
return const SizedBox.shrink(); |
|||
} |
|||
|
|||
return Positioned.fill( |
|||
child: IgnorePointer(child: SVGAImage(_controller!)), |
|||
); |
|||
}); |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save