Compare commits
3 Commits
253a3480f5
...
12c76609c5
Author | SHA1 | Date | |
---|---|---|---|
![]() |
12c76609c5 | ||
![]() |
6da253b403 | ||
![]() |
a0b4cc5495 |
4
Podfile
4
Podfile
@@ -1,5 +1,5 @@
|
||||
# Uncomment the next line to define a global platform for your project
|
||||
platform :ios, '11.0'
|
||||
platform :ios, '13.0'
|
||||
source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git'
|
||||
target 'YuMi' do
|
||||
use_frameworks!
|
||||
@@ -72,7 +72,7 @@ post_install do |installer|
|
||||
installer.generated_projects.each do |project|
|
||||
project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0'
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
|
||||
config.build_settings['DEBUG_INFORMATION_FORMAT'] = 'dwarf-with-dsym'
|
||||
config.build_settings['ENABLE_BITCODE'] = 'NO'
|
||||
xcconfig_path = config.base_configuration_reference.real_path
|
||||
|
@@ -481,6 +481,8 @@
|
||||
4C7F2A672E0BE0AB002F5058 /* FirstRechargeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C7F2A662E0BE0AB002F5058 /* FirstRechargeModel.m */; };
|
||||
4C7F2A6B2E0BE7E7002F5058 /* FirstRechargeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C7F2A6A2E0BE7E7002F5058 /* FirstRechargeManager.m */; };
|
||||
4C815A172CFEB758002A46A6 /* SuperBlockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C815A162CFEB758002A46A6 /* SuperBlockViewController.m */; };
|
||||
4C816A7F2E826C7300EA6A45 /* XPMessageItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C816A7E2E826C7300EA6A45 /* XPMessageItem.m */; };
|
||||
4C816A802E826C7300EA6A45 /* XPMessageDataSourceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C816A7C2E826C7300EA6A45 /* XPMessageDataSourceManager.m */; };
|
||||
4C84A9C22E5ED593002C10FC /* GameBannerGestureManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C84A9C12E5ED593002C10FC /* GameBannerGestureManager.m */; };
|
||||
4C84A9CB2E602B1A002C10FC /* BuglyManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C84A9CA2E602B1A002C10FC /* BuglyManager.m */; };
|
||||
4C85DB812DCDD83E00FD9839 /* CreateEventPresenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C85DB802DCDD83E00FD9839 /* CreateEventPresenter.m */; };
|
||||
@@ -2577,6 +2579,10 @@
|
||||
4C7F2A6A2E0BE7E7002F5058 /* FirstRechargeManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FirstRechargeManager.m; sourceTree = "<group>"; };
|
||||
4C815A152CFEB758002A46A6 /* SuperBlockViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SuperBlockViewController.h; sourceTree = "<group>"; };
|
||||
4C815A162CFEB758002A46A6 /* SuperBlockViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SuperBlockViewController.m; sourceTree = "<group>"; };
|
||||
4C816A7B2E826C7300EA6A45 /* XPMessageDataSourceManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPMessageDataSourceManager.h; sourceTree = "<group>"; };
|
||||
4C816A7C2E826C7300EA6A45 /* XPMessageDataSourceManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPMessageDataSourceManager.m; sourceTree = "<group>"; };
|
||||
4C816A7D2E826C7300EA6A45 /* XPMessageItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPMessageItem.h; sourceTree = "<group>"; };
|
||||
4C816A7E2E826C7300EA6A45 /* XPMessageItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XPMessageItem.m; sourceTree = "<group>"; };
|
||||
4C84A9C02E5ED593002C10FC /* GameBannerGestureManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GameBannerGestureManager.h; sourceTree = "<group>"; };
|
||||
4C84A9C12E5ED593002C10FC /* GameBannerGestureManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GameBannerGestureManager.m; sourceTree = "<group>"; };
|
||||
4C84A9C92E602B1A002C10FC /* BuglyManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BuglyManager.h; sourceTree = "<group>"; };
|
||||
@@ -8725,6 +8731,10 @@
|
||||
E84B0E432727EF2C008818C6 /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C816A7B2E826C7300EA6A45 /* XPMessageDataSourceManager.h */,
|
||||
4C816A7C2E826C7300EA6A45 /* XPMessageDataSourceManager.m */,
|
||||
4C816A7D2E826C7300EA6A45 /* XPMessageItem.h */,
|
||||
4C816A7E2E826C7300EA6A45 /* XPMessageItem.m */,
|
||||
E87A24EF272935920086A794 /* XPMessageRemoteExtModel.h */,
|
||||
E87A24F0272935920086A794 /* XPMessageRemoteExtModel.m */,
|
||||
E8398069290288660084BFC8 /* XPMessageInfoModel.h */,
|
||||
@@ -12652,6 +12662,8 @@
|
||||
9B208A362779B50100F9E54A /* GiftNobleInfoModel.m in Sources */,
|
||||
E80A086227F2AC190027B30C /* RoomPKDetailInfoModel.m in Sources */,
|
||||
4CAFF00A2DD342A400CD81DF /* MessagePublicEventModel.m in Sources */,
|
||||
4C816A7F2E826C7300EA6A45 /* XPMessageItem.m in Sources */,
|
||||
4C816A802E826C7300EA6A45 /* XPMessageDataSourceManager.m in Sources */,
|
||||
E824545126F5CE6E00BE8163 /* XPMineModifPayPwdPresenter.m in Sources */,
|
||||
E8098CB1282E86EF0090B9F0 /* XPMomentsContentView.m in Sources */,
|
||||
E85E3FA728B7A6F000268DC8 /* MessageContentMonentsView.m in Sources */,
|
||||
@@ -13302,7 +13314,7 @@
|
||||
"PB_ENABLE_MALLOC=1",
|
||||
);
|
||||
INFOPLIST_FILE = YuMi/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -13549,7 +13561,7 @@
|
||||
);
|
||||
GCC_PREFIX_HEADER = YuMi/Structure/PrefixHeader.pch;
|
||||
INFOPLIST_FILE = YuMi/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
@@ -190,6 +190,45 @@
|
||||
make.height.mas_equalTo(kGetScaleWidth(20));
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)prepareForReuse {
|
||||
[super prepareForReuse];
|
||||
// 停止并清理动画,避免复用时占用内存
|
||||
[self.pkImageView stopAnimation];
|
||||
self.pkImageView.videoItem = nil;
|
||||
self.pkImageView.hidden = YES;
|
||||
|
||||
// 重置图像资源,打断潜在的异步加载引用
|
||||
[self.avatarView cancelLoadImage];
|
||||
self.avatarView.imageUrl = nil;
|
||||
self.rankImageView.image = nil;
|
||||
[self.tagImageView cancelLoadImage];
|
||||
self.tagImageView.image = nil;
|
||||
self.tagImageView.hidden = YES;
|
||||
[self.levelImageView cancelLoadImage];
|
||||
self.levelImageView.imageUrl = nil;
|
||||
[self.boomImageView cancelLoadImage];
|
||||
self.boomImageView.imageUrl = nil;
|
||||
|
||||
// 重置文本
|
||||
self.nameLabel.attributedText = nil;
|
||||
self.nameLabel.text = @"";
|
||||
self.subLabel.text = @"";
|
||||
self.heatNumLable.text = @"0";
|
||||
|
||||
// 重置成员头像视图:取消下载+清空,隐藏
|
||||
for (NetImageView *iconView in self.iconViews) {
|
||||
[iconView cancelLoadImage];
|
||||
iconView.imageUrl = nil;
|
||||
iconView.hidden = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
// 组件销毁时也保证动画资源释放
|
||||
[self.pkImageView stopAnimation];
|
||||
self.pkImageView.videoItem = nil;
|
||||
}
|
||||
- (void)setRoomInfo:(HomePlayRoomModel *)roomInfo{
|
||||
|
||||
_roomInfo = roomInfo;
|
||||
|
@@ -123,6 +123,28 @@
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)prepareForReuse {
|
||||
[super prepareForReuse];
|
||||
// 重置图像,释放引用,避免复用持有
|
||||
[self.avatarView cancelLoadImage];
|
||||
self.avatarView.imageUrl = nil;
|
||||
self.rankImageView.image = nil;
|
||||
[self.tagImageView cancelLoadImage];
|
||||
self.tagImageView.image = nil;
|
||||
[self.levelImageView cancelLoadImage];
|
||||
self.levelImageView.imageUrl = nil;
|
||||
[self.boomImageView cancelLoadImage];
|
||||
self.boomImageView.imageUrl = nil;
|
||||
[self.flagImage cancelLoadImage];
|
||||
self.flagImage.imageUrl = nil;
|
||||
|
||||
// 重置文本
|
||||
self.nameLabel.attributedText = nil;
|
||||
self.nameLabel.text = @"";
|
||||
self.subLabel.text = @"";
|
||||
self.heatNumLable.text = @"0";
|
||||
}
|
||||
|
||||
- (void)setRoomInfo:(HomePlayRoomModel *)roomInfo {
|
||||
_roomInfo = roomInfo;
|
||||
self.avatarView.imageUrl = roomInfo.avatar;
|
||||
|
@@ -91,6 +91,25 @@ static NSString * const kHomeLayoutTypeKey = @"kHomeLayoutTypeKey";
|
||||
return _bannerView;
|
||||
}
|
||||
|
||||
- (void)prepareForReuse {
|
||||
[super prepareForReuse];
|
||||
// 安全释放:停止轮播并清理数据,避免离屏仍占用内存/CPU
|
||||
self.bannerView.autoScroll = NO;
|
||||
self.bannerView.imageURLStringsGroup = nil;
|
||||
}
|
||||
|
||||
- (void)stopAndRelease {
|
||||
// 离屏时显式停止以释放资源
|
||||
self.bannerView.autoScroll = NO;
|
||||
}
|
||||
|
||||
// 进入可见区域后恢复滚动(由 VC 调用)
|
||||
- (void)resumeIfNeeded {
|
||||
if (self.bannerView.imageURLStringsGroup.count > 0) {
|
||||
self.bannerView.autoScroll = YES;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - SDCycleScrollViewDelegate
|
||||
- (void)cycleScrollView:(SDCycleScrollView *)cycleScrollView didSelectItemAtIndex:(NSInteger)index {
|
||||
HomeBannerInfoModel * bannerInfo = [self.bannerInfoList xpSafeObjectAtIndex:index];
|
||||
@@ -152,8 +171,16 @@ static NSString * const kHomeLayoutTypeKey = @"kHomeLayoutTypeKey";
|
||||
}
|
||||
|
||||
- (void)loadLayoutTypeFromCache {
|
||||
// 从 UserDefaults 读取缓存的布局类型,默认为一列布局(NO)
|
||||
self.isTwoColumnLayout = [[NSUserDefaults standardUserDefaults] boolForKey:kHomeLayoutTypeKey];
|
||||
// 从 UserDefaults 读取缓存的布局类型;若无记录,则默认两列以降低进入内存消耗
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
id storedValue = [defaults objectForKey:kHomeLayoutTypeKey];
|
||||
if (storedValue == nil) {
|
||||
self.isTwoColumnLayout = YES; // 默认两列
|
||||
[defaults setBool:YES forKey:kHomeLayoutTypeKey];
|
||||
[defaults synchronize];
|
||||
} else {
|
||||
self.isTwoColumnLayout = [storedValue boolValue];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)saveLayoutTypeToCache {
|
||||
@@ -387,6 +414,27 @@ static NSString * const kHomeLayoutTypeKey = @"kHomeLayoutTypeKey";
|
||||
return CGSizeMake(kGetScaleWidth(345), kGetScaleWidth(92));
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
// 离屏安全释放:对不同类型 cell 做最小必要释放
|
||||
if ([cell isKindOfClass:[HomePartyBannerCell class]]) {
|
||||
HomePartyBannerCell *bannerCell = (HomePartyBannerCell *)cell;
|
||||
[bannerCell stopAndRelease];
|
||||
} else if ([cell isKindOfClass:[XPNewHomePartyCollectionViewCell class]]) {
|
||||
// 触发其内部 prepareForReuse 等价释放
|
||||
[cell prepareForReuse];
|
||||
} else if ([cell isKindOfClass:[XPPartyRoomItemCollectionViewCell class]]) {
|
||||
[cell prepareForReuse];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
// 进入可见区:如为 banner,恢复自动滚动(若有数据)
|
||||
if ([cell isKindOfClass:[HomePartyBannerCell class]]) {
|
||||
HomePartyBannerCell *bannerCell = (HomePartyBannerCell *)cell;
|
||||
[bannerCell resumeIfNeeded];
|
||||
}
|
||||
}
|
||||
|
||||
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
|
||||
id item = [self.datasource xpSafeObjectAtIndex:indexPath.row];
|
||||
|
||||
|
@@ -33,13 +33,9 @@
|
||||
|
||||
- (void)enqueueBanner:(id)banner {
|
||||
if (!banner) {
|
||||
NSLog(@"⚠️ BannerScheduler: 尝试添加空的 Banner");
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"🔄 BannerScheduler: 添加 Banner 到队列 - 类型: %@, 队列长度: %ld",
|
||||
[banner class], (long)self.bannerQueue.count);
|
||||
|
||||
[self.bannerQueue addObject:banner];
|
||||
|
||||
// 如果当前没有在播放且未暂停,则开始处理
|
||||
@@ -50,31 +46,26 @@
|
||||
|
||||
- (void)processNextBanner {
|
||||
if (self.isPaused) {
|
||||
NSLog(@"⏸️ BannerScheduler: 调度器已暂停,跳过处理");
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔧 新增:检查 delegate 是否有效
|
||||
if (!self.delegate) {
|
||||
NSLog(@"⚠️ BannerScheduler: delegate 已失效,停止处理");
|
||||
[self clearQueue];
|
||||
self.isPlaying = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.isPaused) {
|
||||
NSLog(@"⏸️ BannerScheduler: 调度器已暂停,跳过处理");
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.bannerQueue.count == 0) {
|
||||
NSLog(@"🔄 BannerScheduler: 队列为空,停止播放");
|
||||
self.isPlaying = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.isPlaying) {
|
||||
NSLog(@"🔄 BannerScheduler: 已有 Banner 正在播放,跳过处理");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -84,10 +75,7 @@
|
||||
// 取出队列中的第一个 Banner
|
||||
id nextBanner = [self.bannerQueue firstObject];
|
||||
[self.bannerQueue removeObjectAtIndex:0];
|
||||
|
||||
NSLog(@"🔄 BannerScheduler: 开始播放 Banner - 类型: %@, 剩余队列: %ld",
|
||||
[nextBanner class], (long)self.bannerQueue.count);
|
||||
|
||||
|
||||
self.isPlaying = YES;
|
||||
|
||||
// 通知代理开始播放
|
||||
@@ -102,7 +90,6 @@
|
||||
}
|
||||
|
||||
- (void)clearQueue {
|
||||
NSLog(@"🗑️ BannerScheduler: 清空 Banner 队列 - 原有数量: %ld", (long)self.bannerQueue.count);
|
||||
[self.bannerQueue removeAllObjects];
|
||||
}
|
||||
|
||||
|
@@ -94,29 +94,23 @@
|
||||
- (void)processNextGift {
|
||||
dispatch_async(self.queue, ^{
|
||||
if (self.giftQueue.count == 0) {
|
||||
NSLog(@"[Combo effect] 📭 动画队列为空,停止处理");
|
||||
[self stopGiftQueue];
|
||||
return;
|
||||
}
|
||||
|
||||
GiftReceiveInfoModel *giftInfo = self.giftQueue.firstObject;
|
||||
if (!giftInfo) {
|
||||
NSLog(@"[Combo effect] ⚠️ 队列第一个元素为空,跳过处理");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查并修复连击计数
|
||||
if (giftInfo.comboCount < 1) {
|
||||
NSLog(@"[Combo effect] 🚨 动画处理中检测到连击计数异常 - comboCount: %ld", (long)giftInfo.comboCount);
|
||||
giftInfo.comboCount = 1;
|
||||
NSLog(@"[Combo effect] 🔧 修复动画连击计数为 1");
|
||||
}
|
||||
|
||||
NSLog(@"[Combo effect] 🎬 开始处理动画 - giftId: %ld, combo: %ld", (long)giftInfo.gift.giftId, (long)giftInfo.comboCount);
|
||||
|
||||
// 在同一线程中移除元素
|
||||
[self.giftQueue xpSafeRemoveObjectAtIndex:0];
|
||||
NSLog(@"[Combo effect] 📊 移除后动画队列数量: %ld", (long)self.giftQueue.count);
|
||||
|
||||
@kWeakify(self);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@@ -124,35 +118,27 @@
|
||||
if (self) {
|
||||
[self distributeGiftAnimation:giftInfo];
|
||||
} else {
|
||||
NSLog(@"[Combo effect] ⚠️ self已释放,跳过动画分发");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)distributeGiftAnimation:(GiftReceiveInfoModel *)giftInfo {
|
||||
NSLog(@"[Combo effect] 🎯 开始分发动画 - uid: %@", giftInfo.uid);
|
||||
|
||||
NSArray<NSString *> *targetUids = [self resolveTargetUids:giftInfo];
|
||||
NSLog(@"[Combo effect] 🎯 目标用户数量: %ld", (long)targetUids.count);
|
||||
|
||||
CGPoint startPoint = CGPointZero;
|
||||
BOOL isComboAnimation = [self shouldUseComboAnimationForSender:giftInfo.uid];
|
||||
NSLog(@"[Combo effect] 🎯 是否使用连击动画: %@", isComboAnimation ? @"YES" : @"NO");
|
||||
|
||||
if (isComboAnimation) {
|
||||
startPoint = [self comboAnimationStartPoint];
|
||||
NSLog(@"[Combo effect] 🎯 使用连击动画起点: %@", NSStringFromCGPoint(startPoint));
|
||||
} else {
|
||||
startPoint = [self calculateAnimationPoint:giftInfo.uid isEndPoint:NO];
|
||||
NSLog(@"[Combo effect] 🎯 使用普通动画起点: %@", NSStringFromCGPoint(startPoint));
|
||||
}
|
||||
NSTimeInterval delay = isComboAnimation ? self.comboAnimationDelay : self.standardAnimationDelay;
|
||||
NSLog(@"[Combo effect] 🎯 动画延迟时间: %.2f", delay);
|
||||
|
||||
for (NSString *targetUid in targetUids) {
|
||||
CGPoint endPoint = [self calculateAnimationPoint:targetUid isEndPoint:YES];
|
||||
NSLog(@"[Combo effect] 🎯 为目标用户 %@ 创建动画 - 终点: %@", targetUid, NSStringFromCGPoint(endPoint));
|
||||
[self scheduleAnimationWithDelay:delay
|
||||
giftInfo:giftInfo.gift
|
||||
startPoint:startPoint
|
||||
@@ -245,15 +231,12 @@
|
||||
|
||||
- (void)enqueueGift:(GiftReceiveInfoModel *)giftInfo {
|
||||
if (!giftInfo) {
|
||||
NSLog(@"[Combo effect] ⚠️ 礼物信息为空,跳过动画队列");
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[Combo effect] 🎬 添加礼物到动画队列 - giftId: %ld, combo: %ld", (long)giftInfo.gift.giftId, (long)giftInfo.comboCount);
|
||||
|
||||
dispatch_async(self.queue, ^{
|
||||
[self.giftQueue addObject:giftInfo];
|
||||
NSLog(@"[Combo effect] 📊 动画队列当前数量: %ld", (long)self.giftQueue.count);
|
||||
[self startGiftQueue];
|
||||
});
|
||||
}
|
||||
@@ -268,7 +251,6 @@
|
||||
BOOL isUserInCombo = [self.userComboStates[uid] boolValue];
|
||||
if (isUserInCombo) {
|
||||
BOOL isCurrentUser = [uid isEqualToString:[AccountInfoStorage instance].getUid];
|
||||
NSLog(@"[Combo effect] 🎯 用户 %@ 处于combo状态,是否当前用户: %@", uid, isCurrentUser ? @"YES" : @"NO");
|
||||
return isCurrentUser;
|
||||
}
|
||||
|
||||
@@ -278,12 +260,10 @@
|
||||
NSTimeInterval timeSinceLastGift = [[NSDate date] timeIntervalSinceDate:lastGiftTime];
|
||||
if (timeSinceLastGift <= self.comboTimeWindow) {
|
||||
BOOL isCurrentUser = [uid isEqualToString:[AccountInfoStorage instance].getUid];
|
||||
NSLog(@"[Combo effect] 🎯 用户 %@ 在时间窗口内,是否当前用户: %@", uid, isCurrentUser ? @"YES" : @"NO");
|
||||
return isCurrentUser;
|
||||
}
|
||||
}
|
||||
|
||||
NSLog(@"[Combo effect] 🎯 用户 %@ 不使用combo动画", uid);
|
||||
return NO;
|
||||
}
|
||||
|
||||
@@ -310,16 +290,13 @@
|
||||
|
||||
- (void)setUserComboState:(BOOL)isCombo forUser:(NSString *)uid {
|
||||
if (!uid || uid.length == 0) {
|
||||
NSLog(@"[Combo effect] ⚠️ 用户ID为空,无法设置combo状态");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCombo) {
|
||||
self.userComboStates[uid] = @(YES);
|
||||
NSLog(@"[Combo effect] ✅ 设置用户 %@ 为combo状态", uid);
|
||||
} else {
|
||||
[self.userComboStates removeObjectForKey:uid];
|
||||
NSLog(@"[Combo effect] 🔄 清除用户 %@ 的combo状态", uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +310,6 @@
|
||||
}
|
||||
|
||||
self.userLastGiftTime[uid] = [NSDate date];
|
||||
NSLog(@"[Combo effect] ⏰ 更新用户 %@ 的送礼时间", uid);
|
||||
}
|
||||
|
||||
- (void)cleanupExpiredStates {
|
||||
@@ -349,7 +325,6 @@
|
||||
for (NSString *uid in expiredUsers) {
|
||||
[self.userLastGiftTime removeObjectForKey:uid];
|
||||
[self.userComboStates removeObjectForKey:uid];
|
||||
NSLog(@"[Combo effect] 🧹 清理过期用户状态: %@", uid);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1875,11 +1875,6 @@ BannerSchedulerDelegate
|
||||
// 自定义消息处理入口排查日志:first/second、payload size、线程
|
||||
NSData *payloadJSON = nil;
|
||||
@try { payloadJSON = [NSJSONSerialization dataWithJSONObject:attachment.data ?: @{} options:0 error:nil]; } @catch (__unused NSException *e) {}
|
||||
NSLog(@"[Combo effect][Anim] 🎞 handleNIMCustomMessage | first=%ld second=%ld | payload=%lub | main=%@ | ts=%.3f",
|
||||
(long)attachment.first, (long)attachment.second,
|
||||
(unsigned long)payloadJSON.length,
|
||||
[NSThread isMainThread] ? @"YES" : @"NO",
|
||||
[[NSDate date] timeIntervalSince1970]);
|
||||
#endif
|
||||
switch (attachment.first) {
|
||||
case CustomMessageType_User_Enter_Room:
|
||||
@@ -2018,27 +2013,22 @@ BannerSchedulerDelegate
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[Combo effect] 📨 收到礼物消息 - second: %ld", (long)attachment.second);
|
||||
GiftReceiveInfoModel * receiveInfo = [GiftReceiveInfoModel modelWithJSON:attachment.data];
|
||||
[receiveInfo giftDataAlignment];
|
||||
|
||||
// 检查并修复连击计数
|
||||
if (receiveInfo.comboCount < 1) {
|
||||
NSLog(@"[Combo effect] 🚨 收到云信消息中连击计数异常 - comboCount: %ld", (long)receiveInfo.comboCount);
|
||||
// 尝试从 attachment.data 中获取正确的连击计数
|
||||
NSNumber *dataComboCount = attachment.data[@"comboCount"];
|
||||
if (dataComboCount && [dataComboCount integerValue] >= 1) {
|
||||
NSLog(@"[Combo effect] 🔧 从 attachment.data 修复连击计数 - 修复为: %@", dataComboCount);
|
||||
receiveInfo.comboCount = [dataComboCount integerValue];
|
||||
} else {
|
||||
NSLog(@"[Combo effect] 🔧 设置默认连击计数为 1");
|
||||
receiveInfo.comboCount = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
NSLog(@"[Combo effect] 📨 礼物消息解析完成 - giftId: %ld, combo: %ld, uid: %@", (long)receiveInfo.gift.giftId, (long)receiveInfo.comboCount, receiveInfo.uid);
|
||||
|
||||
|
||||
|
||||
@@ -2093,7 +2083,6 @@ BannerSchedulerDelegate
|
||||
}
|
||||
|
||||
if (receiveInfo.isLuckyBagGift) {
|
||||
NSLog(@"[Combo effect] 🎁 处理福袋礼物");
|
||||
[self receiveGiftHandleSendGiftAnimationWith:receiveInfo
|
||||
attachment:attachment];
|
||||
[self _handleBagGift:receiveInfo];
|
||||
@@ -2101,11 +2090,9 @@ BannerSchedulerDelegate
|
||||
if (receiveInfo.gift.notifyFull == 1 &&
|
||||
attachment.second == Custom_Message_Sub_Gift_Send) {
|
||||
// 全服礼物,但由自己发送,不在此逻辑播放
|
||||
NSLog(@"[Combo effect] 🎁 全服礼物由自己发送,跳过播放");
|
||||
return;
|
||||
}
|
||||
// 处理从位置 发送者 到 接受者 的礼物移动动画
|
||||
NSLog(@"[Combo effect] 🎁 处理普通礼物动画");
|
||||
[self receiveGiftHandleSendGiftAnimationWith:receiveInfo
|
||||
attachment:attachment];
|
||||
// 播放礼物动画(svga/mp4)(如果有的话)
|
||||
@@ -4048,7 +4035,6 @@ BannerSchedulerDelegate
|
||||
|
||||
- (void)bannerSchedulerDidFinishPlaying:(BannerScheduler *)scheduler {
|
||||
// Banner 播放完成,可以在这里进行清理工作
|
||||
NSLog(@"🔄 BannerScheduler: Banner 播放完成");
|
||||
|
||||
// 🔧 新增:确保手势容器状态正确
|
||||
[self ensureBannerGestureContainersEnabled];
|
||||
@@ -4057,7 +4043,6 @@ BannerSchedulerDelegate
|
||||
|
||||
- (void)bannerScheduler:(BannerScheduler *)scheduler didStartPlayingBanner:(id)banner {
|
||||
// Banner 开始播放
|
||||
NSLog(@"🔄 BannerScheduler: Banner 开始播放 - 类型: %@", [banner class]);
|
||||
}
|
||||
|
||||
#pragma mark - Private Methods
|
||||
@@ -4230,7 +4215,6 @@ BannerSchedulerDelegate
|
||||
NSString *uid = userInfo[@"uid"];
|
||||
BOOL isCombo = [userInfo[@"isCombo"] boolValue];
|
||||
|
||||
NSLog(@"[Combo effect] 🔔 收到combo状态变化通知 - 用户: %@, 状态: %@", uid, isCombo ? @"YES" : @"NO");
|
||||
|
||||
// 通知动画管理器更新combo状态
|
||||
if (isCombo) {
|
||||
|
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// XPMessageDataSourceManager.h
|
||||
// YUMI
|
||||
//
|
||||
// Created by YUMI on 2024/12/19.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XPMessageItem.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* 消息数据源管理器
|
||||
* 统一管理所有消息数据,提供线程安全的数据操作
|
||||
* 支持 DiffableDataSource 的单一数据源架构
|
||||
*/
|
||||
@interface XPMessageDataSourceManager : NSObject
|
||||
|
||||
/// 所有消息(只读)
|
||||
@property (nonatomic, strong, readonly) NSArray<XPMessageItem *> *allMessages;
|
||||
|
||||
/// 聊天消息(只读)
|
||||
@property (nonatomic, strong, readonly) NSArray<XPMessageItem *> *chatMessages;
|
||||
|
||||
/// 礼物消息(只读)
|
||||
@property (nonatomic, strong, readonly) NSArray<XPMessageItem *> *giftMessages;
|
||||
|
||||
/// 最大消息数量,超过此数量会自动清理旧消息
|
||||
@property (nonatomic, assign) NSInteger maxMessages;
|
||||
|
||||
/**
|
||||
* 初始化方法
|
||||
* @param maxMessages 最大消息数量,默认1000
|
||||
*/
|
||||
- (instancetype)initWithMaxMessages:(NSInteger)maxMessages;
|
||||
|
||||
/**
|
||||
* 添加消息
|
||||
* @param message 要添加的消息项
|
||||
*/
|
||||
- (void)addMessage:(XPMessageItem *)message;
|
||||
|
||||
/**
|
||||
* 同步添加单条消息(调用返回即已写入),适合随后立刻刷新快照的场景
|
||||
*/
|
||||
- (void)addMessageSync:(XPMessageItem *)message;
|
||||
|
||||
/**
|
||||
* 批量添加消息
|
||||
* @param messages 要添加的消息数组
|
||||
*/
|
||||
- (void)addMessages:(NSArray<XPMessageItem *> *)messages;
|
||||
|
||||
/**
|
||||
* 移除消息
|
||||
* @param message 要移除的消息项
|
||||
*/
|
||||
- (void)removeMessage:(XPMessageItem *)message;
|
||||
|
||||
/**
|
||||
* 清空所有消息
|
||||
*/
|
||||
- (void)clearAllMessages;
|
||||
|
||||
/**
|
||||
* 根据显示类型获取消息
|
||||
* @param displayType 显示类型:1=所有, 2=聊天, 3=礼物
|
||||
* @return 对应类型的消息数组
|
||||
*/
|
||||
- (NSArray<XPMessageItem *> *)messagesForDisplayType:(NSInteger)displayType;
|
||||
|
||||
/**
|
||||
* 手动清理旧消息
|
||||
* 当消息数量超过 maxMessages 时自动调用
|
||||
*/
|
||||
- (void)cleanupOldMessages;
|
||||
|
||||
/**
|
||||
* 获取当前消息总数
|
||||
* @return 消息总数
|
||||
*/
|
||||
- (NSInteger)totalMessageCount;
|
||||
|
||||
/**
|
||||
* 获取指定类型的消息数量
|
||||
* @param displayType 显示类型
|
||||
* @return 消息数量
|
||||
*/
|
||||
- (NSInteger)messageCountForDisplayType:(NSInteger)displayType;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@@ -0,0 +1,254 @@
|
||||
//
|
||||
// XPMessageDataSourceManager.m
|
||||
// YUMI
|
||||
//
|
||||
// Created by YUMI on 2024/12/19.
|
||||
//
|
||||
|
||||
#import "XPMessageDataSourceManager.h"
|
||||
|
||||
@interface XPMessageDataSourceManager ()
|
||||
|
||||
/// 所有消息(内部可变)
|
||||
@property (nonatomic, strong) NSMutableArray *allMessages;
|
||||
|
||||
/// 聊天消息(内部可变)
|
||||
@property (nonatomic, strong) NSMutableArray *chatMessages;
|
||||
|
||||
/// 礼物消息(内部可变)
|
||||
@property (nonatomic, strong) NSMutableArray *giftMessages;
|
||||
|
||||
/// 数据操作串行队列
|
||||
@property (nonatomic, strong) dispatch_queue_t dataQueue;
|
||||
|
||||
@end
|
||||
|
||||
@implementation XPMessageDataSourceManager
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (instancetype)init {
|
||||
return [self initWithMaxMessages:1000];
|
||||
}
|
||||
|
||||
- (instancetype)initWithMaxMessages:(NSInteger)maxMessages {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_allMessages = [NSMutableArray array];
|
||||
_chatMessages = [NSMutableArray array];
|
||||
_giftMessages = [NSMutableArray array];
|
||||
_dataQueue = dispatch_queue_create("com.yumi.message.data", DISPATCH_QUEUE_SERIAL);
|
||||
_maxMessages = maxMessages;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Public Methods
|
||||
|
||||
- (void)addMessage:(XPMessageItem *)message {
|
||||
if (!message) return;
|
||||
|
||||
dispatch_async(self.dataQueue, ^{
|
||||
NSMutableArray *allMessages = (NSMutableArray *)_allMessages;
|
||||
NSMutableArray *chatMessages = (NSMutableArray *)_chatMessages;
|
||||
NSMutableArray *giftMessages = (NSMutableArray *)_giftMessages;
|
||||
|
||||
[allMessages addObject:message];
|
||||
|
||||
// 根据消息类型分发到对应的子数据源
|
||||
if ([message isChatMessage]) {
|
||||
[chatMessages addObject:message];
|
||||
}
|
||||
if ([message isGiftMessage]) {
|
||||
[giftMessages addObject:message];
|
||||
}
|
||||
|
||||
// 检查是否需要清理
|
||||
if (allMessages.count > self.maxMessages) {
|
||||
[self cleanupOldMessages];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 同步写入版本,确保调用返回时数据已落入数组(适合随后立刻刷新快照的场景)
|
||||
- (void)addMessageSync:(XPMessageItem *)message {
|
||||
if (!message) return;
|
||||
|
||||
dispatch_sync(self.dataQueue, ^{
|
||||
NSMutableArray *allMessages = (NSMutableArray *)_allMessages;
|
||||
NSMutableArray *chatMessages = (NSMutableArray *)_chatMessages;
|
||||
NSMutableArray *giftMessages = (NSMutableArray *)_giftMessages;
|
||||
|
||||
[allMessages addObject:message];
|
||||
if ([message isChatMessage]) {
|
||||
[chatMessages addObject:message];
|
||||
}
|
||||
if ([message isGiftMessage]) {
|
||||
[giftMessages addObject:message];
|
||||
}
|
||||
if (allMessages.count > self.maxMessages) {
|
||||
[self cleanupOldMessages];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)addMessages:(NSArray<XPMessageItem *> *)messages {
|
||||
if (!messages || messages.count == 0) return;
|
||||
|
||||
dispatch_async(self.dataQueue, ^{
|
||||
NSMutableArray *allMessages = (NSMutableArray *)_allMessages;
|
||||
NSMutableArray *chatMessages = (NSMutableArray *)_chatMessages;
|
||||
NSMutableArray *giftMessages = (NSMutableArray *)_giftMessages;
|
||||
|
||||
for (XPMessageItem *message in messages) {
|
||||
[allMessages addObject:message];
|
||||
|
||||
// 根据消息类型分发到对应的子数据源
|
||||
if ([message isChatMessage]) {
|
||||
[chatMessages addObject:message];
|
||||
}
|
||||
if ([message isGiftMessage]) {
|
||||
[giftMessages addObject:message];
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否需要清理
|
||||
if (allMessages.count > self.maxMessages) {
|
||||
[self cleanupOldMessages];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)removeMessage:(XPMessageItem *)message {
|
||||
if (!message) return;
|
||||
|
||||
dispatch_async(self.dataQueue, ^{
|
||||
NSMutableArray *allMessages = (NSMutableArray *)_allMessages;
|
||||
NSMutableArray *chatMessages = (NSMutableArray *)_chatMessages;
|
||||
NSMutableArray *giftMessages = (NSMutableArray *)_giftMessages;
|
||||
|
||||
[allMessages removeObject:message];
|
||||
[chatMessages removeObject:message];
|
||||
[giftMessages removeObject:message];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)clearAllMessages {
|
||||
dispatch_async(self.dataQueue, ^{
|
||||
NSMutableArray *allMessages = (NSMutableArray *)_allMessages;
|
||||
NSMutableArray *chatMessages = (NSMutableArray *)_chatMessages;
|
||||
NSMutableArray *giftMessages = (NSMutableArray *)_giftMessages;
|
||||
|
||||
[allMessages removeAllObjects];
|
||||
[chatMessages removeAllObjects];
|
||||
[giftMessages removeAllObjects];
|
||||
});
|
||||
}
|
||||
|
||||
- (NSArray<XPMessageItem *> *)messagesForDisplayType:(NSInteger)displayType {
|
||||
__block NSArray *result = nil;
|
||||
dispatch_sync(self.dataQueue, ^{
|
||||
switch (displayType) {
|
||||
case 1: // 所有消息
|
||||
result = [_allMessages copy];
|
||||
break;
|
||||
case 2: // 聊天消息
|
||||
result = [_chatMessages copy];
|
||||
break;
|
||||
case 3: // 礼物消息
|
||||
result = [_giftMessages copy];
|
||||
break;
|
||||
default:
|
||||
result = [_allMessages copy];
|
||||
break;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)cleanupOldMessages {
|
||||
if (_allMessages.count <= self.maxMessages) return;
|
||||
|
||||
// 计算需要移除的消息数量(移除一半)
|
||||
NSInteger removeCount = self.maxMessages / 2;
|
||||
NSArray *itemsToRemove = [_allMessages subarrayWithRange:NSMakeRange(0, removeCount)];
|
||||
|
||||
NSMutableArray *allMessages = (NSMutableArray *)_allMessages;
|
||||
NSMutableArray *chatMessages = (NSMutableArray *)_chatMessages;
|
||||
NSMutableArray *giftMessages = (NSMutableArray *)_giftMessages;
|
||||
|
||||
// 从所有数据源中移除这些消息
|
||||
for (XPMessageItem *item in itemsToRemove) {
|
||||
[allMessages removeObject:item];
|
||||
[chatMessages removeObject:item];
|
||||
[giftMessages removeObject:item];
|
||||
}
|
||||
|
||||
NSLog(@"[XPMessageDataSourceManager] Cleaned up %ld old messages, remaining: %ld",
|
||||
(long)removeCount, (long)_allMessages.count);
|
||||
}
|
||||
|
||||
- (NSInteger)totalMessageCount {
|
||||
__block NSInteger count = 0;
|
||||
dispatch_sync(self.dataQueue, ^{
|
||||
count = _allMessages.count;
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
- (NSInteger)messageCountForDisplayType:(NSInteger)displayType {
|
||||
__block NSInteger count = 0;
|
||||
dispatch_sync(self.dataQueue, ^{
|
||||
switch (displayType) {
|
||||
case 1: // 所有消息
|
||||
count = _allMessages.count;
|
||||
break;
|
||||
case 2: // 聊天消息
|
||||
count = _chatMessages.count;
|
||||
break;
|
||||
case 3: // 礼物消息
|
||||
count = _giftMessages.count;
|
||||
break;
|
||||
default:
|
||||
count = _allMessages.count;
|
||||
break;
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
#pragma mark - Readonly Properties
|
||||
|
||||
- (NSArray<XPMessageItem *> *)allMessages {
|
||||
__block NSArray *result = nil;
|
||||
dispatch_sync(self.dataQueue, ^{
|
||||
result = [_allMessages copy];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSArray<XPMessageItem *> *)chatMessages {
|
||||
__block NSArray *result = nil;
|
||||
dispatch_sync(self.dataQueue, ^{
|
||||
result = [_chatMessages copy];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSArray<XPMessageItem *> *)giftMessages {
|
||||
__block NSArray *result = nil;
|
||||
dispatch_sync(self.dataQueue, ^{
|
||||
result = [_giftMessages copy];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
#pragma mark - Debug
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<XPMessageDataSourceManager: %p, total: %ld, chat: %ld, gift: %ld, max: %ld>",
|
||||
self, (long)[self totalMessageCount], (long)[self messageCountForDisplayType:2],
|
||||
(long)[self messageCountForDisplayType:3], (long)self.maxMessages];
|
||||
}
|
||||
|
||||
@end
|
@@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "PIBaseModel.h"
|
||||
#import "PIRoomPhotoAlbumItemModel.h"
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// XPMessageItem.h
|
||||
// YUMI
|
||||
//
|
||||
// Created by YUMI on 2024/12/19.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XPMessageInfoModel.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* DiffableDataSource 兼容的消息项模型
|
||||
* 用于替代原有的多数据源架构,提供统一的消息管理
|
||||
*/
|
||||
@interface XPMessageItem : NSObject <NSCopying>
|
||||
|
||||
/// 消息模型
|
||||
@property (nonatomic, strong) XPMessageInfoModel *messageModel;
|
||||
|
||||
/// 唯一标识符,用于 DiffableDataSource 的 itemIdentifier
|
||||
@property (nonatomic, strong) NSString *uniqueIdentifier;
|
||||
|
||||
/// 时间戳,用于排序和清理
|
||||
@property (nonatomic, assign) NSTimeInterval timestamp;
|
||||
|
||||
/// 显示类型:1=所有消息, 2=聊天消息, 3=礼物消息
|
||||
@property (nonatomic, assign) NSInteger displayType;
|
||||
|
||||
/**
|
||||
* 初始化方法
|
||||
* @param model 消息模型
|
||||
* @param identifier 唯一标识符
|
||||
*/
|
||||
- (instancetype)initWithMessageModel:(XPMessageInfoModel *)model
|
||||
uniqueIdentifier:(NSString *)identifier;
|
||||
|
||||
/**
|
||||
* 判断是否为聊天消息
|
||||
* @return YES 如果是聊天消息
|
||||
*/
|
||||
- (BOOL)isChatMessage;
|
||||
|
||||
/**
|
||||
* 判断是否为礼物消息
|
||||
* @return YES 如果是礼物消息
|
||||
*/
|
||||
- (BOOL)isGiftMessage;
|
||||
|
||||
/**
|
||||
* 判断是否应该显示在指定类型中
|
||||
* @param displayType 显示类型
|
||||
* @return YES 如果应该显示
|
||||
*/
|
||||
- (BOOL)shouldDisplayInType:(NSInteger)displayType;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// XPMessageItem.m
|
||||
// YUMI
|
||||
//
|
||||
// Created by YUMI on 2024/12/19.
|
||||
//
|
||||
|
||||
#import "XPMessageItem.h"
|
||||
|
||||
@implementation XPMessageItem
|
||||
|
||||
- (instancetype)initWithMessageModel:(XPMessageInfoModel *)model
|
||||
uniqueIdentifier:(NSString *)identifier {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_messageModel = model;
|
||||
_uniqueIdentifier = identifier;
|
||||
_timestamp = [[NSDate date] timeIntervalSince1970];
|
||||
_displayType = 1; // 默认显示在所有类型中
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isChatMessage {
|
||||
if (!self.messageModel) return NO;
|
||||
|
||||
// 文本消息 (NIMMessageTypeText = 0)
|
||||
if (self.messageModel.first == 0) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// 表情消息 (CustomMessageType_Face = 9)
|
||||
if (self.messageModel.first == 9) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isGiftMessage {
|
||||
if (!self.messageModel) return NO;
|
||||
|
||||
// 礼物相关消息类型
|
||||
switch (self.messageModel.first) {
|
||||
case 3: // CustomMessageType_Gift - 单人送礼物
|
||||
case 63: // CustomMessageType_RoomBoom - 房间爆炸
|
||||
case 26: // CustomMessageType_Candy_Tree - 糖果树
|
||||
case 12: // CustomMessageType_AllMicroSend - 全麦送礼物
|
||||
return YES;
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)shouldDisplayInType:(NSInteger)displayType {
|
||||
switch (displayType) {
|
||||
case 1: // 所有消息
|
||||
return YES;
|
||||
case 2: // 聊天消息
|
||||
return [self isChatMessage];
|
||||
case 3: // 礼物消息
|
||||
return [self isGiftMessage];
|
||||
default:
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
XPMessageItem *copy = [[XPMessageItem alloc] init];
|
||||
copy.messageModel = self.messageModel;
|
||||
copy.uniqueIdentifier = self.uniqueIdentifier;
|
||||
copy.timestamp = self.timestamp;
|
||||
copy.displayType = self.displayType;
|
||||
return copy;
|
||||
}
|
||||
|
||||
#pragma mark - NSObject
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
if (![object isKindOfClass:[XPMessageItem class]]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
XPMessageItem *other = (XPMessageItem *)object;
|
||||
return [self.uniqueIdentifier isEqualToString:other.uniqueIdentifier];
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return [self.uniqueIdentifier hash];
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<XPMessageItem: %p, identifier: %@, type: %ld, timestamp: %.0f>",
|
||||
self, self.uniqueIdentifier, (long)self.displayType, self.timestamp];
|
||||
}
|
||||
|
||||
@end
|
@@ -8,6 +8,7 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "RoomHostDelegate.h"
|
||||
#import "RoomGuestDelegate.h"
|
||||
#import "XPRoomMessageContainerView.h"
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface MsRoomMessageMainView : UIView<RoomGuestDelegate>
|
||||
@@ -16,6 +17,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (void)handleNIMImageMessage:(NIMMessage *)message;
|
||||
///0房间,1.聊天大厅
|
||||
@property(nonatomic,assign) NSInteger type;
|
||||
|
||||
@property(nonatomic,strong) XPRoomMessageContainerView *messageListView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -5,7 +5,7 @@
|
||||
// Created by duoban on 2024/5/10.
|
||||
//
|
||||
#import "MsRoomMessageMainView.h"
|
||||
#import "XPRoomMessageContainerView.h"
|
||||
|
||||
#import "ClientConfig.h"
|
||||
#import "UserInfoModel.h"
|
||||
#import <NIMSDK/NIMSDK.h>
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
@interface MsRoomMessageMainView()<XPRoomMessageContainerViewDelegate, NIMBroadcastManagerDelegate>
|
||||
|
||||
@property(nonatomic,strong) XPRoomMessageContainerView *messageListView;
|
||||
|
||||
|
||||
///房间的代理
|
||||
@property (nonatomic,weak) id<RoomHostDelegate> hostDelegate;
|
||||
|
@@ -10,6 +10,10 @@
|
||||
#import "RoomGuestDelegate.h"
|
||||
#import <JXCategoryView/JXCategoryListContainerView.h>
|
||||
|
||||
// DiffableDataSource 相关导入
|
||||
#import "XPMessageItem.h"
|
||||
#import "XPMessageDataSourceManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@class XPRoomMessageContainerView, AttachmentModel, NIMBroadcastMessage;
|
||||
@protocol XPRoomMessageContainerViewDelegate <NSObject>
|
||||
@@ -30,6 +34,31 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (void)handleBroadcastMessageAttachment:(AttachmentModel *)attachment;
|
||||
- (void)handleBroadcastMessage:(NIMBroadcastMessage *)message;
|
||||
|
||||
#pragma mark - DiffableDataSource Support
|
||||
|
||||
/// 是否使用 DiffableDataSource(开关控制)
|
||||
@property (nonatomic, assign) BOOL useDiffableDataSource;
|
||||
|
||||
/// DiffableDataSource 数据源
|
||||
@property (nonatomic, strong) UITableViewDiffableDataSource<NSString *, XPMessageItem *> *diffableDataSource;
|
||||
|
||||
/// 数据源管理器
|
||||
@property (nonatomic, strong) XPMessageDataSourceManager *dataSourceManager;
|
||||
|
||||
/// 设置 DiffableDataSource
|
||||
- (void)setupDiffableDataSource;
|
||||
|
||||
/// 使用 DiffableDataSource 添加消息
|
||||
- (void)addMessageWithDiffableDataSource:(NIMMessage *)message;
|
||||
|
||||
/// 使用 DiffableDataSource 切换显示类型
|
||||
- (void)changeTypeWithDiffableDataSource:(NSInteger)type;
|
||||
|
||||
/// 使用 DiffableDataSource 滚动到底部
|
||||
- (void)scrollToBottomWithDiffableDataSource:(BOOL)animated;
|
||||
|
||||
/// 更新 DiffableDataSource 快照
|
||||
- (void)updateDiffableDataSourceSnapshot;
|
||||
|
||||
@end
|
||||
|
||||
|
@@ -116,8 +116,17 @@ NSString * const kRoomShowTopicKey = @"kRoomShowTopicKey";
|
||||
self.isLoadHistoryMessage = YES;
|
||||
self.forceReloadNextAppend = NO;
|
||||
self.hostDelegate = delegate;
|
||||
|
||||
// DiffableDataSource 支持(默认关闭)
|
||||
self.useDiffableDataSource = NO;
|
||||
|
||||
[self initSubViews];
|
||||
[self initSubViewConstraints];
|
||||
|
||||
// 如果启用 DiffableDataSource,则设置它
|
||||
if (self.useDiffableDataSource) {
|
||||
[self setupDiffableDataSource];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -1498,31 +1507,39 @@ NSString * const kRoomShowTopicKey = @"kRoomShowTopicKey";
|
||||
|
||||
#pragma mark - UITableViewDelegate And UITableViewDataSource
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
NSArray *source = @[];
|
||||
switch (self.displayType) {
|
||||
case 1:
|
||||
source = self.datasource;
|
||||
break;
|
||||
case 2:
|
||||
source = self.datasource_chat;
|
||||
break;
|
||||
case 3:
|
||||
source = self.datasource_gift;
|
||||
break;
|
||||
|
||||
default:
|
||||
source = self.datasource;
|
||||
break;
|
||||
// 在 DiffableDataSource 模式下,使用 dataSourceManager 的数据计算行高
|
||||
XPMessageInfoModel *model = nil;
|
||||
if (self.useDiffableDataSource) {
|
||||
NSArray *messages = [self.dataSourceManager messagesForDisplayType:self.displayType];
|
||||
if (indexPath.row < messages.count) {
|
||||
XPMessageItem *item = [messages objectAtIndex:indexPath.row];
|
||||
model = item.messageModel;
|
||||
}
|
||||
} else {
|
||||
NSArray *source = @[];
|
||||
switch (self.displayType) {
|
||||
case 1:
|
||||
source = self.datasource;
|
||||
break;
|
||||
case 2:
|
||||
source = self.datasource_chat;
|
||||
break;
|
||||
case 3:
|
||||
source = self.datasource_gift;
|
||||
break;
|
||||
default:
|
||||
source = self.datasource;
|
||||
break;
|
||||
}
|
||||
model = [source xpSafeObjectAtIndex:indexPath.row];
|
||||
}
|
||||
|
||||
XPMessageInfoModel *model = [source xpSafeObjectAtIndex:indexPath.row];
|
||||
|
||||
// 预计算并缓存高度,避免使用 UITableViewAutomaticDimension
|
||||
if (model.rowHeight <= 0) {
|
||||
[self calculateRowHeightForModel:model];
|
||||
}
|
||||
|
||||
if (model.first == CustomMessageType_RedPacket && model.second == Custom_Message_Sub_LuckyPackage) {
|
||||
if (model && model.first == CustomMessageType_RedPacket && model.second == Custom_Message_Sub_LuckyPackage) {
|
||||
return model.rowHeight;
|
||||
}
|
||||
|
||||
@@ -1853,4 +1870,166 @@ NSString * const kRoomShowTopicKey = @"kRoomShowTopicKey";
|
||||
return _messageParser;
|
||||
}
|
||||
|
||||
#pragma mark - DiffableDataSource Implementation
|
||||
|
||||
- (void)setupDiffableDataSource {
|
||||
if (!self.useDiffableDataSource) return;
|
||||
|
||||
// 初始化数据源管理器
|
||||
self.dataSourceManager = [[XPMessageDataSourceManager alloc] initWithMaxMessages:1000];
|
||||
|
||||
// 设置 DiffableDataSource
|
||||
self.diffableDataSource = [[UITableViewDiffableDataSource alloc] initWithTableView:self.messageTableView cellProvider:^UITableViewCell *(UITableView *tableView, NSIndexPath *indexPath, XPMessageItem *item) {
|
||||
return [self dequeueCellForMessageItem:item atIndexPath:indexPath];
|
||||
}];
|
||||
|
||||
// 重要:设置 UITableView 的 dataSource
|
||||
self.messageTableView.dataSource = self.diffableDataSource;
|
||||
|
||||
// 设置初始快照
|
||||
NSDiffableDataSourceSnapshot *snapshot = [[NSDiffableDataSourceSnapshot alloc] init];
|
||||
[snapshot appendSectionsWithIdentifiers:@[@"messages"]];
|
||||
[self.diffableDataSource applySnapshot:snapshot animatingDifferences:NO];
|
||||
|
||||
NSLog(@"[XPRoomMessageContainerView] DiffableDataSource setup completed, tableView.dataSource=%@", self.messageTableView.dataSource);
|
||||
}
|
||||
|
||||
- (UITableViewCell *)dequeueCellForMessageItem:(XPMessageItem *)item atIndexPath:(NSIndexPath *)indexPath {
|
||||
NSLog(@"[DiffableDataSource] dequeueCellForMessageItem called, indexPath=%@, item=%@", indexPath, item);
|
||||
|
||||
XPMessageInfoModel *model = item.messageModel;
|
||||
NSString *cellKey = [NSString isEmpty:model.cellKey] ? NSStringFromClass([XPRoomMessageTableViewCell class]) : model.cellKey;
|
||||
|
||||
// 复用现有的 cell 创建逻辑
|
||||
if(model.first == 59 || // CustomMessageType_Room_Album
|
||||
(model.first == 1 && model.second == 1)){ // CustomMessageType_User_Enter_Room && Custom_Message_Sub_Pic_Message
|
||||
PIRoomMessagePhotoAlbumCell * cell = [self.messageTableView dequeueReusableCellWithIdentifier:NSStringFromClass([PIRoomMessagePhotoAlbumCell class])];
|
||||
cell.delegate = self;
|
||||
cell.messageInfo = model;
|
||||
cell.roomType = self.hostDelegate.getRoomInfo.type;
|
||||
return cell;
|
||||
} else if (model.first == 60 && model.second == 1) { // CustomMessageType_RedPacket && Custom_Message_Sub_LuckyPackage
|
||||
LuckyPackageMessageTableViewCell *cell = [self.messageTableView dequeueReusableCellWithIdentifier:@"LuckyPackageMessageTableViewCell" forIndexPath:indexPath];
|
||||
cell.dataSource = model.customInfo;
|
||||
return cell;
|
||||
} else if (model.first == 1 && model.second == 1) { // CustomMessageType_User_Enter_Room && Custom_Message_Sub_User_Enter_Room
|
||||
CPEnterRoomTableViewCell *cell = [self.messageTableView dequeueReusableCellWithIdentifier:@"CPEnterRoomTableViewCell" forIndexPath:indexPath];
|
||||
cell.cpIndex = [[model.customInfo objectForKey:@"cpIndex"] integerValue];
|
||||
cell.dataSource = model.customInfo;
|
||||
return cell;
|
||||
}
|
||||
else if (model.isBoom) {
|
||||
cellKey = @"XPRoomMessageTableViewCell_Boom";
|
||||
}
|
||||
|
||||
XPRoomMessageTableViewCell * cell = [self.messageTableView dequeueReusableCellWithIdentifier:cellKey];
|
||||
cell.delegate = self;
|
||||
cell.messageInfo = model;
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)addMessageWithDiffableDataSource:(NIMMessage *)message {
|
||||
NSLog(@"[DiffableDataSource] addMessageWithDiffableDataSource called, messageType=%ld", (long)message.messageType);
|
||||
|
||||
if (!self.useDiffableDataSource) {
|
||||
NSLog(@"[DiffableDataSource] useDiffableDataSource is NO, falling back to addRoomMessage");
|
||||
// 回退到原有方法
|
||||
[self addRoomMessage:message];
|
||||
return;
|
||||
}
|
||||
|
||||
// parser 内部创建了 UI 对象(如 NetImageView),必须在主线程执行
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// 解析消息(主线程)
|
||||
XPMessageInfoModel *model = [self.messageParser parseMessageAttribute:message];
|
||||
if (!model) {
|
||||
NSLog(@"[DiffableDataSource] Failed to parse message, messageType=%ld", (long)message.messageType);
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[DiffableDataSource] Parsed message successfully, first=%ld, second=%ld", (long)model.first, (long)model.second);
|
||||
|
||||
// 创建消息项(主线程)
|
||||
NSString *identifier = [NSString stringWithFormat:@"%@_%f",
|
||||
message.from,
|
||||
message.timestamp];
|
||||
XPMessageItem *item = [[XPMessageItem alloc] initWithMessageModel:model uniqueIdentifier:identifier];
|
||||
|
||||
NSLog(@"[DiffableDataSource] Created XPMessageItem, identifier=%@", identifier);
|
||||
|
||||
// 添加到数据源管理器(同步写入,保证后续快照看到最新数据)
|
||||
if ([self.dataSourceManager respondsToSelector:@selector(addMessageSync:)]) {
|
||||
[self.dataSourceManager addMessageSync:item];
|
||||
} else {
|
||||
[self.dataSourceManager addMessage:item];
|
||||
}
|
||||
|
||||
NSLog(@"[DiffableDataSource] Added to dataSourceManager, total count=%ld", (long)[self.dataSourceManager totalMessageCount]);
|
||||
|
||||
// 直接更新 UI 快照
|
||||
[self updateDiffableDataSourceSnapshot];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)changeTypeWithDiffableDataSource:(NSInteger)type {
|
||||
if (self.displayType == type) return;
|
||||
|
||||
self.displayType = type;
|
||||
|
||||
if (!self.useDiffableDataSource) {
|
||||
// 回退到原有方法
|
||||
[self changeType:type];
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新 tableHeaderView
|
||||
if (self.displayType == 1) {
|
||||
self.messageTableView.tableHeaderView = self.headerView;
|
||||
} else {
|
||||
self.messageTableView.tableHeaderView = nil;
|
||||
}
|
||||
|
||||
// 更新数据源
|
||||
[self updateDiffableDataSourceSnapshot];
|
||||
|
||||
// 延迟滚动到底部
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self scrollToBottomWithDiffableDataSource:YES];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)scrollToBottomWithDiffableDataSource:(BOOL)animated {
|
||||
if (!self.useDiffableDataSource) {
|
||||
[self scrollToBottom:animated];
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *messages = [self.dataSourceManager messagesForDisplayType:self.displayType];
|
||||
if (messages.count > 0) {
|
||||
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:messages.count - 1 inSection:0];
|
||||
[self.messageTableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:animated];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateDiffableDataSourceSnapshot {
|
||||
if (!self.useDiffableDataSource) return;
|
||||
|
||||
NSArray *messages = [self.dataSourceManager messagesForDisplayType:self.displayType];
|
||||
NSLog(@"[DiffableDataSource] updateDiffableDataSourceSnapshot called, displayType=%ld, messages count=%ld", (long)self.displayType, (long)messages.count);
|
||||
|
||||
NSDiffableDataSourceSnapshot *snapshot = [[NSDiffableDataSourceSnapshot alloc] init];
|
||||
[snapshot appendSectionsWithIdentifiers:@[@"messages"]];
|
||||
|
||||
// 注意:appendItemsWithIdentifiers 需要的是对象本身作为“标识符”(需实现 isEqual/hash)
|
||||
if (messages.count > 0) {
|
||||
[snapshot appendItemsWithIdentifiers:messages intoSectionWithIdentifier:@"messages"];
|
||||
NSLog(@"[DiffableDataSource] Added %ld items to snapshot", (long)messages.count);
|
||||
} else {
|
||||
NSLog(@"[DiffableDataSource] No messages to add to snapshot");
|
||||
}
|
||||
|
||||
NSLog(@"[DiffableDataSource] Applying snapshot with %ld items", (long)snapshot.numberOfItems);
|
||||
[self.diffableDataSource applySnapshot:snapshot animatingDifferences:YES];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -32,6 +32,12 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// 🔧 新增:更新礼物特效开关状态(通过 RoomInfo.hasAnimationEffect 更新)
|
||||
- (void)updateGiftEffectsForRoom:(NSString *)roomId enabled:(BOOL)enabled;
|
||||
// 支持来源标记:fromUser=YES 表示用户手动开关,打上覆盖标记
|
||||
- (void)updateGiftEffectsForRoom:(NSString *)roomId enabled:(BOOL)enabled fromUser:(BOOL)fromUser;
|
||||
|
||||
// 用户覆盖标记:用于阻止服务端推送覆盖用户选择
|
||||
- (void)setGiftEffectsOverrideForRoom:(NSString *)roomId enabled:(BOOL)enabled;
|
||||
- (BOOL)isGiftEffectsUserOverriddenForRoom:(NSString *)roomId;
|
||||
|
||||
// 🔧 新增:获取各开关状态
|
||||
- (BOOL)isGiftEffectsEnabledForRoom:(NSString *)roomId;
|
||||
|
@@ -87,11 +87,43 @@
|
||||
// 🔧 修复:发送通知以便UI能够更新
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kTurboGiftEffectsEnabledChanged
|
||||
object:nil
|
||||
userInfo:@{@"roomId": roomId, @"on": @(enabled)}];
|
||||
userInfo:@{ @"roomId": roomId, @"on": @(enabled) }];
|
||||
|
||||
NSLog(@"🎮 TurboModeStateManager: 房间 %@ 礼物特效开关更新为 %@", roomId, enabled ? @"开启" : @"关闭");
|
||||
}
|
||||
|
||||
// 向后兼容:默认视为非用户来源
|
||||
- (void)updateGiftEffectsForRoom:(NSString *)roomId enabled:(BOOL)enabled fromUser:(BOOL)fromUser {
|
||||
if (!roomId) return;
|
||||
[self ensureRoomSwitchStatesExist:roomId];
|
||||
NSMutableDictionary *roomStates = [self.roomSwitchStates[roomId] mutableCopy];
|
||||
roomStates[@"giftEffects"] = @(enabled);
|
||||
if (fromUser) {
|
||||
roomStates[@"giftEffectsOverride"] = @(YES);
|
||||
}
|
||||
self.roomSwitchStates[roomId] = roomStates;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kTurboGiftEffectsEnabledChanged
|
||||
object:nil
|
||||
userInfo:@{ @"roomId": roomId, @"on": @(enabled) }];
|
||||
NSLog(@"🎮 TurboModeStateManager: 房间 %@ 礼物特效开关更新为 %@ (fromUser=%@)", roomId, enabled ? @"开启" : @"关闭", fromUser ? @"YES" : @"NO");
|
||||
}
|
||||
|
||||
// 用户覆盖标记控制
|
||||
- (void)setGiftEffectsOverrideForRoom:(NSString *)roomId enabled:(BOOL)enabled {
|
||||
if (!roomId) return;
|
||||
[self ensureRoomSwitchStatesExist:roomId];
|
||||
NSMutableDictionary *roomStates = [self.roomSwitchStates[roomId] mutableCopy];
|
||||
roomStates[@"giftEffectsOverride"] = @(enabled);
|
||||
self.roomSwitchStates[roomId] = roomStates;
|
||||
}
|
||||
|
||||
- (BOOL)isGiftEffectsUserOverriddenForRoom:(NSString *)roomId {
|
||||
if (!roomId) return NO;
|
||||
[self ensureRoomSwitchStatesExist:roomId];
|
||||
return [self.roomSwitchStates[roomId][@"giftEffectsOverride"] boolValue];
|
||||
}
|
||||
|
||||
// 🔧 修复:获取礼物特效开关状态,考虑全局turbo mode影响
|
||||
- (BOOL)isGiftEffectsEnabledForRoom:(NSString *)roomId {
|
||||
if (!roomId) return NO;
|
||||
@@ -259,6 +291,7 @@
|
||||
|
||||
// 礼物特效:按需求不持久化,这里默认开启(面板或 RoomAnimationView 会基于 roomInfo 决定)
|
||||
BOOL giftEffects = YES;
|
||||
BOOL giftEffectsOverride = NO;
|
||||
|
||||
id giftScreenObj = [defaults objectForKey:kTurboGlobalGiftScreenEnabledKey(roomId)];
|
||||
BOOL globalGiftScreen = (giftScreenObj != nil) ? [defaults boolForKey:kTurboGlobalGiftScreenEnabledKey(roomId)] : NO;
|
||||
@@ -438,6 +471,7 @@
|
||||
|
||||
// 礼物特效:从房间信息获取,这里先设为默认值
|
||||
BOOL giftEffects = YES;
|
||||
BOOL giftEffectsOverride = NO;
|
||||
|
||||
// 其他三个开关:从 NSUserDefaults 读取
|
||||
id giftScreenObj = [defaults objectForKey:kTurboGlobalGiftScreenEnabledKey(roomId)];
|
||||
@@ -451,6 +485,7 @@
|
||||
|
||||
self.roomSwitchStates[roomId] = @{
|
||||
@"giftEffects": @(giftEffects),
|
||||
@"giftEffectsOverride": @(giftEffectsOverride),
|
||||
@"globalGiftScreen": @(globalGiftScreen),
|
||||
@"globalGameScreen": @(globalGameScreen),
|
||||
@"cpMic": @(cpMic)
|
||||
|
@@ -211,8 +211,8 @@
|
||||
TurboModeStateManager *manager = [TurboModeStateManager sharedManager];
|
||||
|
||||
if (sender.tag == 0) { // 礼物特效开关
|
||||
// 礼物特效开关不持久化,只更新临时状态
|
||||
[manager updateGiftEffectsForRoom:self.roomId enabled:isOn];
|
||||
// 礼物特效开关不持久化,只更新临时状态,并记录用户覆盖
|
||||
[manager updateGiftEffectsForRoom:self.roomId enabled:isOn fromUser:YES];
|
||||
|
||||
} else if (sender.tag == 1) { // 全局礼物屏幕
|
||||
[manager setGlobalGiftScreenEnabledForRoom:self.roomId enabled:isOn];
|
||||
|
@@ -92,7 +92,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// 新增:连击状态检查方法
|
||||
- (BOOL)isComboStateValid;
|
||||
- (NSDictionary * _Nonnull)getComboStateInfo;
|
||||
- (void)printComboState;
|
||||
|
||||
// 🔧 新增:状态通知方法
|
||||
- (void)setUserComboState:(BOOL)isCombo forUser:(NSString *)uid;
|
||||
|
@@ -71,7 +71,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
#pragma mark - 单例方法
|
||||
|
||||
- (void)dealloc {
|
||||
NSLog(@"[Combo effect] 🗑️ GiftComboManager dealloc开始 - %p", self);
|
||||
|
||||
// 🔥 修复:确保所有timer都被停止
|
||||
[self forceStopAllTimers];
|
||||
@@ -87,7 +86,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
self.enableCombo = NO;
|
||||
self.actionCallback = nil;
|
||||
|
||||
NSLog(@"[Combo effect] 🗑️ GiftComboManager dealloc完成 - %p", self);
|
||||
}
|
||||
|
||||
+ (instancetype)sharedManager {
|
||||
@@ -136,20 +134,14 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
|
||||
// 开始连击,重置
|
||||
- (void)reset {
|
||||
NSLog(@"[Combo effect] 🔄 开始连击重置 - combo: %ld -> 1, enableCombo: %@, actionCallback: %@",
|
||||
(long)self.combo,
|
||||
self.enableCombo ? @"YES" : @"NO",
|
||||
self.actionCallback ? @"可用" : @"为空");
|
||||
|
||||
|
||||
// 🔥 修复:检查是否正在重置过程中
|
||||
if (self.isCombing && self.combo == 0) {
|
||||
NSLog(@"[Combo effect] ⚠️ 正在重置过程中,跳过重复重置");
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔥 修复:如果actionCallback为空,延迟执行reset
|
||||
if (!self.actionCallback) {
|
||||
NSLog(@"[Combo effect] ⚠️ actionCallback为空,延迟执行reset");
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self reset];
|
||||
});
|
||||
@@ -158,7 +150,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
|
||||
// 🔥 修复:确保enableCombo状态正确
|
||||
if (!self.enableCombo) {
|
||||
NSLog(@"[Combo effect] ⚠️ enableCombo为NO,尝试重新激活");
|
||||
self.enableCombo = YES;
|
||||
}
|
||||
|
||||
@@ -167,7 +158,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
_errorMessage = @"";
|
||||
|
||||
// 验证重置后的状态
|
||||
NSLog(@"[Combo effect] 🔍 重置后验证 - combo: %ld, enableCombo: %@", (long)self.combo, self.enableCombo ? @"YES" : @"NO");
|
||||
|
||||
// 发送通知,让 GiftComboView 重置显示
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"ComboCountReset" object:nil];
|
||||
@@ -177,7 +167,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
|
||||
// 检查是否应该显示连击面板 - 确保在主线程执行UI回调
|
||||
if (self.actionCallback) {
|
||||
NSLog(@"[Combo effect] 📱 触发连击面板显示回调");
|
||||
@kWeakify(self);
|
||||
[self safeExecuteUIBlock:^{
|
||||
@kStrongify(self);
|
||||
@@ -186,11 +175,9 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
NSLog(@"[Combo effect] ⚠️ actionCallback为空,不显示连击面板");
|
||||
}
|
||||
|
||||
if (self.handleRoomUIChanged) {
|
||||
NSLog(@"[Combo effect] 🎮 隐藏房间UI元素");
|
||||
@kWeakify(self);
|
||||
[self safeExecuteUIBlock:^{
|
||||
@kStrongify(self);
|
||||
@@ -199,7 +186,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
}
|
||||
}];
|
||||
}
|
||||
NSLog(@"[Combo effect] ✅ 连击重置完成 - isCombing: %@, enableCombo: %@", self.isCombing ? @"YES" : @"NO", self.enableCombo ? @"YES" : @"NO");
|
||||
}
|
||||
|
||||
- (void)registerActions:(void (^)(ComboActionType))action {
|
||||
@@ -210,12 +196,10 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
|
||||
// 新增:实现其他简化接口方法
|
||||
- (void)activate {
|
||||
NSLog(@"[Combo effect] 🔧 激活连击功能");
|
||||
self.enableCombo = YES;
|
||||
}
|
||||
|
||||
- (void)deactivate {
|
||||
NSLog(@"[Combo effect] 🔧 停用连击功能");
|
||||
self.enableCombo = NO;
|
||||
}
|
||||
|
||||
@@ -226,25 +210,21 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
- (NSInteger)currentCount {
|
||||
// 确保连击计数最少为 1
|
||||
if (self.combo < 1) {
|
||||
NSLog(@"[Combo effect] ⚠️ currentCount: 连击计数异常,重置为1 - 当前: %ld", (long)self.combo);
|
||||
self.combo = 1;
|
||||
}
|
||||
return self.combo;
|
||||
}
|
||||
|
||||
- (void)incrementCount {
|
||||
NSLog(@"[Combo effect] 🔢 增加连击计数 - 当前: %ld -> %ld", (long)self.combo, (long)(self.combo + 1));
|
||||
self.combo += 1;
|
||||
}
|
||||
|
||||
- (void)clear {
|
||||
NSLog(@"[Combo effect] 🗑️ 清除连击状态");
|
||||
|
||||
[self forceBoomStateReset];
|
||||
|
||||
// 🔥 修复:确保在主线程执行UI回调,并检查回调是否有效
|
||||
if (self.actionCallback) {
|
||||
NSLog(@"[Combo effect] 📱 触发连击面板移除回调");
|
||||
@kWeakify(self);
|
||||
[self safeExecuteUIBlock:^{
|
||||
@kStrongify(self);
|
||||
@@ -256,7 +236,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
}
|
||||
|
||||
- (void)send {
|
||||
NSLog(@"[Combo effect] 📤 发送连击礼物");
|
||||
[self sendGift];
|
||||
}
|
||||
|
||||
@@ -273,7 +252,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
}
|
||||
|
||||
- (void)handleError:(NSError *)error {
|
||||
NSLog(@"[Combo effect] ❌ 处理错误: %@", error.localizedDescription);
|
||||
self.errorMessage = error.localizedDescription;
|
||||
}
|
||||
|
||||
@@ -288,33 +266,20 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
|
||||
|
||||
- (void)forceBoomStateReset {
|
||||
NSLog(@"[Combo effect] 🚨 执行强制Boom连击状态重置");
|
||||
|
||||
// 1. 立即停止所有定时器(无条件停止)
|
||||
NSLog(@"[Combo effect] ⏰ 停止所有定时器");
|
||||
[self forceStopAllTimers];
|
||||
|
||||
// 2. 清空所有队列
|
||||
NSLog(@"[Combo effect] 🗑️ 清空所有队列");
|
||||
[self clearAllQueues];
|
||||
|
||||
// 3. 重置所有状态标志
|
||||
NSLog(@"[Combo effect] 🔄 重置状态标志 - isCombing: %@ -> NO",
|
||||
self.isCombing ? @"YES" : @"NO");
|
||||
self.isCombing = NO;
|
||||
|
||||
// 4. 重置combo计数为0
|
||||
_combo = 0;
|
||||
NSLog(@"[Combo effect] 🔄 combo计数重置为0");
|
||||
|
||||
// 注意:不重置 enableCombo,保持连击功能可用状态
|
||||
// self.enableCombo = NO; // 移除这行,保持连击功能可用
|
||||
|
||||
// 注意:不清理 actionCallback,保持回调可用,以便重新进入连击状态
|
||||
// self.actionCallback = nil; // 移除这行,保持回调可用
|
||||
|
||||
// 5. 发送通知(优先级最高,通知所有相关组件)
|
||||
NSLog(@"[Combo effect] 📢 发送强制重置通知");
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kBoomStateForceResetNotification
|
||||
object:nil];
|
||||
@@ -322,19 +287,14 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
|
||||
// 6. 强制恢复UI(确保底部栏和侧栏显示)
|
||||
if (self.handleRoomUIChanged) {
|
||||
NSLog(@"[Combo effect] 🎮 恢复房间UI元素");
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.handleRoomUIChanged(NO);
|
||||
});
|
||||
}
|
||||
NSLog(@"[Combo effect] ✅ 强制重置完成 - enableCombo保持: %@, actionCallback保持: %@",
|
||||
self.enableCombo ? @"YES" : @"NO",
|
||||
self.actionCallback ? @"可用" : @"为空");
|
||||
}
|
||||
|
||||
// 无条件停止定时器
|
||||
- (void)forceStopAllTimers {
|
||||
NSLog(@"[Combo effect] ⏰ 强制停止所有Timer");
|
||||
|
||||
// 停止主定时器
|
||||
if (self.timer) {
|
||||
@@ -387,23 +347,14 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:打印当前连击状态(用于调试)
|
||||
- (void)printComboState {
|
||||
NSDictionary *stateInfo = [self getComboStateInfo];
|
||||
// 检查并修复连击计数异常
|
||||
[self validateAndFixComboCount];
|
||||
}
|
||||
|
||||
// 新增:验证并修复连击计数
|
||||
- (void)validateAndFixComboCount {
|
||||
@synchronized (self) {
|
||||
if (self.combo < 1) {
|
||||
NSLog(@"[Combo effect] 🚨 检测到连击计数异常,自动修复 - 当前: %ld -> 1", (long)self.combo);
|
||||
self.combo = 1;
|
||||
}
|
||||
|
||||
if (self.combo > 1000) {
|
||||
NSLog(@"[Combo effect] 🚨 检测到连击计数异常,自动修复 - 当前: %ld -> 100", (long)self.combo);
|
||||
self.combo = 100;
|
||||
}
|
||||
}
|
||||
@@ -412,10 +363,8 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
#pragma mark - 处理飘屏逻辑
|
||||
- (void)receiveGiftInfoForDisplayComboFlags:(GiftReceiveInfoModel *)receiveInfo
|
||||
container:(UIView *)container {
|
||||
NSLog(@"[Combo effect] 🎪 收到连击飘屏请求 - combo: %ld, giftId: %ld", (long)receiveInfo.comboCount, (long)receiveInfo.gift.giftId);
|
||||
self.containerView = container;
|
||||
[self.uiQueue addObject:receiveInfo];
|
||||
NSLog(@"[Combo effect] 📊 连击飘屏队列数量: %ld", (long)self.uiQueue.count);
|
||||
[self startProcessingUIQueue];
|
||||
}
|
||||
|
||||
@@ -467,9 +416,7 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
}
|
||||
|
||||
GiftReceiveInfoModel *receiveInfo = [self.uiQueue firstObject];
|
||||
NSLog(@"[Combo effect] 🎪 处理连击飘屏 - combo: %ld, giftId: %ld", (long)receiveInfo.comboCount, (long)receiveInfo.gift.giftId);
|
||||
[self.uiQueue xpSafeRemoveObjectAtIndex:0];
|
||||
NSLog(@"[Combo effect] <20><> 移除后UI动画队列数量: %ld", (long)self.uiQueue.count);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self handleGiftInfo:receiveInfo];
|
||||
});
|
||||
@@ -602,7 +549,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
dispatch_source_set_event_handler(self.timer, ^{
|
||||
@kStrongify(self);
|
||||
if (!self) {
|
||||
NSLog(@"[Combo effect] ⚠️ self已释放,忽略timer回调");
|
||||
return;
|
||||
}
|
||||
[self processRequestQueue];
|
||||
@@ -649,11 +595,10 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
});
|
||||
} else if ([networkData isKindOfClass:[NSDictionary class]]) {
|
||||
// 处理包含info和metadata的字典
|
||||
NSDictionary *comboData = (NSDictionary *)networkData;
|
||||
GiftReceiveInfoModel *info = comboData[@"info"];
|
||||
NSDictionary *metadata = comboData[@"metadata"];
|
||||
// NSDictionary *comboData = (NSDictionary *)networkData;
|
||||
// GiftReceiveInfoModel *info = comboData[@"info"];
|
||||
// NSDictionary *metadata = comboData[@"metadata"];
|
||||
// 这里可以添加相应的处理逻辑
|
||||
NSLog(@"[Combo effect] <20><> 处理网络数据 - info: %@, metadata: %@", info, metadata);
|
||||
}
|
||||
} else {
|
||||
[self stopProcessingQueue];
|
||||
@@ -701,7 +646,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
sendType:(RoomSendGiftType)sendType
|
||||
giftNum:(NSString *)giftNum {
|
||||
|
||||
NSLog(@"[Combo effect] 🔧 统一配置连击参数");
|
||||
|
||||
self.giftInfo = giftInfo;
|
||||
self.sendGiftToUIDs = UIDs;
|
||||
@@ -712,9 +656,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
self.giftSourceType = sourceType;
|
||||
self.roomSendGiftType = sendType;
|
||||
self.giftNumPerTimes = giftNum;
|
||||
|
||||
NSLog(@"[Combo effect] ✅ 连击参数配置完成 - giftId: %ld, targetCount: %ld",
|
||||
(long)giftInfo.giftId, (long)UIDs.count);
|
||||
}
|
||||
|
||||
- (BOOL)loadEnable {
|
||||
@@ -724,7 +665,6 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
#pragma mark - XPGiftPresenter
|
||||
|
||||
- (void)sendGift {
|
||||
NSLog(@"[Combo effect] 🎁 开始发送连击礼物 - combo: %ld, isCombing: %@", (long)self.combo, self.isCombing ? @"YES" : @"NO");
|
||||
|
||||
NSString *allUIDs = @"";
|
||||
for (NSString *item in self.sendGiftToUIDs) {
|
||||
@@ -744,25 +684,21 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
@"roomUid":self.roomUID
|
||||
};
|
||||
|
||||
NSLog(@"[Combo effect] 📦 添加礼物请求到队列 - giftId: %ld, targetUids: %@", (long)self.giftInfo.giftId, allUIDs);
|
||||
[self.requestQueue addObject:dic];
|
||||
[self startProcessingQueue];
|
||||
}
|
||||
|
||||
- (void)handleSendGift:(NSDictionary *)dic {
|
||||
NSString *allUIDs = [dic objectForKey:@"targetUids"];
|
||||
NSString *giftId = [dic objectForKey:@"giftId"];
|
||||
NSLog(@"[Combo effect] 🌐 开始调用送礼API - giftId: %@, targetUids: %@", giftId, allUIDs);
|
||||
// NSString *giftId = [dic objectForKey:@"giftId"];
|
||||
|
||||
@kWeakify(self);
|
||||
[Api requestSendGift:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
@kStrongify(self);
|
||||
if (!self) {
|
||||
NSLog(@"[Combo effect] ⚠️ self已释放,忽略API回调");
|
||||
return;
|
||||
}
|
||||
if (code == 200) {
|
||||
NSLog(@"[Combo effect] ✅ 送礼API成功 - giftId: %@, combo: %ld", giftId, (long)self.combo);
|
||||
GiftReceiveInfoModel *receive = [GiftReceiveInfoModel modelWithJSON:data.data];
|
||||
receive.sourceType = [[dic objectForKey:@"giftSource"] integerValue];
|
||||
receive.roomSendGiftType = [[dic objectForKey:@"giftType"] integerValue];
|
||||
@@ -774,11 +710,9 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
@"Price": @(receive.gift.goldPrice * receive.giftNum * array.count),
|
||||
@"isFromWinning":@(NO)}];
|
||||
} else {
|
||||
NSLog(@"[Combo effect] ❌ 送礼API失败 - code: %ld, msg: %@", (long)code, msg);
|
||||
// 区分错误类型,优化恢复策略
|
||||
if (code > 500 && code < 600) {
|
||||
// 服务器错误,可能是临时问题,保持连击状态
|
||||
NSLog(@"[Combo effect] 🔄 服务器错误,保持连击状态 - code: %ld", (long)code);
|
||||
#if DEBUG
|
||||
self.errorMessage = [NSString stringWithFormat:@"服务器繁忙,请稍后重试 - %@", msg];
|
||||
#else
|
||||
@@ -798,12 +732,10 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
// 临时错误,不重置连击状态,允许用户重试
|
||||
} else if (code == 31005) {
|
||||
// 余额不足,需要重置连击状态
|
||||
NSLog(@"[Combo effect] 💰 余额不足,强制移除连击状态");
|
||||
self.errorMessage = YMLocalizedString(@"XPCandyTreeInsufficientBalanceView1");
|
||||
[self clear];
|
||||
} else if (code == 8535) {
|
||||
// VIP等级不足,需要重置连击状态, 但不可能出现
|
||||
NSLog(@"[Combo effect] 👑 VIP等级不足,强制移除连击状态");
|
||||
self.errorMessage = @"";
|
||||
[self clear];
|
||||
} else {
|
||||
@@ -812,10 +744,8 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
// 对于网络错误等临时问题,保持连击状态
|
||||
if (code >= 1000 && code < 2000) {
|
||||
// 网络相关错误,保持连击状态
|
||||
NSLog(@"[Combo effect] 🌐 网络错误,保持连击状态 - code: %ld", (long)code);
|
||||
} else {
|
||||
// 其他错误,重置连击状态
|
||||
NSLog(@"[Combo effect] 🚨 其他错误,强制移除连击状态 - code: %ld", (long)code);
|
||||
[self clear];
|
||||
}
|
||||
}
|
||||
@@ -843,14 +773,12 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
|
||||
- (void)handleSendGiftSuccess:(GiftReceiveInfoModel *)receive
|
||||
sourceData:(BaseModel *)response {
|
||||
NSLog(@"[Combo effect] 🎉 连击礼物发送成功 - 当前combo: %ld", (long)self.combo);
|
||||
|
||||
// 验证连击计数有效性
|
||||
[self validateAndFixComboCount];
|
||||
|
||||
// 在API成功时递增combo计数
|
||||
if (self.isCombing) {
|
||||
NSLog(@"[Combo effect] 🔢 API成功,递增连击计数 - 当前: %ld -> %ld", (long)self.combo, (long)(self.combo + 1));
|
||||
self.combo += 1;
|
||||
|
||||
// 更新UI显示 - 确保在主线程执行UI回调
|
||||
@@ -868,20 +796,14 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
// 确保连击计数最少为 1
|
||||
NSInteger comboToSet = self.combo;
|
||||
if (comboToSet < 1) {
|
||||
NSLog(@"[Combo effect] 🚨 发送云信消息时连击计数异常,修复为 1 - 当前: %ld", (long)comboToSet);
|
||||
comboToSet = 1;
|
||||
}
|
||||
|
||||
[dic setObject:@(comboToSet) forKey:@"comboCount"];
|
||||
|
||||
// 验证连击计数设置
|
||||
NSNumber *setComboCount = dic[@"comboCount"];
|
||||
NSLog(@"[Combo effect] 🔍 连击计数设置验证 - 设置值: %@, 当前combo: %ld", setComboCount, (long)self.combo);
|
||||
|
||||
self.sendGiftReceiveInfo = receive;
|
||||
|
||||
if (self.handleComboSuccess) {
|
||||
NSLog(@"[Combo effect] 📨 调用连击成功回调,发送云信消息");
|
||||
self.handleComboSuccess(receive, dic);
|
||||
}
|
||||
|
||||
@@ -894,11 +816,9 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
}];
|
||||
}
|
||||
|
||||
NSLog(@"[Combo effect] ✅ 连击礼物处理完成");
|
||||
}
|
||||
|
||||
- (void)sendCustomMessage:(AttachmentModel *)attachment {
|
||||
NSLog(@"[Combo effect] 📨 发送云信自定义消息 - combo: %ld", (long)self.combo);
|
||||
|
||||
NIMMessage *message = [[NIMMessage alloc]init];
|
||||
message.setting.quickDeliveryEnabled = YES; // 开启快速投递
|
||||
@@ -924,9 +844,7 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
NSError *error = nil;
|
||||
[[NIMSDK sharedSDK].chatManager sendMessage:message toSession:session error:&error];
|
||||
if (error) {
|
||||
NSLog(@"[Combo effect] ❌ 云信消息发送失败 - error: %@", error.localizedDescription);
|
||||
} else {
|
||||
NSLog(@"[Combo effect] ✅ 云信消息发送成功");
|
||||
}
|
||||
}];
|
||||
}
|
||||
@@ -941,24 +859,12 @@ NSString * const kBoomStateForceResetNotification = @"BoomStateForceResetNotific
|
||||
dispatch_async(dispatch_get_main_queue(), uiBlock);
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:性能监控方法
|
||||
- (void)logPerformanceMetrics {
|
||||
NSLog(@"[Combo effect] 📊 性能指标 - 网络队列: %lu, 请求队列: %lu, 定时器: %@",
|
||||
(unsigned long)self.networkQueue.count,
|
||||
(unsigned long)self.requestQueue.count,
|
||||
self.timer ? @"运行中" : @"已停止");
|
||||
}
|
||||
|
||||
// 🔧 新增:状态通知方法实现
|
||||
|
||||
- (void)setUserComboState:(BOOL)isCombo forUser:(NSString *)uid {
|
||||
if (!uid || uid.length == 0) {
|
||||
NSLog(@"[Combo effect] ⚠️ 用户ID为空,无法设置combo状态");
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[Combo effect] 🔔 通知动画管理器设置用户 %@ 的combo状态为: %@", uid, isCombo ? @"YES" : @"NO");
|
||||
|
||||
// 通过通知中心通知动画管理器
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GiftComboStateChanged"
|
||||
|
@@ -29,12 +29,10 @@
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
NSLog(@"[Combo effect] 🗑️ CountdownRingView dealloc开始 - %p", self);
|
||||
|
||||
// 🔥 修复:清理所有资源
|
||||
[self cleanup];
|
||||
|
||||
NSLog(@"[Combo effect] 🗑️ CountdownRingView dealloc完成 - %p", self);
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame duration:(NSInteger)duration {
|
||||
@@ -90,11 +88,9 @@
|
||||
- (void)startCountdown {
|
||||
// 🔥 修复:检查运行状态,避免重复启动
|
||||
if (self.isRunning) {
|
||||
NSLog(@"[Combo effect] ⚠️ 倒计时已在运行中,忽略重复启动");
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[Combo effect] ⏰ 开始倒计时");
|
||||
self.isRunning = YES;
|
||||
|
||||
[self animateRing];
|
||||
@@ -106,16 +102,13 @@
|
||||
userInfo:nil
|
||||
repeats:YES];
|
||||
|
||||
NSLog(@"[Combo effect] ⏰ 倒计时已启动");
|
||||
}
|
||||
|
||||
// 重置倒计时
|
||||
- (void)resetCountdown {
|
||||
NSLog(@"[Combo effect] ⏰ 重置倒计时开始");
|
||||
|
||||
// 🔥 修复:检查运行状态
|
||||
if (!self.isRunning) {
|
||||
NSLog(@"[Combo effect] ⚠️ 倒计时未运行,直接启动");
|
||||
[self startCountdown];
|
||||
return;
|
||||
}
|
||||
@@ -138,7 +131,6 @@
|
||||
[self triggerLabelAnimation];
|
||||
});
|
||||
|
||||
NSLog(@"[Combo effect] ⏰ 重置倒计时完成");
|
||||
}
|
||||
|
||||
// 新增:触发标签动画
|
||||
@@ -158,12 +150,10 @@
|
||||
@synchronized(self) {
|
||||
// 🔥 修复:检查self是否存在
|
||||
if (!self) {
|
||||
NSLog(@"[Combo effect] ⚠️ self已释放,忽略updateCountdown调用");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.isRunning) {
|
||||
NSLog(@"[Combo effect] ⚠️ 倒计时已停止,忽略updateCountdown调用");
|
||||
[self stopCountdown];
|
||||
return;
|
||||
}
|
||||
@@ -171,7 +161,6 @@
|
||||
self.remainingTime -= self.timerInterval;
|
||||
|
||||
if (self.remainingTime <= 0) {
|
||||
NSLog(@"[Combo effect] ⏰ 倒计时结束,准备触发回调");
|
||||
|
||||
// 🔥 优化:先保存回调,再停止Timer,最后调用回调
|
||||
void (^completion)(void) = self.completionHandler;
|
||||
@@ -179,7 +168,6 @@
|
||||
|
||||
// 🔥 优化:安全调用回调,不涉及具体业务逻辑
|
||||
if (completion) {
|
||||
NSLog(@"[Combo effect] ⏰ 执行倒计时结束回调");
|
||||
completion(); // 纯回调,由上层处理业务逻辑
|
||||
}
|
||||
}
|
||||
@@ -189,16 +177,13 @@
|
||||
- (void)stopCountdown {
|
||||
// 🔥 修复:防止重复停止
|
||||
if (self.isStopping) {
|
||||
NSLog(@"[Combo effect] ⚠️ 已在停止过程中,忽略重复调用");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.isRunning) {
|
||||
NSLog(@"[Combo effect] ⚠️ 倒计时未运行,无需停止");
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[Combo effect] ⏰ 停止倒计时开始");
|
||||
self.isStopping = YES;
|
||||
|
||||
@synchronized(self) {
|
||||
@@ -206,13 +191,11 @@
|
||||
if (self.timer) {
|
||||
[self.timer invalidate];
|
||||
self.timer = nil;
|
||||
NSLog(@"[Combo effect] ⏰ Timer已停止");
|
||||
}
|
||||
|
||||
// 停止动画
|
||||
if (self.foregroundLayer) {
|
||||
[self.foregroundLayer removeAllAnimations];
|
||||
NSLog(@"[Combo effect] ⏰ 动画已停止");
|
||||
}
|
||||
|
||||
// 重置UI状态
|
||||
@@ -228,7 +211,6 @@
|
||||
}
|
||||
|
||||
self.isStopping = NO;
|
||||
NSLog(@"[Combo effect] ⏰ 停止倒计时完成");
|
||||
}
|
||||
|
||||
// 环形倒计时动画
|
||||
@@ -244,12 +226,10 @@
|
||||
animation.removedOnCompletion = YES; // 🔥 修复:允许自动移除,避免内存泄漏
|
||||
[self.foregroundLayer addAnimation:animation forKey:@"ringAnimation"];
|
||||
|
||||
NSLog(@"[Combo effect] 🎬 环形动画已启动,时长: %.1f秒", self.remainingTime);
|
||||
}
|
||||
|
||||
// 🔥 新增:完全清理方法(用于dealloc等场景)
|
||||
- (void)cleanup {
|
||||
NSLog(@"[Combo effect] 🗑️ 完全清理开始");
|
||||
|
||||
@synchronized(self) {
|
||||
[self stopCountdown];
|
||||
@@ -258,7 +238,6 @@
|
||||
self.completionHandler = nil;
|
||||
}
|
||||
|
||||
NSLog(@"[Combo effect] 🗑️ 完全清理完成");
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -40,7 +40,6 @@
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
NSLog(@"[Combo effect] 🗑️ GiftComboView dealloc开始 - %p", self);
|
||||
|
||||
// 🔥 修复:设置销毁标记,防止懒加载属性重新创建
|
||||
self.isDeallocating = YES;
|
||||
@@ -69,7 +68,6 @@
|
||||
[self.updateGoldQueue removeAllObjects];
|
||||
self.feedbackGenerator = nil;
|
||||
|
||||
NSLog(@"[Combo effect] 🗑️ GiftComboView dealloc完成 - %p", self);
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
@@ -151,7 +149,6 @@
|
||||
@kStrongify(self);
|
||||
if (!self) return; // 🔥 修复:检查self是否还存在
|
||||
|
||||
NSLog(@"[Combo effect] 📢 收到连击计数重置通知");
|
||||
[self resetComboCount];
|
||||
}];
|
||||
}
|
||||
@@ -182,14 +179,10 @@
|
||||
NSForegroundColorAttributeName: UIColorFromRGB(0xFFE07B),
|
||||
NSShadowAttributeName: shadow}];
|
||||
self.comboCountLabel.attributedText = string;
|
||||
|
||||
// 打印当前连击状态,确认计数正确
|
||||
[[GiftComboManager sharedManager] printComboState];
|
||||
}
|
||||
|
||||
// 新增方法:重置连击计数显示
|
||||
- (void)resetComboCount {
|
||||
NSLog(@"[Combo effect] 🔄 重置连击计数显示");
|
||||
NSString *countStr = @"x1"; // 重置为 x1
|
||||
NSShadow *shadow = [[NSShadow alloc] init];
|
||||
shadow.shadowBlurRadius = 3;
|
||||
@@ -204,7 +197,6 @@
|
||||
|
||||
// 新增方法:使用指定的 combo 计数更新显示
|
||||
- (void)updateCountWithCombo:(NSInteger)comboCount {
|
||||
NSLog(@"[Combo effect] 🔢 使用指定计数更新连击次数显示 - combo: %ld", (long)comboCount);
|
||||
NSString *countStr = [NSString stringWithFormat:@"x%ld", comboCount];
|
||||
NSShadow *shadow = [[NSShadow alloc] init];
|
||||
shadow.shadowBlurRadius = 3;
|
||||
@@ -284,22 +276,18 @@
|
||||
}
|
||||
|
||||
- (void)setupTimer {
|
||||
NSLog(@"[Combo effect] ⏰ 设置连击倒计时");
|
||||
@kWeakify(self);
|
||||
[self.countdownRingView setupCompletionHandler:^{
|
||||
@kStrongify(self);
|
||||
NSLog(@"[Combo effect] ⏰ 连击倒计时结束,触发强制移除");
|
||||
self.userInteractionEnabled = NO;
|
||||
[[GiftComboManager sharedManager] clear];
|
||||
}];
|
||||
[self.countdownRingView startCountdown];
|
||||
NSLog(@"[Combo effect] ⏰ 连击倒计时已启动");
|
||||
}
|
||||
|
||||
- (void)handleTap {
|
||||
static BOOL isHandlingTap = NO;
|
||||
if (isHandlingTap) {
|
||||
NSLog(@"[Combo effect] ⚠️ 点击间隔过短,忽略此次点击");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -308,22 +296,17 @@
|
||||
if (self.feedbackGenerator) {
|
||||
@try {
|
||||
[self.feedbackGenerator impactOccurred];
|
||||
NSLog(@"[Combo effect] 📳 震动反馈已触发");
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"[Combo effect] ⚠️ 震动反馈失败: %@", exception.reason);
|
||||
}
|
||||
} else {
|
||||
NSLog(@"[Combo effect] ⚠️ 震动反馈生成器未初始化");
|
||||
}
|
||||
} else {
|
||||
NSLog(@"[Combo effect] ⚠️ 设备不支持震动反馈 (iOS < 10.0)");
|
||||
}
|
||||
|
||||
#if RELEASE
|
||||
isHandlingTap = YES;
|
||||
#endif
|
||||
|
||||
NSLog(@"[Combo effect] 👆 连击面板被点击,发送礼物");
|
||||
|
||||
// 移除用户点击时的combo递增逻辑,改为在API成功时递增
|
||||
// NSInteger comboCount = [[GiftComboManager sharedManager] loadComboCountFromSendGiftView];
|
||||
@@ -333,7 +316,6 @@
|
||||
[[GiftComboManager sharedManager] sendGift];
|
||||
[self.playImageView startAnimation];
|
||||
[self.countdownRingView resetCountdown];
|
||||
NSLog(@"[Combo effect] ⏰ 重置连击倒计时");
|
||||
|
||||
// 处理点击事件
|
||||
// 延迟重置标志位
|
||||
@@ -354,7 +336,6 @@
|
||||
if (@available(iOS 10.0, *)) {
|
||||
if (self.feedbackGenerator) {
|
||||
[self.feedbackGenerator prepare];
|
||||
NSLog(@"[Combo effect] 📳 震动反馈已重新准备");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -132,18 +132,15 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification;
|
||||
|
||||
// 移除连击相关视图
|
||||
- (void)removeAllComboRelatedViews {
|
||||
NSLog(@"[Combo effect] 🗑️ 开始移除连击相关视图");
|
||||
|
||||
// 移除连击面板
|
||||
if (self.comboView && self.comboView.superview) {
|
||||
NSLog(@"[Combo effect] 🗑️ 移除comboView");
|
||||
[self.comboView stopTimer];
|
||||
[self.comboView endCombo];
|
||||
[self.comboView removeFromSuperview];
|
||||
self.comboView = nil;
|
||||
} else if (self.comboView) {
|
||||
// 🔥 修复:即使没有superview也要清理
|
||||
NSLog(@"[Combo effect] 🗑️ comboView存在但无superview,直接清理");
|
||||
[self.comboView stopTimer];
|
||||
[self.comboView endCombo];
|
||||
self.comboView = nil;
|
||||
@@ -161,7 +158,6 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification;
|
||||
self.bravoGiftView.hidden = NO;
|
||||
}
|
||||
|
||||
NSLog(@"[Combo effect] 🗑️ 连击相关视图移除完成");
|
||||
}
|
||||
|
||||
// 强制重置连击状态
|
||||
@@ -221,7 +217,6 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification;
|
||||
if (self.usingplaceType == SendGiftType_User) {
|
||||
return;
|
||||
}
|
||||
NSLog(@"[Combo effect] 📱 开始注册actionCallback - usingplaceType: %ld", (long)self.usingplaceType);
|
||||
@kWeakify(self);
|
||||
[[GiftComboManager sharedManager] registerActions:^(ComboActionType type) {
|
||||
@kStrongify(self);
|
||||
@@ -232,13 +227,11 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification;
|
||||
case ComboAction_ShowPanel: {
|
||||
// 🔥 修复:检查连击状态,避免在重置过程中显示面板
|
||||
if (![[GiftComboManager sharedManager] isActive]) {
|
||||
NSLog(@"[Combo effect] ⚠️ 连击未激活,跳过显示面板");
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔥 修复:检查usingplaceType,确保在正确的场景下显示面板
|
||||
if (self.usingplaceType == SendGiftType_User) {
|
||||
NSLog(@"[Combo effect] ⚠️ 私聊模式,跳过显示连击面板");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -258,7 +251,6 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification;
|
||||
|
||||
// 🔥 修复:检查comboView是否已存在,避免重复创建
|
||||
if (!self.comboView) {
|
||||
NSLog(@"[Combo effect] 📱 创建新的comboView");
|
||||
self->_comboView = [[GiftComboView alloc] init];
|
||||
}
|
||||
|
||||
@@ -299,7 +291,6 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification;
|
||||
}
|
||||
break;
|
||||
case ComboAction_Combo_Count_Update: {
|
||||
NSLog(@"[Combo effect] 📱 收到连击计数更新回调");
|
||||
[self.comboView updateCount];
|
||||
}
|
||||
break;
|
||||
@@ -432,13 +423,10 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification;
|
||||
|
||||
// 检查连击计数在云信消息中的传递
|
||||
NSNumber *comboCount = data[@"comboCount"];
|
||||
NSLog(@"[Combo effect] 📨 云信消息连击计数检查 - comboCount: %@, giftId: %ld", comboCount, (long)receiveModel.gift.giftId);
|
||||
|
||||
// 如果连击计数为 0 或 nil,尝试修复
|
||||
if (!comboCount || [comboCount integerValue] < 1) {
|
||||
NSLog(@"[Combo effect] 🚨 检测到云信消息中连击计数异常 - comboCount: %@", comboCount);
|
||||
NSInteger currentCombo = [[GiftComboManager sharedManager] currentCount];
|
||||
NSLog(@"[Combo effect] 🔧 使用当前连击计数修复 - 当前: %ld", (long)currentCombo);
|
||||
[data setObject:@(currentCombo) forKey:@"comboCount"];
|
||||
}
|
||||
|
||||
@@ -577,24 +565,6 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification;
|
||||
NIMMessage *message = [[NIMMessage alloc]init];
|
||||
NIMCustomObject *object = [[NIMCustomObject alloc] init];
|
||||
|
||||
// 连击计数应该从 attachment.data 中获取,而不是重新计算
|
||||
// 这样可以避免重复递增连击计数
|
||||
{
|
||||
BOOL onMain = [NSThread isMainThread];
|
||||
NSInteger comboToSend = [attachment.data[@"comboCount"] integerValue];
|
||||
NSData *payloadJSON = nil;
|
||||
@try {
|
||||
payloadJSON = [NSJSONSerialization dataWithJSONObject:attachment.data ?: @{} options:0 error:nil];
|
||||
} @catch (__unused NSException *e) {}
|
||||
NSLog(@"[Combo effect][Send] 📨 即将发送 | sessionId=%@ type=%@ | combo=%ld | payload=%lub | main=%@ | ts=%.3f",
|
||||
sessionID,
|
||||
(self.usingplaceType == SendGiftType_Room ? @"Chatroom" : @"P2P"),
|
||||
(long)comboToSend,
|
||||
(unsigned long)(payloadJSON.length),
|
||||
onMain ? @"YES" : @"NO",
|
||||
[[NSDate date] timeIntervalSince1970]);
|
||||
}
|
||||
|
||||
attachment.data = [self removeNSNullValuesAndEmptyStringsRecursively:attachment.data];
|
||||
|
||||
object.attachment = attachment;
|
||||
@@ -734,17 +704,14 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification;
|
||||
/// 初始化/重置 连击礼物功能状态
|
||||
- (void)readyForCombo:(XPGiftCountModel *)giftCount
|
||||
gift:(GiftInfoModel *)giftInfo {
|
||||
NSLog(@"[Combo effect] 🔧 准备连击状态 - giftType: %ld, segmentType: %ld, usingplaceType: %ld", (long)giftInfo.giftType, (long)self.segmentType, (long)self.usingplaceType);
|
||||
|
||||
// 🔥 修复:检查usingplaceType,私聊模式不支持连击
|
||||
if (self.usingplaceType == SendGiftType_User) {
|
||||
NSLog(@"[Combo effect] ❌ 私聊模式不支持连击");
|
||||
[[GiftComboManager sharedManager] deactivate];
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.segmentType == GiftSegmentType_Pack) {
|
||||
NSLog(@"[Combo effect] ❌ 背包礼物不支持连击");
|
||||
[[GiftComboManager sharedManager] deactivate];
|
||||
return;
|
||||
}
|
||||
@@ -754,12 +721,10 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification;
|
||||
giftInfo.giftType != GiftType_Lucky24 &&
|
||||
giftInfo.giftType != GiftType_Lucky25 &&
|
||||
giftInfo.giftType != GiftType_Bravo) {
|
||||
NSLog(@"[Combo effect] ❌ 礼物类型不支持连击 - giftType: %ld", (long)giftInfo.giftType);
|
||||
[[GiftComboManager sharedManager] deactivate];
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[Combo effect] ✅ 礼物支持连击,启用连击功能");
|
||||
[[GiftComboManager sharedManager] activate];
|
||||
|
||||
NSString *sessionID = self.usingplaceType == SendGiftType_User ? [NSString stringWithFormat:@"%ld", self.userArray.firstObject.uid] : [NSString stringWithFormat:@"%ld", [self.delegate getRoomInfo].roomId];
|
||||
@@ -775,7 +740,6 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification;
|
||||
sendType:[self dealRoomSendGiftType:giftInfo giftCount:giftCount]
|
||||
giftNum:[self dealSendGiftCount:giftCount gift:giftInfo]];
|
||||
|
||||
NSLog(@"[Combo effect] ✅ 连击状态准备完成");
|
||||
}
|
||||
|
||||
#pragma mark - XPGiftBarViewDelegate
|
||||
@@ -785,18 +749,8 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification;
|
||||
GiftInfoModel *giftInfo = self.giftInfoView.lastSelectGift;
|
||||
if (self.usingplaceType == SendGiftType_Room) {
|
||||
if (uids.count > 0) {
|
||||
NSLog(@"[Combo effect] 🎁 开始送礼物流程");
|
||||
[self readyForCombo:giftCount
|
||||
gift:giftInfo];
|
||||
[[GiftComboManager sharedManager] printComboState];
|
||||
|
||||
// 检查连击状态是否准备就绪
|
||||
if ([GiftComboManager sharedManager].enableCombo) {
|
||||
NSLog(@"[Combo effect] ✅ 连击功能已启用,准备调用resetCombo");
|
||||
} else {
|
||||
NSLog(@"[Combo effect] ❌ 连击功能未启用,无法进入连击状态");
|
||||
}
|
||||
|
||||
///送礼物的人
|
||||
NSString * uidString = [self dealSendGiftUids:uids];
|
||||
///送礼物的个数
|
||||
@@ -1206,12 +1160,10 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification;
|
||||
} else {
|
||||
self.giftBarView.walletInfoModel = receiveInfo.userPurse;
|
||||
}
|
||||
NSLog(@"[Combo effect] 📱 检查连击状态 - enableCombo: %@", [GiftComboManager sharedManager].enableCombo ? @"YES" : @"NO");
|
||||
|
||||
|
||||
|
||||
if ([GiftComboManager sharedManager].enableCombo && self.usingplaceType == SendGiftType_Room) {
|
||||
NSLog(@"[Combo effect] 📱 启用连击模式,重置连击状态");
|
||||
|
||||
// 检查 originDic 中的连击计数
|
||||
NSNumber *originComboCount = originDic[@"comboCount"];
|
||||
@@ -1220,7 +1172,6 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification;
|
||||
editDic[@"comboCount"] = @(1);
|
||||
originDic = editDic.copy;
|
||||
}
|
||||
NSLog(@"[Combo effect] 📱 originDic 连击计数检查 - comboCount: %@", originComboCount);
|
||||
|
||||
[[GiftComboManager sharedManager] reset];
|
||||
|
||||
@@ -1230,11 +1181,9 @@ UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification;
|
||||
@kWeakify(self);
|
||||
[[GiftComboManager sharedManager] setHandleComboSuccess:^(GiftReceiveInfoModel * _Nonnull receiveModel, NSMutableDictionary * _Nonnull originDic) {
|
||||
@kStrongify(self);
|
||||
NSLog(@"[Combo effect] 📱 连击回调中发送消息 - comboCount: %@", originDic[@"comboCount"]);
|
||||
[self sendCustomMessage:receiveInfo oringinDic:originDic.copy];
|
||||
}];
|
||||
} else {
|
||||
NSLog(@"[Combo effect] 📱 未启用连击模式,直接发送消息");
|
||||
}
|
||||
|
||||
[self sendCustomMessage:receiveInfo oringinDic:originDic];
|
||||
|
@@ -102,9 +102,12 @@
|
||||
// 遍历匹配并播放对应等级的SVGA(仅在CP麦位开关开启且不是小游戏房时)
|
||||
if (self.cpMicEnabled && self.roomType != 7) { // 7 = RoomType_MiniGame
|
||||
for (MicCpInfoModel *obj in cpList) {
|
||||
if (obj.cpLevel < 1) {
|
||||
continue;
|
||||
}
|
||||
BOOL match = (obj.uid == leftUid && obj.loverUid == rightUid) || (obj.uid == rightUid && obj.loverUid == leftUid);
|
||||
if (match) {
|
||||
NSInteger safeLevel = MAX(1, MIN(5, obj.cpLevel+1));
|
||||
NSInteger safeLevel = MAX(1, MIN(5, obj.cpLevel));
|
||||
NSString *svgaName = [NSString stringWithFormat:@"mic_cp_lv%ld", (long)safeLevel];
|
||||
[self playSVGAAnimationAtFrame:frame withNamed:svgaName];
|
||||
break;
|
||||
|
@@ -221,6 +221,8 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
@property (nonatomic, assign) BOOL currentUserWasOnMic; // 当前用户之前是否在麦上
|
||||
@property (nonatomic, assign) NSInteger currentUserMicPosition; // 当前用户的麦位位置(-1表示不在麦上)
|
||||
@property (nonatomic, assign) BOOL hasCompletedRoomInitialization; // 是否已完成进房初始化
|
||||
/// 是否已请求按房间UID获取CP列表(首进房)
|
||||
@property (nonatomic, assign) BOOL hasRequestedInitialCpByRoomUid;
|
||||
|
||||
@end
|
||||
|
||||
@@ -363,6 +365,65 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
self.upMicAskTimer = nil;
|
||||
}
|
||||
|
||||
// 🔧 释放保障:解散可能残留的弹窗/蒙层
|
||||
[TTPopup dismiss];
|
||||
|
||||
// 🔧 释放保障:停止 Turbo Mode Tips 监听(若支持)
|
||||
if ([[XPTurboModeTipsManager sharedManager] respondsToSelector:@selector(stopTipsMonitoringInRoom)]) {
|
||||
[[XPTurboModeTipsManager sharedManager] performSelector:@selector(stopTipsMonitoringInRoom)];
|
||||
}
|
||||
|
||||
// 🔧 释放保障:清空 GiftComboManager 的 UI 回调,避免对 VC 的强引用
|
||||
if ([GiftComboManager sharedManager]) {
|
||||
[[GiftComboManager sharedManager] setHandleRoomUIChanged:nil];
|
||||
}
|
||||
|
||||
// 🔧 释放保障:清空 sideMenu 的 block 回调
|
||||
if (self.sideMenu) {
|
||||
if ([self.sideMenu respondsToSelector:@selector(setOpenRedPacketHandle:)]) {
|
||||
[self.sideMenu performSelector:@selector(setOpenRedPacketHandle:) withObject:nil];
|
||||
}
|
||||
if ([self.sideMenu respondsToSelector:@selector(setShowSendGiftView:)]) {
|
||||
[self.sideMenu performSelector:@selector(setShowSendGiftView:) withObject:nil];
|
||||
}
|
||||
}
|
||||
|
||||
// 🔧 释放保障:置空各子视图/滚动视图的 delegate,规避潜在强引用
|
||||
NSArray *possibleDelegates = @[
|
||||
self.backContainerView,
|
||||
self.stageView,
|
||||
self.messageContainerView,
|
||||
self.quickMessageContainerView,
|
||||
self.menuContainerView,
|
||||
self.functionView,
|
||||
self.littleGameView,
|
||||
self.anchorScrollView
|
||||
];
|
||||
for (id obj in possibleDelegates) {
|
||||
if (!obj) { continue; }
|
||||
if ([obj respondsToSelector:@selector(setDelegate:)]) {
|
||||
[obj performSelector:@selector(setDelegate:) withObject:nil];
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setHostDelegate:)]) {
|
||||
[obj performSelector:@selector(setHostDelegate:) withObject:nil];
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setAnchorScrollDelegate:)]) {
|
||||
[obj performSelector:@selector(setAnchorScrollDelegate:) withObject:nil];
|
||||
}
|
||||
}
|
||||
|
||||
// 🔧 释放保障:移除视图层级,断开 UI 链接
|
||||
[self __removeAllViews];
|
||||
self.anchorScrollView = nil;
|
||||
self.backContainerView = nil;
|
||||
self.stageView = nil;
|
||||
self.messageContainerView = nil;
|
||||
self.quickMessageContainerView = nil;
|
||||
self.menuContainerView = nil;
|
||||
self.sideMenu = nil;
|
||||
self.functionView = nil;
|
||||
self.littleGameView = nil;
|
||||
|
||||
NSLog(@"🔄 XPRoomViewController: 销毁完成");
|
||||
}
|
||||
|
||||
@@ -662,6 +723,65 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
}
|
||||
|
||||
#pragma mark - Private Method
|
||||
|
||||
/// DiffableDataSource 开关控制(默认关闭)
|
||||
- (BOOL)useDiffableDataSource {
|
||||
// 可以通过配置或用户设置来控制
|
||||
return NO; // 暂时关闭,等测试完成后再开启
|
||||
}
|
||||
|
||||
/// 统一的消息分发方法 - 支持 DiffableDataSource 和原有系统
|
||||
- (void)distributeMessage:(NIMMessage *)message toContainer:(MsRoomMessageMainView *)container {
|
||||
if (!message || !container) return;
|
||||
|
||||
if ([self useDiffableDataSource]) {
|
||||
// 使用 DiffableDataSource 版本 - 直接调用 messageListView 的方法
|
||||
if ([container.messageListView respondsToSelector:@selector(addMessageWithDiffableDataSource:)]) {
|
||||
[container.messageListView addMessageWithDiffableDataSource:message];
|
||||
return;
|
||||
}
|
||||
// 如果不支持则回退到原有方法
|
||||
}
|
||||
|
||||
// 使用原有系统(带安全包装)
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@try {
|
||||
switch (message.messageType) {
|
||||
case NIMMessageTypeText:
|
||||
[container handleNIMTextMessage:message];
|
||||
break;
|
||||
case NIMMessageTypeTip:
|
||||
[container handleNIMTextMessage:message];
|
||||
break;
|
||||
case NIMMessageTypeImage:
|
||||
[container handleNIMImageMessage:message];
|
||||
break;
|
||||
case NIMMessageTypeCustom:
|
||||
[container handleNIMCustomMessage:message];
|
||||
break;
|
||||
case NIMMessageTypeNotification:
|
||||
[container handleNIMNotificationMessage:message];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"[DistributeMessage] Caught %@: %@", exception.name, exception.reason);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)safeMessageContainerCall:(NSString *)origin block:(void (^)(MsRoomMessageMainView *view))block {
|
||||
MsRoomMessageMainView *view = self.messageContainerView;
|
||||
if (!view || !block) { return; }
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@try {
|
||||
block(view);
|
||||
} @catch (NSException *exception) {
|
||||
NSLog(@"[SafeMessage][%@] Caught %@: %@", origin, exception.name, exception.reason);
|
||||
}
|
||||
});
|
||||
}
|
||||
- (void)initSubViews {
|
||||
self.view.backgroundColor = [UIColor darkGrayColor];
|
||||
[self.view addSubview:self.backContainerView];
|
||||
@@ -1727,7 +1847,10 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
// 修复:首次获取roominfo成功后,同步礼物特效状态到开关
|
||||
NSString *roomId = @(roomInfo.roomId).stringValue;
|
||||
[[TurboModeStateManager sharedManager] updateGiftEffectsForRoom:roomId
|
||||
enabled:roomInfo.hasAnimationEffect];
|
||||
enabled:roomInfo.hasAnimationEffect
|
||||
fromUser:NO];
|
||||
// 进入房间后重置用户覆盖标记
|
||||
[[TurboModeStateManager sharedManager] setGiftEffectsOverrideForRoom:roomId enabled:NO];
|
||||
|
||||
[self requestBoomData];
|
||||
|
||||
@@ -1812,6 +1935,9 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
|
||||
// 🔧 新增:初始化当前用户的麦位状态
|
||||
[self initializeCurrentUserMicStatus];
|
||||
|
||||
// 🔧 首次进房:尝试在初始化就绪后触发按房间UID的CP拉取(幂等)
|
||||
[self tryRequestInitialCpByRoomUidIfNeeded];
|
||||
|
||||
//上报进房
|
||||
if (self.roomInfo != nil) {
|
||||
@@ -1825,6 +1951,18 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
}
|
||||
}
|
||||
|
||||
/// 幂等触发:按房间UID拉取全量CP,仅在初始化完成后触发一次
|
||||
- (void)tryRequestInitialCpByRoomUidIfNeeded {
|
||||
@synchronized (self) {
|
||||
if (self.hasRequestedInitialCpByRoomUid) { return; }
|
||||
if (!self.hasCompletedRoomInitialization) { return; }
|
||||
if (self.isExitingRoom || !self.isViewActive) { return; }
|
||||
self.hasRequestedInitialCpByRoomUid = YES;
|
||||
}
|
||||
NSString *roomUid = [NSString stringWithFormat:@"%ld", (long)self.roomInfo.uid];
|
||||
[self.presenter micCpListByRoomUid:roomUid];
|
||||
}
|
||||
|
||||
- (void)enterRoomFail:(NSInteger)code {
|
||||
[XNDJTDDLoadingTool hideHUDInView:self.navigationController.view];
|
||||
[self hideHUD];
|
||||
@@ -2144,14 +2282,10 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
|
||||
// 非房间内消息不处理(记录过滤原因)
|
||||
if (message.session.sessionType != NIMSessionTypeChatroom) {
|
||||
NSLog(@"[Recv] ⛔️ 过滤:非聊天室消息 | type=%ld | sid=%@",
|
||||
(long)message.session.sessionType, message.session.sessionId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (![message.session.sessionId isEqualToString:@(self.roomInfo.roomId).stringValue]) {
|
||||
NSLog(@"[Recv] ⛔️ 过滤:房间不匹配 | msg.sid=%@ | curRoomId=%@",
|
||||
message.session.sessionId, @(self.roomInfo.roomId).stringValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2162,12 +2296,12 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
} else if (message.messageType == NIMMessageTypeCustom) {
|
||||
[self handleNimCustomTypeMessage:message];
|
||||
} else if(message.messageType == NIMMessageTypeText) {
|
||||
[self.messageContainerView handleNIMTextMessage:message];
|
||||
[self distributeMessage:message toContainer:self.messageContainerView];
|
||||
[self.littleGameView handleNIMTextMessage:message];
|
||||
} else if(message.messageType == NIMMessageTypeTip) {
|
||||
[self.messageContainerView handleNIMTextMessage:message];
|
||||
[self distributeMessage:message toContainer:self.messageContainerView];
|
||||
}else if(message.messageType == NIMMessageTypeImage){
|
||||
[self.messageContainerView handleNIMImageMessage:message];
|
||||
[self distributeMessage:message toContainer:self.messageContainerView];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2335,7 +2469,7 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
[self.roomHeaderView onRoomUpdate];
|
||||
[self.stageView handleNIMNotificationMessage:message];
|
||||
[self.animationView handleNIMNotificationMessage:message];
|
||||
[self.messageContainerView handleNIMNotificationMessage:message];
|
||||
[self distributeMessage:message toContainer:self.messageContainerView];
|
||||
[self.sideMenu handleNIMNotificationMessage:message];
|
||||
[self.menuContainerView handleNIMNotificationMessage:message];
|
||||
[self.functionView handleNIMNotificationMessage:message];
|
||||
@@ -2448,7 +2582,7 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
[self.sideMenu handleNIMCustomMessage:message];
|
||||
[self.functionView handleNIMCustomMessage:message];
|
||||
[self.littleGameView handleNIMCustomMessage:message];
|
||||
[self.messageContainerView handleNIMCustomMessage:message];
|
||||
[self distributeMessage:message toContainer:self.messageContainerView];
|
||||
|
||||
switch (attachment.first) {
|
||||
case ClientMessage_Type:
|
||||
@@ -2498,6 +2632,16 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
RoomInfoModel * roomInfo = [RoomInfoModel modelWithDictionary:dic];
|
||||
self.roomInfo.hasAnimationEffect = roomInfo.hasAnimationEffect;
|
||||
[self.roomHeaderView onRoomUpdate];
|
||||
// 同步到 TurboModeStateManager,除非用户已手动覆盖
|
||||
{
|
||||
NSString *roomId = @(self.roomInfo.roomId).stringValue;
|
||||
BOOL userOverridden = [[TurboModeStateManager sharedManager] isGiftEffectsUserOverriddenForRoom:roomId];
|
||||
if (!userOverridden) {
|
||||
[[TurboModeStateManager sharedManager] updateGiftEffectsForRoom:roomId
|
||||
enabled:self.roomInfo.hasAnimationEffect
|
||||
fromUser:NO];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3005,7 +3149,7 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
object.attachment = attachment;
|
||||
message.messageObject = object;
|
||||
|
||||
[self.messageContainerView handleNIMTextMessage:message];
|
||||
[self distributeMessage:message toContainer:self.messageContainerView];
|
||||
}
|
||||
}
|
||||
//发送消息成功回调
|
||||
@@ -3065,10 +3209,10 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
self.roomInfo.hasAnimationEffect = roomInfo.hasAnimationEffect;
|
||||
}
|
||||
[self.roomHeaderView onRoomUpdate];
|
||||
[self.messageContainerView handleNIMCustomMessage:message];
|
||||
[self distributeMessage:message toContainer:self.messageContainerView];
|
||||
}
|
||||
}else if(message.messageType == NIMMessageTypeText) {
|
||||
[self.messageContainerView handleNIMTextMessage:message];;
|
||||
[self distributeMessage:message toContainer:self.messageContainerView];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3793,15 +3937,18 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
case NIMMessageTypeCustom:
|
||||
[self handleNimCustomTypeMessage:message];
|
||||
break;
|
||||
case NIMMessageTypeText:
|
||||
[self.messageContainerView handleNIMTextMessage:message];
|
||||
case NIMMessageTypeText: {
|
||||
[self distributeMessage:message toContainer:self.messageContainerView];
|
||||
[self.littleGameView handleNIMTextMessage:message];
|
||||
}
|
||||
break;
|
||||
case NIMMessageTypeTip:
|
||||
[self.messageContainerView handleNIMTextMessage:message];
|
||||
case NIMMessageTypeTip: {
|
||||
[self distributeMessage:message toContainer:self.messageContainerView];
|
||||
}
|
||||
break;
|
||||
case NIMMessageTypeImage:
|
||||
[self.messageContainerView handleNIMImageMessage:message];
|
||||
case NIMMessageTypeImage:{
|
||||
[self distributeMessage:message toContainer:self.messageContainerView];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -4150,6 +4297,8 @@ XPCandyTreeInsufficientBalanceViewDelegate>
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
self.hasCompletedRoomInitialization = YES;
|
||||
NSLog(@"🔧 进房初始化完成,后续麦位变动将触发 micCpListByUidList 调用");
|
||||
// 初始化完成后,幂等尝试按房间UID拉取一次全量CP
|
||||
[self tryRequestInitialCpByRoomUidIfNeeded];
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -94,7 +94,7 @@
|
||||
editParam = [MSParamsDecode msDecodeParams:editParam];
|
||||
params = [self configBaseParmars:editParam];
|
||||
|
||||
#if 0
|
||||
#if DEBUG
|
||||
// 构建完整的 URL
|
||||
NSString *baseUrl = [HttpRequestHelper getHostUrl];
|
||||
NSString *fullUrl = [NSString stringWithFormat:@"%@/%@", baseUrl, method];
|
||||
@@ -118,7 +118,7 @@
|
||||
@kWeakify(self);
|
||||
[manager GET:method parameters:params headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
|
||||
BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject];
|
||||
#if 0
|
||||
#if DEBUG
|
||||
NSLog(@"%@ - \n%@\n", method, [baseModel toJSONString]);
|
||||
#else
|
||||
#endif
|
||||
@@ -148,7 +148,7 @@
|
||||
|
||||
params = [self configBaseParmars:params];
|
||||
|
||||
#if 0
|
||||
#if DEBUG
|
||||
// 构建完整的 URL
|
||||
NSString *baseUrl = [HttpRequestHelper getHostUrl];
|
||||
NSString *fullUrl = [NSString stringWithFormat:@"%@/%@", baseUrl, method];
|
||||
@@ -163,7 +163,7 @@
|
||||
|
||||
[manager POST:method parameters:params headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
|
||||
BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject];
|
||||
#if 0
|
||||
#if DEBUG
|
||||
NSLog(@"\n%@", [baseModel toJSONString]);
|
||||
#else
|
||||
#endif
|
||||
|
50
issues/diffable-migration-notes.md
Normal file
50
issues/diffable-migration-notes.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# DiffableDataSource 迁移记录(阶段性回退)
|
||||
|
||||
时间:2025-09-23
|
||||
|
||||
## 背景
|
||||
- 目标:将房间消息公屏迁移到 UITableViewDiffableDataSource(iOS 13+)。
|
||||
- 核心组件:`XPMessageItem`、`XPMessageDataSourceManager`、`XPRoomMessageContainerView`、`XPRoomViewController`。
|
||||
|
||||
## 已完成
|
||||
- 新增 `XPMessageItem`(实现 NSCopying/isEqual/hash/description)。
|
||||
- 新增 `XPMessageDataSourceManager`:串行队列封装、读写隔离;修复了在队列内访问只读属性导致的递归/死锁,改用 `_allMessages/_chatMessages/_giftMessages`;新增 `addMessageSync:` 用于快照直后立刻刷新。
|
||||
- `XPRoomMessageContainerView`:
|
||||
- 增加 DiffableDataSource 支持:`setupDiffableDataSource`、`addMessageWithDiffableDataSource:`、`updateDiffableDataSourceSnapshot`、`scrollToBottomWithDiffableDataSource:`、`changeTypeWithDiffableDataSource:`。
|
||||
- 绑定 `tableView.dataSource = diffableDataSource`。
|
||||
- 修正行高计算在 Diffable 模式下基于 `dataSourceManager`。
|
||||
- 解析/建模移动到主线程,避免 parser 内创建 UIView 触发 MTC。
|
||||
- `XPRoomViewController`:统一分发入口 `distributeMessage:toContainer:`,在启用时路由到 `container.messageListView addMessageWithDiffableDataSource:`。
|
||||
|
||||
## 遇到的问题
|
||||
1) Main Thread Checker:parser 内创建 `NetImageView`,后台线程调用崩溃 → 已将解析迁至主线程。
|
||||
2) 数据源递归死锁:在队列内通过 getter 访问只读数组,getter 内 `dispatch_sync` → 已用 ivar 替代。
|
||||
3) API 调用不兼容:`appendItems:toSection:`(Swift 签名)在 Obj‑C 不可用 → 改为 `appendItemsWithIdentifiers:intoSectionWithIdentifier:`。
|
||||
4) UI 不显示:日志显示 snapshot item 数量正确、cellProvider 触发,但 cell 行高与旧数组耦合,渲染为空 → 已在 Diffable 模式下改为从 `dataSourceManager` 读取。
|
||||
5) 仍存在:
|
||||
- 部分 cell 样式依赖旧数据流与时序;
|
||||
- 插入数据不全(需要进一步梳理 parser→model→item 的丢弃路径与过滤条件)。
|
||||
|
||||
## 现状与决策
|
||||
- 暂时关闭 DiffableDataSource(`XPRoomViewController.useDiffableDataSource` 返回 NO,`XPRoomMessageContainerView.useDiffableDataSource = NO`),回退旧方案,确保稳定性。
|
||||
|
||||
## 后续建议(待恢复时)
|
||||
1) 将 parser 改为纯数据产出(不创建 UIView/Attachment UI),UI 组件在 cell 渲染时构造。
|
||||
2) 为 `XPMessageItem`/`XPMessageInfoModel` 增加单元测试与快照测试,覆盖典型 first/second 组合、礼物/文本/通知。
|
||||
3) 建立统一的高度计算服务,消除表与数据源的耦合分支。
|
||||
4) 快照构建前后加一致性校验(section/items 数量、索引有效性)。
|
||||
5) 按功能开关分阶段恢复:先纯文本 → 表情 → 通知 → 礼物 → 特殊 cell。
|
||||
|
||||
## 变更点列表(代码)
|
||||
- `YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageItem.{h,m}` 新增。
|
||||
- `YuMi/Modules/YMRoom/View/MessageContainerView/Model/XPMessageDataSourceManager.{h,m}` 新增与修复(addMessageSync、ivar 访问等)。
|
||||
- `YuMi/Modules/YMRoom/View/MessageContainerView/XPRoomMessageContainerView.m` 多处新增/修改(Diffable 支持、主线程解析、行高来源切换、dataSource 绑定)。
|
||||
- `YuMi/Modules/YMRoom/View/XPRoomViewController.m` 新增 `distributeMessage:toContainer:`,并默认关闭 Diffable。
|
||||
|
||||
## 回退开关位置
|
||||
- `XPRoomViewController - (BOOL)useDiffableDataSource` → NO
|
||||
- `XPRoomMessageContainerView.useDiffableDataSource` → NO
|
||||
|
||||
---
|
||||
记录人:系统自动
|
||||
|
@@ -53,7 +53,6 @@
|
||||
BOOL isUserInCombo = [self.userComboStates[uid] boolValue];
|
||||
if (isUserInCombo) {
|
||||
BOOL isCurrentUser = [uid isEqualToString:[AccountInfoStorage instance].getUid];
|
||||
NSLog(@"[Combo effect] 🎯 用户 %@ 处于combo状态,是否当前用户: %@", uid, isCurrentUser ? @"YES" : @"NO");
|
||||
return isCurrentUser;
|
||||
}
|
||||
|
||||
@@ -63,12 +62,10 @@
|
||||
NSTimeInterval timeSinceLastGift = [[NSDate date] timeIntervalSinceDate:lastGiftTime];
|
||||
if (timeSinceLastGift <= self.comboTimeWindow) {
|
||||
BOOL isCurrentUser = [uid isEqualToString:[AccountInfoStorage instance].getUid];
|
||||
NSLog(@"[Combo effect] 🎯 用户 %@ 在时间窗口内,是否当前用户: %@", uid, isCurrentUser ? @"YES" : @"NO");
|
||||
return isCurrentUser;
|
||||
}
|
||||
}
|
||||
|
||||
NSLog(@"[Combo effect] 🎯 用户 %@ 不使用combo动画", uid);
|
||||
return NO;
|
||||
}
|
||||
```
|
||||
|
Reference in New Issue
Block a user