WFCUMessageCell.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. //
  2. // MessageCell.m
  3. // WFChat UIKit
  4. //
  5. // Created by WF Chat on 2017/9/1.
  6. // Copyright © 2017年 WildFireChat. All rights reserved.
  7. //
  8. #import "WFCUMessageCell.h"
  9. #import "WFCUUtilities.h"
  10. #import <WFChatClient/WFCChatClient.h>
  11. #import "SDWebImage.h"
  12. #import "ZCCCircleProgressView.h"
  13. #define Portrait_Size 40
  14. #define Name_Label_Height 14
  15. #define Name_Label_Padding 6
  16. #define Name_Client_Padding 2
  17. #define Portrait_Padding_Left 16
  18. #define Portrait_Padding_Right 16
  19. #define Portrait_Padding_Buttom 4
  20. #define Client_Arad_Buttom_Padding 8
  21. #define Client_Bubble_Top_Padding 6
  22. #define Client_Bubble_Bottom_Padding 4
  23. #define Bubble_Padding_Arraw 16
  24. #define Bubble_Padding_Another_Side 8
  25. @interface WFCUMessageCell ()
  26. @property (nonatomic, strong)UIActivityIndicatorView *activityIndicatorView;
  27. @property (nonatomic, strong)UIImageView *failureView;
  28. @property (nonatomic, strong)UIImageView *maskView;
  29. @property (nonatomic, strong)ZCCCircleProgressView *receiptView;
  30. @end
  31. @implementation WFCUMessageCell
  32. + (CGFloat)clientAreaWidth {
  33. return [WFCUMessageCell bubbleWidth] - Bubble_Padding_Arraw - Bubble_Padding_Another_Side;
  34. }
  35. + (CGFloat)bubbleWidth {
  36. return ([UIScreen mainScreen].bounds.size.width - Portrait_Size - Portrait_Padding_Left - Portrait_Padding_Right) * 0.7;
  37. }
  38. + (CGSize)sizeForCell:(WFCUMessageModel *)msgModel withViewWidth:(CGFloat)width {
  39. CGFloat height = [super hightForTimeLabel:msgModel];
  40. CGFloat portraitSize = Portrait_Size;
  41. CGFloat nameLabelHeight = Name_Label_Height + Name_Client_Padding;
  42. CGFloat clientAreaWidth = [self clientAreaWidth];
  43. CGSize clientArea = [self sizeForClientArea:msgModel withViewWidth:clientAreaWidth];
  44. CGFloat nameAndClientHeight = clientArea.height;
  45. if (msgModel.showNameLabel) {
  46. nameAndClientHeight += nameLabelHeight;
  47. }
  48. nameAndClientHeight += Client_Bubble_Top_Padding;
  49. nameAndClientHeight += Client_Bubble_Bottom_Padding;
  50. if (portraitSize + Portrait_Padding_Buttom > nameAndClientHeight) {
  51. height += portraitSize + Portrait_Padding_Buttom;
  52. } else {
  53. height += nameAndClientHeight;
  54. }
  55. height += Client_Arad_Buttom_Padding; //buttom padding
  56. return CGSizeMake(width, height);
  57. }
  58. + (CGSize)sizeForClientArea:(WFCUMessageModel *)msgModel withViewWidth:(CGFloat)width {
  59. return CGSizeZero;
  60. }
  61. - (void)updateStatus {
  62. if (self.model.message.direction == MessageDirection_Send) {
  63. if (self.model.message.status == Message_Status_Sending) {
  64. CGRect frame = self.bubbleView.frame;
  65. frame.origin.x -= 24;
  66. frame.origin.y = frame.origin.y + frame.size.height - 24;
  67. frame.size.width = 20;
  68. frame.size.height = 20;
  69. self.activityIndicatorView.hidden = NO;
  70. self.activityIndicatorView.frame = frame;
  71. [self.activityIndicatorView startAnimating];
  72. } else {
  73. [_activityIndicatorView stopAnimating];
  74. _activityIndicatorView.hidden = YES;
  75. }
  76. if (self.model.message.status == Message_Status_Send_Failure) {
  77. CGRect frame = self.bubbleView.frame;
  78. frame.origin.x -= 24;
  79. frame.origin.y = frame.origin.y + frame.size.height - 24;
  80. frame.size.width = 20;
  81. frame.size.height = 20;
  82. self.failureView.frame = frame;
  83. self.failureView.hidden = NO;
  84. } else {
  85. _failureView.hidden = YES;
  86. }
  87. } else {
  88. [_activityIndicatorView stopAnimating];
  89. _activityIndicatorView.hidden = YES;
  90. _failureView.hidden = YES;
  91. }
  92. }
  93. -(void)onStatusChanged:(NSNotification *)notification {
  94. WFCCMessageStatus newStatus = (WFCCMessageStatus)[[notification.userInfo objectForKey:@"status"] integerValue];
  95. self.model.message.status = newStatus;
  96. [self updateStatus];
  97. }
  98. - (void)onUserInfoUpdated:(NSNotification *)notification {
  99. WFCCUserInfo *userInfo = notification.userInfo[@"userInfo"];
  100. if([userInfo.userId isEqualToString:self.model.message.fromUser]) {
  101. if (self.model.message.conversation.type == Group_Type) {
  102. userInfo = [[WFCCIMService sharedWFCIMService] getUserInfo:userInfo.userId inGroup:self.model.message.conversation.target refresh:NO];
  103. }
  104. [self updateUserInfo:userInfo];
  105. }
  106. }
  107. - (void)updateUserInfo:(WFCCUserInfo *)userInfo {
  108. if([userInfo.userId isEqualToString:self.model.message.fromUser]) {
  109. [self.portraitView sd_setImageWithURL:[NSURL URLWithString:[userInfo.portrait stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]] placeholderImage:[UIImage imageNamed:@"PersonalChat"]];
  110. if(self.model.showNameLabel) {
  111. NSString *nameStr = nil;
  112. if (userInfo.friendAlias.length) {
  113. nameStr = userInfo.friendAlias;
  114. } else if(userInfo.groupAlias.length) {
  115. if(userInfo.displayName.length > 0) {
  116. nameStr = [userInfo.groupAlias stringByAppendingFormat:@"(%@)", userInfo.displayName];
  117. } else {
  118. nameStr = userInfo.groupAlias;
  119. }
  120. } else if(userInfo.displayName.length > 0) {
  121. nameStr = userInfo.displayName;
  122. } else {
  123. nameStr = [NSString stringWithFormat:@"%@<%@>", WFCString(@"User"), self.model.message.fromUser];
  124. }
  125. self.nameLabel.text = nameStr;
  126. }
  127. }
  128. }
  129. - (void)setModel:(WFCUMessageModel *)model {
  130. [[NSNotificationCenter defaultCenter] removeObserver:self];
  131. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onStatusChanged:) name:kSendingMessageStatusUpdated object:
  132. @(model.message.messageId)];
  133. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onUserInfoUpdated:) name:kUserInfoUpdated object:
  134. model.message.fromUser];
  135. [super setModel:model];
  136. if (model.message.direction == MessageDirection_Send) {
  137. CGFloat top = [WFCUMessageCellBase hightForTimeLabel:model];
  138. CGRect frame = self.frame;
  139. self.portraitView.frame = CGRectMake(frame.size.width - Portrait_Size - Portrait_Padding_Right, top, Portrait_Size, Portrait_Size);
  140. if (model.showNameLabel) {
  141. self.nameLabel.frame = CGRectMake(frame.size.width - Portrait_Size - Portrait_Padding_Right - Name_Label_Padding - 200, top, 200, Name_Label_Height);
  142. self.nameLabel.hidden = NO;
  143. self.nameLabel.textAlignment = NSTextAlignmentRight;
  144. } else {
  145. self.nameLabel.hidden = YES;
  146. }
  147. CGSize size = [self.class sizeForClientArea:model withViewWidth:[WFCUMessageCell clientAreaWidth]];
  148. self.bubbleView.image = [UIImage imageNamed:@"sent_msg_background"];
  149. self.bubbleView.frame = CGRectMake(frame.size.width - Portrait_Size - Portrait_Padding_Right - Name_Label_Padding - size.width - Bubble_Padding_Arraw - Bubble_Padding_Another_Side, top + Name_Client_Padding, size.width + Bubble_Padding_Arraw + Bubble_Padding_Another_Side, size.height + Client_Bubble_Top_Padding + Client_Bubble_Bottom_Padding);
  150. self.contentArea.frame = CGRectMake(Bubble_Padding_Another_Side, Client_Bubble_Top_Padding, size.width, size.height);
  151. UIImage *image = self.bubbleView.image;
  152. self.bubbleView.image = [self.bubbleView.image
  153. resizableImageWithCapInsets:UIEdgeInsetsMake(image.size.height * 0.95, image.size.width * 0.2,image.size.height * 0.1, image.size.width * 0.05)];
  154. if((model.message.status == Message_Status_Sent || model.message.status == Message_Status_Readed) && [[WFCCIMService sharedWFCIMService] isReceiptEnabled] && [[WFCCIMService sharedWFCIMService] isUserEnableReceipt]) {
  155. if (model.message.conversation.type == Single_Type) {
  156. if (model.message.serverTime <= [[model.readDict objectForKey:model.message.conversation.target] longLongValue]) {
  157. [self.receiptView setProgress:1 subProgress:1];
  158. } else if (model.message.serverTime <= [[model.deliveryDict objectForKey:model.message.conversation.target] longLongValue]) {
  159. [self.receiptView setProgress:0 subProgress:1];
  160. } else {
  161. [self.receiptView setProgress:0 subProgress:0];
  162. }
  163. self.receiptView.hidden = NO;
  164. } else if(model.message.conversation.type == Group_Type) {
  165. long long messageTS = model.message.serverTime;
  166. WFCCGroupInfo *groupInfo = nil;
  167. if (model.deliveryRate == -1) {
  168. __block int delieveriedCount = 0;
  169. [model.deliveryDict enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSNumber * _Nonnull obj, BOOL * _Nonnull stop) {
  170. if ([obj longLongValue] >= messageTS) {
  171. delieveriedCount++;
  172. }
  173. }];
  174. groupInfo = [[WFCCIMService sharedWFCIMService] getGroupInfo:model.message.conversation.target refresh:NO];
  175. model.deliveryRate = (float)delieveriedCount/(groupInfo.memberCount - 1);
  176. }
  177. if (model.readRate == -1) {
  178. __block int readedCount = 0;
  179. [model.readDict enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSNumber * _Nonnull obj, BOOL * _Nonnull stop) {
  180. if ([obj longLongValue] >= messageTS) {
  181. readedCount++;
  182. }
  183. }];
  184. if (!groupInfo) {
  185. groupInfo = [[WFCCIMService sharedWFCIMService] getGroupInfo:model.message.conversation.target refresh:NO];
  186. }
  187. model.readRate = (float)readedCount/(groupInfo.memberCount - 1);
  188. }
  189. if (model.deliveryRate < model.readRate) {
  190. model.deliveryRate = model.readRate;
  191. }
  192. [self.receiptView setProgress:model.readRate subProgress:model.deliveryRate];
  193. self.receiptView.hidden = NO;
  194. } else {
  195. self.receiptView.hidden = YES;
  196. }
  197. } else {
  198. self.receiptView.hidden = YES;
  199. }
  200. if (self.receiptView.hidden == NO) {
  201. self.receiptView.frame = CGRectMake(self.bubbleView.frame.origin.x - 16, self.frame.size.height - 24 , 14, 14);
  202. }
  203. } else {
  204. CGFloat top = [WFCUMessageCellBase hightForTimeLabel:model];
  205. self.portraitView.frame = CGRectMake(Portrait_Padding_Left, top, Portrait_Size, Portrait_Size);
  206. if (model.showNameLabel) {
  207. self.nameLabel.frame = CGRectMake(Portrait_Padding_Left + Portrait_Size + Name_Label_Padding, top, 200, Name_Label_Height);
  208. self.nameLabel.hidden = NO;
  209. self.nameLabel.textAlignment = NSTextAlignmentLeft;
  210. top += Name_Label_Height + Name_Client_Padding;
  211. } else {
  212. self.nameLabel.hidden = YES;
  213. }
  214. NSString *bubbleImageName = @"received_msg_background";
  215. if (@available(iOS 13.0, *)) {
  216. if(UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
  217. bubbleImageName = @"chat_from_bg_normal_dark";
  218. }
  219. }
  220. CGSize size = [self.class sizeForClientArea:model withViewWidth:[WFCUMessageCell clientAreaWidth]];
  221. self.bubbleView.image = [UIImage imageNamed:bubbleImageName];
  222. self.bubbleView.frame = CGRectMake(Portrait_Padding_Left + Portrait_Size + Name_Label_Padding, top, size.width + Bubble_Padding_Arraw + Bubble_Padding_Another_Side, size.height + Client_Bubble_Top_Padding + Client_Bubble_Bottom_Padding);
  223. self.contentArea.frame = CGRectMake(Bubble_Padding_Arraw, Client_Bubble_Top_Padding, size.width, size.height);
  224. UIImage *image = self.bubbleView.image;
  225. CGFloat leftProtection = image.size.width * 0.8;
  226. CGFloat rightProtection = image.size.width * 0.2;
  227. if (self.bubbleView.frame.size.width < image.size.width) {
  228. leftProtection = 17;
  229. rightProtection = 12;
  230. }
  231. self.bubbleView.image = [self.bubbleView.image
  232. resizableImageWithCapInsets:UIEdgeInsetsMake(image.size.height * 0.8, leftProtection,
  233. image.size.height * 0.2, rightProtection)];
  234. self.receiptView.hidden = YES;
  235. }
  236. NSString *groupId = nil;
  237. if (self.model.message.conversation.type == Group_Type) {
  238. groupId = self.model.message.conversation.target;
  239. }
  240. WFCCUserInfo *userInfo = [[WFCCIMService sharedWFCIMService] getUserInfo:model.message.fromUser inGroup:groupId refresh:NO];
  241. if(userInfo.userId.length == 0) {
  242. userInfo = [[WFCCUserInfo alloc] init];
  243. userInfo.userId = model.message.fromUser;
  244. }
  245. [self updateUserInfo:userInfo];
  246. [self setMaskImage:self.bubbleView.image];
  247. [self updateStatus];
  248. if (model.highlighted) {
  249. UIColor *bkColor = self.backgroundColor;
  250. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  251. self.backgroundColor = [UIColor grayColor];
  252. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  253. self.backgroundColor = bkColor;
  254. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  255. self.backgroundColor = [UIColor grayColor];
  256. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  257. self.backgroundColor = bkColor;
  258. });
  259. });
  260. });
  261. });
  262. model.highlighted = NO;
  263. }
  264. }
  265. - (void)onTapReceiptView:(id)sender {
  266. if ([self.delegate respondsToSelector:@selector(didTapReceiptView:withModel:)] && self.model.message.conversation.type == Group_Type) {
  267. [self.delegate didTapReceiptView:self withModel:self.model];
  268. }
  269. }
  270. - (void)setMaskImage:(UIImage *)maskImage{
  271. if (_maskView == nil) {
  272. _maskView = [[UIImageView alloc] initWithImage:maskImage];
  273. _maskView.frame = self.bubbleView.bounds;
  274. self.bubbleView.layer.mask = _maskView.layer;
  275. self.bubbleView.layer.masksToBounds = YES;
  276. } else {
  277. _maskView.image = maskImage;
  278. _maskView.frame = self.bubbleView.bounds;
  279. }
  280. }
  281. - (ZCCCircleProgressView *)receiptView {
  282. if (!_receiptView) {
  283. _receiptView = [[ZCCCircleProgressView alloc] initWithFrame:CGRectMake(0, 0, 14, 14)];
  284. _receiptView.hidden = YES;
  285. _receiptView.userInteractionEnabled = YES;
  286. [_receiptView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapReceiptView:)]];
  287. [self.contentView addSubview:_receiptView];
  288. }
  289. return _receiptView;
  290. }
  291. - (UIImageView *)portraitView {
  292. if (!_portraitView) {
  293. _portraitView = [[UIImageView alloc] init];
  294. _portraitView.clipsToBounds = YES;
  295. _portraitView.layer.cornerRadius = 3.f;
  296. [_portraitView setImage:[UIImage imageNamed:@"PersonalChat"]];
  297. [_portraitView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapPortrait:)]];
  298. [_portraitView addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(didLongPressPortrait:)]];
  299. _portraitView.userInteractionEnabled=YES;
  300. [self.contentView addSubview:_portraitView];
  301. }
  302. return _portraitView;
  303. }
  304. - (void)didTapPortrait:(id)sender {
  305. [self.delegate didTapMessagePortrait:self withModel:self.model];
  306. }
  307. - (void)didLongPressPortrait:(UILongPressGestureRecognizer *)recognizer {
  308. if (recognizer.state == UIGestureRecognizerStateBegan) {
  309. [self.delegate didLongPressMessagePortrait:self withModel:self.model];
  310. }
  311. }
  312. - (UILabel *)nameLabel {
  313. if (!_nameLabel) {
  314. _nameLabel = [[UILabel alloc] init];
  315. _nameLabel.font = [UIFont systemFontOfSize:Name_Label_Height-2];
  316. _nameLabel.textColor = [UIColor grayColor];
  317. [self.contentView addSubview:_nameLabel];
  318. }
  319. return _nameLabel;
  320. }
  321. - (UIView *)contentArea {
  322. if (!_contentArea) {
  323. _contentArea = [[UIView alloc] init];
  324. [self.bubbleView addSubview:_contentArea];
  325. }
  326. return _contentArea;
  327. }
  328. - (UIImageView *)bubbleView {
  329. if (!_bubbleView) {
  330. _bubbleView = [[UIImageView alloc] init];
  331. [self.contentView addSubview:_bubbleView];
  332. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTaped:)];
  333. [_bubbleView addGestureRecognizer:tap];
  334. [_bubbleView addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onLongPressed:)]];
  335. tap.cancelsTouchesInView = NO;
  336. [_bubbleView setUserInteractionEnabled:YES];
  337. }
  338. return _bubbleView;
  339. }
  340. - (UIActivityIndicatorView *)activityIndicatorView {
  341. if (!_activityIndicatorView) {
  342. _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
  343. [self.contentView addSubview:_activityIndicatorView];
  344. }
  345. return _activityIndicatorView;
  346. }
  347. - (UIImageView *)failureView {
  348. if (!_failureView) {
  349. _failureView = [[UIImageView alloc] init];
  350. _failureView.image = [UIImage imageNamed:@"failure"];
  351. [_failureView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onResend:)]];
  352. [_failureView setUserInteractionEnabled:YES];
  353. [self.contentView addSubview:_failureView];
  354. }
  355. return _failureView;
  356. }
  357. - (void)onResend:(id)sender {
  358. [self.delegate didTapResendBtn:self.model];
  359. }
  360. - (void)dealloc {
  361. [[NSNotificationCenter defaultCenter] removeObserver:self];
  362. }
  363. @end