// // TUIMessageSearchDataProvider.m // TXIMSDK_TUIKit_iOS // // Created by kayev on 2021/7/8. // Copyright © 2023 Tencent. All rights reserved. // #import "TUIMessageSearchDataProvider.h" #import "TUIMessageBaseDataProvider+ProtectedAPI.h" #import "TUIChatMediaSendingManager.h" typedef void (^LoadSearchMsgSucceedBlock)(BOOL isOlderNoMoreMsg, BOOL isNewerNoMoreMsg, NSArray *newMsgs); typedef void (^LoadMsgSucceedBlock)(BOOL isOlderNoMoreMsg, BOOL isNewerNoMoreMsg, BOOL isFirstLoad, NSArray *newUIMsgs); @interface TUIMessageSearchDataProvider () @property(nonatomic, copy) LoadSearchMsgSucceedBlock loadSearchMsgSucceedBlock; @property(nonatomic, copy) LoadMsgSucceedBlock loadMsgSucceedBlock; @end @implementation TUIMessageSearchDataProvider - (instancetype)init { self = [super init]; if (self) { _isOlderNoMoreMsg = NO; _isNewerNoMoreMsg = NO; } return self; } - (void)loadMessageWithSearchMsg:(V2TIMMessage *)searchMsg SearchMsgSeq:(uint64_t)searchSeq ConversationInfo:(TUIChatConversationModel *)conversation SucceedBlock:(void (^)(BOOL isOlderNoMoreMsg, BOOL isNewerNoMoreMsg, NSArray *newMsgs))succeedBlock FailBlock:(V2TIMFail)failBlock { if (self.isLoadingData) { failBlock(ERR_SUCC, @"refreshing"); return; } self.isLoadingData = YES; self.isOlderNoMoreMsg = NO; self.isNewerNoMoreMsg = NO; self.loadSearchMsgSucceedBlock = succeedBlock; dispatch_group_t group = dispatch_group_create(); __block NSArray *olders = @[]; __block NSArray *newers = @[]; __block BOOL isOldLoadFail = NO; __block BOOL isNewLoadFail = NO; __block int failCode = 0; __block NSString *failDesc = nil; /** * Load the oldest pageCount messages starting from locating message */ { dispatch_group_enter(group); V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init]; option.getType = V2TIM_GET_CLOUD_OLDER_MSG; option.count = self.pageCount; option.groupID = conversation.groupID; option.userID = conversation.userID; if (searchMsg) { option.lastMsg = searchMsg; } else { option.lastMsgSeq = searchSeq; } [V2TIMManager.sharedInstance getHistoryMessageList:option succ:^(NSArray *msgs) { msgs = msgs.reverseObjectEnumerator.allObjects; olders = msgs ?: @[]; if (olders.count < self.pageCount) { self.isOlderNoMoreMsg = YES; } dispatch_group_leave(group); } fail:^(int code, NSString *desc) { isOldLoadFail = YES; failCode = code; failDesc = desc; dispatch_group_leave(group); }]; } /** * Load the latest pageCount messages starting from the locating message */ { dispatch_group_enter(group); V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init]; option.getType = V2TIM_GET_CLOUD_NEWER_MSG; option.count = self.pageCount; option.groupID = conversation.groupID; option.userID = conversation.userID; if (searchMsg) { option.lastMsg = searchMsg; } else { option.lastMsgSeq = searchSeq; } [V2TIMManager.sharedInstance getHistoryMessageList:option succ:^(NSArray *msgs) { newers = msgs ?: @[]; if (newers.count < self.pageCount) { self.isNewerNoMoreMsg = YES; } dispatch_group_leave(group); } fail:^(int code, NSString *desc) { isNewLoadFail = YES; failCode = code; failDesc = desc; dispatch_group_leave(group); }]; } @weakify(self); dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ @strongify(self); self.isLoadingData = NO; if (isOldLoadFail || isNewLoadFail) { dispatch_async(dispatch_get_main_queue(), ^{ failBlock(failCode, failDesc); }); } self.isFirstLoad = NO; NSMutableArray *results = [NSMutableArray array]; [results addObjectsFromArray:olders]; if (searchMsg) { /** * Pulling messages through the msg will not return the msg object itself, here you need to actively add the msg to the results list */ [results addObject:searchMsg]; } else { /** * Pulling messages through the msg seq, pulling old messages and new messages will return the msg object itself, here you need to deduplicate the msg * object in results */ [results removeLastObject]; } [results addObjectsFromArray:newers]; self.msgForOlderGet = results.firstObject; self.msgForNewerGet = results.lastObject; @weakify(self); dispatch_async(dispatch_get_main_queue(), ^{ @strongify(self); [self.heightCache_ removeAllObjects]; [self.uiMsgs_ removeAllObjects]; NSArray *msgs = results.reverseObjectEnumerator.allObjects; NSMutableArray *uiMsgs = [self transUIMsgFromIMMsg:msgs]; if (uiMsgs.count == 0) { return; } [self getGroupMessageReceipts:msgs uiMsgs:uiMsgs succ:^{ [self preProcessMessage:uiMsgs]; } fail:^{ [self preProcessMessage:uiMsgs]; }]; }); }); } - (void)loadMessageWithIsRequestOlderMsg:(BOOL)orderType ConversationInfo:(TUIChatConversationModel *)conversation SucceedBlock:(void (^)(BOOL isOlderNoMoreMsg, BOOL isNewerNoMoreMsg, BOOL isFirstLoad, NSArray *newUIMsgs))succeedBlock FailBlock:(V2TIMFail)failBlock { self.isLoadingData = YES; self.loadMsgSucceedBlock = succeedBlock; int requestCount = self.pageCount; V2TIMMessageListGetOption *option = [[V2TIMMessageListGetOption alloc] init]; option.userID = conversation.userID; option.groupID = conversation.groupID; option.getType = orderType ? V2TIM_GET_CLOUD_OLDER_MSG : V2TIM_GET_CLOUD_NEWER_MSG; option.count = requestCount; option.lastMsg = orderType ? self.msgForOlderGet : self.msgForNewerGet; @weakify(self); [V2TIMManager.sharedInstance getHistoryMessageList:option succ:^(NSArray *msgs) { @strongify(self); if (!orderType) { msgs = msgs.reverseObjectEnumerator.allObjects; } /** * Update the lastMsg flag * -- The current pull operation is to pull from the latest time point to the past */ BOOL isLastest = (self.msgForNewerGet == nil) && (self.msgForOlderGet == nil) && orderType; if (msgs.count != 0) { if (orderType) { self.msgForOlderGet = msgs.lastObject; if (self.msgForNewerGet == nil) { self.msgForNewerGet = msgs.firstObject; } } else { if (self.msgForOlderGet == nil) { self.msgForOlderGet = msgs.lastObject; } self.msgForNewerGet = msgs.firstObject; } } /** * Update no data flag */ if (msgs.count < requestCount) { if (orderType) { self.isOlderNoMoreMsg = YES; } else { self.isNewerNoMoreMsg = YES; } } if (isLastest) { /** * The current pull operation is to pull from the latest time point to the past */ self.isNewerNoMoreMsg = YES; } NSMutableArray *uiMsgs = [self transUIMsgFromIMMsg:msgs]; if (uiMsgs.count == 0) { if (self.loadMsgSucceedBlock) { self.loadMsgSucceedBlock(self.isOlderNoMoreMsg, self.isNewerNoMoreMsg, self.isFirstLoad, uiMsgs); } return; } //add media placeholder celldata if (self.conversationModel.conversationID.length > 0) { NSMutableArray * tasks = [TUIChatMediaSendingManager.sharedInstance findPlaceHolderListByConversationID:self.conversationModel.conversationID]; for (TUIChatMediaTask * task in tasks) { if (task.placeHolderCellData) { [uiMsgs addObject:task.placeHolderCellData]; } } } [self getGroupMessageReceipts:msgs uiMsgs:uiMsgs succ:^{ [self preProcessMessage:uiMsgs orderType:orderType]; } fail:^{ [self preProcessMessage:uiMsgs orderType:orderType]; }]; } fail:^(int code, NSString *desc) { self.isLoadingData = NO; }]; } - (void)getGroupMessageReceipts:(NSArray *)msgs uiMsgs:(NSArray *)uiMsgs succ:(void (^)(void))succBlock fail:(void (^)(void))failBlock { [[V2TIMManager sharedInstance] getMessageReadReceipts:msgs succ:^(NSArray *receiptList) { NSLog(@"getGroupMessageReceipts succeed, receiptList: %@", receiptList); NSMutableDictionary *dict = [NSMutableDictionary dictionary]; for (V2TIMMessageReceipt *receipt in receiptList) { [dict setObject:receipt forKey:receipt.msgID]; } for (TUIMessageCellData *data in uiMsgs) { V2TIMMessageReceipt *receipt = dict[data.msgID]; data.messageReceipt = receipt; } if (succBlock) { succBlock(); } } fail:^(int code, NSString *desc) { NSLog(@"getGroupMessageReceipts failed, code: %d, desc: %@", code, desc); if (failBlock) { failBlock(); } }]; } - (void)preProcessMessage:(NSArray *)uiMsgs { @weakify(self); [self preProcessMessage:uiMsgs callback:^{ @strongify(self); [self addUIMsgs:uiMsgs]; self.loadSearchMsgSucceedBlock(self.isOlderNoMoreMsg, self.isNewerNoMoreMsg, self.uiMsgs_); }]; } - (void)preProcessMessage:(NSArray *)uiMsgs orderType:(BOOL)orderType { @weakify(self); [self preProcessMessage:uiMsgs callback:^{ @strongify(self); if (orderType) { NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, uiMsgs.count)]; [self insertUIMsgs:uiMsgs atIndexes:indexSet]; } else { [self addUIMsgs:uiMsgs]; } if (self.loadMsgSucceedBlock) { self.loadMsgSucceedBlock(self.isOlderNoMoreMsg, self.isNewerNoMoreMsg, self.isFirstLoad, uiMsgs); } self.isLoadingData = NO; self.isFirstLoad = NO; }]; } - (void)removeAllSearchData { [self.uiMsgs_ removeAllObjects]; self.isNewerNoMoreMsg = NO; self.isOlderNoMoreMsg = NO; self.isFirstLoad = YES; self.msgForNewerGet = nil; self.msgForOlderGet = nil; self.loadSearchMsgSucceedBlock = nil; } - (void)findMessages:(NSArray *)msgIDs callback:(void (^)(BOOL success, NSString *desc, NSArray *messages))callback { [V2TIMManager.sharedInstance findMessages:msgIDs succ:^(NSArray *msgs) { if (callback) { callback(YES, @"", msgs); } } fail:^(int code, NSString *desc) { if (callback) { callback(NO, desc, nil); } }]; } #pragma mark - Override - (void)onRecvNewMessage:(V2TIMMessage *)msg { if (self.isNewerNoMoreMsg == NO) { /** * If the current message list has not pulled the last message, ignore the new message; * If it is processed at this time, it will cause new messages to be added to the history list, resulting in the problem of position confusion. */ return; } if (self.dataSource.isDataSourceConsistent == NO ) { self.isNewerNoMoreMsg = NO; return; } [super onRecvNewMessage:msg]; } @end