提交
This commit is contained in:
249
TUIKit/TIMCommon/BaseCell/TUIMessageCell.h
Normal file
249
TUIKit/TIMCommon/BaseCell/TUIMessageCell.h
Normal file
@@ -0,0 +1,249 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
/**
|
||||
*
|
||||
*
|
||||
* This document declares the modules and components used to implement the message unit.
|
||||
* The message unit (TUIMessageCell) is a general term for the bubble message/picture message/emoticon message/video message displayed in the chat view.
|
||||
* The above messages are implemented by inheriting from this class or a subclass of this class. If you want to customize the message, you also need to
|
||||
* implement it by inheriting from this class or a subclass of this class. The XXXX message unit (TUIXXXXMessageCell) is mainly responsible for displaying on
|
||||
* the page and responding to user interaction events. For data processing and acquisition in the message unit, please refer to
|
||||
* TUIChat\CellData\TUIXXXXMessageCellData.h according to the specific message unit
|
||||
*
|
||||
* The interaction callbacks provided by the TUIMessageCellDelegate protocol include: long press, resend, click on the message, click on the avatar, etc.
|
||||
* The TUIMessageCell class stores message-related information, such as the sender's avatar, sender's nickname, and message content (supports various formats
|
||||
* such as text, pictures, and videos). At the same time, TUIMessageeCell, as a parent class, provides basic properties and behavior templates for subclass
|
||||
* messages.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIFitButton.h"
|
||||
#import "TUIMessageCellData.h"
|
||||
#import "TUISecurityStrikeView.h"
|
||||
|
||||
|
||||
@class TUIMessageCell;
|
||||
|
||||
|
||||
@protocol TUIMessageCellProtocol <NSObject>
|
||||
|
||||
@required
|
||||
+ (CGFloat)getHeight:(TUIMessageCellData *)data withWidth:(CGFloat)width;
|
||||
+ (CGFloat)getEstimatedHeight:(TUIMessageCellData *)data;
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIMessageCellDelegate
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol TUIMessageCellDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* Callback for long press message
|
||||
* You can use this callback to implement secondary operations such as delete and recall (when the sender of the message long-presses his own message) on top
|
||||
* of the long-pressed message.
|
||||
*/
|
||||
- (void)onLongPressMessage:(TUIMessageCell *)cell;
|
||||
|
||||
/**
|
||||
* Callback for clicking retryView
|
||||
* You can use this callback to implement: resend the message.
|
||||
*/
|
||||
- (void)onRetryMessage:(TUIMessageCell *)cell;
|
||||
|
||||
/**
|
||||
* Callback for clicking message cell
|
||||
* Usually:
|
||||
* - Clicking on the sound message means playing voice
|
||||
* - Clicking on the file message means opening the file
|
||||
* - Clicking on the picture message means showing the larger image
|
||||
* - Clicking on the video message means playing the video.
|
||||
* Usually, it only provides a reference for the function implementation, and you can implement the delegate function according to your needs.
|
||||
*/
|
||||
- (void)onSelectMessage:(TUIMessageCell *)cell;
|
||||
|
||||
/**
|
||||
* Callback for clicking avatar view of the messageCell
|
||||
* You can use this callback to implement: in response to the user's click, jump to the detailed information interface of the corresponding user.
|
||||
*/
|
||||
- (void)onSelectMessageAvatar:(TUIMessageCell *)cell;
|
||||
|
||||
/**
|
||||
* Callback for long pressing avatar view of messageCell
|
||||
*/
|
||||
- (void)onLongSelectMessageAvatar:(TUIMessageCell *)cell;
|
||||
|
||||
/**
|
||||
* Callback for clicking read receipt label
|
||||
*/
|
||||
- (void)onSelectReadReceipt:(TUIMessageCellData *)cell;
|
||||
|
||||
/**
|
||||
* Clicking the x-person reply button to jump to the multi-person reply details page
|
||||
*/
|
||||
- (void)onJumpToRepliesDetailPage:(TUIMessageCellData *)data;
|
||||
|
||||
|
||||
- (void)onJumpToMessageInfoPage:(TUIMessageCellData *)data selectCell:(TUIMessageCell *)cell;
|
||||
|
||||
@end
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIMessageCell
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface TUIMessageCell : TUICommonTableViewCell <TUIMessageCellProtocol>
|
||||
|
||||
/**
|
||||
* Icon that identifies the message selected
|
||||
* In the multi-selection scenario, it is used to identify whether the message is selected
|
||||
*/
|
||||
@property(nonatomic, strong) UIImageView *selectedIcon;
|
||||
|
||||
/**
|
||||
* Message selection view
|
||||
* When multiple selection is activated, the view will be overlaid on this cell, and clicking on the view will trigger the check/uncheck of the message
|
||||
*/
|
||||
@property(nonatomic, strong) UIButton *selectedView;
|
||||
|
||||
/**
|
||||
*
|
||||
* The icon view of displays user's avatar
|
||||
*/
|
||||
@property(nonatomic, strong) UIImageView *avatarView;
|
||||
|
||||
/**
|
||||
*
|
||||
* The label of displays user's displayname
|
||||
*/
|
||||
@property(nonatomic, strong) UILabel *nameLabel;
|
||||
|
||||
/**
|
||||
* Container view
|
||||
* It wraps various views of MesageCell as the "bottom" of MessageCell, which is convenient for view management and layout.
|
||||
*/
|
||||
@property(nonatomic, strong) UIView *container;
|
||||
|
||||
/**
|
||||
* Activity indicator
|
||||
* A circling icon is provided while the message is being sent to indicate that the message is being sent.
|
||||
*/
|
||||
@property(nonatomic, strong) UIActivityIndicatorView *indicator;
|
||||
|
||||
/**
|
||||
* Retry view, displayed after sending failed, click on this view to trigger onRetryMessage: callback.
|
||||
*/
|
||||
@property(nonatomic, strong) UIImageView *retryView;
|
||||
|
||||
|
||||
/**
|
||||
* security Strike View
|
||||
*/
|
||||
@property (nonatomic, strong) TUISecurityStrikeView * securityStrikeView;
|
||||
|
||||
/**
|
||||
* Message reply details button
|
||||
*/
|
||||
@property(nonatomic, strong) TUIFitButton *messageModifyRepliesButton;
|
||||
|
||||
/**
|
||||
* The message data class which stores the information required in the messageCell, including sender ID, sender avatar, message sending status, message bubble
|
||||
* icon, etc. For details of messageData, please refer to: TUIChat\Cell\CellData\TUIMessageCellData.h
|
||||
*/
|
||||
@property(readonly) TUIMessageCellData *messageData;
|
||||
|
||||
/**
|
||||
* A control that identifies whether a message has been read
|
||||
*/
|
||||
@property(nonatomic, strong) UILabel *readReceiptLabel;
|
||||
|
||||
/**
|
||||
* The message time label control, which is not displayed by default, is located at the far right of the message cell
|
||||
* In the message forwarding scenario, open the forwarded message list, and the time of the current message will be displayed on the far right of the message.
|
||||
*/
|
||||
@property(nonatomic, strong) UILabel *timeLabel;
|
||||
|
||||
/**
|
||||
* Whether to disable the default selection behavior encapsulated in TUIKit, such as group live broadcast by default to create live room and other behaviors,
|
||||
* default: NO
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL disableDefaultSelectAction;
|
||||
|
||||
@property(nonatomic, weak) id<TUIMessageCellDelegate> delegate;
|
||||
|
||||
/**
|
||||
*
|
||||
* Whether the highlight flashing animation is in progress
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL highlightAnimating;
|
||||
|
||||
- (void)fillWithData:(TUICommonCellData *)data;
|
||||
|
||||
/**
|
||||
* Set the highlighting effect after matching the keyword, mainly used for jumping after message search, subclass rewriting
|
||||
* The base class provides the default highlighting effect, and the subclass can implement it freely
|
||||
*
|
||||
* @param keyword Highlight keywords
|
||||
*/
|
||||
- (void)highlightWhenMatchKeyword:(NSString *)keyword;
|
||||
|
||||
/**
|
||||
* Returns the view for highlighting
|
||||
*/
|
||||
- (UIView *)highlightAnimateView;
|
||||
|
||||
/**
|
||||
* Update the content of the read label
|
||||
*/
|
||||
- (void)updateReadLabelText;
|
||||
|
||||
/// Preset bottom container in cell, which can be added custom view/viewControllers.
|
||||
@property(nonatomic, strong) UIView *bottomContainer;
|
||||
|
||||
/// When bottom container is layout ready, notify it to add custom extensions.
|
||||
- (void)notifyBottomContainerReadyOfData:(TUIMessageCellData *)cellData;
|
||||
|
||||
/// Callback of SelectCell
|
||||
@property(nonatomic, copy) TUIValueCallbck pluginMsgSelectCallback;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface TUIMessageCell (TUILayoutConfiguration)
|
||||
|
||||
/**
|
||||
* The color of the label that displays the recipient's nickname
|
||||
* Used when the nickname needs to be displayed and the message direction is MsgDirectionIncoming
|
||||
*/
|
||||
@property(nonatomic, class) UIColor *incommingNameColor;
|
||||
|
||||
/**
|
||||
*
|
||||
* The font of the label that displays the recipient's nickname
|
||||
* Used when the nickname needs to be displayed and the message direction is MsgDirectionIncoming
|
||||
*
|
||||
*/
|
||||
@property(nonatomic, class) UIFont *incommingNameFont;
|
||||
|
||||
/**
|
||||
* The color of the label showing the sender's nickname
|
||||
* Used when the nickname needs to be displayed and the message direction is MsgDirectionOutgoing.
|
||||
*/
|
||||
@property(nonatomic, class) UIColor *outgoingNameColor;
|
||||
|
||||
/**
|
||||
*
|
||||
* The font of the label that displays the sender's nickname
|
||||
* Used when the nickname needs to be displayed and the message direction is MsgDirectionOutgoing.
|
||||
*/
|
||||
@property(nonatomic, class) UIFont *outgoingNameFont;
|
||||
|
||||
@end
|
||||
680
TUIKit/TIMCommon/BaseCell/TUIMessageCell.m
Normal file
680
TUIKit/TIMCommon/BaseCell/TUIMessageCell.m
Normal file
@@ -0,0 +1,680 @@
|
||||
//
|
||||
// TUIMessageCell.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by kennethmiao on 2018/9/17.
|
||||
// Copyright © 2018 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIMessageCell.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TUICore/TUITool.h>
|
||||
#import "NSString+TUIEmoji.h"
|
||||
#import "TUISystemMessageCellData.h"
|
||||
#import <TUICore/TUICore.h>
|
||||
|
||||
@interface TUIMessageCell () <CAAnimationDelegate>
|
||||
@property(nonatomic, strong) TUIMessageCellData *messageData;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMessageCell
|
||||
|
||||
#pragma mark - Life cycle
|
||||
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
[self setupSubViews];
|
||||
[self setupRAC];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupSubViews {
|
||||
// head
|
||||
_avatarView = [[UIImageView alloc] init];
|
||||
_avatarView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
[self.contentView addSubview:_avatarView];
|
||||
UITapGestureRecognizer *tap1 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onSelectMessageAvatar:)];
|
||||
[_avatarView addGestureRecognizer:tap1];
|
||||
UILongPressGestureRecognizer *tap2 = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onLongSelectMessageAvatar:)];
|
||||
[_avatarView addGestureRecognizer:tap2];
|
||||
[_avatarView setUserInteractionEnabled:YES];
|
||||
|
||||
// nameLabel
|
||||
_nameLabel = [[UILabel alloc] init];
|
||||
_nameLabel.font = [self fontWithSize:13];
|
||||
_nameLabel.textColor = [UIColor d_systemGrayColor];
|
||||
[self.contentView addSubview:_nameLabel];
|
||||
|
||||
// container
|
||||
_container = [[UIView alloc] init];
|
||||
_container.backgroundColor = [UIColor clearColor];
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onSelectMessage:)];
|
||||
tap.cancelsTouchesInView = NO;
|
||||
[_container addGestureRecognizer:tap];
|
||||
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onLongPress:)];
|
||||
[_container addGestureRecognizer:longPress];
|
||||
[self.contentView addSubview:_container];
|
||||
|
||||
// indicator
|
||||
_indicator = [[UIActivityIndicatorView alloc] init];
|
||||
_indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
|
||||
[_indicator sizeToFit];
|
||||
[self.contentView addSubview:_indicator];
|
||||
|
||||
// error
|
||||
_retryView = [[UIImageView alloc] init];
|
||||
_retryView.userInteractionEnabled = YES;
|
||||
UITapGestureRecognizer *resendTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onRetryMessage:)];
|
||||
[_retryView addGestureRecognizer:resendTap];
|
||||
[self.contentView addSubview:_retryView];
|
||||
|
||||
// messageModifyRepliesLabel
|
||||
_messageModifyRepliesButton = [[TUIFitButton alloc] initWithFrame:CGRectMake(0, 0, 12, 12)];
|
||||
_messageModifyRepliesButton.imageSize = CGSizeMake(12, 12);
|
||||
[_messageModifyRepliesButton addTarget:self action:@selector(onJumpToRepliesDetailPage:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_messageModifyRepliesButton.titleLabel setFont:[self fontWithSize:12]];
|
||||
[_messageModifyRepliesButton setTitleColor:TIMCommonDynamicColor(@"chat_message_read_name_date_text_color", @"#999999") forState:UIControlStateNormal];
|
||||
[_messageModifyRepliesButton setImage:TIMCommonBundleThemeImage(@"chat_messageReplyIcon_img", @"messageReplyIcon") forState:UIControlStateNormal];
|
||||
[self.contentView addSubview:_messageModifyRepliesButton];
|
||||
|
||||
_readReceiptLabel = [[UILabel alloc] init];
|
||||
_readReceiptLabel.hidden = YES;
|
||||
_readReceiptLabel.font = [self fontWithSize:12];
|
||||
_readReceiptLabel.textColor = TIMCommonDynamicColor(@"chat_message_read_status_text_gray_color", @"#BBBBBB");
|
||||
_readReceiptLabel.lineBreakMode = NSLineBreakByCharWrapping;
|
||||
UITapGestureRecognizer *showReadReceiptTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onSelectReadReceipt:)];
|
||||
[_readReceiptLabel addGestureRecognizer:showReadReceiptTap];
|
||||
_readReceiptLabel.userInteractionEnabled = YES;
|
||||
[self.contentView addSubview:_readReceiptLabel];
|
||||
|
||||
// selectedIcon
|
||||
_selectedIcon = [[UIImageView alloc] init];
|
||||
[self.contentView addSubview:_selectedIcon];
|
||||
|
||||
// selectedView
|
||||
_selectedView = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_selectedView.backgroundColor = [UIColor clearColor];
|
||||
[_selectedView addTarget:self action:@selector(onSelectMessage:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self.contentView addSubview:_selectedView];
|
||||
|
||||
// timeLabel
|
||||
_timeLabel = [[UILabel alloc] init];
|
||||
_timeLabel.textColor = [UIColor darkGrayColor];
|
||||
_timeLabel.font = [self fontWithSize:11.0];
|
||||
[self.contentView addSubview:_timeLabel];
|
||||
|
||||
self.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
self.backgroundColor = UIColor.clearColor;
|
||||
self.contentView.backgroundColor = UIColor.clearColor;
|
||||
|
||||
[self makeConstraints];
|
||||
}
|
||||
|
||||
- (void)makeConstraints {
|
||||
|
||||
[self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(_container.mas_leading).mas_offset(7);
|
||||
make.top.mas_equalTo(self.avatarView.mas_top);
|
||||
make.width.mas_equalTo(1);
|
||||
make.height.mas_equalTo(20);
|
||||
}];
|
||||
|
||||
[self.selectedIcon mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.contentView.mas_leading).mas_offset(3);
|
||||
make.top.mas_equalTo(self.avatarView.mas_centerY).mas_offset(-10);
|
||||
make.width.mas_equalTo(20);
|
||||
make.height.mas_equalTo(20);
|
||||
}];
|
||||
|
||||
[self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-10);
|
||||
make.top.mas_equalTo(self.avatarView);
|
||||
make.width.mas_greaterThanOrEqualTo(10);
|
||||
make.height.mas_equalTo(10);
|
||||
}];
|
||||
|
||||
[self.selectedView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.mas_equalTo(self.contentView);
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
TUIMessageCellLayout *cellLayout = self.messageData.cellLayout;
|
||||
BOOL isInComing = (self.messageData.direction == MsgDirectionIncoming);
|
||||
|
||||
[self.nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if (isInComing) {
|
||||
make.leading.mas_equalTo(_container.mas_leading).mas_offset(7);
|
||||
make.trailing.mas_equalTo(self.contentView).mas_offset(-7);
|
||||
} else {
|
||||
make.leading.mas_equalTo(self.contentView).mas_offset(7);
|
||||
make.trailing.mas_equalTo(self.container.mas_trailing);
|
||||
}
|
||||
if (self.messageData.showName) {
|
||||
make.width.mas_greaterThanOrEqualTo(20);
|
||||
make.height.mas_greaterThanOrEqualTo(20);
|
||||
} else {
|
||||
make.height.mas_equalTo(0);
|
||||
}
|
||||
make.top.mas_equalTo(self.avatarView.mas_top);
|
||||
}];
|
||||
|
||||
[self.selectedIcon mas_updateConstraints:^(MASConstraintMaker *make) {
|
||||
if (self.messageData.showCheckBox) {
|
||||
make.width.mas_equalTo(20);
|
||||
make.height.mas_equalTo(20);
|
||||
} else {
|
||||
make.size.mas_equalTo(CGSizeZero);
|
||||
}
|
||||
}];
|
||||
|
||||
[self.timeLabel sizeToFit];
|
||||
[self.timeLabel mas_updateConstraints:^(MASConstraintMaker *make) {
|
||||
if (self.messageData.showMessageTime) {
|
||||
make.width.mas_equalTo(self.timeLabel.frame.size.width);
|
||||
make.height.mas_equalTo(self.timeLabel.frame.size.height);
|
||||
} else {
|
||||
make.width.mas_equalTo(0);
|
||||
make.height.mas_equalTo(0);
|
||||
}
|
||||
}];
|
||||
|
||||
CGSize csize = [self.class getContentSize:self.messageData];
|
||||
CGFloat contentWidth = csize.width;
|
||||
CGFloat contentHeight = csize.height;
|
||||
|
||||
if (!CGSizeEqualToSize(self.messageData.messageContainerAppendSize, CGSizeZero)) {
|
||||
/**
|
||||
* Taking the maximum width between the "emoji reply message" and the text content
|
||||
*/
|
||||
contentWidth = MAX(self.messageData.messageContainerAppendSize.width, csize.width);
|
||||
/**
|
||||
* Limit the maximum width to Screen_Width *0.25 * 3
|
||||
*/
|
||||
contentWidth = MIN(contentWidth, Screen_Width * 0.25 * 3);
|
||||
contentHeight = csize.height + self.messageData.messageContainerAppendSize.height;
|
||||
}
|
||||
if (self.messageData.direction == MsgDirectionIncoming) {
|
||||
self.avatarView.hidden = !self.messageData.showAvatar;
|
||||
[self.avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if (self.messageData.showCheckBox) {
|
||||
make.leading.mas_equalTo(self.selectedIcon.mas_trailing).mas_offset(cellLayout.avatarInsets.left);
|
||||
} else {
|
||||
make.leading.mas_equalTo(self.contentView.mas_leading).mas_offset(cellLayout.avatarInsets.left);
|
||||
}
|
||||
make.top.mas_equalTo(cellLayout.avatarInsets.top);
|
||||
make.size.mas_equalTo(cellLayout.avatarSize);
|
||||
}];
|
||||
|
||||
[self.container mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.avatarView.mas_trailing).mas_offset(cellLayout.messageInsets.left);
|
||||
make.top.mas_equalTo(self.nameLabel.mas_bottom).mas_offset(cellLayout.messageInsets.top);
|
||||
make.width.mas_equalTo(contentWidth);
|
||||
make.height.mas_equalTo(contentHeight);
|
||||
}];
|
||||
|
||||
CGRect indicatorFrame = self.indicator.frame;
|
||||
[self.indicator mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.container.mas_trailing).mas_offset(8);
|
||||
make.centerY.mas_equalTo(self.container.mas_centerY);
|
||||
make.size.mas_equalTo(indicatorFrame.size);
|
||||
}];
|
||||
self.retryView.frame = self.indicator.frame;
|
||||
self.readReceiptLabel.hidden = YES;
|
||||
} else {
|
||||
if (!self.messageData.showAvatar) {
|
||||
cellLayout.avatarSize = CGSizeZero;
|
||||
}
|
||||
[self.avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.trailing.mas_equalTo(self.contentView.mas_trailing).mas_offset(-cellLayout.avatarInsets.right);
|
||||
make.top.mas_equalTo(cellLayout.avatarInsets.top);
|
||||
make.size.mas_equalTo(cellLayout.avatarSize);
|
||||
}];
|
||||
[self.container mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.trailing.mas_equalTo(self.avatarView.mas_leading).mas_offset(-cellLayout.messageInsets.right);
|
||||
make.top.mas_equalTo(self.nameLabel.mas_bottom).mas_offset(cellLayout.messageInsets.top);
|
||||
make.width.mas_equalTo(contentWidth);
|
||||
make.height.mas_equalTo(contentHeight);
|
||||
}];
|
||||
|
||||
CGRect indicatorFrame = self.indicator.frame;
|
||||
[self.indicator mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.trailing.mas_equalTo(self.container.mas_leading).mas_offset(-8);
|
||||
make.centerY.mas_equalTo(self.container.mas_centerY);
|
||||
make.size.mas_equalTo(indicatorFrame.size);
|
||||
}];
|
||||
|
||||
self.retryView.frame = self.indicator.frame;
|
||||
|
||||
[self.readReceiptLabel sizeToFit];
|
||||
[self.readReceiptLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.bottom.mas_equalTo(self.container.mas_bottom);
|
||||
make.trailing.mas_equalTo(self.container.mas_leading).mas_offset(-8);
|
||||
make.size.mas_equalTo(self.readReceiptLabel.frame.size);
|
||||
}];
|
||||
}
|
||||
|
||||
if (!self.messageModifyRepliesButton.isHidden) {
|
||||
self.messageModifyRepliesButton.mm_sizeToFit();
|
||||
CGFloat repliesBtnTextWidth = self.messageModifyRepliesButton.frame.size.width;
|
||||
[self.messageModifyRepliesButton mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
if (isInComing) {
|
||||
make.leading.mas_equalTo(self.container.mas_leading);
|
||||
} else {
|
||||
make.trailing.mas_equalTo(self.container.mas_trailing);
|
||||
}
|
||||
make.top.mas_equalTo(self.container.mas_bottom);
|
||||
make.size.mas_equalTo(CGSizeMake(repliesBtnTextWidth + 10, 30));
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
// according to apple super should be called at end of method
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)setupRAC {
|
||||
@weakify(self);
|
||||
[RACObserve(self, readReceiptLabel.text) subscribeNext:^(id _Nullable x) {
|
||||
@strongify(self);
|
||||
if ([self shouldHighlightReadReceiptLabel]) {
|
||||
self.readReceiptLabel.textColor = TIMCommonDynamicColor(@"chat_message_read_status_text_color", @"#147AFF");
|
||||
} else {
|
||||
self.readReceiptLabel.textColor = TIMCommonDynamicColor(@"chat_message_read_status_text_gray_color", @"#BBBBBB");
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)prepareForReuse {
|
||||
[super prepareForReuse];
|
||||
/**
|
||||
* In the future, any UI problems caused by reuse can be solved by coding here.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Once the message is reused, it means that a new message is about to appear, and the label content is changed to empty string.
|
||||
*/
|
||||
_readReceiptLabel.text = @"";
|
||||
_readReceiptLabel.hidden = YES;
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
- (void)fillWithData:(TUIMessageCellData *)data {
|
||||
[super fillWithData:data];
|
||||
self.messageData = data;
|
||||
|
||||
[self loadAvatar:data];
|
||||
|
||||
if (self.messageData.showName) {
|
||||
_nameLabel.hidden = NO;
|
||||
} else {
|
||||
_nameLabel.hidden = YES;
|
||||
}
|
||||
|
||||
if (self.messageData.showCheckBox) {
|
||||
_selectedIcon.hidden = NO;
|
||||
_selectedView.hidden = NO;
|
||||
} else {
|
||||
_selectedIcon.hidden = YES;
|
||||
_selectedView.hidden = YES;
|
||||
}
|
||||
|
||||
if ([TUIConfig defaultConfig].avatarType == TAvatarTypeRounded) {
|
||||
self.avatarView.layer.masksToBounds = YES;
|
||||
self.avatarView.layer.cornerRadius = data.cellLayout.avatarSize.height / 2;
|
||||
} else if ([TUIConfig defaultConfig].avatarType == TAvatarTypeRadiusCorner) {
|
||||
self.avatarView.layer.masksToBounds = YES;
|
||||
self.avatarView.layer.cornerRadius = [TUIConfig defaultConfig].avatarCornerRadius;
|
||||
}
|
||||
|
||||
self.nameLabel.text = data.senderName;
|
||||
|
||||
if (data.direction == MsgDirectionIncoming) {
|
||||
self.nameLabel.textColor = self.class.incommingNameColor;
|
||||
self.nameLabel.font = self.class.incommingNameFont;
|
||||
} else {
|
||||
self.nameLabel.textColor = self.class.outgoingNameColor;
|
||||
self.nameLabel.font = self.class.outgoingNameFont;
|
||||
}
|
||||
|
||||
|
||||
self.retryView.image = [UIImage imageNamed:TUIChatImagePath(@"msg_error")];
|
||||
|
||||
if (data.status == Msg_Status_Fail) {
|
||||
[_indicator stopAnimating];
|
||||
_readReceiptLabel.hidden = YES;
|
||||
self.retryView.hidden = NO;
|
||||
} else {
|
||||
if (data.status == Msg_Status_Sending_2) {
|
||||
[_indicator startAnimating];
|
||||
_readReceiptLabel.hidden = YES;
|
||||
} else if (data.status == Msg_Status_Succ) {
|
||||
[_indicator stopAnimating];
|
||||
/**
|
||||
* 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
|
||||
* displayed.
|
||||
*/
|
||||
if (self.messageData.showReadReceipt && self.messageData.direction == MsgDirectionOutgoing && self.messageData.innerMessage.needReadReceipt &&
|
||||
(self.messageData.innerMessage.userID || self.messageData.innerMessage.groupID) &&
|
||||
![self.messageData isKindOfClass:TUISystemMessageCellData.class]) {
|
||||
[self updateReadLabelText];
|
||||
_readReceiptLabel.hidden = NO;
|
||||
}
|
||||
} else if (data.status == Msg_Status_Sending) {
|
||||
[_indicator startAnimating];
|
||||
_readReceiptLabel.hidden = YES;
|
||||
}
|
||||
self.retryView.hidden = YES;
|
||||
}
|
||||
|
||||
self.messageModifyRepliesButton.hidden = !data.showMessageModifyReplies;
|
||||
if (data.showMessageModifyReplies) {
|
||||
NSString *title = [NSString stringWithFormat:@"%ld%@", data.messageModifyReplies.count, TIMCommonLocalizableString(TUIKitRepliesNum)];
|
||||
[self.messageModifyRepliesButton setTitle:title forState:UIControlStateNormal];
|
||||
[self.messageModifyRepliesButton sizeToFit];
|
||||
[self.messageModifyRepliesButton setNeedsUpdateConstraints];
|
||||
[self.messageModifyRepliesButton updateConstraintsIfNeeded];
|
||||
[self.messageModifyRepliesButton layoutIfNeeded];
|
||||
}
|
||||
|
||||
NSString *imageName = (data.showCheckBox && data.selected) ? TIMCommonImagePath(@"icon_select_selected") : TIMCommonImagePath(@"icon_select_normal");
|
||||
self.selectedIcon.image = [UIImage imageNamed:imageName];
|
||||
|
||||
_timeLabel.text = [TUITool convertDateToStr:data.innerMessage.timestamp];
|
||||
[_timeLabel sizeToFit];
|
||||
_timeLabel.hidden = !data.showMessageTime;
|
||||
|
||||
/**
|
||||
* Text highlighting - asynchronous operations are here to keep the order of execution consistent with subclasses
|
||||
*/
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf highlightWhenMatchKeyword:data.highlightKeyword];
|
||||
});
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
|
||||
}
|
||||
|
||||
- (void)loadAvatar:(TUIMessageCellData *)data {
|
||||
[self.avatarView setImage:DefaultAvatarImage];
|
||||
@weakify(self);
|
||||
[[[RACObserve(data, avatarUrl) takeUntil:self.rac_prepareForReuseSignal] ignore:nil] subscribeNext:^(NSURL *url) {
|
||||
@strongify(self);
|
||||
[self.avatarView sd_setImageWithURL:url placeholderImage:DefaultAvatarImage];
|
||||
}];
|
||||
|
||||
if (data.isUseMsgReceiverAvatar) {
|
||||
NSString *userId = @"";
|
||||
if ([data.innerMessage.sender isEqualToString:V2TIMManager.sharedInstance.getLoginUser]) {
|
||||
userId = data.innerMessage.userID;
|
||||
} else {
|
||||
userId = V2TIMManager.sharedInstance.getLoginUser;
|
||||
}
|
||||
|
||||
[V2TIMManager.sharedInstance getUsersInfo:@[ userId?:@"" ]
|
||||
succ:^(NSArray<V2TIMUserFullInfo *> *infoList) {
|
||||
@strongify(self);
|
||||
V2TIMUserFullInfo *info = infoList.firstObject;
|
||||
if (info && [data isEqual:self.messageData]) {
|
||||
data.avatarUrl = [NSURL URLWithString:info.faceURL];
|
||||
[self.avatarView sd_setImageWithURL:data.avatarUrl placeholderImage:DefaultAvatarImage];
|
||||
}
|
||||
}
|
||||
fail:^(int code, NSString *desc){
|
||||
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)highlightWhenMatchKeyword:(NSString *)keyword {
|
||||
static NSString *const key = @"highlightAnimation";
|
||||
if (keyword && keyword.length) {
|
||||
if (self.highlightAnimating) {
|
||||
return;
|
||||
}
|
||||
self.highlightAnimating = YES;
|
||||
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"backgroundColor"];
|
||||
animation.repeatCount = 3;
|
||||
animation.values = @[
|
||||
(id)[[UIColor orangeColor] colorWithAlphaComponent:0.2].CGColor,
|
||||
(id)[[UIColor orangeColor] colorWithAlphaComponent:0.5].CGColor,
|
||||
(id)[[UIColor orangeColor] colorWithAlphaComponent:0.2].CGColor,
|
||||
];
|
||||
animation.duration = 0.5;
|
||||
animation.removedOnCompletion = YES;
|
||||
animation.delegate = self;
|
||||
[self.highlightAnimateView.layer addAnimation:animation forKey:key];
|
||||
} else {
|
||||
[self.highlightAnimateView.layer removeAnimationForKey:key];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateReadLabelText {
|
||||
if (self.messageData.innerMessage.groupID.length > 0) {
|
||||
// group message
|
||||
NSString *text = TIMCommonLocalizableString(Unread);
|
||||
if (self.messageData.messageReceipt == nil) {
|
||||
// haven't received the message receipt yet
|
||||
return;
|
||||
}
|
||||
NSInteger readCount = self.messageData.messageReceipt.readCount;
|
||||
NSInteger unreadCount = self.messageData.messageReceipt.unreadCount;
|
||||
if (unreadCount == 0) {
|
||||
// show "All read"
|
||||
text = TIMCommonLocalizableString(TUIKitMessageReadAllRead);
|
||||
} else if (readCount > 0) {
|
||||
// show "x read"
|
||||
text = [NSString stringWithFormat:@"%ld %@", (long)readCount, TIMCommonLocalizableString(TUIKitMessageReadPartRead)];
|
||||
}
|
||||
self.readReceiptLabel.text = text;
|
||||
} else {
|
||||
// c2c message
|
||||
BOOL isPeerRead = self.messageData.messageReceipt.isPeerRead;
|
||||
NSString *text = isPeerRead ? TIMCommonLocalizableString(TUIKitMessageReadC2CRead) : TIMCommonLocalizableString(TUIKitMessageReadC2CUnRead);
|
||||
self.readReceiptLabel.text = text;
|
||||
}
|
||||
|
||||
[self.readReceiptLabel sizeToFit];
|
||||
[self.readReceiptLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.bottom.mas_equalTo(self.container.mas_bottom);
|
||||
make.trailing.mas_equalTo(self.container.mas_leading).mas_offset(-8);
|
||||
make.size.mas_equalTo(self.readReceiptLabel.frame.size);
|
||||
}];
|
||||
self.readReceiptLabel.textColor = [self shouldHighlightReadReceiptLabel] ? TIMCommonDynamicColor(@"chat_message_read_status_text_color", @"#147AFF")
|
||||
: TIMCommonDynamicColor(@"chat_message_read_status_text_gray_color", @"#BBBBBB");
|
||||
}
|
||||
|
||||
- (UIView *)highlightAnimateView {
|
||||
return self.container;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMessageCellProtocol
|
||||
+ (CGFloat)getEstimatedHeight:(TUIMessageCellData *)data {
|
||||
return 60.f;
|
||||
}
|
||||
|
||||
+ (CGFloat)getHeight:(TUIMessageCellData *)data withWidth:(CGFloat)width {
|
||||
CGFloat height = 0;
|
||||
if (data.showName) height += kScale375(20);
|
||||
if (data.showMessageModifyReplies) height += kScale375(22);
|
||||
|
||||
if (data.messageContainerAppendSize.height > 0) {
|
||||
height += data.messageContainerAppendSize.height;
|
||||
}
|
||||
|
||||
CGSize containerSize = [self getContentSize:data];
|
||||
height += containerSize.height;
|
||||
height += data.cellLayout.messageInsets.top;
|
||||
height += data.cellLayout.messageInsets.bottom;
|
||||
|
||||
if (height < 55) height = 55;
|
||||
return height;
|
||||
}
|
||||
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
return CGSizeZero;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
- (void)animationDidStart:(CAAnimation *)anim {
|
||||
self.highlightAnimating = YES;
|
||||
}
|
||||
|
||||
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
|
||||
self.highlightAnimating = NO;
|
||||
}
|
||||
|
||||
#pragma mark-- Event
|
||||
- (void)onLongPress:(UIGestureRecognizer *)recognizer {
|
||||
if ([recognizer isKindOfClass:[UILongPressGestureRecognizer class]] && recognizer.state == UIGestureRecognizerStateBegan) {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(onLongPressMessage:)]) {
|
||||
[_delegate onLongPressMessage:self];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onRetryMessage:(UIGestureRecognizer *)recognizer {
|
||||
if (_messageData.status == Msg_Status_Fail)
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(onRetryMessage:)]) {
|
||||
[_delegate onRetryMessage:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onSelectMessage:(UIGestureRecognizer *)recognizer {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(onSelectMessage:)]) {
|
||||
[_delegate onSelectMessage:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onSelectMessageAvatar:(UIGestureRecognizer *)recognizer {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(onSelectMessageAvatar:)]) {
|
||||
[_delegate onSelectMessageAvatar:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onLongSelectMessageAvatar:(UIGestureRecognizer *)recognizer {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(onLongSelectMessageAvatar:)]) {
|
||||
[_delegate onLongSelectMessageAvatar:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onSelectReadReceipt:(UITapGestureRecognizer *)gesture {
|
||||
if (![self shouldHighlightReadReceiptLabel]) {
|
||||
return;
|
||||
}
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(onSelectReadReceipt:)]) {
|
||||
[_delegate onSelectReadReceipt:self.messageData];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onJumpToRepliesDetailPage:(UIButton *)btn {
|
||||
NSLog(@"click onJumpToRepliesDetailPage");
|
||||
NSLog(@"%@", self.messageData.messageModifyReplies);
|
||||
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(onJumpToRepliesDetailPage:)]) {
|
||||
[_delegate onJumpToRepliesDetailPage:self.messageData];
|
||||
}
|
||||
}
|
||||
- (BOOL)shouldHighlightReadReceiptLabel {
|
||||
if (self.messageData.innerMessage.groupID.length == 0) {
|
||||
return ![self.readReceiptLabel.text isEqualToString:TIMCommonLocalizableString(TUIKitMessageReadC2CRead)];
|
||||
} else {
|
||||
return ![self.readReceiptLabel.text isEqualToString:TIMCommonLocalizableString(TUIKitMessageReadAllRead)];
|
||||
}
|
||||
}
|
||||
|
||||
- (UIFont *)fontWithSize:(CGFloat)size {
|
||||
static NSCache *fontCache;
|
||||
if (fontCache == nil) {
|
||||
fontCache = [[NSCache alloc] init];
|
||||
}
|
||||
UIFont *font = [fontCache objectForKey:@(size)];
|
||||
if (font == nil) {
|
||||
font = [UIFont systemFontOfSize:size];
|
||||
[fontCache setObject:font forKey:@(size)];
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
- (void)notifyBottomContainerReadyOfData:(TUIMessageCellData *)cellData {
|
||||
// Override by subclass.
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation TUIMessageCell (TUILayoutConfiguration)
|
||||
|
||||
static UIColor *gOutgoingNameColor;
|
||||
|
||||
+ (UIColor *)outgoingNameColor {
|
||||
if (!gOutgoingNameColor) {
|
||||
gOutgoingNameColor = [UIColor d_systemGrayColor];
|
||||
}
|
||||
return gOutgoingNameColor;
|
||||
}
|
||||
|
||||
+ (void)setOutgoingNameColor:(UIColor *)outgoingNameColor {
|
||||
gOutgoingNameColor = outgoingNameColor;
|
||||
}
|
||||
|
||||
static UIFont *gOutgoingNameFont;
|
||||
|
||||
+ (UIFont *)outgoingNameFont {
|
||||
if (!gOutgoingNameFont) {
|
||||
gOutgoingNameFont = [UIFont systemFontOfSize:14];
|
||||
}
|
||||
return gOutgoingNameFont;
|
||||
}
|
||||
|
||||
+ (void)setOutgoingNameFont:(UIFont *)outgoingNameFont {
|
||||
gOutgoingNameFont = outgoingNameFont;
|
||||
}
|
||||
|
||||
static UIColor *gIncommingNameColor;
|
||||
|
||||
+ (UIColor *)incommingNameColor {
|
||||
if (!gIncommingNameColor) {
|
||||
gIncommingNameColor = [UIColor d_systemGrayColor];
|
||||
}
|
||||
return gIncommingNameColor;
|
||||
}
|
||||
|
||||
+ (void)setIncommingNameColor:(UIColor *)incommingNameColor {
|
||||
gIncommingNameColor = incommingNameColor;
|
||||
}
|
||||
|
||||
static UIFont *gIncommingNameFont;
|
||||
|
||||
+ (UIFont *)incommingNameFont {
|
||||
if (!gIncommingNameFont) {
|
||||
gIncommingNameFont = [UIFont systemFontOfSize:14];
|
||||
}
|
||||
return gIncommingNameFont;
|
||||
}
|
||||
|
||||
+ (void)setIncommingNameFont:(UIFont *)incommingNameFont {
|
||||
gIncommingNameFont = incommingNameFont;
|
||||
}
|
||||
|
||||
@end
|
||||
20
TUIKit/TIMCommon/BaseCell/TUISecurityStrikeView.h
Normal file
20
TUIKit/TIMCommon/BaseCell/TUISecurityStrikeView.h
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// TUISecurityStrikeView.h
|
||||
// TIMCommon
|
||||
//
|
||||
// Created by wyl on 2023/10/11.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
#define kTUISecurityStrikeViewTopLineMargin 14.5
|
||||
#define kTUISecurityStrikeViewTopLineToBottom 28
|
||||
@interface TUISecurityStrikeView : UIView
|
||||
@property(nonatomic, strong) UIView * topLine;
|
||||
@property(nonatomic, strong) UILabel * textLabel;
|
||||
|
||||
+ (UIImage *)changeImageColorWith:(UIColor *)color image:(UIImage *)image alpha:(CGFloat)alpha;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
73
TUIKit/TIMCommon/BaseCell/TUISecurityStrikeView.m
Normal file
73
TUIKit/TIMCommon/BaseCell/TUISecurityStrikeView.m
Normal file
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// TUISecurityStrikeView.m
|
||||
// TIMCommon
|
||||
//
|
||||
// Created by wyl on 2023/10/11.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
|
||||
#import "TUISecurityStrikeView.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
|
||||
@implementation TUISecurityStrikeView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self){
|
||||
[self setupView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)setupView {
|
||||
self.topLine = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
self.topLine.backgroundColor = TUIDynamicColor(@"", TUIThemeModuleTIMCommon, @"#E5C7C7");
|
||||
[self addSubview:self.topLine];
|
||||
|
||||
self.textLabel = [[UILabel alloc] initWithFrame:CGRectZero];
|
||||
[self addSubview:self.textLabel];
|
||||
self.textLabel.font = [UIFont systemFontOfSize:14];
|
||||
self.textLabel.text = TIMCommonLocalizableString(TUIKitMessageTypeSecurityStrike);
|
||||
self.textLabel.textColor = TUIDynamicColor(@"", TUIThemeModuleTIMCommon, @"#DA2222");
|
||||
self.textLabel.numberOfLines = 0;
|
||||
self.textLabel.textAlignment = isRTL()?NSTextAlignmentRight:NSTextAlignmentLeft;
|
||||
|
||||
}
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
[super updateConstraints];
|
||||
[self.topLine mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(kTUISecurityStrikeViewTopLineMargin);
|
||||
make.leading.mas_equalTo(10);
|
||||
make.trailing.mas_equalTo(-10);
|
||||
make.height.mas_equalTo(0.5);
|
||||
}];
|
||||
|
||||
[self.textLabel sizeToFit];
|
||||
[self.textLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(10);
|
||||
make.bottom.mas_equalTo(-11);
|
||||
make.width.mas_equalTo(self);
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
+ (UIImage *)changeImageColorWith:(UIColor *)color image:(UIImage *)image alpha:(CGFloat)alpha {
|
||||
UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextTranslateCTM(context, 0, image.size.height);
|
||||
CGContextScaleCTM(context, 1.0, -1.0);
|
||||
CGContextSetAlpha(context, alpha);
|
||||
CGContextSetBlendMode(context, kCGBlendModeNormal);
|
||||
CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);
|
||||
CGContextClipToMask(context, rect, image.CGImage);
|
||||
[color setFill];
|
||||
CGContextFillRect(context, rect);
|
||||
UIImage*newImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return newImage;
|
||||
}
|
||||
@end
|
||||
|
||||
30
TUIKit/TIMCommon/BaseCell/TUISystemMessageCell.h
Normal file
30
TUIKit/TIMCommon/BaseCell/TUISystemMessageCell.h
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
/**
|
||||
*
|
||||
* This file declares the TUISystemMessageCell class, which is responsible for displaying system messages.
|
||||
* The system message unit is responsible for displaying special messages from the system. Such messages are usually white on a gray background and centered.
|
||||
*/
|
||||
#import "TUIMessageCell.h"
|
||||
#import "TUISystemMessageCellData.h"
|
||||
|
||||
/**
|
||||
* 【Module name】 TUISystemMessageCell
|
||||
* 【Function description】System message unit
|
||||
* - It is used to display the system messages. Common system messages include: recall-message, group-member-change-message, group-created and
|
||||
* group-diss-message, etc.
|
||||
* - System messages are typically used to display notifications from apps that are sent by the system, not from any user.
|
||||
*/
|
||||
@interface TUISystemMessageCell : TUIMessageCell
|
||||
|
||||
/**
|
||||
*
|
||||
* The label of display system message content, such as "You recalled a message.".
|
||||
*/
|
||||
@property(readonly) UILabel *messageLabel;
|
||||
|
||||
@property(readonly) TUISystemMessageCellData *systemData;
|
||||
|
||||
- (void)fillWithData:(TUISystemMessageCellData *)data;
|
||||
@end
|
||||
107
TUIKit/TIMCommon/BaseCell/TUISystemMessageCell.m
Normal file
107
TUIKit/TIMCommon/BaseCell/TUISystemMessageCell.m
Normal file
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// TUISystemMessageCell.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by annidyfeng on 2019/5/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUISystemMessageCell.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/NSString+TUIUtil.h>
|
||||
|
||||
@interface TUISystemMessageCell ()
|
||||
@property(nonatomic, strong) UILabel *messageLabel;
|
||||
@property TUISystemMessageCellData *systemData;
|
||||
@end
|
||||
|
||||
@implementation TUISystemMessageCell
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
_messageLabel = [[UILabel alloc] init];
|
||||
_messageLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_messageLabel.numberOfLines = 0;
|
||||
_messageLabel.backgroundColor = [UIColor clearColor];
|
||||
_messageLabel.layer.cornerRadius = 3;
|
||||
[_messageLabel.layer setMasksToBounds:YES];
|
||||
[self.container addSubview:_messageLabel];
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
self.contentView.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
[super updateConstraints];
|
||||
|
||||
[self.container mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.center.mas_equalTo(self.contentView);
|
||||
make.size.mas_equalTo(self.contentView);
|
||||
}];
|
||||
[self.messageLabel sizeToFit];
|
||||
if(self.messageLabel.superview) {
|
||||
[self.messageLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.center.mas_equalTo(self.container);
|
||||
make.leading.trailing.mas_equalTo(self.container);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUISystemMessageCellData *)data;
|
||||
{
|
||||
[super fillWithData:data];
|
||||
self.systemData = data;
|
||||
|
||||
self.messageLabel.textColor = TUISystemMessageCellData.textColor ? : data.contentColor;
|
||||
self.messageLabel.font = TUISystemMessageCellData.textFont ? : data.contentFont;
|
||||
self.messageLabel.backgroundColor = TUISystemMessageCellData.textBackgroundColor ? : [UIColor clearColor];
|
||||
self.messageLabel.attributedText = data.attributedString;
|
||||
|
||||
self.nameLabel.hidden = YES;
|
||||
self.avatarView.hidden = YES;
|
||||
self.retryView.hidden = YES;
|
||||
[self.indicator stopAnimating];
|
||||
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - TUIMessageCellProtocol
|
||||
+ (CGFloat)getEstimatedHeight:(TUIMessageCellData *)data {
|
||||
return 42.f;
|
||||
}
|
||||
|
||||
+ (CGFloat)getHeight:(TUIMessageCellData *)data withWidth:(CGFloat)width {
|
||||
return [self getContentSize:data].height + kScale375(16);
|
||||
}
|
||||
|
||||
+ (CGSize)getContentSize:(TUIMessageCellData *)data {
|
||||
NSAssert([data isKindOfClass:TUISystemMessageCellData.class], @"data must be kind of TUISystemMessageCellData");
|
||||
TUISystemMessageCellData *systemCellData = (TUISystemMessageCellData *)data;
|
||||
|
||||
static CGSize maxSystemSize;
|
||||
if (CGSizeEqualToSize(maxSystemSize, CGSizeZero)) {
|
||||
maxSystemSize = CGSizeMake(Screen_Width, MAXFLOAT);
|
||||
}
|
||||
CGSize size = [systemCellData.attributedString.string textSizeIn:maxSystemSize font:systemCellData.contentFont];
|
||||
size.height += 10;
|
||||
size.width += 16;
|
||||
return size;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user