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 { + (NSDictionary *)mj_replacedKeyFromPropertyName {
return @{ return @{
@"commentId" : @"id", @"commentId" : @"id",
@"userName" : @[ @"userName", @"nickname", @"name" ], @"userId" : @"userId",
@"avatarUrl" : @[ @"avatarUrl", @"avatar" ], @"userName" : @"userName",
@"avatarUrl" : @"userAvatar",
@"createTime" : @"createdAt",
@"totalReplyCount" : @"replyCount",
}; };
} }
@@ -23,6 +26,24 @@
return @{@"replies" : [KBAIReplyModel class]}; 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 { - (instancetype)init {
self = [super init]; self = [super init];
if (self) { if (self) {

View File

@@ -7,17 +7,39 @@
#import "KBAIReplyModel.h" #import "KBAIReplyModel.h"
#import <MJExtension/MJExtension.h> #import <MJExtension/MJExtension.h>
#import "KBAIReplyModel.h"
@implementation KBAIReplyModel @implementation KBAIReplyModel
+ (NSDictionary *)mj_replacedKeyFromPropertyName { + (NSDictionary *)mj_replacedKeyFromPropertyName {
return @{ return @{
@"replyId" : @"id", @"replyId" : @"id",
@"userName" : @[ @"userName", @"nickname", @"name" ], @"userId" : @"userId",
@"avatarUrl" : @[ @"avatarUrl", @"avatar" ], @"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 { - (NSString *)formattedTime {
NSDate *date = [NSDate dateWithTimeIntervalSince1970:self.createTime]; NSDate *date = [NSDate dateWithTimeIntervalSince1970:self.createTime];
NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:date]; NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:date];

View File

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

View File

@@ -12,6 +12,8 @@
#import "KBAICommentModel.h" #import "KBAICommentModel.h"
#import "KBAIReplyCell.h" #import "KBAIReplyCell.h"
#import "KBAIReplyModel.h" #import "KBAIReplyModel.h"
#import "KBCommentModel.h"
#import "AiVM.h"
#import <MJExtension/MJExtension.h> #import <MJExtension/MJExtension.h>
#import <Masonry/Masonry.h> #import <Masonry/Masonry.h>
@@ -40,6 +42,9 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
/// ///
@property(nonatomic, weak) KBAIReplyModel *replyToReply; @property(nonatomic, weak) KBAIReplyModel *replyToReply;
/// AiVM
@property(nonatomic, strong) AiVM *aiVM;
@end @end
@implementation KBAICommentView @implementation KBAICommentView
@@ -52,7 +57,6 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
self.comments = [NSMutableArray array]; self.comments = [NSMutableArray array];
[self setupUI]; [self setupUI];
[self setupKeyboardObservers]; [self setupKeyboardObservers];
[self loadComments];
} }
return self; return self;
} }
@@ -162,30 +166,40 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
#pragma mark - Data Loading #pragma mark - Data Loading
- (void)loadComments { - (void)loadComments {
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"comments_mock" if (self.companionId <= 0) {
ofType:@"json"]; NSLog(@"[KBAICommentView] companionId 未设置,无法加载评论");
if (!filePath) {
NSLog(@"[KBAICommentView] comments_mock.json not found");
return; return;
} }
NSData *data = [NSData dataWithContentsOfFile:filePath]; __weak typeof(self) weakSelf = self;
if (!data) { [self.aiVM fetchCommentsWithCompanionId:self.companionId
NSLog(@"[KBAICommentView] Failed to read comments_mock.json"); 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];
});
}];
}
/// KBCommentPageModel UI KBAICommentModel
- (void)updateCommentsWithPageModel:(KBCommentPageModel *)pageModel {
if (!pageModel) {
NSLog(@"[KBAICommentView] pageModel 为空");
return; return;
} }
NSError *error; self.totalCommentCount = pageModel.total;
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.comments removeAllObjects]; [self.comments removeAllObjects];
@@ -195,16 +209,20 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
tableWidth = [UIScreen mainScreen].bounds.size.width; tableWidth = [UIScreen mainScreen].bounds.size.width;
} }
for (NSDictionary *dict in commentsArray) { NSLog(@"[KBAICommentView] 加载到 %ld 条评论,共 %ld 条", (long)pageModel.records.count, (long)pageModel.total);
KBAICommentModel *comment = [KBAICommentModel mj_objectWithKeyValues:dict];
for (KBCommentItem *item in pageModel.records) {
// KBAICommentModel使 MJExtension
KBAICommentModel *comment = [KBAICommentModel mj_objectWithKeyValues:[item mj_keyValues]];
// Header // Header
comment.cachedHeaderHeight = comment.cachedHeaderHeight = [comment calculateHeaderHeightWithMaxWidth:tableWidth];
[comment calculateHeaderHeightWithMaxWidth:tableWidth];
// Reply // Reply
for (KBAIReplyModel *reply in comment.replies) { for (KBAIReplyModel *reply in comment.replies) {
reply.cachedCellHeight = reply.cachedCellHeight = [reply calculateCellHeightWithMaxWidth:tableWidth];
[reply calculateCellHeightWithMaxWidth:tableWidth];
} }
[self.comments addObject:comment]; [self.comments addObject:comment];
} }
@@ -422,10 +440,11 @@ static NSInteger const kRepliesLoadCount = 5;
#pragma mark - Actions #pragma mark - Actions
- (void)closeButtonTapped { - (void)closeButtonTapped {
[self.popView dismiss];
// //
[[NSNotificationCenter defaultCenter] // [[NSNotificationCenter defaultCenter]
postNotificationName:@"KBAICommentViewCloseNotification" // postNotificationName:@"KBAICommentViewCloseNotification"
object:nil]; // object:nil];
} }
#pragma mark - Lazy Loading #pragma mark - Lazy Loading
@@ -555,33 +574,34 @@ static NSInteger const kRepliesLoadCount = 5;
} }
- (void)sendNewCommentWithText:(NSString *)text tableWidth:(CGFloat)tableWidth { - (void)sendNewCommentWithText:(NSString *)text tableWidth:(CGFloat)tableWidth {
// // TODO:
KBAICommentModel *newComment = [[KBAICommentModel alloc] init]; NSLog(@"[KBAICommentView] 发送一级评论:%@", text);
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 = // [self.aiVM sendCommentWithCompanionId:self.companionId
[newComment calculateHeaderHeightWithMaxWidth:tableWidth]; // content:text
// completion:^(KBCommentItem *newItem, NSError *error) {
// // if (error) {
[self.comments insertObject:newComment atIndex:0]; // NSLog(@"[KBAICommentView] 发送评论失败:%@", error.localizedDescription);
self.totalCommentCount++; // return;
[self updateTitle]; // }
//
// section // // KBAICommentModel
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:0] // KBAICommentModel *comment = [KBAICommentModel mj_objectWithKeyValues:[newItem mj_keyValues]];
withRowAnimation:UITableViewRowAnimationAutomatic]; // comment.cachedHeaderHeight = [comment calculateHeaderHeightWithMaxWidth:tableWidth];
//
// // //
[self.tableView setContentOffset:CGPointZero animated:YES]; // [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 { - (void)sendReplyWithText:(NSString *)text tableWidth:(CGFloat)tableWidth {
@@ -589,66 +609,67 @@ static NSInteger const kRepliesLoadCount = 5;
if (!comment) if (!comment)
return; return;
// // TODO:
KBAIReplyModel *newReply = [[KBAIReplyModel alloc] init]; NSLog(@"[KBAICommentView] 回复评论 %@%@", comment.commentId, text);
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) { // NSInteger parentId = [comment.commentId integerValue];
newReply.replyToUserName = self.replyToReply.userName; // [self.aiVM replyCommentWithParentId:parentId
} // content:text
// completion:^(KBCommentItem *newItem, NSError *error) {
// // if (error) {
newReply.cachedCellHeight = // NSLog(@"[KBAICommentView] 回复评论失败:%@", error.localizedDescription);
[newReply calculateCellHeightWithMaxWidth:tableWidth]; // return;
// }
// replies //
NSMutableArray *newReplies = [NSMutableArray arrayWithArray:comment.replies]; // // KBAIReplyModel
[newReplies addObject:newReply]; // KBAIReplyModel *newReply = [KBAIReplyModel mj_objectWithKeyValues:[newItem mj_keyValues]];
comment.replies = newReplies; // newReply.cachedCellHeight = [newReply calculateCellHeightWithMaxWidth:tableWidth];
comment.totalReplyCount = newReplies.count; //
// // replies
// section // NSMutableArray *newReplies = [NSMutableArray arrayWithArray:comment.replies];
NSInteger section = [self.comments indexOfObject:comment]; // [newReplies addObject:newReply];
if (section == NSNotFound) // comment.replies = newReplies;
return; // comment.totalReplyCount = newReplies.count;
//
// displayedReplies // // section
if (comment.isRepliesExpanded) { // NSInteger section = [self.comments indexOfObject:comment];
NSInteger newRowIndex = comment.displayedReplies.count; // if (section == NSNotFound) return;
[comment.displayedReplies addObject:newReply]; //
// // displayedReplies
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:newRowIndex // if (comment.isRepliesExpanded) {
inSection:section]; // NSInteger newRowIndex = comment.displayedReplies.count;
[self.tableView insertRowsAtIndexPaths:@[ indexPath ] // [comment.displayedReplies addObject:newReply];
withRowAnimation:UITableViewRowAnimationAutomatic]; //
// NSIndexPath *indexPath = [NSIndexPath indexPathForRow:newRowIndex inSection:section];
// Footer // [self.tableView insertRowsAtIndexPaths:@[indexPath]
KBAICommentFooterView *footerView = // withRowAnimation:UITableViewRowAnimationAutomatic];
(KBAICommentFooterView *)[self.tableView footerViewForSection:section]; //
if (footerView) { // // Footer
[footerView configureWithComment:comment]; // KBAICommentFooterView *footerView = (KBAICommentFooterView *)[self.tableView footerViewForSection:section];
} // if (footerView) {
// [footerView configureWithComment:comment];
// // }
[self.tableView scrollToRowAtIndexPath:indexPath //
atScrollPosition:UITableViewScrollPositionBottom // //
animated:YES]; // [self.tableView scrollToRowAtIndexPath:indexPath
} else { // atScrollPosition:UITableViewScrollPositionBottom
// Footer // animated:YES];
KBAICommentFooterView *footerView = // } else {
(KBAICommentFooterView *)[self.tableView footerViewForSection:section]; // // Footer
if (footerView) { // KBAICommentFooterView *footerView = (KBAICommentFooterView *)[self.tableView footerViewForSection:section];
[footerView configureWithComment:comment]; // if (footerView) {
} // [footerView configureWithComment:comment];
// }
// }
// }];
}
- (AiVM *)aiVM {
if (!_aiVM) {
_aiVM = [[AiVM alloc] init];
} }
return _aiVM;
} }
@end @end

View File

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