TUIMessageCell.m 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. //
  2. // TUIMessageCell.m
  3. // UIKit
  4. //
  5. // Created by kennethmiao on 2018/9/17.
  6. // Copyright © 2018 Tencent. All rights reserved.
  7. //
  8. #import "TUIMessageCell.h"
  9. #import <TIMCommon/TIMDefine.h>
  10. #import <TUICore/TUIThemeManager.h>
  11. #import <TUICore/TUITool.h>
  12. #import "NSString+TUIEmoji.h"
  13. #import "TUISystemMessageCellData.h"
  14. #import <TUICore/TUICore.h>
  15. #import <TIMCommon/MOHeadNormalView.h>
  16. @interface TUIMessageCell () <CAAnimationDelegate>
  17. @property(nonatomic, strong) TUIMessageCellData *messageData;
  18. @property (nonatomic, strong) MOHeadNormalView *headBgView;//头像框
  19. @end
  20. @implementation TUIMessageCell
  21. #pragma mark - Life cycle
  22. - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
  23. self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
  24. if (self) {
  25. [self setupSubViews];
  26. [self setupRAC];
  27. }
  28. return self;
  29. }
  30. - (void)setupSubViews {
  31. // head
  32. _avatarView = [[UIImageView alloc] init];
  33. _avatarView.contentMode = UIViewContentModeScaleAspectFit;
  34. [self.contentView addSubview:_avatarView];
  35. UITapGestureRecognizer *tap1 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onSelectMessageAvatar:)];
  36. [_avatarView addGestureRecognizer:tap1];
  37. UILongPressGestureRecognizer *tap2 = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onLongSelectMessageAvatar:)];
  38. [_avatarView addGestureRecognizer:tap2];
  39. [_avatarView setUserInteractionEnabled:YES];
  40. [self.contentView addSubview:self.headBgView];
  41. // nameLabel
  42. _nameLabel = [[UILabel alloc] init];
  43. _nameLabel.font = [self fontWithSize:13];
  44. _nameLabel.textColor = [UIColor d_systemGrayColor];
  45. [self.contentView addSubview:_nameLabel];
  46. // container
  47. _container = [[UIView alloc] init];
  48. _container.backgroundColor = [UIColor clearColor];
  49. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onSelectMessage:)];
  50. tap.cancelsTouchesInView = NO;
  51. [_container addGestureRecognizer:tap];
  52. UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onLongPress:)];
  53. [_container addGestureRecognizer:longPress];
  54. [self.contentView addSubview:_container];
  55. // indicator
  56. _indicator = [[UIActivityIndicatorView alloc] init];
  57. _indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
  58. [_indicator sizeToFit];
  59. [self.contentView addSubview:_indicator];
  60. // error
  61. _retryView = [[UIImageView alloc] init];
  62. _retryView.userInteractionEnabled = YES;
  63. UITapGestureRecognizer *resendTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onRetryMessage:)];
  64. [_retryView addGestureRecognizer:resendTap];
  65. [self.contentView addSubview:_retryView];
  66. // messageModifyRepliesLabel
  67. _messageModifyRepliesButton = [[TUIFitButton alloc] initWithFrame:CGRectMake(0, 0, 12, 12)];
  68. _messageModifyRepliesButton.imageSize = CGSizeMake(12, 12);
  69. [_messageModifyRepliesButton addTarget:self action:@selector(onJumpToRepliesDetailPage:) forControlEvents:UIControlEventTouchUpInside];
  70. [_messageModifyRepliesButton.titleLabel setFont:[self fontWithSize:12]];
  71. [_messageModifyRepliesButton setTitleColor:TIMCommonDynamicColor(@"chat_message_read_name_date_text_color", @"#999999") forState:UIControlStateNormal];
  72. [_messageModifyRepliesButton setImage:TIMCommonBundleThemeImage(@"chat_messageReplyIcon_img", @"messageReplyIcon") forState:UIControlStateNormal];
  73. [self.contentView addSubview:_messageModifyRepliesButton];
  74. _readReceiptLabel = [[UILabel alloc] init];
  75. _readReceiptLabel.hidden = YES;
  76. _readReceiptLabel.font = [self fontWithSize:12];
  77. _readReceiptLabel.textColor = TIMCommonDynamicColor(@"chat_message_read_status_text_gray_color", @"#BBBBBB");
  78. _readReceiptLabel.lineBreakMode = NSLineBreakByCharWrapping;
  79. UITapGestureRecognizer *showReadReceiptTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onSelectReadReceipt:)];
  80. [_readReceiptLabel addGestureRecognizer:showReadReceiptTap];
  81. _readReceiptLabel.userInteractionEnabled = YES;
  82. [self.contentView addSubview:_readReceiptLabel];
  83. // selectedIcon
  84. _selectedIcon = [[UIImageView alloc] init];
  85. [self.contentView addSubview:_selectedIcon];
  86. // selectedView
  87. _selectedView = [UIButton buttonWithType:UIButtonTypeCustom];
  88. _selectedView.backgroundColor = [UIColor clearColor];
  89. [_selectedView addTarget:self action:@selector(onSelectMessage:) forControlEvents:UIControlEventTouchUpInside];
  90. [self.contentView addSubview:_selectedView];
  91. // timeLabel
  92. _timeLabel = [[UILabel alloc] init];
  93. _timeLabel.textColor = [UIColor darkGrayColor];
  94. _timeLabel.font = [self fontWithSize:11.0];
  95. [self.contentView addSubview:_timeLabel];
  96. self.selectionStyle = UITableViewCellSelectionStyleNone;
  97. self.backgroundColor = UIColor.clearColor;
  98. self.contentView.backgroundColor = UIColor.clearColor;
  99. [self makeConstraints];
  100. }
  101. - (void)makeConstraints {
  102. [self.headBgView mas_makeConstraints:^(MASConstraintMaker *make) {
  103. make.centerX.equalTo(self.avatarView);
  104. make.centerY.equalTo(self.avatarView);
  105. make.width.height.equalTo(@40.0);
  106. }];
  107. self.headBgView.headImgWidth = 40.0;
  108. [self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
  109. make.leading.mas_equalTo(_container.mas_leading).mas_offset(7);
  110. make.top.mas_equalTo(self.avatarView.mas_top);
  111. make.width.mas_equalTo(1);
  112. make.height.mas_equalTo(20);
  113. }];
  114. [self.selectedIcon mas_makeConstraints:^(MASConstraintMaker *make) {
  115. make.leading.mas_equalTo(self.contentView.mas_leading).mas_offset(3);
  116. make.top.mas_equalTo(self.avatarView.mas_centerY).mas_offset(-10);
  117. make.width.mas_equalTo(20);
  118. make.height.mas_equalTo(20);
  119. }];
  120. [self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
  121. make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-10);
  122. make.top.mas_equalTo(self.avatarView);
  123. make.width.mas_greaterThanOrEqualTo(10);
  124. make.height.mas_equalTo(10);
  125. }];
  126. [self.selectedView mas_makeConstraints:^(MASConstraintMaker *make) {
  127. make.edges.mas_equalTo(self.contentView);
  128. }];
  129. }
  130. + (BOOL)requiresConstraintBasedLayout {
  131. return YES;
  132. }
  133. // this is Apple's recommended place for adding/updating constraints
  134. - (void)updateConstraints {
  135. TUIMessageCellLayout *cellLayout = self.messageData.cellLayout;
  136. BOOL isInComing = (self.messageData.direction == MsgDirectionIncoming);
  137. [self.nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
  138. if (isInComing) {
  139. make.leading.mas_equalTo(_container.mas_leading).mas_offset(7);
  140. make.trailing.mas_equalTo(self.contentView).mas_offset(-7);
  141. } else {
  142. make.leading.mas_equalTo(self.contentView).mas_offset(7);
  143. make.trailing.mas_equalTo(self.container.mas_trailing);
  144. }
  145. if (self.messageData.showName) {
  146. make.width.mas_greaterThanOrEqualTo(20);
  147. make.height.mas_greaterThanOrEqualTo(20);
  148. } else {
  149. make.height.mas_equalTo(0);
  150. }
  151. make.top.mas_equalTo(self.avatarView.mas_top);
  152. }];
  153. [self.selectedIcon mas_updateConstraints:^(MASConstraintMaker *make) {
  154. if (self.messageData.showCheckBox) {
  155. make.width.mas_equalTo(20);
  156. make.height.mas_equalTo(20);
  157. } else {
  158. make.size.mas_equalTo(CGSizeZero);
  159. }
  160. }];
  161. [self.timeLabel sizeToFit];
  162. [self.timeLabel mas_updateConstraints:^(MASConstraintMaker *make) {
  163. if (self.messageData.showMessageTime) {
  164. make.width.mas_equalTo(self.timeLabel.frame.size.width);
  165. make.height.mas_equalTo(self.timeLabel.frame.size.height);
  166. } else {
  167. make.width.mas_equalTo(0);
  168. make.height.mas_equalTo(0);
  169. }
  170. }];
  171. CGSize csize = [self.class getContentSize:self.messageData];
  172. CGFloat contentWidth = csize.width;
  173. CGFloat contentHeight = csize.height;
  174. if (!CGSizeEqualToSize(self.messageData.messageContainerAppendSize, CGSizeZero)) {
  175. /**
  176. * Taking the maximum width between the "emoji reply message" and the text content
  177. */
  178. contentWidth = MAX(self.messageData.messageContainerAppendSize.width, csize.width);
  179. /**
  180. * Limit the maximum width to Screen_Width *0.25 * 3
  181. */
  182. contentWidth = MIN(contentWidth, Screen_Width * 0.25 * 3);
  183. contentHeight = csize.height + self.messageData.messageContainerAppendSize.height;
  184. }
  185. if (self.messageData.direction == MsgDirectionIncoming) {
  186. self.avatarView.hidden = !self.messageData.showAvatar;
  187. [self.avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
  188. if (self.messageData.showCheckBox) {
  189. make.leading.mas_equalTo(self.selectedIcon.mas_trailing).mas_offset(cellLayout.avatarInsets.left);
  190. } else {
  191. make.leading.mas_equalTo(self.contentView.mas_leading).mas_offset(cellLayout.avatarInsets.left);
  192. }
  193. make.top.mas_equalTo(cellLayout.avatarInsets.top);
  194. make.size.mas_equalTo(cellLayout.avatarSize);
  195. }];
  196. [self.container mas_remakeConstraints:^(MASConstraintMaker *make) {
  197. make.leading.mas_equalTo(self.avatarView.mas_trailing).mas_offset(cellLayout.messageInsets.left);
  198. make.top.mas_equalTo(self.nameLabel.mas_bottom).mas_offset(cellLayout.messageInsets.top);
  199. make.width.mas_equalTo(contentWidth);
  200. make.height.mas_equalTo(contentHeight);
  201. }];
  202. CGRect indicatorFrame = self.indicator.frame;
  203. [self.indicator mas_remakeConstraints:^(MASConstraintMaker *make) {
  204. make.leading.mas_equalTo(self.container.mas_trailing).mas_offset(8);
  205. make.centerY.mas_equalTo(self.container.mas_centerY);
  206. make.size.mas_equalTo(indicatorFrame.size);
  207. }];
  208. self.retryView.frame = self.indicator.frame;
  209. self.readReceiptLabel.hidden = YES;
  210. } else {
  211. if (!self.messageData.showAvatar) {
  212. cellLayout.avatarSize = CGSizeZero;
  213. }
  214. [self.avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
  215. make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-cellLayout.avatarInsets.right);
  216. make.top.mas_equalTo(cellLayout.avatarInsets.top);
  217. make.size.mas_equalTo(cellLayout.avatarSize);
  218. }];
  219. [self.container mas_remakeConstraints:^(MASConstraintMaker *make) {
  220. make.trailing.mas_equalTo(self.avatarView.mas_leading).mas_offset(-cellLayout.messageInsets.right);
  221. make.top.mas_equalTo(self.nameLabel.mas_bottom).mas_offset(cellLayout.messageInsets.top);
  222. make.width.mas_equalTo(contentWidth);
  223. make.height.mas_equalTo(contentHeight);
  224. }];
  225. CGRect indicatorFrame = self.indicator.frame;
  226. [self.indicator mas_remakeConstraints:^(MASConstraintMaker *make) {
  227. make.trailing.mas_equalTo(self.container.mas_leading).mas_offset(-8);
  228. make.centerY.mas_equalTo(self.container.mas_centerY);
  229. make.size.mas_equalTo(indicatorFrame.size);
  230. }];
  231. self.retryView.frame = self.indicator.frame;
  232. [self.readReceiptLabel sizeToFit];
  233. [self.readReceiptLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
  234. make.bottom.mas_equalTo(self.container.mas_bottom);
  235. make.trailing.mas_equalTo(self.container.mas_leading).mas_offset(-8);
  236. make.size.mas_equalTo(self.readReceiptLabel.frame.size);
  237. }];
  238. }
  239. [self.headBgView mas_remakeConstraints:^(MASConstraintMaker *make) {
  240. make.centerX.equalTo(self.avatarView);
  241. make.centerY.equalTo(self.avatarView);
  242. make.width.height.equalTo(@40.0);
  243. }];
  244. self.headBgView.headImgWidth = 40.0;
  245. if (!self.messageModifyRepliesButton.isHidden) {
  246. self.messageModifyRepliesButton.mm_sizeToFit();
  247. CGFloat repliesBtnTextWidth = self.messageModifyRepliesButton.frame.size.width;
  248. [self.messageModifyRepliesButton mas_remakeConstraints:^(MASConstraintMaker *make) {
  249. if (isInComing) {
  250. make.leading.mas_equalTo(self.container.mas_leading);
  251. } else {
  252. make.trailing.mas_equalTo(self.container.mas_trailing);
  253. }
  254. make.top.mas_equalTo(self.container.mas_bottom);
  255. make.size.mas_equalTo(CGSizeMake(repliesBtnTextWidth + 10, 30));
  256. }];
  257. }
  258. // according to apple super should be called at end of method
  259. [super updateConstraints];
  260. }
  261. - (void)layoutSubviews {
  262. [super layoutSubviews];
  263. }
  264. - (void)setupRAC {
  265. @weakify(self);
  266. [RACObserve(self, readReceiptLabel.text) subscribeNext:^(id _Nullable x) {
  267. @strongify(self);
  268. if ([self shouldHighlightReadReceiptLabel]) {
  269. self.readReceiptLabel.textColor = TIMCommonDynamicColor(@"chat_message_read_status_text_color", @"#147AFF");
  270. } else {
  271. self.readReceiptLabel.textColor = TIMCommonDynamicColor(@"chat_message_read_status_text_gray_color", @"#BBBBBB");
  272. }
  273. }];
  274. }
  275. - (void)prepareForReuse {
  276. [super prepareForReuse];
  277. /**
  278. * In the future, any UI problems caused by reuse can be solved by coding here.
  279. */
  280. /**
  281. * Once the message is reused, it means that a new message is about to appear, and the label content is changed to empty string.
  282. */
  283. _readReceiptLabel.text = @"";
  284. _readReceiptLabel.hidden = YES;
  285. }
  286. #pragma mark - Public
  287. - (void)fillWithData:(TUIMessageCellData *)data {
  288. [super fillWithData:data];
  289. self.messageData = data;
  290. [self loadAvatar:data];
  291. if (self.messageData.showName) {
  292. _nameLabel.hidden = NO;
  293. } else {
  294. _nameLabel.hidden = YES;
  295. }
  296. if (self.messageData.showCheckBox) {
  297. _selectedIcon.hidden = NO;
  298. _selectedView.hidden = NO;
  299. } else {
  300. _selectedIcon.hidden = YES;
  301. _selectedView.hidden = YES;
  302. }
  303. if ([TUIConfig defaultConfig].avatarType == TAvatarTypeRounded) {
  304. self.avatarView.layer.masksToBounds = YES;
  305. self.avatarView.layer.cornerRadius = data.cellLayout.avatarSize.height / 2;
  306. } else if ([TUIConfig defaultConfig].avatarType == TAvatarTypeRadiusCorner) {
  307. self.avatarView.layer.masksToBounds = YES;
  308. self.avatarView.layer.cornerRadius = [TUIConfig defaultConfig].avatarCornerRadius;
  309. }
  310. self.nameLabel.text = data.senderName;
  311. if (data.direction == MsgDirectionIncoming) {
  312. self.nameLabel.textColor = self.class.incommingNameColor;
  313. self.nameLabel.font = self.class.incommingNameFont;
  314. } else {
  315. self.nameLabel.textColor = self.class.outgoingNameColor;
  316. self.nameLabel.font = self.class.outgoingNameFont;
  317. }
  318. self.retryView.image = [UIImage imageNamed:TUIChatImagePath(@"msg_error")];
  319. if (data.status == Msg_Status_Fail) {
  320. [_indicator stopAnimating];
  321. _readReceiptLabel.hidden = YES;
  322. self.retryView.hidden = NO;
  323. } else {
  324. if (data.status == Msg_Status_Sending_2) {
  325. [_indicator startAnimating];
  326. _readReceiptLabel.hidden = YES;
  327. } else if (data.status == Msg_Status_Succ) {
  328. [_indicator stopAnimating];
  329. /**
  330. * The message is sent successfully, indicating that the indicator and error are no longer displayed on the label, and the read receipt label can be
  331. * displayed.
  332. */
  333. if (self.messageData.showReadReceipt && self.messageData.direction == MsgDirectionOutgoing && self.messageData.innerMessage.needReadReceipt &&
  334. (self.messageData.innerMessage.userID || self.messageData.innerMessage.groupID) &&
  335. ![self.messageData isKindOfClass:TUISystemMessageCellData.class]) {
  336. [self updateReadLabelText];
  337. _readReceiptLabel.hidden = NO;
  338. }
  339. } else if (data.status == Msg_Status_Sending) {
  340. [_indicator startAnimating];
  341. _readReceiptLabel.hidden = YES;
  342. }
  343. self.retryView.hidden = YES;
  344. }
  345. self.messageModifyRepliesButton.hidden = !data.showMessageModifyReplies;
  346. if (data.showMessageModifyReplies) {
  347. NSString *title = [NSString stringWithFormat:@"%ld%@", data.messageModifyReplies.count, TIMCommonLocalizableString(TUIKitRepliesNum)];
  348. [self.messageModifyRepliesButton setTitle:title forState:UIControlStateNormal];
  349. [self.messageModifyRepliesButton sizeToFit];
  350. [self.messageModifyRepliesButton setNeedsUpdateConstraints];
  351. [self.messageModifyRepliesButton updateConstraintsIfNeeded];
  352. [self.messageModifyRepliesButton layoutIfNeeded];
  353. }
  354. NSString *imageName = (data.showCheckBox && data.selected) ? TIMCommonImagePath(@"icon_select_selected") : TIMCommonImagePath(@"icon_select_normal");
  355. self.selectedIcon.image = [UIImage imageNamed:imageName];
  356. _timeLabel.text = [TUITool convertDateToStr:data.innerMessage.timestamp];
  357. [_timeLabel sizeToFit];
  358. _timeLabel.hidden = !data.showMessageTime;
  359. /**
  360. * Text highlighting - asynchronous operations are here to keep the order of execution consistent with subclasses
  361. */
  362. __weak typeof(self) weakSelf = self;
  363. dispatch_async(dispatch_get_main_queue(), ^{
  364. [weakSelf highlightWhenMatchKeyword:data.highlightKeyword];
  365. });
  366. if(![data isKindOfClass:[TUISystemMessageCellData class]]){
  367. if(data.headdress.length > 0){
  368. self.headBgView.hidden = NO;
  369. self.headBgView.isLiving = NO;
  370. self.headBgView.effectType = data.headgearType;
  371. self.headBgView.imgUrlStr = data.headdress;
  372. }
  373. else{
  374. self.headBgView.hidden = YES;
  375. }
  376. }
  377. else{
  378. self.headBgView.hidden = YES;
  379. }
  380. // tell constraints they need updating
  381. [self setNeedsUpdateConstraints];
  382. // update constraints now so we can animate the change
  383. [self updateConstraintsIfNeeded];
  384. [self layoutIfNeeded];
  385. }
  386. - (void)loadAvatar:(TUIMessageCellData *)data {
  387. [self.avatarView setImage:DefaultAvatarImage];
  388. @weakify(self);
  389. [[[RACObserve(data, avatarUrl) takeUntil:self.rac_prepareForReuseSignal] ignore:nil] subscribeNext:^(NSURL *url) {
  390. @strongify(self);
  391. [self.avatarView sd_setImageWithURL:url placeholderImage:DefaultAvatarImage];
  392. }];
  393. if (data.isUseMsgReceiverAvatar) {
  394. NSString *userId = @"";
  395. if ([data.innerMessage.sender isEqualToString:V2TIMManager.sharedInstance.getLoginUser]) {
  396. userId = data.innerMessage.userID;
  397. } else {
  398. userId = V2TIMManager.sharedInstance.getLoginUser;
  399. }
  400. [V2TIMManager.sharedInstance getUsersInfo:@[ userId?:@"" ]
  401. succ:^(NSArray<V2TIMUserFullInfo *> *infoList) {
  402. @strongify(self);
  403. V2TIMUserFullInfo *info = infoList.firstObject;
  404. if (info && [data isEqual:self.messageData]) {
  405. data.avatarUrl = [NSURL URLWithString:info.faceURL];
  406. [self.avatarView sd_setImageWithURL:data.avatarUrl placeholderImage:DefaultAvatarImage];
  407. }
  408. }
  409. fail:^(int code, NSString *desc){
  410. }];
  411. }
  412. }
  413. - (void)highlightWhenMatchKeyword:(NSString *)keyword {
  414. static NSString *const key = @"highlightAnimation";
  415. if (keyword && keyword.length) {
  416. if (self.highlightAnimating) {
  417. return;
  418. }
  419. self.highlightAnimating = YES;
  420. CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"backgroundColor"];
  421. animation.repeatCount = 3;
  422. animation.values = @[
  423. (id)[[UIColor orangeColor] colorWithAlphaComponent:0.2].CGColor,
  424. (id)[[UIColor orangeColor] colorWithAlphaComponent:0.5].CGColor,
  425. (id)[[UIColor orangeColor] colorWithAlphaComponent:0.2].CGColor,
  426. ];
  427. animation.duration = 0.5;
  428. animation.removedOnCompletion = YES;
  429. animation.delegate = self;
  430. [self.highlightAnimateView.layer addAnimation:animation forKey:key];
  431. } else {
  432. [self.highlightAnimateView.layer removeAnimationForKey:key];
  433. }
  434. }
  435. - (void)updateReadLabelText {
  436. if (self.messageData.innerMessage.groupID.length > 0) {
  437. // group message
  438. NSString *text = TIMCommonLocalizableString(Unread);
  439. if (self.messageData.messageReceipt == nil) {
  440. // haven't received the message receipt yet
  441. return;
  442. }
  443. NSInteger readCount = self.messageData.messageReceipt.readCount;
  444. NSInteger unreadCount = self.messageData.messageReceipt.unreadCount;
  445. if (unreadCount == 0) {
  446. // show "All read"
  447. text = TIMCommonLocalizableString(TUIKitMessageReadAllRead);
  448. } else if (readCount > 0) {
  449. // show "x read"
  450. text = [NSString stringWithFormat:@"%ld %@", (long)readCount, TIMCommonLocalizableString(TUIKitMessageReadPartRead)];
  451. }
  452. self.readReceiptLabel.text = text;
  453. } else {
  454. // c2c message
  455. BOOL isPeerRead = self.messageData.messageReceipt.isPeerRead;
  456. NSString *text = isPeerRead ? TIMCommonLocalizableString(TUIKitMessageReadC2CRead) : TIMCommonLocalizableString(TUIKitMessageReadC2CUnRead);
  457. self.readReceiptLabel.text = text;
  458. }
  459. [self.readReceiptLabel sizeToFit];
  460. [self.readReceiptLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
  461. make.bottom.mas_equalTo(self.container.mas_bottom);
  462. make.trailing.mas_equalTo(self.container.mas_leading).mas_offset(-8);
  463. make.size.mas_equalTo(self.readReceiptLabel.frame.size);
  464. }];
  465. self.readReceiptLabel.textColor = [self shouldHighlightReadReceiptLabel] ? TIMCommonDynamicColor(@"chat_message_read_status_text_color", @"#147AFF")
  466. : TIMCommonDynamicColor(@"chat_message_read_status_text_gray_color", @"#BBBBBB");
  467. }
  468. - (UIView *)highlightAnimateView {
  469. return self.container;
  470. }
  471. #pragma mark - TUIMessageCellProtocol
  472. + (CGFloat)getEstimatedHeight:(TUIMessageCellData *)data {
  473. return 60.f;
  474. }
  475. + (CGFloat)getHeight:(TUIMessageCellData *)data withWidth:(CGFloat)width {
  476. CGFloat height = 0;
  477. if (data.showName) height += kScale375(20);
  478. if (data.showMessageModifyReplies) height += kScale375(22);
  479. if (data.messageContainerAppendSize.height > 0) {
  480. height += data.messageContainerAppendSize.height;
  481. }
  482. CGSize containerSize = [self getContentSize:data];
  483. height += containerSize.height;
  484. height += data.cellLayout.messageInsets.top;
  485. height += data.cellLayout.messageInsets.bottom;
  486. if (height < 55) height = 55;
  487. return height;
  488. }
  489. + (CGSize)getContentSize:(TUIMessageCellData *)data {
  490. return CGSizeZero;
  491. }
  492. #pragma mark - Private
  493. - (void)animationDidStart:(CAAnimation *)anim {
  494. self.highlightAnimating = YES;
  495. }
  496. - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
  497. self.highlightAnimating = NO;
  498. }
  499. #pragma mark-- Event
  500. - (void)onLongPress:(UIGestureRecognizer *)recognizer {
  501. if ([recognizer isKindOfClass:[UILongPressGestureRecognizer class]] && recognizer.state == UIGestureRecognizerStateBegan) {
  502. if (_delegate && [_delegate respondsToSelector:@selector(onLongPressMessage:)]) {
  503. [_delegate onLongPressMessage:self];
  504. }
  505. }
  506. }
  507. - (void)onRetryMessage:(UIGestureRecognizer *)recognizer {
  508. if (_messageData.status == Msg_Status_Fail)
  509. if (_delegate && [_delegate respondsToSelector:@selector(onRetryMessage:)]) {
  510. [_delegate onRetryMessage:self];
  511. }
  512. }
  513. - (void)onSelectMessage:(UIGestureRecognizer *)recognizer {
  514. if (_delegate && [_delegate respondsToSelector:@selector(onSelectMessage:)]) {
  515. [_delegate onSelectMessage:self];
  516. }
  517. }
  518. - (void)onSelectMessageAvatar:(UIGestureRecognizer *)recognizer {
  519. if (_delegate && [_delegate respondsToSelector:@selector(onSelectMessageAvatar:)]) {
  520. [_delegate onSelectMessageAvatar:self];
  521. }
  522. }
  523. - (void)onLongSelectMessageAvatar:(UIGestureRecognizer *)recognizer {
  524. if (_delegate && [_delegate respondsToSelector:@selector(onLongSelectMessageAvatar:)]) {
  525. [_delegate onLongSelectMessageAvatar:self];
  526. }
  527. }
  528. - (void)onSelectReadReceipt:(UITapGestureRecognizer *)gesture {
  529. if (![self shouldHighlightReadReceiptLabel]) {
  530. return;
  531. }
  532. if (_delegate && [_delegate respondsToSelector:@selector(onSelectReadReceipt:)]) {
  533. [_delegate onSelectReadReceipt:self.messageData];
  534. }
  535. }
  536. - (void)onJumpToRepliesDetailPage:(UIButton *)btn {
  537. NSLog(@"click onJumpToRepliesDetailPage");
  538. NSLog(@"%@", self.messageData.messageModifyReplies);
  539. if (_delegate && [_delegate respondsToSelector:@selector(onJumpToRepliesDetailPage:)]) {
  540. [_delegate onJumpToRepliesDetailPage:self.messageData];
  541. }
  542. }
  543. - (BOOL)shouldHighlightReadReceiptLabel {
  544. if (self.messageData.innerMessage.groupID.length == 0) {
  545. return ![self.readReceiptLabel.text isEqualToString:TIMCommonLocalizableString(TUIKitMessageReadC2CRead)];
  546. } else {
  547. return ![self.readReceiptLabel.text isEqualToString:TIMCommonLocalizableString(TUIKitMessageReadAllRead)];
  548. }
  549. }
  550. - (UIFont *)fontWithSize:(CGFloat)size {
  551. static NSCache *fontCache;
  552. if (fontCache == nil) {
  553. fontCache = [[NSCache alloc] init];
  554. }
  555. UIFont *font = [fontCache objectForKey:@(size)];
  556. if (font == nil) {
  557. font = [UIFont systemFontOfSize:size];
  558. [fontCache setObject:font forKey:@(size)];
  559. }
  560. return font;
  561. }
  562. - (void)notifyBottomContainerReadyOfData:(TUIMessageCellData *)cellData {
  563. // Override by subclass.
  564. }
  565. @end
  566. @implementation TUIMessageCell (TUILayoutConfiguration)
  567. static UIColor *gOutgoingNameColor;
  568. + (UIColor *)outgoingNameColor {
  569. if (!gOutgoingNameColor) {
  570. gOutgoingNameColor = [UIColor d_systemGrayColor];
  571. }
  572. return gOutgoingNameColor;
  573. }
  574. + (void)setOutgoingNameColor:(UIColor *)outgoingNameColor {
  575. gOutgoingNameColor = outgoingNameColor;
  576. }
  577. static UIFont *gOutgoingNameFont;
  578. + (UIFont *)outgoingNameFont {
  579. if (!gOutgoingNameFont) {
  580. gOutgoingNameFont = [UIFont systemFontOfSize:14];
  581. }
  582. return gOutgoingNameFont;
  583. }
  584. + (void)setOutgoingNameFont:(UIFont *)outgoingNameFont {
  585. gOutgoingNameFont = outgoingNameFont;
  586. }
  587. static UIColor *gIncommingNameColor;
  588. + (UIColor *)incommingNameColor {
  589. if (!gIncommingNameColor) {
  590. gIncommingNameColor = [UIColor d_systemGrayColor];
  591. }
  592. return gIncommingNameColor;
  593. }
  594. + (void)setIncommingNameColor:(UIColor *)incommingNameColor {
  595. gIncommingNameColor = incommingNameColor;
  596. }
  597. static UIFont *gIncommingNameFont;
  598. + (UIFont *)incommingNameFont {
  599. if (!gIncommingNameFont) {
  600. gIncommingNameFont = [UIFont systemFontOfSize:14];
  601. }
  602. return gIncommingNameFont;
  603. }
  604. + (void)setIncommingNameFont:(UIFont *)incommingNameFont {
  605. gIncommingNameFont = incommingNameFont;
  606. }
  607. - (MOHeadNormalView *)headBgView{
  608. if(!_headBgView){
  609. _headBgView = [[MOHeadNormalView alloc] init];
  610. }
  611. return _headBgView;
  612. }
  613. @end