分类移动文件
This commit is contained in:
25
keyBoard/Class/AiTalk/V/Comment/KBAICommentFooterView.h
Normal file
25
keyBoard/Class/AiTalk/V/Comment/KBAICommentFooterView.h
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// KBAICommentFooterView.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/16.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class KBAICommentModel;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Section Footer - 展开/收起/加载更多按钮
|
||||
@interface KBAICommentFooterView : UITableViewHeaderFooterView
|
||||
|
||||
/// 配置 Footer 数据
|
||||
- (void)configureWithComment:(KBAICommentModel *)comment;
|
||||
|
||||
/// 操作按钮点击回调
|
||||
@property(nonatomic, copy, nullable) void (^onAction)(void);
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
131
keyBoard/Class/AiTalk/V/Comment/KBAICommentFooterView.m
Normal file
131
keyBoard/Class/AiTalk/V/Comment/KBAICommentFooterView.m
Normal file
@@ -0,0 +1,131 @@
|
||||
//
|
||||
// KBAICommentFooterView.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/16.
|
||||
//
|
||||
|
||||
#import "KBAICommentFooterView.h"
|
||||
#import "KBAICommentModel.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
|
||||
@interface KBAICommentFooterView ()
|
||||
|
||||
@property(nonatomic, strong) UIButton *actionButton;
|
||||
@property(nonatomic, strong) UIView *lineView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBAICommentFooterView
|
||||
|
||||
- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithReuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
[self setupUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - UI Setup
|
||||
|
||||
- (void)setupUI {
|
||||
self.contentView.backgroundColor = [UIColor clearColor];
|
||||
|
||||
[self.contentView addSubview:self.actionButton];
|
||||
[self.contentView addSubview:self.lineView];
|
||||
|
||||
[self.actionButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.contentView).offset(68); // 对齐二级评论
|
||||
make.centerY.equalTo(self.contentView);
|
||||
make.height.mas_equalTo(24);
|
||||
}];
|
||||
|
||||
// [self.lineView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
// make.left.equalTo(self.contentView).offset(16);
|
||||
// make.right.equalTo(self.contentView).offset(-16);
|
||||
// make.bottom.equalTo(self.contentView);
|
||||
// make.height.mas_equalTo(0.5);
|
||||
// }];
|
||||
}
|
||||
|
||||
#pragma mark - Configuration
|
||||
|
||||
- (void)configureWithComment:(KBAICommentModel *)comment {
|
||||
KBAIReplyFooterState state = [comment footerState];
|
||||
NSString *title = @"";
|
||||
|
||||
switch (state) {
|
||||
case KBAIReplyFooterStateHidden: {
|
||||
self.actionButton.hidden = YES;
|
||||
break;
|
||||
}
|
||||
case KBAIReplyFooterStateExpand: {
|
||||
self.actionButton.hidden = NO;
|
||||
title = [NSString
|
||||
stringWithFormat:@"展开%ld条回复", (long)comment.totalReplyCount];
|
||||
[self.actionButton setImage:[UIImage systemImageNamed:@"chevron.down"]
|
||||
forState:UIControlStateNormal];
|
||||
break;
|
||||
}
|
||||
case KBAIReplyFooterStateLoadMore: {
|
||||
self.actionButton.hidden = NO;
|
||||
NSInteger remaining =
|
||||
comment.totalReplyCount - comment.displayedReplies.count;
|
||||
title =
|
||||
[NSString stringWithFormat:@"展开更多回复(%ld条)", (long)remaining];
|
||||
[self.actionButton setImage:[UIImage systemImageNamed:@"chevron.down"]
|
||||
forState:UIControlStateNormal];
|
||||
break;
|
||||
}
|
||||
case KBAIReplyFooterStateCollapse: {
|
||||
self.actionButton.hidden = NO;
|
||||
title = @"收起";
|
||||
[self.actionButton setImage:[UIImage systemImageNamed:@"chevron.up"]
|
||||
forState:UIControlStateNormal];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[self.actionButton setTitle:title forState:UIControlStateNormal];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)actionButtonTapped {
|
||||
if (self.onAction) {
|
||||
self.onAction();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Lazy Loading
|
||||
|
||||
- (UIButton *)actionButton {
|
||||
if (!_actionButton) {
|
||||
_actionButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_actionButton.titleLabel.font = [UIFont systemFontOfSize:13];
|
||||
[_actionButton setTitleColor:[UIColor secondaryLabelColor]
|
||||
forState:UIControlStateNormal];
|
||||
_actionButton.tintColor = [UIColor secondaryLabelColor];
|
||||
|
||||
// 文字在左,图标在右
|
||||
_actionButton.semanticContentAttribute =
|
||||
UISemanticContentAttributeForceLeftToRight;
|
||||
_actionButton.imageEdgeInsets = UIEdgeInsetsMake(0, -2, 0, 2);
|
||||
_actionButton.titleEdgeInsets = UIEdgeInsetsMake(0, 2, 0, -2);
|
||||
|
||||
[_actionButton addTarget:self
|
||||
action:@selector(actionButtonTapped)
|
||||
forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _actionButton;
|
||||
}
|
||||
|
||||
- (UIView *)lineView {
|
||||
if (!_lineView) {
|
||||
_lineView = [[UIView alloc] init];
|
||||
_lineView.backgroundColor = [UIColor separatorColor];
|
||||
}
|
||||
return _lineView;
|
||||
}
|
||||
|
||||
@end
|
||||
28
keyBoard/Class/AiTalk/V/Comment/KBAICommentHeaderView.h
Normal file
28
keyBoard/Class/AiTalk/V/Comment/KBAICommentHeaderView.h
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// KBAICommentHeaderView.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/16.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class KBAICommentModel;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 一级评论 Section Header 视图
|
||||
@interface KBAICommentHeaderView : UITableViewHeaderFooterView
|
||||
|
||||
/// 配置评论数据
|
||||
- (void)configureWithComment:(KBAICommentModel *)comment;
|
||||
|
||||
/// 点赞按钮点击回调
|
||||
@property(nonatomic, copy, nullable) void (^onLikeAction)(void);
|
||||
|
||||
/// 回复按钮点击回调
|
||||
@property(nonatomic, copy, nullable) void (^onReplyAction)(void);
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
201
keyBoard/Class/AiTalk/V/Comment/KBAICommentHeaderView.m
Normal file
201
keyBoard/Class/AiTalk/V/Comment/KBAICommentHeaderView.m
Normal file
@@ -0,0 +1,201 @@
|
||||
//
|
||||
// KBAICommentHeaderView.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/16.
|
||||
//
|
||||
|
||||
#import "KBAICommentHeaderView.h"
|
||||
#import "KBAICommentModel.h"
|
||||
#import "KBTopImageButton.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <SDWebImage/SDWebImage.h>
|
||||
|
||||
@interface KBAICommentHeaderView ()
|
||||
|
||||
@property(nonatomic, strong) UIImageView *avatarImageView;
|
||||
@property(nonatomic, strong) UILabel *userNameLabel;
|
||||
@property(nonatomic, strong) UILabel *contentLabel;
|
||||
@property(nonatomic, strong) UILabel *timeLabel;
|
||||
@property(nonatomic, strong) UIButton *replyButton;
|
||||
@property(nonatomic, strong) KBTopImageButton *likeButton;
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBAICommentHeaderView
|
||||
|
||||
- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithReuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
[self setupUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - UI Setup
|
||||
|
||||
- (void)setupUI {
|
||||
self.contentView.backgroundColor = [UIColor clearColor];
|
||||
|
||||
[self.contentView addSubview:self.avatarImageView];
|
||||
[self.contentView addSubview:self.userNameLabel];
|
||||
[self.contentView addSubview:self.contentLabel];
|
||||
[self.contentView addSubview:self.timeLabel];
|
||||
[self.contentView addSubview:self.replyButton];
|
||||
[self.contentView addSubview:self.likeButton];
|
||||
|
||||
[self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.contentView).offset(16);
|
||||
make.top.equalTo(self.contentView).offset(12);
|
||||
make.width.height.mas_equalTo(28);
|
||||
}];
|
||||
|
||||
[self.userNameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.avatarImageView.mas_right).offset(12);
|
||||
make.top.equalTo(self.avatarImageView);
|
||||
make.right.lessThanOrEqualTo(self.likeButton.mas_left).offset(-10);
|
||||
}];
|
||||
|
||||
|
||||
|
||||
[self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.userNameLabel);
|
||||
make.top.equalTo(self.contentLabel.mas_bottom).offset(8);
|
||||
make.bottom.equalTo(self.contentView).offset(-12).priority(MASLayoutPriorityDefaultHigh);
|
||||
}];
|
||||
|
||||
[self.replyButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.timeLabel.mas_right).offset(16);
|
||||
make.centerY.equalTo(self.timeLabel);
|
||||
}];
|
||||
|
||||
[self.likeButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self.contentView).offset(-16);
|
||||
make.top.equalTo(self.contentView).offset(12);
|
||||
make.width.mas_equalTo(30);
|
||||
make.height.mas_equalTo(40);
|
||||
}];
|
||||
|
||||
[self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.userNameLabel);
|
||||
make.top.equalTo(self.userNameLabel.mas_bottom).offset(6);
|
||||
make.right.equalTo(self.likeButton.mas_left).offset(-5);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Configuration
|
||||
|
||||
- (void)configureWithComment:(KBAICommentModel *)comment {
|
||||
[self.avatarImageView
|
||||
sd_setImageWithURL:[NSURL URLWithString:comment.avatarUrl]
|
||||
placeholderImage:[UIImage imageNamed:@"default_avatar"]];
|
||||
self.userNameLabel.text = comment.userName;
|
||||
self.contentLabel.text = comment.content;
|
||||
self.timeLabel.text = [comment formattedTime];
|
||||
|
||||
// 点赞按钮
|
||||
NSString *likeText =
|
||||
comment.likeCount > 0 ? [self formatLikeCount:comment.likeCount] : @"赞";
|
||||
self.likeButton.textLabel.text = likeText;
|
||||
|
||||
UIImage *likeImage = comment.liked
|
||||
? [UIImage imageNamed:@"comment_sel_icon"]
|
||||
: [UIImage imageNamed:@"comment_nor_icon"];
|
||||
self.likeButton.iconView.image = likeImage;
|
||||
|
||||
}
|
||||
|
||||
- (NSString *)formatLikeCount:(NSInteger)count {
|
||||
if (count >= 10000) {
|
||||
return [NSString stringWithFormat:@"%.1fw", count / 10000.0];
|
||||
} else if (count >= 1000) {
|
||||
return [NSString stringWithFormat:@"%.1fk", count / 1000.0];
|
||||
}
|
||||
return [NSString stringWithFormat:@"%ld", (long)count];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)likeButtonTapped {
|
||||
if (self.onLikeAction) {
|
||||
self.onLikeAction();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)replyButtonTapped {
|
||||
if (self.onReplyAction) {
|
||||
self.onReplyAction();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Lazy Loading
|
||||
|
||||
- (UIImageView *)avatarImageView {
|
||||
if (!_avatarImageView) {
|
||||
_avatarImageView = [[UIImageView alloc] init];
|
||||
_avatarImageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
_avatarImageView.layer.cornerRadius = 14;
|
||||
_avatarImageView.layer.masksToBounds = YES;
|
||||
_avatarImageView.backgroundColor = [UIColor systemGray5Color];
|
||||
}
|
||||
return _avatarImageView;
|
||||
}
|
||||
|
||||
- (UILabel *)userNameLabel {
|
||||
if (!_userNameLabel) {
|
||||
_userNameLabel = [[UILabel alloc] init];
|
||||
_userNameLabel.text = @"--";
|
||||
_userNameLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightMedium];
|
||||
_userNameLabel.textColor = [UIColor colorWithHex:0x9F9F9F];
|
||||
}
|
||||
return _userNameLabel;
|
||||
}
|
||||
|
||||
- (UILabel *)contentLabel {
|
||||
if (!_contentLabel) {
|
||||
_contentLabel = [[UILabel alloc] init];
|
||||
_contentLabel.font = [UIFont systemFontOfSize:12];
|
||||
_contentLabel.textColor = [UIColor whiteColor];
|
||||
_contentLabel.numberOfLines = 0;
|
||||
}
|
||||
return _contentLabel;
|
||||
}
|
||||
|
||||
- (UILabel *)timeLabel {
|
||||
if (!_timeLabel) {
|
||||
_timeLabel = [[UILabel alloc] init];
|
||||
_timeLabel.font = [UIFont systemFontOfSize:12];
|
||||
_timeLabel.textColor = [UIColor colorWithHex:0x9F9F9F];
|
||||
}
|
||||
return _timeLabel;
|
||||
}
|
||||
|
||||
- (UIButton *)replyButton {
|
||||
if (!_replyButton) {
|
||||
_replyButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_replyButton.titleLabel.font = [UIFont systemFontOfSize:12];
|
||||
[_replyButton setTitle:@"回复" forState:UIControlStateNormal];
|
||||
[_replyButton setTitleColor:[UIColor colorWithHex:0x9F9F9F] forState:UIControlStateNormal];
|
||||
[_replyButton addTarget:self
|
||||
action:@selector(replyButtonTapped)
|
||||
forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _replyButton;
|
||||
}
|
||||
|
||||
- (KBTopImageButton *)likeButton {
|
||||
if (!_likeButton) {
|
||||
_likeButton = [[KBTopImageButton alloc] init];
|
||||
_likeButton.iconSize = CGSizeMake(20, 20);
|
||||
_likeButton.spacing = 2;
|
||||
_likeButton.iconView.image = [UIImage imageNamed:@"comment_nor_icon"];
|
||||
_likeButton.textLabel.font = [UIFont systemFontOfSize:10];
|
||||
_likeButton.textLabel.textColor = [UIColor colorWithHex:0xC5BEB4];
|
||||
[_likeButton addTarget:self
|
||||
action:@selector(likeButtonTapped)
|
||||
forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _likeButton;
|
||||
}
|
||||
|
||||
@end
|
||||
29
keyBoard/Class/AiTalk/V/Comment/KBAICommentInputView.h
Normal file
29
keyBoard/Class/AiTalk/V/Comment/KBAICommentInputView.h
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// KBAICommentInputView.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/16.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 底部评论输入框
|
||||
@interface KBAICommentInputView : UIView
|
||||
|
||||
/// 发送回调,参数为输入的文本
|
||||
@property(nonatomic, copy, nullable) void (^onSend)(NSString *text);
|
||||
|
||||
/// 设置占位文字
|
||||
@property(nonatomic, copy) NSString *placeholder;
|
||||
|
||||
/// 清空输入框
|
||||
- (void)clearText;
|
||||
|
||||
/// 弹起键盘
|
||||
- (void)showKeyboard;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
180
keyBoard/Class/AiTalk/V/Comment/KBAICommentInputView.m
Normal file
180
keyBoard/Class/AiTalk/V/Comment/KBAICommentInputView.m
Normal file
@@ -0,0 +1,180 @@
|
||||
//
|
||||
// KBAICommentInputView.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/16.
|
||||
//
|
||||
|
||||
#import "KBAICommentInputView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
|
||||
@interface KBAICommentInputView () <UITextFieldDelegate>
|
||||
|
||||
@property(nonatomic, strong) UIView *containerView;
|
||||
@property(nonatomic, strong) UIImageView *avatarImageView;
|
||||
@property(nonatomic, strong) UITextField *textField;
|
||||
@property(nonatomic, strong) UIButton *sendButton;
|
||||
@property(nonatomic, strong) UIView *topLine;
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBAICommentInputView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setupUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - UI Setup
|
||||
|
||||
- (void)setupUI {
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
[self addSubview:self.topLine];
|
||||
[self addSubview:self.avatarImageView];
|
||||
[self addSubview:self.containerView];
|
||||
[self.containerView addSubview:self.textField];
|
||||
[self addSubview:self.sendButton];
|
||||
|
||||
[self.topLine mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.top.equalTo(self);
|
||||
make.height.mas_equalTo(0.5);
|
||||
}];
|
||||
|
||||
[self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self).offset(12);
|
||||
make.centerY.equalTo(self);
|
||||
make.width.height.mas_equalTo(32);
|
||||
}];
|
||||
|
||||
[self.containerView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.avatarImageView.mas_right).offset(10);
|
||||
make.right.equalTo(self.sendButton.mas_left).offset(-10);
|
||||
make.centerY.equalTo(self);
|
||||
make.height.mas_equalTo(36);
|
||||
}];
|
||||
|
||||
[self.textField mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.containerView).offset(12);
|
||||
make.right.equalTo(self.containerView).offset(-12);
|
||||
make.centerY.equalTo(self.containerView);
|
||||
}];
|
||||
|
||||
[self.sendButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self).offset(-12);
|
||||
make.centerY.equalTo(self);
|
||||
make.width.mas_equalTo(50);
|
||||
make.height.mas_equalTo(30);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Public Methods
|
||||
|
||||
- (void)setPlaceholder:(NSString *)placeholder {
|
||||
_placeholder = placeholder;
|
||||
self.textField.placeholder = placeholder;
|
||||
}
|
||||
|
||||
- (void)clearText {
|
||||
self.textField.text = @"";
|
||||
[self updateSendButtonState];
|
||||
}
|
||||
|
||||
- (void)showKeyboard {
|
||||
[self.textField becomeFirstResponder];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)sendButtonTapped {
|
||||
NSString *text = self.textField.text;
|
||||
if (text.length > 0 && self.onSend) {
|
||||
self.onSend(text);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)textFieldDidChange:(UITextField *)textField {
|
||||
[self updateSendButtonState];
|
||||
}
|
||||
|
||||
- (void)updateSendButtonState {
|
||||
BOOL hasText = self.textField.text.length > 0;
|
||||
self.sendButton.enabled = hasText;
|
||||
self.sendButton.alpha = hasText ? 1.0 : 0.5;
|
||||
}
|
||||
|
||||
#pragma mark - UITextFieldDelegate
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
||||
[self sendButtonTapped];
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Lazy Loading
|
||||
|
||||
- (UIView *)topLine {
|
||||
if (!_topLine) {
|
||||
_topLine = [[UIView alloc] init];
|
||||
_topLine.backgroundColor = [UIColor separatorColor];
|
||||
}
|
||||
return _topLine;
|
||||
}
|
||||
|
||||
- (UIImageView *)avatarImageView {
|
||||
if (!_avatarImageView) {
|
||||
_avatarImageView = [[UIImageView alloc] init];
|
||||
_avatarImageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
_avatarImageView.layer.cornerRadius = 16;
|
||||
_avatarImageView.layer.masksToBounds = YES;
|
||||
_avatarImageView.backgroundColor = [UIColor systemGray5Color];
|
||||
_avatarImageView.image = [UIImage systemImageNamed:@"person.circle.fill"];
|
||||
_avatarImageView.tintColor = [UIColor systemGray3Color];
|
||||
}
|
||||
return _avatarImageView;
|
||||
}
|
||||
|
||||
- (UIView *)containerView {
|
||||
if (!_containerView) {
|
||||
_containerView = [[UIView alloc] init];
|
||||
_containerView.backgroundColor = [UIColor systemGray6Color];
|
||||
_containerView.layer.cornerRadius = 18;
|
||||
_containerView.layer.masksToBounds = YES;
|
||||
}
|
||||
return _containerView;
|
||||
}
|
||||
|
||||
- (UITextField *)textField {
|
||||
if (!_textField) {
|
||||
_textField = [[UITextField alloc] init];
|
||||
_textField.placeholder = @"说点什么...";
|
||||
_textField.font = [UIFont systemFontOfSize:14];
|
||||
_textField.delegate = self;
|
||||
_textField.returnKeyType = UIReturnKeySend;
|
||||
[_textField addTarget:self
|
||||
action:@selector(textFieldDidChange:)
|
||||
forControlEvents:UIControlEventEditingChanged];
|
||||
}
|
||||
return _textField;
|
||||
}
|
||||
|
||||
- (UIButton *)sendButton {
|
||||
if (!_sendButton) {
|
||||
_sendButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_sendButton setTitle:@"发送" forState:UIControlStateNormal];
|
||||
[_sendButton setTitleColor:[UIColor systemBlueColor]
|
||||
forState:UIControlStateNormal];
|
||||
_sendButton.titleLabel.font = [UIFont systemFontOfSize:15
|
||||
weight:UIFontWeightMedium];
|
||||
_sendButton.enabled = NO;
|
||||
_sendButton.alpha = 0.5;
|
||||
[_sendButton addTarget:self
|
||||
action:@selector(sendButtonTapped)
|
||||
forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _sendButton;
|
||||
}
|
||||
|
||||
@end
|
||||
29
keyBoard/Class/AiTalk/V/Comment/KBAICommentView.h
Normal file
29
keyBoard/Class/AiTalk/V/Comment/KBAICommentView.h
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// KBAICommentView.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/16.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <LSTPopView/LSTPopView.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 抖音风格评论视图
|
||||
@interface KBAICommentView : UIView
|
||||
|
||||
/// AI 陪聊角色 ID
|
||||
@property(nonatomic, assign) NSInteger companionId;
|
||||
|
||||
/// 加载评论数据(从网络)
|
||||
- (void)loadComments;
|
||||
|
||||
/// 评论总数
|
||||
@property(nonatomic, readonly) NSInteger totalCommentCount;
|
||||
@property(nonatomic, weak) LSTPopView *popView
|
||||
;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
907
keyBoard/Class/AiTalk/V/Comment/KBAICommentView.m
Normal file
907
keyBoard/Class/AiTalk/V/Comment/KBAICommentView.m
Normal file
@@ -0,0 +1,907 @@
|
||||
//
|
||||
// KBAICommentView.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/16.
|
||||
//
|
||||
|
||||
#import "KBAICommentView.h"
|
||||
#import "KBAICommentFooterView.h"
|
||||
#import "KBAICommentHeaderView.h"
|
||||
#import "KBAICommentInputView.h"
|
||||
#import "KBAICommentModel.h"
|
||||
#import "KBAIReplyCell.h"
|
||||
#import "KBAIReplyModel.h"
|
||||
#import "KBCommentModel.h"
|
||||
#import "AiVM.h"
|
||||
#import <MJExtension/MJExtension.h>
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <MJRefresh/MJRefresh.h>
|
||||
|
||||
static NSString *const kCommentHeaderIdentifier = @"CommentHeader";
|
||||
static NSString *const kReplyCellIdentifier = @"ReplyCell";
|
||||
static NSString *const kCommentFooterIdentifier = @"CommentFooter";
|
||||
|
||||
@interface KBAICommentView () <UITableViewDataSource, UITableViewDelegate>
|
||||
|
||||
@property(nonatomic, strong) UIVisualEffectView *blurBackgroundView;
|
||||
@property(nonatomic, strong) UIView *headerView;
|
||||
@property(nonatomic, strong) UILabel *titleLabel;
|
||||
@property(nonatomic, strong) UIButton *closeButton;
|
||||
@property(nonatomic, strong) BaseTableView *tableView;
|
||||
@property(nonatomic, strong) KBAICommentInputView *inputView;
|
||||
|
||||
@property(nonatomic, strong) NSMutableArray<KBAICommentModel *> *comments;
|
||||
@property(nonatomic, assign) NSInteger totalCommentCount;
|
||||
|
||||
/// 分页参数
|
||||
@property(nonatomic, assign) NSInteger currentPage;
|
||||
@property(nonatomic, assign) NSInteger pageSize;
|
||||
@property(nonatomic, assign) BOOL isLoading;
|
||||
@property(nonatomic, assign) BOOL hasMoreData;
|
||||
|
||||
/// 键盘高度
|
||||
@property(nonatomic, assign) CGFloat keyboardHeight;
|
||||
/// 输入框底部约束
|
||||
@property(nonatomic, strong) MASConstraint *inputBottomConstraint;
|
||||
|
||||
/// 当前回复的目标(一级评论)
|
||||
@property(nonatomic, weak) KBAICommentModel *replyToComment;
|
||||
/// 当前回复的目标(二级评论)
|
||||
@property(nonatomic, weak) KBAIReplyModel *replyToReply;
|
||||
|
||||
/// AiVM 实例
|
||||
@property(nonatomic, strong) AiVM *aiVM;
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBAICommentView
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.comments = [NSMutableArray array];
|
||||
self.currentPage = 1;
|
||||
self.pageSize = 20;
|
||||
self.hasMoreData = YES;
|
||||
[self setupUI];
|
||||
[self setupKeyboardObservers];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
#pragma mark - UI Setup
|
||||
|
||||
- (void)setupUI {
|
||||
// 设置背景为透明,让模糊效果可见
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
self.layer.cornerRadius = 12;
|
||||
self.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner;
|
||||
self.clipsToBounds = YES;
|
||||
|
||||
// 添加模糊背景(最底层)
|
||||
[self addSubview:self.blurBackgroundView];
|
||||
[self.blurBackgroundView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
|
||||
[self addSubview:self.headerView];
|
||||
[self.headerView addSubview:self.titleLabel];
|
||||
[self.headerView addSubview:self.closeButton];
|
||||
[self addSubview:self.tableView];
|
||||
[self addSubview:self.inputView];
|
||||
|
||||
[self.headerView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.left.right.equalTo(self);
|
||||
make.height.mas_equalTo(50);
|
||||
}];
|
||||
|
||||
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.center.equalTo(self.headerView);
|
||||
}];
|
||||
|
||||
[self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self.headerView).offset(-16);
|
||||
make.centerY.equalTo(self.headerView);
|
||||
make.width.height.mas_equalTo(25);
|
||||
}];
|
||||
|
||||
[self.inputView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self);
|
||||
make.height.mas_equalTo(50);
|
||||
self.inputBottomConstraint =
|
||||
make.bottom.equalTo(self).offset(-KB_SafeAreaBottom());
|
||||
}];
|
||||
|
||||
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.headerView.mas_bottom);
|
||||
make.left.right.equalTo(self);
|
||||
make.bottom.equalTo(self.inputView.mas_top);
|
||||
}];
|
||||
|
||||
// 上拉加载更多
|
||||
__weak typeof(self) weakSelf = self;
|
||||
MJRefreshAutoNormalFooter *footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
[strongSelf loadMoreComments];
|
||||
}];
|
||||
footer.stateLabel.hidden = YES;
|
||||
footer.backgroundColor = [UIColor clearColor];
|
||||
footer.automaticallyHidden = YES;
|
||||
self.tableView.mj_footer = footer;
|
||||
}
|
||||
|
||||
#pragma mark - Keyboard Observers
|
||||
|
||||
- (void)setupKeyboardObservers {
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(keyboardWillShow:)
|
||||
name:UIKeyboardWillShowNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(keyboardWillHide:)
|
||||
name:UIKeyboardWillHideNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)keyboardWillShow:(NSNotification *)notification {
|
||||
CGRect keyboardFrame =
|
||||
[notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||
NSTimeInterval duration =
|
||||
[notification.userInfo[UIKeyboardAnimationDurationUserInfoKey]
|
||||
doubleValue];
|
||||
|
||||
self.keyboardHeight = keyboardFrame.size.height;
|
||||
|
||||
[self.inputBottomConstraint uninstall];
|
||||
[self.inputView mas_updateConstraints:^(MASConstraintMaker *make) {
|
||||
self.inputBottomConstraint =
|
||||
make.bottom.equalTo(self).offset(-self.keyboardHeight);
|
||||
}];
|
||||
|
||||
[UIView animateWithDuration:duration
|
||||
animations:^{
|
||||
[self layoutIfNeeded];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)keyboardWillHide:(NSNotification *)notification {
|
||||
NSTimeInterval duration =
|
||||
[notification.userInfo[UIKeyboardAnimationDurationUserInfoKey]
|
||||
doubleValue];
|
||||
|
||||
self.keyboardHeight = 0;
|
||||
|
||||
[self.inputBottomConstraint uninstall];
|
||||
[self.inputView mas_updateConstraints:^(MASConstraintMaker *make) {
|
||||
self.inputBottomConstraint =
|
||||
make.bottom.equalTo(self).offset(-KB_SafeAreaBottom());
|
||||
}];
|
||||
|
||||
[UIView animateWithDuration:duration
|
||||
animations:^{
|
||||
[self layoutIfNeeded];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Data Loading
|
||||
|
||||
- (void)loadComments {
|
||||
if (self.isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.currentPage = 1;
|
||||
self.hasMoreData = YES;
|
||||
[self.tableView.mj_footer resetNoMoreData];
|
||||
|
||||
[self fetchCommentsAtPage:self.currentPage append:NO];
|
||||
}
|
||||
|
||||
- (void)loadMoreComments {
|
||||
if (self.isLoading) {
|
||||
[self.tableView.mj_footer endRefreshing];
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.hasMoreData) {
|
||||
[self.tableView.mj_footer endRefreshingWithNoMoreData];
|
||||
return;
|
||||
}
|
||||
|
||||
NSInteger nextPage = self.currentPage + 1;
|
||||
[self fetchCommentsAtPage:nextPage append:YES];
|
||||
}
|
||||
|
||||
- (void)fetchCommentsAtPage:(NSInteger)page append:(BOOL)append {
|
||||
if (self.companionId <= 0) {
|
||||
NSLog(@"[KBAICommentView] companionId 未设置,无法加载评论");
|
||||
[self showEmptyState];
|
||||
[self.tableView.mj_footer endRefreshing];
|
||||
return;
|
||||
}
|
||||
|
||||
self.isLoading = YES;
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.aiVM fetchCommentsWithCompanionId:self.companionId
|
||||
pageNum:page
|
||||
pageSize:self.pageSize
|
||||
completion:^(KBCommentPageModel *pageModel, NSError *error) {
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
|
||||
strongSelf.isLoading = NO;
|
||||
|
||||
if (error) {
|
||||
NSLog(@"[KBAICommentView] 加载评论失败:%@", error.localizedDescription);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (append) {
|
||||
[strongSelf.tableView.mj_footer endRefreshing];
|
||||
} else {
|
||||
[strongSelf showEmptyStateWithError];
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[strongSelf updateCommentsWithPageModel:pageModel append:append];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
/// 更新评论数据(从后端返回的 KBCommentPageModel 转换为 UI 层的 KBAICommentModel)
|
||||
- (void)updateCommentsWithPageModel:(KBCommentPageModel *)pageModel append:(BOOL)append {
|
||||
if (!pageModel) {
|
||||
NSLog(@"[KBAICommentView] pageModel 为空");
|
||||
// 数据为空,显示空态
|
||||
[self showEmptyState];
|
||||
[self.tableView.mj_footer endRefreshing];
|
||||
return;
|
||||
}
|
||||
|
||||
self.totalCommentCount = pageModel.total;
|
||||
|
||||
if (!append) {
|
||||
[self.comments removeAllObjects];
|
||||
}
|
||||
|
||||
// 获取 tableView 宽度用于计算高度
|
||||
CGFloat tableWidth = self.tableView.bounds.size.width;
|
||||
if (tableWidth <= 0) {
|
||||
tableWidth = [UIScreen mainScreen].bounds.size.width;
|
||||
}
|
||||
|
||||
NSLog(@"[KBAICommentView] 加载到 %ld 条评论,共 %ld 条,页码:%ld/%ld", (long)pageModel.records.count, (long)pageModel.total, (long)pageModel.current, (long)pageModel.pages);
|
||||
|
||||
for (KBCommentItem *item in pageModel.records) {
|
||||
// 转换为 KBAICommentModel(使用 MJExtension)
|
||||
KBAICommentModel *comment = [KBAICommentModel mj_objectWithKeyValues:[item mj_keyValues]];
|
||||
|
||||
// 预先计算并缓存 Header 高度
|
||||
comment.cachedHeaderHeight = [comment calculateHeaderHeightWithMaxWidth:tableWidth];
|
||||
|
||||
// 预先计算并缓存所有 Reply 高度
|
||||
for (KBAIReplyModel *reply in comment.replies) {
|
||||
reply.cachedCellHeight = [reply calculateCellHeightWithMaxWidth:tableWidth];
|
||||
}
|
||||
|
||||
[self.comments addObject:comment];
|
||||
}
|
||||
|
||||
[self updateTitle];
|
||||
[self.tableView reloadData];
|
||||
|
||||
// 更新分页状态
|
||||
self.currentPage = pageModel.current > 0 ? pageModel.current : self.currentPage;
|
||||
if (pageModel.pages > 0) {
|
||||
self.hasMoreData = pageModel.current < pageModel.pages;
|
||||
} else {
|
||||
self.hasMoreData = pageModel.records.count >= self.pageSize;
|
||||
}
|
||||
|
||||
if (self.hasMoreData) {
|
||||
[self.tableView.mj_footer endRefreshing];
|
||||
} else {
|
||||
[self.tableView.mj_footer endRefreshingWithNoMoreData];
|
||||
}
|
||||
|
||||
// 根据数据是否为空,动态控制空态显示
|
||||
if (self.comments.count == 0) {
|
||||
[self showEmptyState];
|
||||
} else {
|
||||
[self hideEmptyState];
|
||||
}
|
||||
}
|
||||
|
||||
/// 显示空态视图
|
||||
- (void)showEmptyState {
|
||||
self.tableView.useEmptyDataSet = YES;
|
||||
self.tableView.emptyTitleText = @"暂无评论";
|
||||
self.tableView.emptyDescriptionText = @"快来抢沙发吧~";
|
||||
self.tableView.emptyImage = nil; // 可选:设置空态图片
|
||||
self.tableView.emptyVerticalOffset = -50; // 向上偏移一点
|
||||
[self.tableView kb_reloadEmptyDataSet];
|
||||
}
|
||||
|
||||
/// 显示错误空态视图
|
||||
- (void)showEmptyStateWithError {
|
||||
self.tableView.useEmptyDataSet = YES;
|
||||
self.tableView.emptyTitleText = @"加载失败";
|
||||
self.tableView.emptyDescriptionText = @"点击重新加载";
|
||||
self.tableView.emptyImage = nil;
|
||||
self.tableView.emptyVerticalOffset = -50;
|
||||
|
||||
// 点击重新加载
|
||||
__weak typeof(self) weakSelf = self;
|
||||
self.tableView.emptyDidTapView = ^{
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
if (strongSelf) {
|
||||
[strongSelf loadComments];
|
||||
}
|
||||
};
|
||||
|
||||
[self.tableView kb_reloadEmptyDataSet];
|
||||
}
|
||||
|
||||
/// 隐藏空态视图
|
||||
- (void)hideEmptyState {
|
||||
self.tableView.useEmptyDataSet = NO;
|
||||
[self.tableView kb_reloadEmptyDataSet];
|
||||
}
|
||||
|
||||
- (void)updateTitle {
|
||||
NSString *countText;
|
||||
if (self.totalCommentCount >= 10000) {
|
||||
countText = [NSString
|
||||
stringWithFormat:@"%.1fw条评论", self.totalCommentCount / 10000.0];
|
||||
} else {
|
||||
countText =
|
||||
[NSString stringWithFormat:@"%ld条评论", (long)self.totalCommentCount];
|
||||
}
|
||||
self.titleLabel.text = countText;
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return self.comments.count;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView
|
||||
numberOfRowsInSection:(NSInteger)section {
|
||||
KBAICommentModel *comment = self.comments[section];
|
||||
return comment.displayedReplies.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView
|
||||
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
KBAIReplyCell *cell =
|
||||
[tableView dequeueReusableCellWithIdentifier:kReplyCellIdentifier
|
||||
forIndexPath:indexPath];
|
||||
|
||||
KBAICommentModel *comment = self.comments[indexPath.section];
|
||||
KBAIReplyModel *reply = comment.displayedReplies[indexPath.row];
|
||||
[cell configureWithReply:reply];
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
cell.onLikeAction = ^{
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取评论 ID(需要转换为 NSInteger)
|
||||
NSInteger commentId = [reply.replyId integerValue];
|
||||
|
||||
// 调用点赞接口
|
||||
[strongSelf.aiVM likeCommentWithCommentId:commentId completion:^(KBCommentLikeResponse * _Nullable response, NSError * _Nullable error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (error) {
|
||||
NSLog(@"[KBAICommentView] 二级评论点赞失败:%@", error.localizedDescription);
|
||||
// TODO: 显示错误提示
|
||||
return;
|
||||
}
|
||||
|
||||
if (response && response.code == 0) {
|
||||
// data = true: 点赞成功,data = false: 取消点赞成功
|
||||
BOOL isNowLiked = response.data;
|
||||
|
||||
// 更新模型状态
|
||||
if (isNowLiked) {
|
||||
// 点赞成功:喜欢数+1
|
||||
reply.isLiked = YES;
|
||||
reply.likeCount = MAX(0, reply.likeCount + 1);
|
||||
NSLog(@"[KBAICommentView] 二级评论点赞成功,ID: %ld", (long)commentId);
|
||||
} else {
|
||||
// 取消点赞成功:喜欢数-1
|
||||
reply.isLiked = NO;
|
||||
reply.likeCount = MAX(0, reply.likeCount - 1);
|
||||
NSLog(@"[KBAICommentView] 二级评论取消点赞成功,ID: %ld", (long)commentId);
|
||||
}
|
||||
|
||||
// 刷新对应的行
|
||||
[strongSelf.tableView reloadRowsAtIndexPaths:@[ indexPath ]
|
||||
withRowAnimation:UITableViewRowAnimationNone];
|
||||
} else {
|
||||
NSLog(@"[KBAICommentView] 二级评论点赞失败:%@", response.message ?: @"未知错误");
|
||||
// TODO: 显示错误提示
|
||||
}
|
||||
});
|
||||
}];
|
||||
};
|
||||
|
||||
cell.onReplyAction = ^{
|
||||
[weakSelf setReplyToComment:comment reply:reply];
|
||||
};
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDelegate
|
||||
|
||||
- (UIView *)tableView:(UITableView *)tableView
|
||||
viewForHeaderInSection:(NSInteger)section {
|
||||
KBAICommentHeaderView *header = [tableView
|
||||
dequeueReusableHeaderFooterViewWithIdentifier:kCommentHeaderIdentifier];
|
||||
|
||||
KBAICommentModel *comment = self.comments[section];
|
||||
[header configureWithComment:comment];
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
header.onLikeAction = ^{
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取评论 ID(需要转换为 NSInteger)
|
||||
NSInteger commentId = [comment.commentId integerValue];
|
||||
|
||||
// 调用点赞接口
|
||||
[strongSelf.aiVM likeCommentWithCommentId:commentId completion:^(KBCommentLikeResponse * _Nullable response, NSError * _Nullable error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (error) {
|
||||
NSLog(@"[KBAICommentView] 一级评论点赞失败:%@", error.localizedDescription);
|
||||
// TODO: 显示错误提示
|
||||
return;
|
||||
}
|
||||
|
||||
if (response && response.code == 0) {
|
||||
// data = true: 点赞成功,data = false: 取消点赞成功
|
||||
BOOL isNowLiked = response.data;
|
||||
|
||||
// 更新模型状态
|
||||
if (isNowLiked) {
|
||||
// 点赞成功:喜欢数+1
|
||||
comment.liked = YES;
|
||||
comment.likeCount = MAX(0, comment.likeCount + 1);
|
||||
NSLog(@"[KBAICommentView] 一级评论点赞成功,ID: %ld", (long)commentId);
|
||||
} else {
|
||||
// 取消点赞成功:喜欢数-1
|
||||
comment.liked = NO;
|
||||
comment.likeCount = MAX(0, comment.likeCount - 1);
|
||||
NSLog(@"[KBAICommentView] 一级评论取消点赞成功,ID: %ld", (long)commentId);
|
||||
}
|
||||
|
||||
// 刷新对应的 section
|
||||
[strongSelf.tableView reloadSections:[NSIndexSet indexSetWithIndex:section]
|
||||
withRowAnimation:UITableViewRowAnimationNone];
|
||||
} else {
|
||||
NSLog(@"[KBAICommentView] 一级评论点赞失败:%@", response.message ?: @"未知错误");
|
||||
// TODO: 显示错误提示
|
||||
}
|
||||
});
|
||||
}];
|
||||
};
|
||||
|
||||
header.onReplyAction = ^{
|
||||
[weakSelf setReplyToComment:comment reply:nil];
|
||||
};
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
- (UIView *)tableView:(UITableView *)tableView
|
||||
viewForFooterInSection:(NSInteger)section {
|
||||
KBAICommentModel *comment = self.comments[section];
|
||||
KBAIReplyFooterState state = [comment footerState];
|
||||
|
||||
// 无二级评论时返回空视图
|
||||
if (state == KBAIReplyFooterStateHidden) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
KBAICommentFooterView *footer = [tableView
|
||||
dequeueReusableHeaderFooterViewWithIdentifier:kCommentFooterIdentifier];
|
||||
[footer configureWithComment:comment];
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
footer.onAction = ^{
|
||||
[weakSelf handleFooterActionForSection:section];
|
||||
};
|
||||
|
||||
return footer;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView
|
||||
heightForHeaderInSection:(NSInteger)section {
|
||||
KBAICommentModel *comment = self.comments[section];
|
||||
return comment.cachedHeaderHeight;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView
|
||||
heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
KBAICommentModel *comment = self.comments[indexPath.section];
|
||||
KBAIReplyModel *reply = comment.displayedReplies[indexPath.row];
|
||||
return reply.cachedCellHeight;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView
|
||||
heightForFooterInSection:(NSInteger)section {
|
||||
KBAICommentModel *comment = self.comments[section];
|
||||
KBAIReplyFooterState state = [comment footerState];
|
||||
|
||||
if (state == KBAIReplyFooterStateHidden) {
|
||||
return CGFLOAT_MIN;
|
||||
}
|
||||
return 30;
|
||||
}
|
||||
|
||||
#pragma mark - Footer Actions
|
||||
|
||||
/// 每次加载的回复数量
|
||||
static NSInteger const kRepliesLoadCount = 5;
|
||||
|
||||
- (void)handleFooterActionForSection:(NSInteger)section {
|
||||
KBAICommentModel *comment = self.comments[section];
|
||||
KBAIReplyFooterState state = [comment footerState];
|
||||
|
||||
switch (state) {
|
||||
case KBAIReplyFooterStateExpand:
|
||||
case KBAIReplyFooterStateLoadMore: {
|
||||
[self loadMoreRepliesForSection:section];
|
||||
break;
|
||||
}
|
||||
case KBAIReplyFooterStateCollapse: {
|
||||
[self collapseRepliesForSection:section];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadMoreRepliesForSection:(NSInteger)section {
|
||||
KBAICommentModel *comment = self.comments[section];
|
||||
NSInteger currentCount = comment.displayedReplies.count;
|
||||
|
||||
// 加载更多回复
|
||||
[comment loadMoreReplies:kRepliesLoadCount];
|
||||
|
||||
// 计算新增的行
|
||||
NSInteger newCount = comment.displayedReplies.count;
|
||||
NSMutableArray *insertIndexPaths = [NSMutableArray array];
|
||||
for (NSInteger i = currentCount; i < newCount; i++) {
|
||||
[insertIndexPaths addObject:[NSIndexPath indexPathForRow:i
|
||||
inSection:section]];
|
||||
}
|
||||
|
||||
// 插入行(不刷新 Header,避免头像闪烁)
|
||||
[self.tableView beginUpdates];
|
||||
if (insertIndexPaths.count > 0) {
|
||||
[self.tableView insertRowsAtIndexPaths:insertIndexPaths
|
||||
withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
}
|
||||
[self.tableView endUpdates];
|
||||
|
||||
// 手动刷新 Footer
|
||||
KBAICommentFooterView *footerView =
|
||||
(KBAICommentFooterView *)[self.tableView footerViewForSection:section];
|
||||
if (footerView) {
|
||||
[footerView configureWithComment:comment];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)collapseRepliesForSection:(NSInteger)section {
|
||||
KBAICommentModel *comment = self.comments[section];
|
||||
NSInteger rowCount = comment.displayedReplies.count;
|
||||
|
||||
// 计算要删除的行
|
||||
NSMutableArray *deleteIndexPaths = [NSMutableArray array];
|
||||
for (NSInteger i = 0; i < rowCount; i++) {
|
||||
[deleteIndexPaths addObject:[NSIndexPath indexPathForRow:i
|
||||
inSection:section]];
|
||||
}
|
||||
|
||||
// 收起全部回复
|
||||
[comment collapseReplies];
|
||||
|
||||
// 删除行(不刷新 Header,避免头像闪烁)
|
||||
[self.tableView beginUpdates];
|
||||
if (deleteIndexPaths.count > 0) {
|
||||
[self.tableView deleteRowsAtIndexPaths:deleteIndexPaths
|
||||
withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
}
|
||||
[self.tableView endUpdates];
|
||||
|
||||
// 手动刷新 Footer
|
||||
KBAICommentFooterView *footerView =
|
||||
(KBAICommentFooterView *)[self.tableView footerViewForSection:section];
|
||||
if (footerView) {
|
||||
[footerView configureWithComment:comment];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)closeButtonTapped {
|
||||
[self.popView dismiss];
|
||||
// 关闭评论视图(由外部处理)
|
||||
// [[NSNotificationCenter defaultCenter]
|
||||
// postNotificationName:@"KBAICommentViewCloseNotification"
|
||||
// object:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Lazy Loading
|
||||
|
||||
- (UIVisualEffectView *)blurBackgroundView {
|
||||
if (!_blurBackgroundView) {
|
||||
// 创建模糊效果(43pt 的模糊半径)
|
||||
// iOS 的 UIBlurEffect 没有直接设置模糊半径的 API,使用系统预设的 dark 效果
|
||||
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
|
||||
_blurBackgroundView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
|
||||
|
||||
// 在模糊效果上叠加一个半透明黑色遮罩来调整透明度和颜色
|
||||
// 颜色:#000000,透明度:0.31
|
||||
UIView *darkOverlay = [[UIView alloc] init];
|
||||
darkOverlay.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.31];
|
||||
[_blurBackgroundView.contentView addSubview:darkOverlay];
|
||||
[darkOverlay mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(_blurBackgroundView);
|
||||
}];
|
||||
}
|
||||
return _blurBackgroundView;
|
||||
}
|
||||
|
||||
- (UIView *)headerView {
|
||||
if (!_headerView) {
|
||||
_headerView = [[UIView alloc] init];
|
||||
_headerView.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
return _headerView;
|
||||
}
|
||||
|
||||
- (UILabel *)titleLabel {
|
||||
if (!_titleLabel) {
|
||||
_titleLabel = [[UILabel alloc] init];
|
||||
_titleLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium];
|
||||
_titleLabel.textColor = [UIColor whiteColor];
|
||||
_titleLabel.text = @"0条评论";
|
||||
}
|
||||
return _titleLabel;
|
||||
}
|
||||
|
||||
- (UIButton *)closeButton {
|
||||
if (!_closeButton) {
|
||||
_closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_closeButton setImage:[UIImage imageNamed:@"comment_close_icon"]
|
||||
forState:UIControlStateNormal];
|
||||
[_closeButton addTarget:self
|
||||
action:@selector(closeButtonTapped)
|
||||
forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _closeButton;
|
||||
}
|
||||
|
||||
- (BaseTableView *)tableView {
|
||||
if (!_tableView) {
|
||||
_tableView = [[BaseTableView alloc] initWithFrame:CGRectZero
|
||||
style:UITableViewStyleGrouped];
|
||||
_tableView.dataSource = self;
|
||||
_tableView.delegate = self;
|
||||
_tableView.backgroundColor = [UIColor clearColor];
|
||||
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
_tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
|
||||
_tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1, 0.01)];
|
||||
|
||||
// 关闭空数据占位,避免加载时显示"暂无数据"
|
||||
_tableView.useEmptyDataSet = NO;
|
||||
|
||||
// 注册 Header/Cell/Footer
|
||||
[_tableView registerClass:[KBAICommentHeaderView class]
|
||||
forHeaderFooterViewReuseIdentifier:kCommentHeaderIdentifier];
|
||||
[_tableView registerClass:[KBAIReplyCell class]
|
||||
forCellReuseIdentifier:kReplyCellIdentifier];
|
||||
[_tableView registerClass:[KBAICommentFooterView class]
|
||||
forHeaderFooterViewReuseIdentifier:kCommentFooterIdentifier];
|
||||
|
||||
// 去掉顶部间距
|
||||
if (@available(iOS 15.0, *)) {
|
||||
_tableView.sectionHeaderTopPadding = 0;
|
||||
}
|
||||
}
|
||||
return _tableView;
|
||||
}
|
||||
|
||||
- (KBAICommentInputView *)inputView {
|
||||
if (!_inputView) {
|
||||
_inputView = [[KBAICommentInputView alloc] init];
|
||||
_inputView.placeholder = @"说点什么...";
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
_inputView.onSend = ^(NSString *text) {
|
||||
[weakSelf sendCommentWithText:text];
|
||||
};
|
||||
}
|
||||
return _inputView;
|
||||
}
|
||||
|
||||
#pragma mark - Reply
|
||||
|
||||
- (void)setReplyToComment:(KBAICommentModel *)comment
|
||||
reply:(KBAIReplyModel *)reply {
|
||||
self.replyToComment = comment;
|
||||
self.replyToReply = reply;
|
||||
|
||||
if (reply) {
|
||||
// 回复二级评论
|
||||
self.inputView.placeholder =
|
||||
[NSString stringWithFormat:@"回复 @%@", reply.userName];
|
||||
} else if (comment) {
|
||||
// 回复一级评论
|
||||
self.inputView.placeholder =
|
||||
[NSString stringWithFormat:@"回复 @%@", comment.userName];
|
||||
} else {
|
||||
// 普通评论
|
||||
self.inputView.placeholder = @"说点什么...";
|
||||
}
|
||||
|
||||
// 弹起键盘
|
||||
[self.inputView showKeyboard];
|
||||
}
|
||||
|
||||
- (void)clearReplyTarget {
|
||||
self.replyToComment = nil;
|
||||
self.replyToReply = nil;
|
||||
self.inputView.placeholder = @"说点什么...";
|
||||
}
|
||||
|
||||
#pragma mark - Send Comment
|
||||
|
||||
- (void)sendCommentWithText:(NSString *)text {
|
||||
if (text.length == 0)
|
||||
return;
|
||||
|
||||
CGFloat tableWidth = self.tableView.bounds.size.width;
|
||||
if (tableWidth <= 0) {
|
||||
tableWidth = [UIScreen mainScreen].bounds.size.width;
|
||||
}
|
||||
|
||||
if (self.replyToComment) {
|
||||
// 回复评论(添加二级评论)
|
||||
[self sendReplyWithText:text tableWidth:tableWidth];
|
||||
} else {
|
||||
// 发送一级评论
|
||||
[self sendNewCommentWithText:text tableWidth:tableWidth];
|
||||
}
|
||||
|
||||
// 清空输入框和回复目标
|
||||
[self.inputView clearText];
|
||||
[self clearReplyTarget];
|
||||
}
|
||||
|
||||
- (void)sendNewCommentWithText:(NSString *)text tableWidth:(CGFloat)tableWidth {
|
||||
// TODO: 调用网络接口发送一级评论
|
||||
NSLog(@"[KBAICommentView] 发送一级评论:%@", text);
|
||||
|
||||
// 示例代码:
|
||||
// [self.aiVM sendCommentWithCompanionId:self.companionId
|
||||
// content:text
|
||||
// completion:^(KBCommentItem *newItem, NSError *error) {
|
||||
// if (error) {
|
||||
// NSLog(@"[KBAICommentView] 发送评论失败:%@", error.localizedDescription);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 转换为 KBAICommentModel
|
||||
// KBAICommentModel *comment = [KBAICommentModel mj_objectWithKeyValues:[newItem mj_keyValues]];
|
||||
// comment.cachedHeaderHeight = [comment calculateHeaderHeightWithMaxWidth:tableWidth];
|
||||
//
|
||||
// // 插入到数组第一个
|
||||
// [self.comments insertObject:comment atIndex:0];
|
||||
// self.totalCommentCount++;
|
||||
// [self updateTitle];
|
||||
//
|
||||
// // 插入新 section
|
||||
// [self.tableView insertSections:[NSIndexSet indexSetWithIndex:0]
|
||||
// withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
//
|
||||
// // 滚动到顶部
|
||||
// [self.tableView setContentOffset:CGPointZero animated:YES];
|
||||
// }];
|
||||
}
|
||||
|
||||
- (void)sendReplyWithText:(NSString *)text tableWidth:(CGFloat)tableWidth {
|
||||
KBAICommentModel *comment = self.replyToComment;
|
||||
if (!comment)
|
||||
return;
|
||||
|
||||
// TODO: 调用网络接口发送二级评论(回复)
|
||||
NSLog(@"[KBAICommentView] 回复评论 %@:%@", comment.commentId, text);
|
||||
|
||||
// 示例代码:
|
||||
// NSInteger parentId = [comment.commentId integerValue];
|
||||
// [self.aiVM replyCommentWithParentId:parentId
|
||||
// content:text
|
||||
// completion:^(KBCommentItem *newItem, NSError *error) {
|
||||
// if (error) {
|
||||
// NSLog(@"[KBAICommentView] 回复评论失败:%@", error.localizedDescription);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 转换为 KBAIReplyModel
|
||||
// KBAIReplyModel *newReply = [KBAIReplyModel mj_objectWithKeyValues:[newItem mj_keyValues]];
|
||||
// newReply.cachedCellHeight = [newReply calculateCellHeightWithMaxWidth:tableWidth];
|
||||
//
|
||||
// // 添加到 replies 数组
|
||||
// NSMutableArray *newReplies = [NSMutableArray arrayWithArray:comment.replies];
|
||||
// [newReplies addObject:newReply];
|
||||
// comment.replies = newReplies;
|
||||
// comment.totalReplyCount = newReplies.count;
|
||||
//
|
||||
// // 找到该评论的 section
|
||||
// NSInteger section = [self.comments indexOfObject:comment];
|
||||
// if (section == NSNotFound) return;
|
||||
//
|
||||
// // 如果已展开,添加到 displayedReplies 并插入行
|
||||
// if (comment.isRepliesExpanded) {
|
||||
// NSInteger newRowIndex = comment.displayedReplies.count;
|
||||
// [comment.displayedReplies addObject:newReply];
|
||||
//
|
||||
// NSIndexPath *indexPath = [NSIndexPath indexPathForRow:newRowIndex inSection:section];
|
||||
// [self.tableView insertRowsAtIndexPaths:@[indexPath]
|
||||
// withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
//
|
||||
// // 刷新 Footer
|
||||
// KBAICommentFooterView *footerView = (KBAICommentFooterView *)[self.tableView footerViewForSection:section];
|
||||
// if (footerView) {
|
||||
// [footerView configureWithComment:comment];
|
||||
// }
|
||||
//
|
||||
// // 滚动到新回复
|
||||
// [self.tableView scrollToRowAtIndexPath:indexPath
|
||||
// atScrollPosition:UITableViewScrollPositionBottom
|
||||
// animated:YES];
|
||||
// } else {
|
||||
// // 未展开,刷新 Footer 显示新的回复数
|
||||
// KBAICommentFooterView *footerView = (KBAICommentFooterView *)[self.tableView footerViewForSection:section];
|
||||
// if (footerView) {
|
||||
// [footerView configureWithComment:comment];
|
||||
// }
|
||||
// }
|
||||
// }];
|
||||
}
|
||||
|
||||
- (AiVM *)aiVM {
|
||||
if (!_aiVM) {
|
||||
_aiVM = [[AiVM alloc] init];
|
||||
}
|
||||
return _aiVM;
|
||||
}
|
||||
|
||||
@end
|
||||
28
keyBoard/Class/AiTalk/V/Comment/KBAIReplyCell.h
Normal file
28
keyBoard/Class/AiTalk/V/Comment/KBAIReplyCell.h
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// KBAIReplyCell.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/16.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class KBAIReplyModel;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 二级评论 Cell
|
||||
@interface KBAIReplyCell : UITableViewCell
|
||||
|
||||
/// 配置回复数据
|
||||
- (void)configureWithReply:(KBAIReplyModel *)reply;
|
||||
|
||||
/// 点赞按钮点击回调
|
||||
@property(nonatomic, copy, nullable) void (^onLikeAction)(void);
|
||||
|
||||
/// 回复按钮点击回调
|
||||
@property(nonatomic, copy, nullable) void (^onReplyAction)(void);
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
239
keyBoard/Class/AiTalk/V/Comment/KBAIReplyCell.m
Normal file
239
keyBoard/Class/AiTalk/V/Comment/KBAIReplyCell.m
Normal file
@@ -0,0 +1,239 @@
|
||||
//
|
||||
// KBAIReplyCell.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/16.
|
||||
//
|
||||
|
||||
#import "KBAIReplyCell.h"
|
||||
#import "KBAIReplyModel.h"
|
||||
#import "KBTopImageButton.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <SDWebImage/SDWebImage.h>
|
||||
|
||||
@interface KBAIReplyCell ()
|
||||
|
||||
@property(nonatomic, strong) UIImageView *avatarImageView;
|
||||
@property(nonatomic, strong) UILabel *userNameLabel;
|
||||
@property(nonatomic, strong) UILabel *contentLabel;
|
||||
@property(nonatomic, strong) UILabel *timeLabel;
|
||||
@property(nonatomic, strong) UIButton *replyButton;
|
||||
@property(nonatomic, strong) KBTopImageButton *likeButton;
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBAIReplyCell
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style
|
||||
reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
self.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
self.contentView.backgroundColor = [UIColor clearColor];
|
||||
|
||||
[self setupUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - UI Setup
|
||||
|
||||
- (void)setupUI {
|
||||
[self.contentView addSubview:self.avatarImageView];
|
||||
[self.contentView addSubview:self.userNameLabel];
|
||||
[self.contentView addSubview:self.contentLabel];
|
||||
[self.contentView addSubview:self.timeLabel];
|
||||
[self.contentView addSubview:self.replyButton];
|
||||
[self.contentView addSubview:self.likeButton];
|
||||
|
||||
// 左侧缩进(对齐一级评论内容)
|
||||
[self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.contentView).offset(68); // 16 + 40 + 12 = 68
|
||||
make.top.equalTo(self.contentView).offset(8);
|
||||
make.width.height.mas_equalTo(26);
|
||||
}];
|
||||
|
||||
[self.userNameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.avatarImageView.mas_right).offset(8);
|
||||
make.top.equalTo(self.avatarImageView);
|
||||
make.right.equalTo(self.contentView).offset(-50);
|
||||
}];
|
||||
|
||||
[self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.userNameLabel);
|
||||
make.top.equalTo(self.contentLabel.mas_bottom).offset(6);
|
||||
make.bottom.equalTo(self.contentView).offset(-8).priority(MASLayoutPriorityDefaultHigh);
|
||||
}];
|
||||
|
||||
[self.replyButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.timeLabel.mas_right).offset(16);
|
||||
make.centerY.equalTo(self.timeLabel);
|
||||
}];
|
||||
|
||||
[self.likeButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self.contentView).offset(-16);
|
||||
make.top.equalTo(self.contentView).offset(8);
|
||||
make.width.mas_equalTo(30);
|
||||
make.height.mas_equalTo(30);
|
||||
}];
|
||||
|
||||
[self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.userNameLabel);
|
||||
make.top.equalTo(self.userNameLabel.mas_bottom).offset(4);
|
||||
make.right.equalTo(self.likeButton.mas_left).offset(-5);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Configuration
|
||||
|
||||
- (void)configureWithReply:(KBAIReplyModel *)reply {
|
||||
[self.avatarImageView
|
||||
sd_setImageWithURL:[NSURL URLWithString:reply.avatarUrl]
|
||||
placeholderImage:[UIImage imageNamed:@"default_avatar"]];
|
||||
|
||||
// 用户名(如果有回复对象,显示 "用户名 回复 @被回复用户")
|
||||
if (reply.replyToUserName.length > 0) {
|
||||
NSMutableAttributedString *attrName = [[NSMutableAttributedString alloc] init];
|
||||
|
||||
NSDictionary *nameAttrs = @{
|
||||
NSFontAttributeName : [UIFont systemFontOfSize:13 weight:UIFontWeightMedium],
|
||||
NSForegroundColorAttributeName : [UIColor secondaryLabelColor]
|
||||
};
|
||||
[attrName appendAttributedString:[[NSAttributedString alloc]
|
||||
initWithString:reply.userName
|
||||
attributes:nameAttrs]];
|
||||
|
||||
NSDictionary *replyAttrs = @{
|
||||
NSFontAttributeName : [UIFont systemFontOfSize:13],
|
||||
NSForegroundColorAttributeName : [UIColor secondaryLabelColor]
|
||||
};
|
||||
[attrName appendAttributedString:[[NSAttributedString alloc]
|
||||
initWithString:@" 回复 "
|
||||
attributes:replyAttrs]];
|
||||
|
||||
NSDictionary *toUserAttrs = @{
|
||||
NSFontAttributeName : [UIFont systemFontOfSize:13],
|
||||
NSForegroundColorAttributeName : [UIColor systemBlueColor]
|
||||
};
|
||||
[attrName appendAttributedString:[[NSAttributedString alloc]
|
||||
initWithString:[NSString stringWithFormat:@"@%@", reply.replyToUserName]
|
||||
attributes:toUserAttrs]];
|
||||
|
||||
self.userNameLabel.attributedText = attrName;
|
||||
} else {
|
||||
self.userNameLabel.attributedText = nil;
|
||||
self.userNameLabel.text = reply.userName;
|
||||
}
|
||||
|
||||
// 评论内容
|
||||
self.contentLabel.text = reply.content;
|
||||
|
||||
// 时间
|
||||
self.timeLabel.text = [reply formattedTime];
|
||||
|
||||
// 点赞
|
||||
NSString *likeText = reply.likeCount > 0 ? [self formatLikeCount:reply.likeCount] : @"";
|
||||
self.likeButton.textLabel.text = likeText;
|
||||
|
||||
UIImage *likeImage = reply.isLiked ? [UIImage systemImageNamed:@"heart.fill"]
|
||||
: [UIImage systemImageNamed:@"heart"];
|
||||
self.likeButton.iconView.image = likeImage;
|
||||
self.likeButton.iconView.tintColor = reply.isLiked ? [UIColor systemRedColor] : [UIColor grayColor];
|
||||
}
|
||||
|
||||
- (NSString *)formatLikeCount:(NSInteger)count {
|
||||
if (count >= 10000) {
|
||||
return [NSString stringWithFormat:@"%.1fw", count / 10000.0];
|
||||
} else if (count >= 1000) {
|
||||
return [NSString stringWithFormat:@"%.1fk", count / 1000.0];
|
||||
}
|
||||
return [NSString stringWithFormat:@"%ld", (long)count];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)likeButtonTapped {
|
||||
if (self.onLikeAction) {
|
||||
self.onLikeAction();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)replyButtonTapped {
|
||||
if (self.onReplyAction) {
|
||||
self.onReplyAction();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Lazy Loading
|
||||
|
||||
- (UIImageView *)avatarImageView {
|
||||
if (!_avatarImageView) {
|
||||
_avatarImageView = [[UIImageView alloc] init];
|
||||
_avatarImageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
_avatarImageView.layer.cornerRadius = 13;
|
||||
_avatarImageView.layer.masksToBounds = YES;
|
||||
_avatarImageView.backgroundColor = [UIColor systemGray5Color];
|
||||
}
|
||||
return _avatarImageView;
|
||||
}
|
||||
|
||||
- (UILabel *)userNameLabel {
|
||||
if (!_userNameLabel) {
|
||||
_userNameLabel = [[UILabel alloc] init];
|
||||
_userNameLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightMedium];
|
||||
_userNameLabel.textColor = [UIColor colorWithHex:0x9F9F9F];
|
||||
_userNameLabel.numberOfLines = 0;
|
||||
}
|
||||
return _userNameLabel;
|
||||
}
|
||||
|
||||
- (UILabel *)contentLabel {
|
||||
if (!_contentLabel) {
|
||||
_contentLabel = [[UILabel alloc] init];
|
||||
_contentLabel.font = [UIFont systemFontOfSize:12];
|
||||
_contentLabel.textColor = [UIColor whiteColor];
|
||||
_contentLabel.numberOfLines = 0;
|
||||
}
|
||||
return _contentLabel;
|
||||
}
|
||||
|
||||
- (UILabel *)timeLabel {
|
||||
if (!_timeLabel) {
|
||||
_timeLabel = [[UILabel alloc] init];
|
||||
_timeLabel.font = [UIFont systemFontOfSize:12];
|
||||
_timeLabel.textColor = [UIColor colorWithHex:0x9F9F9F];
|
||||
}
|
||||
return _timeLabel;
|
||||
}
|
||||
|
||||
- (UIButton *)replyButton {
|
||||
if (!_replyButton) {
|
||||
_replyButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_replyButton.titleLabel.font = [UIFont systemFontOfSize:11];
|
||||
[_replyButton setTitle:@"回复" forState:UIControlStateNormal];
|
||||
[_replyButton setTitleColor:[UIColor secondaryLabelColor] forState:UIControlStateNormal];
|
||||
[_replyButton addTarget:self
|
||||
action:@selector(replyButtonTapped)
|
||||
forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _replyButton;
|
||||
}
|
||||
|
||||
- (KBTopImageButton *)likeButton {
|
||||
if (!_likeButton) {
|
||||
_likeButton = [[KBTopImageButton alloc] init];
|
||||
_likeButton.iconSize = CGSizeMake(16, 16);
|
||||
_likeButton.spacing = 2;
|
||||
_likeButton.iconView.image = [UIImage systemImageNamed:@"heart"];
|
||||
_likeButton.iconView.tintColor = [UIColor grayColor];
|
||||
_likeButton.textLabel.font = [UIFont systemFontOfSize:10];
|
||||
_likeButton.textLabel.textColor = [UIColor grayColor];
|
||||
[_likeButton addTarget:self
|
||||
action:@selector(likeButtonTapped)
|
||||
forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _likeButton;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user