TUIBaseMessageController.m 86 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979
  1. //
  2. // TUIBaseMessageController.m
  3. // UIKit
  4. //
  5. // Created by annidyfeng on 2019/7/1.
  6. // Copyright © 2022 Tencent. All rights reserved.
  7. //
  8. #import "TUIBaseMessageController.h"
  9. #import <TIMCommon/TIMConfig.h>
  10. #import <TIMCommon/TIMDefine.h>
  11. #import <TIMCommon/TIMPopActionProtocol.h>
  12. #import <TIMCommon/TUISystemMessageCell.h>
  13. #import <TUICore/TUICore.h>
  14. #import <TUICore/TUIThemeManager.h>
  15. #import <TUICore/TUITool.h>
  16. #import <TUICore/TUILogin.h>
  17. #import <UIKit/UIWindow.h>
  18. #import "TUIChatCallingDataProvider.h"
  19. #import "TUIChatConversationModel.h"
  20. #import "TUIChatDataProvider.h"
  21. #import "TUIChatPopMenu.h"
  22. #import "TUIFaceMessageCell.h"
  23. #import "TUIFaceView.h"
  24. #import "TUIFileMessageCell.h"
  25. #import "TUIFileViewController.h"
  26. #import "TUIImageMessageCell.h"
  27. #import "TUIJoinGroupMessageCell.h"
  28. #import "TUILinkCell.h"
  29. #import "TUIMediaView.h"
  30. #import "TUIMergeMessageCell.h"
  31. #import "TUIMergeMessageListController.h"
  32. #import "TUIMessageDataProvider.h"
  33. #import "TUIMessageProgressManager.h"
  34. #import "TUIMessageReadViewController.h"
  35. #import "TUIOrderCell.h"
  36. #import "TUIReferenceMessageCell.h"
  37. #import "TUIRepliesDetailViewController.h"
  38. #import "TUIReplyMessageCell.h"
  39. #import "TUIReplyMessageCellData.h"
  40. #import "TUITextMessageCell.h"
  41. #import "TUIVideoMessageCell.h"
  42. #import "TUIVoiceMessageCell.h"
  43. #import "TUIMessageCellConfig.h"
  44. #import "MOSystemNormalCellData.h"
  45. #import "MOSystemNormalCell.h"
  46. @interface TUIBaseMessageController () <TUIMessageCellDelegate,
  47. TUIJoinGroupMessageCellDelegate,
  48. TUIMessageProgressManagerDelegate,
  49. TUIMessageDataProviderDataSource,
  50. TUINotificationProtocol,
  51. TIMPopActionProtocol,
  52. TUIMessageBaseDataProviderDataSource_Two>
  53. @property(nonatomic, strong) TUIMessageDataProvider *messageDataProvider;
  54. @property(nonatomic, strong) TUIMessageCellData *menuUIMsg;
  55. @property(nonatomic, strong) TUIMessageCellData *reSendUIMsg;
  56. @property(nonatomic, strong) TUIChatPopMenu *chatPopMenu;
  57. @property(nonatomic, strong) TUIChatConversationModel *conversationData;
  58. @property(nonatomic, strong) UIActivityIndicatorView *indicatorView;
  59. @property(nonatomic, assign) BOOL isActive;
  60. @property(nonatomic, assign) BOOL showCheckBox;
  61. @property(nonatomic, assign) BOOL scrollingTriggeredByUser;
  62. @property(nonatomic, assign) BOOL isAutoScrolledToBottom;
  63. @property(nonatomic, assign) BOOL hasCoverPage;
  64. @property(nonatomic, strong) TUIMessageCellConfig *messageCellConfig;
  65. @property(nonatomic, strong) TUIVoiceMessageCellData *currentVoiceMsg;
  66. @end
  67. @implementation TUIBaseMessageController
  68. + (void)initialize {
  69. [TUIMessageDataProvider setDataSourceClass:self];
  70. }
  71. + (void)asyncGetDisplayString:(NSArray<V2TIMMessage *> *)messageList callback:(void(^)(NSDictionary<NSString *, NSString *> *))callback {
  72. [TUIMessageDataProvider asyncGetDisplayString:messageList callback:callback];
  73. }
  74. + (nullable NSString *)getDisplayString:(V2TIMMessage *)message {
  75. return [TUIMessageDataProvider getDisplayString:message];
  76. }
  77. #pragma mark - Life Cycle
  78. - (void)viewDidLoad {
  79. [super viewDidLoad];
  80. [self setupViews];
  81. [self registerEvents];
  82. self.isActive = YES;
  83. [TUITool addUnsupportNotificationInVC:self];
  84. [TUIMessageProgressManager.shareManager addDelegate:self];
  85. CGFloat offset = Bottom_SafeHeight > 0 ? Bottom_SafeHeight : 20;
  86. self.tableView.contentInset = UIEdgeInsetsMake(0, 0, offset, 0);
  87. }
  88. - (void)dealloc {
  89. [[NSNotificationCenter defaultCenter] removeObserver:self];
  90. [TUIMessageProgressManager.shareManager removeDelegate:self];
  91. [TUICore unRegisterEventByObject:self];
  92. NSLog(@"%s dealloc", __FUNCTION__);
  93. }
  94. - (void)viewWillAppear:(BOOL)animated {
  95. self.isInVC = YES;
  96. [super viewWillAppear:animated];
  97. }
  98. - (void)viewDidAppear:(BOOL)animated {
  99. [super viewDidAppear:animated];
  100. [self sendVisibleReadGroupMessages];
  101. [self limitReadReport];
  102. }
  103. - (void)viewDidDisappear:(BOOL)animated {
  104. [super viewDidDisappear:animated];
  105. self.isInVC = NO;
  106. }
  107. - (void)viewWillDisappear:(BOOL)animated {
  108. [super viewWillDisappear:animated];
  109. if (_currentVoiceMsg) {
  110. [_currentVoiceMsg stopVoiceMessage];
  111. }
  112. }
  113. - (void)applicationBecomeActive {
  114. self.isActive = YES;
  115. [self sendVisibleReadGroupMessages];
  116. }
  117. - (void)applicationEnterBackground {
  118. self.isActive = NO;
  119. }
  120. - (void)setupViews {
  121. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapViewController)];
  122. /**
  123. * Solve the problem that the touch event is not passed down, causing the gesture to conflict with the collectionView didselect
  124. */
  125. tap.cancelsTouchesInView = NO;
  126. [self.view addGestureRecognizer:tap];
  127. self.tableView.scrollsToTop = NO;
  128. [self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
  129. self.tableView.backgroundColor = TUIChatDynamicColor(@"chat_controller_bg_color", @"#FFFFFF");
  130. self.indicatorView = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, TMessageController_Header_Height)];
  131. self.indicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
  132. self.tableView.tableHeaderView = self.indicatorView;
  133. if (!self.indicatorView.isAnimating) {
  134. [self.indicatorView startAnimating];
  135. }
  136. [self.messageCellConfig bindTableView:self.tableView];
  137. }
  138. - (void)registerEvents {
  139. [TUICore registerEvent:TUICore_TUIPluginNotify
  140. subKey:TUICore_TUIPluginNotify_PluginViewSizeChangedSubKey
  141. object:self];
  142. [TUICore registerEvent:TUICore_TUIPluginNotify
  143. subKey:TUICore_TUIPluginNotify_WillForwardTextSubKey
  144. object:self];
  145. [TUICore registerEvent:TUICore_TUIPluginNotify
  146. subKey:TUICore_TUIPluginNotify_DidChangePluginViewSubKey
  147. object:self];
  148. [[NSNotificationCenter defaultCenter] addObserver:self
  149. selector:@selector(applicationBecomeActive)
  150. name:UIApplicationDidBecomeActiveNotification
  151. object:nil];
  152. [[NSNotificationCenter defaultCenter] addObserver:self
  153. selector:@selector(applicationBecomeActive)
  154. name:UIApplicationWillEnterForegroundNotification
  155. object:nil];
  156. [[NSNotificationCenter defaultCenter] addObserver:self
  157. selector:@selector(applicationEnterBackground)
  158. name:UIApplicationDidEnterBackgroundNotification
  159. object:nil];
  160. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onReceivedSendMessageRequest:) name:TUIChatSendMessageNotification object:nil];
  161. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onReceivedSendMessageWithoutUpdateUIRequest:) name:TUIChatSendMessageWithoutUpdateUINotification object:nil];
  162. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onReceivedInsertMessageWithoutUpdateUIRequest:) name:TUIChatInsertMessageWithoutUpdateUINotification object:nil];
  163. }
  164. - (TUIMessageCellConfig *)messageCellConfig {
  165. if (_messageCellConfig == nil) {
  166. _messageCellConfig = [[TUIMessageCellConfig alloc] init];
  167. }
  168. return _messageCellConfig;
  169. }
  170. #pragma mark - Data Provider
  171. - (void)setConversation:(TUIChatConversationModel *)conversationData {
  172. self.conversationData = conversationData;
  173. if (!self.messageDataProvider) {
  174. self.messageDataProvider = [[TUIMessageDataProvider alloc] initWithConversationModel:conversationData];
  175. self.messageDataProvider.dataSource = self;
  176. self.messageDataProvider.dataSource_Two = self;
  177. }
  178. [self loadMessage];
  179. [self loadGroupInfo];
  180. }
  181. - (void)loadMessage {
  182. if (self.messageDataProvider.isLoadingData || self.messageDataProvider.isNoMoreMsg) {
  183. return;
  184. }
  185. __weak typeof(self) weakSelf = self;
  186. [self.messageDataProvider
  187. loadMessageSucceedBlock:^(BOOL isFirstLoad, BOOL isNoMoreMsg, NSArray<TUIMessageCellData *> *_Nonnull newMsgs) {
  188. if (isNoMoreMsg) {
  189. weakSelf.indicatorView.mm_h = 0;
  190. }
  191. if (newMsgs.count != 0) {
  192. [weakSelf.tableView reloadData];
  193. [weakSelf.tableView layoutIfNeeded];
  194. if (isFirstLoad) {
  195. [weakSelf scrollToBottom:NO];
  196. } else {
  197. CGFloat visibleHeight = 0;
  198. for (NSInteger i = 0; i < newMsgs.count; ++i) {
  199. NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
  200. visibleHeight += [weakSelf tableView:weakSelf.tableView heightForRowAtIndexPath:indexPath];
  201. }
  202. if (isNoMoreMsg) {
  203. visibleHeight -= TMessageController_Header_Height;
  204. }
  205. [weakSelf.tableView scrollRectToVisible:CGRectMake(0, weakSelf.tableView.contentOffset.y + visibleHeight, weakSelf.tableView.frame.size.width,
  206. weakSelf.tableView.frame.size.height)
  207. animated:NO];
  208. }
  209. }
  210. }
  211. FailBlock:^(int code, NSString *desc) {
  212. [TUITool makeToastError:code msg:desc];
  213. }];
  214. }
  215. - (void)loadGroupInfo {
  216. if (self.conversationData.groupID.length > 0) {
  217. __weak typeof(self) weakSelf = self;
  218. [self.messageDataProvider getPinMessageList];
  219. [self.messageDataProvider loadGroupInfo:^{
  220. [weakSelf.messageDataProvider getSelfInfoInGroup:^{}];
  221. }];
  222. self.messageDataProvider.groupRoleChanged = ^(V2TIMGroupMemberRole role) {
  223. if (weakSelf.groupRoleChanged) {
  224. weakSelf.groupRoleChanged(role);
  225. }
  226. };
  227. self.messageDataProvider.pinGroupMessageChanged = ^(NSArray * _Nonnull groupPinList) {
  228. if (weakSelf.pinGroupMessageChanged) {
  229. weakSelf.pinGroupMessageChanged(groupPinList);
  230. }
  231. };
  232. }
  233. }
  234. - (void)clearUImsg {
  235. [self.messageDataProvider clearUIMsgList];
  236. [self.tableView reloadData];
  237. [self.tableView layoutIfNeeded];
  238. if (self.indicatorView.isAnimating) {
  239. [self.indicatorView stopAnimating];
  240. }
  241. }
  242. - (void)reloadAndScrollToBottomOfMessage:(NSString *)messageID needScroll:(BOOL)isNeedScroll {
  243. // Dispatch the task to RunLoop to ensure that they are executed after the UITableView refresh is complete.
  244. dispatch_async(dispatch_get_main_queue(), ^{
  245. [self reloadCellOfMessage:messageID];
  246. dispatch_async(dispatch_get_main_queue(), ^{
  247. if (isNeedScroll) {
  248. [self scrollCellToBottomOfMessage:messageID];
  249. }
  250. });
  251. });
  252. }
  253. - (void)reloadAndScrollToBottomOfMessage:(NSString *)messageID {
  254. [self reloadAndScrollToBottomOfMessage:messageID needScroll:YES];
  255. }
  256. - (void)reloadCellOfMessage:(NSString *)messageID {
  257. NSIndexPath *indexPath = [self indexPathOfMessage:messageID];
  258. // Disable animation when loading to avoid cell jumping.
  259. if (indexPath == nil) {
  260. return;
  261. }
  262. [UIView performWithoutAnimation:^{
  263. [self.tableView reloadRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationNone];
  264. }];
  265. }
  266. - (void)scrollCellToBottomOfMessage:(NSString *)messageID {
  267. if (self.hasCoverPage) {
  268. return;
  269. }
  270. NSIndexPath *indexPath = [self indexPathOfMessage:messageID];
  271. // Scroll the tableView only if the bottom of the cell is invisible.
  272. CGRect cellRect = [self.tableView rectForRowAtIndexPath:indexPath];
  273. CGRect tableViewRect = self.tableView.bounds;
  274. BOOL isBottomInvisible = (cellRect.origin.y < CGRectGetMaxY(tableViewRect) && CGRectGetMaxY(cellRect) > CGRectGetMaxY(tableViewRect)) ||
  275. (cellRect.origin.y >= CGRectGetMaxY(tableViewRect));
  276. if (isBottomInvisible) {
  277. [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
  278. }
  279. if (self.isAutoScrolledToBottom) {
  280. [self scrollToBottom:YES];
  281. }
  282. }
  283. - (NSIndexPath *)indexPathOfMessage:(NSString *)messageID {
  284. for (int i = 0; i < self.messageDataProvider.uiMsgs.count; i++) {
  285. TUIMessageCellData *data = self.messageDataProvider.uiMsgs[i];
  286. if ([data.innerMessage.msgID isEqualToString:messageID]) {
  287. return [NSIndexPath indexPathForRow:i inSection:0];
  288. }
  289. }
  290. return nil;
  291. }
  292. #pragma mark - Event Response
  293. - (void)scrollToBottom:(BOOL)animate {
  294. // Do not call this interface frequently in a short period of time, as it will affect the UI experience.
  295. if (self.messageDataProvider.uiMsgs.count > 0) {
  296. NSIndexPath *bottom = [NSIndexPath indexPathForRow:self.messageDataProvider.uiMsgs.count - 1 inSection:0];
  297. [self.tableView scrollToRowAtIndexPath:bottom atScrollPosition:UITableViewScrollPositionBottom animated:animate];
  298. self.isAutoScrolledToBottom = YES;
  299. }
  300. }
  301. - (void)didTapViewController {
  302. if (self.delegate && [self.delegate respondsToSelector:@selector(didTapInMessageController:)]) {
  303. [self.delegate didTapInMessageController:self];
  304. }
  305. }
  306. - (void)sendPlaceHolderUIMessage:(TUIMessageCellData *)cellData {
  307. [self.messageDataProvider sendPlaceHolderUIMessage:cellData];
  308. [self scrollToBottom:YES];
  309. }
  310. - (void)sendUIMessage:(TUIMessageCellData *)cellData {
  311. @weakify(self);
  312. cellData.innerMessage.needReadReceipt = self.isMsgNeedReadReceipt;
  313. [self.messageDataProvider sendUIMsg:cellData
  314. toConversation:self.conversationData
  315. willSendBlock:^(BOOL isReSend, TUIMessageCellData *_Nonnull dateUIMsg) {
  316. @strongify(self);
  317. if ([cellData isKindOfClass:[TUIVideoMessageCellData class]]||
  318. [cellData isKindOfClass:[TUIImageMessageCellData class]]) {
  319. dispatch_async(dispatch_get_main_queue(), ^{
  320. [self scrollToBottom:YES];
  321. });
  322. } else {
  323. [self scrollToBottom:YES];
  324. }
  325. [self setUIMessageStatus:cellData status:Msg_Status_Sending_2];
  326. }
  327. SuccBlock:^{
  328. @strongify(self);
  329. [self reloadUIMessage:cellData];
  330. [self setUIMessageStatus:cellData status:Msg_Status_Succ];
  331. NSDictionary *param = @{
  332. TUICore_TUIChatNotify_SendMessageSubKey_Code : @0,
  333. TUICore_TUIChatNotify_SendMessageSubKey_Desc : @"",
  334. TUICore_TUIChatNotify_SendMessageSubKey_Message : cellData.innerMessage
  335. };
  336. [TUICore notifyEvent:TUICore_TUIChatNotify subKey:TUICore_TUIChatNotify_SendMessageSubKey object:self param:param];
  337. }
  338. FailBlock:^(int code, NSString *desc) {
  339. @strongify(self);
  340. [self reloadUIMessage:cellData];
  341. [self setUIMessageStatus:cellData status:Msg_Status_Fail];
  342. [self makeSendErrorHud:code desc:desc];
  343. NSDictionary *param = @{TUICore_TUIChatNotify_SendMessageSubKey_Code : @(code),
  344. TUICore_TUIChatNotify_SendMessageSubKey_Desc : desc};
  345. [TUICore notifyEvent:TUICore_TUIChatNotify subKey:TUICore_TUIChatNotify_SendMessageSubKey object:self param:param];
  346. }];
  347. }
  348. - (void)setUIMessageStatus:(TUIMessageCellData *)cellData status:(TMsgStatus)status {
  349. switch (status) {
  350. case Msg_Status_Init:
  351. case Msg_Status_Succ:
  352. case Msg_Status_Fail:
  353. {
  354. [self changeMsg:cellData status:status];
  355. }
  356. break;
  357. case Msg_Status_Sending:
  358. case Msg_Status_Sending_2:
  359. {
  360. int delay = 1;
  361. if ([cellData isKindOfClass:[TUIImageMessageCellData class]] ||
  362. [cellData isKindOfClass:[TUIVideoMessageCellData class]]) {
  363. delay = 0;
  364. }
  365. if (0 == delay) {
  366. [self changeMsg:cellData status:Msg_Status_Sending_2];
  367. } else {
  368. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  369. if (cellData.innerMessage.status == V2TIM_MSG_STATUS_SENDING) {
  370. [self changeMsg:cellData status:Msg_Status_Sending_2];
  371. }
  372. });
  373. }
  374. }
  375. break;
  376. default:
  377. break;
  378. }
  379. }
  380. - (void)makeSendErrorHud:(int)code desc:(NSString *)desc {
  381. // The text or image msg is sensitive, the cell height may change.
  382. if (code == 80001 || code == 80004) {
  383. [self scrollToBottom:YES];
  384. return;
  385. }
  386. NSString *errorMsg = @"";
  387. if (self.isMsgNeedReadReceipt && code == ERR_SDK_INTERFACE_NOT_SUPPORT) {
  388. errorMsg = [NSString stringWithFormat:@"%@%@", TUIKitLocalizableString(TUIKitErrorUnsupportIntefaceMessageRead),
  389. TUIKitLocalizableString(TUIKitErrorUnsupporInterfaceSuffix)];
  390. } else {
  391. errorMsg = [TUITool convertIMError:code msg:desc];
  392. }
  393. UIAlertController *ac = [UIAlertController alertControllerWithTitle:errorMsg message:nil preferredStyle:UIAlertControllerStyleAlert];
  394. [ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(Confirm) style:UIAlertActionStyleDefault handler:nil]];
  395. [self presentViewController:ac animated:YES completion:nil];
  396. }
  397. - (void)sendMessage:(V2TIMMessage *)message {
  398. [self sendMessage:message placeHolderCellData:nil];
  399. }
  400. - (void)sendMessage:(V2TIMMessage *)message placeHolderCellData:(TUIMessageCellData *)placeHolderCellData {
  401. TUIMessageCellData *cellData = nil;
  402. if (message.elemType == V2TIM_ELEM_TYPE_CUSTOM) {
  403. cellData = [self.delegate messageController:self onNewMessage:message];
  404. cellData.innerMessage = message;
  405. }
  406. if (!cellData) {
  407. cellData = [TUIMessageDataProvider getCellData:message];
  408. }
  409. if (cellData) {
  410. cellData.placeHolderCellData = placeHolderCellData;
  411. cellData.identifier = [TUILogin getUserID];
  412. NSString *faceUrl = [TUILogin getFaceUrl];
  413. if (faceUrl.length > 0) {
  414. cellData.avatarUrl = [NSURL URLWithString:faceUrl];
  415. }
  416. [self sendUIMessage:cellData];
  417. }
  418. }
  419. - (void)reloadUIMessage:(TUIMessageCellData *)msg {
  420. // innerMessage maybe changed, reload it
  421. NSInteger index = [self.messageDataProvider.uiMsgs indexOfObject:msg];
  422. NSMutableArray *newUIMsgs = [self.messageDataProvider transUIMsgFromIMMsg:@[ msg.innerMessage ]];
  423. if (newUIMsgs.count == 0) {
  424. return;
  425. }
  426. TUIMessageCellData *newUIMsg = newUIMsgs.firstObject;
  427. @weakify(self)
  428. [self.messageDataProvider preProcessMessage:@[ newUIMsg ]
  429. callback:^{
  430. @strongify(self)
  431. [UIView performWithoutAnimation:^{
  432. [self.messageDataProvider replaceUIMsg:newUIMsg atIndex:index];
  433. [self.tableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:index inSection:0]]
  434. withRowAnimation:UITableViewRowAnimationNone];
  435. }];
  436. }];
  437. }
  438. - (void)changeMsg:(TUIMessageCellData *)msg status:(TMsgStatus)status {
  439. msg.status = status;
  440. NSInteger index = [self.messageDataProvider.uiMsgs indexOfObject:msg];
  441. if ([self.tableView numberOfRowsInSection:0] > index) {
  442. TUIMessageCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]];
  443. [cell fillWithData:msg];
  444. } else {
  445. NSLog(@"lack of cell");
  446. }
  447. [[NSNotificationCenter defaultCenter] postNotificationName:@"kTUINotifyMessageStatusChanged"
  448. object:nil
  449. userInfo:@{
  450. @"msg" : msg,
  451. @"status" : [NSNumber numberWithUnsignedInteger:status],
  452. @"msgSender" : self,
  453. }];
  454. }
  455. - (void)onReceivedSendMessageRequest:(NSNotification *)notification {
  456. NSDictionary *userInfo = notification.userInfo;
  457. if (!userInfo) {
  458. return;
  459. }
  460. V2TIMMessage *message = [userInfo objectForKey:TUICore_TUIChatService_SendMessageMethod_MsgKey];
  461. TUIMessageCellData *cellData = [userInfo objectForKey:TUICore_TUIChatService_SendMessageMethod_PlaceHolderUIMsgKey];
  462. if (cellData && !message) {
  463. [self sendPlaceHolderUIMessage:cellData];
  464. } else if (message) {
  465. [self sendMessage:message placeHolderCellData:cellData];
  466. }
  467. }
  468. - (void)onReceivedSendMessageWithoutUpdateUIRequest:(NSNotification *)notification {
  469. NSDictionary *userInfo = notification.userInfo;
  470. if (userInfo == nil) {
  471. return;
  472. }
  473. V2TIMMessage *message = [userInfo objectForKey:TUICore_TUIChatService_SendMessageMethodWithoutUpdateUI_MsgKey];
  474. if (message == nil) {
  475. return;
  476. }
  477. TUISendMessageAppendParams *param = [TUISendMessageAppendParams new];
  478. param.isOnlineUserOnly = YES;
  479. [TUIMessageDataProvider sendMessage:message
  480. toConversation:self.conversationData
  481. appendParams:param
  482. Progress:nil
  483. SuccBlock:^{
  484. NSLog(@"send message without updating UI succeed");
  485. }
  486. FailBlock:^(int code, NSString *desc) {
  487. NSLog(@"send message without updating UI failed, code: %d, desc: %@", code, desc);
  488. }];
  489. }
  490. - (void)onReceivedInsertMessageWithoutUpdateUIRequest:(NSNotification *)notification {
  491. NSDictionary *userInfo = notification.userInfo;
  492. if (userInfo == nil) {
  493. return;
  494. }
  495. V2TIMMessage *message = [userInfo objectForKey:@"message"];
  496. BOOL isNeedScrollToBottom = [userInfo objectForKey:@"needScrollToBottom"];
  497. if (message == nil) {
  498. return;
  499. }
  500. NSMutableArray *newUIMsgs = [self.messageDataProvider transUIMsgFromIMMsg:@[ message ]];
  501. if (newUIMsgs.count == 0) {
  502. return;
  503. }
  504. TUIMessageCellData *newUIMsg = newUIMsgs.firstObject;
  505. @weakify(self)
  506. [self.messageDataProvider preProcessMessage:@[ newUIMsg ]
  507. callback:^{
  508. @strongify(self)
  509. [UIView performWithoutAnimation:^{
  510. [self.tableView beginUpdates];
  511. @autoreleasepool {
  512. for (TUIMessageCellData *uiMsg in newUIMsgs) {
  513. [self.messageDataProvider addUIMsg:uiMsg];
  514. [self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:self.messageDataProvider.uiMsgs.count -1 inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
  515. }
  516. }
  517. [self.tableView endUpdates];
  518. if (isNeedScrollToBottom) {
  519. [self scrollToBottom:YES];
  520. }
  521. }];
  522. }];
  523. }
  524. #pragma mark - TUINotificationProtocol
  525. - (void)onNotifyEvent:(NSString *)key subKey:(NSString *)subKey object:(id)anObject param:(NSDictionary *)param {
  526. if ([key isEqualToString:TUICore_TUIPluginNotify] && [subKey isEqualToString:TUICore_TUIPluginNotify_PluginViewSizeChangedSubKey]) {
  527. V2TIMMessage *message = param[TUICore_TUIPluginNotify_PluginViewSizeChangedSubKey_Message];
  528. for (TUIMessageCellData *data in self.messageDataProvider.uiMsgs) {
  529. if (data.innerMessage == message) {
  530. [self.messageCellConfig removeHeightCacheOfMessageCellData:data];
  531. [self reloadAndScrollToBottomOfMessage:data.innerMessage.msgID];
  532. NSIndexPath *indexPath = [self indexPathOfMessage:data.innerMessage.msgID];
  533. [self.tableView beginUpdates];
  534. [self tableView:self.tableView heightForRowAtIndexPath:indexPath];
  535. [self.tableView endUpdates];
  536. break;
  537. }
  538. }
  539. } else if ([key isEqualToString: TUICore_TUIPluginNotify] && [subKey isEqualToString:TUICore_TUIPluginNotify_DidChangePluginViewSubKey]) {
  540. // Plugin View is Shown or content changed.
  541. TUIMessageCellData *data = param[TUICore_TUIPluginNotify_DidChangePluginViewSubKey_Data];
  542. BOOL isAllowScroll2Bottom = YES;
  543. if ([param[TUICore_TUIPluginNotify_DidChangePluginViewSubKey_isAllowScroll2Bottom] isEqualToString:@"0"] ) {
  544. isAllowScroll2Bottom = NO ;
  545. TUIMessageCellData *lasData = [self.messageDataProvider.uiMsgs lastObject];
  546. BOOL isInBottomPage = (self.tableView.contentSize.height - self.tableView.contentOffset.y
  547. <= Screen_Height);
  548. if ([lasData.msgID isEqualToString:data.msgID] && isInBottomPage) {
  549. isAllowScroll2Bottom = YES;
  550. }
  551. }
  552. [self.messageCellConfig removeHeightCacheOfMessageCellData:data];
  553. [self reloadAndScrollToBottomOfMessage:data.innerMessage.msgID needScroll:isAllowScroll2Bottom];
  554. } else if ([key isEqualToString:TUICore_TUIPluginNotify] && [subKey isEqualToString:TUICore_TUIPluginNotify_WillForwardTextSubKey]) {
  555. // Text will be forwarded.
  556. NSString *text = param[TUICore_TUIPluginNotify_WillForwardTextSubKey_Text];
  557. if (self.delegate && [self.delegate respondsToSelector:@selector(messageController:onForwardText:)]) {
  558. [self.delegate messageController:self onForwardText:text];
  559. }
  560. }
  561. }
  562. #pragma mark - TUIMessageProgressManagerDelegate
  563. - (void)onMessageSendingResultChanged:(TUIMessageSendingResultType)type messageID:(NSString *)msgID {
  564. // async
  565. __weak typeof(self) weakSelf = self;
  566. dispatch_async(dispatch_get_main_queue(), ^{
  567. for (TUIMessageCellData *cellData in weakSelf.messageDataProvider.uiMsgs) {
  568. if ([cellData.msgID isEqual:msgID]) {
  569. [weakSelf changeMsg:cellData status:(type == TUIMessageSendingResultTypeSucc) ? Msg_Status_Succ : Msg_Status_Fail];
  570. }
  571. }
  572. });
  573. }
  574. #pragma mark - TUIMessageBaseDataProviderDataSource
  575. + (Class)onGetCustomMessageCellDataClass:(NSString *)businessID {
  576. return [TUIMessageCellConfig getCustomMessageCellDataClass:businessID];
  577. }
  578. - (BOOL)isDataSourceConsistent {
  579. NSInteger dataSourceCount = self.messageDataProvider.uiMsgs.count;
  580. NSInteger tableViewCount = [self.tableView numberOfRowsInSection:0];
  581. if (dataSourceCount != tableViewCount) {
  582. NSLog(@"Data source and UI are inconsistent: Data source count = %ld, Table view count = %ld", (long)dataSourceCount, (long)tableViewCount);
  583. return NO;
  584. }
  585. return YES;
  586. }
  587. - (void)dataProviderDataSourceWillChange:(TUIMessageDataProvider *)dataProvider {
  588. [self.tableView beginUpdates];
  589. }
  590. - (void)dataProviderDataSourceChange:(TUIMessageDataProvider *)dataProvider
  591. withType:(TUIMessageBaseDataProviderDataSourceChangeType)type
  592. atIndex:(NSUInteger)index
  593. animation:(BOOL)animation {
  594. switch (type) {
  595. case TUIMessageBaseDataProviderDataSourceChangeTypeInsert:
  596. [self.tableView insertRowsAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ]
  597. withRowAnimation:animation ? UITableViewRowAnimationFade : UITableViewRowAnimationNone];
  598. break;
  599. case TUIMessageBaseDataProviderDataSourceChangeTypeDelete:
  600. [self.tableView deleteRowsAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ]
  601. withRowAnimation:animation ? UITableViewRowAnimationFade : UITableViewRowAnimationNone];
  602. break;
  603. case TUIMessageBaseDataProviderDataSourceChangeTypeReload:
  604. [self.tableView reloadRowsAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ]
  605. withRowAnimation:animation ? UITableViewRowAnimationFade : UITableViewRowAnimationNone];
  606. break;
  607. default:
  608. break;
  609. }
  610. }
  611. - (void)dataProviderDataSourceDidChange:(TUIMessageDataProvider *)dataProvider {
  612. [self.tableView endUpdates];
  613. }
  614. - (void)dataProvider:(TUIMessageBaseDataProvider *)dataProvider onRemoveHeightCache:(TUIMessageCellData *)cellData {
  615. if (cellData) {
  616. [self.messageCellConfig removeHeightCacheOfMessageCellData:cellData];
  617. }
  618. }
  619. - (nullable TUIMessageCellData *)dataProvider:(TUIMessageDataProvider *)dataProvider CustomCellDataFromNewIMMessage:(V2TIMMessage *)msg {
  620. if (![msg.userID isEqualToString:self.conversationData.userID] && ![msg.groupID isEqualToString:self.conversationData.groupID]) {
  621. return nil;
  622. }
  623. if (msg.status == V2TIM_MSG_STATUS_LOCAL_REVOKED) {
  624. return nil;
  625. }
  626. if(msg.elemType == V2TIM_ELEM_TYPE_CUSTOM){
  627. NSDictionary *param = [NSJSONSerialization JSONObjectWithData:msg.customElem.data options:NSJSONReadingAllowFragments error:nil];
  628. if (param) {
  629. NSInteger type = [[MOSystemNormalCellData objectOrNilForKey:@"type" fromDictionary:param] integerValue];
  630. if(type > 0){
  631. TUIMessageCellData *cellData = [MOSystemNormalCellData getCellData:msg];
  632. if(cellData){
  633. return cellData;
  634. }
  635. }
  636. }
  637. }
  638. if ([self.delegate respondsToSelector:@selector(messageController:onNewMessage:)]) {
  639. TUIMessageCellData *customCellData = [self.delegate messageController:self onNewMessage:msg];
  640. if (customCellData) {
  641. customCellData.innerMessage = msg;
  642. return customCellData;
  643. }
  644. }
  645. return nil;
  646. }
  647. - (nullable V2TIMMessage *)dataProvider_Two:(TUIMessageBaseDataProvider *)dataProvider CustomCellDataFromNewIMMessage:(V2TIMMessage *)msg{
  648. if([self.delegate_Two respondsToSelector:@selector(messageController_Two:onNewMessage:)]) {
  649. V2TIMMessage *newMessage = [self.delegate_Two messageController_Two:self onNewMessage:msg];
  650. if(newMessage){
  651. return newMessage;
  652. }
  653. }
  654. return nil;
  655. }
  656. - (void)dataProvider:(TUIMessageDataProvider *)dataProvider ReceiveReadMsgWithUserID:(NSString *)userId Time:(time_t)timestamp {
  657. if (userId.length > 0 && [userId isEqualToString:self.conversationData.userID]) {
  658. for (int i = 0; i < self.messageDataProvider.uiMsgs.count; i++) {
  659. NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.messageDataProvider.uiMsgs.count - 1 - i inSection:0];
  660. TUIMessageCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
  661. /**
  662. *
  663. * Determine whether the current unread needs to be changed to read by the callback timestamp
  664. */
  665. time_t msgTime = [cell.messageData.innerMessage.timestamp timeIntervalSince1970];
  666. if (msgTime <= timestamp && ![cell.readReceiptLabel.text isEqualToString:TIMCommonLocalizableString(Read)]) {
  667. cell.readReceiptLabel.text = TIMCommonLocalizableString(Read);
  668. }
  669. }
  670. }
  671. }
  672. - (void)dataProvider:(TUIMessageDataProvider *)dataProvider
  673. ReceiveReadMsgWithGroupID:(NSString *)groupID
  674. msgID:(NSString *)msgID
  675. readCount:(NSUInteger)readCount
  676. unreadCount:(NSUInteger)unreadCount {
  677. if (groupID != nil && ![groupID isEqualToString:self.conversationData.groupID]) {
  678. return;
  679. }
  680. NSInteger row = [self.messageDataProvider getIndexOfMessage:msgID];
  681. if (row < 0 || row >= self.messageDataProvider.uiMsgs.count) {
  682. return;
  683. }
  684. NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
  685. TUIMessageCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
  686. [cell updateReadLabelText];
  687. }
  688. - (void)dataProvider:(TUIMessageDataProvider *)dataProvider ReceiveNewUIMsg:(TUIMessageCellData *)uiMsg {
  689. /**
  690. * When viewing historical messages, judge whether you need to slide to the bottom according to the current contentOffset
  691. */
  692. if (self.tableView.contentSize.height - self.tableView.contentOffset.y < Screen_Height * 1.5) {
  693. [self scrollToBottom:YES];
  694. if (self.isInVC && self.isActive) {
  695. [self.messageDataProvider sendLatestMessageReadReceipt];
  696. }
  697. }
  698. [self limitReadReport];
  699. }
  700. - (void)dataProvider:(TUIMessageDataProvider *)dataProvider ReceiveRevokeUIMsg:(TUIMessageCellData *)uiMsg {
  701. return;
  702. }
  703. #pragma mark - Private
  704. - (void)limitReadReport {
  705. static uint64_t lastTs = 0;
  706. uint64_t curTs = [[NSDate date] timeIntervalSince1970];
  707. /**
  708. * More than 1s && Not the first time, report immediately
  709. */
  710. if (curTs - lastTs >= 1 && lastTs) {
  711. lastTs = curTs;
  712. [self readReport];
  713. } else {
  714. /**
  715. * Less than 1s || First time, delay 1s and merge report
  716. */
  717. static BOOL delayReport = NO;
  718. if (delayReport) {
  719. return;
  720. }
  721. delayReport = YES;
  722. __weak typeof(self) weakSelf = self;
  723. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  724. [weakSelf readReport];
  725. delayReport = NO;
  726. });
  727. }
  728. }
  729. - (void)readReport {
  730. if (self.isInVC && self.isActive) {
  731. NSString *userID = self.conversationData.userID;
  732. if (userID.length > 0) {
  733. [TUIMessageDataProvider markC2CMessageAsRead:userID succ:nil fail:nil];
  734. }
  735. NSString *groupID = self.conversationData.groupID;
  736. if (groupID.length > 0) {
  737. [TUIMessageDataProvider markGroupMessageAsRead:groupID succ:nil fail:nil];
  738. }
  739. NSString *conversationID = @"";
  740. if (IS_NOT_EMPTY_NSSTRING(userID)) {
  741. conversationID = [NSString stringWithFormat:@"c2c_%@", userID];
  742. }
  743. if (IS_NOT_EMPTY_NSSTRING(groupID)) {
  744. conversationID = [NSString stringWithFormat:@"group_%@", groupID];
  745. }
  746. if (IS_NOT_EMPTY_NSSTRING(self.conversationData.conversationID)) {
  747. conversationID = self.conversationData.conversationID;
  748. }
  749. if (conversationID.length > 0) {
  750. [TUIMessageDataProvider markConversationAsUndead:@[ conversationID ] enableMark:NO];
  751. }
  752. }
  753. }
  754. /**
  755. * When the receiver sends a visible message read receipt:
  756. * 1. The time when messageVC is visible. You will be notified when [self viewDidAppear:] is invoked
  757. * 2. The time when scrollview scrolled to bottom by called [self scrollToBottom:] (For example, click the "x new message" tips in the lower right corner). You
  758. * will be notified when [UIScrollViewDelegate scrollViewDidEndScrollingAnimation:] is invoked.
  759. * + Note that you need to use the state of the scrollView to accurately determine whether the scrollView has really stopped sliding.
  760. * 3. The time when the user drags the scrollView continuously to view the message. You will be notified when [UIScrollViewDelegate scrollViewDidScroll:] is
  761. * invoked.
  762. * + Note here to determine whether the scrolling of the scrollView is triggered by user gestures (rather than automatic code triggers). So use the
  763. * self.scrollingTriggeredByUser flag to distinguish.
  764. * + The update logic of self.scrollingTriggeredByUser is as follows:
  765. * - Set YES when the user's finger touches the screen and starts to drag (scrollViewWillBeginDragging:);
  766. * - When the user's finger drags at a certain acceleration and leaves the screen, when the screen automatically stops sliding
  767. * (scrollViewDidEndDecelerating:), set to NO;
  768. * - No acceleration is applied after the user's finger slides, and when the user lifts the finger directly (scrollViewDidEndDragging:), set NO.
  769. * 4. When the user stays in the latest message interface and receives a new message at this time. Get notified in [self dataProvider:ReceiveNewUIMsg:] .
  770. */
  771. - (void)sendVisibleReadGroupMessages {
  772. if (self.isInVC && self.isActive) {
  773. NSRange range = [self calcVisibleCellRange];
  774. [self.messageDataProvider sendMessageReadReceiptAtIndexes:[self transferIndexFromRange:range]];
  775. }
  776. }
  777. - (NSRange)calcVisibleCellRange {
  778. NSArray *indexPaths = [self.tableView indexPathsForVisibleRows];
  779. if (indexPaths.count == 0) {
  780. return NSMakeRange(0, 0);
  781. }
  782. NSIndexPath *topmost = indexPaths.firstObject;
  783. NSIndexPath *downmost = indexPaths.lastObject;
  784. return NSMakeRange(topmost.row, downmost.row - topmost.row + 1);
  785. }
  786. - (NSArray *)transferIndexFromRange:(NSRange)range {
  787. NSMutableArray *index = [NSMutableArray array];
  788. NSInteger start = range.location;
  789. for (int i = 0; i < range.length; i++) {
  790. [index addObject:@(start + i)];
  791. }
  792. return index;
  793. }
  794. - (void)hideKeyboardIfNeeded {
  795. [self.view endEditing:YES];
  796. [TUITool.applicationKeywindow endEditing:YES];
  797. }
  798. - (CGFloat)getHeightFromMessageCellData:(TUIMessageCellData *)cellData {
  799. return [self.messageCellConfig getHeightFromMessageCellData:cellData];
  800. }
  801. #pragma mark - UITableViewDelegate
  802. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  803. return self.messageDataProvider.uiMsgs.count;
  804. }
  805. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  806. if (indexPath.row < self.messageDataProvider.uiMsgs.count) {
  807. TUIMessageCellData *cellData = self.messageDataProvider.uiMsgs[indexPath.row];
  808. if([cellData isKindOfClass:[MOSystemNormalCellData class]]){
  809. MOSystemNormalCellData *tempCellData = (MOSystemNormalCellData *)cellData;
  810. CGSize size = [tempCellData contentSize];
  811. if(size.height <= 0){
  812. size.height = 80.0;
  813. }
  814. return size.height;
  815. }
  816. return [self.messageCellConfig getHeightFromMessageCellData:cellData];
  817. } else {
  818. return 0;
  819. }
  820. }
  821. - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
  822. if (indexPath.row < self.messageDataProvider.uiMsgs.count) {
  823. TUIMessageCellData *cellData = self.messageDataProvider.uiMsgs[indexPath.row];
  824. return [self.messageCellConfig getEstimatedHeightFromMessageCellData:cellData];
  825. } else {
  826. return UITableViewAutomaticDimension;
  827. }
  828. }
  829. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  830. TUIMessageCellData *data = self.messageDataProvider.uiMsgs[indexPath.row];
  831. data.showCheckBox = self.showCheckBox && [self supportCheckBox:data];
  832. TUIMessageCell *cell = nil;
  833. if ([self.delegate respondsToSelector:@selector(messageController:onShowMessageData:)]) {
  834. cell = [self.delegate messageController:self onShowMessageData:data];
  835. if (cell) {
  836. cell.delegate = self;
  837. return cell;
  838. }
  839. }
  840. if([data isKindOfClass:[MOSystemNormalCellData class]]){
  841. cell = [tableView dequeueReusableCellWithIdentifier:data.reuseId forIndexPath:indexPath];
  842. [cell fillWithData:data];
  843. return cell;
  844. }
  845. if (!data.reuseId) {
  846. NSAssert(NO, @"Unknow cell");
  847. return nil;
  848. }
  849. cell = [tableView dequeueReusableCellWithIdentifier:data.reuseId forIndexPath:indexPath];
  850. TUIMessageCellData *oldData = cell.messageData;
  851. cell.delegate = self;
  852. [cell fillWithData:data];
  853. [cell notifyBottomContainerReadyOfData:oldData];
  854. return cell;
  855. }
  856. - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
  857. TUIMessageCell *uiMsg = (TUIMessageCell *)cell;
  858. if ([uiMsg isKindOfClass:TUIMessageCell.class] && [self.delegate respondsToSelector:@selector(messageController:willDisplayCell:withData:)]) {
  859. [self.delegate messageController:self willDisplayCell:uiMsg withData:uiMsg.messageData];
  860. }
  861. }
  862. - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
  863. if (indexPath.row < self.messageDataProvider.uiMsgs.count) {
  864. TUITextMessageCellData *cellData = (TUITextMessageCellData *)self.messageDataProvider.uiMsgs[indexPath.row];
  865. // It will be deleted after TUICallKit intervenes according to the standard process.
  866. if ([cellData isKindOfClass:TUITextMessageCellData.class]) {
  867. if ((cellData.isAudioCall || cellData.isVideoCall) && cellData.showUnreadPoint) {
  868. cellData.innerMessage.localCustomInt = 1;
  869. cellData.showUnreadPoint = NO;
  870. }
  871. }
  872. [TUICore notifyEvent:TUICore_TUIChatNotify
  873. subKey:TUICore_TUIChatNotify_MessageDisplayedSubKey
  874. object:cellData
  875. param:nil];
  876. }
  877. }
  878. #pragma mark - UIScrollViewDelegate
  879. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  880. if (self.scrollingTriggeredByUser) {
  881. // only if the scrollView is dragged by user's finger to scroll, we need to send read receipts.
  882. [self sendVisibleReadGroupMessages];
  883. self.isAutoScrolledToBottom = NO;
  884. }
  885. }
  886. - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
  887. self.scrollingTriggeredByUser = YES;
  888. [self didTapViewController];
  889. }
  890. - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
  891. if ([self isScrollViewEndDragging:scrollView]) {
  892. // user presses on the scrolling scrollView and forces it to stop scrolling immediately.
  893. self.scrollingTriggeredByUser = NO;
  894. }
  895. }
  896. - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
  897. if ([self isScrollViewEndDecelerating:scrollView]) {
  898. // user drags the scrollView with a certain acceleration and makes a flick gesture, and scrollView will stop scrolling after decelerating.
  899. self.scrollingTriggeredByUser = NO;
  900. }
  901. }
  902. - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
  903. if ([self isScrollViewEndDecelerating:scrollView]) {
  904. // UIScrollView automatically stops scrolling, for example triggered after calling scrollToBottom
  905. [self sendVisibleReadGroupMessages];
  906. }
  907. }
  908. - (BOOL)isScrollViewEndDecelerating:(UIScrollView *)scrollView {
  909. return scrollView.tracking == 0 && scrollView.dragging == 0 && scrollView.decelerating == 0;
  910. }
  911. - (BOOL)isScrollViewEndDragging:(UIScrollView *)scrollView {
  912. return scrollView.tracking == 1 && scrollView.dragging == 0 && scrollView.decelerating == 0;
  913. }
  914. #pragma mark - TUIMessageCellDelegate
  915. - (void)onSelectMessage:(TUIMessageCell *)cell {
  916. if (TUIChatConfig.defaultConfig.eventConfig.chatEventListener &&
  917. [TUIChatConfig.defaultConfig.eventConfig.chatEventListener respondsToSelector:@selector(onMessageClicked:messageCellData:)]) {
  918. BOOL result = [TUIChatConfig.defaultConfig.eventConfig.chatEventListener onMessageClicked:cell messageCellData:cell.messageData];
  919. if (result) {
  920. return;
  921. }
  922. }
  923. if (cell.messageData.innerMessage.hasRiskContent) {
  924. if (![cell isKindOfClass:[TUIReferenceMessageCell class]]) {
  925. return;
  926. }
  927. }
  928. if (self.showCheckBox && [self supportCheckBox:(TUIMessageCellData *)cell.data]) {
  929. TUIMessageCellData *data = (TUIMessageCellData *)cell.data;
  930. data.selected = !data.selected;
  931. [self.tableView reloadData];
  932. return;
  933. }
  934. //Hide the keyboard when tapping on the message.
  935. [self hideKeyboardIfNeeded];
  936. if ([TUIMessageCellConfig isPluginCustomMessageCellData:cell.messageData]) {
  937. NSMutableDictionary *param = [NSMutableDictionary dictionary];
  938. if (cell) {
  939. param[TUICore_TUIPluginNotify_PluginCustomCellClick_Cell] = cell;
  940. }
  941. if (self.navigationController) {
  942. param[TUICore_TUIPluginNotify_PluginCustomCellClick_PushVC] = self.navigationController;
  943. }
  944. if (cell.pluginMsgSelectCallback) {
  945. cell.pluginMsgSelectCallback(param);
  946. }
  947. } else if ([cell isKindOfClass:[TUITextMessageCell class]]) {
  948. [self clickTextMessage:(TUITextMessageCell *)cell];
  949. } else if ([cell isKindOfClass:[TUISystemMessageCell class]]) {
  950. [self clickSystemMessage:(TUISystemMessageCell *)cell];
  951. } else if ([cell isKindOfClass:[TUIVoiceMessageCell class]]) {
  952. [self playVoiceMessage:(TUIVoiceMessageCell *)cell];
  953. } else if ([cell isKindOfClass:[TUIImageMessageCell class]]) {
  954. [self showImageMessage:(TUIImageMessageCell *)cell];
  955. } else if ([cell isKindOfClass:[TUIVideoMessageCell class]]) {
  956. [self showVideoMessage:(TUIVideoMessageCell *)cell];
  957. } else if ([cell isKindOfClass:[TUIFileMessageCell class]]) {
  958. [self showFileMessage:(TUIFileMessageCell *)cell];
  959. } else if ([cell isKindOfClass:[TUIMergeMessageCell class]]) {
  960. [self showRelayMessage:(TUIMergeMessageCell *)cell];
  961. } else if ([cell isKindOfClass:[TUILinkCell class]]) {
  962. [self showLinkMessage:(TUILinkCell *)cell];
  963. } else if ([cell isKindOfClass:TUIReplyMessageCell.class]) {
  964. [self showReplyMessage:(TUIReplyMessageCell *)cell];
  965. } else if ([cell isKindOfClass:TUIReferenceMessageCell.class]) {
  966. [self showReplyMessage:(TUIReplyMessageCell *)cell];
  967. } else if ([cell isKindOfClass:TUIOrderCell.class]) {
  968. [self showOrderMessage:(TUIOrderCell *)cell];
  969. }
  970. if ([self.delegate respondsToSelector:@selector(messageController:onSelectMessageContent:)]) {
  971. [self.delegate messageController:self onSelectMessageContent:cell];
  972. }
  973. }
  974. - (void)onLongPressMessage:(TUIMessageCell *)cell {
  975. if (TUIChatConfig.defaultConfig.eventConfig.chatEventListener &&
  976. [TUIChatConfig.defaultConfig.eventConfig.chatEventListener respondsToSelector:@selector(onMessageLongClicked:messageCellData:)]) {
  977. BOOL result = [TUIChatConfig.defaultConfig.eventConfig.chatEventListener onMessageLongClicked:cell messageCellData:cell.messageData];
  978. if (result) {
  979. return;
  980. }
  981. }
  982. [UIApplication.sharedApplication.keyWindow endEditing:NO];
  983. TUIMessageCellData *data = cell.messageData;
  984. if (![data canLongPress]) {
  985. return;
  986. }
  987. if ([data isKindOfClass:[TUISystemMessageCellData class]]) {
  988. return;
  989. }
  990. self.menuUIMsg = data;
  991. if (self.chatPopMenu && self.chatPopMenu.superview) {
  992. //The pop-up menu can only appear one at a time.
  993. return;
  994. }
  995. TUIChatPopMenu *menu = [[TUIChatPopMenu alloc] initWithEmojiView:YES frame:CGRectZero];
  996. self.chatPopMenu = menu;
  997. menu.targetCellData = data;
  998. menu.targetCell = cell;
  999. __weak typeof(menu) weakMenu = menu;
  1000. BOOL isPluginCustomMessage = [TUIMessageCellConfig isPluginCustomMessageCellData:data];
  1001. BOOL isChatNoramlMessageOrCustomMessage = !isPluginCustomMessage;
  1002. // Insert Action
  1003. if (isChatNoramlMessageOrCustomMessage) {
  1004. // Chat common Action
  1005. [self addChatCommonActionToCell:cell ofMenu:menu];
  1006. } else {
  1007. // Plugin common Action
  1008. // MultiSelect/Quote/Reply/Pin/Delete
  1009. [self addChatPluginCommonActionToCell:cell ofMenu:menu];
  1010. }
  1011. // Actions from extension
  1012. [self addExtensionActionToCell:cell ofMenu:menu];
  1013. if ([data isKindOfClass:[TUITextMessageCellData class]]) {
  1014. /**
  1015. * becomeFirstResponder ,,。
  1016. * When the text message is selected, it will becomeFirstResponder by default, causing the keyboard to disappear and the interface to be chaotic. Here,
  1017. * the keyboard that has popped up is put away first.
  1018. */
  1019. TUITextMessageCell *textCell = (TUITextMessageCell *)cell;
  1020. [textCell.textView becomeFirstResponder];
  1021. [textCell.textView selectAll:self];
  1022. } else if ([data isKindOfClass:[TUIReferenceMessageCellData class]]) {
  1023. TUIReferenceMessageCell *referenceCell = (TUIReferenceMessageCell *)cell;
  1024. [referenceCell.textView becomeFirstResponder];
  1025. [referenceCell.textView selectAll:self];
  1026. }
  1027. BOOL isFirstResponder = NO;
  1028. if (_delegate && [_delegate respondsToSelector:@selector(messageController:willShowMenuInCell:)]) {
  1029. isFirstResponder = [_delegate messageController:self willShowMenuInCell:cell];
  1030. }
  1031. if (isFirstResponder) {
  1032. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(menuDidHide:) name:UIMenuControllerDidHideMenuNotification object:nil];
  1033. } else {
  1034. [self becomeFirstResponder];
  1035. }
  1036. CGRect frame = [UIApplication.sharedApplication.keyWindow convertRect:cell.container.frame fromView:cell];
  1037. CGFloat topMarginiByCustomView = 0;
  1038. if (_delegate && [_delegate respondsToSelector:@selector(getTopMarginByCustomView)]) {
  1039. topMarginiByCustomView = [_delegate getTopMarginByCustomView];
  1040. }
  1041. [menu setArrawPosition:CGPointMake(frame.origin.x + frame.size.width * 0.5, frame.origin.y - 5 - topMarginiByCustomView)
  1042. adjustHeight:frame.size.height + 5];
  1043. [menu showInView:self.tableView];
  1044. [self configSelectActionToCell:cell ofMenu:menu];
  1045. }
  1046. - (void)addChatCommonActionToCell:(TUIMessageCell *)cell ofMenu:(TUIChatPopMenu *)menu {
  1047. // Setup popAction
  1048. TUIChatPopMenuAction *copyAction = [self setupCopyAction:cell];
  1049. TUIChatPopMenuAction *deleteAction = [self setupDeleteAction:cell];
  1050. TUIChatPopMenuAction *recallAction = [self setupRecallAction:cell];
  1051. TUIChatPopMenuAction *multiAction = [self setupMulitSelectAction:cell];
  1052. TUIChatPopMenuAction *forwardAction = [self setupForwardAction:cell];
  1053. TUIChatPopMenuAction *replyAction = [self setupReplyAction:cell];
  1054. TUIChatPopMenuAction *quoteAction = [self setupQuoteAction:cell];
  1055. TUIChatPopMenuAction *audioPlaybackStyleAction = [self setupAudioPlaybackStyleAction:cell];
  1056. TUIChatPopMenuAction *groupPinAction = [self setupGroupPinAction:cell];
  1057. TUIMessageCellData *data = cell.messageData;
  1058. V2TIMMessage *imMsg = data.innerMessage;
  1059. BOOL isMsgSendSucceed = imMsg.status == V2TIM_MSG_STATUS_SEND_SUCC;
  1060. BOOL isContentModerated = imMsg.hasRiskContent;
  1061. if (imMsg.soundElem) {
  1062. [menu addAction:audioPlaybackStyleAction];
  1063. }
  1064. if (([data isKindOfClass:[TUITextMessageCellData class]] || [data isKindOfClass:TUIReplyMessageCellData.class] ||
  1065. [data isKindOfClass:TUIReferenceMessageCellData.class]) && !isContentModerated) {
  1066. [menu addAction:copyAction];
  1067. }
  1068. [menu addAction:deleteAction];
  1069. if (!isContentModerated) {
  1070. [menu addAction:multiAction];
  1071. }
  1072. if (imMsg && [imMsg isSelf] && [[NSDate date] timeIntervalSinceDate:imMsg.timestamp] < TUIChatConfig.defaultConfig.timeIntervalForMessageRecall && isMsgSendSucceed) {
  1073. [menu addAction:recallAction];
  1074. }
  1075. if ([self canForward:data] && isMsgSendSucceed && !isContentModerated) {
  1076. [menu addAction:forwardAction];
  1077. }
  1078. if (isMsgSendSucceed && !isContentModerated) {
  1079. [menu addAction:replyAction];
  1080. }
  1081. if (isMsgSendSucceed && !isContentModerated) {
  1082. [menu addAction:quoteAction];
  1083. }
  1084. BOOL isGroup = (data.innerMessage.groupID.length > 0);
  1085. if (isGroup && [self.messageDataProvider isCurrentUserRoleSuperAdminInGroup] && isMsgSendSucceed && !isContentModerated) {
  1086. [menu addAction:groupPinAction];
  1087. }
  1088. }
  1089. - (void)addChatPluginCommonActionToCell:(TUIMessageCell *)cell ofMenu:(TUIChatPopMenu *)menu {
  1090. // Setup popAction
  1091. TUIChatPopMenuAction *deleteAction = [self setupDeleteAction:cell];
  1092. TUIChatPopMenuAction *recallAction = [self setupRecallAction:cell];
  1093. TUIChatPopMenuAction *multiAction = [self setupMulitSelectAction:cell];
  1094. TUIChatPopMenuAction *replyAction = [self setupReplyAction:cell];
  1095. TUIChatPopMenuAction *quoteAction = [self setupQuoteAction:cell];
  1096. TUIChatPopMenuAction *pinAction = [self setupGroupPinAction:cell];
  1097. TUIMessageCellData *data = cell.messageData;
  1098. V2TIMMessage *imMsg = data.innerMessage;
  1099. BOOL isContentModerated = imMsg.hasRiskContent;
  1100. BOOL isMsgSendSucceed = imMsg.status == V2TIM_MSG_STATUS_SEND_SUCC;
  1101. [menu addAction:multiAction];
  1102. if (isMsgSendSucceed) {
  1103. [menu addAction:replyAction];
  1104. [menu addAction:quoteAction];
  1105. }
  1106. BOOL isGroup = (data.innerMessage.groupID.length > 0);
  1107. if (isGroup && [self.messageDataProvider isCurrentUserRoleSuperAdminInGroup] && isMsgSendSucceed && !isContentModerated) {
  1108. [menu addAction:pinAction];
  1109. }
  1110. [menu addAction:deleteAction];
  1111. if (imMsg && [imMsg isSelf] && [[NSDate date] timeIntervalSinceDate:imMsg.timestamp] < TUIChatConfig.defaultConfig.timeIntervalForMessageRecall && isMsgSendSucceed) {
  1112. [menu addAction:recallAction];
  1113. }
  1114. }
  1115. - (void)addExtensionActionToCell:(TUIMessageCell *)cell ofMenu:(TUIChatPopMenu *)menu {
  1116. // extra
  1117. NSArray<TUIExtensionInfo *> *infoArray =
  1118. [TUICore getExtensionList:TUICore_TUIChatExtension_PopMenuActionItem_ClassicExtensionID
  1119. param:@{TUICore_TUIChatExtension_PopMenuActionItem_TargetVC : self, TUICore_TUIChatExtension_PopMenuActionItem_ClickCell : cell}];
  1120. for (TUIExtensionInfo *info in infoArray) {
  1121. if (info.text && info.icon && info.onClicked) {
  1122. TUIChatPopMenuAction *extension = [[TUIChatPopMenuAction alloc] initWithTitle:info.text
  1123. image:info.icon
  1124. weight:info.weight
  1125. callback:^{
  1126. info.onClicked(@{});
  1127. }];
  1128. [menu addAction:extension];
  1129. }
  1130. }
  1131. }
  1132. - (void)configSelectActionToCell:(TUIMessageCell *)cell ofMenu:(TUIChatPopMenu *)menu {
  1133. // Setup popAction
  1134. TUIChatPopMenuAction *copyAction = [self setupCopyAction:cell];
  1135. TUIChatPopMenuAction *deleteAction = [self setupDeleteAction:cell];
  1136. TUIChatPopMenuAction *multiAction = [self setupMulitSelectAction:cell];
  1137. TUIChatPopMenuAction *forwardAction = [self setupForwardAction:cell];
  1138. TUIChatPopMenuAction *replyAction = [self setupReplyAction:cell];
  1139. TUIChatPopMenuAction *quoteAction = [self setupQuoteAction:cell];
  1140. TUIChatPopMenuAction *groupPinAction = [self setupGroupPinAction:cell];
  1141. TUIMessageCellData *data = cell.messageData;
  1142. BOOL isGroup = (data.innerMessage.groupID.length > 0);
  1143. //Chat common Action special operator
  1144. @weakify(self);
  1145. @weakify(cell);
  1146. @weakify(menu);
  1147. __block BOOL isSelectAll = YES;
  1148. void (^selectAllContentCallback)(BOOL) = ^(BOOL selectAll) {
  1149. @strongify(self);
  1150. @strongify(cell);
  1151. @strongify(menu);
  1152. if (isSelectAll == selectAll) {
  1153. return;
  1154. }
  1155. isSelectAll = selectAll;
  1156. [menu removeAllAction];
  1157. if (isSelectAll) {
  1158. [menu addAction:copyAction];
  1159. [menu addAction:deleteAction];
  1160. [menu addAction:multiAction];
  1161. if ([self canForward:data]) {
  1162. [menu addAction:forwardAction];
  1163. }
  1164. [menu addAction:replyAction];
  1165. [menu addAction:quoteAction];
  1166. if (isGroup && [self.messageDataProvider isCurrentUserRoleSuperAdminInGroup]) {
  1167. [menu addAction:groupPinAction];
  1168. }
  1169. } else {
  1170. [menu addAction:copyAction];
  1171. if ([self canForward:data]) {
  1172. [menu addAction:forwardAction];
  1173. }
  1174. }
  1175. // Select all or not may affect the action menu
  1176. [self addExtensionActionToCell:cell ofMenu:menu];
  1177. [menu layoutSubview];
  1178. };
  1179. /**
  1180. * If it is a text type message, set the text message cursor selected state, if the text is not all selected state, only keep copy and forward
  1181. */
  1182. if ([data isKindOfClass:[TUITextMessageCellData class]]) {
  1183. TUITextMessageCell *textCell = (TUITextMessageCell *)cell;
  1184. [textCell.textView selectAll:self];
  1185. textCell.selectAllContentContent = selectAllContentCallback;
  1186. menu.hideCallback = ^{
  1187. [textCell.textView setSelectedTextRange:nil];
  1188. };
  1189. }
  1190. if ([data isKindOfClass:[TUIReferenceMessageCellData class]] || [data isKindOfClass:[TUIReplyMessageCellData class]]) {
  1191. TUIReplyMessageCell *textCell = (TUIReplyMessageCell *)cell;
  1192. [textCell.textView selectAll:self];
  1193. textCell.selectAllContentContent = selectAllContentCallback;
  1194. menu.hideCallback = ^{
  1195. [textCell.textView setSelectedTextRange:nil];
  1196. };
  1197. };
  1198. }
  1199. - (TUIChatPopMenuAction *)setupCopyAction:(TUIMessageCell *)cell {
  1200. BOOL isCopyShown = [TUIChatConfig defaultConfig].enablePopMenuCopyAction;
  1201. TUIChatPopMenuAction *copyAction = nil;
  1202. @weakify(self);
  1203. @weakify(cell);
  1204. copyAction = [[TUIChatPopMenuAction alloc] initWithTitle:TIMCommonLocalizableString(Copy)
  1205. image:TUIChatBundleThemeImage(@"chat_icon_copy_img", @"icon_copy")
  1206. weight:10000
  1207. callback:^{
  1208. @strongify(self);
  1209. @strongify(cell);
  1210. [self onCopyMsg:cell];
  1211. }];
  1212. return isCopyShown ? copyAction : nil;
  1213. }
  1214. - (TUIChatPopMenuAction *)setupDeleteAction:(TUIMessageCell *)cell {
  1215. BOOL isDeleteShown = [TUIChatConfig defaultConfig].enablePopMenuDeleteAction;
  1216. @weakify(self);
  1217. TUIChatPopMenuAction *deleteAction = [[TUIChatPopMenuAction alloc] initWithTitle:TIMCommonLocalizableString(Delete)
  1218. image:TUIChatBundleThemeImage(@"chat_icon_delete_img", @"icon_delete")
  1219. weight:3000
  1220. callback:^{
  1221. @strongify(self);
  1222. [self onDelete:nil];
  1223. }];
  1224. return isDeleteShown ? deleteAction : nil;
  1225. }
  1226. - (TUIChatPopMenuAction *)setupRecallAction:(TUIMessageCell *)cell {
  1227. BOOL isRecallShown = [TUIChatConfig defaultConfig].enablePopMenuRecallAction;
  1228. TUIChatPopMenuAction *recallAction = nil;
  1229. TUIMessageCellData *data = cell.messageData;
  1230. V2TIMMessage *imMsg = data.innerMessage;
  1231. @weakify(self);
  1232. recallAction = [[TUIChatPopMenuAction alloc] initWithTitle:TIMCommonLocalizableString(Revoke)
  1233. image:TUIChatBundleThemeImage(@"chat_icon_recall_img", @"icon_recall")
  1234. weight:4000
  1235. callback:^{
  1236. @strongify(self);
  1237. [self onRevoke:nil];
  1238. }];
  1239. return isRecallShown ? recallAction : nil;
  1240. }
  1241. - (TUIChatPopMenuAction *)setupMulitSelectAction:(TUIMessageCell *)cell {
  1242. BOOL isSelectShown = [TUIChatConfig defaultConfig].enablePopMenuSelectAction;
  1243. @weakify(self);
  1244. TUIChatPopMenuAction *multiAction = nil;
  1245. multiAction = [[TUIChatPopMenuAction alloc] initWithTitle:TIMCommonLocalizableString(Multiple)
  1246. image:TUIChatBundleThemeImage(@"chat_icon_multi_img", @"icon_multi")
  1247. weight:8000
  1248. callback:^{
  1249. @strongify(self);
  1250. [self onMulitSelect:nil];
  1251. }];
  1252. return isSelectShown ? multiAction : nil;
  1253. }
  1254. - (TUIChatPopMenuAction *)setupForwardAction:(TUIMessageCell *)cell {
  1255. BOOL isForwardShown = [TUIChatConfig defaultConfig].enablePopMenuForwardAction;
  1256. @weakify(self);
  1257. TUIChatPopMenuAction *forwardAction = nil;
  1258. forwardAction = [[TUIChatPopMenuAction alloc] initWithTitle:TIMCommonLocalizableString(Forward)
  1259. image:TUIChatBundleThemeImage(@"chat_icon_forward_img", @"icon_forward")
  1260. weight:9000
  1261. callback:^{
  1262. @strongify(self);
  1263. [self onForward:nil];
  1264. }];
  1265. return isForwardShown ? forwardAction : nil;
  1266. }
  1267. - (TUIChatPopMenuAction *)setupReplyAction:(TUIMessageCell *)cell {
  1268. BOOL isReplyShown = [TUIChatConfig defaultConfig].enablePopMenuReplyAction;
  1269. @weakify(self);
  1270. TUIChatPopMenuAction *replyAction = nil;
  1271. replyAction = [[TUIChatPopMenuAction alloc] initWithTitle:TIMCommonLocalizableString(Reply)
  1272. image:TUIChatBundleThemeImage(@"chat_icon_reply_img", @"icon_reply")
  1273. weight:5000
  1274. callback:^{
  1275. @strongify(self);
  1276. [self onReply:nil];
  1277. }];
  1278. return isReplyShown ? replyAction : nil;
  1279. }
  1280. - (TUIChatPopMenuAction *)setupQuoteAction:(TUIMessageCell *)cell {
  1281. BOOL isQuoteShown = [TUIChatConfig defaultConfig].enablePopMenuReferenceAction;
  1282. @weakify(self);
  1283. TUIChatPopMenuAction *quoteAction = nil;
  1284. quoteAction = [[TUIChatPopMenuAction alloc] initWithTitle:TIMCommonLocalizableString(TUIKitReference)
  1285. image:TUIChatBundleThemeImage(@"chat_icon_reference_img", @"icon_reference")
  1286. weight:7000
  1287. callback:^{
  1288. @strongify(self);
  1289. [self onReference:nil];
  1290. }];
  1291. return isQuoteShown ? quoteAction : nil;
  1292. }
  1293. - (TUIChatPopMenuAction *)setupAudioPlaybackStyleAction:(TUIMessageCell *)cell {
  1294. BOOL isPlaybackShown = [TUIChatConfig defaultConfig].enablePopMenuAudioPlaybackAction;
  1295. @weakify(self);
  1296. TUIChatPopMenuAction *audioPlaybackStyleAction = nil;
  1297. __weak typeof(audioPlaybackStyleAction) weakAction = audioPlaybackStyleAction;
  1298. TUIVoiceAudioPlaybackStyle originStyle = [TUIVoiceMessageCellData getAudioplaybackStyle];
  1299. NSString *title = @"";
  1300. UIImage *img = nil;
  1301. if (originStyle == TUIVoiceAudioPlaybackStyleLoudspeaker) {
  1302. title = TIMCommonLocalizableString(TUIKitAudioPlaybackStyleHandset);
  1303. img = TUIChatBundleThemeImage(@"chat_icon_audio_handset_img", @"icon_handset");
  1304. }
  1305. else {
  1306. title = TIMCommonLocalizableString(TUIKitAudioPlaybackStyleLoudspeaker);
  1307. img = TUIChatBundleThemeImage(@"chat_icon_audio_loudspeaker_img", @"icon_loudspeaker");
  1308. }
  1309. audioPlaybackStyleAction = [[TUIChatPopMenuAction alloc] initWithTitle:title
  1310. image:img
  1311. weight:11000
  1312. callback:^{
  1313. if (originStyle == TUIVoiceAudioPlaybackStyleLoudspeaker) {
  1314. //Change To Handset
  1315. weakAction.title = TIMCommonLocalizableString(TUIKitAudioPlaybackStyleLoudspeaker);
  1316. [TUITool hideToast];
  1317. [TUITool makeToast:TIMCommonLocalizableString(TUIKitAudioPlaybackStyleChange2Handset) duration:2];
  1318. }
  1319. else {
  1320. weakAction.title = TIMCommonLocalizableString(TUIKitAudioPlaybackStyleHandset);
  1321. [TUITool hideToast];
  1322. [TUITool makeToast:TIMCommonLocalizableString(TUIKitAudioPlaybackStyleChange2Loudspeaker) duration:2];
  1323. }
  1324. [TUIVoiceMessageCellData changeAudioPlaybackStyle];
  1325. }];
  1326. return isPlaybackShown ? audioPlaybackStyleAction : nil;
  1327. }
  1328. - (TUIChatPopMenuAction *)setupGroupPinAction:(TUIMessageCell *)cell {
  1329. BOOL isPinShown = [TUIChatConfig defaultConfig].enablePopMenuPinAction;
  1330. @weakify(self);
  1331. BOOL isPinned = [self.messageDataProvider isCurrentMessagePin:self.menuUIMsg.innerMessage.msgID];
  1332. TUIChatPopMenuAction *groupPinAction = nil;
  1333. UIImage* img =
  1334. isPinned ? TUIChatBundleThemeImage(@"chat_icon_group_unpin_img", @"icon_unpin") : TUIChatBundleThemeImage(@"chat_icon_group_pin_img", @"icon_pin");
  1335. groupPinAction = [[TUIChatPopMenuAction alloc] initWithTitle:isPinned?
  1336. TIMCommonLocalizableString(TUIKitGroupMessageUnPin) : TIMCommonLocalizableString(TUIKitGroupMessagePin)
  1337. image:img
  1338. weight:2900
  1339. callback:^{
  1340. @strongify(self);
  1341. [self onGroupPin:nil currentStatus:isPinned];
  1342. }];
  1343. return isPinShown ? groupPinAction : nil;
  1344. }
  1345. - (BOOL)canForward:(TUIMessageCellData *)data {
  1346. return ![TUIMessageCellConfig isPluginCustomMessageCellData:data];
  1347. }
  1348. - (void)onLongSelectMessageAvatar:(TUIMessageCell *)cell {
  1349. if (TUIChatConfig.defaultConfig.eventConfig.chatEventListener &&
  1350. [TUIChatConfig.defaultConfig.eventConfig.chatEventListener respondsToSelector:@selector(onUserIconLongClicked:messageCellData:)]) {
  1351. BOOL result = [TUIChatConfig.defaultConfig.eventConfig.chatEventListener onUserIconLongClicked:cell messageCellData:cell.messageData];
  1352. if (result) {
  1353. return;
  1354. }
  1355. }
  1356. if (_delegate && [_delegate respondsToSelector:@selector(messageController:onLongSelectMessageAvatar:)]) {
  1357. [_delegate messageController:self onLongSelectMessageAvatar:cell];
  1358. }
  1359. }
  1360. - (void)onRetryMessage:(TUIMessageCell *)cell {
  1361. BOOL hasRiskContent = cell.messageData.innerMessage.hasRiskContent;
  1362. if (hasRiskContent) {
  1363. return;
  1364. }
  1365. _reSendUIMsg = cell.messageData;
  1366. __weak typeof(self) weakSelf = self;
  1367. UIAlertController *alert = [UIAlertController alertControllerWithTitle:TIMCommonLocalizableString(TUIKitTipsConfirmResendMessage)
  1368. message:nil
  1369. preferredStyle:UIAlertControllerStyleAlert];
  1370. [alert tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(Re_send)
  1371. style:UIAlertActionStyleDefault
  1372. handler:^(UIAlertAction *_Nonnull action) {
  1373. [weakSelf sendUIMessage:weakSelf.reSendUIMsg];
  1374. }]];
  1375. [alert tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(Cancel)
  1376. style:UIAlertActionStyleCancel
  1377. handler:^(UIAlertAction *_Nonnull action){
  1378. }]];
  1379. [self.navigationController presentViewController:alert animated:YES completion:nil];
  1380. }
  1381. - (void)onSelectMessageAvatar:(TUIMessageCell *)cell {
  1382. if (TUIChatConfig.defaultConfig.eventConfig.chatEventListener &&
  1383. [TUIChatConfig.defaultConfig.eventConfig.chatEventListener respondsToSelector:@selector(onUserIconClicked:messageCellData:)]) {
  1384. BOOL result = [TUIChatConfig.defaultConfig.eventConfig.chatEventListener onUserIconClicked:cell messageCellData:cell.messageData];
  1385. if (result) {
  1386. return;
  1387. }
  1388. }
  1389. if ([self.delegate respondsToSelector:@selector(messageController:onSelectMessageAvatar:)]) {
  1390. [self.delegate messageController:self onSelectMessageAvatar:cell];
  1391. }
  1392. }
  1393. - (void)onSelectReadReceipt:(TUIMessageCellData *)data {
  1394. @weakify(self);
  1395. if (data.innerMessage.groupID.length > 0) {
  1396. // Navigate to group message read VC. Should get members first.
  1397. [TUIMessageDataProvider getMessageReadReceipt:@[ data.innerMessage ]
  1398. succ:^(NSArray<V2TIMMessageReceipt *> *receiptList) {
  1399. @strongify(self);
  1400. if (receiptList.count == 0) {
  1401. return;
  1402. }
  1403. // To avoid the labels in messageReadVC displaying all 0 which is not accurate, try to get message read count before navigation.
  1404. V2TIMMessageReceipt *receipt = receiptList.firstObject;
  1405. data.messageReceipt = receipt;
  1406. [self pushMessageReadViewController:data];
  1407. }
  1408. fail:^(int code, NSString *desc) {
  1409. @strongify(self);
  1410. [self pushMessageReadViewController:data];
  1411. }];
  1412. } else {
  1413. // navigate to c2c message read VC. No need to get member.
  1414. [self pushMessageReadViewController:data];
  1415. }
  1416. }
  1417. - (void)pushMessageReadViewController:(TUIMessageCellData *)data {
  1418. self.hasCoverPage = YES;
  1419. TUIMessageReadViewController *controller = [[TUIMessageReadViewController alloc] initWithCellData:data
  1420. dataProvider:self.messageDataProvider
  1421. showReadStatusDisable:NO
  1422. c2cReceiverName:self.conversationData.title
  1423. c2cReceiverAvatar:self.conversationData.faceUrl];
  1424. [self.navigationController pushViewController:controller animated:YES];
  1425. __weak typeof(self) weakSelf = self;
  1426. controller.viewWillDismissHandler = ^{
  1427. weakSelf.hasCoverPage = NO;
  1428. };
  1429. }
  1430. - (void)onJumpToRepliesDetailPage:(TUIMessageCellData *)data {
  1431. self.hasCoverPage = YES;
  1432. TUIRepliesDetailViewController *repliesDetailVC = [[TUIRepliesDetailViewController alloc] initWithCellData:data conversationData:self.conversationData];
  1433. repliesDetailVC.delegate = self.delegate;
  1434. [self.navigationController pushViewController:repliesDetailVC animated:YES];
  1435. repliesDetailVC.parentPageDataProvider = self.messageDataProvider;
  1436. __weak typeof(self) weakSelf = self;
  1437. repliesDetailVC.willCloseCallback = ^() {
  1438. [weakSelf.tableView reloadData];
  1439. weakSelf.hasCoverPage = NO;
  1440. };
  1441. }
  1442. - (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
  1443. if (action == @selector(onDelete:) || action == @selector(onRevoke:) || action == @selector(onReSend:) || action == @selector(onCopyMsg:) ||
  1444. action == @selector(onMulitSelect:) || action == @selector(onForward:) || action == @selector(onReply:)) {
  1445. return YES;
  1446. }
  1447. return NO;
  1448. }
  1449. - (BOOL)canBecomeFirstResponder {
  1450. return YES;
  1451. }
  1452. - (void)buildMenuWithBuilder:(id<UIMenuBuilder>)builder API_AVAILABLE(ios(13.0)) {
  1453. if (@available(iOS 16.0, *)) {
  1454. [builder removeMenuForIdentifier:UIMenuLookup];
  1455. }
  1456. [super buildMenuWithBuilder:builder];
  1457. }
  1458. - (void)onDelete:(id)sender {
  1459. @weakify(self);
  1460. UIAlertController *vc = [UIAlertController alertControllerWithTitle:nil
  1461. message:TIMCommonLocalizableString(ConfirmDeleteMessage)
  1462. preferredStyle:UIAlertControllerStyleActionSheet];
  1463. [vc tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(Delete)
  1464. style:UIAlertActionStyleDestructive
  1465. handler:^(UIAlertAction *_Nonnull action) {
  1466. @strongify(self);
  1467. [self.messageDataProvider deleteUIMsgs:@[ self.menuUIMsg ]
  1468. SuccBlock:nil
  1469. FailBlock:^(int code, NSString *desc) {
  1470. NSLog(@"remove msg failed!");
  1471. NSAssert(NO, desc);
  1472. }];
  1473. }]];
  1474. [vc tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(Cancel) style:UIAlertActionStyleCancel handler:nil]];
  1475. [self presentViewController:vc animated:YES completion:nil];
  1476. }
  1477. - (void)menuDidHide:(NSNotification *)notification {
  1478. if (_delegate && [_delegate respondsToSelector:@selector(didHideMenuInMessageController:)]) {
  1479. [_delegate didHideMenuInMessageController:self];
  1480. }
  1481. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIMenuControllerDidHideMenuNotification object:nil];
  1482. }
  1483. - (void)onCopyMsg:(id)sender {
  1484. NSString *content = @"";
  1485. /**
  1486. *
  1487. * The text message should be based on the content of the message actually selected by the cursor
  1488. */
  1489. if ([sender isKindOfClass:[TUITextMessageCell class]]) {
  1490. TUITextMessageCell *txtCell = (TUITextMessageCell *)sender;
  1491. content = txtCell.selectContent;
  1492. }
  1493. if ([sender isKindOfClass:TUIReplyMessageCell.class] || [sender isKindOfClass:TUIReferenceMessageCell.class]) {
  1494. TUIReplyMessageCellData *replyMsg = (TUIReplyMessageCellData *)sender;
  1495. content = replyMsg.selectContent;
  1496. }
  1497. if (content.length > 0) {
  1498. UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
  1499. pasteboard.string = content;
  1500. [TUITool makeToast:TIMCommonLocalizableString(Copied)];
  1501. }
  1502. }
  1503. - (void)onRevoke:(id)sender {
  1504. @weakify(self);
  1505. [self.messageDataProvider revokeUIMsg:self.menuUIMsg
  1506. SuccBlock:^{
  1507. @strongify(self);
  1508. if (self.delegate && [self.delegate respondsToSelector:@selector(didHideMenuInMessageController:)]) {
  1509. [self.delegate didHideMenuInMessageController:self];
  1510. }
  1511. }
  1512. FailBlock:^(int code, NSString *desc) {
  1513. NSAssert(NO, desc);
  1514. }];
  1515. }
  1516. - (void)onReSend:(id)sender {
  1517. [self sendUIMessage:_menuUIMsg];
  1518. }
  1519. - (void)onMulitSelect:(id)sender {
  1520. [self enableMultiSelectedMode:YES];
  1521. if (self.menuUIMsg.innerMessage.hasRiskContent) {
  1522. if (_delegate && [_delegate respondsToSelector:@selector(messageController:onSelectMessageMenu:withData:)]) {
  1523. [_delegate messageController:self onSelectMessageMenu:0 withData:nil];
  1524. }
  1525. return;
  1526. }
  1527. self.menuUIMsg.selected = YES;
  1528. [self.tableView beginUpdates];
  1529. NSInteger index = [self.messageDataProvider.uiMsgs indexOfObject:self.menuUIMsg];
  1530. [self.tableView reloadRowsAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ] withRowAnimation:UITableViewRowAnimationNone];
  1531. [self.tableView endUpdates];
  1532. if (_delegate && [_delegate respondsToSelector:@selector(messageController:onSelectMessageMenu:withData:)]) {
  1533. [_delegate messageController:self onSelectMessageMenu:0 withData:_menuUIMsg];
  1534. }
  1535. }
  1536. - (void)onForward:(id)sender {
  1537. if (_delegate && [_delegate respondsToSelector:@selector(messageController:onSelectMessageMenu:withData:)]) {
  1538. [_delegate messageController:self onSelectMessageMenu:1 withData:_menuUIMsg];
  1539. }
  1540. }
  1541. - (void)onReply:(id)sender {
  1542. if (_delegate && [_delegate respondsToSelector:@selector(messageController:onRelyMessage:)]) {
  1543. [_delegate messageController:self onRelyMessage:self.menuUIMsg];
  1544. }
  1545. }
  1546. - (void)onReference:(id)sender {
  1547. if (_delegate && [_delegate respondsToSelector:@selector(messageController:onReferenceMessage:)]) {
  1548. [_delegate messageController:self onReferenceMessage:self.menuUIMsg];
  1549. }
  1550. }
  1551. - (void)onGroupPin:(id)sender currentStatus:(BOOL)currentStatus {
  1552. NSString *groupId = self.conversationData.groupID;
  1553. BOOL isPinned = currentStatus;
  1554. BOOL pinOrUnpin = !isPinned;
  1555. [self.messageDataProvider pinGroupMessage:groupId message:self.menuUIMsg.innerMessage isPinned:pinOrUnpin succ:^{
  1556. } fail:^(int code, NSString *desc) {
  1557. if (code == 10070) {
  1558. [TUITool makeToast:TIMCommonLocalizableString(TUIKitGroupMessagePinOverLimit)];
  1559. }
  1560. else if (code == 10004) {
  1561. if (pinOrUnpin) {
  1562. [TUITool makeToast:TIMCommonLocalizableString(TUIKitGroupMessagePinRepeatedly)];
  1563. }
  1564. else {
  1565. [TUITool makeToast:TIMCommonLocalizableString(TUIKitGroupMessageUnPinRepeatedly)];
  1566. }
  1567. }
  1568. }];
  1569. }
  1570. - (BOOL)supportCheckBox:(TUIMessageCellData *)data {
  1571. if ([data isKindOfClass:TUISystemMessageCellData.class]) {
  1572. return NO;
  1573. }
  1574. return YES;
  1575. }
  1576. - (BOOL)supportRelay:(TUIMessageCellData *)data {
  1577. if ([data isKindOfClass:TUIVoiceMessageCellData.class]) {
  1578. return NO;
  1579. }
  1580. return YES;
  1581. }
  1582. - (void)enableMultiSelectedMode:(BOOL)enable {
  1583. self.showCheckBox = enable;
  1584. if (!enable) {
  1585. for (TUIMessageCellData *cellData in self.messageDataProvider.uiMsgs) {
  1586. cellData.selected = NO;
  1587. }
  1588. }
  1589. [self.tableView reloadData];
  1590. }
  1591. - (NSArray<TUIMessageCellData *> *)multiSelectedResult:(TUIMultiResultOption)option {
  1592. NSMutableArray *arrayM = [NSMutableArray array];
  1593. if (!self.showCheckBox) {
  1594. return [NSArray arrayWithArray:arrayM];
  1595. }
  1596. BOOL filterUnsupported = option & TUIMultiResultOptionFiterUnsupportRelay;
  1597. for (TUIMessageCellData *data in self.messageDataProvider.uiMsgs) {
  1598. if (data.selected) {
  1599. if (filterUnsupported && ![self supportRelay:data]) {
  1600. continue;
  1601. }
  1602. [arrayM addObject:data];
  1603. }
  1604. }
  1605. return [NSArray arrayWithArray:arrayM];
  1606. }
  1607. - (void)deleteMessages:(NSArray<TUIMessageCellData *> *)uiMsgs {
  1608. if (uiMsgs.count == 0 || uiMsgs.count > 30) {
  1609. NSLog(@"The size of messages must be between 0 and 30");
  1610. return;
  1611. }
  1612. [self.messageDataProvider deleteUIMsgs:uiMsgs
  1613. SuccBlock:nil
  1614. FailBlock:^(int code, NSString *desc) {
  1615. NSLog(@"deleteMessages failed!");
  1616. NSAssert(NO, desc);
  1617. }];
  1618. }
  1619. - (void)clickTextMessage:(TUITextMessageCell *)cell {
  1620. V2TIMMessage *message = cell.messageData.innerMessage;
  1621. if (0 == message.userID.length) {
  1622. return;
  1623. }
  1624. [TUIMessageDataProvider.callingDataProvider redialFromMessage:message];
  1625. }
  1626. - (void)clickSystemMessage:(TUISystemMessageCell *)cell {
  1627. TUISystemMessageCellData *data = (TUISystemMessageCellData *)cell.messageData;
  1628. if (data.supportReEdit) {
  1629. if (self.delegate && [self.delegate respondsToSelector:@selector(messageController:onReEditMessage:)]) {
  1630. [self.delegate messageController:self onReEditMessage:cell.messageData];
  1631. }
  1632. }
  1633. }
  1634. - (void)playVoiceMessage:(TUIVoiceMessageCell *)cell {
  1635. for (TUIMessageCellData *cellData in self.messageDataProvider.uiMsgs) {
  1636. if (![cellData isKindOfClass:[TUIVoiceMessageCellData class]]) {
  1637. continue;
  1638. }
  1639. TUIVoiceMessageCellData *voiceMsg = (TUIVoiceMessageCellData *)cellData;
  1640. if (voiceMsg == cell.voiceData) {
  1641. [voiceMsg playVoiceMessage];
  1642. self.currentVoiceMsg = voiceMsg;
  1643. cell.voiceReadPoint.hidden = YES;
  1644. NSMutableArray *unPlayVoiceMessageAfterSelectVoiceMessage = [self getCurrentUnPlayVoiceMessageAfterSelectVoiceMessage:voiceMsg];
  1645. @weakify(self);
  1646. voiceMsg.audioPlayerDidFinishPlayingBlock = ^{
  1647. @strongify(self);
  1648. if (unPlayVoiceMessageAfterSelectVoiceMessage.count > 0) {
  1649. TUIVoiceMessageCellData *nextVoiceCellData = [unPlayVoiceMessageAfterSelectVoiceMessage firstObject];
  1650. NSIndexPath *nextIndex = [self indexPathOfMessage:nextVoiceCellData.msgID];
  1651. [self scrollCellToBottomOfMessage:nextVoiceCellData.msgID];
  1652. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  1653. TUIVoiceMessageCell *nextCell = [self.tableView cellForRowAtIndexPath:nextIndex];
  1654. if (nextCell) {
  1655. [self playVoiceMessage:nextCell];
  1656. [unPlayVoiceMessageAfterSelectVoiceMessage removeObject:nextVoiceCellData];
  1657. } else {
  1658. // rerty: avoid nextCell is nil
  1659. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  1660. TUIVoiceMessageCell *retryNextCell = [self.tableView cellForRowAtIndexPath:nextIndex];
  1661. if (retryNextCell) {
  1662. [self playVoiceMessage:retryNextCell];
  1663. [unPlayVoiceMessageAfterSelectVoiceMessage removeObject:nextVoiceCellData];
  1664. }
  1665. });
  1666. }
  1667. });
  1668. }
  1669. };
  1670. } else {
  1671. [voiceMsg stopVoiceMessage];
  1672. }
  1673. }
  1674. }
  1675. - (NSMutableArray *)getCurrentUnPlayVoiceMessageAfterSelectVoiceMessage:(TUIVoiceMessageCellData *)playingCellData {
  1676. NSMutableArray *neverHitsPlayVoiceQueue = [NSMutableArray array];
  1677. for (TUIMessageCellData *cellData in self.messageDataProvider.uiMsgs) {
  1678. if ([cellData isKindOfClass:[TUIVoiceMessageCellData class]]) {
  1679. TUIVoiceMessageCellData *voiceMsg = (TUIVoiceMessageCellData *)cellData;
  1680. if ((voiceMsg.innerMessage.localCustomInt == 0 && voiceMsg.direction == MsgDirectionIncoming &&
  1681. [voiceMsg.innerMessage.timestamp timeIntervalSince1970] >= [playingCellData.innerMessage.timestamp timeIntervalSince1970])) {
  1682. if (voiceMsg != playingCellData) {
  1683. [neverHitsPlayVoiceQueue addObject:voiceMsg];
  1684. }
  1685. }
  1686. }
  1687. }
  1688. return neverHitsPlayVoiceQueue;
  1689. }
  1690. - (void)showImageMessage:(TUIImageMessageCell *)cell {
  1691. [self hideKeyboardIfNeeded];
  1692. CGRect frame = [cell.thumb convertRect:cell.thumb.bounds toView:[UIApplication sharedApplication].delegate.window];
  1693. TUIMediaView *mediaView = [[TUIMediaView alloc] initWithFrame:CGRectMake(0, 0, Screen_Width, Screen_Height)];
  1694. [mediaView setThumb:cell.thumb frame:frame];
  1695. [mediaView setCurMessage:cell.messageData.innerMessage];
  1696. __weak typeof(self) weakSelf = self;
  1697. mediaView.onClose = ^{
  1698. [weakSelf didCloseMediaMessage:cell];
  1699. };
  1700. [self willShowMediaMessage:cell];
  1701. [[UIApplication sharedApplication].keyWindow addSubview:mediaView];
  1702. }
  1703. - (void)showVideoMessage:(TUIVideoMessageCell *)cell {
  1704. if (![cell.videoData isVideoExist]) {
  1705. [cell.videoData downloadVideo];
  1706. } else {
  1707. [self hideKeyboardIfNeeded];
  1708. CGRect frame = [cell.thumb convertRect:cell.thumb.bounds toView:[UIApplication sharedApplication].delegate.window];
  1709. TUIMediaView *mediaView = [[TUIMediaView alloc] initWithFrame:CGRectMake(0, 0, Screen_Width, Screen_Height)];
  1710. [mediaView setThumb:cell.thumb frame:frame];
  1711. [mediaView setCurMessage:cell.messageData.innerMessage];
  1712. __weak typeof(self) weakSelf = self;
  1713. mediaView.onClose = ^{
  1714. [weakSelf didCloseMediaMessage:cell];
  1715. };
  1716. [self willShowMediaMessage:cell];
  1717. [[UIApplication sharedApplication].keyWindow addSubview:mediaView];
  1718. }
  1719. }
  1720. - (void)showFileMessage:(TUIFileMessageCell *)cell {
  1721. [self hideKeyboardIfNeeded];
  1722. TUIFileMessageCellData *fileData = cell.fileData;
  1723. if (![fileData isLocalExist]) {
  1724. [fileData downloadFile];
  1725. return;
  1726. }
  1727. TUIFileViewController *file = [[TUIFileViewController alloc] init];
  1728. file.data = [cell fileData];
  1729. [self.navigationController pushViewController:file animated:YES];
  1730. }
  1731. - (void)showRelayMessage:(TUIMergeMessageCell *)cell {
  1732. TUIMergeMessageListController *mergeVc = [[TUIMergeMessageListController alloc] init];
  1733. mergeVc.delegate = self.delegate;
  1734. mergeVc.mergerElem = cell.mergeData.mergerElem;
  1735. mergeVc.conversationData = self.conversationData;
  1736. mergeVc.parentPageDataProvider = self.messageDataProvider;
  1737. __weak typeof(self) weakSelf = self;
  1738. mergeVc.willCloseCallback = ^() {
  1739. [weakSelf.tableView reloadData];
  1740. };
  1741. [self.navigationController pushViewController:mergeVc animated:YES];
  1742. }
  1743. - (void)showLinkMessage:(TUILinkCell *)cell {
  1744. TUILinkCellData *cellData = cell.customData;
  1745. if (cellData.link) {
  1746. [[UIApplication sharedApplication] openURL:[NSURL URLWithString:cellData.link]];
  1747. }
  1748. }
  1749. - (void)showOrderMessage:(TUIOrderCell *)cell {
  1750. TUIOrderCellData *cellData = cell.customData;
  1751. if (cellData.link) {
  1752. [TUITool openLinkWithURL:[NSURL URLWithString:cellData.link]];
  1753. }
  1754. }
  1755. - (void)showReplyMessage:(TUIReplyMessageCell *)cell {
  1756. }
  1757. - (void)willShowMediaMessage:(TUIMessageCell *)cell {
  1758. }
  1759. - (void)didCloseMediaMessage:(TUIMessageCell *)cell {
  1760. }
  1761. - (BOOL)isCurrentUserRoleSuperAdminInGroup {
  1762. return [self.messageDataProvider isCurrentUserRoleSuperAdminInGroup];
  1763. }
  1764. - (BOOL)isCurrentMessagePin:(NSString *)msgID {
  1765. return [self.messageDataProvider isCurrentMessagePin:msgID];
  1766. }
  1767. - (void)unPinGroupMessage:(V2TIMMessage *)innerMessage {
  1768. NSString *groupId = self.conversationData.groupID;
  1769. BOOL isPinned = [self.messageDataProvider isCurrentMessagePin:innerMessage.msgID];
  1770. BOOL pinOrUnpin = !isPinned;
  1771. [self.messageDataProvider pinGroupMessage:groupId message:innerMessage isPinned:pinOrUnpin succ:^{
  1772. } fail:^(int code, NSString *desc) {
  1773. }];
  1774. }
  1775. - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
  1776. [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
  1777. if (@available(iOS 16.0, *)) {
  1778. // send reloadview
  1779. [[NSNotificationCenter defaultCenter] postNotificationName:TUIMessageMediaViewDeviceOrientationChangeNotification object:nil];
  1780. } else {
  1781. // Fallback on earlier versions
  1782. }
  1783. }
  1784. @end