TUIMessageCell_Minimalist.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. //
  2. // TUIMessageCell_Minimalist.m
  3. // TXIMSDK_TUIKit_iOS
  4. //
  5. // Created by annidyfeng on 2019/5/22.
  6. // Copyright © 2023 Tencent. All rights reserved.
  7. //
  8. #import "TUIMessageCell_Minimalist.h"
  9. #import <TIMCommon/TIMCommonModel.h>
  10. #import <TIMCommon/TIMDefine.h>
  11. #import "NSString+TUIEmoji.h"
  12. #import <TUICore/TUICore.h>
  13. #import <TIMCommon/TUIRelationUserModel.h>
  14. @interface TUIMessageCell_Minimalist ()
  15. @property(nonatomic, assign) TUIMessageStatus status;
  16. @property(nonatomic, strong) NSMutableArray *animationImages;
  17. @end
  18. @implementation TUIMessageCell_Minimalist
  19. - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
  20. self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
  21. if (self) {
  22. _replyLineView = [[UIImageView alloc] initWithFrame:CGRectZero];
  23. _replyLineView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  24. [self.contentView addSubview:_replyLineView];
  25. [self.messageModifyRepliesButton.titleLabel setFont:[UIFont systemFontOfSize:12]];
  26. if(isRTL()) {
  27. self.messageModifyRepliesButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;
  28. }
  29. else {
  30. self.messageModifyRepliesButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
  31. }
  32. [self.messageModifyRepliesButton setTitleColor:RGBA(0, 95, 255, 1) forState:UIControlStateNormal];
  33. _msgStatusView = [[UIImageView alloc] initWithFrame:CGRectZero];
  34. _msgStatusView.contentMode = UIViewContentModeScaleAspectFit;
  35. _msgStatusView.layer.zPosition = FLT_MAX;
  36. UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onJumpToMessageInfoPage)];
  37. [_msgStatusView addGestureRecognizer:tap2];
  38. _msgStatusView.userInteractionEnabled = YES;
  39. [self.container addSubview:_msgStatusView];
  40. _msgTimeLabel = [[UILabel alloc] initWithFrame:CGRectZero];
  41. _msgTimeLabel.textColor = RGB(102, 102, 102);
  42. _msgTimeLabel.font = [UIFont systemFontOfSize:12];
  43. _msgTimeLabel.rtlAlignment = TUITextRTLAlignmentTrailing;
  44. _msgTimeLabel.layer.zPosition = FLT_MAX;
  45. [self.container addSubview:_msgTimeLabel];
  46. self.animationImages = [NSMutableArray array];
  47. for (int i = 1; i <= 45; ++i) {
  48. NSString *imageName = [NSString stringWithFormat:@"msg_status_sending_%d", i];
  49. NSString *imagePath = TUIChatImagePath_Minimalist(imageName);
  50. UIImage *image = [[TUIImageCache sharedInstance] getResourceFromCache:imagePath];
  51. [self.animationImages addObject:image];
  52. }
  53. _replyAvatarImageViews = [NSMutableArray array];
  54. }
  55. return self;
  56. }
  57. - (void)prepareReactTagUI:(UIView *)containerView {
  58. NSDictionary *param = @{TUICore_TUIChatExtension_ChatMessageReactPreview_Delegate: self};
  59. [TUICore raiseExtension:TUICore_TUIChatExtension_ChatMessageReactPreview_MinimalistExtensionID parentView:containerView param:param];
  60. }
  61. - (void)onJumpToMessageInfoPage {
  62. if (self.delegate && [self.delegate respondsToSelector:@selector(onJumpToMessageInfoPage:selectCell:)]) {
  63. [self.delegate onJumpToMessageInfoPage:self.messageData selectCell:self];
  64. }
  65. }
  66. + (BOOL)requiresConstraintBasedLayout {
  67. return YES;
  68. }
  69. // this is Apple's recommended place for adding/updating constraints
  70. - (void)updateConstraints {
  71. [super updateConstraints];
  72. TUIMessageCellLayout *cellLayout = self.messageData.cellLayout;
  73. BOOL isInComing = (self.messageData.direction == MsgDirectionIncoming);
  74. [self.nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
  75. if (isInComing) {
  76. make.leading.mas_equalTo(self.container.mas_leading).mas_offset(7);
  77. } else {
  78. make.trailing.mas_equalTo(self.container.mas_trailing);
  79. }
  80. if (self.messageData.showName) {
  81. make.width.mas_greaterThanOrEqualTo(20);
  82. make.height.mas_greaterThanOrEqualTo(20);
  83. } else {
  84. make.height.mas_equalTo(0);
  85. }
  86. make.top.mas_equalTo(self.avatarView.mas_top);
  87. }];
  88. [self.selectedIcon mas_remakeConstraints:^(MASConstraintMaker *make) {
  89. make.leading.mas_equalTo(self.contentView.mas_leading).mas_offset(3);
  90. make.centerY.mas_equalTo(self.container.mas_centerY);
  91. if (self.messageData.showCheckBox) {
  92. make.width.mas_equalTo(20);
  93. make.height.mas_equalTo(20);
  94. } else {
  95. make.size.mas_equalTo(CGSizeZero);
  96. }
  97. }];
  98. [self.timeLabel sizeToFit];
  99. [self.timeLabel mas_updateConstraints:^(MASConstraintMaker *make) {
  100. if (self.messageData.showMessageTime) {
  101. make.width.mas_equalTo(self.timeLabel.frame.size.width);
  102. make.height.mas_equalTo(self.timeLabel.frame.size.height);
  103. } else {
  104. make.width.mas_equalTo(0);
  105. make.height.mas_equalTo(0);
  106. }
  107. }];
  108. CGSize csize = [self.class getContentSize:self.messageData];
  109. CGFloat contentWidth = csize.width;
  110. CGFloat contentHeight = csize.height;
  111. if (self.messageData.direction == MsgDirectionIncoming) {
  112. self.avatarView.hidden = !self.messageData.showAvatar;
  113. [self.avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
  114. if (self.messageData.showCheckBox) {
  115. make.leading.mas_equalTo(self.selectedIcon.mas_trailing).mas_offset(cellLayout.avatarInsets.left);
  116. } else {
  117. make.leading.mas_equalTo(self.contentView.mas_leading).mas_offset(cellLayout.avatarInsets.left);
  118. }
  119. make.top.mas_equalTo(cellLayout.avatarInsets.top);
  120. make.size.mas_equalTo(cellLayout.avatarSize);
  121. }];
  122. [self.container mas_remakeConstraints:^(MASConstraintMaker *make) {
  123. make.leading.mas_equalTo(self.avatarView.mas_trailing).mas_offset(cellLayout.messageInsets.left);
  124. make.top.mas_equalTo(self.nameLabel.mas_bottom).mas_offset(cellLayout.messageInsets.top);
  125. make.width.mas_equalTo(contentWidth);
  126. make.height.mas_equalTo(contentHeight);
  127. }];
  128. CGRect indicatorFrame = self.indicator.frame;
  129. [self.indicator mas_remakeConstraints:^(MASConstraintMaker *make) {
  130. make.leading.mas_equalTo(self.container.mas_trailing).mas_offset(8);
  131. make.centerY.mas_equalTo(self.container.mas_centerY);
  132. make.size.mas_equalTo(indicatorFrame.size);
  133. }];
  134. self.retryView.frame = self.indicator.frame;
  135. self.readReceiptLabel.hidden = YES;
  136. } else {
  137. if (!self.messageData.showAvatar) {
  138. cellLayout.avatarSize = CGSizeZero;
  139. }
  140. [self.avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
  141. make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-cellLayout.avatarInsets.right);
  142. make.top.mas_equalTo(cellLayout.avatarInsets.top);
  143. make.size.mas_equalTo(cellLayout.avatarSize);
  144. }];
  145. [self.container mas_remakeConstraints:^(MASConstraintMaker *make) {
  146. make.trailing.mas_equalTo(self.avatarView.mas_leading).mas_offset(-cellLayout.messageInsets.right);
  147. make.top.mas_equalTo(self.nameLabel.mas_bottom).mas_offset(cellLayout.messageInsets.top);
  148. make.width.mas_equalTo(contentWidth);
  149. make.height.mas_equalTo(contentHeight);
  150. }];
  151. CGRect indicatorFrame = self.indicator.frame;
  152. [self.indicator mas_remakeConstraints:^(MASConstraintMaker *make) {
  153. make.trailing.mas_equalTo(self.container.mas_leading).mas_offset(-8);
  154. make.centerY.mas_equalTo(self.container.mas_centerY);
  155. make.size.mas_equalTo(indicatorFrame.size);
  156. }];
  157. self.retryView.frame = self.indicator.frame;
  158. [self.readReceiptLabel sizeToFit];
  159. [self.readReceiptLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
  160. make.bottom.mas_equalTo(self.container.mas_bottom);
  161. make.trailing.mas_equalTo(self.container.mas_leading).mas_offset(-8);
  162. make.size.mas_equalTo(self.readReceiptLabel.frame.size);
  163. }];
  164. }
  165. if (!self.messageModifyRepliesButton.isHidden) {
  166. self.messageModifyRepliesButton.mm_sizeToFit();
  167. CGFloat repliesBtnTextWidth = self.messageModifyRepliesButton.frame.size.width;
  168. [self.messageModifyRepliesButton mas_remakeConstraints:^(MASConstraintMaker *make) {
  169. if (isInComing) {
  170. make.leading.mas_equalTo(self.container.mas_leading);
  171. } else {
  172. make.trailing.mas_equalTo(self.container.mas_trailing);
  173. }
  174. make.top.mas_equalTo(self.container.mas_bottom);
  175. make.size.mas_equalTo(CGSizeMake(repliesBtnTextWidth + 10, 30));
  176. }];
  177. }
  178. if (self.messageData.showMessageModifyReplies && _replyAvatarImageViews.count > 0) {
  179. CGFloat lineViewW = 17;
  180. CGFloat avatarSize = 16;
  181. CGFloat repliesBtnW = kScale390(50);
  182. CGFloat avatarY = self.contentView.mm_h - (self.messageData.sameToNextMsgSender ? avatarSize : avatarSize * 2);
  183. if (self.messageData.direction == MsgDirectionIncoming) {
  184. UIImageView *preAvatarImageView = nil;
  185. for (int i = 0; i < _replyAvatarImageViews.count; ++i) {
  186. UIImageView *avatarView = _replyAvatarImageViews[i];
  187. if (i == 0) {
  188. preAvatarImageView = nil;
  189. }
  190. else {
  191. preAvatarImageView = _replyAvatarImageViews[i-1];
  192. }
  193. [avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
  194. if (i == 0) {
  195. make.leading.mas_equalTo(_replyLineView.mas_trailing);
  196. }
  197. else {
  198. make.leading.mas_equalTo(preAvatarImageView.mas_centerX);
  199. }
  200. make.top.mas_equalTo(avatarY);
  201. make.width.height.mas_equalTo(avatarSize);
  202. }];
  203. avatarView.layer.masksToBounds = YES;
  204. avatarView.layer.cornerRadius = avatarSize / 2.0;
  205. }
  206. }
  207. else {
  208. __block UIImageView *preAvatarImageView = nil;
  209. NSInteger count = _replyAvatarImageViews.count;
  210. for (NSInteger i = (count - 1); i >=0; i--) {
  211. UIImageView *avatarView = _replyAvatarImageViews[i];
  212. [avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
  213. if (!preAvatarImageView) {
  214. make.trailing.mas_equalTo(self.messageModifyRepliesButton.mas_leading);
  215. }
  216. else {
  217. make.trailing.mas_equalTo(preAvatarImageView.mas_centerX);
  218. }
  219. make.top.mas_equalTo(avatarY);
  220. make.width.height.mas_equalTo(avatarSize);
  221. }];
  222. avatarView.layer.masksToBounds = YES;
  223. avatarView.layer.cornerRadius = avatarSize / 2.0;
  224. preAvatarImageView = avatarView;
  225. }
  226. }
  227. UIImageView *lastAvatarImageView = _replyAvatarImageViews.lastObject;
  228. [self.messageModifyRepliesButton mas_remakeConstraints:^(MASConstraintMaker *make) {
  229. if (self.messageData.direction == MsgDirectionIncoming) {
  230. make.leading.mas_equalTo(lastAvatarImageView.mas_trailing);
  231. }
  232. else {
  233. make.trailing.mas_equalTo(_replyLineView.mas_leading);
  234. }
  235. make.top.mas_equalTo(avatarY);
  236. make.width.mas_equalTo(repliesBtnW);
  237. make.height.mas_equalTo(avatarSize);
  238. }];
  239. [_replyLineView mas_remakeConstraints:^(MASConstraintMaker *make) {
  240. if (self.messageData.direction == MsgDirectionIncoming) {
  241. make.leading.mas_equalTo(self.container.mas_leading).mas_offset(- 1);
  242. }
  243. else {
  244. make.trailing.mas_equalTo(self.container.mas_trailing);
  245. }
  246. make.top.mas_equalTo(CGRectGetMaxY(self.container.frame) - 14);
  247. make.width.mas_equalTo(lineViewW);
  248. make.bottom.mas_equalTo(self.messageModifyRepliesButton.mas_centerY);
  249. }];
  250. } else {
  251. _replyLineView.frame = CGRectZero;
  252. self.messageModifyRepliesButton.frame = CGRectZero;
  253. }
  254. [_msgTimeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
  255. make.width.mas_equalTo(38);
  256. make.height.mas_equalTo(self.messageData.msgStatusSize.height);
  257. make.bottom.mas_equalTo(self.container).mas_offset(-kScale390(9));
  258. make.trailing.mas_equalTo(self.container).mas_offset(-kScale390(16));
  259. }];
  260. [_msgStatusView mas_makeConstraints:^(MASConstraintMaker *make) {
  261. make.width.mas_equalTo(16);
  262. make.height.mas_equalTo(self.messageData.msgStatusSize.height);
  263. make.bottom.mas_equalTo(self.msgTimeLabel);
  264. make.trailing.mas_equalTo(_msgTimeLabel.mas_leading);
  265. }];
  266. }
  267. - (void)fillWithData:(TUIMessageCellData *)data {
  268. [super fillWithData:data];
  269. self.readReceiptLabel.hidden = YES;
  270. self.messageModifyRepliesButton.hidden = YES;
  271. [self.messageModifyRepliesButton setImage:nil forState:UIControlStateNormal];
  272. //react
  273. [self prepareReactTagUI:self.contentView];
  274. if (_replyAvatarImageViews.count > 0) {
  275. for (UIImageView *imageView in _replyAvatarImageViews) {
  276. [imageView removeFromSuperview];
  277. }
  278. [_replyAvatarImageViews removeAllObjects];
  279. }
  280. _replyLineView.hidden = YES;
  281. if (data.showMessageModifyReplies) {
  282. _replyLineView.hidden = NO;
  283. self.messageModifyRepliesButton.hidden = NO;
  284. // line
  285. UIImage *lineImage = nil;
  286. if (self.messageData.direction == MsgDirectionIncoming) {
  287. lineImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_reply_line_income")];
  288. } else {
  289. lineImage = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_reply_line_outcome")];
  290. }
  291. lineImage = [lineImage rtl_imageFlippedForRightToLeftLayoutDirection];
  292. UIEdgeInsets ei = UIEdgeInsetsFromString(@"{10,0,20,0}");
  293. ei = rtlEdgeInsetsWithInsets(ei);
  294. _replyLineView.image = [lineImage resizableImageWithCapInsets:ei resizingMode:UIImageResizingModeStretch];
  295. // avtar
  296. NSInteger avatarCount = 0;
  297. NSInteger avatarMaxCount = 4;
  298. NSMutableDictionary *existSenderMap = [NSMutableDictionary dictionary];
  299. for (NSDictionary *senderMap in self.messageData.messageModifyReplies) {
  300. NSString *sender = senderMap[@"messageSender"];
  301. TUIRelationUserModel *userModel = self.messageData.additionalUserInfoResult[sender];
  302. NSURL *headUrl = [NSURL URLWithString:userModel.faceURL];
  303. NSString *existSender = existSenderMap[@"messageSender"];
  304. if (!sender || [sender isEqualToString:existSender]) {
  305. //exist sender head not add again
  306. continue;
  307. }
  308. UIImageView *avatarView = [[UIImageView alloc] init];
  309. if (avatarCount < avatarMaxCount - 1) {
  310. existSenderMap[@"messageSender"] = sender;
  311. [avatarView sd_setImageWithURL:headUrl placeholderImage:DefaultAvatarImage];
  312. } else {
  313. [avatarView setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_reply_more_icon")]];
  314. }
  315. [_replyAvatarImageViews addObject:avatarView];
  316. [self.contentView addSubview:avatarView];
  317. if (++avatarCount >= avatarMaxCount) {
  318. break;
  319. }
  320. }
  321. }
  322. _msgTimeLabel.text = [TUITool convertDateToHMStr:self.messageData.innerMessage.timestamp];
  323. self.indicator.hidden = YES;
  324. _msgStatusView.hidden = YES;
  325. self.readReceiptLabel.hidden = YES;
  326. if (self.messageData.direction == MsgDirectionOutgoing) {
  327. self.status = TUIMessageStatus_Unkown;
  328. if (self.messageData.status == Msg_Status_Sending || self.messageData.status == Msg_Status_Sending_2) {
  329. [self updateMessageStatus:TUIMessageStatus_Sending];
  330. } else if (self.messageData.status == Msg_Status_Succ) {
  331. [self updateMessageStatus:TUIMessageStatus_Send_Succ];
  332. }
  333. [self updateReadLabelText];
  334. }
  335. // tell constraints they need updating
  336. [self setNeedsUpdateConstraints];
  337. // update constraints now so we can animate the change
  338. [self updateConstraintsIfNeeded];
  339. [self layoutIfNeeded];
  340. }
  341. - (void)updateReadLabelText {
  342. if (self.messageData.innerMessage.groupID.length > 0) {
  343. // group message
  344. if (self.messageData.messageReceipt == nil) {
  345. // haven't received the message receipt yet
  346. return;
  347. }
  348. NSInteger readCount = self.messageData.messageReceipt.readCount;
  349. NSInteger unreadCount = self.messageData.messageReceipt.unreadCount;
  350. if (unreadCount == 0) {
  351. // All read
  352. [self updateMessageStatus:TUIMessageStatus_All_People_Read];
  353. } else if (readCount > 0) {
  354. // Some read
  355. [self updateMessageStatus:TUIMessageStatus_Some_People_Read];
  356. }
  357. } else {
  358. // c2c message
  359. BOOL isPeerRead = self.messageData.messageReceipt.isPeerRead;
  360. if (isPeerRead) {
  361. [self updateMessageStatus:TUIMessageStatus_All_People_Read];
  362. }
  363. }
  364. }
  365. - (void)updateMessageStatus:(TUIMessageStatus)status {
  366. if (status <= self.status) {
  367. return;
  368. }
  369. if (self.messageData.showReadReceipt && self.messageData.direction == MsgDirectionOutgoing && self.messageData.innerMessage.needReadReceipt &&
  370. (self.messageData.innerMessage.userID || self.messageData.innerMessage.groupID)) {
  371. _msgStatusView.hidden = NO;
  372. _msgStatusView.image = nil;
  373. }
  374. if (_msgStatusView.isAnimating) {
  375. [_msgStatusView stopAnimating];
  376. _msgStatusView.animationImages = nil;
  377. }
  378. switch (status) {
  379. case TUIMessageStatus_Sending: {
  380. _msgStatusView.animationImages = self.animationImages;
  381. [_msgStatusView startAnimating];
  382. } break;
  383. case TUIMessageStatus_Send_Succ: {
  384. _msgStatusView.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_status_send_succ")];
  385. } break;
  386. case TUIMessageStatus_Some_People_Read: {
  387. _msgStatusView.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_status_some_people_read")];
  388. } break;
  389. case TUIMessageStatus_All_People_Read: {
  390. _msgStatusView.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath_Minimalist(@"msg_status_all_people_read")];
  391. } break;
  392. default:
  393. break;
  394. }
  395. self.status = status;
  396. }
  397. - (void)layoutSubviews {
  398. [super layoutSubviews];
  399. }
  400. #pragma mark - TUIMessageCellProtocol
  401. + (CGFloat)getHeight:(TUIMessageCellData *)data withWidth:(CGFloat)width {
  402. NSAssert([data isKindOfClass:TUIMessageCellData.class], @"data must be kind of TUIMessageCellData");
  403. CGFloat height = [super getHeight:data withWidth:width];
  404. if (data.sameToNextMsgSender) {
  405. height -= kScale375(16);
  406. }
  407. return height;
  408. }
  409. @end