TUIMessageDataProvider.m 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  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. + (NSString *)getDisplayStringAboutCustom:(V2TIMMessage *)message {
  248. NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data options:NSJSONReadingAllowFragments error:nil];
  249. if (param == nil) {
  250. return nil;
  251. }
  252. NSString *contentStr = [TUIMessageDataProvider objectOrNilForKey:@"content" fromDictionary:param];
  253. if(contentStr.length > 0){
  254. return contentStr;
  255. }
  256. return message.customElem.desc;
  257. }
  258. + (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict
  259. {
  260. id object = [dict objectForKey:aKey];
  261. return [object isEqual:[NSNull null]] ? nil : object;
  262. }
  263. + (TUIMessageCellData *)getUnsupportedCellData:(V2TIMMessage *)message {
  264. TUITextMessageCellData *cellData = [[TUITextMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
  265. cellData.content = TIMCommonLocalizableString(TUIKitNotSupportThisMessage);
  266. cellData.reuseId = TTextMessageCell_ReuseId;
  267. return cellData;
  268. }
  269. + (nullable TUISystemMessageCellData *)getSystemCellData:(V2TIMMessage *)message {
  270. V2TIMGroupTipsElem *tip = message.groupTipsElem;
  271. NSString *opUserName = [self getOpUserName:tip.opMember];
  272. NSMutableArray<NSString *> *userNameList = [self getUserNameList:tip.memberList];
  273. NSMutableArray<NSString *> *userIDList = [self getUserIDList:tip.memberList];
  274. if (tip.type == V2TIM_GROUP_TIPS_TYPE_JOIN || tip.type == V2TIM_GROUP_TIPS_TYPE_INVITE || tip.type == V2TIM_GROUP_TIPS_TYPE_KICKED ||
  275. tip.type == V2TIM_GROUP_TIPS_TYPE_GROUP_INFO_CHANGE || tip.type == V2TIM_GROUP_TIPS_TYPE_QUIT ||
  276. tip.type == V2TIM_GROUP_TIPS_TYPE_PINNED_MESSAGE_ADDED || tip.type == V2TIM_GROUP_TIPS_TYPE_PINNED_MESSAGE_DELETED) {
  277. TUIJoinGroupMessageCellData *joinGroupData = [[TUIJoinGroupMessageCellData alloc] initWithDirection:MsgDirectionIncoming];
  278. joinGroupData.content = [self getDisplayString:message];
  279. joinGroupData.opUserName = opUserName;
  280. joinGroupData.opUserID = tip.opMember.userID;
  281. joinGroupData.userNameList = userNameList;
  282. joinGroupData.userIDList = userIDList;
  283. joinGroupData.reuseId = TJoinGroupMessageCell_ReuseId;
  284. return joinGroupData;
  285. } else {
  286. TUISystemMessageCellData *sysdata = [[TUISystemMessageCellData alloc] initWithDirection:MsgDirectionIncoming];
  287. sysdata.content = [self getDisplayString:message];
  288. sysdata.reuseId = TSystemMessageCell_ReuseId;
  289. if (sysdata.content.length) {
  290. return sysdata;
  291. }
  292. }
  293. return nil;
  294. }
  295. + (nullable TUISystemMessageCellData *)getRevokeCellData:(V2TIMMessage *)message {
  296. TUISystemMessageCellData *revoke = [[TUISystemMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
  297. revoke.reuseId = TSystemMessageCell_ReuseId;
  298. revoke.content = [self getRevokeDispayString:message];
  299. revoke.innerMessage = message;
  300. V2TIMUserFullInfo *revokerInfo = message.revokerInfo;
  301. if (message.isSelf) {
  302. if (message.elemType == V2TIM_ELEM_TYPE_TEXT && fabs([[NSDate date] timeIntervalSinceDate:message.timestamp]) < MaxReEditMessageDelay) {
  303. if (revokerInfo && ![revokerInfo.userID isEqualToString:message.sender]) {
  304. // Super User revoke
  305. revoke.supportReEdit = NO;
  306. } else {
  307. revoke.supportReEdit = YES;
  308. }
  309. }
  310. } else if (message.groupID.length > 0) {
  311. /**
  312. * 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
  313. * lowest priority.
  314. */
  315. NSString *userName = [TUIMessageDataProvider getShowName:message];
  316. TUIJoinGroupMessageCellData *joinGroupData = [[TUIJoinGroupMessageCellData alloc] initWithDirection:MsgDirectionIncoming];
  317. joinGroupData.content = [self getRevokeDispayString:message];
  318. joinGroupData.opUserID = message.sender;
  319. joinGroupData.opUserName = userName;
  320. joinGroupData.reuseId = TJoinGroupMessageCell_ReuseId;
  321. return joinGroupData;
  322. }
  323. return revoke;
  324. }
  325. + (nullable TUISystemMessageCellData *)getSystemMsgFromDate:(NSDate *)date {
  326. TUISystemMessageCellData *system = [[TUISystemMessageCellData alloc] initWithDirection:MsgDirectionOutgoing];
  327. system.content = [TUITool convertDateToStr:date];
  328. system.reuseId = TSystemMessageCell_ReuseId;
  329. system.type = TUISystemMessageTypeDate;
  330. return system;
  331. }
  332. #pragma mark - Last message parser
  333. + (void)asyncGetDisplayString:(NSArray<V2TIMMessage *> *)messageList callback:(void (^)(NSDictionary<NSString *, NSString *> *))callback {
  334. if (!callback) {
  335. return;
  336. }
  337. NSMutableDictionary *originDisplayMap = [NSMutableDictionary dictionary];
  338. NSMutableArray *cellDataList = [NSMutableArray array];
  339. for (V2TIMMessage *message in messageList) {
  340. TUIMessageCellData *cellData = [self getCellData:message];
  341. if (cellData) {
  342. [cellDataList addObject:cellData];
  343. }
  344. NSString *displayString = [self getDisplayString:message];
  345. if (displayString && message.msgID) {
  346. originDisplayMap[message.msgID] = displayString;
  347. }
  348. }
  349. if (cellDataList.count == 0) {
  350. callback(@{});
  351. return;
  352. }
  353. TUIMessageDataProvider *provider = [[TUIMessageDataProvider alloc] init];
  354. NSArray *additionUserIDList = [provider getUserIDListForAdditionalUserInfo:cellDataList];
  355. if (additionUserIDList.count == 0) {
  356. callback(@{});
  357. return;
  358. }
  359. NSMutableDictionary *result = [NSMutableDictionary dictionary];
  360. [provider
  361. requestForAdditionalUserInfo:cellDataList
  362. callback:^{
  363. for (TUIMessageCellData *cellData in cellDataList) {
  364. [cellData.additionalUserInfoResult
  365. enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, TUIRelationUserModel *_Nonnull obj, BOOL *_Nonnull stop) {
  366. NSString *str = [NSString stringWithFormat:@"{%@}", key];
  367. NSString *showName = obj.userID;
  368. if (obj.nameCard.length > 0) {
  369. showName = obj.nameCard;
  370. } else if (obj.friendRemark.length > 0) {
  371. showName = obj.friendRemark;
  372. } else if (obj.nickName.length > 0) {
  373. showName = obj.nickName;
  374. }
  375. NSString *displayString = [originDisplayMap objectForKey:cellData.msgID];
  376. if (displayString && [displayString containsString:str]) {
  377. displayString = [displayString stringByReplacingOccurrencesOfString:str withString:showName];
  378. result[cellData.msgID] = displayString;
  379. }
  380. callback(result);
  381. }];
  382. }
  383. }];
  384. }
  385. + (nullable NSString *)getDisplayString:(V2TIMMessage *)message {
  386. BOOL hasRiskContent = message.hasRiskContent;
  387. BOOL isRevoked = (message.status == V2TIM_MSG_STATUS_LOCAL_REVOKED);
  388. if (hasRiskContent && !isRevoked) {
  389. return TIMCommonLocalizableString(TUIKitMessageDisplayRiskContent);
  390. }
  391. NSString *str = [self parseDisplayStringFromMessageStatus:message];
  392. if (str == nil) {
  393. str = [self parseDisplayStringFromMessageElement:message];
  394. }
  395. if (str == nil) {
  396. NSLog(@"current message will be ignored in chat page or conversation list page, msg:%@", message);
  397. }
  398. return str;
  399. }
  400. + (nullable NSString *)parseDisplayStringFromMessageStatus:(V2TIMMessage *)message {
  401. NSString *str = nil;
  402. if (message.status == V2TIM_MSG_STATUS_LOCAL_REVOKED) {
  403. str = [self getRevokeDispayString:message];
  404. }
  405. return str;
  406. }
  407. + (nullable NSString *)parseDisplayStringFromMessageElement:(V2TIMMessage *)message {
  408. NSString *str = nil;
  409. switch (message.elemType) {
  410. case V2TIM_ELEM_TYPE_TEXT: {
  411. str = [TUITextMessageCellData getDisplayString:message];
  412. } break;
  413. case V2TIM_ELEM_TYPE_IMAGE: {
  414. str = [TUIImageMessageCellData getDisplayString:message];
  415. } break;
  416. case V2TIM_ELEM_TYPE_SOUND: {
  417. str = [TUIVoiceMessageCellData getDisplayString:message];
  418. } break;
  419. case V2TIM_ELEM_TYPE_VIDEO: {
  420. str = [TUIVideoMessageCellData getDisplayString:message];
  421. } break;
  422. case V2TIM_ELEM_TYPE_FILE: {
  423. str = [TUIFileMessageCellData getDisplayString:message];
  424. } break;
  425. case V2TIM_ELEM_TYPE_FACE: {
  426. str = [TUIFaceMessageCellData getDisplayString:message];
  427. } break;
  428. case V2TIM_ELEM_TYPE_MERGER: {
  429. str = [TUIMergeMessageCellData getDisplayString:message];
  430. } break;
  431. case V2TIM_ELEM_TYPE_GROUP_TIPS: {
  432. str = [self getGroupTipsDisplayString:message];
  433. } break;
  434. case V2TIM_ELEM_TYPE_CUSTOM: {
  435. str = [self getCustomDisplayString:message];
  436. } break;
  437. default:
  438. str = TIMCommonLocalizableString(TUIKitMessageTipsUnsupportCustomMessage);
  439. break;
  440. }
  441. return str;
  442. }
  443. + (nullable NSString *)getCustomDisplayString:(V2TIMMessage *)message {
  444. // ************************************************************************************
  445. // ************************************************************************************
  446. // ************** TUICallKit , TUICallKit *************
  447. // ************************************************************************************
  448. // ************************************************************************************
  449. NSString *str = nil;
  450. id<TUIChatCallingInfoProtocol> callingInfo = nil;
  451. if ([self.callingDataProvider isCallingMessage:message callingInfo:&callingInfo]) {
  452. // Voice-video-call message
  453. if (callingInfo) {
  454. // Supported voice-video-call message
  455. if (callingInfo.excludeFromHistory) {
  456. // This message will be ignore in chat page
  457. str = nil;
  458. } else {
  459. // Get display text
  460. str = callingInfo.content ?: TIMCommonLocalizableString(TUIKitMessageTipsUnsupportCustomMessage);
  461. }
  462. } else {
  463. // Unsupported voice-video-call message
  464. str = TIMCommonLocalizableString(TUIKitMessageTipsUnsupportCustomMessage);
  465. }
  466. return str;
  467. }
  468. // ************************************************************************************
  469. // ************************************************************************************
  470. // ************************************************************************************
  471. // ************************************************************************************
  472. NSString *businessID = nil;
  473. BOOL excludeFromHistory = NO;
  474. V2TIMSignalingInfo *signalingInfo = [V2TIMManager.sharedInstance getSignallingInfo:message];
  475. if (signalingInfo) {
  476. // This message is signaling message
  477. excludeFromHistory = message.isExcludedFromLastMessage && message.isExcludedFromUnreadCount;
  478. businessID = [self getSignalingBusinessID:signalingInfo];
  479. } else {
  480. // This message is normal custom message
  481. excludeFromHistory = NO;
  482. businessID = [self getCustomBusinessID:message];
  483. }
  484. if (excludeFromHistory) {
  485. // Return nil means not display int the chat page
  486. return nil;
  487. }
  488. if (businessID.length > 0) {
  489. Class cellDataClass = nil;
  490. if (gDataSourceClass && [gDataSourceClass respondsToSelector:@selector(onGetCustomMessageCellDataClass:)]) {
  491. cellDataClass = [gDataSourceClass onGetCustomMessageCellDataClass:businessID];
  492. }
  493. if (cellDataClass && [cellDataClass respondsToSelector:@selector(getDisplayString:)]) {
  494. return [cellDataClass getDisplayString:message];
  495. }
  496. // In CustomerService scenarios, unsupported messages are not displayed directly.
  497. if ([businessID tui_containsString:BussinessID_CustomerService]) {
  498. return nil;
  499. }
  500. NSString *tipStr = [self getDisplayStringAboutCustom:message];
  501. if(tipStr.length > 0){
  502. return tipStr;
  503. }
  504. return TIMCommonLocalizableString(TUIKitMessageTipsUnsupportCustomMessage);
  505. } else {
  506. return TIMCommonLocalizableString(TUIKitMessageTipsUnsupportCustomMessage);
  507. }
  508. }
  509. #pragma mark - Data source operate
  510. - (void)processQuoteMessage:(NSArray<TUIMessageCellData *> *)uiMsgs {
  511. if (uiMsgs.count == 0) {
  512. return;
  513. }
  514. @weakify(self);
  515. dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  516. dispatch_group_t group = dispatch_group_create();
  517. dispatch_group_async(group, concurrentQueue, ^{
  518. for (TUIMessageCellData *cellData in uiMsgs) {
  519. if (![cellData isKindOfClass:TUIReplyMessageCellData.class]) {
  520. continue;
  521. }
  522. TUIReplyMessageCellData *myData = (TUIReplyMessageCellData *)cellData;
  523. __weak typeof(myData) weakMyData = myData;
  524. myData.onFinish = ^{
  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 dataProviderDataSourceChange:self
  533. withType:TUIMessageBaseDataProviderDataSourceChangeTypeReload
  534. atIndex:index
  535. animation:NO];
  536. [self.dataSource dataProviderDataSourceDidChange:self];
  537. }];
  538. }
  539. });
  540. };
  541. dispatch_group_enter(group);
  542. [self loadOriginMessageFromReplyData:myData
  543. dealCallback:^{
  544. dispatch_group_leave(group);
  545. dispatch_async(dispatch_get_main_queue(), ^{
  546. NSUInteger index = [self.uiMsgs indexOfObject:weakMyData];
  547. if (index != NSNotFound) {
  548. // if messageData exist In datasource, reload this data.
  549. [UIView performWithoutAnimation:^{
  550. @strongify(self);
  551. [self.dataSource dataProviderDataSourceWillChange:self];
  552. [self.dataSource dataProvider:self onRemoveHeightCache:weakMyData];
  553. [self.dataSource dataProviderDataSourceChange:self
  554. withType:TUIMessageBaseDataProviderDataSourceChangeTypeReload
  555. atIndex:index
  556. animation:NO];
  557. [self.dataSource dataProviderDataSourceDidChange:self];
  558. }];
  559. }
  560. });
  561. }];
  562. }
  563. });
  564. dispatch_group_notify(group, dispatch_get_main_queue(),
  565. ^{
  566. // complete
  567. });
  568. }
  569. - (void)deleteUIMsgs:(NSArray<TUIMessageCellData *> *)uiMsgs SuccBlock:(nullable V2TIMSucc)succ FailBlock:(nullable V2TIMFail)fail {
  570. NSMutableArray *uiMsgList = [NSMutableArray array];
  571. NSMutableArray *imMsgList = [NSMutableArray array];
  572. for (TUIMessageCellData *uiMsg in uiMsgs) {
  573. if ([self.uiMsgs containsObject:uiMsg]) {
  574. // Check content cell
  575. [uiMsgList addObject:uiMsg];
  576. [imMsgList addObject:uiMsg.innerMessage];
  577. // Check time cell which also need to be deleted
  578. NSInteger index = [self.uiMsgs indexOfObject:uiMsg];
  579. index--;
  580. if (index >= 0 && index < self.uiMsgs.count && [[self.uiMsgs objectAtIndex:index] isKindOfClass:TUISystemMessageCellData.class]) {
  581. TUISystemMessageCellData *systemCellData = (TUISystemMessageCellData *)[self.uiMsgs objectAtIndex:index];
  582. if (systemCellData.type == TUISystemMessageTypeDate) {
  583. [uiMsgList addObject:systemCellData];
  584. }
  585. }
  586. }
  587. }
  588. if (imMsgList.count == 0) {
  589. if (fail) {
  590. fail(ERR_INVALID_PARAMETERS, @"not found uiMsgs");
  591. }
  592. return;
  593. }
  594. @weakify(self);
  595. [self.class deleteMessages:imMsgList
  596. succ:^{
  597. @strongify(self);
  598. [self.dataSource dataProviderDataSourceWillChange:self];
  599. for (TUIMessageCellData *uiMsg in uiMsgList) {
  600. NSInteger index = [self.uiMsgs indexOfObject:uiMsg];
  601. [self.dataSource dataProviderDataSourceChange:self
  602. withType:TUIMessageBaseDataProviderDataSourceChangeTypeDelete
  603. atIndex:index
  604. animation:YES];
  605. }
  606. [self removeUIMsgList:uiMsgList];
  607. [self.dataSource dataProviderDataSourceDidChange:self];
  608. if (succ) {
  609. succ();
  610. }
  611. }
  612. fail:fail];
  613. }
  614. - (void)removeUIMsgList:(NSArray<TUIMessageCellData *> *)cellDatas {
  615. for (TUIMessageCellData *uiMsg in cellDatas) {
  616. [self removeUIMsg:uiMsg];
  617. }
  618. }
  619. #pragma mark - Utils
  620. + (nullable NSString *)getCustomBusinessID:(V2TIMMessage *)message {
  621. if (message == nil || message.customElem.data == nil) {
  622. return nil;
  623. }
  624. NSError *error = nil;
  625. NSDictionary *param = [NSJSONSerialization JSONObjectWithData:message.customElem.data options:NSJSONReadingAllowFragments error:&error];
  626. if (error) {
  627. NSLog(@"parse customElem data error: %@", error);
  628. return nil;
  629. }
  630. if (!param || ![param isKindOfClass:[NSDictionary class]]) {
  631. return nil;
  632. }
  633. NSString *businessID = param[BussinessID];
  634. if ([businessID isKindOfClass:[NSString class]] && businessID.length > 0) {
  635. return businessID;
  636. } else {
  637. if ([param.allKeys containsObject:BussinessID_CustomerService]) {
  638. NSString *src = param[BussinessID_Src_CustomerService];
  639. if (src.length > 0 && [src isKindOfClass:[NSString class]]) {
  640. return [NSString stringWithFormat:@"%@%@", BussinessID_CustomerService, src];
  641. }
  642. }
  643. return nil;
  644. }
  645. }
  646. + (nullable NSString *)getSignalingBusinessID:(V2TIMSignalingInfo *)signalInfo {
  647. if (signalInfo.data == nil) {
  648. return nil;
  649. }
  650. NSError *error = nil;
  651. NSDictionary *param = [NSJSONSerialization JSONObjectWithData:[signalInfo.data dataUsingEncoding:NSUTF8StringEncoding]
  652. options:NSJSONReadingAllowFragments
  653. error:&error];
  654. if (error) {
  655. NSLog(@"parse customElem data error: %@", error);
  656. return nil;
  657. }
  658. if (!param || ![param isKindOfClass:[NSDictionary class]]) {
  659. return nil;
  660. }
  661. NSString *businessID = param[BussinessID];
  662. if (!businessID || ![businessID isKindOfClass:[NSString class]]) {
  663. return nil;
  664. }
  665. return businessID;
  666. }
  667. #pragma mark - TUICallKit
  668. static TUIChatCallingDataProvider *gCallingDataProvider;
  669. + (TUIChatCallingDataProvider *)callingDataProvider {
  670. if (gCallingDataProvider == nil) {
  671. gCallingDataProvider = [[TUIChatCallingDataProvider alloc] init];
  672. }
  673. return gCallingDataProvider;
  674. }
  675. + (TUIMessageCellData *)getCallingCellData:(id<TUIChatCallingInfoProtocol>)callingInfo {
  676. TMsgDirection direction = MsgDirectionIncoming;
  677. if (callingInfo.direction == TUICallMessageDirectionIncoming) {
  678. direction = MsgDirectionIncoming;
  679. } else if (callingInfo.direction == TUICallMessageDirectionOutgoing) {
  680. direction = MsgDirectionOutgoing;
  681. }
  682. if (callingInfo.participantType == TUICallParticipantTypeC2C) {
  683. TUITextMessageCellData *cellData = [[TUITextMessageCellData alloc] initWithDirection:direction];
  684. if (callingInfo.streamMediaType == TUICallStreamMediaTypeVoice) {
  685. cellData.isAudioCall = YES;
  686. } else if (callingInfo.streamMediaType == TUICallStreamMediaTypeVideo) {
  687. cellData.isVideoCall = YES;
  688. } else {
  689. cellData.isAudioCall = NO;
  690. cellData.isVideoCall = NO;
  691. }
  692. cellData.content = callingInfo.content;
  693. cellData.isCaller = (callingInfo.participantRole == TUICallParticipantRoleCaller);
  694. cellData.showUnreadPoint = callingInfo.showUnreadPoint;
  695. cellData.isUseMsgReceiverAvatar = callingInfo.isUseReceiverAvatar;
  696. cellData.reuseId = TTextMessageCell_ReuseId;
  697. return cellData;
  698. } else if (callingInfo.participantType == TUICallParticipantTypeGroup) {
  699. TUISystemMessageCellData *cellData = [[TUISystemMessageCellData alloc] initWithDirection:direction];
  700. cellData.content = callingInfo.content;
  701. cellData.replacedUserIDList = callingInfo.participantIDList;
  702. cellData.reuseId = TSystemMessageCell_ReuseId;
  703. return cellData;
  704. } else {
  705. return nil;
  706. }
  707. }
  708. @end