2026-01-15 18:16:56 +08:00
|
|
|
//
|
|
|
|
|
// KBChatMessageCell.m
|
|
|
|
|
// CustomKeyboard
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#import "KBChatMessageCell.h"
|
|
|
|
|
#import "KBChatMessage.h"
|
|
|
|
|
#import "Masonry.h"
|
|
|
|
|
|
|
|
|
|
@interface KBChatMessageCell ()
|
|
|
|
|
@property (nonatomic, strong) UIImageView *avatarView;
|
|
|
|
|
@property (nonatomic, strong) UILabel *nameLabel;
|
|
|
|
|
@property (nonatomic, strong) UIView *bubbleView;
|
|
|
|
|
@property (nonatomic, strong) UILabel *messageLabel;
|
|
|
|
|
@property (nonatomic, strong) UIImageView *audioIconView;
|
|
|
|
|
@property (nonatomic, strong) UILabel *audioLabel;
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation KBChatMessageCell
|
|
|
|
|
|
|
|
|
|
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
|
|
|
|
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
|
|
|
|
|
self.backgroundColor = [UIColor clearColor];
|
2026-01-15 18:49:31 +08:00
|
|
|
self.contentView.backgroundColor = [UIColor clearColor];
|
2026-01-15 18:16:56 +08:00
|
|
|
self.selectionStyle = UITableViewCellSelectionStyleNone;
|
|
|
|
|
|
|
|
|
|
[self.contentView addSubview:self.avatarView];
|
|
|
|
|
[self.contentView addSubview:self.nameLabel];
|
|
|
|
|
[self.contentView addSubview:self.bubbleView];
|
|
|
|
|
[self.bubbleView addSubview:self.messageLabel];
|
|
|
|
|
[self.bubbleView addSubview:self.audioIconView];
|
|
|
|
|
[self.bubbleView addSubview:self.audioLabel];
|
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)kb_configureWithMessage:(KBChatMessage *)message {
|
|
|
|
|
BOOL outgoing = message.outgoing;
|
|
|
|
|
BOOL audioMessage = (!outgoing && message.audioFilePath.length > 0);
|
|
|
|
|
UIColor *bubbleColor = outgoing ? [UIColor colorWithHex:0x02BEAC] : [UIColor colorWithWhite:1 alpha:0.95];
|
2026-01-15 19:14:34 +08:00
|
|
|
UIColor *incomingTextColor =
|
|
|
|
|
[UIColor kb_dynamicColorWithLightColor:[UIColor colorWithHex:0x1B1F1A]
|
|
|
|
|
darkColor:[UIColor whiteColor]];
|
|
|
|
|
UIColor *textColor = outgoing ? [UIColor whiteColor] : incomingTextColor;
|
|
|
|
|
UIColor *nameColor =
|
|
|
|
|
[UIColor kb_dynamicColorWithLightColor:[UIColor colorWithHex:0x6B6F7A]
|
|
|
|
|
darkColor:[UIColor colorWithHex:0xC7CBD4]];
|
2026-01-15 18:16:56 +08:00
|
|
|
|
|
|
|
|
self.bubbleView.backgroundColor = bubbleColor;
|
|
|
|
|
self.messageLabel.textColor = textColor;
|
|
|
|
|
self.audioLabel.textColor = textColor;
|
|
|
|
|
self.audioIconView.tintColor = textColor;
|
|
|
|
|
self.messageLabel.text = message.text ?: @"";
|
|
|
|
|
self.audioLabel.text =
|
|
|
|
|
(message.text.length > 0) ? message.text : KBLocalized(@"语音回复");
|
|
|
|
|
self.messageLabel.hidden = audioMessage;
|
|
|
|
|
self.audioIconView.hidden = !audioMessage;
|
|
|
|
|
self.audioLabel.hidden = !audioMessage;
|
|
|
|
|
|
|
|
|
|
UIImage *avatarImage = message.avatarImage;
|
|
|
|
|
if (!avatarImage) {
|
|
|
|
|
avatarImage = [self kb_defaultAvatarImage];
|
|
|
|
|
}
|
|
|
|
|
self.avatarView.image = avatarImage;
|
|
|
|
|
self.avatarView.backgroundColor =
|
|
|
|
|
avatarImage ? [UIColor clearColor] : [UIColor colorWithWhite:0.9 alpha:1.0];
|
|
|
|
|
self.nameLabel.hidden = outgoing;
|
2026-01-15 19:14:34 +08:00
|
|
|
self.nameLabel.textColor = nameColor;
|
2026-01-15 18:16:56 +08:00
|
|
|
self.nameLabel.text =
|
|
|
|
|
(message.displayName.length > 0) ? message.displayName : KBLocalized(@"AI助手");
|
|
|
|
|
|
|
|
|
|
CGFloat avatarSize = 28.0;
|
|
|
|
|
[self.avatarView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
make.width.height.mas_equalTo(avatarSize);
|
|
|
|
|
make.top.equalTo(self.contentView.mas_top).offset(6);
|
|
|
|
|
if (outgoing) {
|
|
|
|
|
make.right.equalTo(self.contentView.mas_right).offset(-8);
|
|
|
|
|
} else {
|
|
|
|
|
make.left.equalTo(self.contentView.mas_left).offset(8);
|
|
|
|
|
}
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
if (outgoing) {
|
|
|
|
|
[self.nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
make.top.equalTo(self.contentView.mas_top).offset(0);
|
|
|
|
|
make.left.equalTo(self.contentView.mas_left);
|
|
|
|
|
}];
|
|
|
|
|
} else {
|
|
|
|
|
[self.nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
make.left.equalTo(self.avatarView.mas_right).offset(6);
|
|
|
|
|
make.top.equalTo(self.contentView.mas_top).offset(2);
|
|
|
|
|
make.right.lessThanOrEqualTo(self.contentView.mas_right).offset(-12);
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[self.bubbleView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
make.width.lessThanOrEqualTo(self.contentView.mas_width).multipliedBy(0.65);
|
|
|
|
|
if (outgoing) {
|
|
|
|
|
make.top.equalTo(self.contentView.mas_top).offset(6);
|
|
|
|
|
make.bottom.equalTo(self.contentView.mas_bottom).offset(-6);
|
|
|
|
|
make.right.equalTo(self.avatarView.mas_left).offset(-6);
|
|
|
|
|
} else {
|
|
|
|
|
make.top.equalTo(self.nameLabel.mas_bottom).offset(2);
|
|
|
|
|
make.bottom.equalTo(self.contentView.mas_bottom).offset(-6);
|
|
|
|
|
make.left.equalTo(self.avatarView.mas_right).offset(6);
|
|
|
|
|
make.right.lessThanOrEqualTo(self.contentView.mas_right).offset(-12);
|
|
|
|
|
}
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
if (audioMessage) {
|
2026-01-15 20:30:03 +08:00
|
|
|
[self.messageLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
make.width.height.mas_equalTo(0);
|
|
|
|
|
make.left.equalTo(self.bubbleView.mas_left);
|
|
|
|
|
make.top.equalTo(self.bubbleView.mas_top);
|
|
|
|
|
}];
|
2026-01-15 18:16:56 +08:00
|
|
|
[self.audioIconView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
make.left.equalTo(self.bubbleView.mas_left).offset(10);
|
|
|
|
|
make.centerY.equalTo(self.bubbleView);
|
|
|
|
|
make.width.height.mas_equalTo(16);
|
|
|
|
|
}];
|
|
|
|
|
[self.audioLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
make.left.equalTo(self.audioIconView.mas_right).offset(6);
|
|
|
|
|
make.centerY.equalTo(self.bubbleView);
|
|
|
|
|
make.right.equalTo(self.bubbleView.mas_right).offset(-10);
|
|
|
|
|
make.top.greaterThanOrEqualTo(self.bubbleView.mas_top).offset(8);
|
|
|
|
|
make.bottom.lessThanOrEqualTo(self.bubbleView.mas_bottom).offset(-8);
|
|
|
|
|
}];
|
|
|
|
|
} else {
|
2026-01-15 20:30:03 +08:00
|
|
|
[self.audioIconView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
make.width.height.mas_equalTo(0);
|
|
|
|
|
make.left.equalTo(self.bubbleView.mas_left);
|
|
|
|
|
make.top.equalTo(self.bubbleView.mas_top);
|
|
|
|
|
}];
|
|
|
|
|
[self.audioLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
make.width.height.mas_equalTo(0);
|
|
|
|
|
make.left.equalTo(self.audioIconView.mas_right);
|
|
|
|
|
make.top.equalTo(self.bubbleView.mas_top);
|
|
|
|
|
}];
|
2026-01-15 18:16:56 +08:00
|
|
|
[self.messageLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
make.edges.equalTo(self.bubbleView).insets(UIEdgeInsetsMake(8, 10, 8, 10));
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark - Lazy
|
|
|
|
|
|
|
|
|
|
- (UIImageView *)avatarView {
|
|
|
|
|
if (!_avatarView) {
|
|
|
|
|
_avatarView = [[UIImageView alloc] init];
|
|
|
|
|
_avatarView.contentMode = UIViewContentModeScaleAspectFill;
|
|
|
|
|
_avatarView.layer.cornerRadius = 14;
|
|
|
|
|
_avatarView.layer.masksToBounds = YES;
|
|
|
|
|
_avatarView.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0];
|
2026-01-15 19:14:34 +08:00
|
|
|
_avatarView.tintColor =
|
|
|
|
|
[UIColor kb_dynamicColorWithLightColor:[UIColor colorWithHex:0xB9BDC8]
|
|
|
|
|
darkColor:[UIColor colorWithHex:0x6B6F7A]];
|
2026-01-15 18:16:56 +08:00
|
|
|
}
|
|
|
|
|
return _avatarView;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (UILabel *)nameLabel {
|
|
|
|
|
if (!_nameLabel) {
|
|
|
|
|
_nameLabel = [[UILabel alloc] init];
|
|
|
|
|
_nameLabel.font = [UIFont systemFontOfSize:11];
|
|
|
|
|
_nameLabel.textColor = [UIColor colorWithHex:0x6B6F7A];
|
|
|
|
|
_nameLabel.numberOfLines = 1;
|
|
|
|
|
}
|
|
|
|
|
return _nameLabel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (UIView *)bubbleView {
|
|
|
|
|
if (!_bubbleView) {
|
|
|
|
|
_bubbleView = [[UIView alloc] init];
|
|
|
|
|
_bubbleView.layer.cornerRadius = 12;
|
|
|
|
|
_bubbleView.layer.masksToBounds = YES;
|
|
|
|
|
}
|
|
|
|
|
return _bubbleView;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (UILabel *)messageLabel {
|
|
|
|
|
if (!_messageLabel) {
|
|
|
|
|
_messageLabel = [[UILabel alloc] init];
|
|
|
|
|
_messageLabel.font = [UIFont systemFontOfSize:14];
|
|
|
|
|
_messageLabel.numberOfLines = 0;
|
|
|
|
|
}
|
|
|
|
|
return _messageLabel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (UIImageView *)audioIconView {
|
|
|
|
|
if (!_audioIconView) {
|
|
|
|
|
_audioIconView = [[UIImageView alloc] init];
|
|
|
|
|
_audioIconView.contentMode = UIViewContentModeScaleAspectFit;
|
|
|
|
|
_audioIconView.tintColor = [UIColor colorWithHex:0x1B1F1A];
|
|
|
|
|
UIImage *icon = nil;
|
|
|
|
|
if (@available(iOS 13.0, *)) {
|
|
|
|
|
icon = [UIImage systemImageNamed:@"waveform"];
|
|
|
|
|
}
|
|
|
|
|
_audioIconView.image = icon;
|
|
|
|
|
}
|
|
|
|
|
return _audioIconView;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (UILabel *)audioLabel {
|
|
|
|
|
if (!_audioLabel) {
|
|
|
|
|
_audioLabel = [[UILabel alloc] init];
|
|
|
|
|
_audioLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
|
|
|
|
|
_audioLabel.numberOfLines = 1;
|
|
|
|
|
}
|
|
|
|
|
return _audioLabel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (UIImage *)kb_defaultAvatarImage {
|
|
|
|
|
if (@available(iOS 13.0, *)) {
|
|
|
|
|
return [UIImage systemImageNamed:@"person.circle.fill"];
|
|
|
|
|
}
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|