// // TUIMergeMessageCell.m // Pods // // Created by harvy on 2020/12/9. // Copyright © 2023 Tencent. All rights reserved. // #import "TUIMergeMessageCell.h" #import #import #import #ifndef CGFLOAT_CEIL #ifdef CGFLOAT_IS_DOUBLE #define CGFLOAT_CEIL(value) ceil(value) #else #define CGFLOAT_CEIL(value) ceilf(value) #endif #endif @interface TUIMergeMessageDetailRow : UIView @property(nonatomic, strong) UILabel *abstractName; @property(nonatomic, strong) UILabel *abstractBreak; @property(nonatomic, strong) UILabel *abstractDetail; @property(nonatomic, assign) CGFloat abstractNameLimitedWidth; - (void)fillWithData:(NSAttributedString *)name detailContent:(NSAttributedString *)detailContent; @end @implementation TUIMergeMessageDetailRow - (instancetype)init { self = [super init]; if(self){ [self setupview]; } return self; } - (void)setupview { [self addSubview:self.abstractName]; [self addSubview:self.abstractBreak]; [self addSubview:self.abstractDetail]; } - (UILabel *)abstractName { if(!_abstractName) { _abstractName = [[UILabel alloc] init]; _abstractName.numberOfLines = 1; _abstractName.font = [UIFont systemFontOfSize:12.0]; _abstractName.textColor = [UIColor colorWithRed:187 / 255.0 green:187 / 255.0 blue:187 / 255.0 alpha:1 / 1.0]; _abstractName.textAlignment = isRTL()? NSTextAlignmentRight:NSTextAlignmentLeft; } return _abstractName; } - (UILabel *)abstractBreak { if(!_abstractBreak) { _abstractBreak = [[UILabel alloc] init]; _abstractBreak.text = @":"; _abstractBreak.font = [UIFont systemFontOfSize:12.0]; _abstractBreak.textColor = TUIChatDynamicColor(@"chat_merge_message_content_color", @"#d5d5d5"); } return _abstractBreak; } - (UILabel *)abstractDetail { if(!_abstractDetail) { _abstractDetail = [[UILabel alloc] init]; _abstractDetail.numberOfLines = 0; _abstractDetail.font = [UIFont systemFontOfSize:12.0]; _abstractDetail.textColor = TUIChatDynamicColor(@"chat_merge_message_content_color", @"#d5d5d5"); _abstractDetail.textAlignment = isRTL()? NSTextAlignmentRight:NSTextAlignmentLeft; } return _abstractDetail; } + (BOOL)requiresConstraintBasedLayout { return YES; } // this is Apple's recommended place for adding/updating constraints - (void)updateConstraints { [super updateConstraints]; [self.abstractName sizeToFit]; [self.abstractName mas_remakeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(0); make.top.mas_equalTo(0); make.trailing.mas_lessThanOrEqualTo(self.abstractBreak.mas_leading); make.width.mas_equalTo(self.abstractNameLimitedWidth); }]; [self.abstractBreak sizeToFit]; [self.abstractBreak mas_remakeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(self.abstractName.mas_trailing); make.top.mas_equalTo(self.abstractName); make.width.mas_offset(self.abstractBreak.frame.size.width); make.height.mas_offset(self.abstractBreak.frame.size.height); }]; [self.abstractDetail sizeToFit]; [self.abstractDetail mas_remakeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(self.abstractBreak.mas_trailing); make.top.mas_equalTo(0); make.trailing.mas_lessThanOrEqualTo(self.mas_trailing).mas_offset(-15); make.bottom.mas_equalTo(self); }]; } - (void)fillWithData:(NSAttributedString *)name detailContent:(NSAttributedString *)detailContent { self.abstractName.attributedText = name; self.abstractDetail.attributedText = detailContent; NSAttributedString * senderStr = [[NSAttributedString alloc] initWithString:self.abstractName.text]; CGRect senderRect = [senderStr boundingRectWithSize:CGSizeMake(70, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil]; self.abstractNameLimitedWidth = MIN(ceil(senderRect.size.width) + 2, 70); // tell constraints they need updating [self setNeedsUpdateConstraints]; // update constraints now so we can animate the change [self updateConstraintsIfNeeded]; [self layoutIfNeeded]; } - (void)layoutSubviews { [super layoutSubviews]; } @end @interface TUIMergeMessageCell () @property(nonatomic, strong) CAShapeLayer *maskLayer; @property(nonatomic, strong) CAShapeLayer *borderLayer; @property(nonatomic, strong) TUIMergeMessageDetailRow *contentRowView1; @property(nonatomic, strong) TUIMergeMessageDetailRow *contentRowView2; @property(nonatomic, strong) TUIMergeMessageDetailRow *contentRowView3; @end @implementation TUIMergeMessageCell - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { if ([super initWithStyle:style reuseIdentifier:reuseIdentifier]) { [self setupViews]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onThemeChanged) name:TUIDidApplyingThemeChangedNotfication object:nil]; } return self; } - (void)setupViews { self.container.backgroundColor = TUIChatDynamicColor(@"chat_merge_message_bg_color", @"#FFFFFF"); _relayTitleLabel = [[UILabel alloc] init]; _relayTitleLabel.text = @"Chat history"; _relayTitleLabel.font = [UIFont systemFontOfSize:16]; _relayTitleLabel.textColor = TUIChatDynamicColor(@"chat_merge_message_title_color", @"#000000"); [self.container addSubview:_relayTitleLabel]; _contentRowView1 = [[TUIMergeMessageDetailRow alloc] init]; [self.container addSubview:_contentRowView1]; _contentRowView2 = [[TUIMergeMessageDetailRow alloc] init]; [self.container addSubview:_contentRowView2]; _contentRowView3 = [[TUIMergeMessageDetailRow alloc] init]; [self.container addSubview:_contentRowView3]; _separtorView = [[UIView alloc] init]; _separtorView.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB"); [self.container addSubview:_separtorView]; _bottomTipsLabel = [[UILabel alloc] init]; _bottomTipsLabel.text = TIMCommonLocalizableString(TUIKitRelayChatHistory); _bottomTipsLabel.textColor = TUIChatDynamicColor(@"chat_merge_message_content_color", @"#d5d5d5"); _bottomTipsLabel.font = [UIFont systemFontOfSize:9]; [self.container addSubview:_bottomTipsLabel]; [self.container.layer insertSublayer:self.borderLayer atIndex:0]; [self.container.layer setMask:self.maskLayer]; } + (BOOL)requiresConstraintBasedLayout { return YES; } // this is Apple's recommended place for adding/updating constraints - (void)updateConstraints { [super updateConstraints]; [self.relayTitleLabel sizeToFit]; [self.relayTitleLabel mas_remakeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(self.container).mas_offset(10); make.top.mas_equalTo(self.container).mas_offset(10); make.trailing.mas_equalTo(self.container).mas_offset(-10); make.height.mas_equalTo(self.relayTitleLabel.font.lineHeight); }]; [self.contentRowView1 mas_remakeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(self.relayTitleLabel); make.top.mas_equalTo(self.relayTitleLabel.mas_bottom).mas_offset(3); make.trailing.mas_equalTo(self.container); make.height.mas_equalTo(self.mergeData.abstractRow1Size.height); }]; [self.contentRowView2 mas_remakeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(self.relayTitleLabel); make.top.mas_equalTo(self.contentRowView1.mas_bottom).mas_offset(3); make.trailing.mas_equalTo(self.container); make.height.mas_equalTo(self.mergeData.abstractRow2Size.height); }]; [self.contentRowView3 mas_remakeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(self.relayTitleLabel); make.top.mas_equalTo(self.contentRowView2.mas_bottom).mas_offset(3); make.trailing.mas_equalTo(self.container); make.height.mas_equalTo(self.mergeData.abstractRow3Size.height); }]; UIView *lastView = self.contentRowView1; int count = self.mergeData.abstractSendDetailList.count; if (count >= 3) { lastView = self.contentRowView3; } else if (count == 2){ lastView = self.contentRowView2; } [self.separtorView mas_remakeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(self.container).mas_offset(10); make.trailing.mas_equalTo(self.container).mas_offset(-10); make.top.mas_equalTo(lastView.mas_bottom).mas_offset(3); make.height.mas_equalTo(1); }]; [self.bottomTipsLabel mas_remakeConstraints:^(MASConstraintMaker *make) { make.leading.mas_equalTo(self.contentRowView1); make.top.mas_equalTo(self.separtorView.mas_bottom).mas_offset(5); make.width.mas_lessThanOrEqualTo(self.container); make.height.mas_equalTo(self.bottomTipsLabel.font.lineHeight); }]; self.maskLayer.frame = self.container.bounds; self.borderLayer.frame = self.container.bounds; UIRectCorner corner = UIRectCornerBottomLeft | UIRectCornerBottomRight | UIRectCornerTopLeft; if (self.mergeData.direction == MsgDirectionIncoming) { corner = UIRectCornerBottomLeft | UIRectCornerBottomRight | UIRectCornerTopRight; } UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:self.container.bounds byRoundingCorners:corner cornerRadii:CGSizeMake(10, 10)]; self.maskLayer.path = bezierPath.CGPath; self.borderLayer.path = bezierPath.CGPath; } - (void)layoutSubviews { [super layoutSubviews]; } - (void)fillWithData:(TUIMergeMessageCellData *)data { [super fillWithData:data]; self.mergeData = data; self.relayTitleLabel.text = data.title; int count = self.mergeData.abstractSendDetailList.count; switch (count) { case 1: [self.contentRowView1 fillWithData:self.mergeData.abstractSendDetailList[0][@"sender"] detailContent:self.mergeData.abstractSendDetailList[0][@"detail"]]; self.contentRowView1.hidden = NO; self.contentRowView2.hidden = YES; self.contentRowView3.hidden = YES; break; case 2: [self.contentRowView1 fillWithData:self.mergeData.abstractSendDetailList[0][@"sender"] detailContent:self.mergeData.abstractSendDetailList[0][@"detail"]]; [self.contentRowView2 fillWithData:self.mergeData.abstractSendDetailList[1][@"sender"] detailContent:self.mergeData.abstractSendDetailList[1][@"detail"]]; self.contentRowView1.hidden = NO; self.contentRowView2.hidden = NO; self.contentRowView3.hidden = YES; break; default: [self.contentRowView1 fillWithData:self.mergeData.abstractSendDetailList[0][@"sender"] detailContent:self.mergeData.abstractSendDetailList[0][@"detail"]]; [self.contentRowView2 fillWithData:self.mergeData.abstractSendDetailList[1][@"sender"] detailContent:self.mergeData.abstractSendDetailList[1][@"detail"]]; [self.contentRowView3 fillWithData:self.mergeData.abstractSendDetailList[2][@"sender"] detailContent:self.mergeData.abstractSendDetailList[2][@"detail"]]; self.contentRowView1.hidden = NO; self.contentRowView2.hidden = NO; self.contentRowView3.hidden = NO; break; } [self prepareReactTagUI:self.container]; // tell constraints they need updating [self setNeedsUpdateConstraints]; // update constraints now so we can animate the change [self updateConstraintsIfNeeded]; [self layoutIfNeeded]; } - (CAShapeLayer *)maskLayer { if (_maskLayer == nil) { _maskLayer = [CAShapeLayer layer]; } return _maskLayer; } - (CAShapeLayer *)borderLayer { if (_borderLayer == nil) { _borderLayer = [CAShapeLayer layer]; _borderLayer.lineWidth = 1.0; _borderLayer.strokeColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB").CGColor; _borderLayer.fillColor = [UIColor clearColor].CGColor; } return _borderLayer; } // MARK: ThemeChanged - (void)applyBorderTheme { if (_borderLayer) { _borderLayer.strokeColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB").CGColor; } } - (void)onThemeChanged { [self applyBorderTheme]; } - (void)prepareReactTagUI:(UIView *)containerView { NSDictionary *param = @{TUICore_TUIChatExtension_ChatMessageReactPreview_Delegate: self}; [TUICore raiseExtension:TUICore_TUIChatExtension_ChatMessageReactPreview_ClassicExtensionID parentView:containerView param:param]; } #pragma mark - TUIMessageCellProtocol + (CGSize)getContentSize:(TUIMessageCellData *)data { NSAssert([data isKindOfClass:TUIMergeMessageCellData.class], @"data must be kind of TUIMergeMessageCellData"); TUIMergeMessageCellData *mergeCellData = (TUIMergeMessageCellData *)data; mergeCellData.abstractRow1Size = [self.class caculate:mergeCellData index:0]; mergeCellData.abstractRow2Size = [self.class caculate:mergeCellData index:1]; mergeCellData.abstractRow3Size = [self.class caculate:mergeCellData index:2]; NSAttributedString *abstractAttributedString = [mergeCellData abstractAttributedString]; CGRect rect = [abstractAttributedString boundingRectWithSize:CGSizeMake(TMergeMessageCell_Width_Max - 20, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil]; CGSize size = CGSizeMake(CGFLOAT_CEIL(rect.size.width), CGFLOAT_CEIL(rect.size.height) - 10); mergeCellData.abstractSize = size; CGFloat height = mergeCellData.abstractRow1Size.height + mergeCellData.abstractRow2Size.height + mergeCellData.abstractRow3Size.height; UIFont *titleFont = [UIFont systemFontOfSize:16]; height = (10 + titleFont.lineHeight + 3) + height + 1 + 5 + 20 + 5 +3; return CGSizeMake(TMergeMessageCell_Width_Max, height); } + (CGSize)caculate:(TUIMergeMessageCellData *)data index:(NSInteger)index { NSArray *abstractSendDetailList = data.abstractSendDetailList; if (abstractSendDetailList.count <= index){ return CGSizeZero; } NSAttributedString * senderStr = abstractSendDetailList[index][@"sender"]; CGRect senderRect = [senderStr boundingRectWithSize:CGSizeMake(70, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil]; NSMutableAttributedString *abstr = [[NSMutableAttributedString alloc] initWithString:@""]; [abstr appendAttributedString:[[NSAttributedString alloc] initWithString:@":"]]; [abstr appendAttributedString:abstractSendDetailList[index][@"detail"]]; CGFloat senderWidth = MIN(CGFLOAT_CEIL(senderRect.size.width), 70); CGRect rect = [abstr boundingRectWithSize:CGSizeMake(200 - 20 - senderWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil]; CGSize size = CGSizeMake(TMergeMessageCell_Width_Max, MIN(TMergeMessageCell_Height_Max / 3.0, CGFLOAT_CEIL(rect.size.height))); return size; } @end