Files
fanyin-ios/TUIKit/TUIChat/BaseDataProvider/Base/TUIMessageBaseDataProvider.m
2025-08-12 14:27:12 +08:00

1603 lines
65 KiB
Objective-C

// Created by Tencent on 2023/06/09.
// Copyright © 2023 Tencent. All rights reserved.
#import <AVFoundation/AVFoundation.h>
#import <TIMCommon/NSString+TUIEmoji.h>
#import <TIMCommon/TIMDefine.h>
#import <TIMCommon/TUIMessageCell.h>
#import <TIMCommon/TUISystemMessageCellData.h>
#import <TIMCommon/TUIRelationUserModel.h>
#import <TUICore/NSString+TUIUtil.h>
#import <TUICore/TUICore.h>
#import <TUICore/TUILogin.h>
#import <TUICore/TUITool.h>
#import <TUICore/OfflinePushExtInfo.h>
#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 () <V2TIMAdvancedMsgListener, V2TIMGroupListener,TUIMessageProgressManagerDelegate>
@property(nonatomic, strong) TUIChatConversationModel *conversationModel;
@property(nonatomic, strong) NSMutableArray<TUIMessageCellData *> *uiMsgs_;
@property(nonatomic, strong) NSMutableSet<NSString *> *sentReadGroupMsgSet;
@property(nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *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<V2TIMMessage *> *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<NSString *> *)sentReadGroupMsgSet {
if (_sentReadGroupMsgSet == nil) {
_sentReadGroupMsgSet = [NSMutableSet setWithCapacity:10];
}
return _sentReadGroupMsgSet;
}
- (NSMutableArray<TUIMessageCellData *> *)uiMsgs_ {
if (_uiMsgs_ == nil) {
_uiMsgs_ = [NSMutableArray array];
}
return _uiMsgs_;
}
- (NSMutableDictionary<NSString *, NSNumber *> *)heightCache_ {
if (_heightCache_ == nil) {
_heightCache_ = [NSMutableDictionary dictionary];
}
return _heightCache_;
}
- (NSArray<TUIMessageCellData *> *)uiMsgs {
return _uiMsgs_;
}
- (NSDictionary<NSString *, NSNumber *> *)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<V2TIMMessageReceipt *> *)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<TUIMessageCellData *> *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<V2TIMMessage *> *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<V2TIMMessage *> *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<V2TIMMessage *> *)msgs
SucceedBlock:(void (^)(BOOL isFirstLoad, BOOL isNoMoreMsg, NSArray<TUIMessageCellData *> *newMsgs))succeedBlock {
NSMutableArray<TUIMessageCellData *> *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<TUIMessageCellData *> *)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<TUIMessageCellData *> *)uiMsgs callback:(void (^)(void))callback {
if (callback) {
callback();
}
}
- (void)processMessageAsync:(NSArray<TUIMessageCellData *> *)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<TUIMessageCellData *> *)uiMsgs {
if (uiMsgs.count == 0) {
return;
}
// fetch react
[[NSNotificationCenter defaultCenter] postNotificationName:@"TUIKitFetchReactNotification" object:uiMsgs];
}
- (void)requestForAdditionalUserInfo:(NSArray<TUIMessageCellData *> *)uiMsgs callback:(void (^)(void))callback {
NSArray *userIDList = [self getUserIDListForAdditionalUserInfo:uiMsgs];
if (userIDList.count == 0) {
if (callback) {
callback();
}
return;
}
[self requestForUserDetailsInfo:userIDList
callback:^(NSDictionary<NSString *, TUIRelationUserModel *> *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<NSString *, TUIRelationUserModel *> *))callback {
if (callback == nil) {
return;
}
NSMutableDictionary<NSString *, TUIRelationUserModel *> *result = [NSMutableDictionary dictionary];
if (self.conversationModel.groupID.length > 0) {
[[V2TIMManager sharedInstance] getGroupMembersInfo:self.conversationModel.groupID
memberList:userIDList
succ:^(NSArray<V2TIMGroupMemberFullInfo *> *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<V2TIMFriendInfoResult *> *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<NSString *> *)getUserIDListForAdditionalUserInfo:(NSArray<TUIMessageCellData *> *)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<TUIMessageCellData *> *)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<TUIMessageCellData *> *)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<TUIMessageCellData *> *)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<TUIMessageCellData *> *)uiMsgs {
[self.uiMsgs_ addObjectsFromArray:uiMsgs];
if (self.mergeAdjacentMsgsFromTheSameSender) {
for (TUIMessageCellData *cellData in uiMsgs) {
[self.class updateUIMsgStatus:cellData uiMsgs:self.uiMsgs_];
}
}
}
- (void)removeUIMsgList:(NSArray<TUIMessageCellData *> *)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<V2TIMMessage *> *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<V2TIMGroupInfoResult *> *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<V2TIMGroupMemberFullInfo *> *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<V2TIMGroupChangeInfo *> *)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 <V2TIMGroupMemberInfo *> *)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 <V2TIMGroupMemberInfo *> *)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<V2TIMMessage *> *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<NSString *> *)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<V2TIMMessage *> *)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<V2TIMGroupMemberInfo *> *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<NSString *> *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<NSString *> *)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<V2TIMGroupMemberInfo *> *)infoList {
NSMutableArray<NSString *> *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<V2TIMGroupMemberInfo *> *)infoList {
NSMutableArray<NSString *> *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