2026-01-16 13:38:03 +08:00
|
|
|
|
//
|
|
|
|
|
|
// KBAICommentView.m
|
|
|
|
|
|
// keyBoard
|
|
|
|
|
|
//
|
|
|
|
|
|
// Created by Mac on 2026/1/16.
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
#import "KBAICommentView.h"
|
2026-01-16 15:55:08 +08:00
|
|
|
|
#import "KBAICommentFooterView.h"
|
|
|
|
|
|
#import "KBAICommentHeaderView.h"
|
|
|
|
|
|
#import "KBAICommentInputView.h"
|
|
|
|
|
|
#import "KBAICommentModel.h"
|
|
|
|
|
|
#import "KBAIReplyCell.h"
|
|
|
|
|
|
#import "KBAIReplyModel.h"
|
2026-01-28 12:04:31 +08:00
|
|
|
|
#import "KBCommentModel.h"
|
|
|
|
|
|
#import "AiVM.h"
|
2026-01-16 15:55:08 +08:00
|
|
|
|
#import <MJExtension/MJExtension.h>
|
|
|
|
|
|
#import <Masonry/Masonry.h>
|
|
|
|
|
|
|
|
|
|
|
|
static NSString *const kCommentHeaderIdentifier = @"CommentHeader";
|
|
|
|
|
|
static NSString *const kReplyCellIdentifier = @"ReplyCell";
|
|
|
|
|
|
static NSString *const kCommentFooterIdentifier = @"CommentFooter";
|
|
|
|
|
|
|
|
|
|
|
|
@interface KBAICommentView () <UITableViewDataSource, UITableViewDelegate>
|
|
|
|
|
|
|
2026-01-28 13:43:36 +08:00
|
|
|
|
@property(nonatomic, strong) UIVisualEffectView *blurBackgroundView;
|
2026-01-16 15:55:08 +08:00
|
|
|
|
@property(nonatomic, strong) UIView *headerView;
|
|
|
|
|
|
@property(nonatomic, strong) UILabel *titleLabel;
|
|
|
|
|
|
@property(nonatomic, strong) UIButton *closeButton;
|
2026-01-16 19:09:54 +08:00
|
|
|
|
@property(nonatomic, strong) BaseTableView *tableView;
|
2026-01-16 15:55:08 +08:00
|
|
|
|
@property(nonatomic, strong) KBAICommentInputView *inputView;
|
|
|
|
|
|
|
|
|
|
|
|
@property(nonatomic, strong) NSMutableArray<KBAICommentModel *> *comments;
|
|
|
|
|
|
@property(nonatomic, assign) NSInteger totalCommentCount;
|
|
|
|
|
|
|
|
|
|
|
|
/// 键盘高度
|
|
|
|
|
|
@property(nonatomic, assign) CGFloat keyboardHeight;
|
|
|
|
|
|
/// 输入框底部约束
|
|
|
|
|
|
@property(nonatomic, strong) MASConstraint *inputBottomConstraint;
|
|
|
|
|
|
|
2026-01-16 20:31:42 +08:00
|
|
|
|
/// 当前回复的目标(一级评论)
|
|
|
|
|
|
@property(nonatomic, weak) KBAICommentModel *replyToComment;
|
|
|
|
|
|
/// 当前回复的目标(二级评论)
|
|
|
|
|
|
@property(nonatomic, weak) KBAIReplyModel *replyToReply;
|
|
|
|
|
|
|
2026-01-28 12:04:31 +08:00
|
|
|
|
/// AiVM 实例
|
|
|
|
|
|
@property(nonatomic, strong) AiVM *aiVM;
|
|
|
|
|
|
|
2026-01-16 15:55:08 +08:00
|
|
|
|
@end
|
2026-01-16 13:38:03 +08:00
|
|
|
|
|
|
|
|
|
|
@implementation KBAICommentView
|
|
|
|
|
|
|
2026-01-16 15:55:08 +08:00
|
|
|
|
#pragma mark - Lifecycle
|
|
|
|
|
|
|
|
|
|
|
|
- (instancetype)initWithFrame:(CGRect)frame {
|
|
|
|
|
|
self = [super initWithFrame:frame];
|
|
|
|
|
|
if (self) {
|
|
|
|
|
|
self.comments = [NSMutableArray array];
|
|
|
|
|
|
[self setupUI];
|
|
|
|
|
|
[self setupKeyboardObservers];
|
|
|
|
|
|
}
|
|
|
|
|
|
return self;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)dealloc {
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - UI Setup
|
|
|
|
|
|
|
|
|
|
|
|
- (void)setupUI {
|
2026-01-28 13:43:36 +08:00
|
|
|
|
// 设置背景为透明,让模糊效果可见
|
|
|
|
|
|
self.backgroundColor = [UIColor clearColor];
|
2026-01-16 15:55:08 +08:00
|
|
|
|
self.layer.cornerRadius = 12;
|
|
|
|
|
|
self.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner;
|
|
|
|
|
|
self.clipsToBounds = YES;
|
|
|
|
|
|
|
2026-01-28 13:43:36 +08:00
|
|
|
|
// 添加模糊背景(最底层)
|
|
|
|
|
|
[self addSubview:self.blurBackgroundView];
|
|
|
|
|
|
[self.blurBackgroundView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.edges.equalTo(self);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
2026-01-16 15:55:08 +08:00
|
|
|
|
[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);
|
2026-01-28 13:55:11 +08:00
|
|
|
|
make.width.height.mas_equalTo(25);
|
2026-01-16 15:55:08 +08:00
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[self.inputView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.left.right.equalTo(self);
|
|
|
|
|
|
make.height.mas_equalTo(50);
|
2026-01-16 20:44:10 +08:00
|
|
|
|
self.inputBottomConstraint =
|
|
|
|
|
|
make.bottom.equalTo(self).offset(-KB_SafeAreaBottom());
|
2026-01-16 15:55:08 +08:00
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[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);
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#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) {
|
2026-01-16 20:44:10 +08:00
|
|
|
|
self.inputBottomConstraint =
|
|
|
|
|
|
make.bottom.equalTo(self).offset(-KB_SafeAreaBottom());
|
2026-01-16 15:55:08 +08:00
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[UIView animateWithDuration:duration
|
|
|
|
|
|
animations:^{
|
|
|
|
|
|
[self layoutIfNeeded];
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Data Loading
|
|
|
|
|
|
|
|
|
|
|
|
- (void)loadComments {
|
2026-01-28 12:04:31 +08:00
|
|
|
|
if (self.companionId <= 0) {
|
|
|
|
|
|
NSLog(@"[KBAICommentView] companionId 未设置,无法加载评论");
|
2026-01-16 15:55:08 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-01-28 12:04:31 +08:00
|
|
|
|
|
|
|
|
|
|
__weak typeof(self) weakSelf = self;
|
|
|
|
|
|
[self.aiVM fetchCommentsWithCompanionId:self.companionId
|
|
|
|
|
|
pageNum:1
|
|
|
|
|
|
pageSize:20
|
|
|
|
|
|
completion:^(KBCommentPageModel *pageModel, NSError *error) {
|
|
|
|
|
|
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
|
|
|
|
if (!strongSelf) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
NSLog(@"[KBAICommentView] 加载评论失败:%@", error.localizedDescription);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
|
[strongSelf updateCommentsWithPageModel:pageModel];
|
|
|
|
|
|
});
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
2026-01-16 15:55:08 +08:00
|
|
|
|
|
2026-01-28 12:04:31 +08:00
|
|
|
|
/// 更新评论数据(从后端返回的 KBCommentPageModel 转换为 UI 层的 KBAICommentModel)
|
|
|
|
|
|
- (void)updateCommentsWithPageModel:(KBCommentPageModel *)pageModel {
|
|
|
|
|
|
if (!pageModel) {
|
|
|
|
|
|
NSLog(@"[KBAICommentView] pageModel 为空");
|
2026-01-16 15:55:08 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-01-28 12:04:31 +08:00
|
|
|
|
|
|
|
|
|
|
self.totalCommentCount = pageModel.total;
|
|
|
|
|
|
|
2026-01-16 15:55:08 +08:00
|
|
|
|
[self.comments removeAllObjects];
|
2026-01-28 12:04:31 +08:00
|
|
|
|
|
2026-01-16 19:09:54 +08:00
|
|
|
|
// 获取 tableView 宽度用于计算高度
|
|
|
|
|
|
CGFloat tableWidth = self.tableView.bounds.size.width;
|
|
|
|
|
|
if (tableWidth <= 0) {
|
|
|
|
|
|
tableWidth = [UIScreen mainScreen].bounds.size.width;
|
|
|
|
|
|
}
|
2026-01-28 12:04:31 +08:00
|
|
|
|
|
|
|
|
|
|
NSLog(@"[KBAICommentView] 加载到 %ld 条评论,共 %ld 条", (long)pageModel.records.count, (long)pageModel.total);
|
|
|
|
|
|
|
|
|
|
|
|
for (KBCommentItem *item in pageModel.records) {
|
|
|
|
|
|
// 转换为 KBAICommentModel(使用 MJExtension)
|
|
|
|
|
|
KBAICommentModel *comment = [KBAICommentModel mj_objectWithKeyValues:[item mj_keyValues]];
|
|
|
|
|
|
|
2026-01-16 19:09:54 +08:00
|
|
|
|
// 预先计算并缓存 Header 高度
|
2026-01-28 12:04:31 +08:00
|
|
|
|
comment.cachedHeaderHeight = [comment calculateHeaderHeightWithMaxWidth:tableWidth];
|
|
|
|
|
|
|
2026-01-16 19:09:54 +08:00
|
|
|
|
// 预先计算并缓存所有 Reply 高度
|
|
|
|
|
|
for (KBAIReplyModel *reply in comment.replies) {
|
2026-01-28 12:04:31 +08:00
|
|
|
|
reply.cachedCellHeight = [reply calculateCellHeightWithMaxWidth:tableWidth];
|
2026-01-16 19:09:54 +08:00
|
|
|
|
}
|
2026-01-28 12:04:31 +08:00
|
|
|
|
|
2026-01-16 15:55:08 +08:00
|
|
|
|
[self.comments addObject:comment];
|
|
|
|
|
|
}
|
2026-01-28 12:04:31 +08:00
|
|
|
|
|
2026-01-16 15:55:08 +08:00
|
|
|
|
[self updateTitle];
|
|
|
|
|
|
[self.tableView reloadData];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (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 = ^{
|
2026-01-28 13:55:11 +08:00
|
|
|
|
__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: 显示错误提示
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}];
|
2026-01-16 15:55:08 +08:00
|
|
|
|
};
|
2026-01-16 20:44:10 +08:00
|
|
|
|
|
2026-01-16 20:31:42 +08:00
|
|
|
|
cell.onReplyAction = ^{
|
|
|
|
|
|
[weakSelf setReplyToComment:comment reply:reply];
|
|
|
|
|
|
};
|
2026-01-16 15:55:08 +08:00
|
|
|
|
|
|
|
|
|
|
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 = ^{
|
2026-01-28 13:55:11 +08:00
|
|
|
|
__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: 显示错误提示
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}];
|
2026-01-16 15:55:08 +08:00
|
|
|
|
};
|
2026-01-16 20:44:10 +08:00
|
|
|
|
|
2026-01-16 20:31:42 +08:00
|
|
|
|
header.onReplyAction = ^{
|
|
|
|
|
|
[weakSelf setReplyToComment:comment reply:nil];
|
|
|
|
|
|
};
|
2026-01-16 15:55:08 +08:00
|
|
|
|
|
|
|
|
|
|
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 {
|
2026-01-16 19:09:54 +08:00
|
|
|
|
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;
|
2026-01-16 15:55:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (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
|
|
|
|
|
|
|
2026-01-16 19:29:42 +08:00
|
|
|
|
/// 每次加载的回复数量
|
|
|
|
|
|
static NSInteger const kRepliesLoadCount = 5;
|
|
|
|
|
|
|
2026-01-16 15:55:08 +08:00
|
|
|
|
- (void)handleFooterActionForSection:(NSInteger)section {
|
|
|
|
|
|
KBAICommentModel *comment = self.comments[section];
|
|
|
|
|
|
KBAIReplyFooterState state = [comment footerState];
|
|
|
|
|
|
|
|
|
|
|
|
switch (state) {
|
2026-01-16 19:29:42 +08:00
|
|
|
|
case KBAIReplyFooterStateExpand:
|
|
|
|
|
|
case KBAIReplyFooterStateLoadMore: {
|
|
|
|
|
|
[self loadMoreRepliesForSection:section];
|
2026-01-16 15:55:08 +08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
case KBAIReplyFooterStateCollapse: {
|
|
|
|
|
|
[self collapseRepliesForSection:section];
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
default:
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 19:29:42 +08:00
|
|
|
|
- (void)loadMoreRepliesForSection:(NSInteger)section {
|
2026-01-16 15:55:08 +08:00
|
|
|
|
KBAICommentModel *comment = self.comments[section];
|
2026-01-16 19:29:42 +08:00
|
|
|
|
NSInteger currentCount = comment.displayedReplies.count;
|
2026-01-16 20:44:10 +08:00
|
|
|
|
|
2026-01-16 19:29:42 +08:00
|
|
|
|
// 加载更多回复
|
|
|
|
|
|
[comment loadMoreReplies:kRepliesLoadCount];
|
2026-01-16 20:44:10 +08:00
|
|
|
|
|
2026-01-16 19:29:42 +08:00
|
|
|
|
// 计算新增的行
|
|
|
|
|
|
NSInteger newCount = comment.displayedReplies.count;
|
2026-01-16 19:13:26 +08:00
|
|
|
|
NSMutableArray *insertIndexPaths = [NSMutableArray array];
|
2026-01-16 19:29:42 +08:00
|
|
|
|
for (NSInteger i = currentCount; i < newCount; i++) {
|
2026-01-16 20:44:10 +08:00
|
|
|
|
[insertIndexPaths addObject:[NSIndexPath indexPathForRow:i
|
|
|
|
|
|
inSection:section]];
|
2026-01-16 19:13:26 +08:00
|
|
|
|
}
|
2026-01-16 20:44:10 +08:00
|
|
|
|
|
2026-01-16 19:13:26 +08:00
|
|
|
|
// 插入行(不刷新 Header,避免头像闪烁)
|
|
|
|
|
|
[self.tableView beginUpdates];
|
|
|
|
|
|
if (insertIndexPaths.count > 0) {
|
|
|
|
|
|
[self.tableView insertRowsAtIndexPaths:insertIndexPaths
|
|
|
|
|
|
withRowAnimation:UITableViewRowAnimationAutomatic];
|
|
|
|
|
|
}
|
|
|
|
|
|
[self.tableView endUpdates];
|
2026-01-16 20:44:10 +08:00
|
|
|
|
|
2026-01-16 19:13:26 +08:00
|
|
|
|
// 手动刷新 Footer
|
|
|
|
|
|
KBAICommentFooterView *footerView =
|
|
|
|
|
|
(KBAICommentFooterView *)[self.tableView footerViewForSection:section];
|
|
|
|
|
|
if (footerView) {
|
|
|
|
|
|
[footerView configureWithComment:comment];
|
|
|
|
|
|
}
|
2026-01-16 15:55:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)collapseRepliesForSection:(NSInteger)section {
|
|
|
|
|
|
KBAICommentModel *comment = self.comments[section];
|
2026-01-16 19:13:26 +08:00
|
|
|
|
NSInteger rowCount = comment.displayedReplies.count;
|
2026-01-16 20:44:10 +08:00
|
|
|
|
|
2026-01-16 19:13:26 +08:00
|
|
|
|
// 计算要删除的行
|
|
|
|
|
|
NSMutableArray *deleteIndexPaths = [NSMutableArray array];
|
|
|
|
|
|
for (NSInteger i = 0; i < rowCount; i++) {
|
2026-01-16 20:44:10 +08:00
|
|
|
|
[deleteIndexPaths addObject:[NSIndexPath indexPathForRow:i
|
|
|
|
|
|
inSection:section]];
|
2026-01-16 19:13:26 +08:00
|
|
|
|
}
|
2026-01-16 20:44:10 +08:00
|
|
|
|
|
2026-01-16 19:09:54 +08:00
|
|
|
|
// 收起全部回复
|
2026-01-16 15:55:08 +08:00
|
|
|
|
[comment collapseReplies];
|
2026-01-16 20:44:10 +08:00
|
|
|
|
|
2026-01-16 19:13:26 +08:00
|
|
|
|
// 删除行(不刷新 Header,避免头像闪烁)
|
|
|
|
|
|
[self.tableView beginUpdates];
|
|
|
|
|
|
if (deleteIndexPaths.count > 0) {
|
|
|
|
|
|
[self.tableView deleteRowsAtIndexPaths:deleteIndexPaths
|
|
|
|
|
|
withRowAnimation:UITableViewRowAnimationAutomatic];
|
|
|
|
|
|
}
|
|
|
|
|
|
[self.tableView endUpdates];
|
2026-01-16 20:44:10 +08:00
|
|
|
|
|
2026-01-16 19:13:26 +08:00
|
|
|
|
// 手动刷新 Footer
|
|
|
|
|
|
KBAICommentFooterView *footerView =
|
|
|
|
|
|
(KBAICommentFooterView *)[self.tableView footerViewForSection:section];
|
|
|
|
|
|
if (footerView) {
|
|
|
|
|
|
[footerView configureWithComment:comment];
|
|
|
|
|
|
}
|
2026-01-16 15:55:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Actions
|
|
|
|
|
|
|
|
|
|
|
|
- (void)closeButtonTapped {
|
2026-01-28 12:04:31 +08:00
|
|
|
|
[self.popView dismiss];
|
2026-01-16 15:55:08 +08:00
|
|
|
|
// 关闭评论视图(由外部处理)
|
2026-01-28 12:04:31 +08:00
|
|
|
|
// [[NSNotificationCenter defaultCenter]
|
|
|
|
|
|
// postNotificationName:@"KBAICommentViewCloseNotification"
|
|
|
|
|
|
// object:nil];
|
2026-01-16 15:55:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Lazy Loading
|
|
|
|
|
|
|
2026-01-28 13:43:36 +08:00
|
|
|
|
- (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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 15:55:08 +08:00
|
|
|
|
- (UIView *)headerView {
|
|
|
|
|
|
if (!_headerView) {
|
|
|
|
|
|
_headerView = [[UIView alloc] init];
|
2026-01-28 13:43:36 +08:00
|
|
|
|
_headerView.backgroundColor = [UIColor clearColor];
|
2026-01-16 15:55:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
return _headerView;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (UILabel *)titleLabel {
|
|
|
|
|
|
if (!_titleLabel) {
|
|
|
|
|
|
_titleLabel = [[UILabel alloc] init];
|
|
|
|
|
|
_titleLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium];
|
2026-01-28 13:43:36 +08:00
|
|
|
|
_titleLabel.textColor = [UIColor whiteColor];
|
2026-01-16 15:55:08 +08:00
|
|
|
|
_titleLabel.text = @"0条评论";
|
|
|
|
|
|
}
|
|
|
|
|
|
return _titleLabel;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (UIButton *)closeButton {
|
|
|
|
|
|
if (!_closeButton) {
|
|
|
|
|
|
_closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
2026-01-28 13:55:11 +08:00
|
|
|
|
[_closeButton setImage:[UIImage imageNamed:@"comment_close_icon"]
|
2026-01-16 15:55:08 +08:00
|
|
|
|
forState:UIControlStateNormal];
|
|
|
|
|
|
[_closeButton addTarget:self
|
|
|
|
|
|
action:@selector(closeButtonTapped)
|
|
|
|
|
|
forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
|
|
}
|
|
|
|
|
|
return _closeButton;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 19:09:54 +08:00
|
|
|
|
- (BaseTableView *)tableView {
|
2026-01-16 15:55:08 +08:00
|
|
|
|
if (!_tableView) {
|
2026-01-16 19:09:54 +08:00
|
|
|
|
_tableView = [[BaseTableView alloc] initWithFrame:CGRectZero
|
2026-01-16 20:44:10 +08:00
|
|
|
|
style:UITableViewStyleGrouped];
|
2026-01-16 15:55:08 +08:00
|
|
|
|
_tableView.dataSource = self;
|
|
|
|
|
|
_tableView.delegate = self;
|
2026-01-28 13:43:36 +08:00
|
|
|
|
_tableView.backgroundColor = [UIColor clearColor];
|
2026-01-16 15:55:08 +08:00
|
|
|
|
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
|
|
|
|
|
_tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
|
|
|
|
|
|
|
|
|
|
|
|
// 注册 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) {
|
2026-01-16 19:36:41 +08:00
|
|
|
|
[weakSelf sendCommentWithText:text];
|
2026-01-16 15:55:08 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
return _inputView;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 20:31:42 +08:00
|
|
|
|
#pragma mark - Reply
|
|
|
|
|
|
|
2026-01-16 20:44:10 +08:00
|
|
|
|
- (void)setReplyToComment:(KBAICommentModel *)comment
|
|
|
|
|
|
reply:(KBAIReplyModel *)reply {
|
2026-01-16 20:31:42 +08:00
|
|
|
|
self.replyToComment = comment;
|
|
|
|
|
|
self.replyToReply = reply;
|
2026-01-16 20:44:10 +08:00
|
|
|
|
|
2026-01-16 20:31:42 +08:00
|
|
|
|
if (reply) {
|
|
|
|
|
|
// 回复二级评论
|
2026-01-16 20:44:10 +08:00
|
|
|
|
self.inputView.placeholder =
|
|
|
|
|
|
[NSString stringWithFormat:@"回复 @%@", reply.userName];
|
2026-01-16 20:31:42 +08:00
|
|
|
|
} else if (comment) {
|
|
|
|
|
|
// 回复一级评论
|
2026-01-16 20:44:10 +08:00
|
|
|
|
self.inputView.placeholder =
|
|
|
|
|
|
[NSString stringWithFormat:@"回复 @%@", comment.userName];
|
2026-01-16 20:31:42 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 普通评论
|
|
|
|
|
|
self.inputView.placeholder = @"说点什么...";
|
|
|
|
|
|
}
|
2026-01-16 20:44:10 +08:00
|
|
|
|
|
|
|
|
|
|
// 弹起键盘
|
|
|
|
|
|
[self.inputView showKeyboard];
|
2026-01-16 20:31:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)clearReplyTarget {
|
|
|
|
|
|
self.replyToComment = nil;
|
|
|
|
|
|
self.replyToReply = nil;
|
|
|
|
|
|
self.inputView.placeholder = @"说点什么...";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 19:36:41 +08:00
|
|
|
|
#pragma mark - Send Comment
|
|
|
|
|
|
|
|
|
|
|
|
- (void)sendCommentWithText:(NSString *)text {
|
2026-01-16 20:44:10 +08:00
|
|
|
|
if (text.length == 0)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2026-01-16 20:31:42 +08:00
|
|
|
|
CGFloat tableWidth = self.tableView.bounds.size.width;
|
|
|
|
|
|
if (tableWidth <= 0) {
|
|
|
|
|
|
tableWidth = [UIScreen mainScreen].bounds.size.width;
|
|
|
|
|
|
}
|
2026-01-16 20:44:10 +08:00
|
|
|
|
|
2026-01-16 20:31:42 +08:00
|
|
|
|
if (self.replyToComment) {
|
|
|
|
|
|
// 回复评论(添加二级评论)
|
|
|
|
|
|
[self sendReplyWithText:text tableWidth:tableWidth];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 发送一级评论
|
|
|
|
|
|
[self sendNewCommentWithText:text tableWidth:tableWidth];
|
|
|
|
|
|
}
|
2026-01-16 20:44:10 +08:00
|
|
|
|
|
2026-01-16 20:31:42 +08:00
|
|
|
|
// 清空输入框和回复目标
|
|
|
|
|
|
[self.inputView clearText];
|
|
|
|
|
|
[self clearReplyTarget];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)sendNewCommentWithText:(NSString *)text tableWidth:(CGFloat)tableWidth {
|
2026-01-28 12:04:31 +08:00
|
|
|
|
// 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];
|
|
|
|
|
|
// }];
|
2026-01-16 20:31:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)sendReplyWithText:(NSString *)text tableWidth:(CGFloat)tableWidth {
|
|
|
|
|
|
KBAICommentModel *comment = self.replyToComment;
|
2026-01-16 20:44:10 +08:00
|
|
|
|
if (!comment)
|
|
|
|
|
|
return;
|
2026-01-28 12:04:31 +08:00
|
|
|
|
|
|
|
|
|
|
// 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];
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }];
|
|
|
|
|
|
}
|
2026-01-16 20:44:10 +08:00
|
|
|
|
|
2026-01-28 12:04:31 +08:00
|
|
|
|
- (AiVM *)aiVM {
|
|
|
|
|
|
if (!_aiVM) {
|
|
|
|
|
|
_aiVM = [[AiVM alloc] init];
|
2026-01-16 20:31:42 +08:00
|
|
|
|
}
|
2026-01-28 12:04:31 +08:00
|
|
|
|
return _aiVM;
|
2026-01-16 19:36:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 13:38:03 +08:00
|
|
|
|
@end
|