// Created by Tencent on 2023/06/09. // Copyright © 2023 Tencent. All rights reserved. #import #import #import #import #import #import #import #import #import #import #import #import "TUIChatConfig.h" #import "TUICloudCustomDataTypeCenter.h" #import "TUIMessageBaseDataProvider.h" #import "TUIMessageProgressManager.h" #import "TUITypingStatusCellData.h" /** * Date time interval above the message in the UIMessageCell, in seconds, default is (5 * 60) */ #define MaxDateMessageDelay 5 * 60 @interface TUIMessageBaseDataProvider () @property(nonatomic, strong) TUIChatConversationModel *conversationModel; @property(nonatomic, strong) NSMutableArray *uiMsgs_; @property(nonatomic, strong) NSMutableSet *sentReadGroupMsgSet; @property(nonatomic, strong) NSMutableDictionary *heightCache_; @property(nonatomic, assign) BOOL isLoadingData; @property(nonatomic, assign) BOOL isNoMoreMsg; @property(nonatomic, assign) BOOL isFirstLoad; @property(nonatomic, strong) V2TIMMessage *lastMsg; @property(nonatomic, strong) V2TIMMessage *msgForDate; @property(nonatomic, strong) V2TIMGroupMemberFullInfo *groupSelfInfo; @property(nonatomic, strong) NSMutableArray *groupPinList; @property(nonatomic, assign) V2TIMGroupMemberRole changedRole; @property(nonatomic, strong) V2TIMGroupInfo *groupInfo; @end @implementation TUIMessageBaseDataProvider - (instancetype)initWithConversationModel:(TUIChatConversationModel *)conversationModel { self = [super init]; if (self) { _conversationModel = conversationModel; _isLoadingData = NO; _isNoMoreMsg = NO; _pageCount = 20; _isFirstLoad = YES; [self registerTUIKitNotification]; } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (NSMutableSet *)sentReadGroupMsgSet { if (_sentReadGroupMsgSet == nil) { _sentReadGroupMsgSet = [NSMutableSet setWithCapacity:10]; } return _sentReadGroupMsgSet; } - (NSMutableArray *)uiMsgs_ { if (_uiMsgs_ == nil) { _uiMsgs_ = [NSMutableArray array]; } return _uiMsgs_; } - (NSMutableDictionary *)heightCache_ { if (_heightCache_ == nil) { _heightCache_ = [NSMutableDictionary dictionary]; } return _heightCache_; } - (NSArray *)uiMsgs { return _uiMsgs_; } - (NSDictionary *)heightCache { return _heightCache_; } - (void)setChangedRole:(V2TIMGroupMemberRole)changedRole { _changedRole = changedRole; if (self.groupRoleChanged) { self.groupRoleChanged(changedRole); } } #pragma mark - TUIKitNotification - (void)registerTUIKitNotification { [[V2TIMManager sharedInstance] addAdvancedMsgListener:self]; [[V2TIMManager sharedInstance] addGroupListener:self]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMessageStatusChanged:) name:TUIKitNotification_onMessageStatusChanged object:nil]; } - (void)onMessageStatusChanged:(NSNotification *)notification { V2TIMMessage *targetMsg = notification.object; NSString *msgId = targetMsg.msgID; TUIMessageCellData *uiMsg = nil; BOOL isMatch = NO; for (uiMsg in self.uiMsgs) { if ([uiMsg.msgID isEqualToString:msgId]) { [self.dataSource dataProviderDataSourceWillChange:self]; NSInteger index = [self.uiMsgs indexOfObject:uiMsg]; [self.dataSource dataProviderDataSourceChange:self withType:TUIMessageBaseDataProviderDataSourceChangeTypeReload atIndex:index animation:YES]; [self.dataSource dataProviderDataSourceDidChange:self]; isMatch = YES; break; } } if (!isMatch) { // Need insert UI Message [self onRecvNewMessage:targetMsg]; } } #pragma mark - V2TIMAdvancedMsgListener - (void)onRecvNewMessage:(V2TIMMessage *)msg { // immsg -> uimsg NSMutableArray *newUIMsgs = [self transUIMsgFromIMMsg:@[ msg ]]; if (newUIMsgs.count == 0) { return; } TUIMessageCellData *newUIMsg = newUIMsgs.lastObject; newUIMsg.source = Msg_Source_OnlinePush; if ([newUIMsg isKindOfClass:TUITypingStatusCellData.class]) { if (![TUIChatConfig defaultConfig].enableTypingStatus) { return; } TUITypingStatusCellData *stastusData = (TUITypingStatusCellData *)newUIMsg; if (!NSThread.isMainThread) { @weakify(self); dispatch_async(dispatch_get_main_queue(), ^{ @strongify(self); [self dealTypingByStatusCellData:stastusData]; }); return; } else { [self dealTypingByStatusCellData:stastusData]; } return; } @weakify(self); [self preProcessMessage:newUIMsgs callback:^{ @strongify(self); [self.dataSource dataProviderDataSourceWillChange:self]; @autoreleasepool { for (TUIMessageCellData *uiMsg in newUIMsgs) { [self addUIMsg:uiMsg]; [self.dataSource dataProviderDataSourceChange:self withType:TUIMessageBaseDataProviderDataSourceChangeTypeInsert atIndex:(self.uiMsgs_.count - 1) animation:YES]; } } [self.dataSource dataProviderDataSourceDidChange:self]; if ([self.dataSource respondsToSelector:@selector(dataProvider:ReceiveNewUIMsg:)]) { /** * Note that firstObject cannot be taken here, firstObject may be SystemMessageCellData that displays system time */ [self.dataSource dataProvider:self ReceiveNewUIMsg:newUIMsgs.lastObject]; } }]; } - (NSMutableArray *)transUIMsgFromIMMsg:(NSArray *)msgs { NSMutableArray *uiMsgs = [NSMutableArray array]; for (NSInteger k = msgs.count - 1; k >= 0; --k) { V2TIMMessage *msg = msgs[k]; /** * Messages that are not the current session, ignore them directly */ if (![msg.userID isEqualToString:self.conversationModel.userID] && ![msg.groupID isEqualToString:self.conversationModel.groupID]) { continue; } TUIMessageCellData *cellData = nil; /** * Determine whether it is a custom message outside the component */ if ([self.dataSource respondsToSelector:@selector(dataProvider:CustomCellDataFromNewIMMessage:)]) { cellData = [self.dataSource dataProvider:self CustomCellDataFromNewIMMessage:msg]; } /** * Determine whether it is a component internal message */ if (!cellData) { cellData = [self.class getCellData:msg]; } if (cellData) { TUIMessageCellData *dateMsg = [self getSystemMsgFromDate:msg.timestamp]; if (dateMsg) { if (self.mergeAdjacentMsgsFromTheSameSender) { dateMsg.showName = NO; } self.msgForDate = msg; [uiMsgs addObject:dateMsg]; } if (self.mergeAdjacentMsgsFromTheSameSender) { cellData.showName = NO; } [uiMsgs addObject:cellData]; } } return uiMsgs; } /// Received message read receipts, both in group and c2c conversation. - (void)onRecvMessageReadReceipts:(NSArray *)receiptList { if (receiptList.count == 0) { NSLog(@"group receipt data is empty, ignore"); return; } if (![self.dataSource respondsToSelector:@selector(dataProvider:ReceiveReadMsgWithGroupID:msgID:readCount:unreadCount:)]) { NSLog(@"data source can not respond to protocol, ignore"); } NSMutableDictionary *dict = [NSMutableDictionary dictionary]; for (V2TIMMessageReceipt *receipt in receiptList) { [dict setObject:receipt forKey:receipt.msgID]; } // update TUIMessageCellData readCount/unreadCount for (TUIMessageCellData *data in self.uiMsgs) { if ([dict.allKeys containsObject:data.innerMessage.msgID]) { V2TIMMessageReceipt *receipt = dict[data.innerMessage.msgID]; data.messageReceipt = receipt; if ([self.dataSource respondsToSelector:@selector(dataProvider:ReceiveReadMsgWithGroupID:msgID:readCount:unreadCount:)]) { [self.dataSource dataProvider:self ReceiveReadMsgWithGroupID:receipt.groupID msgID:receipt.msgID readCount:receipt.readCount unreadCount:receipt.unreadCount]; } } } } - (void)onRecvMessageRevoked:(NSString *)msgID operateUser:(V2TIMUserFullInfo *)operateUser reason:(NSString *)reason { @weakify(self); [TUITool dispatchMainAsync:^{ @strongify(self); TUIMessageCellData *uiMsg = nil; for (uiMsg in self.uiMsgs) { if ([uiMsg.msgID isEqualToString:msgID]) { [self.dataSource dataProviderDataSourceWillChange:self]; NSUInteger index = [self.uiMsgs indexOfObject:uiMsg]; TUISystemMessageCellData *revokeCellData = (TUISystemMessageCellData *)[self.class getRevokeCellData:uiMsg.innerMessage]; revokeCellData.content = [self.class getRevokeDispayString:uiMsg.innerMessage operateUser:operateUser reason:reason]; if(![operateUser.userID isEqualToString:uiMsg.innerMessage.sender]) { //Super User revoke revokeCellData.supportReEdit = NO; } [self replaceUIMsg:revokeCellData atIndex:index]; [self.dataSource dataProviderDataSourceChange:self withType:TUIMessageBaseDataProviderDataSourceChangeTypeReload atIndex:index animation:YES]; [self.dataSource dataProviderDataSourceDidChange:self]; break; } } if ([self.dataSource respondsToSelector:@selector(dataProvider:ReceiveRevokeUIMsg:)]) { [self.dataSource dataProvider:self ReceiveRevokeUIMsg:uiMsg]; } }]; } - (void)onRecvMessageModified:(V2TIMMessage *)msg { V2TIMMessage *imMsg = msg; if (imMsg == nil || ![imMsg isKindOfClass:V2TIMMessage.class]) { return; } @weakify(self); for (TUIMessageCellData *uiMsg in self.uiMsgs) { if ([uiMsg.msgID isEqualToString:imMsg.msgID]) { if ([uiMsg customReloadCellWithNewMsg:imMsg]) { return; } NSMutableArray *newUIMsgs = [self transUIMsgFromIMMsg:@[ imMsg ]]; if (newUIMsgs.count == 0) { return; } /** * Note that firstObject cannot be taken here, firstObject may be SystemMessageCellData that displays system time */ TUIMessageCellData *newUIMsg = newUIMsgs.lastObject; newUIMsg.messageReceipt = uiMsg.messageReceipt; [self preProcessMessage:@[newUIMsg] callback:^{ @strongify(self); NSInteger index = [self.uiMsgs indexOfObject:uiMsg]; if (index < self.uiMsgs.count) { [self.dataSource dataProviderDataSourceWillChange:self]; [self replaceUIMsg:newUIMsg atIndex:index]; [self.dataSource dataProviderDataSourceChange:self withType:TUIMessageBaseDataProviderDataSourceChangeTypeReload atIndex:index animation:YES]; [self.dataSource dataProviderDataSourceDidChange:self]; } }]; return; } } } - (void)dealTypingByStatusCellData:(TUITypingStatusCellData *)stastusData { if (1 == stastusData.typingStatus) { // The timer is retimed upon receipt of the notification from the other party's input [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resetTypingStatus) object:nil]; self.conversationModel.otherSideTyping = YES; // If the other party does not continue typing, end the status every 5 seconds [self performSelector:@selector(resetTypingStatus) withObject:nil afterDelay:5.0]; } else { self.conversationModel.otherSideTyping = NO; } } - (void)resetTypingStatus { self.conversationModel.otherSideTyping = NO; } #pragma mark - Msgs - (void)loadMessageSucceedBlock:(void (^)(BOOL isFirstLoad, BOOL isNoMoreMsg, NSArray *newMsgs))succeedBlock FailBlock:(V2TIMFail)failBlock { if (self.isLoadingData || self.isNoMoreMsg) { failBlock(ERR_SUCC, @"refreshing"); return; } self.isLoadingData = YES; @weakify(self); if (self.conversationModel.userID.length > 0) { [[V2TIMManager sharedInstance] getC2CHistoryMessageList:self.conversationModel.userID count:self.pageCount lastMsg:self.lastMsg succ:^(NSArray *msgs) { @strongify(self); if (msgs.count != 0) { self.lastMsg = msgs[msgs.count - 1]; } [self loadMessages:msgs SucceedBlock:succeedBlock]; } fail:^(int code, NSString *desc) { @strongify(self); self.isLoadingData = NO; if (failBlock) { failBlock(code, desc); } }]; } else if (self.conversationModel.groupID.length > 0) { [[V2TIMManager sharedInstance] getGroupHistoryMessageList:self.conversationModel.groupID count:self.pageCount lastMsg:self.lastMsg succ:^(NSArray *msgs) { @strongify(self); if (msgs.count != 0) { self.lastMsg = msgs[msgs.count - 1]; } [self loadMessages:msgs SucceedBlock:succeedBlock]; } fail:^(int code, NSString *desc) { @strongify(self); self.isLoadingData = NO; if (failBlock) { failBlock(code, desc); } }]; } } - (void)loadMessages:(NSArray *)msgs SucceedBlock:(void (^)(BOOL isFirstLoad, BOOL isNoMoreMsg, NSArray *newMsgs))succeedBlock { NSMutableArray *uiMsgs = [self transUIMsgFromIMMsg:msgs]; if (uiMsgs.count == 0) { return; } @weakify(self); [self preProcessMessage:uiMsgs callback:^{ @strongify(self); if (msgs.count < self.pageCount) { self.isNoMoreMsg = YES; } if (uiMsgs.count != 0) { NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, uiMsgs.count)]; [self insertUIMsgs:uiMsgs atIndexes:indexSet]; } self.isLoadingData = NO; if (succeedBlock) { succeedBlock(self.isFirstLoad, self.isNoMoreMsg, uiMsgs); } self.isFirstLoad = NO; }]; } - (void)preProcessMessage:(NSArray *)uiMsgs callback:(void (^)(void))callback { // Synchronous message processing: Messages can be preprocessed here, and complex and time-consuming operations are not recommended. [self processMessageSync:uiMsgs callback:callback]; // Asynchronous processing of messages: Please handle complex time-consuming operations in this function and update them in the form of reload Cell. [self processMessageAsync:uiMsgs]; } - (void)processMessageSync:(NSArray *)uiMsgs callback:(void (^)(void))callback { if (callback) { callback(); } } - (void)processMessageAsync:(NSArray *)uiMsgs { __weak typeof(self) weakSelf = self; [self getReactFromMessage:uiMsgs]; [self requestForAdditionalUserInfo:uiMsgs callback:^{ dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf.dataSource dataProviderDataSourceWillChange:weakSelf]; for (TUIMessageCellData *uiMsg in uiMsgs) { NSArray *userIDList = [weakSelf getUserIDListForAdditionalUserInfo:@[ uiMsg ]]; if (userIDList.count == 0) { continue; } NSUInteger index = [weakSelf.uiMsgs indexOfObject:uiMsg]; if (index != NSNotFound) { [weakSelf.dataSource dataProvider:weakSelf onRemoveHeightCache:uiMsg]; [weakSelf.dataSource dataProviderDataSourceChange:weakSelf withType:TUIMessageBaseDataProviderDataSourceChangeTypeReload atIndex:index animation:YES]; } } [weakSelf.dataSource dataProviderDataSourceDidChange:weakSelf]; }); [weakSelf processQuoteMessage:uiMsgs]; }]; } - (void)getReactFromMessage:(NSArray *)uiMsgs { if (uiMsgs.count == 0) { return; } // fetch react [[NSNotificationCenter defaultCenter] postNotificationName:@"TUIKitFetchReactNotification" object:uiMsgs]; } - (void)requestForAdditionalUserInfo:(NSArray *)uiMsgs callback:(void (^)(void))callback { NSArray *userIDList = [self getUserIDListForAdditionalUserInfo:uiMsgs]; if (userIDList.count == 0) { if (callback) { callback(); } return; } [self requestForUserDetailsInfo:userIDList callback:^(NSDictionary *result) { for (TUIMessageCellData *cellData in uiMsgs) { NSMutableDictionary *additionalUserInfoResult = [NSMutableDictionary dictionary]; NSArray *userIDList = [self getUserIDListForAdditionalUserInfo:@[ cellData ]]; for (NSString *userID in userIDList) { TUIRelationUserModel *userInfo = [result objectForKey:userID]; if (userID.length > 0 && userInfo) { additionalUserInfoResult[userID] = userInfo; } } cellData.additionalUserInfoResult = additionalUserInfoResult; } if (callback) { callback(); } }]; } - (void)requestForUserDetailsInfo:(NSArray *)userIDList callback:(void (^)(NSDictionary *))callback { if (callback == nil) { return; } NSMutableDictionary *result = [NSMutableDictionary dictionary]; if (self.conversationModel.groupID.length > 0) { [[V2TIMManager sharedInstance] getGroupMembersInfo:self.conversationModel.groupID memberList:userIDList succ:^(NSArray *memberList) { [memberList enumerateObjectsUsingBlock:^(V2TIMGroupMemberFullInfo *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { if (obj.userID.length > 0) { TUIRelationUserModel *userInfo = [[TUIRelationUserModel alloc] init]; userInfo.userID = obj.userID; userInfo.nickName = obj.nickName; userInfo.friendRemark = obj.friendRemark; userInfo.nameCard = obj.nameCard; userInfo.faceURL = obj.faceURL; [result setObject:userInfo forKey:userInfo.userID]; } }]; callback(result); } fail:^(int code, NSString *desc) { callback(result); }]; } else { [V2TIMManager.sharedInstance getFriendsInfo:userIDList succ:^(NSArray *resultList) { for (V2TIMFriendInfoResult *item in resultList) { if (item.friendInfo && item.friendInfo.userID.length > 0) { TUIRelationUserModel *userInfo = [[TUIRelationUserModel alloc] init]; userInfo.userID = item.friendInfo.userID; userInfo.nickName = item.friendInfo.userFullInfo.nickName; userInfo.friendRemark = item.friendInfo.friendRemark; userInfo.faceURL = item.friendInfo.userFullInfo.faceURL; [result setObject:userInfo forKey:userInfo.userID]; } } callback(result); } fail:^(int code, NSString *desc) { callback(result); }]; } } - (NSArray *)getUserIDListForAdditionalUserInfo:(NSArray *)uiMsgs { NSMutableSet *userIDSet = [NSMutableSet set]; // Built-in for (TUIMessageCellData *cellData in uiMsgs) { if (![cellData.messageModifyReplies isKindOfClass:NSArray.class] || cellData.messageModifyReplies.count == 0) { continue; } [cellData.messageModifyReplies enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { if (obj && [obj isKindOfClass:NSDictionary.class]) { NSDictionary *dic = (NSDictionary *)obj; if (IS_NOT_EMPTY_NSSTRING(dic[@"messageSender"])) { [userIDSet addObject:dic[@"messageSender"]]; } } }]; } // Get userid from cellData which override the -requestForAdditionalUserInfo method for (TUIMessageCellData *cellData in uiMsgs) { if ([cellData respondsToSelector:@selector(requestForAdditionalUserInfo)]) { NSArray *array = [cellData requestForAdditionalUserInfo]; if (array && array.count > 0) { [userIDSet addObjectsFromArray:array]; } } } NSArray *userIDList = userIDSet.allObjects; return userIDList; } - (void)processQuoteMessage:(NSArray *)uiMsgs { // Subclasses implement this method return; } - (void)sendUIMsg:(TUIMessageCellData *)uiMsg toConversation:(TUIChatConversationModel *)conversationData willSendBlock:(void (^)(BOOL isReSend, TUIMessageCellData *dateUIMsg))willSendBlock SuccBlock:(nullable V2TIMSucc)succ FailBlock:(nullable V2TIMFail)fail { [self preProcessMessage:@[ uiMsg ] callback:^{ [TUITool dispatchMainAsync:^{ V2TIMMessage *imMsg = uiMsg.innerMessage; TUIMessageCellData *placeholderCellData = uiMsg.placeHolderCellData; TUIMessageCellData *dateMsg = nil; BOOL isReSent = NO; if (uiMsg.status == Msg_Status_Init) { // New message dateMsg = [self getSystemMsgFromDate:imMsg.timestamp]; } else if (imMsg) { // Re-sent isReSent = YES; dateMsg = [self getSystemMsgFromDate:[NSDate date]]; } else { if (fail) { fail(ERR_INVALID_PARAMETERS, @"Unknown message state"); } return; } imMsg.isExcludedFromUnreadCount = [TUIConfig defaultConfig].isExcludedFromUnreadCount; imMsg.isExcludedFromLastMessage = [TUIConfig defaultConfig].isExcludedFromLastMessage; // Update sender uiMsg.identifier = [TUILogin getUserID]; [self.dataSource dataProviderDataSourceWillChange:self]; // Handle data if (isReSent) { NSInteger row = [self.uiMsgs indexOfObject:uiMsg]; [self removeUImsgAtIndex:row]; [self.dataSource dataProviderDataSourceChange:self withType:TUIMessageBaseDataProviderDataSourceChangeTypeDelete atIndex:row animation:YES]; } if (placeholderCellData) { NSInteger row = [self.uiMsgs indexOfObject:placeholderCellData]; if (row != NSNotFound) { [self replaceUIMsg:uiMsg atIndex:row]; [self.dataSource dataProviderDataSourceChange:self withType:TUIMessageBaseDataProviderDataSourceChangeTypeReload atIndex:row animation:NO]; } } else { if (dateMsg) { [self addUIMsg:dateMsg]; [self.dataSource dataProviderDataSourceChange:self withType:TUIMessageBaseDataProviderDataSourceChangeTypeInsert atIndex:(self.uiMsgs.count - 1) animation:YES]; } [self addUIMsg:uiMsg]; [self.dataSource dataProviderDataSourceChange:self withType:TUIMessageBaseDataProviderDataSourceChangeTypeInsert atIndex:(self.uiMsgs.count - 1) animation:YES]; } [self.dataSource dataProviderDataSourceDidChange:self]; if (willSendBlock) { willSendBlock(isReSent, dateMsg); } if (dateMsg) { self.msgForDate = imMsg; } TUISendMessageAppendParams *appendParams = [[TUISendMessageAppendParams alloc] init]; appendParams.isSendPushInfo = YES; appendParams.isOnlineUserOnly = NO; appendParams.priority = V2TIM_PRIORITY_NORMAL; uiMsg.msgID = [self.class sendMessage:imMsg toConversation:conversationData appendParams:appendParams Progress:^(uint32_t progress) { [TUIMessageProgressManager.shareManager appendUploadProgress:uiMsg.msgID progress:progress]; } SuccBlock:^{ [TUIMessageProgressManager.shareManager appendUploadProgress:uiMsg.msgID progress:100]; if (succ) { succ(); } [TUIMessageProgressManager.shareManager notifyMessageSendingResult:uiMsg.msgID result:TUIMessageSendingResultTypeSucc]; } FailBlock:^(int code, NSString *desc) { if (fail) { fail(code, desc); } [TUIMessageProgressManager.shareManager notifyMessageSendingResult:uiMsg.msgID result:TUIMessageSendingResultTypeFail]; }]; }]; }]; } - (void)revokeUIMsg:(TUIMessageCellData *)uiMsg SuccBlock:(nullable V2TIMSucc)succ FailBlock:(nullable V2TIMFail)fail { V2TIMMessage *imMsg = uiMsg.innerMessage; if (imMsg == nil) { if (fail) { fail(ERR_INVALID_PARAMETERS, @"cellData.innerMessage is nil"); } return; } NSInteger index = [self.uiMsgs indexOfObject:uiMsg]; if (index == NSNotFound) { if (fail) { fail(ERR_INVALID_PARAMETERS, @"not found cellData in uiMsgs"); } return; } @weakify(self); [self.class revokeMessage:imMsg succ:^{ @strongify(self); if (succ) { succ(); } } fail:fail]; } - (void)deleteUIMsgs:(NSArray *)uiMsgs SuccBlock:(nullable V2TIMSucc)succ FailBlock:(nullable V2TIMFail)fail { } - (void)addUIMsg:(TUIMessageCellData *)cellData { [self.uiMsgs_ addObject:cellData]; if (self.mergeAdjacentMsgsFromTheSameSender) { [self.class updateUIMsgStatus:cellData uiMsgs:self.uiMsgs_]; } } - (void)removeUIMsg:(TUIMessageCellData *)cellData { if (cellData) { [self.uiMsgs_ removeObject:cellData]; if ([self.dataSource respondsToSelector:@selector(dataProvider:onRemoveHeightCache:)]) { [self.dataSource dataProvider:self onRemoveHeightCache:cellData]; } if (self.mergeAdjacentMsgsFromTheSameSender) { [self.class updateUIMsgStatus:cellData uiMsgs:self.uiMsgs_]; } } } - (void)sendPlaceHolderUIMessage:(TUIMessageCellData *)placeHolderCellData { V2TIMMessage *imMsg = placeHolderCellData.innerMessage; TUIMessageCellData *dateMsg = nil; if (placeHolderCellData.status == Msg_Status_Init) { // New message dateMsg = [self getSystemMsgFromDate:imMsg.timestamp]; } [self.dataSource dataProviderDataSourceWillChange:self]; if (dateMsg) { [self addUIMsg:dateMsg]; [self.dataSource dataProviderDataSourceChange:self withType:TUIMessageBaseDataProviderDataSourceChangeTypeInsert atIndex:(self.uiMsgs.count - 1) animation:YES]; } [self addUIMsg:placeHolderCellData]; [self.dataSource dataProviderDataSourceChange:self withType:TUIMessageBaseDataProviderDataSourceChangeTypeInsert atIndex:(self.uiMsgs.count - 1) animation:YES]; [self.dataSource dataProviderDataSourceDidChange:self]; } - (void)insertUIMsgs:(NSArray *)uiMsgs atIndexes:(NSIndexSet *)indexes { [self.uiMsgs_ insertObjects:uiMsgs atIndexes:indexes]; if (self.mergeAdjacentMsgsFromTheSameSender) { for (TUIMessageCellData *cellData in uiMsgs) { [self.class updateUIMsgStatus:cellData uiMsgs:self.uiMsgs_]; } } } - (void)addUIMsgs:(NSArray *)uiMsgs { [self.uiMsgs_ addObjectsFromArray:uiMsgs]; if (self.mergeAdjacentMsgsFromTheSameSender) { for (TUIMessageCellData *cellData in uiMsgs) { [self.class updateUIMsgStatus:cellData uiMsgs:self.uiMsgs_]; } } } - (void)removeUIMsgList:(NSArray *)cellDatas { for (TUIMessageCellData *uiMsg in cellDatas) { [self removeUIMsg:uiMsg]; } } - (void)removeUImsgAtIndex:(NSUInteger)index { if (index < self.uiMsgs.count) { TUIMessageCellData *msg = self.uiMsgs[index]; [self removeUIMsg:msg]; } } - (void)clearUIMsgList { NSArray *clearArray = [NSArray arrayWithArray:self.uiMsgs]; [self removeUIMsgList:clearArray]; self.msgForDate = nil; self.uiMsgs_ = nil; } - (void)replaceUIMsg:(TUIMessageCellData *)cellData atIndex:(NSUInteger)index { if (index < self.uiMsgs.count) { TUIMessageCellData *oldMsg = self.uiMsgs[index]; if ([self.dataSource respondsToSelector:@selector(dataProvider:onRemoveHeightCache:)]) { [self.dataSource dataProvider:self onRemoveHeightCache:oldMsg]; } [self.uiMsgs_ replaceObjectAtIndex:index withObject:cellData]; if (self.mergeAdjacentMsgsFromTheSameSender) { [self.class updateUIMsgStatus:cellData uiMsgs:self.uiMsgs_]; } } } - (void)sendLatestMessageReadReceipt { [self sendMessageReadReceiptAtIndexes:@[ @(self.uiMsgs.count - 1) ]]; } - (void)sendMessageReadReceiptAtIndexes:(NSArray *)indexes { if (indexes.count == 0) { NSLog(@"sendMessageReadReceipt, but indexes is empty, ignore"); return; } NSMutableArray *array = [NSMutableArray array]; for (NSNumber *i in indexes) { if ([i intValue] < 0 || [i intValue] >= self.uiMsgs_.count) { continue; } TUIMessageCellData *data = self.uiMsgs_[[i intValue]]; if (data.innerMessage.isSelf) { continue; } if (data.innerMessage == nil) { continue; } // Use Set to avoid sending duplicate element to SDK if (data.msgID.length > 0) { if ([self.sentReadGroupMsgSet containsObject:data.msgID]) { continue; } else { [self.sentReadGroupMsgSet addObject:data.msgID]; } } // If needReadReceipt is NO, receiver won't send message read receipt if (!data.innerMessage.needReadReceipt) { continue; } [array addObject:data.innerMessage]; } if (array.count == 0) { return; } [self.class sendMessageReadReceipts:array]; } - (NSInteger)getIndexOfMessage:(NSString *)msgID { if (msgID.length == 0) { return -1; } for (int i = 0; i < self.uiMsgs.count; i++) { TUIMessageCellData *data = self.uiMsgs[i]; if ([data.msgID isEqualToString:msgID]) { return i; } } return -1; } - (nullable TUIMessageCellData *)getSystemMsgFromDate:(NSDate *)date { if (self.msgForDate == nil || fabs([date timeIntervalSinceDate:self.msgForDate.timestamp]) > MaxDateMessageDelay) { TUIMessageCellData *system = [self.class getSystemMsgFromDate:date]; return system; } return nil; } + (void)updateUIMsgStatus:(TUIMessageCellData *)cellData uiMsgs:(NSArray *)uiMsgs { if (![uiMsgs containsObject:cellData]) { return; } NSInteger index = [uiMsgs indexOfObject:cellData]; TUIMessageCellData *data = uiMsgs[index]; TUIMessageCellData *lastData = nil; if (index >= 1) { lastData = uiMsgs[index - 1]; if (![lastData isKindOfClass:[TUISystemMessageCellData class]]) { if ([lastData.identifier isEqualToString:data.identifier] && ![data isKindOfClass:[TUISystemMessageCellData class]] &&(lastData.direction == data.direction)) { lastData.sameToNextMsgSender = YES; lastData.showAvatar = NO; } else { lastData.sameToNextMsgSender = NO; lastData.showAvatar = (lastData.direction == MsgDirectionIncoming ? YES : NO); } } } TUIMessageCellData *nextData = nil; if (index < uiMsgs.count - 1) { nextData = uiMsgs[index + 1]; if ([data.identifier isEqualToString:nextData.identifier] &&(data.direction == nextData.direction)) { data.sameToNextMsgSender = YES; data.showAvatar = NO; } else { data.sameToNextMsgSender = NO; data.showAvatar = (data.direction == MsgDirectionIncoming ? YES : NO); } } if (index == uiMsgs.count - 1) { data.showAvatar = (data.direction == MsgDirectionIncoming ? YES : NO); data.sameToNextMsgSender = NO; } } - (void)getPinMessageList { if (self.conversationModel.groupID.length > 0) { __weak typeof(self) weakSelf = self; [V2TIMManager.sharedInstance getPinnedGroupMessageList:self.conversationModel.groupID succ:^(NSArray *messageList) { weakSelf.groupPinList = [NSMutableArray arrayWithArray:messageList]; if (weakSelf.pinGroupMessageChanged) { weakSelf.pinGroupMessageChanged(weakSelf.groupPinList); } } fail:^(int code, NSString *desc) { weakSelf.groupPinList = [NSMutableArray array]; if (weakSelf.pinGroupMessageChanged) { weakSelf.pinGroupMessageChanged(weakSelf.groupPinList); } }]; } } - (void)loadGroupInfo:(dispatch_block_t)callback { if (self.conversationModel.groupID.length == 0) { if (callback) { callback(); } return; } __weak typeof(self) weakSelf = self; [[V2TIMManager sharedInstance] getGroupsInfo:@[ self.conversationModel.groupID ] succ:^(NSArray *groupResultList) { if (groupResultList.count == 1) { V2TIMGroupInfo *info = groupResultList[0].info; weakSelf.groupInfo = info; } if (callback) { callback(); } } fail:^(int code, NSString *msg) { [TUITool makeToastError:code msg:msg]; }]; } - (void)getSelfInfoInGroup:(dispatch_block_t)callback { NSString *loginUserID = [[V2TIMManager sharedInstance] getLoginUser]; if (loginUserID.length == 0) { if (callback) { callback(); } return; } if (!self.conversationModel.enableRoom) { if (callback) { callback(); } return; } __weak typeof(self) weakSelf = self; [[V2TIMManager sharedInstance] getGroupMembersInfo:self.conversationModel.groupID memberList:@[ loginUserID ] succ:^(NSArray *memberList) { for (V2TIMGroupMemberFullInfo *item in memberList) { if ([item.userID isEqualToString:loginUserID]) { weakSelf.groupSelfInfo = item; if ([weakSelf.groupInfo.owner isEqualToString:loginUserID]) { weakSelf.changedRole = V2TIM_GROUP_MEMBER_ROLE_SUPER; } else { weakSelf.changedRole = item.role; } break; } } if (callback) { callback(); } } fail:^(int code, NSString *desc) { [TUITool makeToastError:code msg:desc]; if (callback) { callback(); } }]; } - (BOOL)isCurrentUserRoleSuperAdminInGroup { if (self.changedRole != V2TIM_GROUP_MEMBER_UNDEFINED) { return (self.changedRole == V2TIM_GROUP_MEMBER_ROLE_ADMIN || self.changedRole == V2TIM_GROUP_MEMBER_ROLE_SUPER); } return ([self.groupInfo.owner isEqualToString:[[V2TIMManager sharedInstance] getLoginUser]] ||self.groupSelfInfo.role == V2TIM_GROUP_MEMBER_ROLE_ADMIN || self.groupSelfInfo.role == V2TIM_GROUP_MEMBER_ROLE_SUPER) ; } - (BOOL)isCurrentMessagePin:(NSString *)msgID { BOOL isPin = NO; for (V2TIMMessage *msg in self.groupPinList) { if ([msg.msgID isEqualToString:msgID]) { isPin = YES; break; } } return isPin; } - (void)pinGroupMessage:(NSString *)groupID message:(V2TIMMessage *)message isPinned:(BOOL)isPinned succ:(V2TIMSucc)succ fail:(V2TIMFail)fail { __weak typeof(self) weakSelf = self; [V2TIMManager.sharedInstance pinGroupMessage:groupID message:message isPinned:isPinned succ:^{ if (isPinned) { //del from changed } else { //add from changed } if (succ) { succ(); } } fail:^(int code, NSString *desc) { if (fail) { fail(code,desc); } }]; } - (void)onGroupMessagePinned:(NSString *)groupID message:(V2TIMMessage *)message isPinned:(BOOL)isPinned opUser:(V2TIMGroupMemberInfo *)opUser { if (![groupID isEqualToString:self.conversationModel.groupID]) { return; } if (isPinned) { //add [self.groupPinList addObject:message]; } else { //del NSMutableArray *formatArray = [NSMutableArray arrayWithArray:self.groupPinList]; for (V2TIMMessage *msg in formatArray) { if ([msg.msgID isEqualToString:message.msgID]) { [self.groupPinList removeObject:msg]; break; } } } if (self.pinGroupMessageChanged) { self.pinGroupMessageChanged(self.groupPinList); } } - (void)onGroupInfoChanged:(NSString *)groupID changeInfoList:(NSArray *)changeInfoList { if (![groupID isEqualToString:self.conversationModel.groupID]) { return; } for (V2TIMGroupChangeInfo *changeInfo in changeInfoList) { if (changeInfo.type == V2TIM_GROUP_INFO_CHANGE_TYPE_OWNER) { NSString *ownerID = changeInfo.value; if ([ownerID isEqualToString:[TUILogin getUserID]]) { self.changedRole = V2TIM_GROUP_MEMBER_ROLE_SUPER; } else { if (self.changedRole ==V2TIM_GROUP_MEMBER_ROLE_SUPER) { self.changedRole = V2TIM_GROUP_MEMBER_ROLE_MEMBER; } } return; } } } - (void)onGrantAdministrator:(NSString *)groupID opUser:(V2TIMGroupMemberInfo *)opUser memberList:(NSArray *)memberList { if (![groupID isEqualToString:self.conversationModel.groupID]) { return; } for (V2TIMGroupMemberInfo *changeInfo in memberList) { if (changeInfo.userID == [TUILogin getUserID]) { self.changedRole = V2TIM_GROUP_MEMBER_ROLE_ADMIN; return; } } } - (void)onRevokeAdministrator:(NSString *)groupID opUser:(V2TIMGroupMemberInfo *)opUser memberList:(NSArray *)memberList { if (![groupID isEqualToString:self.conversationModel.groupID]) { return; } for (V2TIMGroupMemberInfo *changeInfo in memberList) { if (changeInfo.userID == [TUILogin getUserID]) { self.changedRole = V2TIM_GROUP_MEMBER_ROLE_MEMBER; return; } } } @end @implementation TUIMessageBaseDataProvider (IMSDK) static const int kOfflinePushVersion = 1; + (NSString *)sendMessage:(V2TIMMessage *)message toConversation:(TUIChatConversationModel *)conversationData appendParams:(TUISendMessageAppendParams *)appendParams Progress:(nullable V2TIMProgress)progress SuccBlock:(nullable V2TIMSucc)succ FailBlock:(nullable V2TIMFail)fail { NSString *userID = conversationData.userID; NSString *groupID = conversationData.groupID; NSAssert(userID || groupID, @"userID and groupID cannot be null at same time"); NSString *conversationID = @""; if (!appendParams) { NSLog(@"appendParams cannot be nil"); } BOOL isSendPushInfo = appendParams.isSendPushInfo; BOOL isOnlineUserOnly = appendParams.isOnlineUserOnly; V2TIMMessagePriority priority = appendParams.priority; if (IS_NOT_EMPTY_NSSTRING(userID)) { conversationID = [NSString stringWithFormat:@"c2c_%@", userID]; } if (IS_NOT_EMPTY_NSSTRING(groupID)) { conversationID = [NSString stringWithFormat:@"group_%@", groupID]; } if (IS_NOT_EMPTY_NSSTRING(conversationData.conversationID)) { conversationID = conversationData.conversationID; } NSParameterAssert(message); V2TIMOfflinePushInfo *pushInfo = nil; if (isSendPushInfo) { pushInfo = [[V2TIMOfflinePushInfo alloc] init]; BOOL isGroup = groupID.length > 0; NSString *senderId = isGroup ? (groupID) : ([TUILogin getUserID]); senderId = senderId ?: @""; NSString *nickName = isGroup ? (conversationData.title) : ([TUILogin getNickName] ?: [TUILogin getUserID]); nickName = nickName ?: @""; NSString * content = [self getDisplayString:message] ?: @""; OfflinePushExtInfo *extInfo = [[OfflinePushExtInfo alloc] init]; OfflinePushExtBusinessInfo * entity = extInfo.entity; entity.action = 1; entity.content = content; entity.sender = senderId; entity.nickname = nickName; entity.faceUrl = [TUILogin getFaceUrl] ?: @""; entity.chatType = [isGroup ? @(V2TIM_GROUP) : @(V2TIM_C2C) integerValue]; entity.version = kOfflinePushVersion; pushInfo.ext = [extInfo toReportExtString]; if (content.length > 0) { pushInfo.desc = content; } if (nickName.length > 0) { pushInfo.title = nickName; } pushInfo.AndroidOPPOChannelID = @"tuikit"; pushInfo.AndroidSound = TUIConfig.defaultConfig.enableCustomRing ? @"private_ring" : nil; pushInfo.AndroidHuaWeiCategory = @"IM"; pushInfo.AndroidVIVOCategory = @"IM"; pushInfo.AndroidHonorImportance = @"NORMAL"; pushInfo.iOSInterruptionLevel = @"time-sensitive"; pushInfo.enableIOSBackgroundNotification = NO; } if ([self isGroupCommunity:conversationData.groupType groupID:conversationData.groupID] || [self isGroupAVChatRoom:conversationData.groupType]) { message.needReadReceipt = NO; } // Hidden conversation evokes the chat page from the address book entry - to send a message, the hidden flag needs to be cleared if (conversationID.length > 0) { [V2TIMManager.sharedInstance markConversation:@[ conversationID ] markType:@(V2TIM_CONVERSATION_MARK_TYPE_HIDE) enableMark:NO succ:nil fail:nil]; } if (conversationData.userID.length > 0) { // C2C NSDictionary *cloudCustomDataDic = @{ @"needTyping" : @1, @"version" : @1, }; [message setCloudCustomData:cloudCustomDataDic forType:messageFeature]; } return [V2TIMManager.sharedInstance sendMessage:message receiver:userID groupID:groupID priority:priority onlineUserOnly:isOnlineUserOnly offlinePushInfo:pushInfo progress:progress succ:^{ succ(); } fail:^(int code, NSString *desc) { if (code == ERR_SDK_INTERFACE_NOT_SUPPORT) { [TUITool postUnsupportNotificationOfService:TUIKitLocalizableString(TUIKitErrorUnsupportIntefaceMessageRead)]; } fail(code, desc); }]; } - (void)getLastMessage:(BOOL)isFromLocal succ:(void (^)(V2TIMMessage *message))succ fail:(V2TIMFail)fail; { V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init]; if (self.conversationModel.userID.length > 0) { option.userID = self.conversationModel.userID; } if (self.conversationModel.groupID.length > 0) { option.groupID = self.conversationModel.groupID; } option.getType = isFromLocal ? V2TIM_GET_LOCAL_OLDER_MSG : V2TIM_GET_CLOUD_OLDER_MSG; option.lastMsg = nil; option.count = 1; [[V2TIMManager sharedInstance] getHistoryMessageList:option succ:^(NSArray *msgs) { if (succ) { succ(msgs.count > 0 ? msgs.firstObject : nil); } } fail:^(int code, NSString *desc) { if (fail) { fail(code, desc); } }]; } + (BOOL)isGroupCommunity:(NSString *)groupType groupID:(NSString *)groupID { return [groupType isEqualToString:@"Community"] || [groupID startsWith:@"@TGS#_"]; } + (BOOL)isGroupAVChatRoom:(NSString *)groupType { return [groupType isEqualToString:@"AVChatRoom"]; } + (void)markC2CMessageAsRead:(NSString *)userID succ:(nullable V2TIMSucc)succ fail:(nullable V2TIMFail)fail { NSString * conversationID = [NSString stringWithFormat:@"c2c_%@",userID]; [[V2TIMManager sharedInstance] cleanConversationUnreadMessageCount:conversationID cleanTimestamp:0 cleanSequence:0 succ:succ fail:fail]; } + (void)markGroupMessageAsRead:(NSString *)groupID succ:(nullable V2TIMSucc)succ fail:(nullable V2TIMFail)fail { NSString * conversationID = [NSString stringWithFormat:@"group_%@",groupID]; [[V2TIMManager sharedInstance] cleanConversationUnreadMessageCount:conversationID cleanTimestamp:0 cleanSequence:0 succ:succ fail:fail]; } + (void)markConversationAsUndead:(NSArray *)conversationIDList enableMark:(BOOL)enableMark { [V2TIMManager.sharedInstance markConversation:conversationIDList markType:@(V2TIM_CONVERSATION_MARK_TYPE_UNREAD) enableMark:enableMark succ:nil fail:nil]; } + (void)revokeMessage:(V2TIMMessage *)msg succ:(nullable V2TIMSucc)succ fail:(nullable V2TIMFail)fail { [[V2TIMManager sharedInstance] revokeMessage:msg succ:succ fail:fail]; } + (void)deleteMessages:(NSArray *)msgList succ:(nullable V2TIMSucc)succ fail:(nullable V2TIMFail)fail { [[V2TIMManager sharedInstance] deleteMessages:msgList succ:succ fail:fail]; } + (void)modifyMessage:(V2TIMMessage *)msg completion:(V2TIMMessageModifyCompletion)completion { [[V2TIMManager sharedInstance] modifyMessage:msg completion:completion]; } + (void)sendMessageReadReceipts:(NSArray *)msgs { [[V2TIMManager sharedInstance] sendMessageReadReceipts:msgs succ:^{ NSLog(@"sendMessageReadReceipts succeed"); } fail:^(int code, NSString *desc) { if (code == ERR_SDK_INTERFACE_NOT_SUPPORT) { [TUITool postUnsupportNotificationOfService:TUIKitLocalizableString(TUIKitErrorUnsupportIntefaceMessageRead)]; } }]; } + (void)getReadMembersOfMessage:(V2TIMMessage *)msg filter:(V2TIMGroupMessageReadMembersFilter)filter nextSeq:(NSUInteger)nextSeq completion:(void (^)(int code, NSString *desc, NSArray *members, NSUInteger nextSeq, BOOL isFinished))block { [[V2TIMManager sharedInstance] getGroupMessageReadMemberList:msg filter:filter nextSeq:nextSeq count:100 succ:^(NSMutableArray *members, uint64_t nextSeq, BOOL isFinished) { if (block) { block(0, nil, members, nextSeq, isFinished); } } fail:^(int code, NSString *desc) { if (block) { block(code, desc, nil, 0, NO); } }]; } + (void)getMessageReadReceipt:(NSArray *)messages succ:(nullable V2TIMMessageReadReceiptsSucc)succ fail:(nullable V2TIMFail)fail { if (messages.count == 0) { if (fail) { fail(-1, @"messages empty"); } return; } [[V2TIMManager sharedInstance] getMessageReadReceipts:messages succ:succ fail:fail]; } + (TUIMessageCellData *__nullable)getCellData:(V2TIMMessage *)message { // subclass override required return nil; } + (nullable TUIMessageCellData *)getSystemMsgFromDate:(NSDate *)date { // subclass override required return nil; } + (TUIMessageCellData *)getRevokeCellData:(V2TIMMessage *)message { // subclass override required return nil; } + (nullable NSString *)getDisplayString:(V2TIMMessage *)message { // subclass override required return nil; } + (NSString *)getRevokeDispayString:(V2TIMMessage *)message { return [self getRevokeDispayString:message operateUser:nil reason:nil]; } + (NSString *)getRevokeDispayString:(V2TIMMessage *)message operateUser:(V2TIMUserFullInfo *)operateUser reason:(NSString *)reason { V2TIMUserFullInfo *revokerInfo = message.revokerInfo ? message.revokerInfo : operateUser; BOOL hasRiskContent = message.hasRiskContent; NSString *revoker = message.sender; NSString *messageSender = message.sender; if (revokerInfo) { revoker = revokerInfo.userID; } NSString *content = TIMCommonLocalizableString(TUIKitMessageTipsNormalRecallMessage); if ([revoker isEqualToString:messageSender]) { if (message.isSelf) { content = TIMCommonLocalizableString(TUIKitMessageTipsYouRecallMessage); } else { if (message.userID.length > 0) { // c2c content = TIMCommonLocalizableString(TUIKitMessageTipsOthersRecallMessage); } else if (message.groupID.length > 0) { NSString *userName = [self.class getShowName:message]; content = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsRecallMessageFormat), userName]; } else { // empty } } } else { NSString *userName = [self.class getShowName:message]; if (revokerInfo) { userName = revokerInfo.showName; } content = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsRecallMessageFormat), userName]; } return rtlString(content); } + (NSString *)getGroupTipsDisplayString:(V2TIMMessage *)message { V2TIMGroupTipsElem *tips = message.groupTipsElem; NSString *opUser = [self getOpUserName:tips.opMember]; NSMutableArray *userList = [self getUserNameList:tips.memberList]; NSString *str = nil; switch (tips.type) { case V2TIM_GROUP_TIPS_TYPE_JOIN: { if (opUser.length > 0) { if ((userList.count == 0) || (userList.count == 1 && [opUser isEqualToString:userList.firstObject])) { str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsJoinGroupFormat), opUser]; } else { NSString *users = [userList componentsJoinedByString:@"、"]; str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsInviteJoinGroupFormat), opUser, users]; } } } break; case V2TIM_GROUP_TIPS_TYPE_INVITE: { if (userList.count > 0) { NSString *users = [userList componentsJoinedByString:@"、"]; str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsInviteJoinGroupFormat), opUser, users]; } } break; case V2TIM_GROUP_TIPS_TYPE_QUIT: { if (opUser.length > 0) { str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsLeaveGroupFormat), opUser]; } } break; case V2TIM_GROUP_TIPS_TYPE_KICKED: { if (userList.count > 0) { NSString *users = [userList componentsJoinedByString:@"、"]; str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsKickoffGroupFormat), opUser, users]; } } break; case V2TIM_GROUP_TIPS_TYPE_SET_ADMIN: { if (userList.count > 0) { NSString *users = [userList componentsJoinedByString:@"、"]; str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsSettAdminFormat), users]; } } break; case V2TIM_GROUP_TIPS_TYPE_CANCEL_ADMIN: { if (userList.count > 0) { NSString *users = [userList componentsJoinedByString:@"、"]; str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsCancelAdminFormat), users]; } } break; case V2TIM_GROUP_TIPS_TYPE_GROUP_INFO_CHANGE: { str = [self opGroupInfoChagedFormatStr:opUser ofUserList:userList ofTips:tips]; if (str.length > 0) { str = [str substringToIndex:str.length - 1]; } } break; case V2TIM_GROUP_TIPS_TYPE_MEMBER_INFO_CHANGE: { for (V2TIMGroupChangeInfo *info in tips.memberChangeInfoList) { if ([info isKindOfClass:V2TIMGroupMemberChangeInfo.class]) { NSString *userId = [(V2TIMGroupMemberChangeInfo *)info userID]; int32_t muteTime = [(V2TIMGroupMemberChangeInfo *)info muteTime]; NSString *myId = V2TIMManager.sharedInstance.getLoginUser; NSString *showName = [self.class getUserName:tips with:userId]; str = [NSString stringWithFormat:@"%@ %@", [userId isEqualToString:myId] ? TIMCommonLocalizableString(You) : showName, muteTime == 0 ? TIMCommonLocalizableString(TUIKitMessageTipsUnmute) : TIMCommonLocalizableString(TUIKitMessageTipsMute)]; break; } } } break; case V2TIM_GROUP_TIPS_TYPE_PINNED_MESSAGE_ADDED: { if (opUser.length > 0) { str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsGroupPinMessage), opUser]; } } break; case V2TIM_GROUP_TIPS_TYPE_PINNED_MESSAGE_DELETED: { if (opUser.length > 0) { str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsGroupUnPinMessage), opUser]; } } break; default: break; } return rtlString(str); } + (V2TIMMessage *)getCustomMessageWithJsonData:(NSData *)data { return [[V2TIMManager sharedInstance] createCustomMessage:data]; } + (V2TIMMessage *)getCustomMessageWithJsonData:(NSData *)data desc:(NSString *)desc extension:(NSString *)extension { return [[V2TIMManager sharedInstance] createCustomMessage:data desc:desc extension:extension]; } + (NSString *)opGroupInfoChagedFormatStr:(NSString *)opUser ofUserList:(NSMutableArray *)userList ofTips:(V2TIMGroupTipsElem *)tips{ NSString *str = nil; str = [NSString stringWithFormat:@"%@", opUser]; for (V2TIMGroupChangeInfo *info in tips.groupChangeInfoList) { switch (info.type) { case V2TIM_GROUP_INFO_CHANGE_TYPE_NAME: { str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIkitMessageTipsEditGroupNameFormat), str, info.value]; } break; case V2TIM_GROUP_INFO_CHANGE_TYPE_INTRODUCTION: { str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsEditGroupIntroFormat), str, info.value]; } break; case V2TIM_GROUP_INFO_CHANGE_TYPE_NOTIFICATION: { if (info.value.length) { str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsEditGroupAnnounceFormat), str, info.value]; } else { str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsDeleteGroupAnnounceFormat), str]; } } break; case V2TIM_GROUP_INFO_CHANGE_TYPE_FACE: { str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsEditGroupAvatarFormat), str]; } break; case V2TIM_GROUP_INFO_CHANGE_TYPE_OWNER: { if (userList.count) { str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsEditGroupOwnerFormat), str, userList.firstObject]; } else { str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsEditGroupOwnerFormat), str, info.value]; } } break; case V2TIM_GROUP_INFO_CHANGE_TYPE_SHUT_UP_ALL: { if (info.boolValue) { str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitSetShutupAllFormat), opUser]; } else { str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitCancelShutupAllFormat), opUser]; } } break; case V2TIM_GROUP_INFO_CHANGE_TYPE_GROUP_ADD_OPT: { uint32_t addOpt = info.intValue; NSString *addOptDesc = @"unknown"; if (addOpt == V2TIM_GROUP_ADD_FORBID) { addOptDesc = TIMCommonLocalizableString(TUIKitGroupProfileJoinDisable); } else if (addOpt == V2TIM_GROUP_ADD_AUTH) { addOptDesc = TIMCommonLocalizableString(TUIKitGroupProfileAdminApprove); } else if (addOpt == V2TIM_GROUP_ADD_ANY) { addOptDesc = TIMCommonLocalizableString(TUIKitGroupProfileAutoApproval); } str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsEditGroupAddOptFormat), str, addOptDesc]; } break; case V2TIM_GROUP_INFO_CHANGE_TYPE_GROUP_APPROVE_OPT: { uint32_t addOpt = info.intValue; NSString *addOptDesc = @"unknown"; if (addOpt == V2TIM_GROUP_ADD_FORBID) { addOptDesc = TIMCommonLocalizableString(TUIKitGroupProfileInviteDisable); } else if (addOpt == V2TIM_GROUP_ADD_AUTH) { addOptDesc = TIMCommonLocalizableString(TUIKitGroupProfileAdminApprove); } else if (addOpt == V2TIM_GROUP_ADD_ANY) { addOptDesc = TIMCommonLocalizableString(TUIKitGroupProfileAutoApproval); } str = [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitMessageTipsEditGroupInviteOptFormat), str, addOptDesc]; } break; default: break; } } return rtlString(str); } + (NSString *)getOpUserName:(V2TIMGroupMemberInfo *)info { NSString *opUser; if (info.nameCard.length > 0) { opUser = info.nameCard; } else if (info.nickName.length > 0) { opUser = info.nickName; } else { opUser = info.userID; } return opUser; } + (NSMutableArray *)getUserNameList:(NSArray *)infoList { NSMutableArray *userNameList = [NSMutableArray array]; for (V2TIMGroupMemberInfo *info in infoList) { if (info.nameCard.length > 0) { [userNameList addObject:info.nameCard]; } else if (info.nickName.length > 0) { [userNameList addObject:info.nickName]; } else { if (info.userID.length > 0) { [userNameList addObject:info.userID]; } } } return userNameList; } + (NSMutableArray *)getUserIDList:(NSArray *)infoList { NSMutableArray *userIDList = [NSMutableArray array]; for (V2TIMGroupMemberInfo *info in infoList) { if (info.userID.length > 0) { [userIDList addObject:info.userID]; } } return userIDList; } + (NSString *)getShowName:(V2TIMMessage *)message { NSString *showName = message.sender; if (message.nameCard.length > 0) { showName = message.nameCard; } else if (message.friendRemark.length > 0) { showName = message.friendRemark; } else if (message.nickName.length > 0) { showName = message.nickName; } return showName; } + (NSString *)getUserName:(V2TIMGroupTipsElem *)tips with:(NSString *)userId { NSString *str = @""; for (V2TIMGroupMemberInfo *info in tips.memberList) { if ([info.userID isEqualToString:userId]) { if (info.nameCard.length > 0) { str = info.nameCard; } else if (info.friendRemark.length > 0) { str = info.friendRemark; } else if (info.nickName.length > 0) { str = info.nickName; } else { str = userId; } break; } } return str; } @end