Compare commits
4 Commits
0147800f75
...
58ab43c7d8
Author | SHA1 | Date | |
---|---|---|---|
![]() |
58ab43c7d8 | ||
![]() |
68088e00e9 | ||
![]() |
10d4abf5ee | ||
![]() |
8d20a9e44f |
@@ -554,6 +554,7 @@
|
||||
4C886BEB2E014AE5006F0BA7 /* MedalsPresenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C886BEA2E014AE5006F0BA7 /* MedalsPresenter.m */; };
|
||||
4C886BEE2E014B6C006F0BA7 /* Api+Medals.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C886BED2E014B6C006F0BA7 /* Api+Medals.m */; };
|
||||
4C886BF22E015D61006F0BA7 /* MedalsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C886BF12E015D61006F0BA7 /* MedalsModel.m */; };
|
||||
4C9828132E6EB50000FC6345 /* MicCpInfoModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C9828122E6EB50000FC6345 /* MicCpInfoModel.m */; };
|
||||
4CA532B42D5AEE9400B8F59F /* Api+LuckyPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CA532B32D5AEE9400B8F59F /* Api+LuckyPackage.m */; };
|
||||
4CA532B72D5B333200B8F59F /* RoomLuckyPackageInfoModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CA532B62D5B333200B8F59F /* RoomLuckyPackageInfoModel.m */; };
|
||||
4CA532BA2D5C8EBE00B8F59F /* LuckyPackageBannerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CA532B92D5C8EBE00B8F59F /* LuckyPackageBannerView.m */; };
|
||||
@@ -562,6 +563,12 @@
|
||||
4CA532C32D5F37DC00B8F59F /* LuckyPackageMessageTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CA532C22D5F37DC00B8F59F /* LuckyPackageMessageTableViewCell.m */; };
|
||||
4CA5A3342D93D4AB00CE41D6 /* 大.svga in Resources */ = {isa = PBXBuildFile; fileRef = 4CA5A3332D93D4AB00CE41D6 /* 大.svga */; };
|
||||
4CACCCE42D9A695000CCB135 /* brove_gift.svga in Resources */ = {isa = PBXBuildFile; fileRef = 4CACCCE32D9A695000CCB135 /* brove_gift.svga */; };
|
||||
4CAE69C52E69922B00A9FC35 /* mic_cp_lv1.svga in Resources */ = {isa = PBXBuildFile; fileRef = 4CAE69C02E69922B00A9FC35 /* mic_cp_lv1.svga */; };
|
||||
4CAE69C62E69922B00A9FC35 /* mic_cp_lv2.svga in Resources */ = {isa = PBXBuildFile; fileRef = 4CAE69C12E69922B00A9FC35 /* mic_cp_lv2.svga */; };
|
||||
4CAE69C72E69922B00A9FC35 /* mic_cp_lv4.svga in Resources */ = {isa = PBXBuildFile; fileRef = 4CAE69C32E69922B00A9FC35 /* mic_cp_lv4.svga */; };
|
||||
4CAE69C82E69922B00A9FC35 /* mic_cp_lv5.svga in Resources */ = {isa = PBXBuildFile; fileRef = 4CAE69C42E69922B00A9FC35 /* mic_cp_lv5.svga */; };
|
||||
4CAE69C92E69922B00A9FC35 /* mic_cp_lv3.svga in Resources */ = {isa = PBXBuildFile; fileRef = 4CAE69C22E69922B00A9FC35 /* mic_cp_lv3.svga */; };
|
||||
4CAE69CC2E69A2DB00A9FC35 /* MicMidpointRectManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CAE69CB2E69A2DB00A9FC35 /* MicMidpointRectManager.m */; };
|
||||
4CAFEFF62DD2F21B00CD81DF /* CreateEventPickerContainerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CAFEFF52DD2F21B00CD81DF /* CreateEventPickerContainerView.m */; };
|
||||
4CAFF00A2DD342A400CD81DF /* MessagePublicEventModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CAFF0092DD342A400CD81DF /* MessagePublicEventModel.m */; };
|
||||
4CAFF00D2DD343B200CD81DF /* PublicEventTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CAFF00C2DD343B200CD81DF /* PublicEventTableViewCell.m */; };
|
||||
@@ -2777,6 +2784,8 @@
|
||||
4C886BED2E014B6C006F0BA7 /* Api+Medals.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "Api+Medals.m"; sourceTree = "<group>"; };
|
||||
4C886BF02E015D61006F0BA7 /* MedalsModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MedalsModel.h; sourceTree = "<group>"; };
|
||||
4C886BF12E015D61006F0BA7 /* MedalsModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MedalsModel.m; sourceTree = "<group>"; };
|
||||
4C9828112E6EB50000FC6345 /* MicCpInfoModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MicCpInfoModel.h; sourceTree = "<group>"; };
|
||||
4C9828122E6EB50000FC6345 /* MicCpInfoModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MicCpInfoModel.m; sourceTree = "<group>"; };
|
||||
4CA532B22D5AEE9400B8F59F /* Api+LuckyPackage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Api+LuckyPackage.h"; sourceTree = "<group>"; };
|
||||
4CA532B32D5AEE9400B8F59F /* Api+LuckyPackage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "Api+LuckyPackage.m"; sourceTree = "<group>"; };
|
||||
4CA532B52D5B333200B8F59F /* RoomLuckyPackageInfoModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoomLuckyPackageInfoModel.h; sourceTree = "<group>"; };
|
||||
@@ -2791,6 +2800,13 @@
|
||||
4CA532C22D5F37DC00B8F59F /* LuckyPackageMessageTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LuckyPackageMessageTableViewCell.m; sourceTree = "<group>"; };
|
||||
4CA5A3332D93D4AB00CE41D6 /* 大.svga */ = {isa = PBXFileReference; lastKnownFileType = file; path = "大.svga"; sourceTree = "<group>"; };
|
||||
4CACCCE32D9A695000CCB135 /* brove_gift.svga */ = {isa = PBXFileReference; lastKnownFileType = file; path = brove_gift.svga; sourceTree = "<group>"; };
|
||||
4CAE69C02E69922B00A9FC35 /* mic_cp_lv1.svga */ = {isa = PBXFileReference; lastKnownFileType = file; path = mic_cp_lv1.svga; sourceTree = "<group>"; };
|
||||
4CAE69C12E69922B00A9FC35 /* mic_cp_lv2.svga */ = {isa = PBXFileReference; lastKnownFileType = file; path = mic_cp_lv2.svga; sourceTree = "<group>"; };
|
||||
4CAE69C22E69922B00A9FC35 /* mic_cp_lv3.svga */ = {isa = PBXFileReference; lastKnownFileType = file; path = mic_cp_lv3.svga; sourceTree = "<group>"; };
|
||||
4CAE69C32E69922B00A9FC35 /* mic_cp_lv4.svga */ = {isa = PBXFileReference; lastKnownFileType = file; path = mic_cp_lv4.svga; sourceTree = "<group>"; };
|
||||
4CAE69C42E69922B00A9FC35 /* mic_cp_lv5.svga */ = {isa = PBXFileReference; lastKnownFileType = file; path = mic_cp_lv5.svga; sourceTree = "<group>"; };
|
||||
4CAE69CA2E69A2DB00A9FC35 /* MicMidpointRectManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MicMidpointRectManager.h; sourceTree = "<group>"; };
|
||||
4CAE69CB2E69A2DB00A9FC35 /* MicMidpointRectManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MicMidpointRectManager.m; sourceTree = "<group>"; };
|
||||
4CAFEFF42DD2F21B00CD81DF /* CreateEventPickerContainerView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CreateEventPickerContainerView.h; sourceTree = "<group>"; };
|
||||
4CAFEFF52DD2F21B00CD81DF /* CreateEventPickerContainerView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CreateEventPickerContainerView.m; sourceTree = "<group>"; };
|
||||
4CAFF0082DD342A400CD81DF /* MessagePublicEventModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MessagePublicEventModel.h; sourceTree = "<group>"; };
|
||||
@@ -7110,6 +7126,11 @@
|
||||
54FFD37D2C9BD12600DE61E5 /* 3.svga */,
|
||||
54FFD37E2C9BD12600DE61E5 /* 4.svga */,
|
||||
54FFD37F2C9BD12600DE61E5 /* 5.svga */,
|
||||
4CAE69C02E69922B00A9FC35 /* mic_cp_lv1.svga */,
|
||||
4CAE69C12E69922B00A9FC35 /* mic_cp_lv2.svga */,
|
||||
4CAE69C22E69922B00A9FC35 /* mic_cp_lv3.svga */,
|
||||
4CAE69C32E69922B00A9FC35 /* mic_cp_lv4.svga */,
|
||||
4CAE69C42E69922B00A9FC35 /* mic_cp_lv5.svga */,
|
||||
);
|
||||
path = cp;
|
||||
sourceTree = "<group>";
|
||||
@@ -7905,6 +7926,8 @@
|
||||
4C75CEFA2D6318FF009147A5 /* RoomEnterModel.m */,
|
||||
4CFBE0C82DAD085700A923AF /* BravoGiftTabInfomationModel.h */,
|
||||
4CFBE0C92DAD085700A923AF /* BravoGiftTabInfomationModel.m */,
|
||||
4C9828112E6EB50000FC6345 /* MicCpInfoModel.h */,
|
||||
4C9828122E6EB50000FC6345 /* MicCpInfoModel.m */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
@@ -10904,6 +10927,8 @@
|
||||
E8AEAEEA27141ACC0017FCE0 /* StageView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4CAE69CA2E69A2DB00A9FC35 /* MicMidpointRectManager.h */,
|
||||
4CAE69CB2E69A2DB00A9FC35 /* MicMidpointRectManager.m */,
|
||||
E874B88527215CFF003954B9 /* Model */,
|
||||
E8680707271959090024F48F /* MicroView */,
|
||||
181D7F192726CE2A00B7C059 /* StageView.h */,
|
||||
@@ -11771,6 +11796,11 @@
|
||||
E80EC82628ACD84000D133C5 /* emoji_10@2x.png in Resources */,
|
||||
E80EC82C28ACD84000D133C5 /* emoji_59@2x.png in Resources */,
|
||||
E80EC81A28ACD84000D133C5 /* emoji_161@2x.png in Resources */,
|
||||
4CAE69C52E69922B00A9FC35 /* mic_cp_lv1.svga in Resources */,
|
||||
4CAE69C62E69922B00A9FC35 /* mic_cp_lv2.svga in Resources */,
|
||||
4CAE69C72E69922B00A9FC35 /* mic_cp_lv4.svga in Resources */,
|
||||
4CAE69C82E69922B00A9FC35 /* mic_cp_lv5.svga in Resources */,
|
||||
4CAE69C92E69922B00A9FC35 /* mic_cp_lv3.svga in Resources */,
|
||||
E80EC85228ACD84000D133C5 /* emoji_114@2x.png in Resources */,
|
||||
E80EC81F28ACD84000D133C5 /* emoji_45@2x.png in Resources */,
|
||||
E80EC85A28ACD84000D133C5 /* emoji_14@2x.png in Resources */,
|
||||
@@ -12713,6 +12743,7 @@
|
||||
E82325F2274E2DE6003A3332 /* XPUserCardViewController.m in Sources */,
|
||||
4CA532B72D5B333200B8F59F /* RoomLuckyPackageInfoModel.m in Sources */,
|
||||
E85E7B512A4EB0D300B6D00A /* Api+Guild.m in Sources */,
|
||||
4C9828132E6EB50000FC6345 /* MicCpInfoModel.m in Sources */,
|
||||
E83645682A40A2DC00E0DBE4 /* XPSkillCardPlayerManager.m in Sources */,
|
||||
E8F65C222869A36F009BB5B9 /* ContentShareMonentsModel.m in Sources */,
|
||||
9B6E856E281AABAB0041A321 /* XPRoomRecommendModel.m in Sources */,
|
||||
@@ -13233,6 +13264,7 @@
|
||||
E878893F273A54F500BF1D57 /* XPGiftPresenter.m in Sources */,
|
||||
1464C5F929A4D00000AF7C94 /* XPIAPRechargeHeaderView.m in Sources */,
|
||||
E83645A82A40AF5400E0DBE4 /* NSBundle+Localizable.m in Sources */,
|
||||
4CAE69CC2E69A2DB00A9FC35 /* MicMidpointRectManager.m in Sources */,
|
||||
23E9EA7F2A839B2F00B792F2 /* MessageTreasureFairyModel.m in Sources */,
|
||||
E8778AFB2989034200CF139B /* XPSessionSayHelloEmptyTableViewCell.m in Sources */,
|
||||
238B37B02AC55A2C00BFC9D5 /* XPTreasureFairyStoreResultCell.m in Sources */,
|
||||
|
@@ -160,9 +160,6 @@ UIKIT_EXTERN NSString * adImageName;
|
||||
info.identifier = emotionDic[@"id"];
|
||||
info.image = image;
|
||||
|
||||
// 添加调试日志
|
||||
NSLog(@"加载表情: %@, 图片: %@, 是否成功: %@", info.displayName, emotionDic[@"file"], image ? @"是" : @"否");
|
||||
|
||||
[array addObject:info];
|
||||
}
|
||||
//在这里强烈建议先预加载一下表情
|
||||
@@ -171,16 +168,7 @@ UIKIT_EXTERN NSString * adImageName;
|
||||
|
||||
// 清理 emoji 缓存,确保新的尺寸设置生效
|
||||
[QEmotionHelper clearEmojiCache];
|
||||
|
||||
#if DEBUG
|
||||
// 测试图片加载
|
||||
NSLog(@"表情数组加载完成,总数: %lu", (unsigned long)array.count);
|
||||
for (int i = 0; i < MIN(array.count, 3); i++) {
|
||||
QEmotion *emotion = array[i];
|
||||
NSLog(@"测试表情 %d: %@, 图片: %@", i, emotion.displayName, emotion.image ? @"加载成功" : @"加载失败");
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark - 广告
|
||||
|
21
YuMi/Assets.xcassets/20.20.62/turbo_mode_tips.imageset/Contents.json
vendored
Normal file
21
YuMi/Assets.xcassets/20.20.62/turbo_mode_tips.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "蒙版组 1@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
YuMi/Assets.xcassets/20.20.62/turbo_mode_tips.imageset/蒙版组 1@3x.png
vendored
Normal file
BIN
YuMi/Assets.xcassets/20.20.62/turbo_mode_tips.imageset/蒙版组 1@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 159 KiB |
@@ -110,6 +110,12 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (void)startLagDetection;
|
||||
|
||||
/**
|
||||
* 模拟卡顿检测(测试用)
|
||||
* 用于测试按钮,直接增加计数
|
||||
*/
|
||||
- (void)simulateLagDetection;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -8,12 +8,18 @@
|
||||
|
||||
#import "BuglyManager.h"
|
||||
#import <Bugly/Bugly.h>
|
||||
#import "TurboModeStateManager.h"
|
||||
|
||||
@interface BuglyManager () <BuglyDelegate>
|
||||
|
||||
@property (nonatomic, strong) NSString *appId;
|
||||
@property (nonatomic, assign) BOOL isConfigured;
|
||||
|
||||
// 卡顿计数相关
|
||||
@property (nonatomic, assign) NSInteger lagCount;
|
||||
@property (nonatomic, assign) BOOL isInRoom;
|
||||
@property (nonatomic, assign) BOOL isTurboModeEnabled;
|
||||
|
||||
@end
|
||||
|
||||
@implementation BuglyManager
|
||||
@@ -33,6 +39,15 @@
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_isConfigured = NO;
|
||||
_lagCount = 0;
|
||||
_isInRoom = NO;
|
||||
_isTurboModeEnabled = NO;
|
||||
|
||||
// 监听 turbo mode 状态变化
|
||||
[self setupTurboModeNotifications];
|
||||
|
||||
// 🔧 修复:初始化时获取当前 turbo mode 状态
|
||||
[self updateTurboModeState];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -212,6 +227,15 @@
|
||||
// Bugly 会自动进行卡顿检测,这里主要是日志记录
|
||||
}
|
||||
|
||||
#pragma mark - 测试方法
|
||||
|
||||
- (void)simulateLagDetection {
|
||||
NSLog(@"[BuglyManager] 🧪 模拟卡顿检测(测试按钮触发)");
|
||||
|
||||
// 模拟卡顿检测,触发计数逻辑
|
||||
[self handleLagCountWithDuration:3.0 stackTrace:@"模拟卡顿 - 测试按钮触发"];
|
||||
}
|
||||
|
||||
#pragma mark - Private Methods
|
||||
|
||||
- (void)handleLagDetection:(NSString *)stackTrace {
|
||||
@@ -227,13 +251,8 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 🔧 新增:发送卡顿检测通知,用于在房间中显示 Turbo Mode Tips
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"BuglyManagerDidDetectLag"
|
||||
object:nil
|
||||
userInfo:@{@"duration": @(duration),
|
||||
@"stackTrace": stackTrace ?: @""}];
|
||||
});
|
||||
// 🔧 新增:卡顿计数逻辑
|
||||
[self handleLagCountWithDuration:duration stackTrace:stackTrace];
|
||||
|
||||
// TODO: 触发卡顿通知逻辑
|
||||
// 1. 记录卡顿信息到本地日志
|
||||
@@ -242,12 +261,135 @@
|
||||
// 4. 触发用户反馈机制
|
||||
}
|
||||
|
||||
#pragma mark - 卡顿计数逻辑
|
||||
|
||||
- (void)handleLagCountWithDuration:(NSTimeInterval)duration stackTrace:(NSString *)stackTrace {
|
||||
// 规则2:当 turbo mode 开启时,不计数
|
||||
if (self.isTurboModeEnabled) {
|
||||
NSLog(@"[BuglyManager] 🎮 Turbo Mode 已开启,跳过卡顿计数");
|
||||
return;
|
||||
}
|
||||
|
||||
// 规则1:只有在房间内才计数
|
||||
if (!self.isInRoom) {
|
||||
NSLog(@"[BuglyManager] 🏠 不在房间内,跳过卡顿计数");
|
||||
return;
|
||||
}
|
||||
|
||||
// 增加计数
|
||||
self.lagCount++;
|
||||
NSLog(@"[BuglyManager] 📊 卡顿计数: %ld/3", (long)self.lagCount);
|
||||
|
||||
// 检查是否达到3次
|
||||
if (self.lagCount >= 3) {
|
||||
NSLog(@"[BuglyManager] 🚨 累计卡顿3次,触发 Turbo Mode Tips");
|
||||
|
||||
// 发送通知给 Tips Manager
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"BuglyManagerDidDetectLag"
|
||||
object:nil
|
||||
userInfo:@{@"duration": @(duration),
|
||||
@"stackTrace": stackTrace ?: @"",
|
||||
@"lagCount": @(self.lagCount),
|
||||
@"shouldShowTips": @YES}];
|
||||
});
|
||||
|
||||
// 重置计数
|
||||
self.lagCount = 0;
|
||||
} else {
|
||||
// 未达到3次,只发送计数通知
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"BuglyManagerDidDetectLag"
|
||||
object:nil
|
||||
userInfo:@{@"duration": @(duration),
|
||||
@"stackTrace": stackTrace ?: @"",
|
||||
@"lagCount": @(self.lagCount),
|
||||
@"shouldShowTips": @NO}];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupTurboModeNotifications {
|
||||
// 监听 turbo mode 状态变化
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(turboModeStateChanged:)
|
||||
name:@"TurboModeStateChanged"
|
||||
object:nil];
|
||||
|
||||
// 监听房间进入/退出
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(roomDidEnter:)
|
||||
name:@"RoomDidEnter"
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(roomDidExit:)
|
||||
name:@"RoomDidExit"
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)turboModeStateChanged:(NSNotification *)notification {
|
||||
NSNumber *enabled = notification.userInfo[@"enabled"];
|
||||
if (enabled) {
|
||||
self.isTurboModeEnabled = [enabled boolValue];
|
||||
NSLog(@"[BuglyManager] 🎮 Turbo Mode 状态变化: %@", enabled.boolValue ? @"开启" : @"关闭");
|
||||
|
||||
// 规则2:当 turbo mode 开启时,计数复位为0
|
||||
if (enabled.boolValue) {
|
||||
self.lagCount = 0;
|
||||
NSLog(@"[BuglyManager] 🔄 Turbo Mode 开启,卡顿计数复位为0");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)roomDidEnter:(NSNotification *)notification {
|
||||
self.isInRoom = YES;
|
||||
|
||||
// 🔧 修复:进入房间时主动获取当前 turbo mode 状态
|
||||
[self updateTurboModeState];
|
||||
|
||||
NSLog(@"[BuglyManager] 🏠 用户进入房间,开始卡顿监控 - Turbo Mode: %@",
|
||||
self.isTurboModeEnabled ? @"开启" : @"关闭");
|
||||
}
|
||||
|
||||
- (void)roomDidExit:(NSNotification *)notification {
|
||||
self.isInRoom = NO;
|
||||
// 规则3:当用户退出房间时,计数复位为0
|
||||
self.lagCount = 0;
|
||||
NSLog(@"[BuglyManager] 🚪 用户退出房间,卡顿计数复位为0");
|
||||
}
|
||||
|
||||
#pragma mark - 状态更新方法
|
||||
|
||||
- (void)updateTurboModeState {
|
||||
// 从 TurboModeStateManager 获取当前状态
|
||||
BOOL currentTurboModeState = [[TurboModeStateManager sharedManager] isTurboModeEnabled];
|
||||
|
||||
if (self.isTurboModeEnabled != currentTurboModeState) {
|
||||
self.isTurboModeEnabled = currentTurboModeState;
|
||||
NSLog(@"[BuglyManager] 🔄 主动更新 Turbo Mode 状态: %@",
|
||||
currentTurboModeState ? @"开启" : @"关闭");
|
||||
|
||||
// 如果 turbo mode 开启,计数复位为0
|
||||
if (currentTurboModeState) {
|
||||
self.lagCount = 0;
|
||||
NSLog(@"[BuglyManager] 🔄 Turbo Mode 开启,卡顿计数复位为0");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)getAppChannel {
|
||||
// 这里应该调用项目中的工具方法获取渠道信息
|
||||
// 暂时返回默认值
|
||||
return @"AppStore";
|
||||
}
|
||||
|
||||
#pragma mark - Dealloc
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (NSString *)getIAPStatusMessage:(NSInteger)status {
|
||||
switch (status) {
|
||||
case 0:
|
||||
|
@@ -156,13 +156,19 @@ typedef NS_ENUM(NSUInteger, CustomMessageType) {
|
||||
|
||||
/// 客户端独立收发的消息, 从 1000 开始
|
||||
ClientMessage_Type = 1000,
|
||||
|
||||
MicRelationship_Type = 1001,
|
||||
};
|
||||
|
||||
|
||||
typedef NS_ENUM(NSUInteger, ClientMessageType) {
|
||||
ClientMessage_UpMic_Ask = 1001,
|
||||
ClientMessage_UpMic_Agree = 1002,
|
||||
ClientMessage_UpMic_Reject = 1003,
|
||||
ClientMessage_UpMic_Ask = 10001,
|
||||
ClientMessage_UpMic_Agree = 10002,
|
||||
ClientMessage_UpMic_Reject = 10003,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, MicRelationshipType) {
|
||||
MicRelationship_CP = 10011,
|
||||
};
|
||||
|
||||
|
||||
|
@@ -163,6 +163,9 @@ UIKIT_EXTERN NSString *kRequestTicket;
|
||||
// self.isHavePermission = YES;
|
||||
// return;
|
||||
//#endif
|
||||
|
||||
// TODO: 切换账号可能会保持权限状态,需要进一步检查逻辑
|
||||
|
||||
ClientConfig *config = [ClientConfig shareConfig];
|
||||
NSArray *uidList = config.configInfo.giveDiamondErbanNoList;
|
||||
for (id uid in uidList) {
|
||||
|
@@ -232,6 +232,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
+ (void)shareGen:(HttpRequestHelperCompletion)completion targetUid:(NSString *)targetUid;
|
||||
|
||||
+ (void)getRoomMicCpListByRoomUid:(HttpRequestHelperCompletion)completion roomUid:(NSString *)roomUid;
|
||||
|
||||
+ (void)getRoomMicCpListByUidList:(HttpRequestHelperCompletion)completion uidList:(NSString *)uidList;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -322,4 +322,13 @@
|
||||
+ (void)shareGen:(HttpRequestHelperCompletion)completion targetUid:(NSString *)targetUid {
|
||||
[self makeRequest:@"share/gen" method:HttpRequestHelperMethodPOST completion:completion, __FUNCTION__, targetUid, nil];
|
||||
}
|
||||
|
||||
+ (void)getRoomMicCpListByRoomUid:(HttpRequestHelperCompletion)completion roomUid:(NSString *)roomUid {
|
||||
[self makeRequest:@"room/mic/cp/listByRoomUid" method:HttpRequestHelperMethodGET completion:completion, __FUNCTION__, roomUid, nil];
|
||||
}
|
||||
|
||||
+ (void)getRoomMicCpListByUidList:(HttpRequestHelperCompletion)completion uidList:(NSArray<NSString *> *)uidList {
|
||||
[self makeRequest:@"room/mic/cp/listByUidList" method:HttpRequestHelperMethodGET completion:completion, __FUNCTION__, uidList, nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -133,6 +133,7 @@
|
||||
@property (nonatomic, strong) UILabel *rankName_3;
|
||||
|
||||
@property (nonatomic, strong) UILabel *rankTipsLabel;
|
||||
@property (nonatomic, strong) UILabel *ratioLabel;
|
||||
|
||||
@end
|
||||
|
||||
@@ -438,6 +439,12 @@
|
||||
make.center.mas_equalTo(progressNumberBG);
|
||||
}];
|
||||
|
||||
[progressBG addSubview:self.ratioLabel];
|
||||
[self.ratioLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerX.mas_equalTo(progressBG).offset(2);
|
||||
make.centerY.mas_equalTo(progressBG);
|
||||
}];
|
||||
|
||||
UIImageView *rocketsBG = [self rocketsBG];
|
||||
[self.view addSubview:rocketsBG];
|
||||
[rocketsBG mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
@@ -681,6 +688,18 @@
|
||||
progress = 0.1;
|
||||
}
|
||||
|
||||
// 更新比值 label 并保持发光效果
|
||||
NSString *ratioText = [NSString stringWithFormat:@"%@/%@",
|
||||
@(boom.exp * boom.speed / 100.0).stringValue,
|
||||
@(boom.exp).stringValue];
|
||||
|
||||
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:ratioText];
|
||||
[attributedString addAttribute:NSStrokeColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0, attributedString.length)];
|
||||
[attributedString addAttribute:NSStrokeWidthAttributeName value:@(-2.0) range:NSMakeRange(0, attributedString.length)];
|
||||
[attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor blackColor] range:NSMakeRange(0, attributedString.length)];
|
||||
[attributedString addAttribute:NSFontAttributeName value:kFontMedium(14) range:NSMakeRange(0, attributedString.length)];
|
||||
self.ratioLabel.attributedText = attributedString;
|
||||
|
||||
[UIView animateWithDuration:0.2 animations:^{
|
||||
[self.progressBar mas_updateConstraints:^(MASConstraintMaker *make) {
|
||||
make.height.mas_equalTo(kGetScaleWidth(180) * progress); // 动态调整高度
|
||||
@@ -1120,5 +1139,27 @@
|
||||
return _rankTipsLabel;
|
||||
}
|
||||
|
||||
- (UILabel *)ratioLabel {
|
||||
if (!_ratioLabel) {
|
||||
_ratioLabel = [UILabel labelInitWithText:@"0/0" font:kFontMedium(14) textColor:[UIColor blackColor]];
|
||||
_ratioLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_ratioLabel.transform = CGAffineTransformMakeRotation(M_PI_2); // 顺时针旋转90度
|
||||
|
||||
// 添加发光效果
|
||||
_ratioLabel.layer.shadowColor = [UIColor whiteColor].CGColor;
|
||||
_ratioLabel.layer.shadowOffset = CGSizeZero;
|
||||
_ratioLabel.layer.shadowRadius = 3.0;
|
||||
_ratioLabel.layer.shadowOpacity = 0.8;
|
||||
_ratioLabel.layer.masksToBounds = NO;
|
||||
|
||||
// 添加文字描边效果
|
||||
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"0/0"];
|
||||
[attributedString addAttribute:NSStrokeColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0, attributedString.length)];
|
||||
[attributedString addAttribute:NSStrokeWidthAttributeName value:@(-2.0) range:NSMakeRange(0, attributedString.length)];
|
||||
_ratioLabel.attributedText = attributedString;
|
||||
}
|
||||
return _ratioLabel;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
@@ -385,7 +385,7 @@
|
||||
- (void)handleMessageWithAttachmentAndFirstSecond:(NIMMessage *)message {
|
||||
// 只有用户在房间时,才会转发
|
||||
if (![XPSkillCardPlayerManager shareInstance].isInRoom) {
|
||||
NSLog(@"PublicRoomManager: 用户未在房间中,跳过消息转发");
|
||||
// NSLog(@"PublicRoomManager: 用户未在房间中,跳过消息转发");
|
||||
return;
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"MessageFromPublicRoomWithAttachmentNotification"
|
||||
|
20
YuMi/Modules/YMRoom/Model/MicCpInfoModel.h
Normal file
20
YuMi/Modules/YMRoom/Model/MicCpInfoModel.h
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// MicCpInfoModel.h
|
||||
// YuMi
|
||||
//
|
||||
// Created by P on 2025/9/8.
|
||||
//
|
||||
|
||||
#import "PIBaseModel.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface MicCpInfoModel : PIBaseModel
|
||||
|
||||
@property (nonatomic, assign) NSInteger uid;
|
||||
@property (nonatomic, assign) NSInteger cpLevel;
|
||||
@property (nonatomic, assign) NSInteger loverUid;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
12
YuMi/Modules/YMRoom/Model/MicCpInfoModel.m
Normal file
12
YuMi/Modules/YMRoom/Model/MicCpInfoModel.m
Normal file
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// MicCpInfoModel.m
|
||||
// YuMi
|
||||
//
|
||||
// Created by P on 2025/9/8.
|
||||
//
|
||||
|
||||
#import "MicCpInfoModel.h"
|
||||
|
||||
@implementation MicCpInfoModel
|
||||
|
||||
@end
|
@@ -82,6 +82,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (void)getShareLink:(NSString *)roomUid success:(void(^)(NSString *link))success failure:(void(^)(NSError *error))failure;
|
||||
|
||||
- (void)micCpListByRoomUid:(NSString *)roomUid;
|
||||
|
||||
- (void)micCpListByUidList:(NSArray<NSString *> *)uidList;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#import "XPRedPacketModel.h"
|
||||
#import "XPFreeGiftModel.h"
|
||||
#import "BoomInfoModel.h"
|
||||
#import "MicCpInfoModel.h"
|
||||
///P
|
||||
#import "XPRoomProtocol.h"
|
||||
|
||||
@@ -343,4 +344,29 @@
|
||||
} showLoading:YES errorToast:YES] targetUid:roomUid];
|
||||
}
|
||||
|
||||
- (void)micCpListByRoomUid:(NSString *)roomUid {
|
||||
@kWeakify(self);
|
||||
[Api getRoomMicCpListByRoomUid:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
@kStrongify(self);
|
||||
if (code == 200) {
|
||||
if ([[self getView] respondsToSelector:@selector(getMicCpListByRoomUidSuccess:)]) {
|
||||
NSArray *cpList = [MicCpInfoModel modelsWithArray:data.data];
|
||||
[[self getView] getMicCpListByRoomUidSuccess:cpList];
|
||||
}
|
||||
}
|
||||
} roomUid:roomUid];
|
||||
}
|
||||
|
||||
- (void)micCpListByUidList:(NSArray<NSString *> *)uidList {
|
||||
NSString *uidsString = [uidList componentsJoinedByString:@","];
|
||||
[Api getRoomMicCpListByUidList:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
|
||||
if (code == 200) {
|
||||
if ([[self getView] respondsToSelector:@selector(getMicCpListByUidListSuccess:)]) {
|
||||
NSArray *cpList = [MicCpInfoModel modelsWithArray:data.data];
|
||||
[[self getView] getMicCpListByUidListSuccess:cpList];
|
||||
}
|
||||
}
|
||||
} uidList:uidsString];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -10,7 +10,7 @@
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
@class RoomInfoModel, UserInfoModel, NIMChatroom, FirstChargeRoomWindowModel, XPRedPacketModel, BoomInfoModel, BoomDetailModel;
|
||||
@class RoomInfoModel, UserInfoModel, NIMChatroom, FirstChargeRoomWindowModel, XPRedPacketModel, BoomInfoModel, BoomDetailModel, MicCpInfoModel;
|
||||
|
||||
@protocol XPRoomProtocol <NSObject>
|
||||
|
||||
@@ -42,7 +42,13 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
-(void)getKickUserListSuccessWithList:(NSArray *)list;
|
||||
|
||||
- (void)getRoomBoomInfoSuccess:(NSArray <BoomDetailModel*> *)models;
|
||||
|
||||
- (void)getRoomBoomExplosionSuccess:(BoomInfoModel *)model;
|
||||
|
||||
- (void)getMicCpListByRoomUidSuccess:(NSArray <MicCpInfoModel *> *)cpList;
|
||||
|
||||
- (void)getMicCpListByUidListSuccess:(NSArray <MicCpInfoModel *> *)cpList;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -76,6 +76,7 @@
|
||||
#import "XPRoomFunctionContainerView.h"
|
||||
#import "XPRoomRankEntranceView.h"
|
||||
#import "XPRoomAnchorRankEnterView.h"
|
||||
#import "SocialStageView.h"
|
||||
#import "MSRoomOnLineView.h"
|
||||
#import "XPTurboModeConstants.h"
|
||||
#import "../MoreView/Manager/TurboModeStateManager.h"
|
||||
@@ -3874,6 +3875,57 @@ BannerSchedulerDelegate
|
||||
self.savedTapPoint = CGPointZero;
|
||||
}
|
||||
|
||||
/// 调试方法:在界面上绘制所有符合条件(横向相邻,1-4 或 5-8)的麦位中点 75x75 半透明矩形
|
||||
- (void)debug_drawMicMidpointRects {
|
||||
// 移除旧的调试矩形
|
||||
NSArray<UIView *> *subviews = [self.topContainer.subviews copy];
|
||||
for (UIView *view in subviews) {
|
||||
if (view.tag == 56001) {
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
// 在宿主视图层级中查找 SocialStageView
|
||||
SocialStageView *stageView = nil;
|
||||
for (UIView *obj in self.hostDelegate.getSuperView.subviews) {
|
||||
if ([obj isKindOfClass:[SocialStageView class]]) {
|
||||
stageView = (SocialStageView *)obj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!stageView) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 定义所有横向相邻的有效麦位对
|
||||
NSArray<NSArray<NSNumber *> *> *pairs = @[
|
||||
@[@1, @2], @[@2, @3], @[@3, @4],
|
||||
@[@5, @6], @[@6, @7], @[@7, @8]
|
||||
];
|
||||
|
||||
for (NSArray<NSNumber *> *pair in pairs) {
|
||||
NSInteger left = pair.firstObject.integerValue;
|
||||
NSInteger right = pair.lastObject.integerValue;
|
||||
|
||||
CGRect stageRect = [stageView rectForMidpointBetweenMicAtIndex:left andIndex:right];
|
||||
if (CGRectIsEmpty(stageRect)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 转换到当前视图坐标系
|
||||
CGRect rectInSelf = [self convertRect:stageRect fromView:stageView];
|
||||
|
||||
UIView *overlay = [[UIView alloc] initWithFrame:rectInSelf];
|
||||
overlay.backgroundColor = [[UIColor redColor] colorWithAlphaComponent:0.25];
|
||||
overlay.layer.borderColor = [UIColor.redColor colorWithAlphaComponent:0.6].CGColor;
|
||||
overlay.layer.borderWidth = 1.0;
|
||||
overlay.layer.cornerRadius = 8.0;
|
||||
overlay.clipsToBounds = YES;
|
||||
overlay.tag = 56001;
|
||||
[self.topContainer addSubview:overlay];
|
||||
}
|
||||
}
|
||||
|
||||
- (CGRect)calculateInteractiveBoundsForView:(UIView *)view {
|
||||
// 计算视图的可交互区域
|
||||
// 这里可以根据具体的 Banner 类型来调整可交互区域
|
||||
@@ -4055,14 +4107,14 @@ BannerSchedulerDelegate
|
||||
#pragma mark - BannerSchedulerDelegate
|
||||
|
||||
- (void)bannerScheduler:(BannerScheduler *)scheduler shouldPlayBanner:(id)banner {
|
||||
NSLog(@"🎯 BannerSchedulerDelegate: 收到播放 Banner 请求");
|
||||
NSLog(@"🎯 Banner 类型: %@", [banner class]);
|
||||
// NSLog(@"🎯 BannerSchedulerDelegate: 收到播放 Banner 请求");
|
||||
// NSLog(@"🎯 Banner 类型: %@", [banner class]);
|
||||
|
||||
// 将 Banner 数据转换为 AttachmentModel 并播放
|
||||
if ([banner isKindOfClass:[AttachmentModel class]]) {
|
||||
AttachmentModel *attachment = (AttachmentModel *)banner;
|
||||
NSLog(@"🎯 AttachmentModel 类型: %ld", (long)attachment.second);
|
||||
NSLog(@"🎯 AttachmentModel 数据: %@", attachment.data);
|
||||
// NSLog(@"🎯 AttachmentModel 类型: %ld", (long)attachment.second);
|
||||
// NSLog(@"🎯 AttachmentModel 数据: %@", attachment.data);
|
||||
[self _playBannerWithAttachment:attachment];
|
||||
} else {
|
||||
NSLog(@"⚠️ BannerSchedulerDelegate: Banner 不是 AttachmentModel 类型");
|
||||
|
@@ -296,8 +296,6 @@ done:
|
||||
self.chatFaceTabList = [ChatFaceResponse modelsWithArray:data];
|
||||
NSString *cachePath = [self getFaceCachePath];
|
||||
|
||||
NSLog(@"-------------- 表情缓存路径: %@", cachePath);
|
||||
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSMutableDictionary *cachedUrls = [[defaults objectForKey:@"SVGACachedUrls"] mutableCopy] ?: [NSMutableDictionary dictionary];
|
||||
|
||||
@@ -310,7 +308,6 @@ done:
|
||||
// 检查URL是否已缓存且文件存在
|
||||
if ([cachedUrls[faceVo.faceUrl] isEqualToString:fileName] &&
|
||||
[[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
|
||||
NSLog(@"已有 SVGA文件缓存:%@", faceVo.faceUrl);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -320,7 +317,6 @@ done:
|
||||
cachedUrls[faceVo.faceUrl] = fileName;
|
||||
[defaults setObject:cachedUrls forKey:@"SVGACachedUrls"];
|
||||
[defaults synchronize];
|
||||
NSLog(@"SVGA文件缓存成功:%@ - %@", faceVo.faceUrl, filePath);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
@@ -43,6 +43,11 @@
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
|
||||
NSLog(@"🎮 TurboModeStateManager: 全局 turbo mode 设置为 %@", enabled ? @"开启" : @"关闭");
|
||||
|
||||
// 发送状态变化通知
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"TurboModeStateChanged"
|
||||
object:nil
|
||||
userInfo:@{@"enabled": @(enabled)}];
|
||||
}
|
||||
|
||||
- (BOOL)isTurboModeEnabled {
|
||||
|
@@ -24,6 +24,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// 手动显示 Tips(用于测试)
|
||||
- (void)showTipsManually;
|
||||
|
||||
// 直接开启 Turbo Mode(用于 Tips View 显示时)
|
||||
- (void)enableTurboModeDirectly;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -8,12 +8,14 @@
|
||||
|
||||
#import "XPTurboModeTipsManager.h"
|
||||
#import "XPTurboModeTipsView.h"
|
||||
#import "TurboModeStateManager.h"
|
||||
|
||||
@interface XPTurboModeTipsManager ()
|
||||
|
||||
@property (nonatomic, strong) XPTurboModeTipsView *currentTipsView;
|
||||
@property (nonatomic, assign) BOOL isMonitoring;
|
||||
@property (nonatomic, assign) BOOL hasShownTipsInCurrentSession;
|
||||
@property (nonatomic, strong) NSString *currentRoomId; // 🔧 新增:保存当前房间ID
|
||||
//@property (nonatomic, assign) BOOL hasShownTipsInCurrentSession;
|
||||
|
||||
@end
|
||||
|
||||
@@ -34,7 +36,6 @@
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_isMonitoring = NO;
|
||||
_hasShownTipsInCurrentSession = NO;
|
||||
[self setupNotifications];
|
||||
}
|
||||
return self;
|
||||
@@ -49,7 +50,6 @@
|
||||
}
|
||||
|
||||
self.isMonitoring = YES;
|
||||
self.hasShownTipsInCurrentSession = NO;
|
||||
NSLog(@"🎮 XPTurboModeTipsManager 开始在房间中监听卡顿");
|
||||
}
|
||||
|
||||
@@ -75,6 +75,12 @@
|
||||
name:@"BuglyManagerDidDetectLag"
|
||||
object:nil];
|
||||
|
||||
// 🔧 新增:监听房间进入通知,保存房间ID
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleRoomEnter:)
|
||||
name:@"RoomDidEnter"
|
||||
object:nil];
|
||||
|
||||
// 监听房间退出通知
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleRoomExit:)
|
||||
@@ -88,21 +94,36 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.hasShownTipsInCurrentSession) {
|
||||
NSLog(@"🎮 XPTurboModeTipsManager 当前会话已显示过 Tips,跳过");
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *userInfo = notification.userInfo;
|
||||
NSTimeInterval duration = [userInfo[@"duration"] doubleValue];
|
||||
NSString *stackTrace = userInfo[@"stackTrace"];
|
||||
NSNumber *shouldShowTips = userInfo[@"shouldShowTips"];
|
||||
NSNumber *lagCount = userInfo[@"lagCount"];
|
||||
|
||||
NSLog(@"🎮 XPTurboModeTipsManager 收到卡顿通知 - 持续时间: %.2f秒", duration);
|
||||
NSLog(@"🎮 XPTurboModeTipsManager 收到卡顿通知 - 持续时间: %.2f秒, 计数: %@/3", duration, lagCount);
|
||||
|
||||
// 延迟显示 Tips,避免在卡顿期间立即显示
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self showTipsWithReason:[NSString stringWithFormat:@"检测到卡顿 (%.1fs)", duration]];
|
||||
});
|
||||
// 检查是否应该显示 Tips(基于新的计数逻辑)
|
||||
if (shouldShowTips && [shouldShowTips boolValue]) {
|
||||
NSLog(@"🎮 累计卡顿 %@ 次,显示 Turbo Mode Tips", lagCount);
|
||||
|
||||
// 延迟显示 Tips,避免在卡顿期间立即显示
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self showTipsWithReason:[NSString stringWithFormat:@"累计卡顿%@次", lagCount]];
|
||||
});
|
||||
} else {
|
||||
NSLog(@"🎮 卡顿计数: %@/3,暂不显示 Tips", lagCount);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleRoomEnter:(NSNotification *)notification {
|
||||
// 保存当前房间ID
|
||||
NSString *roomId = notification.userInfo[@"roomId"];
|
||||
if (roomId && ![roomId isEqualToString:@"unknown"]) {
|
||||
self.currentRoomId = roomId;
|
||||
NSLog(@"🎮 XPTurboModeTipsManager 保存当前房间ID: %@", roomId);
|
||||
} else {
|
||||
NSLog(@"🎮 XPTurboModeTipsManager 收到无效的房间ID: %@", roomId);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleRoomExit:(NSNotification *)notification {
|
||||
@@ -141,9 +162,6 @@
|
||||
self.currentTipsView = [XPTurboModeTipsView showInView:window];
|
||||
self.currentTipsView.delegate = self;
|
||||
|
||||
// 标记已显示
|
||||
self.hasShownTipsInCurrentSession = YES;
|
||||
|
||||
NSLog(@"🎮 Tips 弹窗创建成功,已添加到窗口");
|
||||
}
|
||||
|
||||
@@ -159,6 +177,74 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)turboModeTipsViewDidEnableTurboMode {
|
||||
NSLog(@"🎮 XPTurboModeTipsManager 用户确认开启 Turbo Mode");
|
||||
|
||||
// 开启全局 turbo mode
|
||||
[[TurboModeStateManager sharedManager] setTurboModeEnabled:YES];
|
||||
|
||||
// 获取当前房间ID(如果有的话)
|
||||
NSString *currentRoomId = [self getCurrentRoomId];
|
||||
if (currentRoomId) {
|
||||
// 应用 turbo mode 到当前房间的开关
|
||||
[[TurboModeStateManager sharedManager] applyTurboModeToSwitchesForRoom:currentRoomId];
|
||||
NSLog(@"🎮 已为房间 %@ 应用 Turbo Mode 设置", currentRoomId);
|
||||
} else {
|
||||
NSLog(@"🎮 无法获取当前房间ID,仅设置全局 Turbo Mode 状态");
|
||||
}
|
||||
|
||||
// 显示成功提示
|
||||
[self showTurboModeEnabledToast];
|
||||
}
|
||||
|
||||
#pragma mark - 直接开启 Turbo Mode
|
||||
|
||||
- (void)enableTurboModeDirectly {
|
||||
NSLog(@"🎮 XPTurboModeTipsManager 直接开启 Turbo Mode");
|
||||
|
||||
// 开启全局 turbo mode
|
||||
[[TurboModeStateManager sharedManager] setTurboModeEnabled:YES];
|
||||
|
||||
// 获取当前房间ID(如果有的话)
|
||||
NSString *currentRoomId = [self getCurrentRoomId];
|
||||
if (currentRoomId) {
|
||||
// 应用 turbo mode 到当前房间的开关
|
||||
[[TurboModeStateManager sharedManager] applyTurboModeToSwitchesForRoom:currentRoomId];
|
||||
NSLog(@"🎮 已为房间 %@ 应用 Turbo Mode 设置", currentRoomId);
|
||||
} else {
|
||||
NSLog(@"🎮 无法获取当前房间ID,仅设置全局 Turbo Mode 状态");
|
||||
}
|
||||
|
||||
// 显示成功提示
|
||||
[self showTurboModeEnabledToast];
|
||||
}
|
||||
|
||||
#pragma mark - Helper Methods
|
||||
|
||||
- (NSString *)getCurrentRoomId {
|
||||
// 🔧 修复:返回保存的当前房间ID
|
||||
if (self.currentRoomId) {
|
||||
NSLog(@"🎮 获取当前房间ID: %@", self.currentRoomId);
|
||||
return self.currentRoomId;
|
||||
} else {
|
||||
NSLog(@"🎮 当前房间ID为空,请检查房间进入逻辑");
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showTurboModeEnabledToast {
|
||||
// 显示 Turbo Mode 已开启的提示
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// 这里可以使用项目中的 Toast 组件
|
||||
// 例如:[[ToastManager sharedManager] showToast:@"Turbo Mode 已开启"];
|
||||
|
||||
NSLog(@"🎮 显示提示:Turbo Mode 已开启");
|
||||
|
||||
// 如果需要显示 UI 提示,可以在这里添加
|
||||
// 例如:使用 UIAlertController 或者自定义 Toast
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Dealloc
|
||||
|
||||
- (void)dealloc {
|
||||
|
@@ -245,6 +245,10 @@
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
[self sendTurboGlobalGiftScreenNotification:isOn];
|
||||
|
||||
// 🔧 新增:显示 toast 提示
|
||||
NSString *toastMessage = isOn ? YMLocalizedString(@"20.20.62_text_17") : YMLocalizedString(@"20.20.62_text_18");
|
||||
[self showToastWithMessage:toastMessage];
|
||||
|
||||
} else if (sender.tag == 2) { // 全局游戏屏幕
|
||||
self.globalGameScreenEnabled = isOn;
|
||||
// 按房间 ID 持久化
|
||||
@@ -252,6 +256,10 @@
|
||||
setBool:isOn forKey:kTurboGlobalGameScreenEnabledKey(self.roomId)];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
[self sendTurboGlobalGameScreenNotification:isOn];
|
||||
|
||||
// 🔧 新增:显示 toast 提示
|
||||
NSString *toastMessage = isOn ? YMLocalizedString(@"20.20.62_text_19") : YMLocalizedString(@"20.20.62_text_20");
|
||||
[self showToastWithMessage:toastMessage];
|
||||
}
|
||||
|
||||
// 通知父控制器更新 Turbo_Mode 按钮状态
|
||||
@@ -333,4 +341,52 @@
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Toast Methods
|
||||
|
||||
- (void)showToastWithMessage:(NSString *)message {
|
||||
// 创建 toast 视图
|
||||
UIView *toastView = [[UIView alloc] init];
|
||||
toastView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.8];
|
||||
toastView.layer.cornerRadius = 8;
|
||||
[self.view addSubview:toastView];
|
||||
|
||||
// 创建文本标签
|
||||
UILabel *messageLabel = [[UILabel alloc] init];
|
||||
messageLabel.text = message;
|
||||
messageLabel.textColor = [UIColor whiteColor];
|
||||
messageLabel.font = [UIFont systemFontOfSize:14];
|
||||
messageLabel.textAlignment = NSTextAlignmentCenter;
|
||||
messageLabel.numberOfLines = 0;
|
||||
[toastView addSubview:messageLabel];
|
||||
|
||||
// 设置约束
|
||||
[toastView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerX.equalTo(self.view);
|
||||
make.top.equalTo(self.view).offset(100);
|
||||
make.leading.greaterThanOrEqualTo(self.view).offset(20);
|
||||
make.trailing.lessThanOrEqualTo(self.view).offset(-20);
|
||||
}];
|
||||
|
||||
[messageLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(toastView).insets(UIEdgeInsetsMake(12, 16, 12, 16));
|
||||
}];
|
||||
|
||||
// 显示动画
|
||||
toastView.alpha = 0;
|
||||
[UIView animateWithDuration:0.3 animations:^{
|
||||
toastView.alpha = 1;
|
||||
}];
|
||||
|
||||
// 自动隐藏
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[UIView animateWithDuration:0.3 animations:^{
|
||||
toastView.alpha = 0;
|
||||
} completion:^(BOOL finished) {
|
||||
[toastView removeFromSuperview];
|
||||
}];
|
||||
});
|
||||
|
||||
NSLog(@"🎮 显示 Toast: %@", message);
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@optional
|
||||
- (void)turboModeTipsViewDidTapUnderstand;
|
||||
- (void)turboModeTipsViewDidEnableTurboMode;
|
||||
|
||||
@end
|
||||
|
||||
|
@@ -7,15 +7,20 @@
|
||||
//
|
||||
|
||||
#import "XPTurboModeTipsView.h"
|
||||
#import "XPTurboModeTipsManager.h"
|
||||
|
||||
@interface XPTurboModeTipsView ()
|
||||
|
||||
@property (nonatomic, strong) UIView *backgroundView;
|
||||
@property (nonatomic, strong) UIView *contentView;
|
||||
@property (nonatomic, strong) UILabel *titleLabel;
|
||||
@property (nonatomic, strong) UIScrollView *scrollView;
|
||||
@property (nonatomic, strong) UIView *scrollContentView;
|
||||
@property (nonatomic, strong) UILabel *contentLabel;
|
||||
@property (nonatomic, strong) UILabel *contentLabel2;
|
||||
@property (nonatomic, strong) UIButton *understandButton;
|
||||
@property (nonatomic, strong) UIView *parentView;
|
||||
@property (nonatomic, strong) UIImageView *tipsImageView;
|
||||
|
||||
@end
|
||||
|
||||
@@ -27,6 +32,10 @@
|
||||
XPTurboModeTipsView *tipsView = [[XPTurboModeTipsView alloc] init];
|
||||
tipsView.parentView = parentView;
|
||||
[tipsView setupUI];
|
||||
|
||||
// 🔧 修改:在显示时自动开启 turbo mode
|
||||
[tipsView enableTurboModeOnShow];
|
||||
|
||||
[tipsView showWithAnimation];
|
||||
return tipsView;
|
||||
}
|
||||
@@ -35,6 +44,16 @@
|
||||
[self dismissWithAnimation];
|
||||
}
|
||||
|
||||
#pragma mark - Turbo Mode Management
|
||||
|
||||
- (void)enableTurboModeOnShow {
|
||||
NSLog(@"🎮 Tips View 显示时自动开启 Turbo Mode");
|
||||
|
||||
// 🔧 修复:直接使用 XPTurboModeTipsManager 单例开启 turbo mode
|
||||
// 因为此时 delegate 可能还没有设置
|
||||
[[XPTurboModeTipsManager sharedManager] enableTurboModeDirectly];
|
||||
}
|
||||
|
||||
#pragma mark - Private Methods
|
||||
|
||||
- (void)setupUI {
|
||||
@@ -51,9 +70,18 @@
|
||||
// 添加标题
|
||||
[self setupTitleLabel];
|
||||
|
||||
// 添加滚动视图
|
||||
[self setupScrollView];
|
||||
|
||||
// 添加内容文字
|
||||
[self setupContentLabel];
|
||||
|
||||
// 添加图片视图
|
||||
[self setupTipsImageView];
|
||||
|
||||
// 添加第二个内容标签
|
||||
[self setupContentLabel2];
|
||||
|
||||
// 添加按钮
|
||||
[self setupUnderstandButton];
|
||||
|
||||
@@ -84,21 +112,50 @@
|
||||
|
||||
- (void)setupTitleLabel {
|
||||
self.titleLabel = [[UILabel alloc] init];
|
||||
self.titleLabel.text = @"Tips";
|
||||
self.titleLabel.text = YMLocalizedString(@"UserDetail_CP_Toast_0");
|
||||
self.titleLabel.font = [UIFont boldSystemFontOfSize:20.0];
|
||||
self.titleLabel.textColor = [UIColor blackColor];
|
||||
self.titleLabel.textAlignment = NSTextAlignmentCenter;
|
||||
[self.contentView addSubview:self.titleLabel];
|
||||
}
|
||||
|
||||
- (void)setupScrollView {
|
||||
self.scrollView = [[UIScrollView alloc] init];
|
||||
self.scrollView.showsVerticalScrollIndicator = YES;
|
||||
self.scrollView.showsHorizontalScrollIndicator = NO;
|
||||
self.scrollView.alwaysBounceVertical = YES;
|
||||
self.scrollView.alwaysBounceHorizontal = NO;
|
||||
[self.contentView addSubview:self.scrollView];
|
||||
|
||||
self.scrollContentView = [[UIView alloc] init];
|
||||
[self.scrollView addSubview:self.scrollContentView];
|
||||
}
|
||||
|
||||
- (void)setupContentLabel {
|
||||
self.contentLabel = [[UILabel alloc] init];
|
||||
self.contentLabel.text = @"Enabling Turbo mode can make the app run smoother.\n\nTurbo mode: Turns off room gift animations, in-room broadcasts, CP displays, and other animated effects.\n\nTo make the app run smoother, it has automatically switched to Turbo mode for you (Turbo mode: Automatically turns off gift animations, headwear effects, and merges gift messages in the public chat). You can turn off this mode in the room settings.";
|
||||
self.contentLabel.text = NSLocalizedString(@"20.20.62_text_15.1", @"Turbo mode tips content 1");
|
||||
self.contentLabel.font = [UIFont systemFontOfSize:16.0];
|
||||
self.contentLabel.textColor = [UIColor darkGrayColor];
|
||||
self.contentLabel.numberOfLines = 0;
|
||||
self.contentLabel.textAlignment = NSTextAlignmentLeft;
|
||||
[self.contentView addSubview:self.contentLabel];
|
||||
[self.scrollContentView addSubview:self.contentLabel];
|
||||
}
|
||||
|
||||
- (void)setupTipsImageView {
|
||||
self.tipsImageView = [[UIImageView alloc] init];
|
||||
self.tipsImageView.image = [UIImage imageNamed:@"turbo_mode_tips"];
|
||||
self.tipsImageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
[self.scrollContentView addSubview:self.tipsImageView];
|
||||
}
|
||||
|
||||
- (void)setupContentLabel2 {
|
||||
self.contentLabel2 = [[UILabel alloc] init];
|
||||
self.contentLabel2.text = NSLocalizedString(@"20.20.62_text_15.2", @"Turbo mode tips content 2");
|
||||
self.contentLabel2.font = [UIFont systemFontOfSize:16.0];
|
||||
self.contentLabel2.textColor = [UIColor darkGrayColor];
|
||||
self.contentLabel2.numberOfLines = 0;
|
||||
self.contentLabel2.textAlignment = NSTextAlignmentLeft;
|
||||
[self.scrollContentView addSubview:self.contentLabel2];
|
||||
}
|
||||
|
||||
- (void)setupUnderstandButton {
|
||||
@@ -113,45 +170,85 @@
|
||||
}
|
||||
|
||||
- (void)setupConstraints {
|
||||
// 背景遮罩
|
||||
[self.backgroundView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
|
||||
// 内容容器 - 屏幕的 3/5 尺寸,居中显示
|
||||
[self.contentView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.center.equalTo(self);
|
||||
make.width.equalTo(@320);
|
||||
make.height.lessThanOrEqualTo(@500);
|
||||
make.width.equalTo(self).multipliedBy(0.8); // 屏幕宽度的 3/5
|
||||
make.height.equalTo(self).multipliedBy(0.6); // 屏幕高度的 3/5
|
||||
}];
|
||||
|
||||
// 标题 - 固定位置
|
||||
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.contentView).offset(24);
|
||||
make.left.right.equalTo(self.contentView).insets(UIEdgeInsetsMake(0, 24, 0, 24));
|
||||
make.leading.trailing.equalTo(self.contentView).insets(UIEdgeInsetsMake(0, 24, 0, 24));
|
||||
make.height.equalTo(@30);
|
||||
}];
|
||||
|
||||
[self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
// 滚动视图 - 在标题和按钮之间
|
||||
[self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.titleLabel.mas_bottom).offset(16);
|
||||
make.left.right.equalTo(self.contentView).insets(UIEdgeInsetsMake(0, 24, 0, 24));
|
||||
make.leading.trailing.equalTo(self.contentView);
|
||||
make.bottom.equalTo(self.understandButton.mas_top).offset(-16);
|
||||
}];
|
||||
|
||||
// 滚动内容容器
|
||||
[self.scrollContentView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self.scrollView);
|
||||
make.width.equalTo(self.scrollView); // 重要:设置宽度约束
|
||||
}];
|
||||
|
||||
// 上层:原 contentLabel
|
||||
[self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.scrollContentView).offset(0);
|
||||
make.leading.trailing.equalTo(self.scrollContentView).insets(UIEdgeInsetsMake(0, 24, 0, 24));
|
||||
}];
|
||||
|
||||
// 中层:图片视图
|
||||
[self.tipsImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.contentLabel.mas_bottom).offset(20);
|
||||
make.centerX.equalTo(self.scrollContentView);
|
||||
make.width.equalTo(self.scrollContentView).multipliedBy(0.6); // 相对宽度
|
||||
make.height.equalTo(self.tipsImageView.mas_width).multipliedBy(0.6); // 保持宽高比
|
||||
}];
|
||||
|
||||
// 下层:新增 contentLabel2
|
||||
[self.contentLabel2 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.tipsImageView.mas_bottom).offset(20);
|
||||
make.leading.trailing.equalTo(self.scrollContentView).insets(UIEdgeInsetsMake(0, 24, 0, 24));
|
||||
make.bottom.equalTo(self.scrollContentView).offset(-16); // 重要:设置底部约束
|
||||
}];
|
||||
|
||||
// 按钮 - 固定位置
|
||||
[self.understandButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.contentLabel.mas_bottom).offset(24);
|
||||
make.centerX.equalTo(self.contentView);
|
||||
make.width.equalTo(@200);
|
||||
make.height.equalTo(@44);
|
||||
make.bottom.equalTo(self.contentView).offset(-24);
|
||||
make.centerX.equalTo(self.contentView);
|
||||
make.width.equalTo(self.contentView).multipliedBy(0.6); // 相对宽度
|
||||
make.height.equalTo(@44);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)backgroundTapped {
|
||||
// 点击背景不关闭弹窗,保持用户体验
|
||||
NSLog(@"🎮 用户点击了背景,关闭 Tips 弹窗");
|
||||
|
||||
// 点击背景关闭弹窗,但不触发开启 turbo mode
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(turboModeTipsViewDidTapUnderstand)]) {
|
||||
[self.delegate turboModeTipsViewDidTapUnderstand];
|
||||
}
|
||||
|
||||
[self dismiss];
|
||||
}
|
||||
|
||||
- (void)understandButtonTapped {
|
||||
NSLog(@"🎮 用户点击了 'I understand' 按钮");
|
||||
NSLog(@"🎮 用户点击了 'I understand' 按钮,关闭 Tips 弹窗");
|
||||
|
||||
// 🔧 修改:只触发理解回调,不重复开启 turbo mode(已在显示时开启)
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(turboModeTipsViewDidTapUnderstand)]) {
|
||||
[self.delegate turboModeTipsViewDidTapUnderstand];
|
||||
}
|
||||
|
@@ -6,11 +6,14 @@
|
||||
//
|
||||
|
||||
#import "StageView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface AnchorPKStageView : StageView
|
||||
|
||||
@property (nonatomic, strong, readonly) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
#import "AnchorPKStageView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
///Third
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <NIMSDK/NIMSDK.h>
|
||||
@@ -60,6 +61,7 @@
|
||||
@property (strong, nonatomic) SVGAParser *parser;
|
||||
///中间进度的动画
|
||||
@property (nonatomic, strong) SVGAImageView *svgDisplayView;
|
||||
@property (nonatomic, strong) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
@end
|
||||
|
||||
@@ -67,6 +69,9 @@
|
||||
|
||||
- (instancetype)initWithDelegate:(id<RoomHostDelegate>)delegate {
|
||||
if (self = [super initWithDelegate:delegate]) {
|
||||
// 初始化中点矩形管理器
|
||||
_midpointRectManager = [[MicMidpointRectManager alloc] initWithContainerView:self];
|
||||
|
||||
[self addSubview:self.svgDisplayView];
|
||||
[self addSubview:self.ruleButton];
|
||||
[self.svgDisplayView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
@@ -142,6 +147,26 @@
|
||||
return point;
|
||||
}
|
||||
|
||||
// 主播PK:两个位置(0、1)互为相邻
|
||||
- (CGRect)rectForMidpointBetweenMicAtIndex:(NSInteger)firstIndex andIndex:(NSInteger)secondIndex {
|
||||
if (firstIndex == secondIndex) return CGRectZero;
|
||||
NSInteger left = MIN(firstIndex, secondIndex);
|
||||
NSInteger right = MAX(firstIndex, secondIndex);
|
||||
if (!(left == 0 && right == 1)) return CGRectZero;
|
||||
CGRect l = [self rectForViewAtIndex:left];
|
||||
CGRect r = [self rectForViewAtIndex:right];
|
||||
if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero;
|
||||
CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0;
|
||||
|
||||
// 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐
|
||||
CGFloat leftTopY = CGRectGetMinY(l);
|
||||
CGFloat rightTopY = CGRectGetMinY(r);
|
||||
CGFloat midTopY = (leftTopY + rightTopY) / 2.0;
|
||||
|
||||
CGFloat size = 75.0;
|
||||
return CGRectMake(midX - size / 2.0, midTopY, size, size);
|
||||
}
|
||||
|
||||
- (void)onRoomEntered {
|
||||
[super onRoomEntered];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
|
@@ -6,11 +6,14 @@
|
||||
//
|
||||
|
||||
#import "StageView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface AnchorStageView : StageView
|
||||
|
||||
@property (nonatomic, strong, readonly) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -13,6 +13,7 @@
|
||||
#import "AccountInfoStorage.h"
|
||||
#import "XNDJTDDLoadingTool.h"
|
||||
#import "YUMIMacroUitls.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
#import "TTPopup.h"
|
||||
#import "Api+Room.h"
|
||||
///Model
|
||||
@@ -39,8 +40,22 @@
|
||||
// 下边坑位的左右间距
|
||||
#define marginW 50
|
||||
|
||||
@interface AnchorStageView ()
|
||||
|
||||
@property (nonatomic, strong) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
@end
|
||||
|
||||
@implementation AnchorStageView
|
||||
|
||||
- (instancetype)initWithDelegate:(id<RoomHostDelegate>)delegate {
|
||||
if (self = [super initWithDelegate:delegate]) {
|
||||
// 初始化中点矩形管理器
|
||||
_midpointRectManager = [[MicMidpointRectManager alloc] initWithContainerView:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger)countOfMicroView {
|
||||
return 4;
|
||||
}
|
||||
@@ -90,6 +105,32 @@
|
||||
return point;
|
||||
}
|
||||
|
||||
// 主播房布局:房主(0) + 3个麦位(1-3),仅相邻麦位(1-2, 2-3)返回中点75x75
|
||||
- (CGRect)rectForMidpointBetweenMicAtIndex:(NSInteger)firstIndex andIndex:(NSInteger)secondIndex {
|
||||
if (firstIndex == secondIndex) return CGRectZero;
|
||||
NSInteger left = MIN(firstIndex, secondIndex);
|
||||
NSInteger right = MAX(firstIndex, secondIndex);
|
||||
|
||||
// 仅支持1-2, 2-3的相邻对
|
||||
if (!((left == 1 && right == 2) || (left == 2 && right == 3))) {
|
||||
return CGRectZero;
|
||||
}
|
||||
|
||||
CGRect l = [self rectForViewAtIndex:left];
|
||||
CGRect r = [self rectForViewAtIndex:right];
|
||||
if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero;
|
||||
|
||||
CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0;
|
||||
|
||||
// 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐
|
||||
CGFloat leftTopY = CGRectGetMinY(l);
|
||||
CGFloat rightTopY = CGRectGetMinY(r);
|
||||
CGFloat midTopY = (leftTopY + rightTopY) / 2.0;
|
||||
|
||||
CGFloat size = 75.0;
|
||||
return CGRectMake(midX - size / 2.0, midTopY, size, size);
|
||||
}
|
||||
|
||||
- (void)onRoomEntered {
|
||||
[super onRoomEntered];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
|
@@ -6,11 +6,14 @@
|
||||
//
|
||||
|
||||
#import "StageView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface DatingStageView : StageView
|
||||
|
||||
@property (nonatomic, strong, readonly) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
#import "DatingStageView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
///Third
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <NIMSDK/NIMSDK.h>
|
||||
@@ -52,6 +53,7 @@
|
||||
@interface DatingStageView()
|
||||
///相亲进行的阶段
|
||||
@property (nonatomic,strong) MicroDatingProgressView *datingProgressView;
|
||||
@property (nonatomic, strong) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
@end
|
||||
|
||||
@@ -59,6 +61,9 @@
|
||||
|
||||
- (instancetype)initWithDelegate:(id<RoomHostDelegate>)delegate {
|
||||
if (self = [super initWithDelegate:delegate]) {
|
||||
// 初始化中点矩形管理器
|
||||
_midpointRectManager = [[MicMidpointRectManager alloc] initWithContainerView:self];
|
||||
|
||||
[self addSubview:self.datingProgressView];
|
||||
[self.datingProgressView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.mas_equalTo(datingWidth);
|
||||
@@ -152,6 +157,28 @@
|
||||
return point;
|
||||
}
|
||||
|
||||
// 相亲布局:每行两侧各 2 个麦位,中间留 dating 区,允许每侧相邻对返回中点(1-2、3-4;5-6、7-8)
|
||||
- (CGRect)rectForMidpointBetweenMicAtIndex:(NSInteger)firstIndex andIndex:(NSInteger)secondIndex {
|
||||
if (firstIndex == secondIndex) return CGRectZero;
|
||||
NSInteger left = MIN(firstIndex, secondIndex);
|
||||
NSInteger right = MAX(firstIndex, secondIndex);
|
||||
BOOL valid = ((left == 1 && right == 2) || (left == 3 && right == 4) ||
|
||||
(left == 5 && right == 6) || (left == 7 && right == 8));
|
||||
if (!valid) return CGRectZero;
|
||||
CGRect l = [self rectForViewAtIndex:left];
|
||||
CGRect r = [self rectForViewAtIndex:right];
|
||||
if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero;
|
||||
CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0;
|
||||
|
||||
// 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐
|
||||
CGFloat leftTopY = CGRectGetMinY(l);
|
||||
CGFloat rightTopY = CGRectGetMinY(r);
|
||||
CGFloat midTopY = (leftTopY + rightTopY) / 2.0;
|
||||
|
||||
CGFloat size = 75.0;
|
||||
return CGRectMake(midX - size / 2.0, midTopY, size, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 麦位更新扩展字段。
|
||||
*/
|
||||
|
@@ -6,11 +6,14 @@
|
||||
//
|
||||
|
||||
#import "StageView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FifteenMicStageView : StageView
|
||||
|
||||
@property (nonatomic, strong, readonly) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -7,6 +7,7 @@
|
||||
|
||||
#import "FifteenMicStageView.h"
|
||||
#import "SocialMicroView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
//第一行距离顶部
|
||||
#define firstRowTopMargin 60
|
||||
// 房主头像宽 58 + 光圈 5
|
||||
@@ -28,8 +29,22 @@
|
||||
//#define marginV2 15
|
||||
// 第三行坑位的顶部
|
||||
#define tRowTop (sRowTop + mcHeight + lineMargin)
|
||||
@interface FifteenMicStageView ()
|
||||
|
||||
@property (nonatomic, strong) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FifteenMicStageView
|
||||
|
||||
- (instancetype)initWithDelegate:(id<RoomHostDelegate>)delegate {
|
||||
if (self = [super initWithDelegate:delegate]) {
|
||||
// 初始化中点矩形管理器
|
||||
_midpointRectManager = [[MicMidpointRectManager alloc] initWithContainerView:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger)countOfMicroView {
|
||||
return 15;
|
||||
}
|
||||
@@ -111,4 +126,36 @@
|
||||
return point;
|
||||
}
|
||||
|
||||
// 15麦位布局:5x3网格,仅同一行且相邻列(列差1)返回中点75x75
|
||||
- (CGRect)rectForMidpointBetweenMicAtIndex:(NSInteger)firstIndex andIndex:(NSInteger)secondIndex {
|
||||
if (firstIndex == secondIndex) return CGRectZero;
|
||||
NSInteger left = MIN(firstIndex, secondIndex);
|
||||
NSInteger right = MAX(firstIndex, secondIndex);
|
||||
|
||||
// 检查是否在同一行且相邻
|
||||
BOOL sameRow = ((left >= 0 && left <= 4 && right >= 0 && right <= 4) ||
|
||||
(left >= 5 && left <= 9 && right >= 5 && right <= 9) ||
|
||||
(left >= 10 && left <= 14 && right >= 10 && right <= 14));
|
||||
if (!sameRow) return CGRectZero;
|
||||
|
||||
// 检查是否相邻(列差为1)
|
||||
NSInteger leftCol = (left <= 4) ? left : ((left <= 9) ? (left - 5) : (left - 10));
|
||||
NSInteger rightCol = (right <= 4) ? right : ((right <= 9) ? (right - 5) : (right - 10));
|
||||
if ((rightCol - leftCol) != 1) return CGRectZero;
|
||||
|
||||
CGRect l = [self rectForViewAtIndex:left];
|
||||
CGRect r = [self rectForViewAtIndex:right];
|
||||
if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero;
|
||||
|
||||
CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0;
|
||||
|
||||
// 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐
|
||||
CGFloat leftTopY = CGRectGetMinY(l);
|
||||
CGFloat rightTopY = CGRectGetMinY(r);
|
||||
CGFloat midTopY = (leftTopY + rightTopY) / 2.0;
|
||||
|
||||
CGFloat size = 75.0;
|
||||
return CGRectMake(midX - size / 2.0, midTopY, size, size);
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -6,11 +6,20 @@
|
||||
//
|
||||
|
||||
#import "StageView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface LittleGameScrollStageView : StageView
|
||||
|
||||
@property (nonatomic, strong, readonly) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
/// 添加中点矩形到滚动视图
|
||||
- (void)addMidpointRect:(UIView *)rectView;
|
||||
|
||||
/// 移除所有中点矩形
|
||||
- (void)removeAllMidpointRects;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -14,6 +14,7 @@
|
||||
|
||||
///View
|
||||
#import "LittleGameMicroView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
|
||||
UIKIT_EXTERN NSString * const kRoomRoomLittleGameMiniStageNotificationKey;
|
||||
|
||||
@@ -27,6 +28,7 @@ UIKIT_EXTERN NSString * const kRoomRoomLittleGameMiniStageNotificationKey;
|
||||
@interface LittleGameScrollStageView ()
|
||||
|
||||
@property (nonatomic, strong) UIScrollView *scrollView;
|
||||
@property (nonatomic, strong) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
@end
|
||||
|
||||
@@ -92,7 +94,9 @@ UIKIT_EXTERN NSString * const kRoomRoomLittleGameMiniStageNotificationKey;
|
||||
|
||||
- (instancetype)initWithDelegate:(id<RoomHostDelegate>)delegate {
|
||||
if (self = [super initWithDelegate:delegate]) {
|
||||
|
||||
// 初始化中点矩形管理器
|
||||
_midpointRectManager = [[MicMidpointRectManager alloc] initWithContainerView:self.scrollView];
|
||||
|
||||
[self setupObserver];
|
||||
[self setupMicroScrollView];
|
||||
}
|
||||
@@ -133,6 +137,28 @@ UIKIT_EXTERN NSString * const kRoomRoomLittleGameMiniStageNotificationKey;
|
||||
return point;
|
||||
}
|
||||
|
||||
// 线性可滚动布局,相邻 index(差 1)返回中点 75x75(坐标基于 scrollView)
|
||||
- (CGRect)rectForMidpointBetweenMicAtIndex:(NSInteger)firstIndex andIndex:(NSInteger)secondIndex {
|
||||
if (firstIndex == secondIndex) return CGRectZero;
|
||||
NSInteger left = MIN(firstIndex, secondIndex);
|
||||
NSInteger right = MAX(firstIndex, secondIndex);
|
||||
if (left < 0 || right >= [self countOfMicroView]) return CGRectZero;
|
||||
if ((right - left) != 1) return CGRectZero; // 仅相邻
|
||||
CGRect l = [self rectForViewAtIndex:left];
|
||||
CGRect r = [self rectForViewAtIndex:right];
|
||||
if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero;
|
||||
CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0;
|
||||
|
||||
// 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐
|
||||
CGFloat leftTopY = CGRectGetMinY(l);
|
||||
CGFloat rightTopY = CGRectGetMinY(r);
|
||||
CGFloat midTopY = (leftTopY + rightTopY) / 2.0;
|
||||
|
||||
CGFloat size = 75.0;
|
||||
// 坐标在自身(scrollView 内),返回给调用方在自身坐标使用
|
||||
return CGRectMake(midX - size / 2.0, midTopY, size, size);
|
||||
}
|
||||
|
||||
- (void)didSelectAtIndex:(NSInteger)index {
|
||||
MicroQueueModel* micModel = [self findMicroInfoByUid:[AccountInfoStorage instance].getUid];
|
||||
if (micModel && micModel.userInfo.uid > 0 && micModel.userInfo.gameStatus == LittleGamePlayStatus_Plying) {
|
||||
@@ -157,10 +183,40 @@ UIKIT_EXTERN NSString * const kRoomRoomLittleGameMiniStageNotificationKey;
|
||||
_scrollView.scrollEnabled = YES;
|
||||
_scrollView.showsHorizontalScrollIndicator = NO;
|
||||
_scrollView.clipsToBounds = NO;
|
||||
_scrollView.delegate = self;
|
||||
}
|
||||
return _scrollView;
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
// 中点矩形已经基于 scrollView 坐标系,无需额外调整
|
||||
// 因为 rectForMidpointBetweenMicAtIndex:andIndex: 返回的坐标已经是相对于 scrollView 的
|
||||
}
|
||||
|
||||
#pragma mark - 中点矩形管理
|
||||
|
||||
- (void)addMidpointRect:(UIView *)rectView {
|
||||
// 使用新的管理器添加中点矩形
|
||||
NSString *micPairText = @"";
|
||||
for (UIView *subview in rectView.subviews) {
|
||||
if ([subview isKindOfClass:[UILabel class]]) {
|
||||
UILabel *label = (UILabel *)subview;
|
||||
micPairText = label.text ?: @"";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[self.midpointRectManager addMidpointRectAtFrame:rectView.frame
|
||||
micPairText:micPairText
|
||||
autoPlaySVGA:YES];
|
||||
}
|
||||
|
||||
- (void)removeAllMidpointRects {
|
||||
[self.midpointRectManager removeAllMidpointRects];
|
||||
}
|
||||
|
||||
#pragma mark - 触摸事件日志
|
||||
|
||||
// 触摸开始
|
||||
|
@@ -6,11 +6,20 @@
|
||||
//
|
||||
|
||||
#import "StageView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface LittleGameStageView : StageView
|
||||
|
||||
@property (nonatomic, strong, readonly) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
/// 添加中点矩形到视图
|
||||
- (void)addMidpointRect:(UIView *)rectView;
|
||||
|
||||
/// 移除所有中点矩形
|
||||
- (void)removeAllMidpointRects;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -14,6 +14,7 @@
|
||||
|
||||
///View
|
||||
#import "LittleGameMicroView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
|
||||
UIKIT_EXTERN NSString * const kRoomRoomLittleGameMiniStageNotificationKey;
|
||||
|
||||
@@ -26,6 +27,8 @@ UIKIT_EXTERN NSString * const kRoomRoomLittleGameMiniStageNotificationKey;
|
||||
|
||||
@interface LittleGameStageView ()
|
||||
|
||||
@property (nonatomic, strong) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LittleGameStageView
|
||||
@@ -36,6 +39,9 @@ UIKIT_EXTERN NSString * const kRoomRoomLittleGameMiniStageNotificationKey;
|
||||
|
||||
- (instancetype)initWithDelegate:(id<RoomHostDelegate>)delegate {
|
||||
if (self = [super initWithDelegate:delegate]) {
|
||||
// 初始化中点矩形管理器
|
||||
_midpointRectManager = [[MicMidpointRectManager alloc] initWithContainerView:self];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeViewState:) name:kRoomRoomLittleGameMiniStageNotificationKey object:nil];
|
||||
}
|
||||
return self;
|
||||
@@ -75,6 +81,27 @@ UIKIT_EXTERN NSString * const kRoomRoomLittleGameMiniStageNotificationKey;
|
||||
return point;
|
||||
}
|
||||
|
||||
// 同一行线性排布,允许相邻 index(差 1)返回中点 75x75
|
||||
- (CGRect)rectForMidpointBetweenMicAtIndex:(NSInteger)firstIndex andIndex:(NSInteger)secondIndex {
|
||||
if (firstIndex == secondIndex) return CGRectZero;
|
||||
NSInteger left = MIN(firstIndex, secondIndex);
|
||||
NSInteger right = MAX(firstIndex, secondIndex);
|
||||
if (left < 0 || right >= [self countOfMicroView]) return CGRectZero;
|
||||
if ((right - left) != 1) return CGRectZero; // 仅相邻
|
||||
CGRect l = [self rectForViewAtIndex:left];
|
||||
CGRect r = [self rectForViewAtIndex:right];
|
||||
if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero;
|
||||
CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0;
|
||||
|
||||
// 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐
|
||||
CGFloat leftTopY = CGRectGetMinY(l);
|
||||
CGFloat rightTopY = CGRectGetMinY(r);
|
||||
CGFloat midTopY = (leftTopY + rightTopY) / 2.0;
|
||||
|
||||
CGFloat size = 75.0;
|
||||
return CGRectMake(midX - size / 2.0, midTopY, size, size);
|
||||
}
|
||||
|
||||
- (void)didSelectAtIndex:(NSInteger)index {
|
||||
MicroQueueModel* micModel = [self findMicroInfoByUid:[AccountInfoStorage instance].getUid];
|
||||
if (micModel && micModel.userInfo.uid > 0 && micModel.userInfo.gameStatus == LittleGamePlayStatus_Plying) {
|
||||
@@ -94,4 +121,26 @@ UIKIT_EXTERN NSString * const kRoomRoomLittleGameMiniStageNotificationKey;
|
||||
self.hidden = [[dic objectForKey:@"isMini"] boolValue];
|
||||
}
|
||||
|
||||
#pragma mark - 中点矩形管理
|
||||
|
||||
- (void)addMidpointRect:(UIView *)rectView {
|
||||
// 使用新的管理器添加中点矩形
|
||||
NSString *micPairText = @"";
|
||||
for (UIView *subview in rectView.subviews) {
|
||||
if ([subview isKindOfClass:[UILabel class]]) {
|
||||
UILabel *label = (UILabel *)subview;
|
||||
micPairText = label.text ?: @"";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[self.midpointRectManager addMidpointRectAtFrame:rectView.frame
|
||||
micPairText:micPairText
|
||||
autoPlaySVGA:YES];
|
||||
}
|
||||
|
||||
- (void)removeAllMidpointRects {
|
||||
[self.midpointRectManager removeAllMidpointRects];
|
||||
}
|
||||
|
||||
@end
|
||||
|
101
YuMi/Modules/YMRoom/View/StageView/MicMidpointRectManager.h
Normal file
101
YuMi/Modules/YMRoom/View/StageView/MicMidpointRectManager.h
Normal file
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// MicMidpointRectManager.h
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI Assistant on 2024/12/19.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class MicMidpointRectManager;
|
||||
@class UIView;
|
||||
|
||||
@protocol MicMidpointRectManagerDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
/// 中点矩形动画播放完成
|
||||
- (void)midpointRectManager:(MicMidpointRectManager *)manager didFinishAnimationAtRect:(CGRect)rect;
|
||||
|
||||
@end
|
||||
|
||||
@interface MicMidpointRectManager : NSObject
|
||||
|
||||
@property (nonatomic, weak) id<MicMidpointRectManagerDelegate> delegate;
|
||||
@property (nonatomic, weak) UIView *containerView;
|
||||
/// 缓存:当前房间的CP关系列表
|
||||
@property (nonatomic, strong, readonly) NSArray *cachedCpList;
|
||||
/// 缓存:麦位position -> uid(无用户为-1)
|
||||
@property (nonatomic, strong, readonly) NSDictionary<NSNumber *, NSNumber *> *micPositionToUid;
|
||||
|
||||
/// 初始化方法
|
||||
- (instancetype)initWithContainerView:(UIView *)containerView;
|
||||
|
||||
/// 添加中点矩形,支持SVGA动画
|
||||
- (void)addMidpointRectAtFrame:(CGRect)frame
|
||||
micPairText:(NSString *)micPairText
|
||||
autoPlaySVGA:(BOOL)autoPlaySVGA;
|
||||
|
||||
/// 移除所有中点矩形
|
||||
- (void)removeAllMidpointRects;
|
||||
|
||||
/// 移除指定位置的中点矩形
|
||||
- (void)removeMidpointRectAtFrame:(CGRect)frame;
|
||||
|
||||
/// 播放指定位置的随机SVGA动画
|
||||
- (void)playSVGAAnimationAtFrame:(CGRect)frame;
|
||||
|
||||
/// 播放指定位置的指定资源名SVGA动画(资源名不带后缀)
|
||||
- (void)playSVGAAnimationAtFrame:(CGRect)frame withNamed:(NSString *)resourceName;
|
||||
|
||||
/// 抽象:在指定中点位置绘制关系位(透明容器),并根据CP列表与两端uid匹配后播放对应等级SVGA
|
||||
/// - Parameters:
|
||||
/// - frame: 中点关系位frame
|
||||
/// - micPairText: 调试展示用的麦位对文本(仅DEBUG下显示,50%透明)
|
||||
/// - leftUid: 左侧麦位用户uid(0表示无)
|
||||
/// - rightUid: 右侧麦位用户uid(0表示无)
|
||||
/// - cpList: CP关系列表(MicCpInfoModel 数组)
|
||||
- (void)addRelationshipAtFrame:(CGRect)frame
|
||||
micPairText:(NSString *)micPairText
|
||||
leftUid:(NSInteger)leftUid
|
||||
rightUid:(NSInteger)rightUid
|
||||
cpList:(NSArray *)cpList;
|
||||
|
||||
/// 基于用户UID清理相关的中点矩形和SVGA动画
|
||||
/// - Parameter uids: 需要清理的用户UID数组
|
||||
- (void)removeMidpointRectsForUids:(NSArray<NSNumber *> *)uids;
|
||||
|
||||
/// 设置并缓存CP列表
|
||||
- (void)setCpListCache:(NSArray *)cpList;
|
||||
|
||||
/// 使用当前stageView重建麦位快照(position->uid),无用户记为-1
|
||||
- (void)rebuildMicSnapshotWithStageView:(id)stageView micCount:(NSInteger)micCount;
|
||||
|
||||
/// 基于上一次快照与当前stageView的实际用户,生成“变动用户+左右邻居”的uid列表(字符串数组)并更新快照
|
||||
- (NSArray<NSString *> *)uidListForChangedMicWithStageView:(id)stageView micCount:(NSInteger)micCount;
|
||||
|
||||
/// 计算本次与快照的差异,返回 @{ @"added": NSArray<NSNumber*>, @"removed": NSArray<NSNumber*> } 并更新快照
|
||||
- (NSDictionary<NSString *, NSArray<NSNumber *> *> *)diffMicChangeWithStageView:(id)stageView micCount:(NSInteger)micCount;
|
||||
|
||||
/// 从缓存中移除包含这些uid的所有CP关系
|
||||
- (void)removeCpEntriesForUids:(NSArray<NSNumber *> *)uids;
|
||||
|
||||
/// 用新列表替换指定uid相关的CP关系(先移除涉及这些uid的旧数据,再合并新数据)
|
||||
- (void)mergeCpListCache:(NSArray *)cpList replaceForUids:(NSArray<NSNumber *> *)uids;
|
||||
|
||||
/// 停止所有SVGA动画
|
||||
- (void)stopAllSVGAAnimations;
|
||||
|
||||
/// 处理下麦事件:移除相关用户的CP关系和SVGA显示
|
||||
/// - Parameters:
|
||||
/// - downMicUids: 下麦用户的UID数组
|
||||
/// - stageView: 当前舞台视图
|
||||
/// - micCount: 麦位总数
|
||||
- (void)handleDownMicEvent:(NSArray<NSNumber *> *)downMicUids
|
||||
stageView:(id)stageView
|
||||
micCount:(NSInteger)micCount;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
383
YuMi/Modules/YMRoom/View/StageView/MicMidpointRectManager.m
Normal file
383
YuMi/Modules/YMRoom/View/StageView/MicMidpointRectManager.m
Normal file
@@ -0,0 +1,383 @@
|
||||
//
|
||||
// MicMidpointRectManager.m
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI Assistant on 2024/12/19.
|
||||
//
|
||||
|
||||
#import "MicMidpointRectManager.h"
|
||||
#import "MicCpInfoModel.h"
|
||||
#import <SVGA.h>
|
||||
#import "StageView.h"
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class SVGAImageView;
|
||||
@class SVGAParser;
|
||||
|
||||
@interface MicMidpointRectManager ()
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray<UIView *> *midpointRects;
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSString *, SVGAImageView *> *svgaViews;
|
||||
@property (nonatomic, strong) SVGAParser *svgaParser;
|
||||
@property (nonatomic, strong) NSArray *cachedCpListInternal;
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, NSNumber *> *micPositionToUidInternal; // position->uid (-1 if empty)
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSDictionary *> *midpointRectInfo; // frameKey -> @{@"leftUid": @(uid), @"rightUid": @(uid)}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MicMidpointRectManager
|
||||
|
||||
- (instancetype)initWithContainerView:(UIView *)containerView {
|
||||
if (self = [super init]) {
|
||||
_containerView = containerView;
|
||||
_midpointRects = [NSMutableArray array];
|
||||
_svgaViews = [NSMutableDictionary dictionary];
|
||||
_svgaParser = [[SVGAParser alloc] init];
|
||||
_micPositionToUidInternal = [NSMutableDictionary dictionary];
|
||||
_midpointRectInfo = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSArray *)cachedCpList {
|
||||
return self.cachedCpListInternal ?: @[];
|
||||
}
|
||||
|
||||
- (NSDictionary<NSNumber *,NSNumber *> *)micPositionToUid {
|
||||
return self.micPositionToUidInternal ?: @{};
|
||||
}
|
||||
|
||||
- (void)addMidpointRectAtFrame:(CGRect)frame
|
||||
micPairText:(NSString *)micPairText
|
||||
autoPlaySVGA:(BOOL)autoPlaySVGA {
|
||||
// 创建透明容器
|
||||
UIView *rectView = [[UIView alloc] initWithFrame:frame];
|
||||
rectView.backgroundColor = [UIColor clearColor];
|
||||
rectView.userInteractionEnabled = NO;
|
||||
rectView.tag = 56002;
|
||||
|
||||
#if DEBUG
|
||||
// 仅在DEBUG显示麦位对文本,50%透明
|
||||
UILabel *label = [[UILabel alloc] init];
|
||||
label.text = micPairText;
|
||||
label.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5];
|
||||
label.font = [UIFont boldSystemFontOfSize:12];
|
||||
label.textAlignment = NSTextAlignmentCenter;
|
||||
label.frame = rectView.bounds;
|
||||
[rectView addSubview:label];
|
||||
#endif
|
||||
|
||||
// 添加到容器视图
|
||||
[self.containerView addSubview:rectView];
|
||||
[self.midpointRects addObject:rectView];
|
||||
|
||||
// 如果需要自动播放SVGA动画
|
||||
if (autoPlaySVGA) {
|
||||
[self playSVGAAnimationAtFrame:frame];
|
||||
}
|
||||
|
||||
NSLog(@"🔧 添加中点矩形: %@, frame: %@", micPairText, NSStringFromCGRect(frame));
|
||||
}
|
||||
|
||||
- (void)addRelationshipAtFrame:(CGRect)frame
|
||||
micPairText:(NSString *)micPairText
|
||||
leftUid:(NSInteger)leftUid
|
||||
rightUid:(NSInteger)rightUid
|
||||
cpList:(NSArray *)cpList {
|
||||
// 基础容器
|
||||
[self addMidpointRectAtFrame:frame micPairText:micPairText autoPlaySVGA:NO];
|
||||
|
||||
// 存储中点矩形的用户信息
|
||||
NSString *frameKey = NSStringFromCGRect(frame);
|
||||
self.midpointRectInfo[frameKey] = @{
|
||||
@"leftUid": @(leftUid),
|
||||
@"rightUid": @(rightUid)
|
||||
};
|
||||
|
||||
if (cpList.count < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (leftUid <= 0 && rightUid <= 0) {
|
||||
return;
|
||||
}
|
||||
// 遍历匹配并播放对应等级的SVGA
|
||||
for (MicCpInfoModel *obj in cpList) {
|
||||
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));
|
||||
NSString *svgaName = [NSString stringWithFormat:@"mic_cp_lv%ld", (long)safeLevel];
|
||||
[self playSVGAAnimationAtFrame:frame withNamed:svgaName];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Cache & Snapshot
|
||||
|
||||
- (void)setCpListCache:(NSArray *)cpList {
|
||||
self.cachedCpListInternal = cpList ?: @[];
|
||||
}
|
||||
|
||||
- (void)rebuildMicSnapshotWithStageView:(id)stageView micCount:(NSInteger)micCount {
|
||||
[self.micPositionToUidInternal removeAllObjects];
|
||||
for (NSInteger i = 0; i < micCount; i++) {
|
||||
NSInteger uid = -1;
|
||||
if ([stageView respondsToSelector:@selector(findMicroViewByIndex:)]) {
|
||||
UIView *microView = [(StageView *)stageView findMicroViewByIndex:i];
|
||||
if ([microView respondsToSelector:@selector(getUser)]) {
|
||||
id userInfo = [microView performSelector:@selector(getUser)];
|
||||
if (userInfo && [userInfo respondsToSelector:@selector(uid)]) {
|
||||
uid = (NSInteger)[userInfo valueForKey:@"uid"];
|
||||
if (uid <= 0) { uid = -1; }
|
||||
}
|
||||
}
|
||||
}
|
||||
self.micPositionToUidInternal[@(i)] = @(uid);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)uidListForChangedMicWithStageView:(id)stageView micCount:(NSInteger)micCount {
|
||||
NSMutableSet<NSNumber *> *result = [NSMutableSet set];
|
||||
// 计算新状态
|
||||
NSMutableArray<NSNumber *> *newUids = [NSMutableArray arrayWithCapacity:micCount];
|
||||
for (NSInteger i = 0; i < micCount; i++) {
|
||||
NSInteger uid = -1;
|
||||
if ([stageView respondsToSelector:@selector(findMicroViewByIndex:)]) {
|
||||
UIView *microView = [(StageView *)stageView findMicroViewByIndex:i];
|
||||
if ([microView respondsToSelector:@selector(getUser)]) {
|
||||
id userInfo = [microView performSelector:@selector(getUser)];
|
||||
if (userInfo && [userInfo respondsToSelector:@selector(uid)]) {
|
||||
uid = (NSInteger)[userInfo valueForKey:@"uid"];
|
||||
if (uid <= 0) { uid = -1; }
|
||||
}
|
||||
}
|
||||
}
|
||||
[newUids addObject:@(uid)];
|
||||
NSNumber *oldVal = self.micPositionToUidInternal[@(i)] ?: @(-1);
|
||||
if (oldVal.integerValue != uid) {
|
||||
// 该坑位发生变化,将该位及其左右邻居加入
|
||||
if (uid > 0) { [result addObject:@(uid)]; }
|
||||
NSInteger leftIdx = i - 1;
|
||||
NSInteger rightIdx = i + 1;
|
||||
if (leftIdx >= 0) {
|
||||
NSInteger leftUid = (leftIdx < newUids.count) ? newUids[leftIdx].integerValue : -1;
|
||||
if (leftUid > 0) { [result addObject:@(leftUid)]; }
|
||||
}
|
||||
if (rightIdx < micCount) {
|
||||
NSInteger rightUid = (rightIdx < newUids.count) ? newUids[rightIdx].integerValue : -1;
|
||||
if (rightUid > 0) { [result addObject:@(rightUid)]; }
|
||||
}
|
||||
}
|
||||
}
|
||||
// 更新快照
|
||||
for (NSInteger i = 0; i < micCount; i++) {
|
||||
self.micPositionToUidInternal[@(i)] = newUids[i];
|
||||
}
|
||||
// 转字符串数组
|
||||
NSMutableArray<NSString *> *uids = [NSMutableArray arrayWithCapacity:result.count];
|
||||
for (NSNumber *num in result) { [uids addObject:num.stringValue]; }
|
||||
return uids;
|
||||
}
|
||||
|
||||
- (NSDictionary<NSString *, NSArray<NSNumber *> *> *)diffMicChangeWithStageView:(id)stageView micCount:(NSInteger)micCount {
|
||||
NSMutableArray<NSNumber *> *added = [NSMutableArray array];
|
||||
NSMutableArray<NSNumber *> *removed = [NSMutableArray array];
|
||||
for (NSInteger i = 0; i < micCount; i++) {
|
||||
NSInteger uid = -1;
|
||||
if ([stageView respondsToSelector:@selector(findMicroViewByIndex:)]) {
|
||||
UIView *microView = [(StageView *)stageView findMicroViewByIndex:i];
|
||||
if ([microView respondsToSelector:@selector(getUser)]) {
|
||||
id userInfo = [microView performSelector:@selector(getUser)];
|
||||
if (userInfo && [userInfo respondsToSelector:@selector(uid)]) {
|
||||
uid = (NSInteger)[userInfo valueForKey:@"uid"];
|
||||
if (uid <= 0) { uid = -1; }
|
||||
}
|
||||
}
|
||||
}
|
||||
NSInteger oldUid = (self.micPositionToUidInternal[@(i)] ?: @(-1)).integerValue;
|
||||
if (uid != oldUid) {
|
||||
if (oldUid > 0 && uid == -1) { // 下麦
|
||||
[removed addObject:@(oldUid)];
|
||||
} else if (uid > 0) { // 上麦或换位到此
|
||||
[added addObject:@(uid)];
|
||||
}
|
||||
}
|
||||
self.micPositionToUidInternal[@(i)] = @(uid);
|
||||
}
|
||||
return @{ @"added": added.copy, @"removed": removed.copy };
|
||||
}
|
||||
|
||||
- (void)removeCpEntriesForUids:(NSArray<NSNumber *> *)uids {
|
||||
if (uids.count == 0 || self.cachedCpListInternal.count == 0) { return; }
|
||||
NSMutableArray *filtered = [NSMutableArray array];
|
||||
NSSet *uidSet = [NSSet setWithArray:uids];
|
||||
for (id obj in self.cachedCpListInternal) {
|
||||
NSInteger a = 0, b = 0;
|
||||
if ([obj respondsToSelector:@selector(uid)]) { a = (NSInteger)[obj valueForKey:@"uid"]; }
|
||||
if ([obj respondsToSelector:@selector(loverUid)]) { b = (NSInteger)[obj valueForKey:@"loverUid"]; }
|
||||
if (![uidSet containsObject:@(a)] && ![uidSet containsObject:@(b)]) {
|
||||
[filtered addObject:obj];
|
||||
}
|
||||
}
|
||||
self.cachedCpListInternal = filtered.copy;
|
||||
}
|
||||
|
||||
- (void)mergeCpListCache:(NSArray *)cpList replaceForUids:(NSArray<NSNumber *> *)uids {
|
||||
// 先移除相关uid的旧数据
|
||||
[self removeCpEntriesForUids:uids];
|
||||
if (cpList.count == 0) { return; }
|
||||
// 合并新数据
|
||||
NSMutableArray *merged = [self.cachedCpListInternal mutableCopy] ?: [NSMutableArray array];
|
||||
[merged addObjectsFromArray:cpList];
|
||||
self.cachedCpListInternal = merged.copy;
|
||||
}
|
||||
|
||||
- (void)removeAllMidpointRects {
|
||||
// 停止所有SVGA动画
|
||||
[self stopAllSVGAAnimations];
|
||||
|
||||
// 移除所有中点矩形
|
||||
for (UIView *rectView in self.midpointRects) {
|
||||
[rectView removeFromSuperview];
|
||||
}
|
||||
[self.midpointRects removeAllObjects];
|
||||
[self.midpointRectInfo removeAllObjects];
|
||||
|
||||
NSLog(@"🔧 移除所有中点矩形");
|
||||
}
|
||||
|
||||
- (void)removeMidpointRectAtFrame:(CGRect)frame {
|
||||
// 停止对应位置的SVGA动画
|
||||
NSString *frameKey = NSStringFromCGRect(frame);
|
||||
SVGAImageView *svgaView = self.svgaViews[frameKey];
|
||||
if (svgaView) {
|
||||
[svgaView stopAnimation];
|
||||
[svgaView removeFromSuperview];
|
||||
[self.svgaViews removeObjectForKey:frameKey];
|
||||
}
|
||||
|
||||
// 移除对应位置的中点矩形
|
||||
for (UIView *rectView in [self.midpointRects copy]) {
|
||||
if (CGRectEqualToRect(rectView.frame, frame)) {
|
||||
[rectView removeFromSuperview];
|
||||
[self.midpointRects removeObject:rectView];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 移除对应的用户信息
|
||||
[self.midpointRectInfo removeObjectForKey:frameKey];
|
||||
|
||||
NSLog(@"🔧 移除指定位置的中点矩形: %@", NSStringFromCGRect(frame));
|
||||
}
|
||||
|
||||
- (void)playSVGAAnimationAtFrame:(CGRect)frame {
|
||||
// 随机选择一个SVGA资源
|
||||
NSArray *svgaFiles = @[@"mic_cp_lv1", @"mic_cp_lv2", @"mic_cp_lv3", @"mic_cp_lv4", @"mic_cp_lv5"];
|
||||
NSString *randomSVGA = svgaFiles[arc4random_uniform((uint32_t)svgaFiles.count)];
|
||||
[self playSVGAAnimationAtFrame:frame withNamed:randomSVGA];
|
||||
}
|
||||
|
||||
// 播放指定资源名SVGA
|
||||
- (void)playSVGAAnimationAtFrame:(CGRect)frame withNamed:(NSString *)resourceName {
|
||||
if (resourceName.length == 0) { return; }
|
||||
NSString *svgaPath = [[NSBundle mainBundle] pathForResource:resourceName ofType:@"svga"];
|
||||
if (!svgaPath) {
|
||||
NSLog(@"⚠️ 找不到SVGA文件: %@", resourceName);
|
||||
return;
|
||||
}
|
||||
SVGAImageView *svgaView = [[SVGAImageView alloc] initWithFrame:frame];
|
||||
svgaView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
svgaView.userInteractionEnabled = NO;
|
||||
svgaView.backgroundColor = [UIColor clearColor];
|
||||
[self.containerView addSubview:svgaView];
|
||||
NSString *frameKey = NSStringFromCGRect(frame);
|
||||
self.svgaViews[frameKey] = svgaView;
|
||||
[self.svgaParser parseWithURL:[NSURL fileURLWithPath:svgaPath]
|
||||
completionBlock:^(SVGAVideoEntity * _Nonnull videoItem) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
svgaView.videoItem = videoItem;
|
||||
svgaView.loops = 0;
|
||||
svgaView.clearsAfterStop = YES;
|
||||
[svgaView startAnimation];
|
||||
NSLog(@"🎬 开始播放SVGA动画: %@, frame: %@", resourceName, NSStringFromCGRect(frame));
|
||||
});
|
||||
} failureBlock:^(NSError * _Nonnull error) {
|
||||
NSLog(@"❌ SVGA动画解析失败: %@, error: %@", resourceName, error.localizedDescription);
|
||||
[svgaView removeFromSuperview];
|
||||
[self.svgaViews removeObjectForKey:frameKey];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)stopAllSVGAAnimations {
|
||||
for (SVGAImageView *svgaView in self.svgaViews.allValues) {
|
||||
[svgaView stopAnimation];
|
||||
[svgaView removeFromSuperview];
|
||||
}
|
||||
[self.svgaViews removeAllObjects];
|
||||
|
||||
NSLog(@"🔧 停止所有SVGA动画");
|
||||
}
|
||||
|
||||
- (void)removeMidpointRectsForUids:(NSArray<NSNumber *> *)uids {
|
||||
if (uids.count == 0) {
|
||||
NSLog(@"🔧 基于UID清理:没有需要清理的用户,跳过处理");
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"🔧 基于UID清理:开始清理用户 %@ 相关的中点矩形和SVGA", uids);
|
||||
|
||||
NSSet<NSNumber *> *uidSet = [NSSet setWithArray:uids];
|
||||
NSMutableArray<NSString *> *frameKeysToRemove = [NSMutableArray array];
|
||||
|
||||
// 遍历所有中点矩形信息,找出涉及下麦用户的矩形
|
||||
for (NSString *frameKey in self.midpointRectInfo.allKeys) {
|
||||
NSDictionary *rectInfo = self.midpointRectInfo[frameKey];
|
||||
NSNumber *leftUid = rectInfo[@"leftUid"];
|
||||
NSNumber *rightUid = rectInfo[@"rightUid"];
|
||||
|
||||
// 检查是否涉及下麦用户
|
||||
if ([uidSet containsObject:leftUid] || [uidSet containsObject:rightUid]) {
|
||||
[frameKeysToRemove addObject:frameKey];
|
||||
}
|
||||
}
|
||||
|
||||
// 移除相关的中点矩形和SVGA动画
|
||||
for (NSString *frameKey in frameKeysToRemove) {
|
||||
CGRect frame = CGRectFromString(frameKey);
|
||||
[self removeMidpointRectAtFrame:frame];
|
||||
}
|
||||
|
||||
NSLog(@"🔧 基于UID清理完成:移除了 %lu 个中点矩形和SVGA动画", (unsigned long)frameKeysToRemove.count);
|
||||
}
|
||||
|
||||
- (void)handleDownMicEvent:(NSArray<NSNumber *> *)downMicUids
|
||||
stageView:(id)stageView
|
||||
micCount:(NSInteger)micCount {
|
||||
if (downMicUids.count == 0) {
|
||||
NSLog(@"🔧 处理下麦事件:没有下麦用户,跳过处理");
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"🔧 处理下麦事件:下麦用户 %@", downMicUids);
|
||||
|
||||
// 1. 从缓存中移除相关用户的CP关系
|
||||
[self removeCpEntriesForUids:downMicUids];
|
||||
|
||||
// 2. 基于UID精确清理相关的中点矩形和SVGA动画
|
||||
[self removeMidpointRectsForUids:downMicUids];
|
||||
|
||||
// 3. 更新麦位快照
|
||||
[self rebuildMicSnapshotWithStageView:stageView micCount:micCount];
|
||||
|
||||
NSLog(@"🔧 下麦事件处理完成");
|
||||
}
|
||||
|
||||
#pragma mark - Dealloc
|
||||
|
||||
- (void)dealloc {
|
||||
[self removeAllMidpointRects];
|
||||
}
|
||||
|
||||
@end
|
@@ -0,0 +1,167 @@
|
||||
# 头饰 Turbo Mode Bug 修复报告
|
||||
|
||||
## 问题描述
|
||||
|
||||
在头饰 turbo mode 控制功能中发现两个关键问题:
|
||||
|
||||
1. **用户在麦位上开启 turbo mode 时,头饰会消失不见**
|
||||
2. **Turbo mode 开启时,切换麦位或上麦,头饰会正常播放而不是只显示第一帧**
|
||||
|
||||
## 问题分析
|
||||
|
||||
### 问题1:头饰消失
|
||||
**原因**:在 `setHeadWearToFirstFrameOnly` 方法中,调用 `stopAnimating` 和 `stopAnimation` 后,头饰内容可能被清空,导致显示消失。
|
||||
|
||||
**影响**:用户体验差,头饰功能完全失效。
|
||||
|
||||
### 问题2:切换麦位时头饰正常播放
|
||||
**原因**:SVGA 头饰的处理逻辑有问题,在设置 `imageName` 后立即调用 `applyTurboModeToHeadWear`,但此时 SVGA 可能还没有加载完成,导致 turbo mode 状态没有正确应用。
|
||||
|
||||
**影响**:Turbo mode 功能不完整,性能优化效果打折扣。
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 1. 修复头饰消失问题
|
||||
|
||||
**修改文件**:`MicroView.m`
|
||||
**修改方法**:`setHeadWearToFirstFrameOnly`
|
||||
|
||||
**修复内容**:
|
||||
- 添加详细的调试日志来跟踪执行流程
|
||||
- 在停止动画后检查头饰内容是否仍然存在
|
||||
- 如果内容消失,添加恢复逻辑(预留接口)
|
||||
|
||||
```objective-c
|
||||
- (void)setHeadWearToFirstFrameOnly {
|
||||
NSLog(@"🎮 MicroView: 设置头饰为只显示第一帧模式");
|
||||
|
||||
// 对于 YYAnimatedImageView,停止动画并显示第一帧
|
||||
if (!self.headWearImageView.hidden) {
|
||||
NSLog(@"🎮 MicroView: 停止 YYAnimatedImageView 动画");
|
||||
[self.headWearImageView stopAnimating];
|
||||
|
||||
// 确保图片仍然可见,手动设置显示第一帧
|
||||
if (self.headWearImageView.image) {
|
||||
// YYAnimatedImageView 停止动画后会自动显示第一帧
|
||||
// 如果图片消失,尝试重新设置
|
||||
if (!self.headWearImageView.image) {
|
||||
NSLog(@"🎮 MicroView: YYAnimatedImageView 图片消失,尝试恢复");
|
||||
// 这里可能需要重新加载或设置图片
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 对于 SVGAImageView,停止动画
|
||||
if (!self.headWearSVGAImageView.hidden) {
|
||||
NSLog(@"🎮 MicroView: 停止 SVGAImageView 动画");
|
||||
[self.headWearSVGAImageView stopAnimation];
|
||||
|
||||
// 确保 SVGA 第一帧仍然显示
|
||||
if (self.headWearSVGAImageView.videoItem) {
|
||||
// SVGA 停止动画后会自动显示第一帧
|
||||
// 如果内容消失,可能需要手动设置
|
||||
if (!self.headWearSVGAImageView.videoItem) {
|
||||
NSLog(@"🎮 MicroView: SVGAImageView 内容消失,尝试恢复");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 修复切换麦位时头饰正常播放问题
|
||||
|
||||
**修改文件**:`MicroView.m`
|
||||
**修改方法**:`configUser` 中的头饰配置逻辑
|
||||
|
||||
**修复内容**:
|
||||
- 为 SVGA 头饰添加延迟处理,确保加载完成后再应用 turbo mode
|
||||
- 添加详细的调试日志来跟踪配置流程
|
||||
- 改进错误处理逻辑
|
||||
|
||||
```objective-c
|
||||
if ([userInfo isHeadWearSVGA]) {
|
||||
NSLog(@"🎮 MicroView: 配置 SVGA 头饰: %@", headWearUrl);
|
||||
self.headWearSVGAImageView.hidden = NO;
|
||||
[self.headWearSVGAImageView setImageName:headWearUrl];
|
||||
|
||||
// 对于 SVGA,需要等待加载完成后再应用 turbo mode 状态
|
||||
// 这里使用延迟来确保 SVGA 加载完成
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self applyTurboModeToHeadWear];
|
||||
});
|
||||
} else {
|
||||
NSLog(@"🎮 MicroView: 配置精灵图头饰: %@", headWearUrl);
|
||||
self.headWearImageView.hidden = NO;
|
||||
NSURL *url = [NSURL URLWithString:headWearUrl];
|
||||
@kWeakify(self);
|
||||
[self.manager loadSpriteSheetImageWithURL:url completionBlock:^(YYSpriteSheetImage * _Nullable sprit) {
|
||||
@kStrongify(self);
|
||||
self.headWearImageView.image = sprit;
|
||||
NSLog(@"🎮 MicroView: 精灵图头饰加载完成,应用 turbo mode 状态");
|
||||
// 加载完成后应用 turbo mode 状态
|
||||
[self applyTurboModeToHeadWear];
|
||||
} failureBlock:^(NSError * _Nullable error) {
|
||||
NSLog(@"🎮 MicroView: 精灵图头饰加载失败: %@", error.localizedDescription);
|
||||
}];
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 添加调试日志
|
||||
|
||||
**目的**:便于问题排查和功能验证
|
||||
|
||||
**添加位置**:
|
||||
- `handleTurboModeStateChanged`:跟踪状态变化
|
||||
- `updateHeadWearForTurboMode`:跟踪更新流程
|
||||
- `setHeadWearToFirstFrameOnly`:跟踪第一帧设置
|
||||
- `setHeadWearToNormalPlayback`:跟踪正常播放设置
|
||||
- `applyTurboModeToHeadWear`:跟踪应用流程
|
||||
- 头饰配置逻辑:跟踪配置流程
|
||||
|
||||
## 修复效果
|
||||
|
||||
### 预期改进
|
||||
|
||||
1. **头饰不再消失**:
|
||||
- 用户在麦位上开启 turbo mode 时,头饰会正确显示第一帧
|
||||
- 添加了内容检查逻辑,如果出现问题会有日志提示
|
||||
|
||||
2. **切换麦位时正确应用 turbo mode**:
|
||||
- SVGA 头饰会在加载完成后正确应用 turbo mode 状态
|
||||
- 精灵图头饰在加载完成后也会正确应用 turbo mode 状态
|
||||
|
||||
3. **更好的调试体验**:
|
||||
- 详细的日志输出,便于问题排查
|
||||
- 清晰的执行流程跟踪
|
||||
|
||||
### 测试建议
|
||||
|
||||
1. **测试场景1**:用户在麦位上开启 turbo mode
|
||||
- 预期:头饰显示第一帧,不播放动画
|
||||
- 验证:检查日志输出,确认执行流程正确
|
||||
|
||||
2. **测试场景2**:Turbo mode 开启时切换麦位
|
||||
- 预期:新头饰只显示第一帧,不播放动画
|
||||
- 验证:检查日志输出,确认 turbo mode 状态正确应用
|
||||
|
||||
3. **测试场景3**:Turbo mode 开启时上麦
|
||||
- 预期:头饰只显示第一帧,不播放动画
|
||||
- 验证:检查日志输出,确认配置流程正确
|
||||
|
||||
4. **测试场景4**:关闭 turbo mode
|
||||
- 预期:所有头饰恢复正常动画播放
|
||||
- 验证:检查日志输出,确认状态切换正确
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **延迟时间**:SVGA 头饰的延迟时间设置为 0.1 秒,如果发现不够可以适当调整
|
||||
2. **日志输出**:生产环境可能需要移除或减少日志输出
|
||||
3. **性能影响**:添加的检查逻辑对性能影响很小,但需要注意监控
|
||||
4. **兼容性**:修复保持了与现有功能的完全兼容性
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. **更精确的 SVGA 加载检测**:使用 SVGA 的回调机制而不是延迟
|
||||
2. **头饰恢复机制**:如果头饰消失,实现自动恢复逻辑
|
||||
3. **性能监控**:添加性能指标来监控 turbo mode 的效果
|
||||
4. **用户反馈**:收集用户对 turbo mode 效果的反馈
|
@@ -0,0 +1,137 @@
|
||||
# 头饰 Turbo Mode 集成实现
|
||||
|
||||
## 功能概述
|
||||
|
||||
在 MicroView.m 中集成了 turbo mode 状态控制,当 turbo mode 开启时,头饰只显示第一帧不播放动画;当 turbo mode 关闭时,头饰按原逻辑正常播放动画。
|
||||
|
||||
## 实现细节
|
||||
|
||||
### 1. 添加 Turbo Mode 状态监听
|
||||
|
||||
```objective-c
|
||||
// 在 MicroView 接口中添加状态属性
|
||||
@property (nonatomic, assign) BOOL isTurboModeEnabled;
|
||||
|
||||
// 在初始化时设置监听
|
||||
- (void)setupTurboModeListener {
|
||||
// 初始化 turbo mode 状态
|
||||
self.isTurboModeEnabled = [[TurboModeStateManager sharedManager] isTurboModeEnabled];
|
||||
|
||||
// 监听 turbo mode 状态变化
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleTurboModeStateChanged:)
|
||||
name:@"TurboModeStateChanged"
|
||||
object:nil];
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 状态变化处理
|
||||
|
||||
```objective-c
|
||||
- (void)handleTurboModeStateChanged:(NSNotification *)notification {
|
||||
NSDictionary *userInfo = notification.userInfo;
|
||||
BOOL enabled = [userInfo[@"enabled"] boolValue];
|
||||
|
||||
self.isTurboModeEnabled = enabled;
|
||||
|
||||
// 如果当前有头饰,需要重新配置以应用新的 turbo mode 状态
|
||||
if (self.userInfo && (self.userInfo.headwearEffect.length || self.userInfo.headWearUrl.length || self.userInfo.headwearPic.length)) {
|
||||
[self updateHeadWearForTurboMode];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 头饰动画控制
|
||||
|
||||
```objective-c
|
||||
- (void)updateHeadWearForTurboMode {
|
||||
if (self.isTurboModeEnabled) {
|
||||
// Turbo mode 开启:只显示第一帧,不播放动画
|
||||
[self setHeadWearToFirstFrameOnly];
|
||||
} else {
|
||||
// Turbo mode 关闭:正常播放动画
|
||||
[self setHeadWearToNormalPlayback];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHeadWearToFirstFrameOnly {
|
||||
// 对于 YYAnimatedImageView,停止动画并显示第一帧
|
||||
if (!self.headWearImageView.hidden) {
|
||||
[self.headWearImageView stopAnimating];
|
||||
// YYAnimatedImageView 会自动显示第一帧
|
||||
}
|
||||
|
||||
// 对于 SVGAImageView,停止动画
|
||||
if (!self.headWearSVGAImageView.hidden) {
|
||||
[self.headWearSVGAImageView stopAnimation];
|
||||
// SVGA 会自动显示第一帧
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHeadWearToNormalPlayback {
|
||||
// 对于 YYAnimatedImageView,恢复动画播放
|
||||
if (!self.headWearImageView.hidden && self.headWearImageView.image) {
|
||||
[self.headWearImageView startAnimating];
|
||||
}
|
||||
|
||||
// 对于 SVGAImageView,恢复动画播放
|
||||
if (!self.headWearSVGAImageView.hidden && self.headWearSVGAImageView.videoItem) {
|
||||
[self.headWearSVGAImageView startAnimation];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 头饰配置集成
|
||||
|
||||
在 `configUser` 方法中,头饰加载完成后会自动应用当前的 turbo mode 状态:
|
||||
|
||||
```objective-c
|
||||
[self.manager loadSpriteSheetImageWithURL:url completionBlock:^(YYSpriteSheetImage * _Nullable sprit) {
|
||||
@kStrongify(self);
|
||||
self.headWearImageView.image = sprit;
|
||||
// 加载完成后应用 turbo mode 状态
|
||||
[self applyTurboModeToHeadWear];
|
||||
} failureBlock:^(NSError * _Nullable error) {
|
||||
}];
|
||||
```
|
||||
|
||||
### 5. 内存管理
|
||||
|
||||
```objective-c
|
||||
- (void)dealloc {
|
||||
// 移除 turbo mode 状态变化监听
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"TurboModeStateChanged" object:nil];
|
||||
}
|
||||
```
|
||||
|
||||
## 支持的头饰类型
|
||||
|
||||
1. **YYAnimatedImageView (精灵图头饰)**
|
||||
- Turbo mode 开启:调用 `stopAnimating` 停止动画,自动显示第一帧
|
||||
- Turbo mode 关闭:调用 `startAnimating` 恢复动画播放
|
||||
|
||||
2. **SVGAImageView (SVGA 头饰)**
|
||||
- Turbo mode 开启:调用 `stopAnimation` 停止动画,自动显示第一帧
|
||||
- Turbo mode 关闭:调用 `startAnimation` 恢复动画播放
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. **初始化阶段**:MicroView 创建时自动获取当前 turbo mode 状态并设置监听
|
||||
2. **头饰配置阶段**:用户配置头饰时,加载完成后自动应用当前 turbo mode 状态
|
||||
3. **状态变化阶段**:turbo mode 状态改变时,自动更新所有现有头饰的播放状态
|
||||
4. **销毁阶段**:MicroView 销毁时自动移除通知监听
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. 开启 turbo mode,验证头饰只显示第一帧不播放动画
|
||||
2. 关闭 turbo mode,验证头饰恢复正常动画播放
|
||||
3. 在头饰播放过程中切换 turbo mode 状态,验证状态切换的实时性
|
||||
4. 测试不同类型的头饰(精灵图、SVGA)在 turbo mode 下的表现
|
||||
5. 验证多个 MicroView 实例同时响应 turbo mode 状态变化
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 该实现不会影响头饰的加载和显示逻辑,只是在播放控制层面进行干预
|
||||
- 支持实时状态切换,无需重新加载头饰资源
|
||||
- 与现有的 VIP 麦位头饰隐藏逻辑兼容
|
||||
- 内存管理正确,避免通知监听泄漏
|
@@ -27,6 +27,8 @@
|
||||
///Model
|
||||
#import "RoomFaceInfoModel.h"
|
||||
#import "RoomResourceManager.h"
|
||||
///Turbo Mode
|
||||
#import "TurboModeStateManager.h"
|
||||
|
||||
#define kScpaces 13
|
||||
|
||||
@@ -76,6 +78,11 @@
|
||||
|
||||
@property(nonatomic, strong) UILabel *goldLabel;
|
||||
|
||||
///Turbo Mode 状态
|
||||
@property (nonatomic, assign) BOOL isTurboModeEnabled;
|
||||
|
||||
@property (nonatomic, strong) UIImage <YYAnimatedImage> *tempSprites;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -86,6 +93,7 @@
|
||||
if (self) {
|
||||
[self initSubViews];
|
||||
[self initSubViewConstraints];
|
||||
[self setupTurboModeListener];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -670,7 +678,16 @@
|
||||
if (headWearUrl.length > 0 && !userInfo.vipMic) {
|
||||
if ([userInfo isHeadWearSVGA]) {
|
||||
self.headWearSVGAImageView.hidden = NO;
|
||||
[self.headWearSVGAImageView setImageName:headWearUrl];
|
||||
SVGAParser *parse = [[SVGAParser alloc] init];
|
||||
@kWeakify(self);
|
||||
[parse parseWithURL:[NSURL URLWithString:headWearUrl]
|
||||
completionBlock:^(SVGAVideoEntity * _Nullable videoItem) {
|
||||
@kStrongify(self);
|
||||
if (videoItem) {
|
||||
self.headWearSVGAImageView.videoItem = videoItem;
|
||||
[self applyTurboModeToHeadWear];
|
||||
}
|
||||
} failureBlock:^(NSError * _Nullable error) { }];
|
||||
} else {
|
||||
self.headWearImageView.hidden = NO;
|
||||
NSURL *url = [NSURL URLWithString:headWearUrl];
|
||||
@@ -678,6 +695,8 @@
|
||||
[self.manager loadSpriteSheetImageWithURL:url completionBlock:^(YYSpriteSheetImage * _Nullable sprit) {
|
||||
@kStrongify(self);
|
||||
self.headWearImageView.image = sprit;
|
||||
// 加载完成后应用 turbo mode 状态
|
||||
[self applyTurboModeToHeadWear];
|
||||
} failureBlock:^(NSError * _Nullable error) {
|
||||
}];
|
||||
}
|
||||
@@ -787,6 +806,82 @@
|
||||
return hexComponent / 255.0;
|
||||
}
|
||||
|
||||
#pragma mark - Turbo Mode Management
|
||||
|
||||
- (void)setupTurboModeListener {
|
||||
// 初始化 turbo mode 状态
|
||||
self.isTurboModeEnabled = [[TurboModeStateManager sharedManager] isTurboModeEnabled];
|
||||
|
||||
// 监听 turbo mode 状态变化
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleTurboModeStateChanged:)
|
||||
name:@"TurboModeStateChanged"
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)handleTurboModeStateChanged:(NSNotification *)notification {
|
||||
NSDictionary *userInfo = notification.userInfo;
|
||||
BOOL enabled = [userInfo[@"enabled"] boolValue];
|
||||
|
||||
self.isTurboModeEnabled = enabled;
|
||||
|
||||
// 如果当前有头饰,需要重新配置以应用新的 turbo mode 状态
|
||||
if (self.userInfo && (self.userInfo.headwearEffect.length || self.userInfo.headWearUrl.length || self.userInfo.headwearPic.length)) {
|
||||
[self updateHeadWearForTurboMode];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateHeadWearForTurboMode {
|
||||
if (self.isTurboModeEnabled) {
|
||||
// Turbo mode 开启:只显示第一帧,不播放动画
|
||||
[self setHeadWearToFirstFrameOnly];
|
||||
} else {
|
||||
// Turbo mode 关闭:正常播放动画
|
||||
[self setHeadWearToNormalPlayback];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHeadWearToFirstFrameOnly {
|
||||
// 对于 YYAnimatedImageView,停止动画并显示第一帧
|
||||
if (!self.headWearImageView.hidden) {
|
||||
[self.headWearImageView stopAnimating];
|
||||
UIImage <YYAnimatedImage> *sprites = (YYSpriteSheetImage *)self.headWearImageView.image;
|
||||
self.tempSprites = sprites;
|
||||
UIImage *firstFrame = [sprites animatedImageFrameAtIndex:0];
|
||||
if (firstFrame) {
|
||||
self.headWearImageView.image = firstFrame;
|
||||
}
|
||||
} else if (!self.headWearSVGAImageView.hidden) {
|
||||
// 对于 SVGAImageView,停止动画
|
||||
[self.headWearSVGAImageView pauseAnimation];
|
||||
// [self.headWearSVGAImageView stepToFrame:1 andPlay:NO];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHeadWearToNormalPlayback {
|
||||
|
||||
// 对于 YYAnimatedImageView,恢复动画播放
|
||||
if (!self.headWearImageView.hidden && self.headWearImageView.image) {
|
||||
[self.headWearImageView startAnimating];
|
||||
}
|
||||
|
||||
// 对于 SVGAImageView,恢复动画播放
|
||||
if (!self.headWearSVGAImageView.hidden && self.headWearSVGAImageView.videoItem) {
|
||||
self.headWearImageView.image = self.tempSprites;
|
||||
[self.headWearSVGAImageView startAnimation];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applyTurboModeToHeadWear {
|
||||
if (self.isTurboModeEnabled) {
|
||||
// Turbo mode 开启:只显示第一帧,不播放动画
|
||||
[self setHeadWearToFirstFrameOnly];
|
||||
} else {
|
||||
// Turbo mode 关闭:正常播放动画
|
||||
[self setHeadWearToNormalPlayback];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Getters And Setters
|
||||
- (NetImageView *)avatarImageView {
|
||||
if (!_avatarImageView) {
|
||||
@@ -1083,4 +1178,9 @@
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
// 移除 turbo mode 状态变化监听
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"TurboModeStateChanged" object:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -6,11 +6,14 @@
|
||||
//
|
||||
|
||||
#import "StageView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NineteenMicStageView : StageView
|
||||
|
||||
@property (nonatomic, strong, readonly) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -7,6 +7,7 @@
|
||||
|
||||
#import "NineteenMicStageView.h"
|
||||
#import "SocialMicroView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
|
||||
//第一行距离顶部
|
||||
#define firstRowTopMargin 60
|
||||
@@ -32,8 +33,22 @@
|
||||
|
||||
static const NSInteger kMicCountPerRow = 5;
|
||||
|
||||
@interface NineteenMicStageView ()
|
||||
|
||||
@property (nonatomic, strong) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
@end
|
||||
|
||||
@implementation NineteenMicStageView
|
||||
|
||||
- (instancetype)initWithDelegate:(id<RoomHostDelegate>)delegate {
|
||||
if (self = [super initWithDelegate:delegate]) {
|
||||
// 初始化中点矩形管理器
|
||||
_midpointRectManager = [[MicMidpointRectManager alloc] initWithContainerView:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger)countOfMicroView {
|
||||
return 19; // 麦位总数
|
||||
}
|
||||
@@ -128,4 +143,64 @@ static const NSInteger kMicCountPerRow = 5;
|
||||
return [self convertPoint:center toView:nil];
|
||||
}
|
||||
|
||||
// 19麦位布局:复杂布局,仅同一行且相邻列(列差1)返回中点75x75
|
||||
- (CGRect)rectForMidpointBetweenMicAtIndex:(NSInteger)firstIndex andIndex:(NSInteger)secondIndex {
|
||||
if (firstIndex == secondIndex) return CGRectZero;
|
||||
NSInteger left = MIN(firstIndex, secondIndex);
|
||||
NSInteger right = MAX(firstIndex, secondIndex);
|
||||
|
||||
// 检查是否在同一行且相邻
|
||||
BOOL sameRow = NO;
|
||||
NSInteger leftCol = -1, rightCol = -1;
|
||||
|
||||
// 第一行:0-4 (5列)
|
||||
if (left >= 0 && left <= 4 && right >= 0 && right <= 4) {
|
||||
sameRow = YES;
|
||||
leftCol = left;
|
||||
rightCol = right;
|
||||
}
|
||||
// 第二行:5-6, 8-9 (4列,中间有Boss位7)
|
||||
else if (((left >= 5 && left <= 6) && (right >= 5 && right <= 6)) ||
|
||||
((left >= 8 && left <= 9) && (right >= 8 && right <= 9))) {
|
||||
sameRow = YES;
|
||||
leftCol = (left <= 6) ? left - 5 : left - 6;
|
||||
rightCol = (right <= 6) ? right - 5 : right - 6;
|
||||
}
|
||||
// 第三行:10-13 (4列),但排除11-12
|
||||
else if (left >= 10 && left <= 13 && right >= 10 && right <= 13) {
|
||||
// 排除11-12的相邻对
|
||||
if (left == 11 && right == 12) {
|
||||
return CGRectZero;
|
||||
}
|
||||
sameRow = YES;
|
||||
leftCol = left - 10;
|
||||
rightCol = right - 10;
|
||||
}
|
||||
// 第四行:14-18 (5列)
|
||||
else if (left >= 14 && left <= 18 && right >= 14 && right <= 18) {
|
||||
sameRow = YES;
|
||||
leftCol = left - 14;
|
||||
rightCol = right - 14;
|
||||
}
|
||||
|
||||
if (!sameRow) return CGRectZero;
|
||||
|
||||
// 检查是否相邻(列差为1)
|
||||
if ((rightCol - leftCol) != 1) return CGRectZero;
|
||||
|
||||
CGRect l = [self rectForViewAtIndex:left];
|
||||
CGRect r = [self rectForViewAtIndex:right];
|
||||
if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero;
|
||||
|
||||
CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0;
|
||||
|
||||
// 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐
|
||||
CGFloat leftTopY = CGRectGetMinY(l);
|
||||
CGFloat rightTopY = CGRectGetMinY(r);
|
||||
CGFloat midTopY = (leftTopY + rightTopY) / 2.0;
|
||||
|
||||
CGFloat size = 75.0;
|
||||
return CGRectMake(midX - size / 2.0, midTopY, size, size);
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -6,11 +6,18 @@
|
||||
//
|
||||
|
||||
#import "StageView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SocialStageView : StageView
|
||||
|
||||
@property (nonatomic, strong, readonly) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
/// 返回两个横向相邻麦位中点位置的矩形区域(75x75)。
|
||||
/// 仅当两个索引同一行且相邻,并且位于 [1-4] 或 [5-8] 范围内时有效;否则返回 CGRectZero。
|
||||
- (CGRect)rectForMidpointBetweenMicAtIndex:(NSInteger)firstIndex andIndex:(NSInteger)secondIndex;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -11,6 +11,7 @@
|
||||
#import "YUMIMacroUitls.h"
|
||||
#import "RoomInfoModel.h"
|
||||
#import "ClientConfig.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
//房主距离顶部间距
|
||||
#define ownerTopMargin 100
|
||||
#define ownerTopMargin_little_screen 35
|
||||
@@ -34,8 +35,22 @@
|
||||
// 第一行坑位的顶部
|
||||
#define secondRowTop (firstRowTop + mcHeight + marginV2)
|
||||
|
||||
@interface SocialStageView ()
|
||||
|
||||
@property (nonatomic, strong) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SocialStageView
|
||||
|
||||
- (instancetype)initWithDelegate:(id<RoomHostDelegate>)delegate {
|
||||
if (self = [super initWithDelegate:delegate]) {
|
||||
// 初始化中点矩形管理器
|
||||
_midpointRectManager = [[MicMidpointRectManager alloc] initWithContainerView:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger)countOfMicroView {
|
||||
return 9;
|
||||
}
|
||||
@@ -113,4 +128,47 @@
|
||||
return point;
|
||||
}
|
||||
|
||||
// 计算两个横向相邻麦位之间的中点矩形(75x75)。
|
||||
- (CGRect)rectForMidpointBetweenMicAtIndex:(NSInteger)firstIndex andIndex:(NSInteger)secondIndex {
|
||||
// 仅支持下排与上排的麦位区间 [1-4], [5-8] 且相邻
|
||||
if (firstIndex == secondIndex) {
|
||||
return CGRectZero;
|
||||
}
|
||||
NSInteger left = MIN(firstIndex, secondIndex);
|
||||
NSInteger right = MAX(firstIndex, secondIndex);
|
||||
BOOL sameRowFirst = (left >= 1 && left <= 4 && right >= 1 && right <= 4);
|
||||
BOOL sameRowSecond = (left >= 5 && left <= 8 && right >= 5 && right <= 8);
|
||||
if (!(sameRowFirst || sameRowSecond)) {
|
||||
return CGRectZero;
|
||||
}
|
||||
if (right - left != 1) {
|
||||
return CGRectZero;
|
||||
}
|
||||
|
||||
// 取两个麦位的 rect
|
||||
CGRect leftRect = [self rectForViewAtIndex:left];
|
||||
CGRect rightRect = [self rectForViewAtIndex:right];
|
||||
if (CGRectIsEmpty(leftRect) || CGRectIsEmpty(rightRect)) {
|
||||
return CGRectZero;
|
||||
}
|
||||
|
||||
// 取头像区域(上方圆形区域)的中心点作为基准。头像宽为 mcWidth 的圆区域,居于麦位 rect 顶部。
|
||||
// 在已有实现中,动画点位使用了 (rect.origin.y + mcWidth/2 - 10)。这里复用相同的纵向基准,保证视觉一致。
|
||||
CGFloat f_top = iPhoneXSeries ? firstRowTop : ownerHeight + marginV1 + ownerTopMargin_little_screen;
|
||||
CGFloat s_top = iPhoneXSeries ? secondRowTop : mcHeight + marginV2 + f_top;
|
||||
|
||||
BOOL inFirstRow = sameRowFirst;
|
||||
CGFloat circleCenterY = (inFirstRow ? (f_top + mcWidth / 2 - 10) : (s_top + mcWidth / 2 - 10));
|
||||
|
||||
// 两个头像中心的 X 坐标
|
||||
CGFloat leftCenterX = CGRectGetMinX(leftRect) + mcWidth / 2;
|
||||
CGFloat rightCenterX = CGRectGetMinX(rightRect) + mcWidth / 2;
|
||||
CGFloat midX = (leftCenterX + rightCenterX) / 2.0;
|
||||
|
||||
// 以中点为中心,返回 75x75 的矩形
|
||||
CGFloat size = 75.0;
|
||||
CGRect midRect = CGRectMake(midX - size / 2.0, circleCenterY - size / 2.0, size, size);
|
||||
return midRect;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -74,6 +74,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (instancetype)initWithDelegate:(id<RoomHostDelegate>)delegate;
|
||||
-(void)exitNIMRoom;
|
||||
|
||||
/// 计算两个横向相邻麦位中点的矩形(默认返回基于两个坑位中心点的 75x75,非同一行或非相邻返回 CGRectZero)。
|
||||
- (CGRect)rectForMidpointBetweenMicAtIndex:(NSInteger)firstIndex andIndex:(NSInteger)secondIndex;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -1210,6 +1210,39 @@
|
||||
return CGRectZero;
|
||||
}
|
||||
|
||||
/// 默认实现:基于两个坑位的 rect 中点,返回 75x75 的矩形。
|
||||
/// 子类如有明确排布规则(如相同行的相邻判定),可重写以增强约束。
|
||||
- (CGRect)rectForMidpointBetweenMicAtIndex:(NSInteger)firstIndex andIndex:(NSInteger)secondIndex {
|
||||
if (firstIndex == secondIndex) {
|
||||
return CGRectZero;
|
||||
}
|
||||
NSInteger left = MIN(firstIndex, secondIndex);
|
||||
NSInteger right = MAX(firstIndex, secondIndex);
|
||||
CGRect leftRect = [self rectForViewAtIndex:left];
|
||||
CGRect rightRect = [self rectForViewAtIndex:right];
|
||||
if (CGRectIsEmpty(leftRect) || CGRectIsEmpty(rightRect)) {
|
||||
return CGRectZero;
|
||||
}
|
||||
// 判断是否同一"行":中心 y 差值小于高度的三分之一
|
||||
CGFloat leftCenterY = CGRectGetMidY(leftRect);
|
||||
CGFloat rightCenterY = CGRectGetMidY(rightRect);
|
||||
if (fabs(leftCenterY - rightCenterY) > (CGRectGetHeight(leftRect) / 3.0)) {
|
||||
return CGRectZero;
|
||||
}
|
||||
// 计算中点
|
||||
CGFloat leftCenterX = CGRectGetMidX(leftRect);
|
||||
CGFloat rightCenterX = CGRectGetMidX(rightRect);
|
||||
CGFloat midX = (leftCenterX + rightCenterX) / 2.0;
|
||||
|
||||
// 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐
|
||||
CGFloat leftTopY = CGRectGetMinY(leftRect);
|
||||
CGFloat rightTopY = CGRectGetMinY(rightRect);
|
||||
CGFloat midTopY = (leftTopY + rightTopY) / 2.0;
|
||||
|
||||
CGFloat size = 75.0;
|
||||
return CGRectMake(midX - size / 2.0, midTopY, size, size);
|
||||
}
|
||||
|
||||
#pragma mark - StageViewProtocol - 基本上都是工具方法
|
||||
- (NSInteger)positionToIndex:(NSString*)position {
|
||||
return position.intValue + 1;
|
||||
|
@@ -6,11 +6,14 @@
|
||||
//
|
||||
|
||||
#import "StageView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TenMicStageView : StageView
|
||||
|
||||
@property (nonatomic, strong, readonly) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@@ -7,6 +7,7 @@
|
||||
|
||||
#import "TenMicStageView.h"
|
||||
#import "SocialMicroView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
//第一行距离顶部
|
||||
#define firstRowTopMargin 60
|
||||
// 房主头像宽 58 + 光圈 5
|
||||
@@ -27,8 +28,22 @@
|
||||
// 第一行坑位与第二行坑位的间距
|
||||
//#define marginV2 15
|
||||
|
||||
@interface TenMicStageView ()
|
||||
|
||||
@property (nonatomic, strong) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TenMicStageView
|
||||
|
||||
- (instancetype)initWithDelegate:(id<RoomHostDelegate>)delegate {
|
||||
if (self = [super initWithDelegate:delegate]) {
|
||||
// 初始化中点矩形管理器
|
||||
_midpointRectManager = [[MicMidpointRectManager alloc] initWithContainerView:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger)countOfMicroView {
|
||||
return 10;
|
||||
}
|
||||
@@ -79,5 +94,35 @@
|
||||
return point;
|
||||
}
|
||||
|
||||
// 10麦位布局:5x2网格,仅同一行且相邻列(列差1)返回中点75x75
|
||||
- (CGRect)rectForMidpointBetweenMicAtIndex:(NSInteger)firstIndex andIndex:(NSInteger)secondIndex {
|
||||
if (firstIndex == secondIndex) return CGRectZero;
|
||||
NSInteger left = MIN(firstIndex, secondIndex);
|
||||
NSInteger right = MAX(firstIndex, secondIndex);
|
||||
|
||||
// 检查是否在同一行且相邻
|
||||
BOOL sameRow = ((left >= 0 && left <= 4 && right >= 0 && right <= 4) ||
|
||||
(left >= 5 && left <= 9 && right >= 5 && right <= 9));
|
||||
if (!sameRow) return CGRectZero;
|
||||
|
||||
// 检查是否相邻(列差为1)
|
||||
NSInteger leftCol = (left <= 4) ? left : (left - 5);
|
||||
NSInteger rightCol = (right <= 4) ? right : (right - 5);
|
||||
if ((rightCol - leftCol) != 1) return CGRectZero;
|
||||
|
||||
CGRect l = [self rectForViewAtIndex:left];
|
||||
CGRect r = [self rectForViewAtIndex:right];
|
||||
if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero;
|
||||
|
||||
CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0;
|
||||
|
||||
// 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐
|
||||
CGFloat leftTopY = CGRectGetMinY(l);
|
||||
CGFloat rightTopY = CGRectGetMinY(r);
|
||||
CGFloat midTopY = (leftTopY + rightTopY) / 2.0;
|
||||
|
||||
CGFloat size = 75.0;
|
||||
return CGRectMake(midX - size / 2.0, midTopY, size, size);
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -6,11 +6,14 @@
|
||||
//
|
||||
|
||||
#import "StageView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TwentyMicStageView : StageView
|
||||
|
||||
@property (nonatomic, strong, readonly) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
//- (instancetype)initWithRows:(NSInteger)rows columns:(NSInteger)columns;
|
||||
|
||||
@end
|
||||
|
@@ -7,6 +7,7 @@
|
||||
|
||||
#import "TwentyMicStageView.h"
|
||||
#import "SocialMicroView.h"
|
||||
#import "MicMidpointRectManager.h"
|
||||
|
||||
//第一行距离顶部
|
||||
#define firstRowTopMargin 60
|
||||
@@ -32,10 +33,20 @@
|
||||
|
||||
@interface TwentyMicStageView ()
|
||||
|
||||
@property (nonatomic, strong) MicMidpointRectManager *midpointRectManager;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TwentyMicStageView
|
||||
|
||||
- (instancetype)initWithDelegate:(id<RoomHostDelegate>)delegate {
|
||||
if (self = [super initWithDelegate:delegate]) {
|
||||
// 初始化中点矩形管理器
|
||||
_midpointRectManager = [[MicMidpointRectManager alloc] initWithContainerView:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
static const NSInteger kMicCountPerRow = 5;
|
||||
|
||||
- (NSInteger)countOfMicroView {
|
||||
@@ -83,5 +94,32 @@ static const NSInteger kMicCountPerRow = 5;
|
||||
return CGPointMake(x, y);
|
||||
}
|
||||
|
||||
// 覆盖:仅当同一行且相邻列(列差 1)时,返回两麦位中点 75x75 矩形
|
||||
- (CGRect)rectForMidpointBetweenMicAtIndex:(NSInteger)firstIndex andIndex:(NSInteger)secondIndex {
|
||||
if (firstIndex == secondIndex) return CGRectZero;
|
||||
NSInteger left = MIN(firstIndex, secondIndex);
|
||||
NSInteger right = MAX(firstIndex, secondIndex);
|
||||
// 同一行且列差为 1
|
||||
NSInteger leftRow = left / kMicCountPerRow;
|
||||
NSInteger rightRow = right / kMicCountPerRow;
|
||||
NSInteger leftCol = left % kMicCountPerRow;
|
||||
NSInteger rightCol = right % kMicCountPerRow;
|
||||
if (!(leftRow == rightRow && (rightCol - leftCol) == 1)) {
|
||||
return CGRectZero;
|
||||
}
|
||||
CGRect l = [self rectForViewAtIndex:left];
|
||||
CGRect r = [self rectForViewAtIndex:right];
|
||||
if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero;
|
||||
CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0;
|
||||
|
||||
// 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐
|
||||
CGFloat leftTopY = CGRectGetMinY(l);
|
||||
CGFloat rightTopY = CGRectGetMinY(r);
|
||||
CGFloat midTopY = (leftTopY + rightTopY) / 2.0;
|
||||
|
||||
CGFloat size = 75.0;
|
||||
return CGRectMake(midX - size / 2.0, midTopY, size, size);
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -94,7 +94,7 @@
|
||||
editParam = [MSParamsDecode msDecodeParams:editParam];
|
||||
params = [self configBaseParmars:editParam];
|
||||
|
||||
#if DEBUG
|
||||
#if 0
|
||||
// 构建完整的 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 DEBUG
|
||||
#if 0
|
||||
NSLog(@"%@ - \n%@\n", method, [baseModel toJSONString]);
|
||||
#else
|
||||
#endif
|
||||
@@ -129,103 +129,6 @@
|
||||
}];
|
||||
}
|
||||
|
||||
//+ (void)POST:(NSString *)method
|
||||
// params:(NSDictionary *)params
|
||||
// success:(void (^)(BaseModel *data))success
|
||||
// failure:(void (^)(NSInteger resCode, NSString *message))failure
|
||||
//{
|
||||
// if ([self checkNetworkStatusWithFailure:^{
|
||||
// failure(-1, YMLocalizedString(@"HttpRequestHelper0"));
|
||||
// }]) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// [self configHeaders];
|
||||
// AFHTTPSessionManager *manager = [HttpRequestHelper requestManager];
|
||||
// params = [MSParamsDecode msDecodeParams:[params mutableCopy]];
|
||||
// params = [self configBaseParmars:params];
|
||||
//
|
||||
//#ifdef DEBUG
|
||||
// NSLog(@"\nmethod:\n%@\nparameter:\n%@\n 超時:%@",
|
||||
// method,
|
||||
// params,
|
||||
// @(manager.session.configuration.timeoutIntervalForRequest));
|
||||
//#else
|
||||
//#endif
|
||||
//
|
||||
// // 将 NSDictionary 转换为 JSON
|
||||
// NSError *jsonError;
|
||||
// NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&jsonError];
|
||||
// if (jsonError) {
|
||||
// NSLog(@"Error serializing JSON: %@", jsonError);
|
||||
// if (failure) {
|
||||
// failure(jsonError.code, @"Error creating JSON data");
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Gzip 压缩数据
|
||||
// NSData *gzipData = [jsonData gzippedData];
|
||||
//
|
||||
// // 创建请求
|
||||
// NSString *urlString = [[NSURL URLWithString:method relativeToURL:manager.baseURL] absoluteString];
|
||||
// NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]];
|
||||
// [request setHTTPMethod:@"POST"];
|
||||
//
|
||||
// // 设置请求头 (合并 manager 的通用头和特定头)
|
||||
// [manager.requestSerializer.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
|
||||
// [request setValue:value forHTTPHeaderField:field];
|
||||
// }];
|
||||
// [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
// [request setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
|
||||
// [request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)gzipData.length] forHTTPHeaderField:@"Content-Length"]; // AFNetworking 通常会自动处理 Content-Length,但手动设置以确保
|
||||
//
|
||||
// // !!! 添加认证头 !!!
|
||||
// if ([[AccountInfoStorage instance] getUid].length > 0) {
|
||||
// [request setValue:[[AccountInfoStorage instance] getUid] forHTTPHeaderField:@"pub_uid"];
|
||||
// } else {
|
||||
// [request setValue:nil forHTTPHeaderField:@"pub_uid"];
|
||||
// }
|
||||
// if ([[AccountInfoStorage instance] getTicket].length > 0) {
|
||||
// [request setValue:[[AccountInfoStorage instance] getTicket] forHTTPHeaderField:@"pub_ticket"];
|
||||
// }else {
|
||||
// [request setValue:nil forHTTPHeaderField:@"pub_ticket"];
|
||||
// }
|
||||
// // !!! 添加其他在 configHeaders 中设置的头 !!!
|
||||
// [request setValue:[NSBundle uploadLanguageText] forHTTPHeaderField:@"Accept-Language"];
|
||||
// [request setValue:PI_App_Version forHTTPHeaderField:@"App-Version"];
|
||||
//
|
||||
// // 设置请求体
|
||||
// [request setHTTPBody:gzipData];
|
||||
//
|
||||
// // 设置超时
|
||||
// request.timeoutInterval = manager.requestSerializer.timeoutInterval;
|
||||
//
|
||||
// @kWeakify(self);
|
||||
// NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request
|
||||
// uploadProgress:nil
|
||||
// downloadProgress:nil
|
||||
// completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
|
||||
// if (error) {
|
||||
//#ifdef DEBUG
|
||||
// NSLog(@"%@ - \n%@\n", method, error);
|
||||
//#else
|
||||
//#endif
|
||||
// @kStrongify(self);
|
||||
// [self handleNetError:error method:method failure:failure];
|
||||
// } else {
|
||||
// BaseModel *baseModel = [BaseModel modelWithDictionary:responseObject];
|
||||
//#ifdef DEBUG
|
||||
// NSLog(@"%@ - \n%@\n", method, [baseModel toJSONString]);
|
||||
//#else
|
||||
//#endif
|
||||
// success(baseModel);
|
||||
// }
|
||||
// }];
|
||||
//
|
||||
// [dataTask resume];
|
||||
//}
|
||||
|
||||
+ (void)POST:(NSString *)method
|
||||
params:(NSDictionary *)params
|
||||
success:(void (^)(BaseModel *data))success
|
||||
@@ -245,7 +148,7 @@
|
||||
|
||||
params = [self configBaseParmars:params];
|
||||
|
||||
#if DEBUG
|
||||
#if 0
|
||||
// 构建完整的 URL
|
||||
NSString *baseUrl = [HttpRequestHelper getHostUrl];
|
||||
NSString *fullUrl = [NSString stringWithFormat:@"%@/%@", baseUrl, method];
|
||||
@@ -260,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 DEBUG
|
||||
#if 0
|
||||
NSLog(@"\n%@", [baseModel toJSONString]);
|
||||
#else
|
||||
#endif
|
||||
|
BIN
YuMi/Resources/cp/mic_cp_lv1.svga
Normal file
BIN
YuMi/Resources/cp/mic_cp_lv1.svga
Normal file
Binary file not shown.
BIN
YuMi/Resources/cp/mic_cp_lv2.svga
Normal file
BIN
YuMi/Resources/cp/mic_cp_lv2.svga
Normal file
Binary file not shown.
BIN
YuMi/Resources/cp/mic_cp_lv3.svga
Normal file
BIN
YuMi/Resources/cp/mic_cp_lv3.svga
Normal file
Binary file not shown.
BIN
YuMi/Resources/cp/mic_cp_lv4.svga
Normal file
BIN
YuMi/Resources/cp/mic_cp_lv4.svga
Normal file
Binary file not shown.
BIN
YuMi/Resources/cp/mic_cp_lv5.svga
Normal file
BIN
YuMi/Resources/cp/mic_cp_lv5.svga
Normal file
Binary file not shown.
@@ -178,7 +178,6 @@ static UploadFile* manager;
|
||||
}];
|
||||
|
||||
[[QCloudCOSTransferMangerService defaultCOSTransferManager] UploadObject:put];
|
||||
|
||||
}
|
||||
|
||||
/// 上传一个Image
|
||||
|
@@ -4236,11 +4236,18 @@ ineHeadView12" = "الحمل";
|
||||
"20.20.62_text_7" = "ليس لديك إذن لدعوة المستخدم إلى الميكروفون.";
|
||||
"20.20.62_text_8" = "فشل إرسال الدعوة. يُرجى التحقق من الشبكة.";
|
||||
|
||||
"20.20.62_text_9" = "Turn effects on/off";
|
||||
"20.20.62_text_9.1" = "Turn effects on";
|
||||
"20.20.62_text_9.2" = "Turn effects off";
|
||||
"20.20.62_text_9" = "تشغيل/إيقاف المؤثرات";
|
||||
"20.20.62_text_9.1" = "وضع Turbo (السرعة الفائقة)";
|
||||
"20.20.62_text_9.2" = "وضع عادي";
|
||||
"20.20.62_text_10" = "Gift effects";
|
||||
"20.20.62_text_11" = "Global gift screen";
|
||||
"20.20.62_text_12" = "Global game screen";
|
||||
"20.20.62_text_13" = "Turbo mode";
|
||||
"20.20.62_text_14" = "我的特效";
|
||||
"20.20.62_text_15.1" = "تمكين وضع Turbo يمكن أن يجعل التطبيق يعمل بسلاسة أكبر.\nوضع Turbo: يُوقف رسوميات الهدايا في الغرفة، البث داخل الغرفة، عروض CP، وعروض المؤثرات الأخرى.";
|
||||
"20.20.62_text_15.2" = "لجعل التطبيق يعمل بسلاسة أكبر، تم التبديل تلقائيًا إلى وضع Turbo لك (وضع Turbo: يُوقف تلقائيًا مؤثرات الهدايا، مؤثرات غطاء الرأس، ويدمج رسائل الهدايا في الدردشة العامة). يمكنك إيقاف هذا الوضع في إعدادات الغرفة.";
|
||||
"20.20.62_text_16" = "أنا أدرك";
|
||||
"20.20.62_text_17" = "لقد فعّلتَ تأثير شاشة هدية غروبال العائمة.";
|
||||
"20.20.62_text_18" = "لقد عطّلتَ تأثير شاشة هدية غروبال العائمة. لم تعد شاشة هدية غروبال العائمة ظاهرة في هذه الغرفة. انقر لتفعيلها مجددًا.";
|
||||
"20.20.62_text_19" = "لقد فعّلتَ تأثير شاشة لعبة غروبال العائمة.";
|
||||
"20.20.62_text_20" = "لقد عطّلتَ تأثير شاشة لعبة غروبال العائمة. لم تعد شاشة لعبة غروبال العائمة ظاهرة في هذه الغرفة. انقر لتفعيلها مجددًا.";
|
||||
|
@@ -4024,10 +4024,17 @@
|
||||
"20.20.62_text_8" = "The invitation failed to be sent. Please check the network.";
|
||||
|
||||
"20.20.62_text_9" = "Turn effects on/off";
|
||||
"20.20.62_text_9.1" = "Turn effects on";
|
||||
"20.20.62_text_9.2" = "Turn effects off";
|
||||
"20.20.62_text_9.1" = "Turbo mode";
|
||||
"20.20.62_text_9.2" = "Non-turbo mode";
|
||||
"20.20.62_text_10" = "Gift effects";
|
||||
"20.20.62_text_11" = "Global gift screen";
|
||||
"20.20.62_text_12" = "Global game screen";
|
||||
"20.20.62_text_13" = "Turbo mode";
|
||||
"20.20.62_text_14" = "我的特效";
|
||||
"20.20.62_text_15.1" = "Enabling Turbo mode can make the app run smoother.\nTurbo mode: Turns off room gift animations, in-room broadcasts, CP displays, and other animated effects.";
|
||||
"20.20.62_text_15.2" = "To make the app run smoother, it has automatically switched to Turbo mode for you (Turbo mode: Automatically turns off gift animations, headwear effects, and merges gift messages in the public chat). You can turn off this mode in the room settings.";
|
||||
"20.20.62_text_16" = "Anladım";
|
||||
"20.20.62_text_17" = "You have enabled the grobal gift floating screen effect.";
|
||||
"20.20.62_text_18" = "You have disabled the grobal gift floating screen effect. The grobal gift floating screen is no longer visible in this room. Click to enable it again.";
|
||||
"20.20.62_text_19" = "You have enabled the grobal game floating screen effect.";
|
||||
"20.20.62_text_20" = "You have disabled the grobal game floating screen effect. The grobal game floating screen is no longer visible in this room. Click to enable it again.";
|
||||
|
@@ -3316,11 +3316,18 @@
|
||||
"20.20.62_text_7" = "Você não tem permissão para convidar o usuário para o microfone.";
|
||||
"20.20.62_text_8" = "O convite não foi enviado. Verifique a rede.";
|
||||
|
||||
"20.20.62_text_9" = "Turn effects on/off";
|
||||
"20.20.62_text_9.1" = "Turn effects on";
|
||||
"20.20.62_text_9.2" = "Turn effects off";
|
||||
"20.20.62_text_9" = "Ativar/Desativar efeitos";
|
||||
"20.20.62_text_9.1" = "Modo turbo";
|
||||
"20.20.62_text_9.2" = "Modo normal";
|
||||
"20.20.62_text_10" = "Gift effects";
|
||||
"20.20.62_text_11" = "Global gift screen";
|
||||
"20.20.62_text_12" = "Global game screen";
|
||||
"20.20.62_text_13" = "Turbo mode";
|
||||
"20.20.62_text_14" = "我的特效";
|
||||
"20.20.62_text_15.1" = "Ativar o Modo turbo pode fazer com que o aplicativo funcione de forma mais suave.\nModo turbo: Desativa as animações de presentes na sala, transmissões na sala, exibições de CP e outros efeitos animados.";
|
||||
"20.20.62_text_15.2" = "Para fazer o aplicativo funcionar de forma mais suave, mudou automaticamente para o Modo turbo para si (Modo turbo: Desativa automaticamente os efeitos de presentes, os efeitos de chapéu e funde as mensagens de presentes no chat público). Pode desativar este modo nas definições da sala.";
|
||||
"20.20.62_text_16" = "Compreendo";
|
||||
"20.20.62_text_17" = "Você ativou o efeito de tela flutuante do presente Grobal.";
|
||||
"20.20.62_text_18" = "Você desativou o efeito de tela flutuante do presente Grobal. A tela flutuante do presente Grobal não está mais visível nesta sala. Clique para ativá-la novamente.";
|
||||
"20.20.62_text_19" = "Você ativou o efeito de tela flutuante do jogo Grobal.";
|
||||
"20.20.62_text_20" = "Você desativou o efeito de tela flutuante do jogo Grobal. A tela flutuante do jogo Grobal não está mais visível nesta sala. Clique para ativá-la novamente.";
|
||||
|
@@ -3817,11 +3817,18 @@
|
||||
"20.20.62_text_7" = "Kullanıcıyı mikrofona davet etme izniniz yok.";
|
||||
"20.20.62_text_8" = "Davet gönderilemedi. Lütfen ağı kontrol edin.";
|
||||
|
||||
"20.20.62_text_9" = "Turn effects on/off";
|
||||
"20.20.62_text_9.1" = "Turn effects on";
|
||||
"20.20.62_text_9.2" = "Turn effects off";
|
||||
"20.20.62_text_9" = "Efektleri Aç/Kapat";
|
||||
"20.20.62_text_9.1" = "Turbo mod";
|
||||
"20.20.62_text_9.2" = "Normal mod";
|
||||
"20.20.62_text_10" = "Gift effects";
|
||||
"20.20.62_text_11" = "Global gift screen";
|
||||
"20.20.62_text_12" = "Global game screen";
|
||||
"20.20.62_text_13" = "Turbo mode";
|
||||
"20.20.62_text_14" = "我的特效";
|
||||
"20.20.62_text_15.1" = "Turbo modu etkinleştirmek, uygulamanın daha sorunsuz çalışmasını sağlayabilir.\nTurbo mod: Oda hediye animasyonlarını, odadaki yayınları, CP görüntülerini ve diğer animasyonlu efektleri kapatır.";
|
||||
"20.20.62_text_15.2" = "Uygulamanın daha sorunsuz çalışması için sizin için otomatik olarak Turbo moda geçildi (Turbo mod: Hediye animasyonlarını, başlık efektlerini otomatik olarak kapatır ve genel sohbetteki hediye mesajlarını birleştirir). Bu modu oda ayarlarından kapatabilirsiniz.";
|
||||
"20.20.62_text_16" = "Anladım";
|
||||
"20.20.62_text_17" = "Küresel hediye yüzen ekran efektini etkinleştirdiniz.";
|
||||
"20.20.62_text_18" = "Küresel hediye yüzen ekran efektini devre dışı bıraktınız. Küresel hediye yüzen ekran artık bu odada görünmüyor. Tekrar etkinleştirmek için tıklayın.";
|
||||
"20.20.62_text_19" = "Küresel oyun yüzen ekran efektini etkinleştirdiniz.";
|
||||
"20.20.62_text_20" = "Küresel oyun yüzen ekran efektini devre dışı bıraktınız. Küresel oyun yüzen ekran artık bu odada görünmüyor. Tekrar etkinleştirmek için tıklayın.";
|
||||
|
@@ -3687,12 +3687,18 @@
|
||||
"20.20.62_text_7" = "您無權邀請該使用者加入麥克風。";
|
||||
"20.20.62_text_8" = "邀請發送失敗,請檢查網路。";
|
||||
|
||||
"20.20.62_text_9" = "Turn effects on/off";
|
||||
"20.20.62_text_9.1" = "Turbo mode on";
|
||||
"20.20.62_text_9.2" = "Turbo mode off";
|
||||
"20.20.62_text_10" = "Gift effects";
|
||||
"20.20.62_text_11" = "Global gift screen";
|
||||
"20.20.62_text_12" = "Global game screen";
|
||||
"20.20.62_text_13" = "Turbo mode";
|
||||
"20.20.62_text_9" = "开关我的特效";
|
||||
"20.20.62_text_9.1" = "极速模式";
|
||||
"20.20.62_text_9.2" = "非极速模式";
|
||||
"20.20.62_text_10" = "礼物特效";
|
||||
"20.20.62_text_11" = "全服礼物飘屏";
|
||||
"20.20.62_text_12" = "全服游戏飘屏";
|
||||
"20.20.62_text_13" = "极速模式";
|
||||
"20.20.62_text_14" = "我的特效";
|
||||
|
||||
"20.20.62_text_15.1" = "开启极速模式,可以使app运行更加流畅。\n极速模式:关闭房间礼物动效果、房间内飘屏,CP展示等动效展示。";
|
||||
"20.20.62_text_15.2" = "为使APP运行更加顺畅,已自动为你切换至极速模式(极速模式:自动关闭礼物动效、头饰动效、公屏的送礼消息已合并)。你可以在房间设置中关闭该模式。";
|
||||
"20.20.62_text_16" = "我已知晓";
|
||||
"20.20.62_text_17" = "你已经开启全服礼物飘屏特效。";
|
||||
"20.20.62_text_18" = "你已经关闭全服礼物飘屏特效。本房间内无法看到全服礼物飘屏,点击即可重新开启。";
|
||||
"20.20.62_text_19" = "你已经开启全服游戏飘屏特效。";
|
||||
"20.20.62_text_20" = "你已经关闭全服游戏飘屏特效。本房间内无法看到全服游戏飘屏,点击即可重新开启。";
|
||||
|
98
current_user_mic_switch_flow.md
Normal file
98
current_user_mic_switch_flow.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# 当前用户切换 mic 时云信消息发送流程
|
||||
|
||||
## 修复后的完整流程
|
||||
|
||||
```
|
||||
1. 用户切换 mic (位置A → 位置B)
|
||||
↓
|
||||
2. 接收云信通知 NIMChatroomEventTypeQueueChange
|
||||
- 下麦通知 (changeType == 2)
|
||||
- 上麦通知 (changeType == 1)
|
||||
↓
|
||||
3. 调用 handleMicChangeForCP:queue
|
||||
↓
|
||||
4. 在 handleMicChangeForCP 中执行:
|
||||
a) updateCurrentUserMicStatus:queue
|
||||
- 检测到麦位变化
|
||||
- 设置 currentUserMicStatusChanged = YES
|
||||
b) updateMicMidpointRectManagerSnapshot
|
||||
c) handleDownMicEventIfNeeded:queue
|
||||
d) handleMicSwitchScenarioIfNeeded:queue
|
||||
- 检测到 currentUserMicStatusChanged = YES
|
||||
- 清除所有CP关系
|
||||
- 重新构建CP关系
|
||||
- 🔧 不重置状态标志(关键修复)
|
||||
e) callMicCpListByUidListOnMicChangeWithQueue:queue
|
||||
- 调用 micCpListByUidList API
|
||||
↓
|
||||
5. API 成功回调 getMicCpListByUidListSuccess:cpList
|
||||
↓
|
||||
6. 在 getMicCpListByUidListSuccess 中:
|
||||
a) 更新 CP 缓存
|
||||
b) 刷新绘制
|
||||
c) 检查 currentUserMicStatusChanged = YES
|
||||
d) 🔧 发送云信消息 sendMicRelationshipNIMessage:cpList
|
||||
e) 重置状态标志 currentUserMicStatusChanged = NO
|
||||
↓
|
||||
7. 其他用户收到云信消息,更新CP关系
|
||||
```
|
||||
|
||||
## 关键修复点
|
||||
|
||||
### 修复前的问题
|
||||
- `handleMicSwitchScenarioIfNeeded` 中提前重置 `currentUserMicStatusChanged = NO`
|
||||
- API 成功回调时状态标志已经是 `NO`
|
||||
- 无法发送云信消息
|
||||
|
||||
### 修复后的逻辑
|
||||
- `handleMicSwitchScenarioIfNeeded` 中不重置状态标志
|
||||
- 状态标志保持 `YES` 直到 API 成功回调
|
||||
- API 成功回调时正确发送云信消息
|
||||
- 发送消息后重置状态标志
|
||||
|
||||
## 代码修改
|
||||
|
||||
### 修改前
|
||||
```objc
|
||||
// 在 handleMicSwitchScenarioIfNeeded 中
|
||||
if (hasMicPositionChanged) {
|
||||
// 处理麦位变化场景
|
||||
[midpointRectManager removeAllMidpointRects];
|
||||
[midpointRectManager rebuildMicSnapshotWithStageView:self.stageView micCount:micCount];
|
||||
[self drawSocialStageMidpointRects];
|
||||
|
||||
// ❌ 提前重置状态标志
|
||||
self.currentUserMicStatusChanged = NO;
|
||||
}
|
||||
```
|
||||
|
||||
### 修改后
|
||||
```objc
|
||||
// 在 handleMicSwitchScenarioIfNeeded 中
|
||||
if (hasMicPositionChanged) {
|
||||
// 处理麦位变化场景
|
||||
[midpointRectManager removeAllMidpointRects];
|
||||
[midpointRectManager rebuildMicSnapshotWithStageView:self.stageView micCount:micCount];
|
||||
[self drawSocialStageMidpointRects];
|
||||
|
||||
// ✅ 不在这里重置状态标志,等待API成功回调后再重置
|
||||
// 状态标志将在 getMicCpListByUidListSuccess 中重置,确保能发送云信消息
|
||||
}
|
||||
```
|
||||
|
||||
## 验证要点
|
||||
|
||||
1. **当前用户切换mic时**:
|
||||
- ✅ 正确检测到麦位状态变化
|
||||
- ✅ 重新构建所有CP关系
|
||||
- ✅ 调用 micCpListByUidList API
|
||||
- ✅ API成功回调时发送云信消息
|
||||
- ✅ 状态标志在发送消息后正确重置
|
||||
|
||||
2. **其他用户切换mic时**:
|
||||
- ✅ 通过 handleOtherUserMicChange 处理
|
||||
- ✅ 不发送云信消息(由切换用户发送)
|
||||
|
||||
3. **边界情况**:
|
||||
- ✅ 当前用户不在麦上时清理CP数据
|
||||
- ✅ 只有当前用户一个人时清理CP数据
|
Reference in New Issue
Block a user