TUIFileMessageCell.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. //
  2. // TFileMessageCell.m
  3. // UIKit
  4. //
  5. // Created by annidyfeng on 2019/5/30.
  6. // Copyright © 2023 Tencent. All rights reserved.
  7. //
  8. #import "TUIFileMessageCell.h"
  9. #import <TIMCommon/TIMDefine.h>
  10. #import <TUICore/TUIThemeManager.h>
  11. #import "ReactiveObjC/ReactiveObjC.h"
  12. #import "TUIMessageProgressManager.h"
  13. #import <TUICore/TUICore.h>
  14. @interface TUIFileMessageCell () <V2TIMSDKListener, TUIMessageProgressManagerDelegate>
  15. @property(nonatomic, strong) CAShapeLayer *maskLayer;
  16. @property(nonatomic, strong) CAShapeLayer *borderLayer;
  17. @property(nonatomic, strong) UIView *progressView;
  18. @property(nonatomic, strong) UIView *fileContainer;
  19. @property(nonatomic, strong) UIView *animateHighlightView;
  20. @end
  21. @implementation TUIFileMessageCell
  22. - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
  23. self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
  24. if (self) {
  25. _bubble = [[UIImageView alloc] initWithFrame:self.container.bounds];
  26. [self.container addSubview:_bubble];
  27. _bubble.hidden = YES;
  28. _bubble.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  29. self.securityStrikeView = [[TUISecurityStrikeView alloc] init];
  30. [self.container addSubview:self.securityStrikeView];
  31. [self.container addSubview:self.fileContainer];
  32. self.fileContainer.backgroundColor = TUIChatDynamicColor(@"chat_file_message_bg_color", @"#FFFFFF");
  33. [self.fileContainer addSubview:self.progressView];
  34. _fileName = [[UILabel alloc] init];
  35. _fileName.font = [UIFont boldSystemFontOfSize:15];
  36. _fileName.textColor = TUIChatDynamicColor(@"chat_file_message_title_color", @"#000000");
  37. [self.fileContainer addSubview:_fileName];
  38. _length = [[UILabel alloc] init];
  39. _length.font = [UIFont systemFontOfSize:12];
  40. _length.textColor = TUIChatDynamicColor(@"chat_file_message_subtitle_color", @"#888888");
  41. [self.fileContainer addSubview:_length];
  42. _image = [[UIImageView alloc] init];
  43. _image.image = [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"msg_file_p")];
  44. _image.contentMode = UIViewContentModeScaleAspectFit;
  45. [self.fileContainer addSubview:_image];
  46. [self.fileContainer.layer insertSublayer:self.borderLayer atIndex:0];
  47. [self.fileContainer.layer setMask:self.maskLayer];
  48. [V2TIMManager.sharedInstance addIMSDKListener:self];
  49. [TUIMessageProgressManager.shareManager addDelegate:self];
  50. }
  51. return self;
  52. }
  53. - (void)fillWithData:(TUIFileMessageCellData *)data {
  54. // set data
  55. [super fillWithData:data];
  56. self.fileData = data;
  57. _fileName.text = data.fileName;
  58. _length.text = [self formatLength:data.length];
  59. _image.image = [[TUIImageCache sharedInstance] getResourceFromCache:[self getImagePathByCurrentFileType:data.fileName.pathExtension]];
  60. @weakify(self);
  61. [self prepareReactTagUI:self.container];
  62. self.securityStrikeView.hidden = YES;
  63. BOOL hasRiskContent = self.messageData.innerMessage.hasRiskContent;
  64. if (hasRiskContent) {
  65. self.bubble.image = [self getErrorBubble];
  66. self.securityStrikeView.hidden = NO;
  67. self.readReceiptLabel.hidden = YES;
  68. self.retryView.hidden = NO;
  69. }
  70. dispatch_async(dispatch_get_main_queue(), ^{
  71. @strongify(self);
  72. NSInteger uploadProgress = [TUIMessageProgressManager.shareManager uploadProgressForMessage:self.fileData.msgID];
  73. NSInteger downloadProgress = [TUIMessageProgressManager.shareManager downloadProgressForMessage:self.fileData.msgID];
  74. [self onUploadProgress:self.fileData.msgID progress:uploadProgress];
  75. [self onDownloadProgress:self.fileData.msgID progress:downloadProgress];
  76. // tell constraints they need updating
  77. [self setNeedsUpdateConstraints];
  78. // update constraints now so we can animate the change
  79. [self updateConstraintsIfNeeded];
  80. [self layoutIfNeeded];
  81. });
  82. }
  83. - (UIImage *)getErrorBubble {
  84. if (self.messageData.direction == MsgDirectionIncoming) {
  85. return TUIBubbleMessageCell.incommingErrorBubble;
  86. } else {
  87. return TUIBubbleMessageCell.outgoingErrorBubble;
  88. }
  89. }
  90. #pragma mark - TUIMessageProgressManagerDelegate
  91. - (void)onUploadProgress:(NSString *)msgID progress:(NSInteger)progress {
  92. if (![msgID isEqualToString:self.fileData.msgID]) {
  93. return;
  94. }
  95. self.fileData.uploadProgress = progress;
  96. [self updateUploadProgress:(int)progress];
  97. }
  98. - (void)onDownloadProgress:(NSString *)msgID progress:(NSInteger)progress {
  99. if (![msgID isEqualToString:self.fileData.msgID]) {
  100. return;
  101. }
  102. self.fileData.downladProgress = progress;
  103. [self updateDownloadProgress:(int)progress];
  104. }
  105. - (void)updateUploadProgress:(int)progress {
  106. [self.indicator startAnimating];
  107. self.progressView.hidden = YES;
  108. self.length.text = [self formatLength:self.fileData.length];
  109. NSLog(@"updateProgress:%ld,isLocalExist:%@,isDownloading:%@", (long)progress, self.fileData.isLocalExist ? @"YES" : @"NO",
  110. self.fileData.isDownloading ? @"YES" : @"NO");
  111. if (progress >= 100 || progress == 0) {
  112. [self.indicator stopAnimating];
  113. return;
  114. }
  115. [self showProgressLodingAnimation:progress];
  116. }
  117. - (void)updateDownloadProgress:(int)progress {
  118. [self.indicator startAnimating];
  119. self.progressView.hidden = YES;
  120. self.length.text = [self formatLength:self.fileData.length];
  121. if (progress >= 100 || progress == 0) {
  122. [self.indicator stopAnimating];
  123. return;
  124. }
  125. [self showProgressLodingAnimation:progress];
  126. }
  127. - (void)showProgressLodingAnimation:(NSInteger)progress {
  128. self.progressView.hidden = NO;
  129. NSLog(@"showProgressLodingAnimation:%ld", (long)progress);
  130. [UIView animateWithDuration:0.25
  131. animations:^{
  132. [self.progressView mas_updateConstraints:^(MASConstraintMaker *make) {
  133. make.width.mas_equalTo(self.fileContainer.mm_w * progress / 100.0);
  134. }];
  135. }
  136. completion:^(BOOL finished) {
  137. if (progress == 0 || progress >= 100) {
  138. self.progressView.hidden = YES;
  139. [self.indicator stopAnimating];
  140. self.length.text = [self formatLength:self.fileData.length];
  141. }
  142. }];
  143. self.length.text = [self formatLength:self.fileData.length];
  144. }
  145. - (NSString *)formatLength:(long)length {
  146. /**
  147. *
  148. * Display file size by default
  149. */
  150. double len = length;
  151. NSArray *array = [NSArray arrayWithObjects:@"Bytes", @"K", @"M", @"G", @"T", nil];
  152. int factor = 0;
  153. while (len > 1024) {
  154. len /= 1024;
  155. factor++;
  156. if (factor >= 4) {
  157. break;
  158. }
  159. }
  160. NSString *str = [NSString stringWithFormat:@"%4.2f%@", len, array[factor]];
  161. /**
  162. *
  163. * Formatted display characters
  164. */
  165. if (self.fileData.direction == MsgDirectionOutgoing) {
  166. if (length == 0 && (self.fileData.status == Msg_Status_Sending || self.fileData.status == Msg_Status_Sending_2)) {
  167. str = [NSString
  168. stringWithFormat:@"%zd%%", self.fileData.direction == MsgDirectionIncoming ? self.fileData.downladProgress : self.fileData.uploadProgress];
  169. }
  170. } else {
  171. if (!self.fileData.isLocalExist && !self.fileData.isDownloading) {
  172. str = [NSString stringWithFormat:@"%@ %@", str, TIMCommonLocalizableString(TUIKitNotDownload)];
  173. }
  174. }
  175. return str;
  176. }
  177. - (NSString *)getImagePathByCurrentFileType:(NSString *)pathExtension {
  178. if (pathExtension.length > 0) {
  179. if ([pathExtension hasSuffix:@"ppt"] || [pathExtension hasSuffix:@"key"] || [pathExtension hasSuffix:@"pdf"]) {
  180. return TUIChatImagePath(@"msg_file_p");
  181. }
  182. }
  183. return TUIChatImagePath(@"msg_file");
  184. }
  185. + (BOOL)requiresConstraintBasedLayout {
  186. return YES;
  187. }
  188. // this is Apple's recommended place for adding/updating constraints
  189. - (void)updateConstraints {
  190. [super updateConstraints];
  191. CGSize containerSize = [self.class getContentSize:self.fileData];
  192. CGSize fileContainerSize = [self.class getFileContentSize:self.fileData];
  193. [self.fileContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
  194. make.center.mas_equalTo(self.container);
  195. make.size.mas_equalTo(fileContainerSize);
  196. }];
  197. CGFloat imageHeight = fileContainerSize.height - 2 * TFileMessageCell_Margin;
  198. CGFloat imageWidth = imageHeight;
  199. [self.image mas_remakeConstraints:^(MASConstraintMaker *make) {
  200. make.leading.mas_equalTo(self.fileContainer.mas_leading).mas_offset(TFileMessageCell_Margin);
  201. make.top.mas_equalTo(self.fileContainer.mas_top).mas_offset(TFileMessageCell_Margin);
  202. make.size.mas_equalTo(CGSizeMake(imageWidth, imageHeight));
  203. }];
  204. CGFloat textWidth = fileContainerSize.width - 2 * TFileMessageCell_Margin - imageWidth;
  205. CGSize nameSize = [_fileName sizeThatFits:fileContainerSize];
  206. [self.fileName mas_remakeConstraints:^(MASConstraintMaker *make) {
  207. make.leading.mas_equalTo(self.image.mas_trailing).mas_offset(TFileMessageCell_Margin);
  208. make.top.mas_equalTo(self.image);
  209. make.size.mas_equalTo(CGSizeMake(textWidth, nameSize.height));
  210. }];
  211. CGSize lengthSize = [_length sizeThatFits:fileContainerSize];
  212. [self.length mas_remakeConstraints:^(MASConstraintMaker *make) {
  213. make.leading.mas_equalTo(self.fileName);
  214. make.top.mas_equalTo(self.fileName.mas_bottom).mas_offset(TFileMessageCell_Margin * 0.5);
  215. make.size.mas_equalTo(CGSizeMake(textWidth, nameSize.height));
  216. }];
  217. if (self.messageData.messageContainerAppendSize.height > 0) {
  218. [self.fileContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
  219. make.center.mas_equalTo(self.container);
  220. make.size.mas_equalTo(self.container);
  221. }];
  222. self.bubble.hidden = NO;
  223. }
  224. self.maskLayer.frame = self.fileContainer.bounds;
  225. self.borderLayer.frame = self.fileContainer.bounds;
  226. UIRectCorner corner = UIRectCornerBottomLeft | UIRectCornerBottomRight | UIRectCornerTopLeft;
  227. if (self.fileData.direction == MsgDirectionIncoming) {
  228. corner = UIRectCornerBottomLeft | UIRectCornerBottomRight | UIRectCornerTopRight;
  229. }
  230. UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:self.fileContainer.bounds byRoundingCorners:corner cornerRadii:CGSizeMake(10, 10)];
  231. self.maskLayer.path = bezierPath.CGPath;
  232. self.borderLayer.path = bezierPath.CGPath;
  233. BOOL hasRiskContent = self.messageData.innerMessage.hasRiskContent;
  234. if (hasRiskContent ) {
  235. [self.fileContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
  236. make.top.mas_equalTo(self.container).mas_offset(13);
  237. make.leading.mas_equalTo(12);
  238. make.size.mas_equalTo(fileContainerSize);
  239. }];
  240. [self.bubble mas_remakeConstraints:^(MASConstraintMaker *make) {
  241. make.leading.mas_equalTo(0);
  242. make.size.mas_equalTo(self.container);
  243. make.top.mas_equalTo(self.container);
  244. }];
  245. [self.securityStrikeView mas_remakeConstraints:^(MASConstraintMaker *make) {
  246. make.top.mas_equalTo(self.fileContainer.mas_bottom);
  247. make.width.mas_equalTo(self.container);
  248. make.bottom.mas_equalTo(self.container).mas_offset(-self.messageData.messageContainerAppendSize.height);
  249. }];
  250. self.bubble.hidden = NO;
  251. }
  252. else {
  253. self.bubble.hidden = YES;
  254. }
  255. [self.progressView mas_remakeConstraints:^(MASConstraintMaker *make) {
  256. make.leading.mas_equalTo(0);
  257. make.top.mas_equalTo(0);
  258. make.width.mas_equalTo(self.progressView.mm_w ?: 1);
  259. make.height.mas_equalTo(self.fileContainer.mm_h);
  260. }];
  261. }
  262. - (void)layoutSubviews {
  263. [super layoutSubviews];
  264. }
  265. - (CAShapeLayer *)maskLayer {
  266. if (_maskLayer == nil) {
  267. _maskLayer = [CAShapeLayer layer];
  268. }
  269. return _maskLayer;
  270. }
  271. - (CAShapeLayer *)borderLayer {
  272. if (_borderLayer == nil) {
  273. _borderLayer = [CAShapeLayer layer];
  274. _borderLayer.lineWidth = 0.5f;
  275. _borderLayer.strokeColor = [UIColor colorWithRed:221 / 255.0 green:221 / 255.0 blue:221 / 255.0 alpha:1.0].CGColor;
  276. _borderLayer.fillColor = [UIColor clearColor].CGColor;
  277. }
  278. return _borderLayer;
  279. }
  280. - (UIView *)progressView {
  281. if (_progressView == nil) {
  282. _progressView = [[UIView alloc] init];
  283. _progressView.backgroundColor = [UIColor colorWithRed:208 / 255.0 green:228 / 255.0 blue:255 / 255.0 alpha:1 / 1.0];
  284. }
  285. return _progressView;
  286. }
  287. - (UIView *)fileContainer {
  288. if (_fileContainer == nil) {
  289. _fileContainer = [[UIView alloc] init];
  290. _fileContainer.backgroundColor = TUIChatDynamicColor(@"chat_file_message_bg_color", @"#FFFFFF");
  291. }
  292. return _fileContainer;
  293. }
  294. - (void)onConnectSuccess {
  295. [self fillWithData:self.fileData];
  296. }
  297. - (void)highlightWhenMatchKeyword:(NSString *)keyword {
  298. if (keyword) {
  299. if (self.highlightAnimating) {
  300. return;
  301. }
  302. [self animate:3];
  303. }
  304. }
  305. - (void)animate:(int)times {
  306. times--;
  307. if (times < 0) {
  308. [self.animateHighlightView removeFromSuperview];
  309. self.highlightAnimating = NO;
  310. return;
  311. }
  312. self.highlightAnimating = YES;
  313. self.animateHighlightView.frame = self.container.bounds;
  314. self.animateHighlightView.alpha = 0.1;
  315. [self.fileContainer addSubview:self.animateHighlightView];
  316. [UIView animateWithDuration:0.25
  317. animations:^{
  318. self.animateHighlightView.alpha = 0.5;
  319. }
  320. completion:^(BOOL finished) {
  321. [UIView animateWithDuration:0.25
  322. animations:^{
  323. self.animateHighlightView.alpha = 0.1;
  324. }
  325. completion:^(BOOL finished) {
  326. if (!self.messageData.highlightKeyword) {
  327. [self animate:0];
  328. return;
  329. }
  330. [self animate:times];
  331. }];
  332. }];
  333. }
  334. - (UIView *)animateHighlightView {
  335. if (_animateHighlightView == nil) {
  336. _animateHighlightView = [[UIView alloc] init];
  337. _animateHighlightView.backgroundColor = [UIColor orangeColor];
  338. }
  339. return _animateHighlightView;
  340. }
  341. - (void)prepareReactTagUI:(UIView *)containerView {
  342. NSDictionary *param = @{TUICore_TUIChatExtension_ChatMessageReactPreview_Delegate: self};
  343. [TUICore raiseExtension:TUICore_TUIChatExtension_ChatMessageReactPreview_ClassicExtensionID parentView:containerView param:param];
  344. }
  345. #pragma mark - TUIMessageCellProtocol
  346. + (CGSize)getFileContentSize:(TUIMessageCellData *)data {
  347. BOOL hasRiskContent = data.innerMessage.hasRiskContent;
  348. if (hasRiskContent) {
  349. return CGSizeMake(237, 62);
  350. }
  351. return TFileMessageCell_Container_Size;
  352. }
  353. + (CGSize)getContentSize:(TUIMessageCellData *)data {
  354. CGSize size = [self.class getFileContentSize:data];
  355. BOOL hasRiskContent = data.innerMessage.hasRiskContent;
  356. if (hasRiskContent) {
  357. CGFloat bubbleTopMargin = 12;
  358. CGFloat bubbleBottomMargin = 12;
  359. size.width = MAX(size.width, 261);// width must more than TIMCommonLocalizableString(TUIKitMessageTypeSecurityStrike)
  360. size.height += bubbleTopMargin;
  361. size.height += kTUISecurityStrikeViewTopLineMargin;
  362. size.height += kTUISecurityStrikeViewTopLineToBottom;
  363. size.height += bubbleBottomMargin;
  364. }
  365. return size;
  366. }
  367. @end