This commit is contained in:
2026-03-09 17:34:08 +08:00
parent c1ace5f53e
commit 0af7428353
55 changed files with 1630 additions and 665 deletions

View File

@@ -85,13 +85,16 @@
NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:date];
if (interval < 60) {
return @"刚刚";
return KBLocalized(@"Just now");
} else if (interval < 3600) {
return [NSString stringWithFormat:@"%.0f分钟前", interval / 60];
return [NSString stringWithFormat:KBLocalized(@"%.0f minutes ago"),
interval / 60];
} else if (interval < 86400) {
return [NSString stringWithFormat:@"%.0f小时前", interval / 3600];
return [NSString stringWithFormat:KBLocalized(@"%.0f hours ago"),
interval / 3600];
} else if (interval < 86400 * 30) {
return [NSString stringWithFormat:@"%.0f天前", interval / 86400];
return [NSString stringWithFormat:KBLocalized(@"%.0f days ago"),
interval / 86400];
} else {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"MM-dd";

View File

@@ -68,13 +68,16 @@
NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:date];
if (interval < 60) {
return @"刚刚";
return KBLocalized(@"Just now");
} else if (interval < 3600) {
return [NSString stringWithFormat:@"%.0f分钟前", interval / 60];
return [NSString stringWithFormat:KBLocalized(@"%.0f minutes ago"),
interval / 60];
} else if (interval < 86400) {
return [NSString stringWithFormat:@"%.0f小时前", interval / 3600];
return [NSString stringWithFormat:KBLocalized(@"%.0f hours ago"),
interval / 3600];
} else if (interval < 86400 * 30) {
return [NSString stringWithFormat:@"%.0f天前", interval / 86400];
return [NSString stringWithFormat:KBLocalized(@"%.0f days ago"),
interval / 86400];
} else {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"MM-dd";
@@ -95,7 +98,8 @@
// "回复 @xxx"
NSMutableString *userNameText = [NSMutableString stringWithString:self.userName ?: @""];
if (self.replyToUserName.length > 0) {
[userNameText appendFormat:@" 回复 @%@", self.replyToUserName];
[userNameText appendFormat:@" %@ @%@", KBLocalized(@"Reply"),
self.replyToUserName];
}
UIFont *userNameFont = [UIFont systemFontOfSize:13 weight:UIFontWeightMedium];
CGRect userNameRect = [userNameText boundingRectWithSize:CGSizeMake(contentWidth, CGFLOAT_MAX)

View File

@@ -58,10 +58,12 @@
formatter.dateFormat = @"HH:mm";
} else if ([calendar isDateInYesterday:timestamp]) {
//
formatter.dateFormat = @"'昨天' HH:mm";
formatter.dateFormat = @"HH:mm";
NSString *timeText = [formatter stringFromDate:timestamp];
return [NSString stringWithFormat:@"%@ %@", KBLocalized(@"Yesterday"), timeText];
} else {
// +
formatter.dateFormat = @"MMdd HH:mm";
formatter.dateFormat = @"MM-dd HH:mm";
}
return [formatter stringFromDate:timestamp];

View File

@@ -61,8 +61,8 @@
}
case KBAIReplyFooterStateExpand: {
self.actionButton.hidden = NO;
title = [NSString
stringWithFormat:@"展开%ld条回复", (long)comment.totalReplyCount];
title = [NSString stringWithFormat:KBLocalized(@"View %ld replies"),
(long)comment.totalReplyCount];
[self.actionButton setImage:[UIImage systemImageNamed:@"chevron.down"]
forState:UIControlStateNormal];
break;
@@ -71,15 +71,15 @@
self.actionButton.hidden = NO;
NSInteger remaining =
comment.totalReplyCount - comment.displayedReplies.count;
title =
[NSString stringWithFormat:@"展开更多回复(%ld条", (long)remaining];
title = [NSString stringWithFormat:KBLocalized(@"View more replies (%ld)"),
(long)remaining];
[self.actionButton setImage:[UIImage systemImageNamed:@"chevron.down"]
forState:UIControlStateNormal];
break;
}
case KBAIReplyFooterStateCollapse: {
self.actionButton.hidden = NO;
title = @"收起";
title = KBLocalized(@"Collapse");
[self.actionButton setImage:[UIImage systemImageNamed:@"chevron.up"]
forState:UIControlStateNormal];
break;

View File

@@ -94,8 +94,9 @@
self.timeLabel.text = [comment formattedTime];
//
NSString *likeText =
comment.likeCount > 0 ? [self formatLikeCount:comment.likeCount] : @"赞";
NSString *likeText = comment.likeCount > 0
? [self formatLikeCount:comment.likeCount]
: KBLocalized(@"Like");
self.likeButton.textLabel.text = likeText;
UIImage *likeImage = comment.liked
@@ -174,7 +175,7 @@
if (!_replyButton) {
_replyButton = [UIButton buttonWithType:UIButtonTypeCustom];
_replyButton.titleLabel.font = [UIFont systemFontOfSize:12];
[_replyButton setTitle:@"回复" forState:UIControlStateNormal];
[_replyButton setTitle:KBLocalized(@"Reply") forState:UIControlStateNormal];
[_replyButton setTitleColor:[UIColor colorWithHex:0x9F9F9F] forState:UIControlStateNormal];
[_replyButton addTarget:self
action:@selector(replyButtonTapped)

View File

@@ -35,23 +35,23 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
@property(nonatomic, strong) NSMutableArray<KBAICommentModel *> *comments;
///
/// Pagination params
@property(nonatomic, assign) NSInteger currentPage;
@property(nonatomic, assign) NSInteger pageSize;
@property(nonatomic, assign) BOOL isLoading;
@property(nonatomic, assign) BOOL hasMoreData;
///
/// Keyboard height
@property(nonatomic, assign) CGFloat keyboardHeight;
///
/// Bottom constraint for input view
@property(nonatomic, strong) MASConstraint *inputBottomConstraint;
///
/// Current reply target (top-level comment)
@property(nonatomic, weak) KBAICommentModel *replyToComment;
///
/// Current reply target (reply)
@property(nonatomic, weak) KBAIReplyModel *replyToReply;
/// AiVM
/// AiVM instance
@property(nonatomic, strong) AiVM *aiVM;
@end
@@ -65,7 +65,7 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
if (user.nickName.length > 0) {
return user.nickName;
}
return @"我";
return KBLocalized(@"Me");
}
- (NSString *)currentUserId {
@@ -80,7 +80,7 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
- (NSString *)generateTempIdString {
long long ms = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
// 使 ID
// Use negative values to avoid colliding with server IDs
long long tmp = -ms;
return [NSString stringWithFormat:@"%lld", tmp];
}
@@ -155,13 +155,13 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
#pragma mark - UI Setup
- (void)setupUI {
//
// Make background transparent so blur effect is visible
self.backgroundColor = [UIColor clearColor];
self.layer.cornerRadius = 12;
self.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner;
self.clipsToBounds = YES;
//
// Add blur background (bottom-most layer)
[self addSubview:self.blurBackgroundView];
[self.blurBackgroundView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self);
@@ -201,7 +201,7 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
make.bottom.equalTo(self.inputView.mas_top);
}];
//
// Load more on pull-up
__weak typeof(self) weakSelf = self;
MJRefreshAutoNormalFooter *footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
@@ -302,7 +302,7 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
- (void)fetchCommentsAtPage:(NSInteger)page append:(BOOL)append {
if (self.companionId <= 0) {
NSLog(@"[KBAICommentView] companionId 未设置,无法加载评论");
NSLog(@"[KBAICommentView] companionId is not set, cannot load comments");
[self showEmptyState];
[self.tableView.mj_footer endRefreshing];
return;
@@ -323,7 +323,7 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
strongSelf.isLoading = NO;
if (error) {
NSLog(@"[KBAICommentView] 加载评论失败:%@", error.localizedDescription);
NSLog(@"[KBAICommentView] Failed to load comments: %@", error.localizedDescription);
dispatch_async(dispatch_get_main_queue(), ^{
if (append) {
[strongSelf.tableView.mj_footer endRefreshing];
@@ -340,11 +340,11 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
}];
}
/// KBCommentPageModel UI KBAICommentModel
/// Update comments (convert backend KBCommentPageModel to UI KBAICommentModel)
- (void)updateCommentsWithPageModel:(KBCommentPageModel *)pageModel append:(BOOL)append {
if (!pageModel) {
NSLog(@"[KBAICommentView] pageModel 为空");
//
NSLog(@"[KBAICommentView] pageModel is nil");
// Data is empty, show empty state
[self showEmptyState];
[self.tableView.mj_footer endRefreshing];
return;
@@ -356,19 +356,20 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
[self.comments removeAllObjects];
}
// tableView
// Get tableView width for height calculation
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);
NSLog(@"[KBAICommentView] Loaded %ld comments, total %ld, page: %ld/%ld", (long)pageModel.records.count, (long)pageModel.total, (long)pageModel.current, (long)pageModel.pages);
for (KBCommentItem *item in pageModel.records) {
// KBAICommentModel使 MJExtension
// KBCommentItem MJExtension id commentId
// mj_keyValues commentIdKBAICommentModel/KBAIReplyModel
// commentId/replyId -> id commentId/replyId parentId/rootId
// Convert to KBAICommentModel (via MJExtension)
// Note: KBCommentItem maps backend field id to commentId via MJExtension.
// If we directly use mj_keyValues, the dictionary only has commentId and
// KBAICommentModel/KBAIReplyModel mapping (commentId/replyId -> id) misses it.
// That would make commentId/replyId empty and break parentId/rootId when replying.
NSMutableDictionary *itemKV = [[item mj_keyValues] mutableCopy];
id commentIdVal = itemKV[@"commentId"];
if (commentIdVal) {
@@ -397,10 +398,10 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
KBAICommentModel *comment = [KBAICommentModel mj_objectWithKeyValues:[itemKV copy]];
// Header
// Precompute and cache header height
comment.cachedHeaderHeight = [comment calculateHeaderHeightWithMaxWidth:tableWidth];
// Reply
// Precompute and cache all reply heights
for (KBAIReplyModel *reply in comment.replies) {
reply.cachedCellHeight = [reply calculateCellHeightWithMaxWidth:tableWidth];
}
@@ -411,7 +412,7 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
[self updateTitle];
[self.tableView reloadData];
//
// Update pagination state
self.currentPage = pageModel.current > 0 ? pageModel.current : self.currentPage;
if (pageModel.pages > 0) {
self.hasMoreData = pageModel.current < pageModel.pages;
@@ -425,7 +426,7 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
[self.tableView.mj_footer endRefreshingWithNoMoreData];
}
//
// Toggle empty state based on data
if (self.comments.count == 0) {
[self showEmptyState];
} else {
@@ -433,25 +434,25 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
}
}
///
/// Show empty state view
- (void)showEmptyState {
self.tableView.useEmptyDataSet = YES;
self.tableView.emptyTitleText = @"暂无评论";
self.tableView.emptyDescriptionText = @"快来抢沙发吧~";
self.tableView.emptyImage = nil; //
self.tableView.emptyVerticalOffset = -50; //
self.tableView.emptyTitleText = KBLocalized(@"No comments yet");
self.tableView.emptyDescriptionText = KBLocalized(@"Be the first to comment");
self.tableView.emptyImage = nil; // Optional: set empty-state image
self.tableView.emptyVerticalOffset = -50; // Slight upward offset
[self.tableView kb_reloadEmptyDataSet];
}
///
/// Show error empty state view
- (void)showEmptyStateWithError {
self.tableView.useEmptyDataSet = YES;
self.tableView.emptyTitleText = @"加载失败";
self.tableView.emptyDescriptionText = @"点击重新加载";
self.tableView.emptyTitleText = KBLocalized(@"Load failed");
self.tableView.emptyDescriptionText = KBLocalized(@"Tap to retry");
self.tableView.emptyImage = nil;
self.tableView.emptyVerticalOffset = -50;
//
// Tap to reload
__weak typeof(self) weakSelf = self;
self.tableView.emptyDidTapView = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
@@ -463,7 +464,7 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
[self.tableView kb_reloadEmptyDataSet];
}
///
/// Hide empty state view
- (void)hideEmptyState {
self.tableView.useEmptyDataSet = NO;
[self.tableView kb_reloadEmptyDataSet];
@@ -473,10 +474,10 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
NSString *countText;
if (self.totalCommentCount >= 10000) {
countText = [NSString
stringWithFormat:@"%.1fw条评论", self.totalCommentCount / 10000.0];
stringWithFormat:KBLocalized(@"%.1fw comments"), self.totalCommentCount / 10000.0];
} else {
countText =
[NSString stringWithFormat:@"%ld条评论", (long)self.totalCommentCount];
[NSString stringWithFormat:KBLocalized(@"%ld comments"), (long)self.totalCommentCount];
}
self.titleLabel.text = countText;
}
@@ -510,41 +511,41 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
return;
}
// ID NSInteger
// Get comment ID (convert to NSInteger)
NSInteger commentId = [reply.replyId integerValue];
//
// Call like API
[strongSelf.aiVM likeCommentWithCommentId:commentId completion:^(KBCommentLikeResponse * _Nullable response, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
NSLog(@"[KBAICommentView] 二级评论点赞失败:%@", error.localizedDescription);
// TODO:
NSLog(@"[KBAICommentView] Failed to like reply: %@", error.localizedDescription);
// TODO: Show error message
return;
}
if (response && response.code == 0) {
// data = true: data = false:
// data = true: liked, data = false: unliked
BOOL isNowLiked = response.data;
//
// Update model state
if (isNowLiked) {
// +1
// Like succeeded: like count +1
reply.liked = YES;
reply.likeCount = MAX(0, reply.likeCount + 1);
NSLog(@"[KBAICommentView] 二级评论点赞成功,ID: %ld", (long)commentId);
NSLog(@"[KBAICommentView] Reply liked successfully, ID: %ld", (long)commentId);
} else {
// -1
// Unlike succeeded: like count -1
reply.liked = NO;
reply.likeCount = MAX(0, reply.likeCount - 1);
NSLog(@"[KBAICommentView] 二级评论取消点赞成功,ID: %ld", (long)commentId);
NSLog(@"[KBAICommentView] Reply unliked successfully, ID: %ld", (long)commentId);
}
//
// Refresh target row
[strongSelf.tableView reloadRowsAtIndexPaths:@[ indexPath ]
withRowAnimation:UITableViewRowAnimationNone];
} else {
NSLog(@"[KBAICommentView] 二级评论点赞失败:%@", response.message ?: @"未知错误");
// TODO:
NSLog(@"[KBAICommentView] Failed to like reply: %@", response.message ?: @"Unknown error");
// TODO: Show error message
}
});
}];
@@ -574,41 +575,41 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
return;
}
// ID NSInteger
// Get comment ID (convert to NSInteger)
NSInteger commentId = [comment.commentId integerValue];
//
// Call like API
[strongSelf.aiVM likeCommentWithCommentId:commentId completion:^(KBCommentLikeResponse * _Nullable response, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
NSLog(@"[KBAICommentView] 一级评论点赞失败:%@", error.localizedDescription);
// TODO:
NSLog(@"[KBAICommentView] Failed to like top-level comment: %@", error.localizedDescription);
// TODO: Show error message
return;
}
if (response && response.code == 0) {
// data = true: data = false:
// data = true: liked, data = false: unliked
BOOL isNowLiked = response.data;
//
// Update model state
if (isNowLiked) {
// +1
// Like succeeded: like count +1
comment.liked = YES;
comment.likeCount = MAX(0, comment.likeCount + 1);
NSLog(@"[KBAICommentView] 一级评论点赞成功,ID: %ld", (long)commentId);
NSLog(@"[KBAICommentView] Top-level comment liked successfully, ID: %ld", (long)commentId);
} else {
// -1
// Unlike succeeded: like count -1
comment.liked = NO;
comment.likeCount = MAX(0, comment.likeCount - 1);
NSLog(@"[KBAICommentView] 一级评论取消点赞成功,ID: %ld", (long)commentId);
NSLog(@"[KBAICommentView] Top-level comment unliked successfully, ID: %ld", (long)commentId);
}
// section
// Refresh target section
[strongSelf.tableView reloadSections:[NSIndexSet indexSetWithIndex:section]
withRowAnimation:UITableViewRowAnimationNone];
} else {
NSLog(@"[KBAICommentView] 一级评论点赞失败:%@", response.message ?: @"未知错误");
// TODO:
NSLog(@"[KBAICommentView] Failed to like top-level comment: %@", response.message ?: @"Unknown error");
// TODO: Show error message
}
});
}];
@@ -626,7 +627,7 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
KBAICommentModel *comment = self.comments[section];
KBAIReplyFooterState state = [comment footerState];
//
// Return empty view when there are no replies
if (state == KBAIReplyFooterStateHidden) {
return nil;
}
@@ -669,7 +670,7 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
#pragma mark - Footer Actions
///
/// Number of replies loaded each time
static NSInteger const kRepliesLoadCount = 5;
- (void)handleFooterActionForSection:(NSInteger)section {
@@ -695,10 +696,10 @@ static NSInteger const kRepliesLoadCount = 5;
KBAICommentModel *comment = self.comments[section];
NSInteger currentCount = comment.displayedReplies.count;
//
// Load more replies
[comment loadMoreReplies:kRepliesLoadCount];
//
// Calculate newly inserted rows
NSInteger newCount = comment.displayedReplies.count;
NSMutableArray *insertIndexPaths = [NSMutableArray array];
for (NSInteger i = currentCount; i < newCount; i++) {
@@ -706,7 +707,7 @@ static NSInteger const kRepliesLoadCount = 5;
inSection:section]];
}
// Header
// Insert rows (do not refresh header to avoid avatar flicker)
[self.tableView beginUpdates];
if (insertIndexPaths.count > 0) {
[self.tableView insertRowsAtIndexPaths:insertIndexPaths
@@ -714,7 +715,7 @@ static NSInteger const kRepliesLoadCount = 5;
}
[self.tableView endUpdates];
// Footer
// Manually refresh footer
KBAICommentFooterView *footerView =
(KBAICommentFooterView *)[self.tableView footerViewForSection:section];
if (footerView) {
@@ -726,17 +727,17 @@ static NSInteger const kRepliesLoadCount = 5;
KBAICommentModel *comment = self.comments[section];
NSInteger rowCount = comment.displayedReplies.count;
//
// Calculate rows to delete
NSMutableArray *deleteIndexPaths = [NSMutableArray array];
for (NSInteger i = 0; i < rowCount; i++) {
[deleteIndexPaths addObject:[NSIndexPath indexPathForRow:i
inSection:section]];
}
//
// Collapse all replies
[comment collapseReplies];
// Header
// Delete rows (do not refresh header to avoid avatar flicker)
[self.tableView beginUpdates];
if (deleteIndexPaths.count > 0) {
[self.tableView deleteRowsAtIndexPaths:deleteIndexPaths
@@ -744,7 +745,7 @@ static NSInteger const kRepliesLoadCount = 5;
}
[self.tableView endUpdates];
// Footer
// Manually refresh footer
KBAICommentFooterView *footerView =
(KBAICommentFooterView *)[self.tableView footerViewForSection:section];
if (footerView) {
@@ -756,7 +757,7 @@ static NSInteger const kRepliesLoadCount = 5;
- (void)closeButtonTapped {
[self.popView dismiss];
//
// Close comment view (handled by outside)
// [[NSNotificationCenter defaultCenter]
// postNotificationName:@"KBAICommentViewCloseNotification"
// object:nil];
@@ -766,13 +767,13 @@ static NSInteger const kRepliesLoadCount = 5;
- (UIVisualEffectView *)blurBackgroundView {
if (!_blurBackgroundView) {
// 43pt
// iOS UIBlurEffect API使 dark
// Create blur effect (43pt blur radius in design)
// iOS UIBlurEffect has no direct blur-radius API; use system dark style
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
_blurBackgroundView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
//
// #0000000.31
// Overlay a semi-transparent black layer to tune tone and opacity
// Color: #000000, alpha: 0.31
UIView *darkOverlay = [[UIView alloc] init];
darkOverlay.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.31];
[_blurBackgroundView.contentView addSubview:darkOverlay];
@@ -796,7 +797,7 @@ static NSInteger const kRepliesLoadCount = 5;
_titleLabel = [[UILabel alloc] init];
_titleLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium];
_titleLabel.textColor = [UIColor whiteColor];
_titleLabel.text = @"0条评论";
_titleLabel.text = [NSString stringWithFormat:KBLocalized(@"%ld comments"), (long)0];
}
return _titleLabel;
}
@@ -824,10 +825,10 @@ static NSInteger const kRepliesLoadCount = 5;
_tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
_tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1, 0.01)];
// "暂无数据"
// Disable empty placeholder by default to avoid showing "No data" while loading
_tableView.useEmptyDataSet = NO;
// Header/Cell/Footer
// Register Header/Cell/Footer
[_tableView registerClass:[KBAICommentHeaderView class]
forHeaderFooterViewReuseIdentifier:kCommentHeaderIdentifier];
[_tableView registerClass:[KBAIReplyCell class]
@@ -835,7 +836,7 @@ static NSInteger const kRepliesLoadCount = 5;
[_tableView registerClass:[KBAICommentFooterView class]
forHeaderFooterViewReuseIdentifier:kCommentFooterIdentifier];
//
// Remove top padding
if (@available(iOS 15.0, *)) {
_tableView.sectionHeaderTopPadding = 0;
}
@@ -846,7 +847,7 @@ static NSInteger const kRepliesLoadCount = 5;
- (KBAICommentInputView *)inputView {
if (!_inputView) {
_inputView = [[KBAICommentInputView alloc] init];
_inputView.placeholder = @"Send A Message";
_inputView.placeholder = KBLocalized(@"Send A Message");
_inputView.layer.cornerRadius = 26;
_inputView.clipsToBounds = true;
__weak typeof(self) weakSelf = self;
@@ -865,26 +866,26 @@ static NSInteger const kRepliesLoadCount = 5;
self.replyToReply = reply;
if (reply) {
//
// Reply to a second-level comment
self.inputView.placeholder =
[NSString stringWithFormat:@"回复 @%@", reply.userName];
[NSString stringWithFormat:KBLocalized(@"Reply to @%@"), reply.userName];
} else if (comment) {
//
// Reply to a top-level comment
self.inputView.placeholder =
[NSString stringWithFormat:@"回复 @%@", comment.userName];
[NSString stringWithFormat:KBLocalized(@"Reply to @%@"), comment.userName];
} else {
//
self.inputView.placeholder = @"说点什么...";
// New comment
self.inputView.placeholder = KBLocalized(@"Say something...");
}
//
// Show keyboard
[self.inputView showKeyboard];
}
- (void)clearReplyTarget {
self.replyToComment = nil;
self.replyToReply = nil;
self.inputView.placeholder = @"说点什么...";
self.inputView.placeholder = KBLocalized(@"Say something...");
}
#pragma mark - Send Comment
@@ -899,20 +900,20 @@ static NSInteger const kRepliesLoadCount = 5;
}
if (self.replyToComment) {
//
// Send reply (add second-level comment)
[self sendReplyWithText:text tableWidth:tableWidth];
} else {
//
// Send top-level comment
[self sendNewCommentWithText:text tableWidth:tableWidth];
}
//
// Clear input and reply target
[self.inputView clearText];
[self clearReplyTarget];
}
- (void)sendNewCommentWithText:(NSString *)text tableWidth:(CGFloat)tableWidth {
NSLog(@"[KBAICommentView] 发送一级评论:%@", text);
NSLog(@"[KBAICommentView] Send top-level comment: %@", text);
__weak typeof(self) weakSelf = self;
[self.aiVM addCommentWithCompanionId:self.companionId
@@ -927,11 +928,11 @@ static NSInteger const kRepliesLoadCount = 5;
dispatch_async(dispatch_get_main_queue(), ^{
if (error || code != 0) {
NSLog(@"[KBAICommentView] 发送一级评论失败:%@", error.localizedDescription ?: @"");
NSLog(@"[KBAICommentView] Failed to send top-level comment: %@", error.localizedDescription ?: @"");
return;
}
//
// Insert new comment locally at first position; avoid full reload
KBAICommentModel *localComment =
[strongSelf buildLocalNewCommentWithText:text
serverItem:newItem
@@ -954,29 +955,29 @@ static NSInteger const kRepliesLoadCount = 5;
});
}];
//
// Example code:
// [self.aiVM sendCommentWithCompanionId:self.companionId
// content:text
// completion:^(KBCommentItem *newItem, NSError *error) {
// if (error) {
// NSLog(@"[KBAICommentView] 发送评论失败:%@", error.localizedDescription);
// NSLog(@"[KBAICommentView] Failed to send comment: %@", error.localizedDescription);
// return;
// }
//
// // KBAICommentModel
// // Convert to KBAICommentModel
// KBAICommentModel *comment = [KBAICommentModel mj_objectWithKeyValues:[newItem mj_keyValues]];
// comment.cachedHeaderHeight = [comment calculateHeaderHeightWithMaxWidth:tableWidth];
//
// //
// // Insert into array at index 0
// [self.comments insertObject:comment atIndex:0];
// self.totalCommentCount++;
// [self updateTitle];
//
// // section
// // Insert new section
// [self.tableView insertSections:[NSIndexSet indexSetWithIndex:0]
// withRowAnimation:UITableViewRowAnimationAutomatic];
//
// //
// // Scroll to top
// [self.tableView setContentOffset:CGPointZero animated:YES];
// }];
}
@@ -986,7 +987,7 @@ static NSInteger const kRepliesLoadCount = 5;
if (!comment)
return;
NSLog(@"[KBAICommentView] 回复评论 %@%@", comment.commentId, text);
NSLog(@"[KBAICommentView] Reply to comment %@: %@", comment.commentId, text);
NSInteger root = [comment.commentId integerValue];
NSNumber *rootId = @(root);
@@ -1011,7 +1012,7 @@ static NSInteger const kRepliesLoadCount = 5;
dispatch_async(dispatch_get_main_queue(), ^{
if (error || code != 0) {
NSLog(@"[KBAICommentView] 回复评论失败:%@", error.localizedDescription ?: @"");
NSLog(@"[KBAICommentView] Failed to send reply: %@", error.localizedDescription ?: @"");
return;
}
@@ -1051,7 +1052,7 @@ static NSInteger const kRepliesLoadCount = 5;
[strongSelf.delegate commentView:strongSelf didUpdateTotalCommentCount:strongSelf.totalCommentCount];
}
// displayedReplies loadMoreReplies
// If fully expanded, insert new row directly; otherwise keep displayedReplies as prefix to preserve loadMoreReplies behavior
if (wasFullyExpanded) {
[comment.displayedReplies addObject:localReply];
NSInteger newRowIndex = comment.displayedReplies.count - 1;
@@ -1085,31 +1086,31 @@ static NSInteger const kRepliesLoadCount = 5;
});
}];
//
// Example code:
// NSInteger parentId = [comment.commentId integerValue];
// [self.aiVM replyCommentWithParentId:parentId
// content:text
// completion:^(KBCommentItem *newItem, NSError *error) {
// if (error) {
// NSLog(@"[KBAICommentView] 回复评论失败:%@", error.localizedDescription);
// NSLog(@"[KBAICommentView] Failed to reply comment: %@", error.localizedDescription);
// return;
// }
//
// // KBAIReplyModel
// // Convert to KBAIReplyModel
// KBAIReplyModel *newReply = [KBAIReplyModel mj_objectWithKeyValues:[newItem mj_keyValues]];
// newReply.cachedCellHeight = [newReply calculateCellHeightWithMaxWidth:tableWidth];
//
// // replies
// // Append to replies array
// NSMutableArray *newReplies = [NSMutableArray arrayWithArray:comment.replies];
// [newReplies addObject:newReply];
// comment.replies = newReplies;
// comment.totalReplyCount = newReplies.count;
//
// // section
// // Find section for this comment
// NSInteger section = [self.comments indexOfObject:comment];
// if (section == NSNotFound) return;
//
// // displayedReplies
// // If expanded, append to displayedReplies and insert row
// if (comment.isRepliesExpanded) {
// NSInteger newRowIndex = comment.displayedReplies.count;
// [comment.displayedReplies addObject:newReply];
@@ -1118,18 +1119,18 @@ static NSInteger const kRepliesLoadCount = 5;
// [self.tableView insertRowsAtIndexPaths:@[indexPath]
// withRowAnimation:UITableViewRowAnimationAutomatic];
//
// // Footer
// // Refresh footer
// KBAICommentFooterView *footerView = (KBAICommentFooterView *)[self.tableView footerViewForSection:section];
// if (footerView) {
// [footerView configureWithComment:comment];
// }
//
// //
// // Scroll to new reply
// [self.tableView scrollToRowAtIndexPath:indexPath
// atScrollPosition:UITableViewScrollPositionBottom
// animated:YES];
// } else {
// // Footer
// // If not expanded, refresh footer to show updated reply count
// KBAICommentFooterView *footerView = (KBAICommentFooterView *)[self.tableView footerViewForSection:section];
// if (footerView) {
// [footerView configureWithComment:comment];

View File

@@ -108,8 +108,10 @@
NSFontAttributeName : [UIFont systemFontOfSize:13],
NSForegroundColorAttributeName : [UIColor whiteColor]
};
NSString *replyText =
[NSString stringWithFormat:@" %@ ", KBLocalized(@"Reply")];
[attrName appendAttributedString:[[NSAttributedString alloc]
initWithString:@" 回复 "
initWithString:replyText
attributes:replyAttrs]];
NSDictionary *toUserAttrs = @{
@@ -133,8 +135,9 @@
self.timeLabel.text = [reply formattedTime];
//
NSString *likeText =
reply.likeCount > 0 ? [self formatLikeCount:reply.likeCount] : @"赞";
NSString *likeText = reply.likeCount > 0
? [self formatLikeCount:reply.likeCount]
: KBLocalized(@"Like");
self.likeButton.textLabel.text = likeText;
UIImage *likeImage = reply.liked
@@ -212,7 +215,7 @@
if (!_replyButton) {
_replyButton = [UIButton buttonWithType:UIButtonTypeCustom];
_replyButton.titleLabel.font = [UIFont systemFontOfSize:11];
[_replyButton setTitle:@"回复" forState:UIControlStateNormal];
[_replyButton setTitle:KBLocalized(@"Reply") forState:UIControlStateNormal];
[_replyButton setTitleColor:[UIColor colorWithHex:0x9F9F9F] forState:UIControlStateNormal];
[_replyButton addTarget:self
action:@selector(replyButtonTapped)

View File

@@ -39,8 +39,8 @@
- (void)setup {
_state = KBAiRecordButtonStateNormal;
_normalTitle = @"按住说话";
_recordingTitle = @"松开结束";
_normalTitle = KBLocalized(@"Hold To Speak");
_recordingTitle = KBLocalized(@"Release To Finish");
_tintColor = [UIColor systemBlueColor];
//

View File

@@ -298,7 +298,7 @@
- (UILabel *)statusLabel {
if (!_statusLabel) {
_statusLabel = [[UILabel alloc] init];
_statusLabel.text = @"按住按钮开始对话";
_statusLabel.text = KBLocalized(@"Hold To Start Talking");
_statusLabel.font = [UIFont systemFontOfSize:14];
_statusLabel.textColor = [UIColor secondaryLabelColor];
_statusLabel.textAlignment = NSTextAlignmentCenter;
@@ -310,8 +310,8 @@
if (!_recordButton) {
_recordButton = [[KBAiRecordButton alloc] init];
_recordButton.delegate = self;
_recordButton.normalTitle = @"按住说话";
_recordButton.recordingTitle = @"松开结束";
_recordButton.normalTitle = KBLocalized(@"Hold To Speak");
_recordButton.recordingTitle = KBLocalized(@"Release To Finish");
_recordButton.normalIconImage = [UIImage imageNamed:@"ai_jianpan_icon"];
_recordButton.recordingIconImage = [UIImage imageNamed:@"ai_luyining_icon"];
_recordButton.hidden = YES;
@@ -340,7 +340,7 @@
- (UIButton *)textCenterButton {
if (!_textCenterButton) {
_textCenterButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_textCenterButton setTitle:@"发送一个消息给她" forState:UIControlStateNormal];
[_textCenterButton setTitle:KBLocalized(@"Send A Message To Her") forState:UIControlStateNormal];
_textCenterButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
[_textCenterButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_textCenterButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
@@ -362,7 +362,7 @@
- (UILabel *)voiceCenterLabel {
if (!_voiceCenterLabel) {
_voiceCenterLabel = [[UILabel alloc] init];
_voiceCenterLabel.text = @"按住说话";
_voiceCenterLabel.text = KBLocalized(@"Hold To Speak");
_voiceCenterLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
_voiceCenterLabel.textColor = [UIColor whiteColor];
_voiceCenterLabel.textAlignment = NSTextAlignmentCenter;
@@ -526,9 +526,9 @@
- (void)updateCenterTextIfNeeded {
if (self.inputState == KBVoiceInputBarStateText) {
[self.textCenterButton setTitle:@"发送一个消息给她" forState:UIControlStateNormal];
[self.textCenterButton setTitle:KBLocalized(@"Send A Message To Her") forState:UIControlStateNormal];
} else if (self.inputState == KBVoiceInputBarStateVoice) {
self.voiceCenterLabel.text = @"按住说话";
self.voiceCenterLabel.text = KBLocalized(@"Hold To Speak");
}
}

View File

@@ -936,7 +936,7 @@ static void KBChatUpdatedDarwinCallback(CFNotificationCenterRef center,
- (KBVoiceInputBar *)voiceInputBar {
if (!_voiceInputBar) {
_voiceInputBar = [[KBVoiceInputBar alloc] init];
_voiceInputBar.statusText = @"按住按钮开始对话";
_voiceInputBar.statusText = KBLocalized(@"Hold To Start Talking");
}
return _voiceInputBar;
}
@@ -1372,7 +1372,7 @@ static void KBChatUpdatedDarwinCallback(CFNotificationCenterRef center,
if (cell) {
[cell removeLoadingAssistantMessageWithRequestId:requestId];
}
NSString *message = response.message ?: @"聊天响应为空";
NSString *message = response.message ?: KBLocalized(@"Chat response is empty");
NSLog(@"[KBAIHomeVC] 聊天响应为空:%@", message);
if (message.length > 0) {
[KBHUD showError:message];

View File

@@ -144,7 +144,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
//
if (!self.deleteButton) {
self.deleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.deleteButton setTitle:@"删除此记录" forState:UIControlStateNormal];
[self.deleteButton setTitle:KBLocalized(@"Delete This Record") forState:UIControlStateNormal];
[self.deleteButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
self.deleteButton.titleLabel.font = [UIFont systemFontOfSize:14];
self.deleteButton.backgroundColor = [UIColor colorWithRed:244/255.0 green:67/255.0 blue:54/255.0 alpha:1.0]; // #F44336
@@ -317,7 +317,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
if (error) {
NSLog(@"[KBAIMessageChatingVC] 删除失败:%@", error.localizedDescription);
[KBHUD showError:@"删除失败,请重试"];
[KBHUD showError:KBLocalized(@"Delete failed, please try again")];
return;
}
@@ -346,7 +346,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
NSLog(@"[KBAIMessageChatingVC] ✅ 已发送重置通知companionId=%ld", (long)companionId);
// 5.
[KBHUD showSuccess:@"已删除"];
[KBHUD showSuccess:KBLocalized(@"Deleted")];
});
}];
}

View File

@@ -336,7 +336,7 @@ autoShowBusinessError:NO
//
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSString *message = json[@"message"] ?: KBLocalized(@"Request failed");
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
@@ -356,7 +356,7 @@ autoShowBusinessError:NO
} else {
NSError *parseError = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"数据格式错误"}];
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid data format")}];
if (completion) {
completion(nil, parseError);
}
@@ -398,7 +398,7 @@ autoShowBusinessError:NO
//
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSString *message = json[@"message"] ?: KBLocalized(@"Request failed");
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
@@ -418,7 +418,7 @@ autoShowBusinessError:NO
} else {
NSError *parseError = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"数据格式错误"}];
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid data format")}];
if (completion) {
completion(nil, parseError);
}
@@ -463,7 +463,7 @@ autoShowBusinessError:NO
if (![json isKindOfClass:[NSDictionary class]]) {
NSError *parseError = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey : @"数据格式错误"}];
userInfo:@{NSLocalizedDescriptionKey : KBLocalized(@"Invalid data format")}];
if (completion) {
completion(NO, parseError);
}
@@ -472,7 +472,7 @@ autoShowBusinessError:NO
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSString *message = json[@"message"] ?: KBLocalized(@"Request failed");
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey : message}];
@@ -498,7 +498,7 @@ autoShowBusinessError:NO
if (content.length == 0) {
NSError *error = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"评论内容不能为空"}];
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Comment content cannot be empty")}];
if (completion) {
completion(nil, -1, error);
}
@@ -535,7 +535,7 @@ autoShowBusinessError:NO
NSLog(@"[AiVM] /ai-companion/comment/add response: %@", json);
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSString *message = json[@"message"] ?: KBLocalized(@"Request failed");
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
@@ -596,7 +596,7 @@ autoShowBusinessError:NO
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSString *message = json[@"message"] ?: KBLocalized(@"Request failed");
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
@@ -615,7 +615,7 @@ autoShowBusinessError:NO
} else {
NSError *parseError = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"数据格式错误"}];
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid data format")}];
if (completion) {
completion(nil, parseError);
}
@@ -711,7 +711,7 @@ autoShowBusinessError:NO
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSString *message = json[@"message"] ?: KBLocalized(@"Request failed");
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
@@ -755,7 +755,7 @@ autoShowBusinessError:NO
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSString *message = json[@"message"] ?: KBLocalized(@"Request failed");
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
@@ -838,7 +838,7 @@ autoShowBusinessError:NO
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSString *message = json[@"message"] ?: KBLocalized(@"Request failed");
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
@@ -857,7 +857,7 @@ autoShowBusinessError:NO
} else {
NSError *parseError = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"数据格式错误"}];
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid data format")}];
if (completion) {
completion(nil, parseError);
}
@@ -936,7 +936,7 @@ autoShowBusinessError:NO
if (![json isKindOfClass:[NSDictionary class]]) {
NSError *parseError = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey : @"数据格式错误"}];
userInfo:@{NSLocalizedDescriptionKey : KBLocalized(@"Invalid data format")}];
if (completion) {
completion(NO, parseError);
}
@@ -945,7 +945,7 @@ autoShowBusinessError:NO
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSString *message = json[@"message"] ?: KBLocalized(@"Request failed");
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey : message}];