TUIConversationListBaseDataProvider.m 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239
  1. //
  2. // TUIConversationListBaseDataProvider.m
  3. // TUIConversation
  4. //
  5. // Created by harvy on 2022/7/14.
  6. // Copyright © 2023 Tencent. All rights reserved.
  7. //
  8. #import "TUIConversationListBaseDataProvider.h"
  9. #import <ImSDK_Plus/ImSDK_Plus.h>
  10. #import <TIMCommon/TIMDefine.h>
  11. #import <TUICore/TUICore.h>
  12. #import <TUICore/TUILogin.h>
  13. #import "TUIConversationCellData.h"
  14. #define kPageSize 100
  15. #define kLog(...) NSLog(__VA_ARGS__)
  16. @interface TUIConversationListBaseDataProvider () <V2TIMConversationListener,
  17. V2TIMGroupListener,
  18. V2TIMSDKListener,
  19. V2TIMAdvancedMsgListener,
  20. TUINotificationProtocol>
  21. @property(nonatomic, strong) NSMutableArray<TUIConversationCellData *> *conversationList;
  22. @property(nonatomic, assign, getter=isLastPage) BOOL lastPage;
  23. /**
  24. * Locally inserted conversation collapse list
  25. */
  26. @property(nonatomic, strong) TUIConversationCellData *conversationFoldListData;
  27. /**
  28. * Deleting conversation list
  29. */
  30. @property(nonatomic, strong) NSMutableArray *deletingConversationList;
  31. @end
  32. @implementation TUIConversationListBaseDataProvider
  33. - (instancetype)init {
  34. if (self = [super init]) {
  35. self.pageIndex = 0;
  36. self.pageSize = kPageSize;
  37. self.lastPage = NO;
  38. self.filter = [[V2TIMConversationListFilter alloc] init];
  39. self.deletingConversationList = [NSMutableArray array];
  40. [[V2TIMManager sharedInstance] addConversationListener:self];
  41. [[V2TIMManager sharedInstance] addGroupListener:self];
  42. [[V2TIMManager sharedInstance] addIMSDKListener:self];
  43. [[V2TIMManager sharedInstance] addAdvancedMsgListener:self];
  44. [TUICore registerEvent:TUICore_TUIConversationNotify subKey:TUICore_TUIConversationNotify_RemoveConversationSubKey object:self];
  45. [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(onLoginSucc) name:TUILoginSuccessNotification object:nil];
  46. }
  47. return self;
  48. }
  49. - (void)loadNexPageConversations {
  50. if (self.isLastPage) {
  51. return;
  52. }
  53. @weakify(self);
  54. [V2TIMManager.sharedInstance getConversationListByFilter:self.filter
  55. nextSeq:self.pageIndex
  56. count:(int)self.pageSize
  57. succ:^(NSArray<V2TIMConversation *> *list, uint64_t nextSeq, BOOL isFinished) {
  58. @strongify(self);
  59. self.pageIndex = nextSeq;
  60. self.lastPage = isFinished;
  61. [self preprocess:list];
  62. }
  63. fail:^(int code, NSString *desc) {
  64. @strongify(self);
  65. self.lastPage = YES;
  66. kLog(@"[TUIConversation] %s, code:%d, desc:%@", __func__, code, desc);
  67. }];
  68. }
  69. - (void)addConversationList:(NSArray<TUIConversationCellData *> *)conversationList {
  70. if (!NSThread.isMainThread) {
  71. __weak typeof(self) weakSelf = self;
  72. dispatch_async(dispatch_get_main_queue(), ^{
  73. [weakSelf addConversationList:conversationList];
  74. });
  75. return;
  76. }
  77. [self handleInsertConversationList:conversationList];
  78. }
  79. - (void)removeConversation:(TUIConversationCellData *)conversation {
  80. if (!NSThread.isMainThread) {
  81. __weak typeof(self) weakSelf = self;
  82. dispatch_async(dispatch_get_main_queue(), ^{
  83. [weakSelf removeConversation:conversation];
  84. });
  85. return;
  86. }
  87. [self handleRemoveConversation:conversation];
  88. }
  89. - (void)clearHistoryMessage:(TUIConversationCellData *)conversation {
  90. if (!NSThread.isMainThread) {
  91. __weak typeof(self) weakSelf = self;
  92. dispatch_async(dispatch_get_main_queue(), ^{
  93. [weakSelf clearHistoryMessage:conversation];
  94. });
  95. return;
  96. }
  97. if (conversation.groupID) {
  98. [self handleClearGroupHistoryMessage:conversation.groupID];
  99. } else if (conversation.userID) {
  100. [self handleClearC2CHistoryMessage:conversation.userID];
  101. }
  102. }
  103. - (void)pinConversation:(TUIConversationCellData *)conversation pin:(BOOL)pin {
  104. if (!NSThread.isMainThread) {
  105. __weak typeof(self) weakSelf = self;
  106. dispatch_async(dispatch_get_main_queue(), ^{
  107. [weakSelf pinConversation:conversation pin:pin];
  108. });
  109. return;
  110. }
  111. [self handlePinConversation:conversation pin:pin];
  112. }
  113. - (void)hideConversation:(TUIConversationCellData *)conversation {
  114. if (!NSThread.isMainThread) {
  115. __weak typeof(self) weakSelf = self;
  116. dispatch_async(dispatch_get_main_queue(), ^{
  117. [weakSelf hideConversation:conversation];
  118. });
  119. return;
  120. }
  121. [self handleHideConversation:conversation];
  122. }
  123. #pragma mark - Data process
  124. - (void)preprocess:(NSArray<V2TIMConversation *> *)v2Convs {
  125. if (!NSThread.isMainThread) {
  126. __weak typeof(self) weakSelf = self;
  127. dispatch_async(dispatch_get_main_queue(), ^{
  128. [weakSelf preprocess:v2Convs];
  129. });
  130. return;
  131. }
  132. NSMutableDictionary<NSString *, NSNumber *> *conversationMap = [NSMutableDictionary dictionary];
  133. for (TUIConversationCellData *item in self.conversationList) {
  134. if (item.conversationID) {
  135. [conversationMap setObject:@([self.conversationList indexOfObject:item]) forKey:item.conversationID];
  136. }
  137. }
  138. NSMutableArray *duplicateDataList = [NSMutableArray array];
  139. NSMutableArray *addedDataList = [NSMutableArray array];
  140. NSMutableArray *markHideDataList = [NSMutableArray array];
  141. NSMutableArray *markFoldDataList = [NSMutableArray array];
  142. for (V2TIMConversation *conv in v2Convs) {
  143. if ([self filteConversation:conv]) {
  144. continue;
  145. }
  146. TUIConversationCellData *cellData = [self cellDataForConversation:conv];
  147. if ([self.markHideMap objectForKey:cellData.conversationID]) {
  148. if (![TUIConversationCellData isMarkedByHideType:conv.markList]) {
  149. [self.markHideMap removeObjectForKey:cellData.conversationID];
  150. }
  151. } else {
  152. if ([TUIConversationCellData isMarkedByHideType:conv.markList]) {
  153. [self.markHideMap setObject:cellData forKey:cellData.conversationID];
  154. }
  155. }
  156. if ([self.markFoldMap objectForKey:cellData.conversationID]) {
  157. if (![TUIConversationCellData isMarkedByFoldType:conv.markList]) {
  158. [self.markFoldMap removeObjectForKey:cellData.conversationID];
  159. }
  160. } else {
  161. if ([TUIConversationCellData isMarkedByFoldType:conv.markList]) {
  162. [self.markFoldMap setObject:cellData forKey:cellData.conversationID];
  163. }
  164. }
  165. if ([TUIConversationCellData isMarkedByHideType:conv.markList] || [TUIConversationCellData isMarkedByFoldType:conv.markList]) {
  166. if ([TUIConversationCellData isMarkedByHideType:conv.markList]) {
  167. [markHideDataList addObject:cellData];
  168. }
  169. if ([TUIConversationCellData isMarkedByFoldType:conv.markList]) {
  170. [markFoldDataList addObject:cellData];
  171. }
  172. continue;
  173. }
  174. if ([conversationMap objectForKey:cellData.conversationID]) {
  175. [duplicateDataList addObject:cellData];
  176. } else {
  177. [addedDataList addObject:cellData];
  178. }
  179. if ([self.markUnreadMap objectForKey:cellData.conversationID]) {
  180. //
  181. // This is an operation to cancel the unread mark operation or to be cleaned away by a new message from someone else
  182. if (![TUIConversationCellData isMarkedByUnReadType:conv.markList]) {
  183. [self.markUnreadMap removeObjectForKey:cellData.conversationID];
  184. }
  185. } else {
  186. if ([TUIConversationCellData isMarkedByUnReadType:conv.markList]) {
  187. [self.markUnreadMap setObject:cellData forKey:cellData.conversationID];
  188. }
  189. }
  190. }
  191. if (markFoldDataList.count) {
  192. __block TUIConversationCellData *cellRecent = nil;
  193. [markFoldDataList enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
  194. TUIConversationCellData *cellData = (TUIConversationCellData *)obj;
  195. if (!cellData.isMarkAsHide) {
  196. cellRecent = cellData;
  197. *stop = YES;
  198. }
  199. }];
  200. if (IS_NOT_EMPTY_NSSTRING([cellRecent.foldSubTitle string])) {
  201. if (cellRecent.orderKey > self.conversationFoldListData.orderKey) {
  202. if (self.conversationFoldListData.orderKey == 0) {
  203. if ([self.class getConversationFoldListSettings_FoldItemIsUnread]) {
  204. self.conversationFoldListData.isMarkAsUnread = YES;
  205. }
  206. } else {
  207. self.conversationFoldListData.isMarkAsUnread = YES;
  208. [self.class cacheConversationFoldListSettings_FoldItemIsUnread:YES];
  209. [self.class cacheConversationFoldListSettings_HideFoldItem:NO];
  210. }
  211. }
  212. self.conversationFoldListData.subTitle = cellRecent.foldSubTitle;
  213. self.conversationFoldListData.orderKey = cellRecent.orderKey;
  214. }
  215. if (![self.class getConversationFoldListSettings_HideFoldItem]) {
  216. if ([conversationMap objectForKey:self.conversationFoldListData.conversationID]) {
  217. [duplicateDataList addObject:self.conversationFoldListData];
  218. } else if (0 == self.filter.conversationGroup.length) {
  219. [addedDataList addObject:self.conversationFoldListData];
  220. }
  221. }
  222. self.conversationFoldListData.isLocalConversationFoldList = YES;
  223. } else {
  224. [self updateFoldGroupNameWhileKickOffOrDismissed];
  225. }
  226. if (duplicateDataList.count) {
  227. [self sortDataList:duplicateDataList];
  228. [self handleUpdateConversationList:duplicateDataList positions:conversationMap];
  229. }
  230. if (addedDataList.count) {
  231. [self sortDataList:addedDataList];
  232. [self handleInsertConversationList:addedDataList];
  233. }
  234. [self updateMardHide:markHideDataList];
  235. [self updateMarkUnreadCount];
  236. [self updateMarkFold:markFoldDataList];
  237. [self asnycGetLastMessageDisplay:duplicateDataList addedDataList:addedDataList];
  238. }
  239. - (void)updateMardHide:(NSMutableArray *) markHideDataList {
  240. if (markHideDataList.count) {
  241. [self sortDataList:markHideDataList];
  242. NSMutableArray *pRemoveCellUIList = [NSMutableArray array];
  243. NSMutableDictionary<NSString *, TUIConversationCellData *> *pMarkHideDataMap = [NSMutableDictionary dictionary];
  244. for (TUIConversationCellData *item in markHideDataList) {
  245. if (item.conversationID) {
  246. [pRemoveCellUIList addObject:item];
  247. [pMarkHideDataMap setObject:item forKey:item.conversationID];
  248. }
  249. }
  250. for (TUIConversationCellData *item in self.conversationList) {
  251. if ([pMarkHideDataMap objectForKey:item.conversationID]) {
  252. [pRemoveCellUIList addObject:item];
  253. }
  254. }
  255. for (TUIConversationCellData *item in pRemoveCellUIList) {
  256. [self handleHideConversation:item];
  257. }
  258. }
  259. }
  260. - (void)updateMarkUnreadCount {
  261. __block NSInteger markUnreadCount = 0;
  262. [self.markUnreadMap enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, TUIConversationCellData *_Nonnull obj, BOOL *_Nonnull stop) {
  263. if (!obj.isNotDisturb) {
  264. markUnreadCount++;
  265. }
  266. }];
  267. __block NSInteger markHideUnreadCount = 0;
  268. [self.markHideMap enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, TUIConversationCellData *_Nonnull obj, BOOL *_Nonnull stop) {
  269. if (obj && [obj isKindOfClass:TUIConversationCellData.class]) {
  270. if (!obj.isNotDisturb) {
  271. if (obj.isMarkAsUnread) {
  272. markHideUnreadCount = markHideUnreadCount + 1;
  273. } else {
  274. markHideUnreadCount = markHideUnreadCount + obj.unreadCount;
  275. }
  276. }
  277. }
  278. }];
  279. [[NSNotificationCenter defaultCenter]
  280. postNotificationName:TUIKitNotification_onConversationMarkUnreadCountChanged
  281. object:nil
  282. userInfo:@{
  283. TUIKitNotification_onConversationMarkUnreadCountChanged_DataProvider : self,
  284. TUIKitNotification_onConversationMarkUnreadCountChanged_MarkUnreadCount : [NSNumber numberWithInteger:markUnreadCount],
  285. TUIKitNotification_onConversationMarkUnreadCountChanged_MarkHideUnreadCount : [NSNumber numberWithInteger:markHideUnreadCount],
  286. TUIKitNotification_onConversationMarkUnreadCountChanged_MarkUnreadMap : self.markUnreadMap,
  287. }];
  288. }
  289. - (void)updateMarkFold:(NSMutableArray *) markFoldDataList {
  290. if (markFoldDataList.count) {
  291. [self sortDataList:markFoldDataList];
  292. NSMutableArray *pRemoveCellUIList = [NSMutableArray array];
  293. NSMutableDictionary<NSString *, TUIConversationCellData *> *pMarkFoldDataMap = [NSMutableDictionary dictionary];
  294. for (TUIConversationCellData *item in markFoldDataList) {
  295. if (item.conversationID) {
  296. [pRemoveCellUIList addObject:item];
  297. [pMarkFoldDataMap setObject:item forKey:item.conversationID];
  298. }
  299. }
  300. for (TUIConversationCellData *item in self.conversationList) {
  301. if ([pMarkFoldDataMap objectForKey:item.conversationID]) {
  302. [pRemoveCellUIList addObject:item];
  303. }
  304. }
  305. // If a collapsed session appears in the home page List, it needs to be hidden. Note that the history cannot be deleted.
  306. for (TUIConversationCellData *item in pRemoveCellUIList) {
  307. [self handleHideConversation:item];
  308. }
  309. }
  310. }
  311. - (void)asnycGetLastMessageDisplay:(NSArray<TUIConversationCellData *> *)duplicateDataList addedDataList:(NSArray<TUIConversationCellData *> *)addedDataList {
  312. // override by subclass
  313. }
  314. - (void)handleInsertConversationList:(NSArray<TUIConversationCellData *> *)conversationList {
  315. [self.conversationList addObjectsFromArray:conversationList];
  316. [self sortDataList:self.conversationList];
  317. NSMutableArray *indexPaths = [NSMutableArray array];
  318. for (TUIConversationCellData *item in conversationList) {
  319. NSInteger index = [self.conversationList indexOfObject:item];
  320. [indexPaths addObject:[NSIndexPath indexPathForRow:index inSection:0]];
  321. }
  322. if (self.delegate && [self.delegate respondsToSelector:@selector(insertConversationsAtIndexPaths:)]) {
  323. [self.delegate insertConversationsAtIndexPaths:indexPaths];
  324. }
  325. [self updateOnlineStatus:conversationList];
  326. }
  327. - (void)handleUpdateConversationList:(NSArray<TUIConversationCellData *> *)conversationList
  328. positions:(NSMutableDictionary<NSString *, NSNumber *> *)positionMaps {
  329. if (0 == conversationList.count) {
  330. return;
  331. }
  332. for (TUIConversationCellData *item in conversationList) {
  333. if (item.isLocalConversationFoldList){
  334. continue;
  335. }
  336. NSNumber *position = [positionMaps objectForKey:item.conversationID];
  337. NSAssert((position && [position isKindOfClass:NSNumber.class]), @"serius error, the self.conversationList maybe changed on the other thread");
  338. if (position && position.integerValue < self.conversationList.count) {
  339. TUIConversationCellData *cellData = self.conversationList[position.integerValue];
  340. item.onlineStatus = cellData.onlineStatus;
  341. [self.conversationList replaceObjectAtIndex:[position integerValue] withObject:item];
  342. }
  343. }
  344. [self sortDataList:self.conversationList];
  345. NSInteger minIndex = self.conversationList.count - 1;
  346. NSInteger maxIndex = 0;
  347. NSMutableDictionary<NSString *, TUIConversationCellData *> *conversationMap = [NSMutableDictionary dictionary];
  348. for (TUIConversationCellData *item in self.conversationList) {
  349. if (item.conversationID) {
  350. [conversationMap setObject:item forKey:item.conversationID];
  351. }
  352. }
  353. for (TUIConversationCellData *cellData in conversationList) {
  354. TUIConversationCellData *item = [conversationMap objectForKey:cellData.conversationID];
  355. if (item) {
  356. NSInteger previous = [[positionMaps objectForKey:item.conversationID] integerValue];
  357. NSInteger current = [self.conversationList indexOfObject:item];
  358. minIndex = minIndex < MIN(previous, current) ? minIndex : MIN(previous, current);
  359. maxIndex = maxIndex > MAX(previous, current) ? maxIndex : MAX(previous, current);
  360. }
  361. }
  362. if (minIndex > maxIndex) {
  363. return;
  364. }
  365. NSMutableArray *indexPaths = [NSMutableArray array];
  366. for (NSInteger index = minIndex; index < maxIndex + 1; index++) {
  367. [indexPaths addObject:[NSIndexPath indexPathForRow:index inSection:0]];
  368. }
  369. if (self.delegate && [self.delegate respondsToSelector:@selector(reloadConversationsAtIndexPaths:)]) {
  370. [self.delegate reloadConversationsAtIndexPaths:indexPaths];
  371. }
  372. }
  373. - (void)handleRemoveConversation:(TUIConversationCellData *)conversation {
  374. NSInteger index = [self.conversationList indexOfObject:conversation];
  375. if (index != NSNotFound) {
  376. [self.conversationList removeObject:conversation];
  377. if (self.delegate && [self.delegate respondsToSelector:@selector(deleteConversationAtIndexPaths:)]) {
  378. [self.delegate deleteConversationAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ]];
  379. }
  380. [self.deletingConversationList addObject:conversation.conversationID];
  381. @weakify(self)
  382. [[V2TIMManager sharedInstance] deleteConversation:conversation.conversationID succ:^{
  383. @strongify(self)
  384. [self.deletingConversationList removeObject:conversation.conversationID];
  385. if ([self.markUnreadMap objectForKey:conversation.conversationID]) {
  386. [self.markUnreadMap removeObjectForKey:conversation.conversationID];
  387. }
  388. [self updateMarkUnreadCount];
  389. } fail:^(int code, NSString *desc) {
  390. @strongify(self)
  391. NSLog(@"deleteConversation failed, conversationID:%@ code:%d desc:%@", conversation.conversationID, code, desc);
  392. [self.deletingConversationList removeObject:conversation.conversationID];
  393. }];
  394. }
  395. }
  396. - (void)handleHideConversation:(TUIConversationCellData *)conversation {
  397. NSInteger index = [self.conversationList indexOfObject:conversation];
  398. if (index != NSNotFound) {
  399. [self.conversationList removeObject:conversation];
  400. if (self.delegate && [self.delegate respondsToSelector:@selector(deleteConversationAtIndexPaths:)]) {
  401. [self.delegate deleteConversationAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ]];
  402. }
  403. }
  404. }
  405. - (void)sortDataList:(NSMutableArray<TUIConversationCellData *> *)dataList {
  406. [dataList sortUsingComparator:^NSComparisonResult(TUIConversationCellData *obj1, TUIConversationCellData *obj2) {
  407. return obj1.orderKey < obj2.orderKey;
  408. }];
  409. }
  410. - (TUIConversationCellData *)cellDataOfGroupID:(NSString *)groupID {
  411. TUIConversationCellData *cellData = nil;
  412. NSString *conversationID = [NSString stringWithFormat:@"group_%@", groupID];
  413. for (TUIConversationCellData *item in self.conversationList) {
  414. if ([item.conversationID isEqualToString:conversationID]) {
  415. cellData = item;
  416. break;
  417. }
  418. }
  419. return cellData;
  420. }
  421. - (void)dealFoldcellDataOfGroupID:(NSString *)groupID {
  422. TUIConversationCellData *cellData = nil;
  423. NSString *conversationID = [NSString stringWithFormat:@"group_%@", groupID];
  424. cellData = [self.markFoldMap objectForKey:conversationID];
  425. if (cellData) {
  426. @weakify(self);
  427. [[V2TIMManager sharedInstance] deleteConversation:cellData.conversationID
  428. succ:^{
  429. @strongify(self);
  430. [self.markFoldMap removeObjectForKey:conversationID];
  431. [self updateFoldGroupNameWhileKickOffOrDismissed];
  432. }
  433. fail:nil];
  434. }
  435. }
  436. - (void)updateFoldGroupNameWhileKickOffOrDismissed {
  437. __block TUIConversationCellData *cellRecent = nil;
  438. [self.markFoldMap enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, TUIConversationCellData *_Nonnull obj, BOOL *_Nonnull stop) {
  439. TUIConversationCellData *cellData = (TUIConversationCellData *)obj;
  440. if (!cellData.isMarkAsHide) {
  441. if (!cellRecent) {
  442. cellRecent = cellData;
  443. } else {
  444. if (cellData.orderKey > cellRecent.orderKey) {
  445. cellRecent = cellData;
  446. }
  447. }
  448. }
  449. }];
  450. if (cellRecent) {
  451. if (IS_NOT_EMPTY_NSSTRING([cellRecent.foldSubTitle string])) {
  452. self.conversationFoldListData.subTitle = cellRecent.foldSubTitle;
  453. }
  454. NSMutableDictionary<NSString *, NSNumber *> *conversationMap = [NSMutableDictionary dictionary];
  455. for (TUIConversationCellData *item in self.conversationList) {
  456. if (item.conversationID) {
  457. [conversationMap setObject:@([self.conversationList indexOfObject:item]) forKey:item.conversationID];
  458. }
  459. }
  460. [self handleUpdateConversationList:@[ self.conversationFoldListData ] positions:conversationMap];
  461. } else {
  462. if (_conversationFoldListData) {
  463. [self hideConversation:self.conversationFoldListData];
  464. }
  465. }
  466. }
  467. #pragma mark - TUICore
  468. - (void)onNotifyEvent:(NSString *)key subKey:(NSString *)subKey object:(nullable id)anObject param:(nullable NSDictionary *)param {
  469. if ([key isEqualToString:TUICore_TUIConversationNotify] && [subKey isEqualToString:TUICore_TUIConversationNotify_RemoveConversationSubKey]) {
  470. NSString *conversationID = param[TUICore_TUIConversationNotify_RemoveConversationSubKey_ConversationID];
  471. if (IS_NOT_EMPTY_NSSTRING(conversationID)) {
  472. TUIConversationCellData *removeConversation = nil;
  473. for (TUIConversationCellData *item in self.conversationList) {
  474. if ([conversationID isEqualToString:item.conversationID]) {
  475. removeConversation = item;
  476. break;
  477. }
  478. }
  479. if (removeConversation) {
  480. [self removeConversation:removeConversation];
  481. } else {
  482. @weakify(self);
  483. [[V2TIMManager sharedInstance] deleteConversation:conversationID
  484. succ:^{
  485. @strongify(self);
  486. [self updateMarkUnreadCount];
  487. }
  488. fail:nil];
  489. }
  490. }
  491. }
  492. }
  493. #pragma mark - User Status
  494. - (void)updateOnlineStatus:(NSArray<TUIConversationCellData *> *)conversationList {
  495. if (conversationList.count == 0) {
  496. return;
  497. }
  498. if (!TUILogin.isUserLogined) {
  499. return;
  500. }
  501. if (!NSThread.isMainThread) {
  502. __weak typeof(self) weakSelf = self;
  503. dispatch_async(dispatch_get_main_queue(), ^{
  504. [weakSelf updateOnlineStatus:conversationList];
  505. });
  506. return;
  507. }
  508. // reset
  509. NSMutableArray *userIDList = [NSMutableArray array];
  510. NSMutableDictionary *positionMap = [NSMutableDictionary dictionary];
  511. for (TUIConversationCellData *item in conversationList) {
  512. if (item.onlineStatus == TUIConversationOnlineStatusOnline) {
  513. item.onlineStatus = TUIConversationOnlineStatusOffline;
  514. }
  515. if (item.userID.length && item.groupID.length == 0) {
  516. [userIDList addObject:item.userID];
  517. }
  518. if (item.conversationID) {
  519. [positionMap setObject:@([self.conversationList indexOfObject:item]) forKey:item.conversationID];
  520. }
  521. }
  522. [self handleUpdateConversationList:conversationList positions:positionMap];
  523. // fetch
  524. [self asyncGetOnlineStatus:userIDList];
  525. }
  526. - (void)asyncGetOnlineStatus:(NSArray *)userIDList {
  527. if (userIDList.count == 0) {
  528. return;
  529. }
  530. if (NSThread.isMainThread) {
  531. @weakify(self);
  532. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  533. @strongify(self);
  534. [self asyncGetOnlineStatus:userIDList];
  535. });
  536. return;
  537. }
  538. // get
  539. @weakify(self);
  540. [V2TIMManager.sharedInstance getUserStatus:userIDList
  541. succ:^(NSArray<V2TIMUserStatus *> *result) {
  542. @strongify(self);
  543. [self handleOnlineStatus:result];
  544. }
  545. fail:^(int code, NSString *desc) {
  546. #if DEBUG
  547. if (code == ERR_SDK_INTERFACE_NOT_SUPPORT && TUIConfig.defaultConfig.displayOnlineStatusIcon) {
  548. [TUITool makeToast:desc];
  549. }
  550. #endif
  551. }];
  552. // subscribe for the users who was deleted from friend list
  553. [V2TIMManager.sharedInstance subscribeUserStatus:userIDList
  554. succ:^{
  555. }
  556. fail:^(int code, NSString *desc){
  557. }];
  558. }
  559. - (void)handleOnlineStatus:(NSArray<V2TIMUserStatus *> *)userStatusList {
  560. if (!NSThread.isMainThread) {
  561. __weak typeof(self) weakSelf = self;
  562. dispatch_async(dispatch_get_main_queue(), ^{
  563. [weakSelf handleOnlineStatus:userStatusList];
  564. });
  565. return;
  566. }
  567. NSMutableDictionary *positonMap = [NSMutableDictionary dictionary];
  568. for (TUIConversationCellData *item in self.conversationList) {
  569. NSAssert(item.conversationID, @"the data of self.conversation maybe damaged");
  570. if (item.conversationID && item.userID.length != 0 && item.groupID.length == 0) {
  571. [positonMap setObject:@([self.conversationList indexOfObject:item]) forKey:item.conversationID];
  572. }
  573. }
  574. NSMutableArray *changedConversation = [NSMutableArray array];
  575. for (V2TIMUserStatus *item in userStatusList) {
  576. NSString *conversationID = [NSString stringWithFormat:@"c2c_%@", item.userID];
  577. NSNumber *position = [positonMap objectForKey:conversationID];
  578. if (position == nil) {
  579. continue;
  580. }
  581. TUIConversationCellData *conversation = [self.conversationList objectAtIndex:position.integerValue];
  582. if (![conversation.conversationID isEqualToString:conversationID]) {
  583. continue;
  584. }
  585. switch (item.statusType) {
  586. case V2TIM_USER_STATUS_ONLINE: {
  587. conversation.onlineStatus = TUIConversationOnlineStatusOnline;
  588. } break;
  589. case V2TIM_USER_STATUS_OFFLINE:
  590. case V2TIM_USER_STATUS_UNLOGINED: {
  591. conversation.onlineStatus = TUIConversationOnlineStatusOffline;
  592. } break;
  593. default:
  594. conversation.onlineStatus = TUIConversationOnlineStatusUnknown;
  595. break;
  596. }
  597. [changedConversation addObject:conversation];
  598. }
  599. if (changedConversation.count > 0) {
  600. [self handleUpdateConversationList:changedConversation positions:positonMap];
  601. }
  602. }
  603. #pragma mark - V2TIMConversationListener
  604. - (void)onNewConversation:(NSArray<V2TIMConversation *> *)conversationList {
  605. [self preprocess:conversationList];
  606. }
  607. - (void)onConversationChanged:(NSArray<V2TIMConversation *> *)conversationList {
  608. [self preprocess:conversationList];
  609. }
  610. - (void)onConversationDeleted:(NSArray<NSString *> *)conversationIDList {
  611. NSArray *cacheConversationList = [self.conversationList copy];
  612. for (TUIConversationCellData *item in cacheConversationList) {
  613. if ([conversationIDList containsObject:item.conversationID]) {
  614. NSInteger index = [self.conversationList indexOfObject:item];
  615. if (index != NSNotFound) {
  616. [self.conversationList removeObject:item];
  617. if (self.delegate && [self.delegate respondsToSelector:@selector(deleteConversationAtIndexPaths:)]) {
  618. [self.delegate deleteConversationAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ]];
  619. }
  620. if ([self.markUnreadMap objectForKey:item.conversationID]) {
  621. [self.markUnreadMap removeObjectForKey:item.conversationID];
  622. }
  623. [self updateMarkUnreadCount];
  624. }
  625. }
  626. }
  627. }
  628. #pragma mark - V2TIMGroupListener
  629. - (NSString *)getGroupName:(TUIConversationCellData *)cellData {
  630. NSString *formatString = cellData.groupID;
  631. if (cellData.title.length > 0) {
  632. return cellData.title;
  633. }
  634. else if (cellData.groupID.length > 0) {
  635. return cellData.groupID;
  636. }
  637. return @"";
  638. }
  639. - (void)onGroupDismissed:(NSString *)groupID opUser:(V2TIMGroupMemberInfo *)opUser {
  640. TUIConversationCellData *data = [self cellDataOfGroupID:groupID];
  641. if (data == nil) {
  642. [self dealFoldcellDataOfGroupID:groupID];
  643. return;
  644. }
  645. NSString *groupName = [self getGroupName:data];
  646. [TUITool makeToast:[NSString stringWithFormat:TIMCommonLocalizableString(TUIKitGroupDismssTipsFormat), groupName]];
  647. [self handleRemoveConversation:data];
  648. }
  649. - (void)onGroupRecycled:(NSString *)groupID opUser:(V2TIMGroupMemberInfo *)opUser {
  650. TUIConversationCellData *data = [self cellDataOfGroupID:groupID];
  651. if (data == nil) {
  652. [self dealFoldcellDataOfGroupID:groupID];
  653. return;
  654. }
  655. NSString *groupName = [self getGroupName:data];
  656. [TUITool makeToast:[NSString stringWithFormat:TIMCommonLocalizableString(TUIKitGroupRecycledTipsFormat), groupName]];
  657. [self handleRemoveConversation:data];
  658. }
  659. - (void)onMemberKicked:(NSString *)groupID opUser:(V2TIMGroupMemberInfo *)opUser memberList:(NSArray<V2TIMGroupMemberInfo *> *)memberList {
  660. BOOL kicked = NO;
  661. for (V2TIMGroupMemberInfo *info in memberList) {
  662. if ([info.userID isEqualToString:[TUILogin getUserID]]) {
  663. kicked = YES;
  664. break;
  665. }
  666. }
  667. if (kicked == NO) {
  668. return;
  669. }
  670. TUIConversationCellData *data = [self cellDataOfGroupID:groupID];
  671. if (data == nil) {
  672. [self dealFoldcellDataOfGroupID:groupID];
  673. return;
  674. }
  675. NSString *groupName = [self getGroupName:data];
  676. [TUITool makeToast:[NSString stringWithFormat:TIMCommonLocalizableString(TUIKitGroupKickOffTipsFormat), groupName]];
  677. [self handleRemoveConversation:data];
  678. }
  679. - (void)onQuitFromGroup:(NSString *)groupID {
  680. TUIConversationCellData *data = [self cellDataOfGroupID:groupID];
  681. if (data == nil) {
  682. [self dealFoldcellDataOfGroupID:groupID];
  683. return;
  684. }
  685. NSString *groupName = [self getGroupName:data];
  686. [TUITool makeToast:[NSString stringWithFormat:TIMCommonLocalizableString(TUIKitGroupDropoutTipsFormat), groupName]];
  687. [self handleRemoveConversation:data];
  688. }
  689. - (void)onGroupInfoChanged:(NSString *)groupID changeInfoList:(NSArray<V2TIMGroupChangeInfo *> *)changeInfoList {
  690. TUIConversationCellData *data = [self cellDataOfGroupID:groupID];
  691. if (groupID.length == 0 || data == nil) {
  692. return;
  693. }
  694. __weak typeof(self) weakSelf = self;
  695. NSString *conversationID = [NSString stringWithFormat:@"group_%@", groupID];
  696. [V2TIMManager.sharedInstance getConversation:conversationID
  697. succ:^(V2TIMConversation *conv) {
  698. [weakSelf preprocess:@[ conv ]];
  699. }
  700. fail:^(int code, NSString *desc) {
  701. kLog(@"[TUIConversation] %s, code:%d, desc:%@", __func__, code, desc);
  702. }];
  703. }
  704. #pragma mark - V2TIMSDKListener
  705. - (void)onUserStatusChanged:(NSArray<V2TIMUserStatus *> *)userStatusList {
  706. [self handleOnlineStatus:userStatusList];
  707. }
  708. - (void)onConnectFailed:(int)code err:(NSString *)err {
  709. NSLog(@"%s", __func__);
  710. }
  711. - (void)onConnectSuccess {
  712. NSLog(@"%s", __func__);
  713. if (self.conversationList.count > 0) {
  714. NSArray *conversationList = [NSArray arrayWithArray:self.conversationList];
  715. [self updateOnlineStatus:conversationList];
  716. }
  717. }
  718. - (void)onLoginSucc {
  719. NSLog(@"%s", __func__);
  720. if (self.conversationList.count > 0) {
  721. NSArray *conversationList = [NSArray arrayWithArray:self.conversationList];
  722. [self updateOnlineStatus:conversationList];
  723. }
  724. }
  725. #pragma mark - V2TIMAdvancedMsgListener
  726. - (void)onRecvNewMessage:(V2TIMMessage *)msg {
  727. // when a new message is received, if the conversation is a hidden conversation, you need to clear the hidden mark first
  728. // when a new message is received, if the conversation is marked unread, you need to clear the mark
  729. NSString *userID = msg.userID;
  730. NSString *groupID = msg.groupID;
  731. NSString *conversationID = @"";
  732. if ([self.class isTypingBusinessMessage:msg]) {
  733. return;
  734. }
  735. if (IS_NOT_EMPTY_NSSTRING(userID)) {
  736. conversationID = [NSString stringWithFormat:@"c2c_%@", userID];
  737. }
  738. if (IS_NOT_EMPTY_NSSTRING(groupID)) {
  739. conversationID = [NSString stringWithFormat:@"group_%@", groupID];
  740. }
  741. TUIConversationCellData * targetCellData = nil;
  742. for (TUIConversationCellData * cellData in self.conversationList) {
  743. if ([cellData.conversationID isEqualToString:conversationID]) {
  744. targetCellData = cellData;
  745. break;
  746. }
  747. }
  748. if (targetCellData) {
  749. BOOL existInHidelist = targetCellData.isMarkAsHide;
  750. BOOL existInUnreadlist = targetCellData.isMarkAsUnread;
  751. if (existInHidelist || existInUnreadlist) {
  752. [self cancelHideAndUnreadMarkConversation:conversationID existInHidelist:existInHidelist existInUnreadlist:existInUnreadlist];
  753. }
  754. }
  755. else {
  756. [V2TIMManager.sharedInstance getConversation:conversationID
  757. succ:^(V2TIMConversation *conv) {
  758. TUIConversationCellData *cellData = [self cellDataForConversation:conv];
  759. BOOL existInHidelist = cellData.isMarkAsHide;
  760. BOOL existInUnreadlist = cellData.isMarkAsUnread;
  761. if (existInHidelist || existInUnreadlist) {
  762. [self cancelHideAndUnreadMarkConversation:conversationID existInHidelist:existInHidelist existInUnreadlist:existInUnreadlist];
  763. }
  764. }
  765. fail:^(int code, NSString *desc) {
  766. kLog(@"[TUIConversation] %s, code:%d, desc:%@", __func__, code, desc);
  767. }];
  768. }
  769. }
  770. - (void)cancelHideAndUnreadMarkConversation:(NSString *)conversationID existInHidelist:(BOOL)existInHidelist existInUnreadlist:(BOOL)existInUnreadlist {
  771. if(existInHidelist && existInUnreadlist) {
  772. [V2TIMManager.sharedInstance markConversation:@[ conversationID ]
  773. markType:@(V2TIM_CONVERSATION_MARK_TYPE_HIDE)
  774. enableMark:NO
  775. succ:^(NSArray<V2TIMConversationOperationResult *> *result) {
  776. [V2TIMManager.sharedInstance markConversation:@[ conversationID ]
  777. markType:@(V2TIM_CONVERSATION_MARK_TYPE_UNREAD)
  778. enableMark:NO
  779. succ:nil
  780. fail:^(int code, NSString *desc) {
  781. kLog(@"[TUIConversation] %s code:%d, desc:%@", __func__, code, desc);
  782. }];
  783. }
  784. fail:^(int code, NSString *desc) {
  785. kLog(@"[TUIConversation] %s code:%d, desc:%@", __func__, code, desc);
  786. }];
  787. }
  788. else if (existInHidelist) {
  789. [V2TIMManager.sharedInstance markConversation:@[ conversationID ]
  790. markType:@(V2TIM_CONVERSATION_MARK_TYPE_HIDE)
  791. enableMark:NO
  792. succ:nil
  793. fail:^(int code, NSString *desc) {
  794. kLog(@"[TUIConversation] %s code:%d, desc:%@", __func__, code, desc);
  795. }];
  796. }
  797. else if (existInUnreadlist) {
  798. [V2TIMManager.sharedInstance markConversation:@[ conversationID ]
  799. markType:@(V2TIM_CONVERSATION_MARK_TYPE_UNREAD)
  800. enableMark:NO
  801. succ:nil
  802. fail:^(int code, NSString *desc) {
  803. kLog(@"[TUIConversation] %s code:%d, desc:%@", __func__, code, desc);
  804. }];
  805. }
  806. else {
  807. // noting to do
  808. }
  809. }
  810. #pragma mark - SDK Data Process
  811. - (void)handleClearGroupHistoryMessage:(NSString *)groupID {
  812. [V2TIMManager.sharedInstance clearGroupHistoryMessage:groupID
  813. succ:^{
  814. kLog(@"[TUIConversation] %s success", __func__);
  815. }
  816. fail:^(int code, NSString *desc) {
  817. kLog(@"[TUIConversation] %s code:%d, desc:%@", __func__, code, desc);
  818. }];
  819. }
  820. - (void)handleClearC2CHistoryMessage:(NSString *)userID {
  821. [V2TIMManager.sharedInstance clearC2CHistoryMessage:userID
  822. succ:^{
  823. kLog(@"[TUIConversation] %s success", __func__);
  824. }
  825. fail:^(int code, NSString *desc) {
  826. kLog(@"[TUIConversation] %s code:%d, desc:%@", __func__, code, desc);
  827. }];
  828. }
  829. - (void)handlePinConversation:(TUIConversationCellData *)conversation pin:(BOOL)pin {
  830. dispatch_async(dispatch_get_main_queue(), ^{
  831. [V2TIMManager.sharedInstance pinConversation:conversation.conversationID isPinned:pin succ:nil fail:nil];
  832. });
  833. }
  834. - (TUIConversationCellData *)cellDataForConversation:(V2TIMConversation *)conversation {
  835. Class cls = [self getConversationCellClass];
  836. if (cls) {
  837. TUIConversationCellData *data = (TUIConversationCellData *)[[cls alloc] init];
  838. data.conversationID = conversation.conversationID;
  839. data.groupID = conversation.groupID;
  840. data.groupType = conversation.groupType;
  841. data.userID = conversation.userID;
  842. data.title = conversation.showName;
  843. data.faceUrl = conversation.faceUrl;
  844. data.subTitle = [self getLastDisplayString:conversation];
  845. data.foldSubTitle = [self getLastDisplayStringForFoldList:conversation];
  846. data.atTipsStr = [self getGroupAtTipString:conversation];
  847. data.atMsgSeqs = [self getGroupatMsgSeqs:conversation];
  848. data.time = [self getLastDisplayDate:conversation];
  849. data.isOnTop = conversation.isPinned;
  850. data.unreadCount = conversation.unreadCount;
  851. data.draftText = conversation.draftText;
  852. data.isNotDisturb = [self isConversationNotDisturb:conversation];
  853. data.orderKey = conversation.orderKey;
  854. data.avatarImage = (conversation.type == V2TIM_C2C ? DefaultAvatarImage : DefaultGroupAvatarImageByGroupType(conversation.groupType));
  855. data.onlineStatus = TUIConversationOnlineStatusUnknown;
  856. data.isMarkAsUnread = [TUIConversationCellData isMarkedByUnReadType:conversation.markList];
  857. data.isMarkAsHide = [TUIConversationCellData isMarkedByHideType:conversation.markList];
  858. data.isMarkAsFolded = [TUIConversationCellData isMarkedByFoldType:conversation.markList];
  859. data.lastMessage = conversation.lastMessage;
  860. data.innerConversation = conversation;
  861. data.conversationGroupList = conversation.conversationGroupList;
  862. data.conversationMarkList = conversation.markList;
  863. return data;
  864. }
  865. return nil;
  866. }
  867. - (BOOL)isConversationNotDisturb:(V2TIMConversation *)conversation {
  868. return ![conversation.groupType isEqualToString:GroupType_Meeting] && (V2TIM_RECEIVE_NOT_NOTIFY_MESSAGE == conversation.recvOpt);
  869. }
  870. - (NSMutableAttributedString *)getLastDisplayStringForFoldList:(V2TIMConversation *)conversation {
  871. NSMutableAttributedString *attributeString = [[NSMutableAttributedString alloc] initWithString:@""];
  872. NSDictionary *attributeDict = @{NSForegroundColorAttributeName : [UIColor d_systemRedColor]};
  873. [attributeString setAttributes:attributeDict range:NSMakeRange(0, attributeString.length)];
  874. NSString *showName = [NSString stringWithFormat:@"%@: ", conversation.showName];
  875. [attributeString appendAttributedString:[[NSAttributedString alloc] initWithString:showName]];
  876. NSString *lastMsgStr = @"";
  877. /**
  878. * Attempt to get externally customized display information
  879. */
  880. if (self.delegate && [self.delegate respondsToSelector:@selector(getConversationDisplayString:)]) {
  881. lastMsgStr = [self.delegate getConversationDisplayString:conversation];
  882. }
  883. /**
  884. * If there is no external customization, get the lastMsg display information through the message module
  885. */
  886. if (lastMsgStr.length == 0 && conversation.lastMessage) {
  887. lastMsgStr = [self getDisplayStringFromService:conversation.lastMessage];
  888. }
  889. /**
  890. * If there is no lastMsg display information and no draft information, return nil directly
  891. */
  892. if (lastMsgStr.length == 0) {
  893. return nil;
  894. }
  895. [attributeString appendAttributedString:[[NSAttributedString alloc] initWithString:lastMsgStr]];
  896. return attributeString;
  897. }
  898. - (NSMutableAttributedString *)getLastDisplayString:(V2TIMConversation *)conversation {
  899. // subclass overide
  900. return nil;
  901. }
  902. - (NSMutableArray<NSNumber *> *)getGroupatMsgSeqs:(V2TIMConversation *)conv {
  903. NSMutableArray *seqList = [NSMutableArray array];
  904. for (V2TIMGroupAtInfo *atInfo in conv.groupAtInfolist) {
  905. [seqList addObject:@(atInfo.seq)];
  906. }
  907. if (seqList.count > 0) {
  908. return seqList;
  909. }
  910. return nil;
  911. }
  912. - (NSDate *)getLastDisplayDate:(V2TIMConversation *)conv {
  913. if (conv.draftText.length > 0) {
  914. return conv.draftTimestamp;
  915. }
  916. if (conv.lastMessage) {
  917. return conv.lastMessage.timestamp;
  918. }
  919. return [NSDate distantPast];
  920. }
  921. - (NSString *)getGroupAtTipString:(V2TIMConversation *)conv {
  922. NSString *atTipsStr = @"";
  923. BOOL atMe = NO;
  924. BOOL atAll = NO;
  925. for (V2TIMGroupAtInfo *atInfo in conv.groupAtInfolist) {
  926. switch (atInfo.atType) {
  927. case V2TIM_AT_ME:
  928. atMe = YES;
  929. continue;
  930. ;
  931. case V2TIM_AT_ALL:
  932. atAll = YES;
  933. continue;
  934. ;
  935. case V2TIM_AT_ALL_AT_ME:
  936. atMe = YES;
  937. atAll = YES;
  938. continue;
  939. ;
  940. default:
  941. continue;
  942. ;
  943. }
  944. }
  945. if (atMe && !atAll) {
  946. atTipsStr = TIMCommonLocalizableString(TUIKitConversationTipsAtMe);
  947. }
  948. if (!atMe && atAll) {
  949. atTipsStr = TIMCommonLocalizableString(TUIKitConversationTipsAtAll);
  950. }
  951. if (atMe && atAll) {
  952. atTipsStr = TIMCommonLocalizableString(TUIKitConversationTipsAtMeAndAll);
  953. }
  954. return atTipsStr;
  955. }
  956. - (NSString *)getDraftContent:(V2TIMConversation *)conv {
  957. NSString *draft = conv.draftText;
  958. if (draft.length == 0) {
  959. return nil;
  960. }
  961. NSError *error = nil;
  962. NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:[draft dataUsingEncoding:NSUTF8StringEncoding]
  963. options:NSJSONReadingMutableLeaves
  964. error:&error];
  965. if (error || jsonDict == nil) {
  966. return draft;
  967. }
  968. NSString *draftContent = [jsonDict.allKeys containsObject:@"content"] ? jsonDict[@"content"] : @"";
  969. return draftContent;
  970. }
  971. - (BOOL)filteConversation:(V2TIMConversation *)conversation {
  972. if (conversation.conversationID.length == 0 ||
  973. [self.deletingConversationList containsObject:conversation.conversationID]) {
  974. return YES;
  975. }
  976. if (conversation.userID.length == 0 && conversation.groupID.length == 0) {
  977. return YES;
  978. }
  979. if (conversation.type == V2TIM_UNKNOWN) {
  980. return YES;
  981. }
  982. if ([conversation.groupType isEqualToString:@"AVChatRoom"]) {
  983. return YES;
  984. }
  985. if ([self getLastDisplayDate:conversation] == nil) {
  986. if (conversation.unreadCount != 0) {
  987. /**
  988. * In some case, the time of unread conversation will be nil.
  989. * If this happens, directly mark the conversation as read.
  990. */
  991. [[V2TIMManager sharedInstance] cleanConversationUnreadMessageCount:conversation.conversationID cleanTimestamp:0 cleanSequence:0 succ:nil fail:nil];
  992. }
  993. return YES;
  994. }
  995. return NO;
  996. }
  997. - (void)markConversationHide:(TUIConversationCellData *)data {
  998. [self handleHideConversation:data];
  999. [V2TIMManager.sharedInstance markConversation:@[ data.conversationID ] markType:@(V2TIM_CONVERSATION_MARK_TYPE_HIDE) enableMark:YES succ:nil fail:nil];
  1000. }
  1001. - (void)markConversationAsRead:(TUIConversationCellData *)conv {
  1002. [[V2TIMManager sharedInstance] cleanConversationUnreadMessageCount:conv.conversationID cleanTimestamp:0 cleanSequence:0 succ:nil fail:nil];
  1003. [V2TIMManager.sharedInstance markConversation:@[ conv.conversationID ] markType:@(V2TIM_CONVERSATION_MARK_TYPE_UNREAD) enableMark:NO succ:nil fail:nil];
  1004. }
  1005. - (void)markConversationAsUnRead:(TUIConversationCellData *)conv {
  1006. [V2TIMManager.sharedInstance markConversation:@[ conv.conversationID ] markType:@(V2TIM_CONVERSATION_MARK_TYPE_UNREAD) enableMark:YES succ:nil fail:nil];
  1007. }
  1008. + (BOOL)isTypingBusinessMessage:(V2TIMMessage *)message {
  1009. if (!message.customElem) {
  1010. return NO;
  1011. }
  1012. NSError *error;
  1013. NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data options:NSJSONReadingAllowFragments error:&error];
  1014. if (error) {
  1015. NSLog(@"parse customElem data error: %@", error);
  1016. return NO;
  1017. }
  1018. if (!param || ![param isKindOfClass:[NSDictionary class]]) {
  1019. return NO;
  1020. }
  1021. // Normal session peer is typing
  1022. NSString *businessID = param[BussinessID];
  1023. if (businessID && [businessID isKindOfClass:[NSString class]] && [businessID isEqualToString:BussinessID_Typing]) {
  1024. return YES;
  1025. }
  1026. // The customer service session peer is typing
  1027. if ([param.allKeys containsObject:BussinessID_CustomerService]) {
  1028. NSString *src = param[BussinessID_Src_CustomerService];
  1029. if (src && [src isKindOfClass:[NSString class]] && [src isEqualToString:BussinessID_Src_CustomerService_Typing]) {
  1030. return YES;
  1031. }
  1032. }
  1033. return NO;
  1034. }
  1035. + (void)cacheConversationFoldListSettings_HideFoldItem:(BOOL)flag {
  1036. NSString *userID = [TUILogin getUserID];
  1037. NSString *key = [NSString stringWithFormat:@"hide_fold_item_%@", userID];
  1038. [NSUserDefaults.standardUserDefaults setBool:flag forKey:key];
  1039. [NSUserDefaults.standardUserDefaults synchronize];
  1040. }
  1041. + (void)cacheConversationFoldListSettings_FoldItemIsUnread:(BOOL)flag {
  1042. NSString *userID = [TUILogin getUserID];
  1043. NSString *key = [NSString stringWithFormat:@"fold_item_is_unread_%@", userID];
  1044. [NSUserDefaults.standardUserDefaults setBool:flag forKey:key];
  1045. [NSUserDefaults.standardUserDefaults synchronize];
  1046. }
  1047. + (BOOL)getConversationFoldListSettings_HideFoldItem {
  1048. NSString *userID = [TUILogin getUserID];
  1049. NSString *key = [NSString stringWithFormat:@"hide_fold_item_%@", userID];
  1050. return [NSUserDefaults.standardUserDefaults boolForKey:key];
  1051. }
  1052. + (BOOL)getConversationFoldListSettings_FoldItemIsUnread {
  1053. NSString *userID = [TUILogin getUserID];
  1054. NSString *key = [NSString stringWithFormat:@"fold_item_is_unread_%@", userID];
  1055. return [NSUserDefaults.standardUserDefaults boolForKey:key];
  1056. }
  1057. #pragma mark - Lazy
  1058. - (NSMutableArray<TUIConversationCellData *> *)conversationList {
  1059. if (_conversationList == nil) {
  1060. _conversationList = [NSMutableArray array];
  1061. }
  1062. return _conversationList;
  1063. }
  1064. - (TUIConversationCellData *)conversationFoldListData {
  1065. if (_conversationFoldListData == nil) {
  1066. Class cls = [self getConversationCellClass];
  1067. if (cls) {
  1068. _conversationFoldListData = (TUIConversationCellData *)[[cls alloc] init];
  1069. _conversationFoldListData.conversationID = gGroup_conversationFoldListMockID;
  1070. _conversationFoldListData.title = TIMCommonLocalizableString(TUIKitConversationMarkFoldGroups);
  1071. _conversationFoldListData.avatarImage = TUICoreBundleThemeImage(@"", @"default_fold_group");
  1072. _conversationFoldListData.isNotDisturb = YES;
  1073. }
  1074. }
  1075. return _conversationFoldListData;
  1076. }
  1077. - (NSMutableDictionary<NSString *, TUIConversationCellData *> *)markUnreadMap {
  1078. if (_markUnreadMap == nil) {
  1079. _markUnreadMap = [NSMutableDictionary dictionary];
  1080. }
  1081. return _markUnreadMap;
  1082. }
  1083. - (NSMutableDictionary<NSString *, TUIConversationCellData *> *)markHideMap {
  1084. if (_markHideMap == nil) {
  1085. _markHideMap = [NSMutableDictionary dictionary];
  1086. }
  1087. return _markHideMap;
  1088. }
  1089. - (NSMutableDictionary<NSString *, TUIConversationCellData *> *)markFoldMap {
  1090. if (_markFoldMap == nil) {
  1091. _markFoldMap = [NSMutableDictionary dictionary];
  1092. }
  1093. return _markFoldMap;
  1094. }
  1095. #pragma mark Override func
  1096. - (Class)getConversationCellClass {
  1097. // subclass override
  1098. return nil;
  1099. }
  1100. - (NSString *)getDisplayStringFromService:(V2TIMMessage *)msg {
  1101. // subclass override
  1102. return nil;
  1103. }
  1104. @end