This commit is contained in:
2026-01-28 12:04:31 +08:00
parent 3fd7d2af2e
commit 51b744ecd7
5 changed files with 199 additions and 125 deletions

View File

@@ -14,8 +14,11 @@
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
return @{
@"commentId" : @"id",
@"userName" : @[ @"userName", @"nickname", @"name" ],
@"avatarUrl" : @[ @"avatarUrl", @"avatar" ],
@"userId" : @"userId",
@"userName" : @"userName",
@"avatarUrl" : @"userAvatar",
@"createTime" : @"createdAt",
@"totalReplyCount" : @"replyCount",
};
}
@@ -23,6 +26,24 @@
return @{@"replies" : [KBAIReplyModel class]};
}
- (void)setLiked:(NSInteger)liked {
// NSInteger (0/1) BOOL
_isLiked = (liked == 1);
}
- (void)setCreatedAt:(NSString *)createdAt {
//
if (createdAt && createdAt.length > 0) {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
formatter.timeZone = [NSTimeZone timeZoneWithName:@"Asia/Shanghai"];
NSDate *date = [formatter dateFromString:createdAt];
if (date) {
_createTime = [date timeIntervalSince1970];
}
}
}
- (instancetype)init {
self = [super init];
if (self) {

View File

@@ -7,17 +7,39 @@
#import "KBAIReplyModel.h"
#import <MJExtension/MJExtension.h>
#import "KBAIReplyModel.h"
@implementation KBAIReplyModel
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
return @{
@"replyId" : @"id",
@"userName" : @[ @"userName", @"nickname", @"name" ],
@"avatarUrl" : @[ @"avatarUrl", @"avatar" ],
@"userId" : @"userId",
@"userName" : @"userName",
@"avatarUrl" : @"userAvatar",
@"createTime" : @"createdAt",
};
}
- (void)setLiked:(NSInteger)liked {
// NSInteger (0/1) BOOL
_isLiked = (liked == 1);
}
- (void)setCreatedAt:(NSString *)createdAt {
//
if (createdAt && createdAt.length > 0) {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
formatter.timeZone = [NSTimeZone timeZoneWithName:@"Asia/Shanghai"];
NSDate *date = [formatter dateFromString:createdAt];
if (date) {
_createTime = [date timeIntervalSince1970];
}
}
}
- (NSString *)formattedTime {
NSDate *date = [NSDate dateWithTimeIntervalSince1970:self.createTime];
NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:date];

View File

@@ -6,17 +6,23 @@
//
#import <UIKit/UIKit.h>
#import <LSTPopView/LSTPopView.h>
NS_ASSUME_NONNULL_BEGIN
/// 抖音风格评论视图
@interface KBAICommentView : UIView
/// 加载评论数据(从本地 JSON 文件)
/// AI 陪聊角色 ID
@property(nonatomic, assign) NSInteger companionId;
/// 加载评论数据(从网络)
- (void)loadComments;
/// 评论总数
@property(nonatomic, readonly) NSInteger totalCommentCount;
@property(nonatomic, weak) LSTPopView *popView
;
@end

View File

@@ -12,6 +12,8 @@
#import "KBAICommentModel.h"
#import "KBAIReplyCell.h"
#import "KBAIReplyModel.h"
#import "KBCommentModel.h"
#import "AiVM.h"
#import <MJExtension/MJExtension.h>
#import <Masonry/Masonry.h>
@@ -40,6 +42,9 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
///
@property(nonatomic, weak) KBAIReplyModel *replyToReply;
/// AiVM
@property(nonatomic, strong) AiVM *aiVM;
@end
@implementation KBAICommentView
@@ -52,7 +57,6 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
self.comments = [NSMutableArray array];
[self setupUI];
[self setupKeyboardObservers];
[self loadComments];
}
return self;
}
@@ -162,52 +166,66 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
#pragma mark - Data Loading
- (void)loadComments {
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"comments_mock"
ofType:@"json"];
if (!filePath) {
NSLog(@"[KBAICommentView] comments_mock.json not found");
if (self.companionId <= 0) {
NSLog(@"[KBAICommentView] companionId 未设置,无法加载评论");
return;
}
__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];
});
}];
}
NSData *data = [NSData dataWithContentsOfFile:filePath];
if (!data) {
NSLog(@"[KBAICommentView] Failed to read comments_mock.json");
/// KBCommentPageModel UI KBAICommentModel
- (void)updateCommentsWithPageModel:(KBCommentPageModel *)pageModel {
if (!pageModel) {
NSLog(@"[KBAICommentView] pageModel 为空");
return;
}
NSError *error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&error];
if (error) {
NSLog(@"[KBAICommentView] JSON parse error: %@", error);
return;
}
self.totalCommentCount = [json[@"totalCount"] integerValue];
NSArray *commentsArray = json[@"comments"];
self.totalCommentCount = pageModel.total;
[self.comments removeAllObjects];
// tableView
CGFloat tableWidth = self.tableView.bounds.size.width;
if (tableWidth <= 0) {
tableWidth = [UIScreen mainScreen].bounds.size.width;
}
for (NSDictionary *dict in commentsArray) {
KBAICommentModel *comment = [KBAICommentModel mj_objectWithKeyValues:dict];
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]];
// Header
comment.cachedHeaderHeight =
[comment calculateHeaderHeightWithMaxWidth:tableWidth];
comment.cachedHeaderHeight = [comment calculateHeaderHeightWithMaxWidth:tableWidth];
// Reply
for (KBAIReplyModel *reply in comment.replies) {
reply.cachedCellHeight =
[reply calculateCellHeightWithMaxWidth:tableWidth];
reply.cachedCellHeight = [reply calculateCellHeightWithMaxWidth:tableWidth];
}
[self.comments addObject:comment];
}
[self updateTitle];
[self.tableView reloadData];
}
@@ -422,10 +440,11 @@ static NSInteger const kRepliesLoadCount = 5;
#pragma mark - Actions
- (void)closeButtonTapped {
[self.popView dismiss];
//
[[NSNotificationCenter defaultCenter]
postNotificationName:@"KBAICommentViewCloseNotification"
object:nil];
// [[NSNotificationCenter defaultCenter]
// postNotificationName:@"KBAICommentViewCloseNotification"
// object:nil];
}
#pragma mark - Lazy Loading
@@ -555,100 +574,102 @@ static NSInteger const kRepliesLoadCount = 5;
}
- (void)sendNewCommentWithText:(NSString *)text tableWidth:(CGFloat)tableWidth {
//
KBAICommentModel *newComment = [[KBAICommentModel alloc] init];
newComment.commentId = [NSUUID UUID].UUIDString;
newComment.userId = @"current_user";
newComment.userName = @"我";
newComment.avatarUrl = @"";
newComment.content = text;
newComment.likeCount = 0;
newComment.isLiked = NO;
newComment.createTime = [[NSDate date] timeIntervalSince1970];
newComment.replies = @[];
//
newComment.cachedHeaderHeight =
[newComment calculateHeaderHeightWithMaxWidth:tableWidth];
//
[self.comments insertObject:newComment atIndex:0];
self.totalCommentCount++;
[self updateTitle];
// section
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
//
[self.tableView setContentOffset:CGPointZero animated:YES];
// 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];
// }
// }
// }];
}
//
KBAIReplyModel *newReply = [[KBAIReplyModel alloc] init];
newReply.replyId = [NSUUID UUID].UUIDString;
newReply.userId = @"current_user";
newReply.userName = @"我";
newReply.avatarUrl = @"";
newReply.content = text;
newReply.likeCount = 0;
newReply.isLiked = NO;
newReply.createTime = [[NSDate date] timeIntervalSince1970];
//
if (self.replyToReply) {
newReply.replyToUserName = self.replyToReply.userName;
}
//
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

View File

@@ -531,12 +531,16 @@
initWithFrame:CGRectMake(0, 0, KB_SCREEN_WIDTH, customViewHeight)];
// ID
// customView.companionId = self.persona.personaId;
customView.companionId = self.persona.personaId;
//
[customView loadComments];
LSTPopView *popView = [LSTPopView initWithCustomView:customView
parentView:nil
popStyle:LSTPopStyleSmoothFromBottom
dismissStyle:LSTDismissStyleSmoothToBottom];
customView.popView = popView;
self.popView = popView;
popView.priority = 1000;
popView.isAvoidKeyboard = NO;