// // YMRoomMessageView.m // YUMI // // Created by YUMI on 2021/10/11. // #import "XPRoomMessageContainerView.h" ///Third #import #import ///Tool #import "DJDKMIMOMColor.h" #import "YUMIMacroUitls.h" #import "XPRoomMessageConstant.h" #import "XPRoomMessageParser.h" #import "AccountInfoStorage.h" #import "XPRoomMiniManager.h" #import "PLTimeUtil.h" #import "ClientConfig.h" #import #import "ThemeColor+Room.h" #import "NSArray+Safe.h" #import "Api+Room.h" ///Model #import "RoomInfoModel.h" #import "AttachmentModel.h" #import "RoomFaceSendInfoModel.h" #import "XPMessageRemoteExtModel.h" #import "RoomPKChooseUserModel.h" #import "CandyTreeResultModel.h" #import "RoomSailingPrizeModel.h" #import "UserInfoModel.h" #import "XPMessageInfoModel.h" #import "GiftReceiveInfoModel.h" #import "XPGiftStorage.h" ///View #import "XPRoomMessageTableViewCell.h" #import "XPRoomMessageHeaderView.h" #import "PIRoomMessagePhotoAlbumCell.h" #import "PIRoomMessageUnlockPhotoAlbumView.h" #import "PIRoomPhotoAlbumItemModel.h" #import "SDPhotoBrowser.h" #import "XPSkillCardPlayerManager.h" // Boom #import "BoomInfoModel.h" // 红包 #import "LuckyPackageMessageTableViewCell.h" // CP 进场提示 #import "CPEnterRoomTableViewCell.h" #import "RoomEnterModel.h" NSString * const kRoomShowTopicKey = @"kRoomShowTopicKey"; @interface XPRoomMessageContainerView () ///房间的代理 @property (nonatomic,weak) id hostDelegate; ///列表 @property (nonatomic,strong) UITableView *messageTableView; ///头部 @property (nonatomic,strong) XPRoomMessageHeaderView *headerView; ///底部有新的消息 @property (nonatomic,strong) UIButton *messageTipsBtn; ///有人at你 @property (nonatomic, strong) UIButton *atTipBtn; ///是否处于正在爬楼 @property (nonatomic,assign) BOOL isPending; ///是否是最小化进房的 @property (nonatomic,assign) BOOL isMiniEnter; ///数据源 @property (nonatomic,strong) NSMutableArray *datasource; @property (nonatomic,strong) NSMutableArray *datasource_chat; @property (nonatomic,strong) NSMutableArray *datasource_gift; ///临时存放消息的数组 @property (nonatomic,strong) NSMutableArray *incomingMessages; ///有多少人at我 @property (nonatomic, assign) NSInteger atCount; ///@我的消息位置集合 @property (nonatomic, strong) NSMutableArray *locationArray; ///messageView 持有这个工具类 进行数据的分发操作 @property (nonatomic,strong) XPRoomMessageParser *messageParser; @property(nonatomic,strong) PIRoomPhotoAlbumItemModel *lookUpModel; @property(nonatomic,assign) BOOL isLoadHistoryMessage; @property (nonatomic, assign) NSInteger displayType; /// 清空公屏后下次追加强制走 reload,避免插入动画与数据源不一致 @property (nonatomic, assign) BOOL forceReloadNextAppend; /// 视图是否已经完成首次布局,未就绪期间的 UI 更新将排队 @property (atomic, assign) BOOL viewReady; /// 是否正在刷新,避免嵌套批量更新 @property (atomic, assign) BOOL isFlushing; /// 待执行的 UI 操作队列(在 viewReady 之前累积) @property (nonatomic, strong) NSMutableArray *pendingOps; @end @implementation XPRoomMessageContainerView - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (instancetype)initWithDelegate:(id)delegate { self = [super init]; if (self) { self.displayType = 1; self.isLoadHistoryMessage = YES; self.forceReloadNextAppend = NO; self.hostDelegate = delegate; [self initSubViews]; [self initSubViewConstraints]; } return self; } /// 统一的安全插入实现:在数据与 UI 不一致时回退为 reload,永不崩溃 - (void)safeApplyInsertsFromStartIndex:(NSInteger)startIndex newItemsCount:(NSInteger)newItemsCount needReload:(BOOL)needReload { if (needReload || newItemsCount <= 0) { [self.messageTableView reloadData]; return; } if (self.isFlushing) { // 合并到下一帧,避免嵌套更新 dispatch_async(dispatch_get_main_queue(), ^{ [self safeApplyInsertsFromStartIndex:startIndex newItemsCount:newItemsCount needReload:NO]; }); return; } self.isFlushing = YES; // 校验一致性 NSInteger beforeRows = [self.messageTableView numberOfRowsInSection:0]; NSInteger expectedRows = [self getCurrentDataSourceCount]; BOOL indexValid = (startIndex >= 0 && startIndex <= beforeRows); BOOL countValid = (beforeRows + newItemsCount == expectedRows); if (!indexValid || !countValid) { [self.messageTableView reloadData]; self.isFlushing = NO; return; } // 生成 indexPaths NSMutableArray *indexPaths = [NSMutableArray array]; for (NSInteger i = 0; i < newItemsCount; i++) { [indexPaths addObject:[NSIndexPath indexPathForRow:startIndex + i inSection:0]]; } // 原子提交 [self.messageTableView beginUpdates]; @try { [self.messageTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade]; } @catch (__unused NSException *e) { [self.messageTableView reloadData]; } @finally { [self.messageTableView endUpdates]; self.isFlushing = NO; } } - (void)changeType:(NSInteger)type { if (self.displayType == type) { return; } self.displayType = type; [self.messageTableView reloadData]; if (self.displayType == 1) { self.messageTableView.tableHeaderView = self.headerView; } else { self.messageTableView.tableHeaderView = nil; } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self scrollToBottom:YES]; }); } /// 更新其他 tag 的数据源,若传入空数组,则初始化并从 datasource 中获取数据 - (void)updateAllDataSource:(NSArray *)datas { if (!datas || datas.count == 0) { // 清空分类数据源 [self.datasource_chat removeAllObjects]; [self.datasource_gift removeAllObjects]; // 从主数据源重新构建分类数据源 datas = self.datasource; } for (XPMessageInfoModel *model in datas) { switch (model.first) { case NIMMessageTypeText: case CustomMessageType_Face: [self.datasource_chat addObject:model]; break; case CustomMessageType_Gift: case CustomMessageType_RoomBoom: case CustomMessageType_Candy_Tree: case CustomMessageType_Super_Gift: case CustomMessageType_AllMicroSend: [self.datasource_gift addObject:model]; break; default: break; } } } - (UIView *)listView { return self; } - (void)showUserCard:(NSInteger)uid{ [self.messageParser showUserCard:uid]; } #pragma mark - Response - (void)messageTipsBtnAction:(UIButton *)sender { self.isPending = NO; self.messageTipsBtn.hidden = YES; [self appendAndScrollToBottom]; } - (void)atTipsBtnAction:(UIButton *)sender { self.isPending = YES; [self appendAndScrollToAtUser]; } ///追加数据源 - (void)appendAndScrollToAtUser { // 在未就绪时排队 if (![NSThread isMainThread]) { dispatch_async(dispatch_get_main_queue(), ^{ [self appendAndScrollToAtUser]; }); return; } if (!self.viewReady) { if (!self.pendingOps) self.pendingOps = [NSMutableArray array]; __weak typeof(self) weakSelf = self; [self.pendingOps addObject:^{ __strong typeof(weakSelf) self = weakSelf; [self appendAndScrollToAtUser]; }]; return; } // 1. 检查 incomingMessages 是否为空 if (self.incomingMessages.count < 1) { // 2. 安全检查 locationArray 是否为空 if (self.locationArray.count == 0) { [self scrollToBottomWithTipsHidden:YES]; return; } // 3. 获取首个 @ 消息的索引,并进行安全检查 NSInteger index = [self safeGetIndexFromLocationArrayAt:0]; if (index == NSNotFound) { [self scrollToBottomWithTipsHidden:YES]; return; } // 将 datasource 的索引转换为当前显示数据源的索引 NSInteger convertedIndex = [self convertDataSourceIndexToCurrentDisplayIndex:index]; if (convertedIndex == NSNotFound) { [self scrollToBottomWithTipsHidden:YES]; } else { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:convertedIndex inSection:0]; NSInteger currentRows = [self getCurrentDataSourceCount]; if (currentRows > indexPath.row) { [self.messageTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES]; if (currentRows == indexPath.row + 1) { self.messageTipsBtn.hidden = YES; self.isPending = NO; } } else { [self scrollToBottomWithTipsHidden:YES]; } } [self safelyRemoveLocationAtIndex:0]; [self updateAtTipButton]; return; } // 4. 超长消息处理逻辑 BOOL needReloadData = NO; if (self.datasource.count > kRoomMessageMaxLength) { NSInteger removedCount = kRoomMessageMaxLength / 2; [self safelyRemoveMessages:removedCount]; needReloadData = YES; // 标记需要重新加载数据 } // 5. 在更新数据源之前获取当前行数 NSInteger currentRows = [self getCurrentDataSourceCount]; // 6. 插入新消息 NSMutableArray *tempNewDatas = @[].mutableCopy; for (id item in self.incomingMessages) { XPMessageInfoModel *model = [self parseMessage:item]; if (!model) continue; [tempNewDatas addObject:model]; [self.datasource addObject:model]; [self processAtMentionsForMessage:item]; } [self updateAllDataSource:tempNewDatas]; [self.incomingMessages removeAllObjects]; // 7. 更新 UITableView [self safeApplyInsertsFromStartIndex:currentRows newItemsCount:tempNewDatas.count needReload:(needReloadData || self.forceReloadNextAppend)]; self.forceReloadNextAppend = NO; // 6. 滚动到指定位置或底部 [self scrollToFirstLocationOrBottom]; } - (void)scrollToBottomWithTipsHidden:(BOOL)hidden { NSInteger rows = [self getCurrentDataSourceCount]; if (rows > 0) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:(rows - 1) inSection:0]; [self.messageTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES]; } self.messageTipsBtn.hidden = hidden; self.isPending = NO; self.atCount = 0; self.atTipBtn.hidden = hidden; } - (void)updateLocationArrayForMessageRemoval:(NSInteger)removedCount { NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet]; for (int i = 0; i < self.locationArray.count; i++) { NSNumber *number = self.locationArray[i]; if (number.integerValue < removedCount) { self.atCount--; [indexSet addIndex:i]; } } [self.locationArray removeObjectsAtIndexes:indexSet]; for (int i = 0; i < self.locationArray.count; i++) { NSNumber *number = self.locationArray[i]; self.locationArray[i] = @(number.integerValue - removedCount); } } - (void)updateAtTipButton { if (self.locationArray.count == 0) { self.atTipBtn.hidden = YES; } else { [self.atTipBtn setTitle:[NSString stringWithFormat:YMLocalizedString(@"XPRoomMessageContainerView0"), (unsigned long)self.locationArray.count] forState:UIControlStateNormal]; self.atTipBtn.hidden = NO; } } - (NSInteger)safeGetIndexFromLocationArrayAt:(NSUInteger)index { if (index < self.locationArray.count) { NSNumber *number = self.locationArray[index]; return [number intValue]; } return NSNotFound; // 返回一个无效值,避免崩溃 } - (void)safelyRemoveLocationAtIndex:(NSUInteger)index { if (index < self.locationArray.count) { [self.locationArray xpSafeRemoveObjectAtIndex:index]; self.atCount = MAX(0, self.atCount - 1); } } - (void)safelyRemoveMessages:(NSInteger)count { if (self.datasource.count >= count) { // 获取要删除的消息 NSIndexSet *set = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, count)]; NSArray *removedMessages = [self.datasource objectsAtIndexes:set]; // 从主数据源删除 [self.datasource removeObjectsAtIndexes:set]; // 从分类数据源中删除对应的消息 for (XPMessageInfoModel *removedModel in removedMessages) { switch (removedModel.first) { case NIMMessageTypeText: case CustomMessageType_Face: [self.datasource_chat removeObject:removedModel]; break; case CustomMessageType_Gift: case CustomMessageType_RoomBoom: case CustomMessageType_Candy_Tree: case CustomMessageType_Super_Gift: case CustomMessageType_AllMicroSend: [self.datasource_gift removeObject:removedModel]; break; default: break; } } // 更新 locationArray NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet]; for (NSUInteger i = 0; i < self.locationArray.count; i++) { NSNumber *number = self.locationArray[i]; if (number.integerValue < count) { [indexSet addIndex:i]; } else { self.locationArray[i] = @(number.integerValue - count); } } [self.locationArray removeObjectsAtIndexes:indexSet]; } } - (XPMessageInfoModel *)parseMessage:(id)item { if ([item isKindOfClass:[NIMMessage class]]) { return [self.messageParser parseMessageAttribute:item]; } else if ([item isKindOfClass:[NIMBroadcastMessage class]]) { return [self.messageParser parseBroadcastMessageAttribute:item]; } return nil; } - (void)processAtMentionsForMessage:(id)item { if ([item isKindOfClass:[NIMMessage class]]) { NSArray *nickNameInfos = [(NIMMessage *)item remoteExt][@"atUids"]; if ([nickNameInfos isKindOfClass:[NSArray class]]) { for (NSString *nick in nickNameInfos) { if ([nick isEqualToString:[AccountInfoStorage instance].getUid]) { [self.locationArray addObject:@(self.datasource.count - 1)]; } } } } } /// 检查是否为礼物消息 - (BOOL)isGiftMessage:(id)messageData { if ([messageData isKindOfClass:[NIMMessage class]]) { NIMMessage *message = (NIMMessage *)messageData; if ([message.messageObject isKindOfClass:[NIMCustomObject class]]) { NIMCustomObject *obj = (NIMCustomObject *)message.messageObject; if (obj.attachment && [obj.attachment isKindOfClass:[AttachmentModel class]]) { AttachmentModel *attachment = (AttachmentModel *)obj.attachment; return (attachment.first == CustomMessageType_Gift || attachment.first == CustomMessageType_AllMicroSend || attachment.first == CustomMessageType_Super_Gift); } } } return NO; } - (NSInteger)getCurrentDataSourceCount { NSInteger count = 0; switch (self.displayType) { case 1: count = self.datasource.count; break; case 2: count = self.datasource_chat.count; break; case 3: count = self.datasource_gift.count; break; default: count = self.datasource.count; break; } // 确保返回非负数 return MAX(0, count); } - (NSInteger)convertDataSourceIndexToCurrentDisplayIndex:(NSInteger)dataSourceIndex { if (self.displayType == 1) { return dataSourceIndex; } if (dataSourceIndex >= self.datasource.count) { return NSNotFound; } XPMessageInfoModel *targetModel = [self.datasource objectAtIndex:dataSourceIndex]; if (!targetModel) { return NSNotFound; } NSArray *currentDataSource = nil; switch (self.displayType) { case 2: currentDataSource = self.datasource_chat; break; case 3: currentDataSource = self.datasource_gift; break; default: return dataSourceIndex; } // 在当前数据源中查找对应的模型 for (NSInteger i = 0; i < currentDataSource.count; i++) { XPMessageInfoModel *model = [currentDataSource objectAtIndex:i]; if ([model isEqual:targetModel]) { return i; } } return NSNotFound; } - (void)scrollToFirstLocationOrBottom { if (self.locationArray.count == 0) { [self scrollToBottomWithTipsHidden:YES]; return; } NSInteger index = [self safeGetIndexFromLocationArrayAt:0]; if (index == NSNotFound) { [self scrollToBottomWithTipsHidden:YES]; return; } // 将 datasource 的索引转换为当前显示数据源的索引 NSInteger convertedIndex = [self convertDataSourceIndexToCurrentDisplayIndex:index]; if (convertedIndex == NSNotFound) { [self scrollToBottomWithTipsHidden:YES]; } else { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:convertedIndex inSection:0]; [self.messageTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES]; [self safelyRemoveLocationAtIndex:0]; } [self updateAtTipButton]; } #pragma mark - @我 ///查找有多少人@我 - (void)findAtMeNumber { if (self.incomingMessages.count) { NIMMessage *message = self.incomingMessages.lastObject; if (![message isKindOfClass:[NIMMessage class]] || ![message respondsToSelector:@selector(remoteExt)]) { return; } id nickNameNifo = message.remoteExt[@"atUids"]; if ([nickNameNifo isKindOfClass:[NSArray class]]) { for (NSString *nick in nickNameNifo) { if ([nick isEqualToString:[AccountInfoStorage instance].getUid]) { self.atCount += 1; } } } if (self.atCount > 0) { self.atTipBtn.hidden = NO; [self.atTipBtn setTitle:[NSString stringWithFormat:YMLocalizedString(@"XPRoomMessageContainerView2"), self.atCount] forState:UIControlStateNormal]; } else { self.atTipBtn.hidden = YES; } } } #pragma mark - Private Method - (void)initSubViews { self.clipsToBounds = YES; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(addCustomMessage:) name:@"message" object:nil]; [self addSubview:self.messageTableView]; [self addSubview:self.messageTipsBtn]; [self addSubview:self.atTipBtn]; self.messageTableView.tableHeaderView = self.headerView; // 标记未就绪,等待首次布局完成后再 flush 队列 self.viewReady = NO; self.isFlushing = NO; } - (void)initSubViewConstraints { RoomInfoModel *infoModel = self.hostDelegate.getRoomInfo; CGFloat top = infoModel.type == RoomType_MiniGame ? 0 : 10; [self.messageTableView mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(self).offset(15); make.bottom.trailing.mas_equalTo(self); make.top.equalTo(self).mas_offset(top); }]; [self.messageTipsBtn mas_makeConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(100); make.height.mas_equalTo(30); make.bottom.mas_equalTo(self.mas_bottom).offset(-5); make.leading.mas_equalTo(self); }]; [self.atTipBtn mas_makeConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(100); make.height.mas_equalTo(30); make.bottom.mas_equalTo(self.mas_bottom).offset(-5); make.leading.mas_equalTo(self); }]; // 首次约束完成后,标记就绪并 flush 排队操作 dispatch_async(dispatch_get_main_queue(), ^{ if (!self.viewReady) { self.viewReady = YES; if (self.pendingOps.count > 0) { NSArray *ops = [self.pendingOps copy]; [self.pendingOps removeAllObjects]; for (void (^op)(void) in ops) { op(); } } } }); } ///是否是当前房间 - (BOOL)isCurrentRoom:(NSString *)sessionId { if ([sessionId isEqualToString:[NSString stringWithFormat:@"%ld", self.hostDelegate.getRoomInfo.roomId]]) { return YES; } return NO; } ///判断是否隐身进房 - (BOOL)handleHideEnter:(NIMMessage *)message { NIMMessageChatroomExtension * messageExt = (NIMMessageChatroomExtension *)message.messageExt; NSDictionary * dic = [(NSDictionary *)messageExt.roomExt.toJSONObject objectForKey:message.from]; XPMessageRemoteExtModel * extModel = [XPMessageRemoteExtModel modelWithJSON:dic]; return (extModel.enterHide || extModel.platformRole == 1); } #pragma mark - 添加数据并且做自动滚动 ///添加信息 - (void)addRoomMessage:(id)messageData { if(self.isLoadHistoryMessage == YES)return; [self.incomingMessages addObject:messageData]; if ([messageData isKindOfClass:[NIMBroadcastMessage class]]) { NIMBroadcastMessage *broadcastMessage = (NIMBroadcastMessage *)messageData; [[XPRoomMiniManager shareManager] saveRoomMessage:broadcastMessage]; } else if ([messageData isKindOfClass:[NIMMessage class]]) { NIMMessage *message = (NIMMessage *)messageData; if (!self.isMiniEnter) {/// 最小化进房的话 不需要重新保存 if (self.hostDelegate.getRoomInfo.isCloseScreen) { NIMCustomObject *obj = (NIMCustomObject *)message.messageObject; if (obj.attachment != nil && [obj.attachment isKindOfClass:[AttachmentModel class]]) { AttachmentModel *attachment = (AttachmentModel *)obj.attachment; if (attachment.first == CustomMessageType_Update_RoomInfo && attachment.second == Custom_Message_Sub_Update_RoomInfo_MessageState){ [[XPRoomMiniManager shareManager] saveRoomMessage:message]; } } } else { [[XPRoomMiniManager shareManager] saveRoomMessage:message]; } } } if (self.isPending) { self.messageTipsBtn.hidden = NO; [self findAtMeNumber]; } else { // 对于礼物消息,使用更平滑的更新策略 if ([self isGiftMessage:messageData]) { // 稍微延迟处理礼物消息,让动画先执行 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self appendAndScrollToBottom]; }); } else { [self appendAndScrollToBottom]; } } } ///追加数据源 - (void)appendAndScrollToBottom { // 在未就绪时排队 if (![NSThread isMainThread]) { dispatch_async(dispatch_get_main_queue(), ^{ [self appendAndScrollToBottom]; }); return; } if (!self.viewReady) { if (!self.pendingOps) self.pendingOps = [NSMutableArray array]; __weak typeof(self) weakSelf = self; [self.pendingOps addObject:^{ __strong typeof(weakSelf) self = weakSelf; [self appendAndScrollToBottom]; }]; return; } if (self.incomingMessages.count < 1) { ///滚动到底部(如果有人at自己后,income消息在点击at按钮处做了拼接处理,因为点击at按钮跳转到的是对应的at人消息,如果后面有其他消息时,点有更多按钮时需要滚动到最底部) [self scrollToBottom:YES]; return; } BOOL needReloadData = NO; if (self.datasource.count > kRoomMessageMaxLength) { NSInteger removedCount = kRoomMessageMaxLength / 2; [self safelyRemoveMessages:removedCount]; needReloadData = YES; // 标记需要重新加载数据 } // 在更新数据源之前获取当前行数 NSInteger currentRows = [self getCurrentDataSourceCount]; NSMutableArray *tempArray = @[].mutableCopy; @kWeakify(self); [self.incomingMessages enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { @kStrongify(self); id model = nil; if ([obj isKindOfClass:[NIMMessage class]]) { model = [self.messageParser parseMessageAttribute:(NIMMessage *)obj]; } else if ([obj isKindOfClass:[NIMBroadcastMessage class]]) { model = [self.messageParser parseBroadcastMessageAttribute:(NIMBroadcastMessage *)obj]; } if (model) { [tempArray addObject:model]; [self.datasource addObject:model]; } }]; [self.incomingMessages removeAllObjects]; [self updateAllDataSource:tempArray]; // 如果有删除操作或清空标记,使用 reloadData;否则使用增量更新 [self safeApplyInsertsFromStartIndex:currentRows newItemsCount:tempArray.count needReload:(needReloadData || self.forceReloadNextAppend)]; self.forceReloadNextAppend = NO; //执行插入动画并滚动 // 延迟滚动执行,避免与礼物动画产生视觉冲突 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self scrollToBottom:NO]; }); } ///执行插入动画并滚动 - (void)scrollToBottom:(BOOL)animated { NSArray *source = @[]; switch (self.displayType) { case 1: source = self.datasource; break; case 2: source = self.datasource_chat; break; case 3: source = self.datasource_gift; break; default: source = self.datasource; break; } if(source.count > 0){ NSIndexPath *ip = [NSIndexPath indexPathForRow:source.count-1 inSection:0]; //取最后一行数据 // 优化滚动动画,减少与布局更新的冲突 if (animated) { // 使用 dispatch_async 确保布局更新完成后再滚动 dispatch_async(dispatch_get_main_queue(), ^{ [self.messageTableView scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }); } else { // 使用更平滑的滚动动画,减少与礼物动画的视觉冲突 [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ [self.messageTableView scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionBottom animated:NO]; } completion:nil]; } self.atCount = 0; self.atTipBtn.hidden = YES; [self.locationArray removeAllObjects]; } } ///自定义消息 是否可以加到 公屏 需要自己维护 - (BOOL)isCanDisplayMessage:(NIMMessage *)message { NIMCustomObject *obj = (NIMCustomObject *)message.messageObject; if (obj.attachment != nil && [obj.attachment isKindOfClass:[AttachmentModel class]]) { AttachmentModel *attachment = (AttachmentModel *)obj.attachment; if (attachment.first == CustomMessageType_Face && attachment.second == Custom_Message_Sub_Face_Send) { return NO; } if (attachment.first == CustomMessageType_User_Enter_Room && attachment.second == Custom_Message_Sub_User_Enter_Room) { return NO; } if (attachment.first == CustomMessageType_RedPacket) { return NO; } // if (attachment.first == CustomMessageType_Face && attachment.second == Custom_Message_Sub_Face_Send) { // if ([attachment.data[@"data"] isKindOfClass:[NSArray class]]) { // NSArray * array = [RoomFaceSendInfoModel modelsWithArray:attachment.data[@"data"]]; // for (int i = 0; i< array.count; i++) { // RoomFaceSendInfoModel * sendInfo = [array xpSafeObjectAtIndex:i]; // if (sendInfo.resultIndexes.count <=0) { // return NO; // } // } // } // } if (attachment.first == CustomMessageType_Room_PK && attachment.second == Custom_Message_Sub_Room_PK_Manager_Up_Mic) { if (attachment.data && [attachment.data allKeys].count > 0) { for (NSDictionary * dic in [attachment.data allValues]) { RoomPKChooseUserModel * userModel = [RoomPKChooseUserModel modelWithDictionary:dic]; if (userModel.groupType == GroupType_Red || userModel.groupType == GroupType_Blue) { return YES; } } } return NO; } else if(attachment.first == CustomMessageType_Room_Sailing && (attachment.second == Custom_Message_Sub_Sailing_AllRoom_Notify || attachment.second == Custom_Message_Sub_Sailing_InRoom_NeedAllMicSend)) { RoomSailingPrizeModel * prizeModel = [RoomSailingPrizeModel modelWithDictionary:attachment.data]; if (self.hostDelegate.getUserInfo.userLevelVo.experLevelSeq > prizeModel.userLevelLimit) { return YES; } } else if(attachment.first == CustomMessageType_Room_Album){ return YES; }else if(attachment.first == CustomMessageType_Gift) { if(attachment.data[@"isRoomAlbum"] != nil){ BOOL isRoomAlbum = [attachment.data[@"isRoomAlbum"] boolValue]; if(isRoomAlbum == YES){ return NO; } } GiftReceiveInfoModel *info = [GiftReceiveInfoModel modelWithJSON:attachment.data]; GiftInfoModel *giftInfo = info.gift == nil ? info.giftInfo : info.gift; if (giftInfo == nil) { giftInfo = [[XPGiftStorage shareStorage] findGiftInfo:info.giftId inRoom:@(info.roomUid).stringValue]; } if (giftInfo.giftType == GiftType_super){ return NO; } }else if(attachment.first == CustomMessageType_AllMicroSend){ GiftReceiveInfoModel *info = [GiftReceiveInfoModel modelWithJSON:attachment.data]; GiftInfoModel *giftInfo = info.gift == nil ? info.giftInfo : info.gift; if (giftInfo == nil) { giftInfo = [[XPGiftStorage shareStorage] findGiftInfo:info.giftId inRoom:@(info.roomUid).stringValue]; } if (giftInfo.giftType == GiftType_super){ return NO; } } return [[[self supportMessageDic] objectForKey:@(attachment.first)] containsObject:@(attachment.second)]; } return NO; } - (NSDictionary *)supportMessageDic { return @{ @(CustomMessageType_AllMicroSend): [NSSet setWithObjects: @(Custom_Message_Sub_AllMicroSend), @(Custom_Message_Sub_AllMicroLuckySend), @(Custom_Message_Sub_AllBatchSend), @(Custom_Message_Sub_AllBatchMicroLuckySend), nil], @(CustomMessageType_Gift): [NSSet setWithObjects: @(Custom_Message_Sub_Gift_Send), @(Custom_Message_Sub_Gift_LuckySend), nil], @(CustomMessageType_Room_Tip): [NSSet setWithObjects: @(Custom_Message_Sub_Room_Tip_ShareRoom), @(Custom_Message_Sub_Room_Tip_Attention_Owner), nil], @(CustomMessageType_Kick_User): [NSSet setWithObjects: @(Custom_Message_Sub_Kick_BeKicked), @(Custom_Message_Sub_Kick_BlackList), nil], @(CustomMessageType_Queue): [NSSet setWithObjects: @(Custom_Message_Sub_Queue_Kick), nil], @(CustomMessageType_Look_Love): [NSSet setWithObjects: @(Custom_Message_Sub_Look_Love_Me), @(Custom_Message_Sub_Look_Love_InRoom), @(Custom_Message_Sub_Look_Love_AllRoom), @(Custom_Message_Sub_Look_Love_AllRoom_Notify), @(Custom_Message_Sub_Look_Love_InRoom_NeedAllMicSend), nil], @(CustomMessageType_Arrange_Mic): [NSSet setWithObjects: @(Custom_Message_Sub_Arrange_Mic_Mode_Open), @(Custom_Message_Sub_Arrange_Mic_Mode_Close), @(Custom_Message_Sub_Arrange_Mic_Free_Mic_Open), @(Custom_Message_Sub_Arrange_Mic_Free_Mic_Close), nil], @(CustomMessageType_Update_RoomInfo): [NSSet setWithObjects: @(Custom_Message_Sub_Update_RoomInfo_MessageState), @(Custom_Message_Sub_Update_RoomInfo_AnimateEffect), nil], @(CustomMessageType_Collection_Room): [NSSet setWithObjects: @(Custom_Message_Sub_Collect_Room_Tips), nil], @(CustomMessageType_RoomPlay_Dating): [NSSet setWithObjects: @(Custom_Message_Sub_Room_Play_Dating_Pick_Heart), @(Custom_Message_Sub_Room_Play_Dating_Result_Mutual), @(Custom_Message_Sub_Room_Play_Dating_Result_Not_Mutual), nil], @(CustomMessageType_Noble_VIP): [NSSet setWithObjects: @(Custom_Message_Sub_Room_Open_Noble_VIP), @(Custom_Message_Sub_Room_Noble_LevelUp), @(Custom_Message_Sub_AllRoom_Noble_LevelUp_Suspend), nil], @(CustomMessageType_Face): [NSSet setWithObjects: @(Custom_Message_Sub_Face_Send), nil], @(CustomMessageType_Anchor_FansTeam): [NSSet setWithObjects: @(Custom_Message_Sub_FansTeam_Open_Success), @(Custom_Message_Sub_FansTeam_Open_Fail), @(Custom_Message_Sub_FansTeam_Join_Success), @(Custom_Message_Sub_FansTeam_Out_Success), nil], @(CustomMessageType_Hall_Super_Admin): [NSSet setWithObjects: @(Custom_Message_Sub_Hall_Super_Admin_Kick_Manager_Out_Room), nil], @(CustomMessageType_Room_PK): [NSSet setWithObjects: @(Custom_Message_Sub_Room_PK_Manager_Up_Mic), @(Custom_Message_Sub_Room_PK_Mode_Close), @(Custom_Message_Sub_Room_PK_Result), @(Custom_Message_Sub_Room_PK_Mode_Open), @(Custom_Message_Sub_Room_PK_Start), @(Custom_Message_Sub_Room_PK_Re_Start), nil], @(CustomMessageType_LuckyBag): [NSSet setWithObjects: @(Custom_Message_Sub_Room_Gift_LuckBag_Server), @(Custom_Message_Sub_Room_Gift_LuckBag_FullScree), nil], @(CustomMessageType_Gift_Compound): [NSSet setWithObjects:@(Custom_Message_Sub_Gift_Compound), nil], @(CustomMessageType_Tarot): [NSSet setWithObjects: @(Custom_Message_Sub_Tarot_Novice), @(Custom_Message_Sub_Tarot_Advanced), @(Custom_Message_Sub_Tarot_Intermediate), nil], @(CustomMessageType_Room_Sailing): [NSSet setWithObjects: @(Custom_Message_Sub_Sailing_Me), @(Custom_Message_Sub_Sailing_InRoom), @(Custom_Message_Sub_Sailing_AllRoom), @(Custom_Message_Sub_Sailing_AllRoom_Notify), @(Custom_Message_Sub_Sailing_InRoom_NeedAllMicSend), nil], @(CustomMessageType_RedPacket): [NSSet setWithObjects: @(Custom_Message_Sub_OpenRedPacketSuccess), @(Custom_Message_Sub_LuckyPackage), nil], @(CustomMessageType_General_Public_Screen): [NSSet setWithObjects: @(Custom_Message_Sub_General_Public_Screen_One_Room), @(Custom_Message_Sub_General_Public_Screen_All_Room), nil], @(CustomMessageType_Super_Gift): [NSSet setWithObjects: @(Custom_Message_Sub_Super_Gift), @(Custom_Message_Sub_Super_Gift_Room_Message), nil], }; } - (void)addCustomMessage:(NSNotification *)notification { self.isMiniEnter = NO; if (self.hostDelegate.getRoomInfo.isCloseScreen) {return;} NIMMessage * message = notification.object; [self addRoomMessage:message]; } ///用户进入房间显示 房间话题 - (void)createUserEnterRoomAddRoomTopicMessage { if ([ClientConfig shareConfig].configInfo.hideNoticeVersion == YES) { return; } RoomInfoModel * roomInfo = self.hostDelegate.getRoomInfo; if (roomInfo.uid == [AccountInfoStorage instance].getUid.integerValue) {return;} NSString * key = [NSString stringWithFormat:@"%@_%@_%ld", kRoomShowTopicKey, [AccountInfoStorage instance].getUid, roomInfo.uid]; NSDate * oldDate = [[NSUserDefaults standardUserDefaults] valueForKey:key]; NSDate * date = [NSDate date]; if (roomInfo.introduction.length > 0) { if (oldDate) { NSDateComponents *compons = [PLTimeUtil compareTwoDate:date secondDate:[NSDate date]]; if (ABS(compons.year) > 0 || ABS(compons.month) > 0 || ABS(compons.day) > 0) { NIMMessage * message = [[NIMMessage alloc] init]; NIMTipObject *tipObject = [[NIMTipObject alloc] init]; message.messageObject = tipObject; NSString * content = [NSString stringWithFormat:YMLocalizedString(@"XPRoomMessageContainerView3"), roomInfo.introduction]; message.text = content; message.localExt = @{@"isRoomTopic": @(NO)}; [message setValue:@(NIMMessageTypeTip) forKey:@"messageType"]; [message setValue:[NIMSession session:[NSString stringWithFormat:@"%ld", roomInfo.roomId] type:NIMSessionTypeChatroom] forKey:@"session"]; [self addRoomMessage:message]; [[NSUserDefaults standardUserDefaults] setValue:@(YES) forKey:key]; [[NSUserDefaults standardUserDefaults] synchronize]; } } else { NIMMessage * message = [[NIMMessage alloc] init]; NSString * content = [NSString stringWithFormat:YMLocalizedString(@"XPRoomMessageContainerView3"), roomInfo.introduction]; message.text = content; message.localExt = @{@"isRoomTopic": @(NO)}; [message setValue:@(NIMMessageTypeTip) forKey:@"messageType"]; [message setValue:[NIMSession session:[NSString stringWithFormat:@"%ld", roomInfo.roomId] type:NIMSessionTypeChatroom] forKey:@"session"]; [self addRoomMessage:message]; [[NSUserDefaults standardUserDefaults] setValue:@(YES) forKey:key]; [[NSUserDefaults standardUserDefaults] synchronize]; } } NIMMessage * message = [[NIMMessage alloc] init]; NIMTipObject *tipObject = [[NIMTipObject alloc] init]; message.messageObject = tipObject; message.text = YMLocalizedString(@"XPRoomMessageContainerView5"); message.localExt = @{@"isRoomTopic": @(YES)}; [message setValue:@(NIMMessageTypeTip) forKey:@"messageType"]; [message setValue:[NIMSession session:[NSString stringWithFormat:@"%ld", roomInfo.roomId] type:NIMSessionTypeChatroom] forKey:@"session"]; [self addRoomMessage:message]; } - (void)handleBroadcastMessageAttachment:(AttachmentModel *)attachment; { // if (attachment.first == CustomMessageType_RoomBoom) { // Boom632Model *m = [Boom632Model modelWithJSON:attachment.data]; // NIMMessage * message = [[NIMMessage alloc] init]; // message.text = YMLocalizedString(@"XPRoomMessageContainerView5"); // message.localExt = @{@"isRoomTopic": @(YES)}; // [message setValue:@(NIMMessageTypeTip) forKey:@"messageType"]; // [message setValue:[NIMSession session:[NSString stringWithFormat:@"%ld", roomInfo.roomId] type:NIMSessionTypeChatroom] forKey:@"session"]; // [self addRoomMessage:message]; // } } - (void)handleBroadcastMessage:(NIMBroadcastMessage *)message { [self addRoomMessage:message]; } #pragma mark - RoomGuestDelegate - (void)handleNIMCustomAttachment:(AttachmentModel *)attachment{ } - (void)handleNIMCustomMessage:(NIMMessage *)message { self.isMiniEnter = NO; NIMCustomObject *obj = (NIMCustomObject *)message.messageObject; // 这里其实是特殊处理 if (obj.attachment != nil && [obj.attachment isKindOfClass:[AttachmentModel class]]) { AttachmentModel *attachment = (AttachmentModel *)obj.attachment; switch (attachment.first) { case CustomMessageType_Update_RoomInfo: { if (attachment.second == Custom_Message_Sub_Update_RoomInfo_MessageState) { [self.datasource removeAllObjects]; [self updateAllDataSource:nil]; [self.incomingMessages removeAllObjects]; [self.locationArray removeAllObjects]; self.forceReloadNextAppend = YES; self.atCount = 0; self.atTipBtn.hidden = YES; self.messageTipsBtn.hidden = YES; self.isPending = NO; [[XPRoomMiniManager shareManager] resetLocalMessage]; [self addRoomMessage:message]; } else if (attachment.second == Custom_Message_Sub_Update_RoomInfo_CleanScreen) { [self.datasource removeAllObjects]; [self updateAllDataSource:nil]; [self.incomingMessages removeAllObjects]; [self.locationArray removeAllObjects]; self.forceReloadNextAppend = YES; self.atCount = 0; self.atTipBtn.hidden = YES; self.messageTipsBtn.hidden = YES; self.isPending = NO; [[XPRoomMiniManager shareManager] resetLocalMessage]; [self addRoomMessage:message]; } } break; case CustomMessageType_Candy_Tree: { if(attachment.second == Custom_Message_Sub_Candy_Tree_Me) { CandyTreeGiftInfoModel * model = [CandyTreeGiftInfoModel modelWithDictionary:attachment.data]; if (model.uid.integerValue != [AccountInfoStorage instance].getUid.integerValue) { return; } } } break; case CustomMessageType_Kick_User: { NSString * uid = [AccountInfoStorage instance].getUid; NSString * roomId = [NSString stringWithFormat:@"%ld", self.hostDelegate.getRoomInfo.roomId]; NIMChatroomMembersByIdsRequest *request = [[NIMChatroomMembersByIdsRequest alloc]init]; request.roomId = roomId; request.userIds = @[uid]; [[NIMSDK sharedSDK].chatroomManager fetchChatroomMembersByIds:request completion:^(NSError * _Nullable error, NSArray * _Nullable members) { if (error == nil) { NIMChatroomMember * member = members.firstObject; BOOL isCreator = member.type == NIMChatroomMemberTypeCreator; BOOL isManager = member.type == NIMChatroomMemberTypeManager; if (isCreator || isManager) { [self addRoomMessage:message]; } } }]; } break; case CustomMessageType_Hall_Super_Admin: { [self addRoomMessage:message]; } break; case CustomMessageType_RedPacket: { NSDictionary *data = attachment.data; if (data) { NSInteger currentRoomUID = self.hostDelegate.getRoomInfo.uid; NSNumber *messageRoomUI = [data objectForKey:@"roomUid"]; if (messageRoomUI) { if (currentRoomUID == messageRoomUI.integerValue) { [self addRoomMessage:message]; } } } } break; case CustomMessageType_User_Enter_Room: { if (attachment.second == Custom_Message_Sub_Pic_Message) { [self addRoomMessage:message]; } else if (attachment.second == Custom_Message_Sub_User_Enter_Room) { RoomEnterModel *model = [RoomEnterModel modelWithJSON:attachment.data]; if (model.screenType == 1 || model.enterHide == 1) { return; } for (int index = 0; index 0) { delay = 2.5; } } } } if (delay > 0 ) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self addRoomMessage:message]; }); } } break; default: break; } } if (self.hostDelegate.getRoomInfo.isCloseScreen) { return; } // isCanDisplayMessage 是通用的过滤 if ([self isCanDisplayMessage:message]) { [self addRoomMessage:message]; } } - (void)handleNIMNotificationMessage:(NIMMessage *)message { self.isMiniEnter = NO; NIMNotificationObject *notiMsg = (NIMNotificationObject *)message.messageObject; if (![notiMsg.content isKindOfClass:[NIMChatroomNotificationContent class]]) { return; } NIMChatroomNotificationContent *content = (NIMChatroomNotificationContent *)notiMsg.content; RoomInfoModel *roomInfo = self.hostDelegate.getRoomInfo; if (content.eventType == NIMChatroomEventTypeEnter) { if (roomInfo.isCloseScreen) { self.isLoadHistoryMessage = NO; AttachmentModel *attachment = [[AttachmentModel alloc]init]; attachment.first = CustomMessageType_Update_RoomInfo; attachment.second = Custom_Message_Sub_Update_RoomInfo_MessageState; attachment.data = @{@"roomInfo":self.hostDelegate.getRoomInfo.model2dictionary}; NIMMessage *message = [[NIMMessage alloc]init]; NIMCustomObject *object = [[NIMCustomObject alloc] init]; object.attachment = attachment; message.messageObject = object; [self addRoomMessage:message]; return; } else { NIMChatroomNotificationMember *member = content.targets[0]; if (member.userId.integerValue == [AccountInfoStorage instance].getUid.integerValue) { ///自己进房成功后拉取历史消息 if(self.datasource.count > 0) { self.isLoadHistoryMessage = NO; return; } [self handleFetchHistoryMessage:message]; } else { self.isLoadHistoryMessage = NO; BOOL hideEnter = [self handleHideEnter:message]; if (!hideEnter) { ///插入进房消息及房间公告提示 [self addRoomMessage:message]; } } } } else if(content.eventType == NIMChatroomEventTypeInfoUpdated) { if (roomInfo.isCloseScreen) {return;} if (roomInfo.datingState == RoomDatingStateChangeType_Open) { NSString * uid = [AccountInfoStorage instance].getUid; NSString * roomId = [NSString stringWithFormat:@"%ld", roomInfo.roomId]; NIMChatroomMembersByIdsRequest *request = [[NIMChatroomMembersByIdsRequest alloc]init]; request.roomId = roomId; request.userIds = @[uid]; [[NIMSDK sharedSDK].chatroomManager fetchChatroomMembersByIds:request completion:^(NSError * _Nullable error, NSArray * _Nullable members) { if (error == nil) { NIMChatroomMember * member = members.firstObject; BOOL isCreator = member.type == NIMChatroomMemberTypeCreator; BOOL isManager = member.type == NIMChatroomMemberTypeManager; if (isCreator || isManager) { [self addRoomMessage:message]; } } }]; }// else if (roomInfo) } } - (void)handleFetchNewestMessage:(NIMMessage *)message { RoomInfoModel * roomInfo = self.hostDelegate.getRoomInfo; NSString *roomId = [NSString stringWithFormat:@"%ld", self.hostDelegate.getRoomInfo.roomId]; NIMHistoryMessageSearchOption *option = [[NIMHistoryMessageSearchOption alloc] init]; option.limit = [ClientConfig shareConfig].configInfo.roomMessageCount; option.order = NIMMessageSearchOrderDesc; option.messageTypes = @[@(NIMMessageTypeText),@(NIMMessageTypeCustom)]; @kWeakify(self); [[NIMSDK sharedSDK].chatroomManager fetchMessageHistory:roomId option:option result:^(NSError * _Nullable error, NSArray * _Nullable messages) { @kStrongify(self); if(error != nil){ self.isLoadHistoryMessage = NO; } if (self.datasource.count > kRoomMessageMaxLength) { NSIndexSet *set = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, kRoomMessageMaxLength/2)]; NSArray *needRemoveMsgArray = [self.datasource objectsAtIndexes:set]; [self.datasource removeObjectsInArray:needRemoveMsgArray]; } // 执行插入 for (NIMMessage *item in messages.reverseObjectEnumerator) { [self dealWithHistoryDataWithMessage:item]; } self.isLoadHistoryMessage = NO; BOOL hideEnter = [self handleHideEnter:message]; if (!hideEnter) { ///插入进房消息及房间公告提示 [self addRoomMessage:message]; } if (!roomInfo.hasAnimationEffect) { [self roomInfoNoGiftAnimationMessage:message]; } [self createUserEnterRoomAddRoomTopicMessage]; [self updateAllDataSource:nil]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.messageTableView reloadData]; [self scrollToBottom:YES]; }); }]; } - (void)handleFetchHistoryMessage:(NIMMessage *)message { RoomInfoModel * roomInfo = self.hostDelegate.getRoomInfo; NSString *roomId = [NSString stringWithFormat:@"%ld", self.hostDelegate.getRoomInfo.roomId]; NIMHistoryMessageSearchOption *option = [[NIMHistoryMessageSearchOption alloc] init]; option.limit = [ClientConfig shareConfig].configInfo.roomMessageCount; option.startTime = self.hostDelegate.getRoomInfo.clearScreenTime / 1000.0; option.order = NIMMessageSearchOrderAsc; option.messageTypes = @[@(NIMMessageTypeText),@(NIMMessageTypeCustom)]; [[NIMSDK sharedSDK].chatroomManager fetchMessageHistory:roomId option:option result:^(NSError * _Nullable error, NSArray * _Nullable messages) { if(error != nil){ self.isLoadHistoryMessage = NO; } if (messages.count) { //如果拉取的数量等于请求的数量,说明这个时间点以后的消息数量大于等于需要拉取的数量,直接拉取最新的50条 if (messages.count == [ClientConfig shareConfig].configInfo.roomMessageCount) { [self handleFetchNewestMessage:message]; } else { if (self.datasource.count > kRoomMessageMaxLength) { NSIndexSet *set = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, kRoomMessageMaxLength/2)]; NSArray *needRemoveMsgArray = [self.datasource objectsAtIndexes:set]; [self.datasource removeObjectsInArray:needRemoveMsgArray]; } // 执行插入 for (NIMMessage *item in messages) { [self dealWithHistoryDataWithMessage:item]; } self.isLoadHistoryMessage = NO; BOOL hideEnter = [self handleHideEnter:message]; if (!hideEnter) { ///插入进房消息及房间公告提示 [self addRoomMessage:message]; } if (!roomInfo.hasAnimationEffect) { [self roomInfoNoGiftAnimationMessage:message]; } [self createUserEnterRoomAddRoomTopicMessage]; [self updateAllDataSource:nil]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.messageTableView reloadData]; [self scrollToBottom:YES]; }); } }else{ dispatch_async(dispatch_get_main_queue(), ^{///回到主线程 self.isLoadHistoryMessage = NO; BOOL hideEnter = [self handleHideEnter:message]; if (!hideEnter) { ///插入进房消息及房间公告提示 [self addRoomMessage:message]; } if (!roomInfo.hasAnimationEffect) { [self roomInfoNoGiftAnimationMessage:message]; } [self createUserEnterRoomAddRoomTopicMessage]; }); } }]; } -(void)dealWithHistoryDataWithMessage:(NIMMessage *)item{ BOOL isHaveSave = NO; if(item.messageType == NIMMessageTypeText){ isHaveSave = YES; }else if(item.messageType == NIMMessageTypeCustom){ NIMCustomObject *obj = (NIMCustomObject *)item.messageObject; if (obj.attachment != nil && [obj.attachment isKindOfClass:[AttachmentModel class]]) { AttachmentModel *attachment = (AttachmentModel *)obj.attachment; if (attachment.first == CustomMessageType_Room_Album && attachment.second == Custom_Message_Sub_Room_Album) { isHaveSave = YES; } else if (attachment.first == CustomMessageType_User_Enter_Room && attachment.second == Custom_Message_Sub_Pic_Message) { isHaveSave = YES; } } } if(isHaveSave == NO)return; [self.datasource addObject:[self.messageParser parseMessageAttribute:item]]; if (!self.isMiniEnter) {/// 最小化进房的话 不需要重新保存 if (self.hostDelegate.getRoomInfo.isCloseScreen) { NIMCustomObject *obj = (NIMCustomObject *)item.messageObject; if (obj.attachment != nil && [obj.attachment isKindOfClass:[AttachmentModel class]]) { AttachmentModel *attachment = (AttachmentModel *)obj.attachment; if (attachment.first == CustomMessageType_Update_RoomInfo && attachment.second == Custom_Message_Sub_Update_RoomInfo_MessageState){ [[XPRoomMiniManager shareManager] saveRoomMessage:item]; } } } else { [[XPRoomMiniManager shareManager] saveRoomMessage:item]; } } } - (void)handleNIMTextMessage:(NIMMessage *)message { self.isMiniEnter = NO; if (self.hostDelegate.getRoomInfo.isCloseScreen) {return;} [self addRoomMessage:message]; } - (void)roomInfoNoGiftAnimationMessage:(NIMMessage *)message { self.isMiniEnter = NO; AttachmentModel *attachement = [[AttachmentModel alloc]init]; attachement.first = CustomMessageType_Update_RoomInfo; attachement.second = Custom_Message_Sub_Update_RoomInfo_AnimateEffect; NIMMessage *tempMessage = [[NIMMessage alloc]init]; NIMCustomObject *customObject = [[NIMCustomObject alloc]init]; customObject.attachment = attachement; tempMessage.messageObject = customObject; [self addRoomMessage:tempMessage]; } - (void)onRoomMiniEntered { self.isMiniEnter = YES; self.isLoadHistoryMessage = NO; ///最小化进房 不需要请求任何接口 但是如果不加延迟的话 无法滚动到底部 原因还不知道 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSMutableArray *tempArray = @[].mutableCopy; NSArray * temArray = [XPRoomMiniManager shareManager].getLocalCurrentRoomMessage; [temArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { id model = nil; if ([obj isKindOfClass:[NIMMessage class]]) { model = [self.messageParser parseMessageAttribute:(NIMMessage *)obj]; } else if ([obj isKindOfClass:[NIMBroadcastMessage class]]) { model = [self.messageParser parseBroadcastMessageAttribute:(NIMBroadcastMessage *)obj]; } if (model) { [tempArray addObject:model]; [self.datasource addObject:model]; } }]; [self updateAllDataSource:tempArray]; [self.messageTableView reloadData]; //执行插入动画并滚动 [self scrollToBottom:YES]; }); } - (void)onRoomEntered { [[XPRoomMiniManager shareManager] resetLocalMessage]; self.headerView.bubbleColor = self.hostDelegate.getRoomInfo.type == RoomType_MiniGame ? [UIColor colorWithWhite:1 alpha:0.2] : [UIColor colorWithWhite:1 alpha:0.3]; } - (void)onRoomUpdate { ///改变公屏的背景样式 if (self.hostDelegate.getRoomInfo.hadChangeRoomType) { self.headerView.bubbleColor = self.hostDelegate.getRoomInfo.type == RoomType_MiniGame ? [UIColor colorWithWhite:1 alpha:0.2] : [UIColor colorWithWhite:1 alpha:0.3]; [self.messageTableView reloadData]; } RoomInfoModel *infoModel = self.hostDelegate.getRoomInfo; CGFloat top = infoModel.type == RoomType_MiniGame ? 0 : 10; [self.messageTableView mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(self).offset(15); make.bottom.trailing.mas_equalTo(self); make.top.equalTo(self).mas_offset(top); }]; } #pragma mark - ScrollViewDelegate - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { // 手动拖拽开始 self.isPending = YES; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { // 手动拖拽结束(decelerate:0松手时静止;1松手时还在运动,会触发DidEndDecelerating方法) if (!decelerate) { [self finishDraggingWith:scrollView]; } } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { // 静止后触发(手动) [self finishDraggingWith:scrollView]; } - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { if (!self.isPending && self.incomingMessages.count <= 0) { [self scrollToBottom:YES]; } } /** 手动拖拽动作彻底完成(减速到零) */ - (void)finishDraggingWith:(UIScrollView *)scrollView { CGFloat contentSizeH = scrollView.contentSize.height; CGFloat contentOffsetY = scrollView.contentOffset.y; CGFloat sizeH = scrollView.frame.size.height; self.isPending = contentSizeH - contentOffsetY - sizeH > 20.0; if (!self.isPending) { self.messageTipsBtn.hidden = YES; self.atTipBtn.hidden = YES; self.atCount = 0; [self.locationArray removeAllObjects]; if (self.incomingMessages.count > 0) { [self appendAndScrollToBottom]; } } [[NSNotificationCenter defaultCenter] postNotificationName:@"roomMessageTabelViewStopScroll" object:nil]; } #pragma mark - UITableViewDelegate And UITableViewDataSource - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { NSArray *source = @[]; switch (self.displayType) { case 1: source = self.datasource; break; case 2: source = self.datasource_chat; break; case 3: source = self.datasource_gift; break; default: source = self.datasource; break; } XPMessageInfoModel *model = [source xpSafeObjectAtIndex:indexPath.row]; // 预计算并缓存高度,避免使用 UITableViewAutomaticDimension if (model.rowHeight <= 0) { [self calculateRowHeightForModel:model]; } if (model.first == CustomMessageType_RedPacket && model.second == Custom_Message_Sub_LuckyPackage) { return model.rowHeight; } return model.rowHeight + 20; } // 新增方法:预计算行高 - (void)calculateRowHeightForModel:(XPMessageInfoModel *)model { if (!model || !model.content) { model.rowHeight = 44; // 默认高度 return; } // 使用 YYTextLayout 计算文本高度 YYTextContainer *container = [YYTextContainer new]; container.size = CGSizeMake(kRoomMessageMaxWidth - 24, MAXFLOAT); container.maximumNumberOfRows = 0; YYTextLayout *layout = [YYTextLayout layoutWithContainer:container text:model.content]; model.rowHeight = layout.textBoundingSize.height; // 确保最小高度 if (model.rowHeight < 44) { model.rowHeight = 44; } } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { NSInteger count = 0; switch (self.displayType) { case 1: count = self.datasource.count; break; case 2: count = self.datasource_chat.count; break; case 3: count = self.datasource_gift.count; break; default: count = self.datasource.count; break; } // 确保返回非负数 return MAX(0, count); } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSArray *source = @[]; switch (self.displayType) { case 1: source = self.datasource; break; case 2: source = self.datasource_chat; break; case 3: source = self.datasource_gift; break; default: source = self.datasource; break; } XPMessageInfoModel *model = [source xpSafeObjectAtIndex:indexPath.row]; NSString *cellKey = [NSString isEmpty:model.cellKey] ? NSStringFromClass([XPRoomMessageTableViewCell class]) : model.cellKey; if(model.first == CustomMessageType_Room_Album || (model.first == CustomMessageType_User_Enter_Room && model.second == Custom_Message_Sub_Pic_Message)){ PIRoomMessagePhotoAlbumCell * cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([PIRoomMessagePhotoAlbumCell class])]; cell.delegate = self; cell.messageInfo = model; cell.roomType = self.hostDelegate.getRoomInfo.type; return cell; } else if (model.first == CustomMessageType_RedPacket && model.second == Custom_Message_Sub_LuckyPackage) { LuckyPackageMessageTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"LuckyPackageMessageTableViewCell" forIndexPath:indexPath]; cell.dataSource = model.customInfo; return cell; } else if (model.first == CustomMessageType_User_Enter_Room && model.second == Custom_Message_Sub_User_Enter_Room) { CPEnterRoomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CPEnterRoomTableViewCell" forIndexPath:indexPath]; cell.cpIndex = [[model.customInfo objectForKey:@"cpIndex"] integerValue]; cell.dataSource = model.customInfo; return cell; } else if (model.isBoom) { cellKey = @"XPRoomMessageTableViewCell_Boom"; } XPRoomMessageTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:cellKey]; cell.delegate = self; cell.isLeftBigImage = model.isBoom; cell.messageInfo = model; cell.roomType = self.hostDelegate.getRoomInfo.type; return cell; } //- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { // NSArray *source = @[]; // switch (self.displayType) { // case 1: // source = self.datasource; // break; // case 2: // source = self.datasource_chat; // break; // case 3: // source = self.datasource_gift; // break; // // default: // source = self.datasource; // break; // } // XPMessageInfoModel *model = [source xpSafeObjectAtIndex:indexPath.row]; // if (model.rowHeight == 0) { // model.rowHeight = CGRectGetHeight(cell.frame); // } //} #pragma mark - XPRoomMessageTableViewCellDelegate - (void)xPRoomMessageTableViewCellDidTapEmpty:(XPRoomMessageTableViewCell *)view { if (self.delegate && [self.delegate respondsToSelector:@selector(xPRoomMessageContainerViewlDidTapEmpty:)]) { [self.delegate xPRoomMessageContainerViewlDidTapEmpty:self]; } } -(void)clickDidTapEmptyAction{ if (self.delegate && [self.delegate respondsToSelector:@selector(xPRoomMessageContainerViewlDidTapEmpty:)]) { [self.delegate xPRoomMessageContainerViewlDidTapEmpty:self]; } } #pragma mark - PIRoomMessagePhotoAlbumCell - (void)pIRoomMessagePhotoAlbumCellDelegateDidTapEmpty:(PIRoomMessagePhotoAlbumCell *)view{ if (self.delegate && [self.delegate respondsToSelector:@selector(xPRoomMessageContainerViewlDidTapEmpty:)]) { [self.delegate xPRoomMessageContainerViewlDidTapEmpty:self]; } } -(void)unlockAlbumHandleWithInfo:(PIRoomPhotoAlbumItemModel *_Nonnull)info{ PIRoomMessageUnlockPhotoAlbumView *albumView = [[PIRoomMessageUnlockPhotoAlbumView alloc]initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)]; albumView.albumModel = info; albumView.delegate = self; [TTPopup popupView:albumView style:TTPopupStyleAlert]; } -(void)pIRoomMessagePhotoAlbumCell:(PIRoomMessagePhotoAlbumCell *_Nonnull)cell lookUpAlbumPhotoWithAlbumModel:(PIRoomPhotoAlbumItemModel *_Nonnull)albumModel;{ SDPhotoBrowser *browser = [[SDPhotoBrowser alloc]init]; browser.sourceImagesContainerView = cell; browser.delegate = self; browser.imageCount = 1; browser.currentImageIndex = 0; browser.isMe = NO; self.lookUpModel = albumModel; [browser show]; } - (void)pIRoomMessagePhotoAlbumCell:(PIRoomMessagePhotoAlbumCell *)cell lookUpUserCardModel:(PIRoomPhotoAlbumItemModel *)albumModel{ [self.messageParser showUserCard:albumModel.uid.integerValue]; } #pragma mark - PIRoomMessageUnlockPhotoAlbumViewDelegate - (void)unlockRoomAlbumImageWithAlbumModel:(PIRoomPhotoAlbumItemModel *)albumModel{ [XNDJTDDLoadingTool showLoading]; [Api unlockRoomAlbumPhoto:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) { [XNDJTDDLoadingTool hideHUD]; if(code == 200){ NSMutableDictionary *getData = [NSMutableDictionary dictionary]; [getData addEntriesFromDictionary:data.data]; AttachmentModel *attachment = [[AttachmentModel alloc] init]; attachment.first = CustomMessageType_Gift; attachment.second = Custom_Message_Sub_Gift_Send; NSDictionary *targetUsers = ((NSArray *)[getData objectForKey:@"targetUsers"]).firstObject; [getData setObject:[targetUsers valueForKeyPath:@"uid"] forKey:@"targetUid"]; [getData setObject:[targetUsers valueForKeyPath:@"nick"] forKey:@"targetNick"]; [getData setObject:[targetUsers valueForKeyPath:@"avatar"] forKey:@"targetAvatar"]; [getData setObject:@(YES) forKey:@"isRoomAlbum"]; attachment.data = getData; [self sendCustomMessage:attachment]; if(albumModel.ID != nil){ if(![[XPSkillCardPlayerManager shareInstance].photoIdList containsObject:albumModel.ID]){ if([XPSkillCardPlayerManager shareInstance].photoIdList == nil){ [XPSkillCardPlayerManager shareInstance].photoIdList = [NSMutableArray arrayWithArray:@[albumModel.ID]]; }else{ [[XPSkillCardPlayerManager shareInstance].photoIdList addObject:albumModel.ID]; } } [[NSNotificationCenter defaultCenter]postNotificationName:@"kGetgetUnlockRoomAlbumPhotoListNot" object:nil]; } return; } [XNDJTDDLoadingTool showErrorWithMessage:msg]; } id:albumModel.ID roomUid:@(self.hostDelegate.getRoomInfo.uid).stringValue]; } - (void)sendCustomMessage:(AttachmentModel *)attachment { NSString *sessionID = [NSString stringWithFormat:@"%ld", [self.hostDelegate getRoomInfo].roomId]; NIMMessage *message = [[NIMMessage alloc]init]; NIMCustomObject *object = [[NIMCustomObject alloc] init]; object.attachment = attachment; message.messageObject = object; UserInfoModel *userInfo = [self.hostDelegate getUserInfo]; XPMessageRemoteExtModel *extModel = [[XPMessageRemoteExtModel alloc] init]; extModel.androidBubbleUrl = userInfo.androidBubbleUrl; extModel.iosBubbleUrl = userInfo.iosBubbleUrl; extModel.fromSayHelloChannel = userInfo.fromSayHelloChannel; extModel.platformRole = userInfo.platformRole; NSMutableDictionary *remoteExt = [NSMutableDictionary dictionaryWithObject:extModel.model2dictionary forKey:[NSString stringWithFormat:@"%ld", userInfo.uid]]; message.remoteExt = remoteExt; NIMSessionType sessionType = NIMSessionTypeChatroom; //构造会话 NIMSession *session = [NIMSession session:sessionID type:sessionType]; [[NIMSDK sharedSDK].chatManager sendMessage:message toSession:session error:nil]; } #pragma mark - SDPhotoBrowserDelegate - (NSURL *)photoBrowser:(SDPhotoBrowser *)browser highQualityImageURLForIndex:(NSInteger)index{ if(self.lookUpModel != nil){ return [[NSURL alloc]initWithString:self.lookUpModel.photoUrl]; } return nil; } - (UIImage *)photoBrowser:(SDPhotoBrowser *)browser placeholderImageForIndex:(NSInteger)index { return [UIImageConstant defaultBannerPlaceholder]; } #pragma mark - Getters And Setters - (UITableView *)messageTableView { if (!_messageTableView) { _messageTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; _messageTableView.delegate = self; _messageTableView.dataSource = self; // 移除 UITableViewAutomaticDimension,使用预计算的高度 _messageTableView.rowHeight = 64; // 设置一个合理的默认高度 _messageTableView.tableFooterView = [UIView new]; _messageTableView.separatorStyle = UITableViewCellSeparatorStyleNone; _messageTableView.backgroundColor = [UIColor clearColor]; _messageTableView.showsVerticalScrollIndicator = NO; _messageTableView.clipsToBounds = NO; _messageTableView.tag = 666; if (@available(iOS 11.0, *)) { _messageTableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; } [_messageTableView registerClass:[XPRoomMessageTableViewCell class] forCellReuseIdentifier:@"XPRoomMessageTableViewCell_Boom"]; [_messageTableView registerClass:[XPRoomMessageTableViewCell class] forCellReuseIdentifier:NSStringFromClass([XPRoomMessageTableViewCell class])]; [_messageTableView registerClass:[XPRoomMessageTableViewCell class] forCellReuseIdentifier:@"ChatMessage"]; [_messageTableView registerClass:[PIRoomMessagePhotoAlbumCell class] forCellReuseIdentifier:NSStringFromClass([PIRoomMessagePhotoAlbumCell class])]; [_messageTableView registerClass:[LuckyPackageMessageTableViewCell class] forCellReuseIdentifier:@"LuckyPackageMessageTableViewCell"]; [_messageTableView registerClass:[CPEnterRoomTableViewCell class] forCellReuseIdentifier:@"CPEnterRoomTableViewCell"]; } return _messageTableView; } - (XPRoomMessageHeaderView *)headerView { if (!_headerView) { _headerView = [[XPRoomMessageHeaderView alloc] init]; } return _headerView; } - (UIButton *)messageTipsBtn { if (!_messageTipsBtn) { _messageTipsBtn = [UIButton buttonWithType:UIButtonTypeCustom]; [_messageTipsBtn setTitle:YMLocalizedString(@"XPRoomMessageContainerView6") forState:UIControlStateNormal]; [_messageTipsBtn setTitleColor:[DJDKMIMOMColor appMainColor] forState:UIControlStateNormal]; _messageTipsBtn.layer.cornerRadius = 15.0; _messageTipsBtn.layer.masksToBounds = YES; _messageTipsBtn.titleLabel.font = [UIFont systemFontOfSize:12.0]; _messageTipsBtn.backgroundColor = [UIColor whiteColor]; [_messageTipsBtn addTarget:self action:@selector(messageTipsBtnAction:) forControlEvents:UIControlEventTouchUpInside]; _messageTipsBtn.hidden = YES; } return _messageTipsBtn; } - (UIButton *)atTipBtn { if (!_atTipBtn) { _atTipBtn = [UIButton buttonWithType:UIButtonTypeCustom]; [_atTipBtn setTitle:YMLocalizedString(@"XPRoomMessageContainerView7") forState:UIControlStateNormal]; [_atTipBtn setTitleColor:[DJDKMIMOMColor appMainColor] forState:UIControlStateNormal]; _atTipBtn.layer.cornerRadius = 15.0; _atTipBtn.layer.masksToBounds = YES; _atTipBtn.titleLabel.font = [UIFont systemFontOfSize:12.0]; _atTipBtn.backgroundColor = [UIColor whiteColor]; [_atTipBtn addTarget:self action:@selector(atTipsBtnAction:) forControlEvents:UIControlEventTouchUpInside]; _atTipBtn.hidden = YES; } return _atTipBtn; } - (NSMutableArray *)datasource { if (!_datasource) { _datasource = [NSMutableArray array]; } return _datasource; } - (NSMutableArray *)datasource_chat { if (!_datasource_chat) { _datasource_chat = [NSMutableArray array]; } return _datasource_chat; } - (NSMutableArray *)datasource_gift { if (!_datasource_gift) { _datasource_gift = [NSMutableArray array]; } return _datasource_gift; } - (NSMutableArray *)incomingMessages { if (!_incomingMessages) { _incomingMessages = [NSMutableArray array]; } return _incomingMessages; } - (NSMutableArray *)locationArray { if (!_locationArray) { _locationArray = [NSMutableArray array]; } return _locationArray; } - (XPRoomMessageParser *)messageParser { if (!_messageParser) { _messageParser = [[XPRoomMessageParser alloc] init]; _messageParser.hostDelegate = self.hostDelegate; } return _messageParser; } @end