Files
featherVoice/TUIKit/TUIConversation/BaseDataProvider/TUIConversationListBaseDataProvider.m
2025-08-08 10:49:36 +08:00

1240 lines
49 KiB
Objective-C

//
// TUIConversationListBaseDataProvider.m
// TUIConversation
//
// Created by harvy on 2022/7/14.
// Copyright © 2023 Tencent. All rights reserved.
//
#import "TUIConversationListBaseDataProvider.h"
#import <ImSDK_Plus/ImSDK_Plus.h>
#import <TIMCommon/TIMDefine.h>
#import <TUICore/TUICore.h>
#import <TUICore/TUILogin.h>
#import "TUIConversationCellData.h"
#define kPageSize 100
#define kLog(...) NSLog(__VA_ARGS__)
@interface TUIConversationListBaseDataProvider () <V2TIMConversationListener,
V2TIMGroupListener,
V2TIMSDKListener,
V2TIMAdvancedMsgListener,
TUINotificationProtocol>
@property(nonatomic, strong) NSMutableArray<TUIConversationCellData *> *conversationList;
@property(nonatomic, assign, getter=isLastPage) BOOL lastPage;
/**
* Locally inserted conversation collapse list
*/
@property(nonatomic, strong) TUIConversationCellData *conversationFoldListData;
/**
* Deleting conversation list
*/
@property(nonatomic, strong) NSMutableArray *deletingConversationList;
@end
@implementation TUIConversationListBaseDataProvider
- (instancetype)init {
if (self = [super init]) {
self.pageIndex = 0;
self.pageSize = kPageSize;
self.lastPage = NO;
self.filter = [[V2TIMConversationListFilter alloc] init];
self.deletingConversationList = [NSMutableArray array];
[[V2TIMManager sharedInstance] addConversationListener:self];
[[V2TIMManager sharedInstance] addGroupListener:self];
[[V2TIMManager sharedInstance] addIMSDKListener:self];
[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];
[TUICore registerEvent:TUICore_TUIConversationNotify subKey:TUICore_TUIConversationNotify_RemoveConversationSubKey object:self];
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(onLoginSucc) name:TUILoginSuccessNotification object:nil];
}
return self;
}
- (void)loadNexPageConversations {
if (self.isLastPage) {
return;
}
@weakify(self);
[V2TIMManager.sharedInstance getConversationListByFilter:self.filter
nextSeq:self.pageIndex
count:(int)self.pageSize
succ:^(NSArray<V2TIMConversation *> *list, uint64_t nextSeq, BOOL isFinished) {
@strongify(self);
self.pageIndex = nextSeq;
self.lastPage = isFinished;
[self preprocess:list];
}
fail:^(int code, NSString *desc) {
@strongify(self);
self.lastPage = YES;
kLog(@"[TUIConversation] %s, code:%d, desc:%@", __func__, code, desc);
}];
}
- (void)addConversationList:(NSArray<TUIConversationCellData *> *)conversationList {
if (!NSThread.isMainThread) {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf addConversationList:conversationList];
});
return;
}
[self handleInsertConversationList:conversationList];
}
- (void)removeConversation:(TUIConversationCellData *)conversation {
if (!NSThread.isMainThread) {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf removeConversation:conversation];
});
return;
}
[self handleRemoveConversation:conversation];
}
- (void)clearHistoryMessage:(TUIConversationCellData *)conversation {
if (!NSThread.isMainThread) {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf clearHistoryMessage:conversation];
});
return;
}
if (conversation.groupID) {
[self handleClearGroupHistoryMessage:conversation.groupID];
} else if (conversation.userID) {
[self handleClearC2CHistoryMessage:conversation.userID];
}
}
- (void)pinConversation:(TUIConversationCellData *)conversation pin:(BOOL)pin {
if (!NSThread.isMainThread) {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf pinConversation:conversation pin:pin];
});
return;
}
[self handlePinConversation:conversation pin:pin];
}
- (void)hideConversation:(TUIConversationCellData *)conversation {
if (!NSThread.isMainThread) {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf hideConversation:conversation];
});
return;
}
[self handleHideConversation:conversation];
}
#pragma mark - Data process
- (void)preprocess:(NSArray<V2TIMConversation *> *)v2Convs {
if (!NSThread.isMainThread) {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf preprocess:v2Convs];
});
return;
}
NSMutableDictionary<NSString *, NSNumber *> *conversationMap = [NSMutableDictionary dictionary];
for (TUIConversationCellData *item in self.conversationList) {
if (item.conversationID) {
[conversationMap setObject:@([self.conversationList indexOfObject:item]) forKey:item.conversationID];
}
}
NSMutableArray *duplicateDataList = [NSMutableArray array];
NSMutableArray *addedDataList = [NSMutableArray array];
NSMutableArray *markHideDataList = [NSMutableArray array];
NSMutableArray *markFoldDataList = [NSMutableArray array];
for (V2TIMConversation *conv in v2Convs) {
if ([self filteConversation:conv]) {
continue;
}
TUIConversationCellData *cellData = [self cellDataForConversation:conv];
if ([self.markHideMap objectForKey:cellData.conversationID]) {
if (![TUIConversationCellData isMarkedByHideType:conv.markList]) {
[self.markHideMap removeObjectForKey:cellData.conversationID];
}
} else {
if ([TUIConversationCellData isMarkedByHideType:conv.markList]) {
[self.markHideMap setObject:cellData forKey:cellData.conversationID];
}
}
if ([self.markFoldMap objectForKey:cellData.conversationID]) {
if (![TUIConversationCellData isMarkedByFoldType:conv.markList]) {
[self.markFoldMap removeObjectForKey:cellData.conversationID];
}
} else {
if ([TUIConversationCellData isMarkedByFoldType:conv.markList]) {
[self.markFoldMap setObject:cellData forKey:cellData.conversationID];
}
}
if ([TUIConversationCellData isMarkedByHideType:conv.markList] || [TUIConversationCellData isMarkedByFoldType:conv.markList]) {
if ([TUIConversationCellData isMarkedByHideType:conv.markList]) {
[markHideDataList addObject:cellData];
}
if ([TUIConversationCellData isMarkedByFoldType:conv.markList]) {
[markFoldDataList addObject:cellData];
}
continue;
}
if ([conversationMap objectForKey:cellData.conversationID]) {
[duplicateDataList addObject:cellData];
} else {
[addedDataList addObject:cellData];
}
if ([self.markUnreadMap objectForKey:cellData.conversationID]) {
//
// This is an operation to cancel the unread mark operation or to be cleaned away by a new message from someone else
if (![TUIConversationCellData isMarkedByUnReadType:conv.markList]) {
[self.markUnreadMap removeObjectForKey:cellData.conversationID];
}
} else {
if ([TUIConversationCellData isMarkedByUnReadType:conv.markList]) {
[self.markUnreadMap setObject:cellData forKey:cellData.conversationID];
}
}
}
if (markFoldDataList.count) {
__block TUIConversationCellData *cellRecent = nil;
[markFoldDataList enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
TUIConversationCellData *cellData = (TUIConversationCellData *)obj;
if (!cellData.isMarkAsHide) {
cellRecent = cellData;
*stop = YES;
}
}];
if (IS_NOT_EMPTY_NSSTRING([cellRecent.foldSubTitle string])) {
if (cellRecent.orderKey > self.conversationFoldListData.orderKey) {
if (self.conversationFoldListData.orderKey == 0) {
if ([self.class getConversationFoldListSettings_FoldItemIsUnread]) {
self.conversationFoldListData.isMarkAsUnread = YES;
}
} else {
self.conversationFoldListData.isMarkAsUnread = YES;
[self.class cacheConversationFoldListSettings_FoldItemIsUnread:YES];
[self.class cacheConversationFoldListSettings_HideFoldItem:NO];
}
}
self.conversationFoldListData.subTitle = cellRecent.foldSubTitle;
self.conversationFoldListData.orderKey = cellRecent.orderKey;
}
if (![self.class getConversationFoldListSettings_HideFoldItem]) {
if ([conversationMap objectForKey:self.conversationFoldListData.conversationID]) {
[duplicateDataList addObject:self.conversationFoldListData];
} else if (0 == self.filter.conversationGroup.length) {
[addedDataList addObject:self.conversationFoldListData];
}
}
self.conversationFoldListData.isLocalConversationFoldList = YES;
} else {
[self updateFoldGroupNameWhileKickOffOrDismissed];
}
if (duplicateDataList.count) {
[self sortDataList:duplicateDataList];
[self handleUpdateConversationList:duplicateDataList positions:conversationMap];
}
if (addedDataList.count) {
[self sortDataList:addedDataList];
[self handleInsertConversationList:addedDataList];
}
[self updateMardHide:markHideDataList];
[self updateMarkUnreadCount];
[self updateMarkFold:markFoldDataList];
[self asnycGetLastMessageDisplay:duplicateDataList addedDataList:addedDataList];
}
- (void)updateMardHide:(NSMutableArray *) markHideDataList {
if (markHideDataList.count) {
[self sortDataList:markHideDataList];
NSMutableArray *pRemoveCellUIList = [NSMutableArray array];
NSMutableDictionary<NSString *, TUIConversationCellData *> *pMarkHideDataMap = [NSMutableDictionary dictionary];
for (TUIConversationCellData *item in markHideDataList) {
if (item.conversationID) {
[pRemoveCellUIList addObject:item];
[pMarkHideDataMap setObject:item forKey:item.conversationID];
}
}
for (TUIConversationCellData *item in self.conversationList) {
if ([pMarkHideDataMap objectForKey:item.conversationID]) {
[pRemoveCellUIList addObject:item];
}
}
for (TUIConversationCellData *item in pRemoveCellUIList) {
[self handleHideConversation:item];
}
}
}
- (void)updateMarkUnreadCount {
__block NSInteger markUnreadCount = 0;
[self.markUnreadMap enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, TUIConversationCellData *_Nonnull obj, BOOL *_Nonnull stop) {
if (!obj.isNotDisturb) {
markUnreadCount++;
}
}];
__block NSInteger markHideUnreadCount = 0;
[self.markHideMap enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, TUIConversationCellData *_Nonnull obj, BOOL *_Nonnull stop) {
if (obj && [obj isKindOfClass:TUIConversationCellData.class]) {
if (!obj.isNotDisturb) {
if (obj.isMarkAsUnread) {
markHideUnreadCount = markHideUnreadCount + 1;
} else {
markHideUnreadCount = markHideUnreadCount + obj.unreadCount;
}
}
}
}];
[[NSNotificationCenter defaultCenter]
postNotificationName:TUIKitNotification_onConversationMarkUnreadCountChanged
object:nil
userInfo:@{
TUIKitNotification_onConversationMarkUnreadCountChanged_DataProvider : self,
TUIKitNotification_onConversationMarkUnreadCountChanged_MarkUnreadCount : [NSNumber numberWithInteger:markUnreadCount],
TUIKitNotification_onConversationMarkUnreadCountChanged_MarkHideUnreadCount : [NSNumber numberWithInteger:markHideUnreadCount],
TUIKitNotification_onConversationMarkUnreadCountChanged_MarkUnreadMap : self.markUnreadMap,
}];
}
- (void)updateMarkFold:(NSMutableArray *) markFoldDataList {
if (markFoldDataList.count) {
[self sortDataList:markFoldDataList];
NSMutableArray *pRemoveCellUIList = [NSMutableArray array];
NSMutableDictionary<NSString *, TUIConversationCellData *> *pMarkFoldDataMap = [NSMutableDictionary dictionary];
for (TUIConversationCellData *item in markFoldDataList) {
if (item.conversationID) {
[pRemoveCellUIList addObject:item];
[pMarkFoldDataMap setObject:item forKey:item.conversationID];
}
}
for (TUIConversationCellData *item in self.conversationList) {
if ([pMarkFoldDataMap objectForKey:item.conversationID]) {
[pRemoveCellUIList addObject:item];
}
}
// If a collapsed session appears in the home page List, it needs to be hidden. Note that the history cannot be deleted.
for (TUIConversationCellData *item in pRemoveCellUIList) {
[self handleHideConversation:item];
}
}
}
- (void)asnycGetLastMessageDisplay:(NSArray<TUIConversationCellData *> *)duplicateDataList addedDataList:(NSArray<TUIConversationCellData *> *)addedDataList {
// override by subclass
}
- (void)handleInsertConversationList:(NSArray<TUIConversationCellData *> *)conversationList {
[self.conversationList addObjectsFromArray:conversationList];
[self sortDataList:self.conversationList];
NSMutableArray *indexPaths = [NSMutableArray array];
for (TUIConversationCellData *item in conversationList) {
NSInteger index = [self.conversationList indexOfObject:item];
[indexPaths addObject:[NSIndexPath indexPathForRow:index inSection:0]];
}
if (self.delegate && [self.delegate respondsToSelector:@selector(insertConversationsAtIndexPaths:)]) {
[self.delegate insertConversationsAtIndexPaths:indexPaths];
}
[self updateOnlineStatus:conversationList];
}
- (void)handleUpdateConversationList:(NSArray<TUIConversationCellData *> *)conversationList
positions:(NSMutableDictionary<NSString *, NSNumber *> *)positionMaps {
if (0 == conversationList.count) {
return;
}
for (TUIConversationCellData *item in conversationList) {
if (item.isLocalConversationFoldList){
continue;
}
NSNumber *position = [positionMaps objectForKey:item.conversationID];
NSAssert((position && [position isKindOfClass:NSNumber.class]), @"serius error, the self.conversationList maybe changed on the other thread");
if (position && position.integerValue < self.conversationList.count) {
TUIConversationCellData *cellData = self.conversationList[position.integerValue];
item.onlineStatus = cellData.onlineStatus;
[self.conversationList replaceObjectAtIndex:[position integerValue] withObject:item];
}
}
[self sortDataList:self.conversationList];
NSInteger minIndex = self.conversationList.count - 1;
NSInteger maxIndex = 0;
NSMutableDictionary<NSString *, TUIConversationCellData *> *conversationMap = [NSMutableDictionary dictionary];
for (TUIConversationCellData *item in self.conversationList) {
if (item.conversationID) {
[conversationMap setObject:item forKey:item.conversationID];
}
}
for (TUIConversationCellData *cellData in conversationList) {
TUIConversationCellData *item = [conversationMap objectForKey:cellData.conversationID];
if (item) {
NSInteger previous = [[positionMaps objectForKey:item.conversationID] integerValue];
NSInteger current = [self.conversationList indexOfObject:item];
minIndex = minIndex < MIN(previous, current) ? minIndex : MIN(previous, current);
maxIndex = maxIndex > MAX(previous, current) ? maxIndex : MAX(previous, current);
}
}
if (minIndex > maxIndex) {
return;
}
NSMutableArray *indexPaths = [NSMutableArray array];
for (NSInteger index = minIndex; index < maxIndex + 1; index++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:index inSection:0]];
}
if (self.delegate && [self.delegate respondsToSelector:@selector(reloadConversationsAtIndexPaths:)]) {
[self.delegate reloadConversationsAtIndexPaths:indexPaths];
}
}
- (void)handleRemoveConversation:(TUIConversationCellData *)conversation {
NSInteger index = [self.conversationList indexOfObject:conversation];
if (index != NSNotFound) {
[self.conversationList removeObject:conversation];
if (self.delegate && [self.delegate respondsToSelector:@selector(deleteConversationAtIndexPaths:)]) {
[self.delegate deleteConversationAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ]];
}
[self.deletingConversationList addObject:conversation.conversationID];
@weakify(self)
[[V2TIMManager sharedInstance] deleteConversation:conversation.conversationID succ:^{
@strongify(self)
[self.deletingConversationList removeObject:conversation.conversationID];
if ([self.markUnreadMap objectForKey:conversation.conversationID]) {
[self.markUnreadMap removeObjectForKey:conversation.conversationID];
}
[self updateMarkUnreadCount];
} fail:^(int code, NSString *desc) {
@strongify(self)
NSLog(@"deleteConversation failed, conversationID:%@ code:%d desc:%@", conversation.conversationID, code, desc);
[self.deletingConversationList removeObject:conversation.conversationID];
}];
}
}
- (void)handleHideConversation:(TUIConversationCellData *)conversation {
NSInteger index = [self.conversationList indexOfObject:conversation];
if (index != NSNotFound) {
[self.conversationList removeObject:conversation];
if (self.delegate && [self.delegate respondsToSelector:@selector(deleteConversationAtIndexPaths:)]) {
[self.delegate deleteConversationAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ]];
}
}
}
- (void)sortDataList:(NSMutableArray<TUIConversationCellData *> *)dataList {
[dataList sortUsingComparator:^NSComparisonResult(TUIConversationCellData *obj1, TUIConversationCellData *obj2) {
return obj1.orderKey < obj2.orderKey;
}];
}
- (TUIConversationCellData *)cellDataOfGroupID:(NSString *)groupID {
TUIConversationCellData *cellData = nil;
NSString *conversationID = [NSString stringWithFormat:@"group_%@", groupID];
for (TUIConversationCellData *item in self.conversationList) {
if ([item.conversationID isEqualToString:conversationID]) {
cellData = item;
break;
}
}
return cellData;
}
- (void)dealFoldcellDataOfGroupID:(NSString *)groupID {
TUIConversationCellData *cellData = nil;
NSString *conversationID = [NSString stringWithFormat:@"group_%@", groupID];
cellData = [self.markFoldMap objectForKey:conversationID];
if (cellData) {
@weakify(self);
[[V2TIMManager sharedInstance] deleteConversation:cellData.conversationID
succ:^{
@strongify(self);
[self.markFoldMap removeObjectForKey:conversationID];
[self updateFoldGroupNameWhileKickOffOrDismissed];
}
fail:nil];
}
}
- (void)updateFoldGroupNameWhileKickOffOrDismissed {
__block TUIConversationCellData *cellRecent = nil;
[self.markFoldMap enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, TUIConversationCellData *_Nonnull obj, BOOL *_Nonnull stop) {
TUIConversationCellData *cellData = (TUIConversationCellData *)obj;
if (!cellData.isMarkAsHide) {
if (!cellRecent) {
cellRecent = cellData;
} else {
if (cellData.orderKey > cellRecent.orderKey) {
cellRecent = cellData;
}
}
}
}];
if (cellRecent) {
if (IS_NOT_EMPTY_NSSTRING([cellRecent.foldSubTitle string])) {
self.conversationFoldListData.subTitle = cellRecent.foldSubTitle;
}
NSMutableDictionary<NSString *, NSNumber *> *conversationMap = [NSMutableDictionary dictionary];
for (TUIConversationCellData *item in self.conversationList) {
if (item.conversationID) {
[conversationMap setObject:@([self.conversationList indexOfObject:item]) forKey:item.conversationID];
}
}
[self handleUpdateConversationList:@[ self.conversationFoldListData ] positions:conversationMap];
} else {
if (_conversationFoldListData) {
[self hideConversation:self.conversationFoldListData];
}
}
}
#pragma mark - TUICore
- (void)onNotifyEvent:(NSString *)key subKey:(NSString *)subKey object:(nullable id)anObject param:(nullable NSDictionary *)param {
if ([key isEqualToString:TUICore_TUIConversationNotify] && [subKey isEqualToString:TUICore_TUIConversationNotify_RemoveConversationSubKey]) {
NSString *conversationID = param[TUICore_TUIConversationNotify_RemoveConversationSubKey_ConversationID];
if (IS_NOT_EMPTY_NSSTRING(conversationID)) {
TUIConversationCellData *removeConversation = nil;
for (TUIConversationCellData *item in self.conversationList) {
if ([conversationID isEqualToString:item.conversationID]) {
removeConversation = item;
break;
}
}
if (removeConversation) {
[self removeConversation:removeConversation];
} else {
@weakify(self);
[[V2TIMManager sharedInstance] deleteConversation:conversationID
succ:^{
@strongify(self);
[self updateMarkUnreadCount];
}
fail:nil];
}
}
}
}
#pragma mark - User Status
- (void)updateOnlineStatus:(NSArray<TUIConversationCellData *> *)conversationList {
if (conversationList.count == 0) {
return;
}
if (!TUILogin.isUserLogined) {
return;
}
if (!NSThread.isMainThread) {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf updateOnlineStatus:conversationList];
});
return;
}
// reset
NSMutableArray *userIDList = [NSMutableArray array];
NSMutableDictionary *positionMap = [NSMutableDictionary dictionary];
for (TUIConversationCellData *item in conversationList) {
if (item.onlineStatus == TUIConversationOnlineStatusOnline) {
item.onlineStatus = TUIConversationOnlineStatusOffline;
}
if (item.userID.length && item.groupID.length == 0) {
[userIDList addObject:item.userID];
}
if (item.conversationID) {
[positionMap setObject:@([self.conversationList indexOfObject:item]) forKey:item.conversationID];
}
}
[self handleUpdateConversationList:conversationList positions:positionMap];
// fetch
[self asyncGetOnlineStatus:userIDList];
}
- (void)asyncGetOnlineStatus:(NSArray *)userIDList {
if (userIDList.count == 0) {
return;
}
if (NSThread.isMainThread) {
@weakify(self);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@strongify(self);
[self asyncGetOnlineStatus:userIDList];
});
return;
}
// get
@weakify(self);
[V2TIMManager.sharedInstance getUserStatus:userIDList
succ:^(NSArray<V2TIMUserStatus *> *result) {
@strongify(self);
[self handleOnlineStatus:result];
}
fail:^(int code, NSString *desc) {
#if DEBUG
if (code == ERR_SDK_INTERFACE_NOT_SUPPORT && TUIConfig.defaultConfig.displayOnlineStatusIcon) {
[TUITool makeToast:desc];
}
#endif
}];
// subscribe for the users who was deleted from friend list
[V2TIMManager.sharedInstance subscribeUserStatus:userIDList
succ:^{
}
fail:^(int code, NSString *desc){
}];
}
- (void)handleOnlineStatus:(NSArray<V2TIMUserStatus *> *)userStatusList {
if (!NSThread.isMainThread) {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf handleOnlineStatus:userStatusList];
});
return;
}
NSMutableDictionary *positonMap = [NSMutableDictionary dictionary];
for (TUIConversationCellData *item in self.conversationList) {
NSAssert(item.conversationID, @"the data of self.conversation maybe damaged");
if (item.conversationID && item.userID.length != 0 && item.groupID.length == 0) {
[positonMap setObject:@([self.conversationList indexOfObject:item]) forKey:item.conversationID];
}
}
NSMutableArray *changedConversation = [NSMutableArray array];
for (V2TIMUserStatus *item in userStatusList) {
NSString *conversationID = [NSString stringWithFormat:@"c2c_%@", item.userID];
NSNumber *position = [positonMap objectForKey:conversationID];
if (position == nil) {
continue;
}
TUIConversationCellData *conversation = [self.conversationList objectAtIndex:position.integerValue];
if (![conversation.conversationID isEqualToString:conversationID]) {
continue;
}
switch (item.statusType) {
case V2TIM_USER_STATUS_ONLINE: {
conversation.onlineStatus = TUIConversationOnlineStatusOnline;
} break;
case V2TIM_USER_STATUS_OFFLINE:
case V2TIM_USER_STATUS_UNLOGINED: {
conversation.onlineStatus = TUIConversationOnlineStatusOffline;
} break;
default:
conversation.onlineStatus = TUIConversationOnlineStatusUnknown;
break;
}
[changedConversation addObject:conversation];
}
if (changedConversation.count > 0) {
[self handleUpdateConversationList:changedConversation positions:positonMap];
}
}
#pragma mark - V2TIMConversationListener
- (void)onNewConversation:(NSArray<V2TIMConversation *> *)conversationList {
[self preprocess:conversationList];
}
- (void)onConversationChanged:(NSArray<V2TIMConversation *> *)conversationList {
[self preprocess:conversationList];
}
- (void)onConversationDeleted:(NSArray<NSString *> *)conversationIDList {
NSArray *cacheConversationList = [self.conversationList copy];
for (TUIConversationCellData *item in cacheConversationList) {
if ([conversationIDList containsObject:item.conversationID]) {
NSInteger index = [self.conversationList indexOfObject:item];
if (index != NSNotFound) {
[self.conversationList removeObject:item];
if (self.delegate && [self.delegate respondsToSelector:@selector(deleteConversationAtIndexPaths:)]) {
[self.delegate deleteConversationAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ]];
}
if ([self.markUnreadMap objectForKey:item.conversationID]) {
[self.markUnreadMap removeObjectForKey:item.conversationID];
}
[self updateMarkUnreadCount];
}
}
}
}
#pragma mark - V2TIMGroupListener
- (NSString *)getGroupName:(TUIConversationCellData *)cellData {
NSString *formatString = cellData.groupID;
if (cellData.title.length > 0) {
return cellData.title;
}
else if (cellData.groupID.length > 0) {
return cellData.groupID;
}
return @"";
}
- (void)onGroupDismissed:(NSString *)groupID opUser:(V2TIMGroupMemberInfo *)opUser {
TUIConversationCellData *data = [self cellDataOfGroupID:groupID];
if (data == nil) {
[self dealFoldcellDataOfGroupID:groupID];
return;
}
NSString *groupName = [self getGroupName:data];
[TUITool makeToast:[NSString stringWithFormat:TIMCommonLocalizableString(TUIKitGroupDismssTipsFormat), groupName]];
[self handleRemoveConversation:data];
}
- (void)onGroupRecycled:(NSString *)groupID opUser:(V2TIMGroupMemberInfo *)opUser {
TUIConversationCellData *data = [self cellDataOfGroupID:groupID];
if (data == nil) {
[self dealFoldcellDataOfGroupID:groupID];
return;
}
NSString *groupName = [self getGroupName:data];
[TUITool makeToast:[NSString stringWithFormat:TIMCommonLocalizableString(TUIKitGroupRecycledTipsFormat), groupName]];
[self handleRemoveConversation:data];
}
- (void)onMemberKicked:(NSString *)groupID opUser:(V2TIMGroupMemberInfo *)opUser memberList:(NSArray<V2TIMGroupMemberInfo *> *)memberList {
BOOL kicked = NO;
for (V2TIMGroupMemberInfo *info in memberList) {
if ([info.userID isEqualToString:[TUILogin getUserID]]) {
kicked = YES;
break;
}
}
if (kicked == NO) {
return;
}
TUIConversationCellData *data = [self cellDataOfGroupID:groupID];
if (data == nil) {
[self dealFoldcellDataOfGroupID:groupID];
return;
}
NSString *groupName = [self getGroupName:data];
[TUITool makeToast:[NSString stringWithFormat:TIMCommonLocalizableString(TUIKitGroupKickOffTipsFormat), groupName]];
[self handleRemoveConversation:data];
}
- (void)onQuitFromGroup:(NSString *)groupID {
TUIConversationCellData *data = [self cellDataOfGroupID:groupID];
if (data == nil) {
[self dealFoldcellDataOfGroupID:groupID];
return;
}
NSString *groupName = [self getGroupName:data];
[TUITool makeToast:[NSString stringWithFormat:TIMCommonLocalizableString(TUIKitGroupDropoutTipsFormat), groupName]];
[self handleRemoveConversation:data];
}
- (void)onGroupInfoChanged:(NSString *)groupID changeInfoList:(NSArray<V2TIMGroupChangeInfo *> *)changeInfoList {
TUIConversationCellData *data = [self cellDataOfGroupID:groupID];
if (groupID.length == 0 || data == nil) {
return;
}
__weak typeof(self) weakSelf = self;
NSString *conversationID = [NSString stringWithFormat:@"group_%@", groupID];
[V2TIMManager.sharedInstance getConversation:conversationID
succ:^(V2TIMConversation *conv) {
[weakSelf preprocess:@[ conv ]];
}
fail:^(int code, NSString *desc) {
kLog(@"[TUIConversation] %s, code:%d, desc:%@", __func__, code, desc);
}];
}
#pragma mark - V2TIMSDKListener
- (void)onUserStatusChanged:(NSArray<V2TIMUserStatus *> *)userStatusList {
[self handleOnlineStatus:userStatusList];
}
- (void)onConnectFailed:(int)code err:(NSString *)err {
NSLog(@"%s", __func__);
}
- (void)onConnectSuccess {
NSLog(@"%s", __func__);
if (self.conversationList.count > 0) {
NSArray *conversationList = [NSArray arrayWithArray:self.conversationList];
[self updateOnlineStatus:conversationList];
}
}
- (void)onLoginSucc {
NSLog(@"%s", __func__);
if (self.conversationList.count > 0) {
NSArray *conversationList = [NSArray arrayWithArray:self.conversationList];
[self updateOnlineStatus:conversationList];
}
}
#pragma mark - V2TIMAdvancedMsgListener
- (void)onRecvNewMessage:(V2TIMMessage *)msg {
// when a new message is received, if the conversation is a hidden conversation, you need to clear the hidden mark first
// when a new message is received, if the conversation is marked unread, you need to clear the mark
NSString *userID = msg.userID;
NSString *groupID = msg.groupID;
NSString *conversationID = @"";
if ([self.class isTypingBusinessMessage:msg]) {
return;
}
if (IS_NOT_EMPTY_NSSTRING(userID)) {
conversationID = [NSString stringWithFormat:@"c2c_%@", userID];
}
if (IS_NOT_EMPTY_NSSTRING(groupID)) {
conversationID = [NSString stringWithFormat:@"group_%@", groupID];
}
TUIConversationCellData * targetCellData = nil;
for (TUIConversationCellData * cellData in self.conversationList) {
if ([cellData.conversationID isEqualToString:conversationID]) {
targetCellData = cellData;
break;
}
}
if (targetCellData) {
BOOL existInHidelist = targetCellData.isMarkAsHide;
BOOL existInUnreadlist = targetCellData.isMarkAsUnread;
if (existInHidelist || existInUnreadlist) {
[self cancelHideAndUnreadMarkConversation:conversationID existInHidelist:existInHidelist existInUnreadlist:existInUnreadlist];
}
}
else {
[V2TIMManager.sharedInstance getConversation:conversationID
succ:^(V2TIMConversation *conv) {
TUIConversationCellData *cellData = [self cellDataForConversation:conv];
BOOL existInHidelist = cellData.isMarkAsHide;
BOOL existInUnreadlist = cellData.isMarkAsUnread;
if (existInHidelist || existInUnreadlist) {
[self cancelHideAndUnreadMarkConversation:conversationID existInHidelist:existInHidelist existInUnreadlist:existInUnreadlist];
}
}
fail:^(int code, NSString *desc) {
kLog(@"[TUIConversation] %s, code:%d, desc:%@", __func__, code, desc);
}];
}
}
- (void)cancelHideAndUnreadMarkConversation:(NSString *)conversationID existInHidelist:(BOOL)existInHidelist existInUnreadlist:(BOOL)existInUnreadlist {
if(existInHidelist && existInUnreadlist) {
[V2TIMManager.sharedInstance markConversation:@[ conversationID ]
markType:@(V2TIM_CONVERSATION_MARK_TYPE_HIDE)
enableMark:NO
succ:^(NSArray<V2TIMConversationOperationResult *> *result) {
[V2TIMManager.sharedInstance markConversation:@[ conversationID ]
markType:@(V2TIM_CONVERSATION_MARK_TYPE_UNREAD)
enableMark:NO
succ:nil
fail:^(int code, NSString *desc) {
kLog(@"[TUIConversation] %s code:%d, desc:%@", __func__, code, desc);
}];
}
fail:^(int code, NSString *desc) {
kLog(@"[TUIConversation] %s code:%d, desc:%@", __func__, code, desc);
}];
}
else if (existInHidelist) {
[V2TIMManager.sharedInstance markConversation:@[ conversationID ]
markType:@(V2TIM_CONVERSATION_MARK_TYPE_HIDE)
enableMark:NO
succ:nil
fail:^(int code, NSString *desc) {
kLog(@"[TUIConversation] %s code:%d, desc:%@", __func__, code, desc);
}];
}
else if (existInUnreadlist) {
[V2TIMManager.sharedInstance markConversation:@[ conversationID ]
markType:@(V2TIM_CONVERSATION_MARK_TYPE_UNREAD)
enableMark:NO
succ:nil
fail:^(int code, NSString *desc) {
kLog(@"[TUIConversation] %s code:%d, desc:%@", __func__, code, desc);
}];
}
else {
// noting to do
}
}
#pragma mark - SDK Data Process
- (void)handleClearGroupHistoryMessage:(NSString *)groupID {
[V2TIMManager.sharedInstance clearGroupHistoryMessage:groupID
succ:^{
kLog(@"[TUIConversation] %s success", __func__);
}
fail:^(int code, NSString *desc) {
kLog(@"[TUIConversation] %s code:%d, desc:%@", __func__, code, desc);
}];
}
- (void)handleClearC2CHistoryMessage:(NSString *)userID {
[V2TIMManager.sharedInstance clearC2CHistoryMessage:userID
succ:^{
kLog(@"[TUIConversation] %s success", __func__);
}
fail:^(int code, NSString *desc) {
kLog(@"[TUIConversation] %s code:%d, desc:%@", __func__, code, desc);
}];
}
- (void)handlePinConversation:(TUIConversationCellData *)conversation pin:(BOOL)pin {
dispatch_async(dispatch_get_main_queue(), ^{
[V2TIMManager.sharedInstance pinConversation:conversation.conversationID isPinned:pin succ:nil fail:nil];
});
}
- (TUIConversationCellData *)cellDataForConversation:(V2TIMConversation *)conversation {
Class cls = [self getConversationCellClass];
if (cls) {
TUIConversationCellData *data = (TUIConversationCellData *)[[cls alloc] init];
data.conversationID = conversation.conversationID;
data.groupID = conversation.groupID;
data.groupType = conversation.groupType;
data.userID = conversation.userID;
data.title = conversation.showName;
data.faceUrl = conversation.faceUrl;
data.subTitle = [self getLastDisplayString:conversation];
data.foldSubTitle = [self getLastDisplayStringForFoldList:conversation];
data.atTipsStr = [self getGroupAtTipString:conversation];
data.atMsgSeqs = [self getGroupatMsgSeqs:conversation];
data.time = [self getLastDisplayDate:conversation];
data.isOnTop = conversation.isPinned;
data.unreadCount = conversation.unreadCount;
data.draftText = conversation.draftText;
data.isNotDisturb = [self isConversationNotDisturb:conversation];
data.orderKey = conversation.orderKey;
data.avatarImage = (conversation.type == V2TIM_C2C ? DefaultAvatarImage : DefaultGroupAvatarImageByGroupType(conversation.groupType));
data.onlineStatus = TUIConversationOnlineStatusUnknown;
data.isMarkAsUnread = [TUIConversationCellData isMarkedByUnReadType:conversation.markList];
data.isMarkAsHide = [TUIConversationCellData isMarkedByHideType:conversation.markList];
data.isMarkAsFolded = [TUIConversationCellData isMarkedByFoldType:conversation.markList];
data.lastMessage = conversation.lastMessage;
data.innerConversation = conversation;
data.conversationGroupList = conversation.conversationGroupList;
data.conversationMarkList = conversation.markList;
return data;
}
return nil;
}
- (BOOL)isConversationNotDisturb:(V2TIMConversation *)conversation {
return ![conversation.groupType isEqualToString:GroupType_Meeting] && (V2TIM_RECEIVE_NOT_NOTIFY_MESSAGE == conversation.recvOpt);
}
- (NSMutableAttributedString *)getLastDisplayStringForFoldList:(V2TIMConversation *)conversation {
NSMutableAttributedString *attributeString = [[NSMutableAttributedString alloc] initWithString:@""];
NSDictionary *attributeDict = @{NSForegroundColorAttributeName : [UIColor d_systemRedColor]};
[attributeString setAttributes:attributeDict range:NSMakeRange(0, attributeString.length)];
NSString *showName = [NSString stringWithFormat:@"%@: ", conversation.showName];
[attributeString appendAttributedString:[[NSAttributedString alloc] initWithString:showName]];
NSString *lastMsgStr = @"";
/**
* Attempt to get externally customized display information
*/
if (self.delegate && [self.delegate respondsToSelector:@selector(getConversationDisplayString:)]) {
lastMsgStr = [self.delegate getConversationDisplayString:conversation];
}
/**
* If there is no external customization, get the lastMsg display information through the message module
*/
if (lastMsgStr.length == 0 && conversation.lastMessage) {
lastMsgStr = [self getDisplayStringFromService:conversation.lastMessage];
}
/**
* If there is no lastMsg display information and no draft information, return nil directly
*/
if (lastMsgStr.length == 0) {
return nil;
}
[attributeString appendAttributedString:[[NSAttributedString alloc] initWithString:lastMsgStr]];
return attributeString;
}
- (NSMutableAttributedString *)getLastDisplayString:(V2TIMConversation *)conversation {
// subclass overide
return nil;
}
- (NSMutableArray<NSNumber *> *)getGroupatMsgSeqs:(V2TIMConversation *)conv {
NSMutableArray *seqList = [NSMutableArray array];
for (V2TIMGroupAtInfo *atInfo in conv.groupAtInfolist) {
[seqList addObject:@(atInfo.seq)];
}
if (seqList.count > 0) {
return seqList;
}
return nil;
}
- (NSDate *)getLastDisplayDate:(V2TIMConversation *)conv {
if (conv.draftText.length > 0) {
return conv.draftTimestamp;
}
if (conv.lastMessage) {
return conv.lastMessage.timestamp;
}
return [NSDate distantPast];
}
- (NSString *)getGroupAtTipString:(V2TIMConversation *)conv {
NSString *atTipsStr = @"";
BOOL atMe = NO;
BOOL atAll = NO;
for (V2TIMGroupAtInfo *atInfo in conv.groupAtInfolist) {
switch (atInfo.atType) {
case V2TIM_AT_ME:
atMe = YES;
continue;
;
case V2TIM_AT_ALL:
atAll = YES;
continue;
;
case V2TIM_AT_ALL_AT_ME:
atMe = YES;
atAll = YES;
continue;
;
default:
continue;
;
}
}
if (atMe && !atAll) {
atTipsStr = TIMCommonLocalizableString(TUIKitConversationTipsAtMe);
}
if (!atMe && atAll) {
atTipsStr = TIMCommonLocalizableString(TUIKitConversationTipsAtAll);
}
if (atMe && atAll) {
atTipsStr = TIMCommonLocalizableString(TUIKitConversationTipsAtMeAndAll);
}
return atTipsStr;
}
- (NSString *)getDraftContent:(V2TIMConversation *)conv {
NSString *draft = conv.draftText;
if (draft.length == 0) {
return nil;
}
NSError *error = nil;
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:[draft dataUsingEncoding:NSUTF8StringEncoding]
options:NSJSONReadingMutableLeaves
error:&error];
if (error || jsonDict == nil) {
return draft;
}
NSString *draftContent = [jsonDict.allKeys containsObject:@"content"] ? jsonDict[@"content"] : @"";
return draftContent;
}
- (BOOL)filteConversation:(V2TIMConversation *)conversation {
if (conversation.conversationID.length == 0 ||
[self.deletingConversationList containsObject:conversation.conversationID]) {
return YES;
}
if (conversation.userID.length == 0 && conversation.groupID.length == 0) {
return YES;
}
if (conversation.type == V2TIM_UNKNOWN) {
return YES;
}
if ([conversation.groupType isEqualToString:@"AVChatRoom"]) {
return YES;
}
if ([self getLastDisplayDate:conversation] == nil) {
if (conversation.unreadCount != 0) {
/**
* In some case, the time of unread conversation will be nil.
* If this happens, directly mark the conversation as read.
*/
[[V2TIMManager sharedInstance] cleanConversationUnreadMessageCount:conversation.conversationID cleanTimestamp:0 cleanSequence:0 succ:nil fail:nil];
}
return YES;
}
return NO;
}
- (void)markConversationHide:(TUIConversationCellData *)data {
[self handleHideConversation:data];
[V2TIMManager.sharedInstance markConversation:@[ data.conversationID ] markType:@(V2TIM_CONVERSATION_MARK_TYPE_HIDE) enableMark:YES succ:nil fail:nil];
}
- (void)markConversationAsRead:(TUIConversationCellData *)conv {
[[V2TIMManager sharedInstance] cleanConversationUnreadMessageCount:conv.conversationID cleanTimestamp:0 cleanSequence:0 succ:nil fail:nil];
[V2TIMManager.sharedInstance markConversation:@[ conv.conversationID ] markType:@(V2TIM_CONVERSATION_MARK_TYPE_UNREAD) enableMark:NO succ:nil fail:nil];
}
- (void)markConversationAsUnRead:(TUIConversationCellData *)conv {
[V2TIMManager.sharedInstance markConversation:@[ conv.conversationID ] markType:@(V2TIM_CONVERSATION_MARK_TYPE_UNREAD) enableMark:YES succ:nil fail:nil];
}
+ (BOOL)isTypingBusinessMessage:(V2TIMMessage *)message {
if (!message.customElem) {
return NO;
}
NSError *error;
NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data options:NSJSONReadingAllowFragments error:&error];
if (error) {
NSLog(@"parse customElem data error: %@", error);
return NO;
}
if (!param || ![param isKindOfClass:[NSDictionary class]]) {
return NO;
}
// Normal session peer is typing
NSString *businessID = param[BussinessID];
if (businessID && [businessID isKindOfClass:[NSString class]] && [businessID isEqualToString:BussinessID_Typing]) {
return YES;
}
// The customer service session peer is typing
if ([param.allKeys containsObject:BussinessID_CustomerService]) {
NSString *src = param[BussinessID_Src_CustomerService];
if (src && [src isKindOfClass:[NSString class]] && [src isEqualToString:BussinessID_Src_CustomerService_Typing]) {
return YES;
}
}
return NO;
}
+ (void)cacheConversationFoldListSettings_HideFoldItem:(BOOL)flag {
NSString *userID = [TUILogin getUserID];
NSString *key = [NSString stringWithFormat:@"hide_fold_item_%@", userID];
[NSUserDefaults.standardUserDefaults setBool:flag forKey:key];
[NSUserDefaults.standardUserDefaults synchronize];
}
+ (void)cacheConversationFoldListSettings_FoldItemIsUnread:(BOOL)flag {
NSString *userID = [TUILogin getUserID];
NSString *key = [NSString stringWithFormat:@"fold_item_is_unread_%@", userID];
[NSUserDefaults.standardUserDefaults setBool:flag forKey:key];
[NSUserDefaults.standardUserDefaults synchronize];
}
+ (BOOL)getConversationFoldListSettings_HideFoldItem {
NSString *userID = [TUILogin getUserID];
NSString *key = [NSString stringWithFormat:@"hide_fold_item_%@", userID];
return [NSUserDefaults.standardUserDefaults boolForKey:key];
}
+ (BOOL)getConversationFoldListSettings_FoldItemIsUnread {
NSString *userID = [TUILogin getUserID];
NSString *key = [NSString stringWithFormat:@"fold_item_is_unread_%@", userID];
return [NSUserDefaults.standardUserDefaults boolForKey:key];
}
#pragma mark - Lazy
- (NSMutableArray<TUIConversationCellData *> *)conversationList {
if (_conversationList == nil) {
_conversationList = [NSMutableArray array];
}
return _conversationList;
}
- (TUIConversationCellData *)conversationFoldListData {
if (_conversationFoldListData == nil) {
Class cls = [self getConversationCellClass];
if (cls) {
_conversationFoldListData = (TUIConversationCellData *)[[cls alloc] init];
_conversationFoldListData.conversationID = gGroup_conversationFoldListMockID;
_conversationFoldListData.title = TIMCommonLocalizableString(TUIKitConversationMarkFoldGroups);
_conversationFoldListData.avatarImage = TUICoreBundleThemeImage(@"", @"default_fold_group");
_conversationFoldListData.isNotDisturb = YES;
}
}
return _conversationFoldListData;
}
- (NSMutableDictionary<NSString *, TUIConversationCellData *> *)markUnreadMap {
if (_markUnreadMap == nil) {
_markUnreadMap = [NSMutableDictionary dictionary];
}
return _markUnreadMap;
}
- (NSMutableDictionary<NSString *, TUIConversationCellData *> *)markHideMap {
if (_markHideMap == nil) {
_markHideMap = [NSMutableDictionary dictionary];
}
return _markHideMap;
}
- (NSMutableDictionary<NSString *, TUIConversationCellData *> *)markFoldMap {
if (_markFoldMap == nil) {
_markFoldMap = [NSMutableDictionary dictionary];
}
return _markFoldMap;
}
#pragma mark Override func
- (Class)getConversationCellClass {
// subclass override
return nil;
}
- (NSString *)getDisplayStringFromService:(V2TIMMessage *)msg {
// subclass override
return nil;
}
@end