TUIMergeMessageCell.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. //
  2. // TUIMergeMessageCell.m
  3. // Pods
  4. //
  5. // Created by harvy on 2020/12/9.
  6. // Copyright © 2023 Tencent. All rights reserved.
  7. //
  8. #import "TUIMergeMessageCell.h"
  9. #import <TIMCommon/TIMDefine.h>
  10. #import <TUICore/TUIThemeManager.h>
  11. #import <TUICore/TUICore.h>
  12. #ifndef CGFLOAT_CEIL
  13. #ifdef CGFLOAT_IS_DOUBLE
  14. #define CGFLOAT_CEIL(value) ceil(value)
  15. #else
  16. #define CGFLOAT_CEIL(value) ceilf(value)
  17. #endif
  18. #endif
  19. @interface TUIMergeMessageDetailRow : UIView
  20. @property(nonatomic, strong) UILabel *abstractName;
  21. @property(nonatomic, strong) UILabel *abstractBreak;
  22. @property(nonatomic, strong) UILabel *abstractDetail;
  23. @property(nonatomic, assign) CGFloat abstractNameLimitedWidth;
  24. - (void)fillWithData:(NSAttributedString *)name detailContent:(NSAttributedString *)detailContent;
  25. @end
  26. @implementation TUIMergeMessageDetailRow
  27. - (instancetype)init {
  28. self = [super init];
  29. if(self){
  30. [self setupview];
  31. }
  32. return self;
  33. }
  34. - (void)setupview {
  35. [self addSubview:self.abstractName];
  36. [self addSubview:self.abstractBreak];
  37. [self addSubview:self.abstractDetail];
  38. }
  39. - (UILabel *)abstractName {
  40. if(!_abstractName) {
  41. _abstractName = [[UILabel alloc] init];
  42. _abstractName.numberOfLines = 1;
  43. _abstractName.font = [UIFont systemFontOfSize:12.0];
  44. _abstractName.textColor = [UIColor colorWithRed:187 / 255.0 green:187 / 255.0 blue:187 / 255.0 alpha:1 / 1.0];
  45. _abstractName.textAlignment = isRTL()? NSTextAlignmentRight:NSTextAlignmentLeft;
  46. }
  47. return _abstractName;
  48. }
  49. - (UILabel *)abstractBreak {
  50. if(!_abstractBreak) {
  51. _abstractBreak = [[UILabel alloc] init];
  52. _abstractBreak.text = @":";
  53. _abstractBreak.font = [UIFont systemFontOfSize:12.0];
  54. _abstractBreak.textColor = TUIChatDynamicColor(@"chat_merge_message_content_color", @"#d5d5d5");
  55. }
  56. return _abstractBreak;
  57. }
  58. - (UILabel *)abstractDetail {
  59. if(!_abstractDetail) {
  60. _abstractDetail = [[UILabel alloc] init];
  61. _abstractDetail.numberOfLines = 0;
  62. _abstractDetail.font = [UIFont systemFontOfSize:12.0];
  63. _abstractDetail.textColor = TUIChatDynamicColor(@"chat_merge_message_content_color", @"#d5d5d5");
  64. _abstractDetail.textAlignment = isRTL()? NSTextAlignmentRight:NSTextAlignmentLeft;
  65. }
  66. return _abstractDetail;
  67. }
  68. + (BOOL)requiresConstraintBasedLayout {
  69. return YES;
  70. }
  71. // this is Apple's recommended place for adding/updating constraints
  72. - (void)updateConstraints {
  73. [super updateConstraints];
  74. [self.abstractName sizeToFit];
  75. [self.abstractName mas_remakeConstraints:^(MASConstraintMaker *make) {
  76. make.leading.mas_equalTo(0);
  77. make.top.mas_equalTo(0);
  78. make.trailing.mas_lessThanOrEqualTo(self.abstractBreak.mas_leading);
  79. make.width.mas_equalTo(self.abstractNameLimitedWidth);
  80. }];
  81. [self.abstractBreak sizeToFit];
  82. [self.abstractBreak mas_remakeConstraints:^(MASConstraintMaker *make) {
  83. make.leading.mas_equalTo(self.abstractName.mas_trailing);
  84. make.top.mas_equalTo(self.abstractName);
  85. make.width.mas_offset(self.abstractBreak.frame.size.width);
  86. make.height.mas_offset(self.abstractBreak.frame.size.height);
  87. }];
  88. [self.abstractDetail sizeToFit];
  89. [self.abstractDetail mas_remakeConstraints:^(MASConstraintMaker *make) {
  90. make.leading.mas_equalTo(self.abstractBreak.mas_trailing);
  91. make.top.mas_equalTo(0);
  92. make.trailing.mas_lessThanOrEqualTo(self.mas_trailing).mas_offset(-15);
  93. make.bottom.mas_equalTo(self);
  94. }];
  95. }
  96. - (void)fillWithData:(NSAttributedString *)name detailContent:(NSAttributedString *)detailContent {
  97. self.abstractName.attributedText = name;
  98. self.abstractDetail.attributedText = detailContent;
  99. NSAttributedString * senderStr = [[NSAttributedString alloc] initWithString:self.abstractName.text];
  100. CGRect senderRect = [senderStr boundingRectWithSize:CGSizeMake(70, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
  101. context:nil];
  102. self.abstractNameLimitedWidth = MIN(ceil(senderRect.size.width) + 2, 70);
  103. // tell constraints they need updating
  104. [self setNeedsUpdateConstraints];
  105. // update constraints now so we can animate the change
  106. [self updateConstraintsIfNeeded];
  107. [self layoutIfNeeded];
  108. }
  109. - (void)layoutSubviews {
  110. [super layoutSubviews];
  111. }
  112. @end
  113. @interface TUIMergeMessageCell ()
  114. @property(nonatomic, strong) CAShapeLayer *maskLayer;
  115. @property(nonatomic, strong) CAShapeLayer *borderLayer;
  116. @property(nonatomic, strong) TUIMergeMessageDetailRow *contentRowView1;
  117. @property(nonatomic, strong) TUIMergeMessageDetailRow *contentRowView2;
  118. @property(nonatomic, strong) TUIMergeMessageDetailRow *contentRowView3;
  119. @end
  120. @implementation TUIMergeMessageCell
  121. - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
  122. if ([super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
  123. [self setupViews];
  124. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onThemeChanged) name:TUIDidApplyingThemeChangedNotfication object:nil];
  125. }
  126. return self;
  127. }
  128. - (void)setupViews {
  129. self.container.backgroundColor = TUIChatDynamicColor(@"chat_merge_message_bg_color", @"#FFFFFF");
  130. _relayTitleLabel = [[UILabel alloc] init];
  131. _relayTitleLabel.text = @"Chat history";
  132. _relayTitleLabel.font = [UIFont systemFontOfSize:16];
  133. _relayTitleLabel.textColor = TUIChatDynamicColor(@"chat_merge_message_title_color", @"#000000");
  134. [self.container addSubview:_relayTitleLabel];
  135. _contentRowView1 = [[TUIMergeMessageDetailRow alloc] init];
  136. [self.container addSubview:_contentRowView1];
  137. _contentRowView2 = [[TUIMergeMessageDetailRow alloc] init];
  138. [self.container addSubview:_contentRowView2];
  139. _contentRowView3 = [[TUIMergeMessageDetailRow alloc] init];
  140. [self.container addSubview:_contentRowView3];
  141. _separtorView = [[UIView alloc] init];
  142. _separtorView.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB");
  143. [self.container addSubview:_separtorView];
  144. _bottomTipsLabel = [[UILabel alloc] init];
  145. _bottomTipsLabel.text = TIMCommonLocalizableString(TUIKitRelayChatHistory);
  146. _bottomTipsLabel.textColor = TUIChatDynamicColor(@"chat_merge_message_content_color", @"#d5d5d5");
  147. _bottomTipsLabel.font = [UIFont systemFontOfSize:9];
  148. [self.container addSubview:_bottomTipsLabel];
  149. [self.container.layer insertSublayer:self.borderLayer atIndex:0];
  150. [self.container.layer setMask:self.maskLayer];
  151. }
  152. + (BOOL)requiresConstraintBasedLayout {
  153. return YES;
  154. }
  155. // this is Apple's recommended place for adding/updating constraints
  156. - (void)updateConstraints {
  157. [super updateConstraints];
  158. [self.relayTitleLabel sizeToFit];
  159. [self.relayTitleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
  160. make.leading.mas_equalTo(self.container).mas_offset(10);
  161. make.top.mas_equalTo(self.container).mas_offset(10);
  162. make.trailing.mas_equalTo(self.container).mas_offset(-10);
  163. make.height.mas_equalTo(self.relayTitleLabel.font.lineHeight);
  164. }];
  165. [self.contentRowView1 mas_remakeConstraints:^(MASConstraintMaker *make) {
  166. make.leading.mas_equalTo(self.relayTitleLabel);
  167. make.top.mas_equalTo(self.relayTitleLabel.mas_bottom).mas_offset(3);
  168. make.trailing.mas_equalTo(self.container);
  169. make.height.mas_equalTo(self.mergeData.abstractRow1Size.height);
  170. }];
  171. [self.contentRowView2 mas_remakeConstraints:^(MASConstraintMaker *make) {
  172. make.leading.mas_equalTo(self.relayTitleLabel);
  173. make.top.mas_equalTo(self.contentRowView1.mas_bottom).mas_offset(3);
  174. make.trailing.mas_equalTo(self.container);
  175. make.height.mas_equalTo(self.mergeData.abstractRow2Size.height);
  176. }];
  177. [self.contentRowView3 mas_remakeConstraints:^(MASConstraintMaker *make) {
  178. make.leading.mas_equalTo(self.relayTitleLabel);
  179. make.top.mas_equalTo(self.contentRowView2.mas_bottom).mas_offset(3);
  180. make.trailing.mas_equalTo(self.container);
  181. make.height.mas_equalTo(self.mergeData.abstractRow3Size.height);
  182. }];
  183. UIView *lastView = self.contentRowView1;
  184. int count = self.mergeData.abstractSendDetailList.count;
  185. if (count >= 3) {
  186. lastView = self.contentRowView3;
  187. }
  188. else if (count == 2){
  189. lastView = self.contentRowView2;
  190. }
  191. [self.separtorView mas_remakeConstraints:^(MASConstraintMaker *make) {
  192. make.leading.mas_equalTo(self.container).mas_offset(10);
  193. make.trailing.mas_equalTo(self.container).mas_offset(-10);
  194. make.top.mas_equalTo(lastView.mas_bottom).mas_offset(3);
  195. make.height.mas_equalTo(1);
  196. }];
  197. [self.bottomTipsLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
  198. make.leading.mas_equalTo(self.contentRowView1);
  199. make.top.mas_equalTo(self.separtorView.mas_bottom).mas_offset(5);
  200. make.width.mas_lessThanOrEqualTo(self.container);
  201. make.height.mas_equalTo(self.bottomTipsLabel.font.lineHeight);
  202. }];
  203. self.maskLayer.frame = self.container.bounds;
  204. self.borderLayer.frame = self.container.bounds;
  205. UIRectCorner corner = UIRectCornerBottomLeft | UIRectCornerBottomRight | UIRectCornerTopLeft;
  206. if (self.mergeData.direction == MsgDirectionIncoming) {
  207. corner = UIRectCornerBottomLeft | UIRectCornerBottomRight | UIRectCornerTopRight;
  208. }
  209. UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:self.container.bounds byRoundingCorners:corner cornerRadii:CGSizeMake(10, 10)];
  210. self.maskLayer.path = bezierPath.CGPath;
  211. self.borderLayer.path = bezierPath.CGPath;
  212. }
  213. - (void)layoutSubviews {
  214. [super layoutSubviews];
  215. }
  216. - (void)fillWithData:(TUIMergeMessageCellData *)data {
  217. [super fillWithData:data];
  218. self.mergeData = data;
  219. self.relayTitleLabel.text = data.title;
  220. int count = self.mergeData.abstractSendDetailList.count;
  221. switch (count) {
  222. case 1:
  223. [self.contentRowView1 fillWithData:self.mergeData.abstractSendDetailList[0][@"sender"] detailContent:self.mergeData.abstractSendDetailList[0][@"detail"]];
  224. self.contentRowView1.hidden = NO;
  225. self.contentRowView2.hidden = YES;
  226. self.contentRowView3.hidden = YES;
  227. break;
  228. case 2:
  229. [self.contentRowView1 fillWithData:self.mergeData.abstractSendDetailList[0][@"sender"] detailContent:self.mergeData.abstractSendDetailList[0][@"detail"]];
  230. [self.contentRowView2 fillWithData:self.mergeData.abstractSendDetailList[1][@"sender"] detailContent:self.mergeData.abstractSendDetailList[1][@"detail"]];
  231. self.contentRowView1.hidden = NO;
  232. self.contentRowView2.hidden = NO;
  233. self.contentRowView3.hidden = YES;
  234. break;
  235. default:
  236. [self.contentRowView1 fillWithData:self.mergeData.abstractSendDetailList[0][@"sender"] detailContent:self.mergeData.abstractSendDetailList[0][@"detail"]];
  237. [self.contentRowView2 fillWithData:self.mergeData.abstractSendDetailList[1][@"sender"] detailContent:self.mergeData.abstractSendDetailList[1][@"detail"]];
  238. [self.contentRowView3 fillWithData:self.mergeData.abstractSendDetailList[2][@"sender"] detailContent:self.mergeData.abstractSendDetailList[2][@"detail"]];
  239. self.contentRowView1.hidden = NO;
  240. self.contentRowView2.hidden = NO;
  241. self.contentRowView3.hidden = NO;
  242. break;
  243. }
  244. [self prepareReactTagUI:self.container];
  245. // tell constraints they need updating
  246. [self setNeedsUpdateConstraints];
  247. // update constraints now so we can animate the change
  248. [self updateConstraintsIfNeeded];
  249. [self layoutIfNeeded];
  250. }
  251. - (CAShapeLayer *)maskLayer {
  252. if (_maskLayer == nil) {
  253. _maskLayer = [CAShapeLayer layer];
  254. }
  255. return _maskLayer;
  256. }
  257. - (CAShapeLayer *)borderLayer {
  258. if (_borderLayer == nil) {
  259. _borderLayer = [CAShapeLayer layer];
  260. _borderLayer.lineWidth = 1.0;
  261. _borderLayer.strokeColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB").CGColor;
  262. _borderLayer.fillColor = [UIColor clearColor].CGColor;
  263. }
  264. return _borderLayer;
  265. }
  266. // MARK: ThemeChanged
  267. - (void)applyBorderTheme {
  268. if (_borderLayer) {
  269. _borderLayer.strokeColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB").CGColor;
  270. }
  271. }
  272. - (void)onThemeChanged {
  273. [self applyBorderTheme];
  274. }
  275. - (void)prepareReactTagUI:(UIView *)containerView {
  276. NSDictionary *param = @{TUICore_TUIChatExtension_ChatMessageReactPreview_Delegate: self};
  277. [TUICore raiseExtension:TUICore_TUIChatExtension_ChatMessageReactPreview_ClassicExtensionID parentView:containerView param:param];
  278. }
  279. #pragma mark - TUIMessageCellProtocol
  280. + (CGSize)getContentSize:(TUIMessageCellData *)data {
  281. NSAssert([data isKindOfClass:TUIMergeMessageCellData.class], @"data must be kind of TUIMergeMessageCellData");
  282. TUIMergeMessageCellData *mergeCellData = (TUIMergeMessageCellData *)data;
  283. mergeCellData.abstractRow1Size = [self.class caculate:mergeCellData index:0];
  284. mergeCellData.abstractRow2Size = [self.class caculate:mergeCellData index:1];
  285. mergeCellData.abstractRow3Size = [self.class caculate:mergeCellData index:2];
  286. NSAttributedString *abstractAttributedString = [mergeCellData abstractAttributedString];
  287. CGRect rect = [abstractAttributedString boundingRectWithSize:CGSizeMake(TMergeMessageCell_Width_Max - 20, MAXFLOAT)
  288. options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
  289. context:nil];
  290. CGSize size = CGSizeMake(CGFLOAT_CEIL(rect.size.width), CGFLOAT_CEIL(rect.size.height) - 10);
  291. mergeCellData.abstractSize = size;
  292. CGFloat height = mergeCellData.abstractRow1Size.height + mergeCellData.abstractRow2Size.height + mergeCellData.abstractRow3Size.height;
  293. UIFont *titleFont = [UIFont systemFontOfSize:16];
  294. height = (10 + titleFont.lineHeight + 3) + height + 1 + 5 + 20 + 5 +3;
  295. return CGSizeMake(TMergeMessageCell_Width_Max, height);
  296. }
  297. + (CGSize)caculate:(TUIMergeMessageCellData *)data index:(NSInteger)index {
  298. NSArray<NSDictionary *> *abstractSendDetailList = data.abstractSendDetailList;
  299. if (abstractSendDetailList.count <= index){
  300. return CGSizeZero;
  301. }
  302. NSAttributedString * senderStr = abstractSendDetailList[index][@"sender"];
  303. CGRect senderRect = [senderStr boundingRectWithSize:CGSizeMake(70, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
  304. context:nil];
  305. NSMutableAttributedString *abstr = [[NSMutableAttributedString alloc] initWithString:@""];
  306. [abstr appendAttributedString:[[NSAttributedString alloc] initWithString:@":"]];
  307. [abstr appendAttributedString:abstractSendDetailList[index][@"detail"]];
  308. CGFloat senderWidth = MIN(CGFLOAT_CEIL(senderRect.size.width), 70);
  309. CGRect rect = [abstr boundingRectWithSize:CGSizeMake(200 - 20 - senderWidth, MAXFLOAT)
  310. options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
  311. context:nil];
  312. CGSize size = CGSizeMake(TMergeMessageCell_Width_Max,
  313. MIN(TMergeMessageCell_Height_Max / 3.0, CGFLOAT_CEIL(rect.size.height)));
  314. return size;
  315. }
  316. @end