TUIMessageDataProvider.m 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  1. // Created by Tencent on 2023/06/09.
  2. // Copyright © 2023 Tencent. All rights reserved.
  3. #import "TUIMessageDataProvider.h"
  4. #import <AVFoundation/AVFoundation.h>
  5. #import <TIMCommon/TUIRelationUserModel.h>
  6. #import <TIMCommon/TUISystemMessageCellData.h>
  7. #import <TUICore/TUILogin.h>
  8. #import "TUIChatCallingDataProvider.h"
  9. #import "TUICloudCustomDataTypeCenter.h"
  10. #import "TUIFaceMessageCellData.h"
  11. #import "TUIFileMessageCellData.h"
  12. #import "TUIImageMessageCellData.h"
  13. #import "TUIJoinGroupMessageCellData.h"
  14. #import "TUIMergeMessageCellData.h"
  15. #import "TUIMessageDataProvider+MessageDeal.h"
  16. #import "TUIMessageProgressManager.h"
  17. #import "TUIReplyMessageCellData.h"
  18. #import "TUITextMessageCellData.h"
  19. #import "TUIVideoMessageCellData.h"
  20. #import "TUIVoiceMessageCellData.h"
  21. /**
  22. * The maximum editable time after the message is recalled, default is (2 * 60)
  23. */
  24. #define MaxReEditMessageDelay 2 * 60
  25. #define kIsCustomMessageFromPlugin @"kIsCustomMessageFromPlugin"
  26. static Class<TUIMessageDataProviderDataSource> gDataSourceClass = nil;
  27. @implementation TUIMessageDataProvider
  28. #pragma mark - Life cycle
  29. - (void)dealloc {
  30. gCallingDataProvider = nil;
  31. }
  32. + (void)setDataSourceClass:(Class<TUIMessageDataProviderDataSource>)dataSourceClass {
  33. gDataSourceClass = dataSourceClass;
  34. }
  35. #pragma mark - TUIMessageCellData parser
  36. + (nullable TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
  37. // 1 Parse cell data
  38. TUIMessageCellData *data = [self parseMessageCellDataFromMessageStatus:message];
  39. if (data == nil) {
  40. data = [self parseMessageCellDataFromMessageCustomData:message];
  41. }
  42. if (data == nil) {
  43. data = [self parseMessageCellDataFromMessageElement:message];
  44. }
  45. // 2 Fill in property if needed
  46. if (data) {
  47. [self fillPropertyToCellData:data ofMessage:message];
  48. } else {
  49. NSLog(@"current message will be ignored in chat page, msg:%@", message);
  50. }
  51. return data;
  52. }
  53. + (nullable TUIMessageCellData *)parseMessageCellDataFromMessageStatus:(V2TIMMessage *)message {
  54. TUIMessageCellData *data = nil;
  55. if (message.status == V2TIM_MSG_STATUS_LOCAL_REVOKED) {
  56. data = [TUIMessageDataProvider getRevokeCellData:message];
  57. }
  58. return data;
  59. }
  60. + (nullable TUIMessageCellData *)parseMessageCellDataFromMessageCustomData:(V2TIMMessage *)message {
  61. TUIMessageCellData *data = nil;
  62. if ([message isContainsCloudCustomOfDataType:TUICloudCustomDataType_MessageReply]) {
  63. /**
  64. * Determine whether to include "reply-message"
  65. */
  66. data = [TUIReplyMessageCellData getCellData:message];
  67. } else if ([message isContainsCloudCustomOfDataType:TUICloudCustomDataType_MessageReference]) {
  68. /**
  69. * Determine whether to include "quote-message"
  70. */
  71. data = [TUIReferenceMessageCellData getCellData:message];
  72. }
  73. return data;
  74. }
  75. + (nullable TUIMessageCellData *)parseMessageCellDataFromMessageElement:(V2TIMMessage *)message {
  76. TUIMessageCellData *data = nil;
  77. switch (message.elemType) {
  78. case V2TIM_ELEM_TYPE_TEXT: {
  79. data = [TUITextMessageCellData getCellData:message];
  80. } break;
  81. case V2TIM_ELEM_TYPE_IMAGE: {
  82. data = [TUIImageMessageCellData getCellData:message];
  83. } break;
  84. case V2TIM_ELEM_TYPE_SOUND: {
  85. data = [TUIVoiceMessageCellData getCellData:message];
  86. } break;
  87. case V2TIM_ELEM_TYPE_VIDEO: {
  88. data = [TUIVideoMessageCellData getCellData:message];
  89. } break;
  90. case V2TIM_ELEM_TYPE_FILE: {
  91. data = [TUIFileMessageCellData getCellData:message];
  92. } break;
  93. case V2TIM_ELEM_TYPE_FACE: {
  94. data = [TUIFaceMessageCellData getCellData:message];
  95. } break;
  96. case V2TIM_ELEM_TYPE_GROUP_TIPS: {
  97. data = [self getSystemCellData:message];
  98. } break;
  99. case V2TIM_ELEM_TYPE_MERGER: {
  100. data = [TUIMergeMessageCellData getCellData:message];
  101. } break;
  102. case V2TIM_ELEM_TYPE_CUSTOM: {
  103. data = [self getCustomMessageCellData:message];
  104. } break;
  105. default:
  106. data = [self getUnsupportedCellData:message];
  107. break;
  108. }
  109. return data;
  110. }
  111. + (void)fillPropertyToCellData:(TUIMessageCellData *)data ofMessage:(V2TIMMessage *)message {
  112. data.innerMessage = message;
  113. if (message.groupID.length > 0 && !message.isSelf && ![data isKindOfClass:[TUISystemMessageCellData class]]) {
  114. data.showName = YES;
  115. }
  116. switch (message.status) {
  117. case V2TIM_MSG_STATUS_SEND_SUCC:
  118. data.status = Msg_Status_Succ;
  119. break;
  120. case V2TIM_MSG_STATUS_SEND_FAIL:
  121. data.status = Msg_Status_Fail;
  122. break;
  123. case V2TIM_MSG_STATUS_SENDING:
  124. data.status = Msg_Status_Sending_2;
  125. break;
  126. default:
  127. break;
  128. }
  129. /**
  130. * Update progress of message uploading/downloading
  131. */
  132. {
  133. NSInteger uploadProgress = [TUIMessageProgressManager.shareManager uploadProgressForMessage:message.msgID];
  134. NSInteger downloadProgress = [TUIMessageProgressManager.shareManager downloadProgressForMessage:message.msgID];
  135. if ([data conformsToProtocol:@protocol(TUIMessageCellDataFileUploadProtocol)]) {
  136. ((id<TUIMessageCellDataFileUploadProtocol>)data).uploadProgress = uploadProgress;
  137. }
  138. if ([data conformsToProtocol:@protocol(TUIMessageCellDataFileDownloadProtocol)]) {
  139. ((id<TUIMessageCellDataFileDownloadProtocol>)data).downladProgress = downloadProgress;
  140. ((id<TUIMessageCellDataFileDownloadProtocol>)data).isDownloading = (downloadProgress != 0) && (downloadProgress != 100);
  141. }
  142. }
  143. /**
  144. * Determine whether to include "replies-message"
  145. */
  146. if ([message isContainsCloudCustomOfDataType:TUICloudCustomDataType_MessageReplies]) {
  147. [message doThingsInContainsCloudCustomOfDataType:TUICloudCustomDataType_MessageReplies
  148. callback:^(BOOL isContains, id obj) {
  149. if (isContains) {
  150. if ([data isKindOfClass:TUISystemMessageCellData.class] ||
  151. [data isKindOfClass:TUIJoinGroupMessageCellData.class]) {
  152. data.showMessageModifyReplies = NO;
  153. } else {
  154. data.showMessageModifyReplies = YES;
  155. }
  156. if (obj && [obj isKindOfClass:NSDictionary.class]) {
  157. NSDictionary *dic = (NSDictionary *)obj;
  158. NSString *typeStr =
  159. [TUICloudCustomDataTypeCenter convertType2String:TUICloudCustomDataType_MessageReplies];
  160. NSDictionary *messageReplies = [dic valueForKey:typeStr];
  161. NSArray *repliesArr = [messageReplies valueForKey:@"replies"];
  162. if ([repliesArr isKindOfClass:NSArray.class]) {
  163. data.messageModifyReplies = repliesArr.copy;
  164. }
  165. }
  166. }
  167. }];
  168. }
  169. }
  170. + (nullable TUIMessageCellData *)getCustomMessageCellData:(V2TIMMessage *)message {
  171. // ************************************************************************************
  172. // ************************************************************************************
  173. // **The compatible processing logic of TUICallKit will be removed after***************
  174. // **TUICallKit is connected according to the standard process. ***********************
  175. // ************************************************************************************
  176. // ************************************************************************************
  177. TUIMessageCellData *data = nil;
  178. id<TUIChatCallingInfoProtocol> callingInfo = nil;
  179. if ([self.callingDataProvider isCallingMessage:message callingInfo:&callingInfo]) {
  180. // Voice-video-call message
  181. if (callingInfo) {
  182. // Supported voice-video-call message
  183. if (callingInfo.excludeFromHistory) {
  184. // This message will be ignore in chat page
  185. data = nil;
  186. } else {
  187. data = [self getCallingCellData:callingInfo];
  188. if (data == nil) {
  189. data = [self getUnsupportedCellData:message];
  190. }
  191. }
  192. } else {
  193. // Unsupported voice-video-call message
  194. data = [self getUnsupportedCellData:message];
  195. }
  196. return data;
  197. }
  198. // ************************************************************************************
  199. // ************************************************************************************
  200. // ************************************************************************************
  201. // ************************************************************************************
  202. NSString *businessID = nil;
  203. BOOL excludeFromHistory = NO;
  204. V2TIMSignalingInfo *signalingInfo = [V2TIMManager.sharedInstance getSignallingInfo:message];
  205. if (signalingInfo) {
  206. // This message is signaling message
  207. BOOL isOnlineOnly = NO;
  208. @try {
  209. isOnlineOnly = [[message valueForKeyPath:@"message.IsOnlineOnly"] boolValue];
  210. } @catch (NSException *exception) {
  211. isOnlineOnly = NO;
  212. }
  213. excludeFromHistory = isOnlineOnly || (message.isExcludedFromLastMessage && message.isExcludedFromUnreadCount);
  214. businessID = [self getSignalingBusinessID:signalingInfo];
  215. } else {
  216. // This message is normal custom message
  217. excludeFromHistory = NO;
  218. businessID = [self getCustomBusinessID:message];
  219. }
  220. if (excludeFromHistory) {
  221. // Return nil means not display in the chat page
  222. return nil;
  223. }
  224. if (businessID.length > 0) {
  225. Class cellDataClass = nil;
  226. if (gDataSourceClass && [gDataSourceClass respondsToSelector:@selector(onGetCustomMessageCellDataClass:)]) {
  227. cellDataClass = [gDataSourceClass onGetCustomMessageCellDataClass:businessID];
  228. }
  229. if (cellDataClass && [cellDataClass respondsToSelector:@selector(getCellData:)]) {
  230. TUIMessageCellData *data = [cellDataClass getCellData:message];
  231. if (data.shouldHide) {
  232. return nil;
  233. } else {
  234. data.reuseId = businessID;
  235. return data;
  236. }
  237. }
  238. // In CustomerService scenarios, unsupported messages are not displayed directly.
  239. if ([businessID tui_containsString:BussinessID_CustomerService]) {
  240. return nil;
  241. }
  242. return [self getUnsupportedCellData:message];
  243. } else {
  244. return [self getUnsupportedCellData:message];
  245. }
  246. }
  247. + (TUIMessageCellData *)getUnsupportedCellData:(V2TIMMessage *)message {
  248. TUITextMessageCellData *cellData = [[TUITextMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
  249. cellData.content = TIMCommonLocalizableString(TUIKitNotSupportThisMessage);
  250. cellData.reuseId = TTextMessageCell_ReuseId;
  251. return cellData;
  252. }
  253. + (nullable TUISystemMessageCellData *)getSystemCellData:(V2TIMMessage *)message {
  254. V2TIMGroupTipsElem *tip = message.groupTipsElem;
  255. NSString *opUserName = [self getOpUserName:tip.opMember];
  256. NSMutableArray<NSString *> *userNameList = [self getUserNameList:tip.memberList];
  257. NSMutableArray<NSString *> *userIDList = [self getUserIDList:tip.memberList];
  258. if (tip.type == V2TIM_GROUP_TIPS_TYPE_JOIN || tip.type == V2TIM_GROUP_TIPS_TYPE_INVITE || tip.type == V2TIM_GROUP_TIPS_TYPE_KICKED ||
  259. tip.type == V2TIM_GROUP_TIPS_TYPE_GROUP_INFO_CHANGE || tip.type == V2TIM_GROUP_TIPS_TYPE_QUIT ||
  260. tip.type == V2TIM_GROUP_TIPS_TYPE_PINNED_MESSAGE_ADDED || tip.type == V2TIM_GROUP_TIPS_TYPE_PINNED_MESSAGE_DELETED) {
  261. TUIJoinGroupMessageCellData *joinGroupData = [[TUIJoinGroupMessageCellData alloc] initWithDirection:MsgDirectionIncoming];
  262. joinGroupData.content = [self getDisplayString:message];
  263. joinGroupData.opUserName = opUserName;
  264. joinGroupData.opUserID = tip.opMember.userID;
  265. joinGroupData.userNameList = userNameList;
  266. joinGroupData.userIDList = userIDList;
  267. joinGroupData.reuseId = TJoinGroupMessageCell_ReuseId;
  268. return joinGroupData;
  269. } else {
  270. TUISystemMessageCellData *sysdata = [[TUISystemMessageCellData alloc] initWithDirection:MsgDirectionIncoming];
  271. sysdata.content = [self getDisplayString:message];
  272. sysdata.reuseId = TSystemMessageCell_ReuseId;
  273. if (sysdata.content.length) {
  274. return sysdata;
  275. }
  276. }
  277. return nil;
  278. }
  279. + (nullable TUISystemMessageCellData *)getRevokeCellData:(V2TIMMessage *)message {
  280. TUISystemMessageCellData *revoke = [[TUISystemMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
  281. revoke.reuseId = TSystemMessageCell_ReuseId;
  282. revoke.content = [self getRevokeDispayString:message];
  283. revoke.innerMessage = message;
  284. V2TIMUserFullInfo *revokerInfo = message.revokerInfo;
  285. if (message.isSelf) {
  286. if (message.elemType == V2TIM_ELEM_TYPE_TEXT && fabs([[NSDate date] timeIntervalSinceDate:message.timestamp]) < MaxReEditMessageDelay) {
  287. if (revokerInfo && ![revokerInfo.userID isEqualToString:message.sender]) {
  288. // Super User revoke
  289. revoke.supportReEdit = NO;
  290. } else {
  291. revoke.supportReEdit = YES;
  292. }
  293. }
  294. } else if (message.groupID.length > 0) {
  295. /**
  296. * For the name display of group messages, the group business card is displayed first, the nickname has the second priority, and the user ID has the
  297. * lowest priority.
  298. */
  299. NSString *userName = [TUIMessageDataProvider getShowName:message];
  300. TUIJoinGroupMessageCellData *joinGroupData = [[TUIJoinGroupMessageCellData alloc] initWithDirection:MsgDirectionIncoming];
  301. joinGroupData.content = [self getRevokeDispayString:message];
  302. joinGroupData.opUserID = message.sender;
  303. joinGroupData.opUserName = userName;
  304. joinGroupData.reuseId = TJoinGroupMessageCell_ReuseId;
  305. return joinGroupData;
  306. }
  307. return revoke;
  308. }
  309. + (nullable TUISystemMessageCellData *)getSystemMsgFromDate:(NSDate *)date {
  310. TUISystemMessageCellData *system = [[TUISystemMessageCellData alloc] initWithDirection:MsgDirectionOutgoing];
  311. system.content = [TUITool convertDateToStr:date];
  312. system.reuseId = TSystemMessageCell_ReuseId;
  313. system.type = TUISystemMessageTypeDate;
  314. return system;
  315. }
  316. #pragma mark - Last message parser
  317. + (void)asyncGetDisplayString:(NSArray<V2TIMMessage *> *)messageList callback:(void (^)(NSDictionary<NSString *, NSString *> *))callback {
  318. if (!callback) {
  319. return;
  320. }
  321. NSMutableDictionary *originDisplayMap = [NSMutableDictionary dictionary];
  322. NSMutableArray *cellDataList = [NSMutableArray array];
  323. for (V2TIMMessage *message in messageList) {
  324. TUIMessageCellData *cellData = [self getCellData:message];
  325. if (cellData) {
  326. [cellDataList addObject:cellData];
  327. }
  328. NSString *displayString = [self getDisplayString:message];
  329. if (displayString && message.msgID) {
  330. originDisplayMap[message.msgID] = displayString;
  331. }
  332. }
  333. if (cellDataList.count == 0) {
  334. callback(@{});
  335. return;
  336. }
  337. TUIMessageDataProvider *provider = [[TUIMessageDataProvider alloc] init];
  338. NSArray *additionUserIDList = [provider getUserIDListForAdditionalUserInfo:cellDataList];
  339. if (additionUserIDList.count == 0) {
  340. callback(@{});
  341. return;
  342. }
  343. NSMutableDictionary *result = [NSMutableDictionary dictionary];
  344. [provider
  345. requestForAdditionalUserInfo:cellDataList
  346. callback:^{
  347. for (TUIMessageCellData *cellData in cellDataList) {
  348. [cellData.additionalUserInfoResult
  349. enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, TUIRelationUserModel *_Nonnull obj, BOOL *_Nonnull stop) {
  350. NSString *str = [NSString stringWithFormat:@"{%@}", key];
  351. NSString *showName = obj.userID;
  352. if (obj.nameCard.length > 0) {
  353. showName = obj.nameCard;
  354. } else if (obj.friendRemark.length > 0) {
  355. showName = obj.friendRemark;
  356. } else if (obj.nickName.length > 0) {
  357. showName = obj.nickName;
  358. }
  359. NSString *displayString = [originDisplayMap objectForKey:cellData.msgID];
  360. if (displayString && [displayString containsString:str]) {
  361. displayString = [displayString stringByReplacingOccurrencesOfString:str withString:showName];
  362. result[cellData.msgID] = displayString;
  363. }
  364. callback(result);
  365. }];
  366. }
  367. }];
  368. }
  369. + (nullable NSString *)getDisplayString:(V2TIMMessage *)message {
  370. BOOL hasRiskContent = message.hasRiskContent;
  371. BOOL isRevoked = (message.status == V2TIM_MSG_STATUS_LOCAL_REVOKED);
  372. if (hasRiskContent && !isRevoked) {
  373. return TIMCommonLocalizableString(TUIKitMessageDisplayRiskContent);
  374. }
  375. NSString *str = [self parseDisplayStringFromMessageStatus:message];
  376. if (str == nil) {
  377. str = [self parseDisplayStringFromMessageElement:message];
  378. }
  379. if (str == nil) {
  380. NSLog(@"current message will be ignored in chat page or conversation list page, msg:%@", message);
  381. }
  382. return str;
  383. }
  384. + (nullable NSString *)parseDisplayStringFromMessageStatus:(V2TIMMessage *)message {
  385. NSString *str = nil;
  386. if (message.status == V2TIM_MSG_STATUS_LOCAL_REVOKED) {
  387. str = [self getRevokeDispayString:message];
  388. }
  389. return str;
  390. }
  391. + (nullable NSString *)parseDisplayStringFromMessageElement:(V2TIMMessage *)message {
  392. NSString *str = nil;
  393. switch (message.elemType) {
  394. case V2TIM_ELEM_TYPE_TEXT: {
  395. str = [TUITextMessageCellData getDisplayString:message];
  396. } break;
  397. case V2TIM_ELEM_TYPE_IMAGE: {
  398. str = [TUIImageMessageCellData getDisplayString:message];
  399. } break;
  400. case V2TIM_ELEM_TYPE_SOUND: {
  401. str = [TUIVoiceMessageCellData getDisplayString:message];
  402. } break;
  403. case V2TIM_ELEM_TYPE_VIDEO: {
  404. str = [TUIVideoMessageCellData getDisplayString:message];
  405. } break;
  406. case V2TIM_ELEM_TYPE_FILE: {
  407. str = [TUIFileMessageCellData getDisplayString:message];
  408. } break;
  409. case V2TIM_ELEM_TYPE_FACE: {
  410. str = [TUIFaceMessageCellData getDisplayString:message];
  411. } break;
  412. case V2TIM_ELEM_TYPE_MERGER: {
  413. str = [TUIMergeMessageCellData getDisplayString:message];
  414. } break;
  415. case V2TIM_ELEM_TYPE_GROUP_TIPS: {
  416. str = [self getGroupTipsDisplayString:message];
  417. } break;
  418. case V2TIM_ELEM_TYPE_CUSTOM: {
  419. str = [self getCustomDisplayString:message];
  420. } break;
  421. default:
  422. str = TIMCommonLocalizableString(TUIKitMessageTipsUnsupportCustomMessage);
  423. break;
  424. }
  425. return str;
  426. }
  427. + (nullable NSString *)getCustomDisplayString:(V2TIMMessage *)message {
  428. // ************************************************************************************
  429. // ************************************************************************************
  430. // ************** TUICallKit , TUICallKit *************
  431. // ************************************************************************************
  432. // ************************************************************************************
  433. NSString *str = nil;
  434. id<TUIChatCallingInfoProtocol> callingInfo = nil;
  435. if ([self.callingDataProvider isCallingMessage:message callingInfo:&callingInfo]) {
  436. // Voice-video-call message
  437. if (callingInfo) {
  438. // Supported voice-video-call message
  439. if (callingInfo.excludeFromHistory) {
  440. // This message will be ignore in chat page
  441. str = nil;
  442. } else {
  443. // Get display text
  444. str = callingInfo.content ?: TIMCommonLocalizableString(TUIKitMessageTipsUnsupportCustomMessage);
  445. }
  446. } else {
  447. // Unsupported voice-video-call message
  448. str = TIMCommonLocalizableString(TUIKitMessageTipsUnsupportCustomMessage);
  449. }
  450. return str;
  451. }
  452. // ************************************************************************************
  453. // ************************************************************************************
  454. // ************************************************************************************
  455. // ************************************************************************************
  456. NSString *businessID = nil;
  457. BOOL excludeFromHistory = NO;
  458. V2TIMSignalingInfo *signalingInfo = [V2TIMManager.sharedInstance getSignallingInfo:message];
  459. if (signalingInfo) {
  460. // This message is signaling message
  461. excludeFromHistory = message.isExcludedFromLastMessage && message.isExcludedFromUnreadCount;
  462. businessID = [self getSignalingBusinessID:signalingInfo];
  463. } else {
  464. // This message is normal custom message
  465. excludeFromHistory = NO;
  466. businessID = [self getCustomBusinessID:message];
  467. }
  468. if (excludeFromHistory) {
  469. // Return nil means not display int the chat page
  470. return nil;
  471. }
  472. if (businessID.length > 0) {
  473. Class cellDataClass = nil;
  474. if (gDataSourceClass && [gDataSourceClass respondsToSelector:@selector(onGetCustomMessageCellDataClass:)]) {
  475. cellDataClass = [gDataSourceClass onGetCustomMessageCellDataClass:businessID];
  476. }
  477. if (cellDataClass && [cellDataClass respondsToSelector:@selector(getDisplayString:)]) {
  478. return [cellDataClass getDisplayString:message];
  479. }
  480. // In CustomerService scenarios, unsupported messages are not displayed directly.
  481. if ([businessID tui_containsString:BussinessID_CustomerService]) {
  482. return nil;
  483. }
  484. return TIMCommonLocalizableString(TUIKitMessageTipsUnsupportCustomMessage);
  485. } else {
  486. return TIMCommonLocalizableString(TUIKitMessageTipsUnsupportCustomMessage);
  487. }
  488. }
  489. #pragma mark - Data source operate
  490. - (void)processQuoteMessage:(NSArray<TUIMessageCellData *> *)uiMsgs {
  491. if (uiMsgs.count == 0) {
  492. return;
  493. }
  494. @weakify(self);
  495. dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  496. dispatch_group_t group = dispatch_group_create();
  497. dispatch_group_async(group, concurrentQueue, ^{
  498. for (TUIMessageCellData *cellData in uiMsgs) {
  499. if (![cellData isKindOfClass:TUIReplyMessageCellData.class]) {
  500. continue;
  501. }
  502. TUIReplyMessageCellData *myData = (TUIReplyMessageCellData *)cellData;
  503. __weak typeof(myData) weakMyData = myData;
  504. myData.onFinish = ^{
  505. dispatch_async(dispatch_get_main_queue(), ^{
  506. NSUInteger index = [self.uiMsgs indexOfObject:weakMyData];
  507. if (index != NSNotFound) {
  508. // if messageData exist In datasource, reload this data.
  509. [UIView performWithoutAnimation:^{
  510. @strongify(self);
  511. [self.dataSource dataProviderDataSourceWillChange:self];
  512. [self.dataSource dataProviderDataSourceChange:self
  513. withType:TUIMessageBaseDataProviderDataSourceChangeTypeReload
  514. atIndex:index
  515. animation:NO];
  516. [self.dataSource dataProviderDataSourceDidChange:self];
  517. }];
  518. }
  519. });
  520. };
  521. dispatch_group_enter(group);
  522. [self loadOriginMessageFromReplyData:myData
  523. dealCallback:^{
  524. dispatch_group_leave(group);
  525. dispatch_async(dispatch_get_main_queue(), ^{
  526. NSUInteger index = [self.uiMsgs indexOfObject:weakMyData];
  527. if (index != NSNotFound) {
  528. // if messageData exist In datasource, reload this data.
  529. [UIView performWithoutAnimation:^{
  530. @strongify(self);
  531. [self.dataSource dataProviderDataSourceWillChange:self];
  532. [self.dataSource dataProvider:self onRemoveHeightCache:weakMyData];
  533. [self.dataSource dataProviderDataSourceChange:self
  534. withType:TUIMessageBaseDataProviderDataSourceChangeTypeReload
  535. atIndex:index
  536. animation:NO];
  537. [self.dataSource dataProviderDataSourceDidChange:self];
  538. }];
  539. }
  540. });
  541. }];
  542. }
  543. });
  544. dispatch_group_notify(group, dispatch_get_main_queue(),
  545. ^{
  546. // complete
  547. });
  548. }
  549. - (void)deleteUIMsgs:(NSArray<TUIMessageCellData *> *)uiMsgs SuccBlock:(nullable V2TIMSucc)succ FailBlock:(nullable V2TIMFail)fail {
  550. NSMutableArray *uiMsgList = [NSMutableArray array];
  551. NSMutableArray *imMsgList = [NSMutableArray array];
  552. for (TUIMessageCellData *uiMsg in uiMsgs) {
  553. if ([self.uiMsgs containsObject:uiMsg]) {
  554. // Check content cell
  555. [uiMsgList addObject:uiMsg];
  556. [imMsgList addObject:uiMsg.innerMessage];
  557. // Check time cell which also need to be deleted
  558. NSInteger index = [self.uiMsgs indexOfObject:uiMsg];
  559. index--;
  560. if (index >= 0 && index < self.uiMsgs.count && [[self.uiMsgs objectAtIndex:index] isKindOfClass:TUISystemMessageCellData.class]) {
  561. TUISystemMessageCellData *systemCellData = (TUISystemMessageCellData *)[self.uiMsgs objectAtIndex:index];
  562. if (systemCellData.type == TUISystemMessageTypeDate) {
  563. [uiMsgList addObject:systemCellData];
  564. }
  565. }
  566. }
  567. }
  568. if (imMsgList.count == 0) {
  569. if (fail) {
  570. fail(ERR_INVALID_PARAMETERS, @"not found uiMsgs");
  571. }
  572. return;
  573. }
  574. @weakify(self);
  575. [self.class deleteMessages:imMsgList
  576. succ:^{
  577. @strongify(self);
  578. [self.dataSource dataProviderDataSourceWillChange:self];
  579. for (TUIMessageCellData *uiMsg in uiMsgList) {
  580. NSInteger index = [self.uiMsgs indexOfObject:uiMsg];
  581. [self.dataSource dataProviderDataSourceChange:self
  582. withType:TUIMessageBaseDataProviderDataSourceChangeTypeDelete
  583. atIndex:index
  584. animation:YES];
  585. }
  586. [self removeUIMsgList:uiMsgList];
  587. [self.dataSource dataProviderDataSourceDidChange:self];
  588. if (succ) {
  589. succ();
  590. }
  591. }
  592. fail:fail];
  593. }
  594. - (void)removeUIMsgList:(NSArray<TUIMessageCellData *> *)cellDatas {
  595. for (TUIMessageCellData *uiMsg in cellDatas) {
  596. [self removeUIMsg:uiMsg];
  597. }
  598. }
  599. #pragma mark - Utils
  600. + (nullable NSString *)getCustomBusinessID:(V2TIMMessage *)message {
  601. if (message == nil || message.customElem.data == nil) {
  602. return nil;
  603. }
  604. NSError *error = nil;
  605. NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data options:NSJSONReadingAllowFragments error:&error];
  606. if (error) {
  607. NSLog(@"parse customElem data error: %@", error);
  608. return nil;
  609. }
  610. if (!param || ![param isKindOfClass:[NSDictionary class]]) {
  611. return nil;
  612. }
  613. NSString *businessID = param[BussinessID];
  614. if ([businessID isKindOfClass:[NSString class]] && businessID.length > 0) {
  615. return businessID;
  616. } else {
  617. if ([param.allKeys containsObject:BussinessID_CustomerService]) {
  618. NSString *src = param[BussinessID_Src_CustomerService];
  619. if (src.length > 0 && [src isKindOfClass:[NSString class]]) {
  620. return [NSString stringWithFormat:@"%@%@", BussinessID_CustomerService, src];
  621. }
  622. }
  623. return nil;
  624. }
  625. }
  626. + (nullable NSString *)getSignalingBusinessID:(V2TIMSignalingInfo *)signalInfo {
  627. if (signalInfo.data == nil) {
  628. return nil;
  629. }
  630. NSError *error = nil;
  631. NSDictionary *param = [NSJSONSerialization JSONObjectWithData:[signalInfo.data dataUsingEncoding:NSUTF8StringEncoding]
  632. options:NSJSONReadingAllowFragments
  633. error:&error];
  634. if (error) {
  635. NSLog(@"parse customElem data error: %@", error);
  636. return nil;
  637. }
  638. if (!param || ![param isKindOfClass:[NSDictionary class]]) {
  639. return nil;
  640. }
  641. NSString *businessID = param[BussinessID];
  642. if (!businessID || ![businessID isKindOfClass:[NSString class]]) {
  643. return nil;
  644. }
  645. return businessID;
  646. }
  647. #pragma mark - TUICallKit
  648. static TUIChatCallingDataProvider *gCallingDataProvider;
  649. + (TUIChatCallingDataProvider *)callingDataProvider {
  650. if (gCallingDataProvider == nil) {
  651. gCallingDataProvider = [[TUIChatCallingDataProvider alloc] init];
  652. }
  653. return gCallingDataProvider;
  654. }
  655. + (TUIMessageCellData *)getCallingCellData:(id<TUIChatCallingInfoProtocol>)callingInfo {
  656. TMsgDirection direction = MsgDirectionIncoming;
  657. if (callingInfo.direction == TUICallMessageDirectionIncoming) {
  658. direction = MsgDirectionIncoming;
  659. } else if (callingInfo.direction == TUICallMessageDirectionOutgoing) {
  660. direction = MsgDirectionOutgoing;
  661. }
  662. if (callingInfo.participantType == TUICallParticipantTypeC2C) {
  663. TUITextMessageCellData *cellData = [[TUITextMessageCellData alloc] initWithDirection:direction];
  664. if (callingInfo.streamMediaType == TUICallStreamMediaTypeVoice) {
  665. cellData.isAudioCall = YES;
  666. } else if (callingInfo.streamMediaType == TUICallStreamMediaTypeVideo) {
  667. cellData.isVideoCall = YES;
  668. } else {
  669. cellData.isAudioCall = NO;
  670. cellData.isVideoCall = NO;
  671. }
  672. cellData.content = callingInfo.content;
  673. cellData.isCaller = (callingInfo.participantRole == TUICallParticipantRoleCaller);
  674. cellData.showUnreadPoint = callingInfo.showUnreadPoint;
  675. cellData.isUseMsgReceiverAvatar = callingInfo.isUseReceiverAvatar;
  676. cellData.reuseId = TTextMessageCell_ReuseId;
  677. return cellData;
  678. } else if (callingInfo.participantType == TUICallParticipantTypeGroup) {
  679. TUISystemMessageCellData *cellData = [[TUISystemMessageCellData alloc] initWithDirection:direction];
  680. cellData.content = callingInfo.content;
  681. cellData.replacedUserIDList = callingInfo.participantIDList;
  682. cellData.reuseId = TSystemMessageCell_ReuseId;
  683. return cellData;
  684. } else {
  685. return nil;
  686. }
  687. }
  688. @end