From 2b75ad90fbdba6fecab3ee7c11854562ea59ce1e Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Mon, 2 Feb 2026 17:07:46 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=BE=E6=8A=A5=E5=92=8CUI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AiTalk/V/Comment/KBAICommentFooterView.m | 2 +- .../Class/AiTalk/V/Comment/KBAIReplyCell.m | 2 +- keyBoard/Class/AiTalk/VC/AIReportVC.m | 123 ++++++++++++++++-- keyBoard/Class/AiTalk/VM/AiVM.h | 16 +++ keyBoard/Class/AiTalk/VM/AiVM.m | 96 ++++++++++++++ 5 files changed, 228 insertions(+), 11 deletions(-) diff --git a/keyBoard/Class/AiTalk/V/Comment/KBAICommentFooterView.m b/keyBoard/Class/AiTalk/V/Comment/KBAICommentFooterView.m index 6457492..170a239 100644 --- a/keyBoard/Class/AiTalk/V/Comment/KBAICommentFooterView.m +++ b/keyBoard/Class/AiTalk/V/Comment/KBAICommentFooterView.m @@ -103,7 +103,7 @@ if (!_actionButton) { _actionButton = [UIButton buttonWithType:UIButtonTypeCustom]; _actionButton.titleLabel.font = [UIFont systemFontOfSize:13]; - [_actionButton setTitleColor:[UIColor secondaryLabelColor] + [_actionButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; _actionButton.tintColor = [UIColor secondaryLabelColor]; diff --git a/keyBoard/Class/AiTalk/V/Comment/KBAIReplyCell.m b/keyBoard/Class/AiTalk/V/Comment/KBAIReplyCell.m index 14024ea..3ffe6d2 100644 --- a/keyBoard/Class/AiTalk/V/Comment/KBAIReplyCell.m +++ b/keyBoard/Class/AiTalk/V/Comment/KBAIReplyCell.m @@ -213,7 +213,7 @@ _replyButton = [UIButton buttonWithType:UIButtonTypeCustom]; _replyButton.titleLabel.font = [UIFont systemFontOfSize:11]; [_replyButton setTitle:@"回复" forState:UIControlStateNormal]; - [_replyButton setTitleColor:[UIColor secondaryLabelColor] forState:UIControlStateNormal]; + [_replyButton setTitleColor:[UIColor colorWithHex:0x9F9F9F] forState:UIControlStateNormal]; [_replyButton addTarget:self action:@selector(replyButtonTapped) forControlEvents:UIControlEventTouchUpInside]; diff --git a/keyBoard/Class/AiTalk/VC/AIReportVC.m b/keyBoard/Class/AiTalk/VC/AIReportVC.m index e42e070..019751b 100644 --- a/keyBoard/Class/AiTalk/VC/AIReportVC.m +++ b/keyBoard/Class/AiTalk/VC/AIReportVC.m @@ -6,6 +6,7 @@ // #import "AIReportVC.h" +#import "AiVM.h" #pragma mark - AIReportOptionCell @@ -116,6 +117,8 @@ /// 提交按钮 @property (nonatomic, strong) UIButton *submitButton; +@property (nonatomic, strong) AiVM *viewModel; + @end @implementation AIReportVC @@ -134,6 +137,8 @@ [self setupUI]; /// 3:绑定事件 [self bindActions]; + /// 4:其他(通知/键盘等) + [self bindKeyboardNotifications]; } #pragma mark - 1:初始化数据 @@ -286,6 +291,59 @@ [self.view endEditing:YES]; } +#pragma mark - 4:键盘处理 + +- (void)bindKeyboardNotifications { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleKeyboardWillChange:) + name:UIKeyboardWillChangeFrameNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleKeyboardWillHide:) + name:UIKeyboardWillHideNotification + object:nil]; +} + +- (void)handleKeyboardWillChange:(NSNotification *)notification { + NSDictionary *userInfo = notification.userInfo ?: @{}; + CGRect endFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; + NSTimeInterval duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + NSInteger curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; + + CGRect endFrameInView = [self.view convertRect:endFrame fromView:nil]; + CGFloat keyboardHeight = MAX(0, CGRectGetMaxY(self.view.bounds) - CGRectGetMinY(endFrameInView)); + + UIEdgeInsets inset = self.scrollView.contentInset; + inset.bottom = keyboardHeight; + + UIViewAnimationOptions options = (UIViewAnimationOptions)(curve << 16); + [UIView animateWithDuration:duration delay:0 options:options animations:^{ + self.scrollView.contentInset = inset; + self.scrollView.scrollIndicatorInsets = inset; + if ([self.descriptionTextView isFirstResponder]) { + CGRect rect = [self.descriptionTextView convertRect:self.descriptionTextView.bounds toView:self.scrollView]; + rect = CGRectInset(rect, 0, -12); + [self.scrollView scrollRectToVisible:rect animated:NO]; + } + } completion:nil]; +} + +- (void)handleKeyboardWillHide:(NSNotification *)notification { + NSDictionary *userInfo = notification.userInfo ?: @{}; + NSTimeInterval duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + NSInteger curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; + UIViewAnimationOptions options = (UIViewAnimationOptions)(curve << 16); + + [UIView animateWithDuration:duration delay:0 options:options animations:^{ + self.scrollView.contentInset = UIEdgeInsetsZero; + self.scrollView.scrollIndicatorInsets = UIEdgeInsetsZero; + } completion:nil]; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + #pragma mark - UITableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { @@ -351,6 +409,11 @@ #pragma mark - Actions - (void)submitButtonTapped { + if (self.personaId <= 0) { + [KBHUD showError:KBLocalized(@"Invalid parameter")]; + return; + } + if (self.selectedReasonIndexes.count == 0) { [KBHUD showError:KBLocalized(@"Please select at least one report reason")]; return; @@ -373,19 +436,53 @@ [selectedContents addObject:self.contentOptions[index.integerValue]]; } - NSString *description = self.descriptionTextView.text ?: @""; + NSString *reportDesc = [self.descriptionTextView.text ?: @"" stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; NSLog(@"[AIReportVC] 举报人设 ID: %ld", (long)self.personaId); NSLog(@"[AIReportVC] 举报原因: %@", selectedReasons); NSLog(@"[AIReportVC] 内容类型: %@", selectedContents); - NSLog(@"[AIReportVC] 描述: %@", description); - - // TODO: 调用举报接口 - [KBHUD showSuccess:KBLocalized(@"Report submitted")]; - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self.navigationController popViewControllerAnimated:YES]; - }); + NSLog(@"[AIReportVC] 描述: %@", reportDesc); + + // 组装举报类型(从上到下 1-12:举报原因 1-9,内容类型 10-12) + NSMutableArray *reportTypes = [NSMutableArray array]; + NSArray *sortedReasonIndexes = [[self.selectedReasonIndexes allObjects] + sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES]]]; + for (NSNumber *index in sortedReasonIndexes) { + NSInteger type = index.integerValue + 1; + if (type > 0) { + [reportTypes addObject:@(type)]; + } + } + NSArray *sortedContentIndexes = [[self.selectedContentIndexes allObjects] + sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES]]]; + for (NSNumber *index in sortedContentIndexes) { + NSInteger type = self.reportReasons.count + index.integerValue + 1; + if (type > 0) { + [reportTypes addObject:@(type)]; + } + } + + [KBHUD show]; + __weak typeof(self) weakSelf = self; + [self.viewModel reportCompanionWithCompanionId:self.personaId + reportTypes:reportTypes + reportDesc:reportDesc + chatContext:nil + evidenceImageUrl:nil + completion:^(BOOL success, NSError * _Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + [KBHUD dismiss]; + if (!success) { + NSString *msg = error.localizedDescription ?: KBLocalized(@"Network error"); + [KBHUD showError:msg]; + return; + } + [KBHUD showSuccess:KBLocalized(@"Report submitted")]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [weakSelf.navigationController popViewControllerAnimated:YES]; + }); + }); + }]; } #pragma mark - Lazy Load @@ -395,6 +492,7 @@ _scrollView = [[UIScrollView alloc] init]; _scrollView.showsVerticalScrollIndicator = NO; _scrollView.alwaysBounceVertical = YES; + _scrollView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag; } return _scrollView; } @@ -536,4 +634,11 @@ return _submitButton; } +- (AiVM *)viewModel { + if (!_viewModel) { + _viewModel = [[AiVM alloc] init]; + } + return _viewModel; +} + @end diff --git a/keyBoard/Class/AiTalk/VM/AiVM.h b/keyBoard/Class/AiTalk/VM/AiVM.h index 458ce85..291cc85 100644 --- a/keyBoard/Class/AiTalk/VM/AiVM.h +++ b/keyBoard/Class/AiTalk/VM/AiVM.h @@ -173,6 +173,22 @@ typedef void (^AiVMSpeechTranscribeCompletion)(KBAiSpeechTranscribeResponse *_Nu - (void)fetchCompanionDetailWithCompanionId:(NSInteger)companionId completion:(void(^)(KBAICompanionDetailModel * _Nullable detail, NSError * _Nullable error))completion; +#pragma mark - 举报接口 + +/// 举报 AI 角色 +/// @param companionId AI 角色 ID +/// @param reportTypes 举报类型数组(按界面从上到下 1-12) +/// @param reportDesc 详细描述 +/// @param chatContext 聊天上下文快照 JSON 字符串 +/// @param evidenceImageUrl 图片证据 URL +/// @param completion 完成回调 +- (void)reportCompanionWithCompanionId:(NSInteger)companionId + reportTypes:(NSArray *)reportTypes + reportDesc:(nullable NSString *)reportDesc + chatContext:(nullable NSString *)chatContext + evidenceImageUrl:(nullable NSString *)evidenceImageUrl + completion:(void(^)(BOOL success, NSError * _Nullable error))completion; + @end NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/AiTalk/VM/AiVM.m b/keyBoard/Class/AiTalk/VM/AiVM.m index 43ddbb5..4d4157b 100644 --- a/keyBoard/Class/AiTalk/VM/AiVM.m +++ b/keyBoard/Class/AiTalk/VM/AiVM.m @@ -808,4 +808,100 @@ autoShowBusinessError:NO }]; } +#pragma mark - 举报接口 + +- (void)reportCompanionWithCompanionId:(NSInteger)companionId + reportTypes:(NSArray *)reportTypes + reportDesc:(nullable NSString *)reportDesc + chatContext:(nullable NSString *)chatContext + evidenceImageUrl:(nullable NSString *)evidenceImageUrl + completion:(void (^)(BOOL, NSError * _Nullable))completion { + if (companionId <= 0) { + NSError *error = [NSError errorWithDomain:@"AiVM" + code:-1 + userInfo:@{NSLocalizedDescriptionKey : @"invalid companionId"}]; + if (completion) { + completion(NO, error); + } + return; + } + + NSMutableArray *typeList = [NSMutableArray array]; + for (id item in reportTypes) { + if ([item isKindOfClass:[NSNumber class]]) { + [typeList addObject:(NSNumber *)item]; + } else if ([item isKindOfClass:[NSString class]]) { + NSInteger value = [(NSString *)item integerValue]; + [typeList addObject:@(value)]; + } + } + if (typeList.count == 0) { + NSError *error = [NSError errorWithDomain:@"AiVM" + code:-1 + userInfo:@{NSLocalizedDescriptionKey : @"reportTypes is empty"}]; + if (completion) { + completion(NO, error); + } + return; + } + + NSMutableDictionary *params = [NSMutableDictionary dictionary]; + params[@"companionId"] = @(companionId); + params[@"reportTypes"] = [typeList copy]; + if (reportDesc.length > 0) { + params[@"reportDesc"] = reportDesc; + } + if (chatContext.length > 0) { + params[@"chatContext"] = chatContext; + } + if (evidenceImageUrl.length > 0) { + params[@"evidenceImageUrl"] = evidenceImageUrl; + } + + NSLog(@"[AiVM] /ai-companion/report request: %@", params); + [[KBNetworkManager shared] + POST:@"/ai-companion/report" + jsonBody:[params copy] + headers:nil +autoShowBusinessError:NO + completion:^(NSDictionary *_Nullable json, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + NSLog(@"[AiVM] /ai-companion/report failed: %@", error.localizedDescription ?: @""); + if (completion) { + completion(NO, error); + } + return; + } + + NSLog(@"[AiVM] /ai-companion/report response: %@", json); + if (![json isKindOfClass:[NSDictionary class]]) { + NSError *parseError = [NSError errorWithDomain:@"AiVM" + code:-1 + userInfo:@{NSLocalizedDescriptionKey : @"数据格式错误"}]; + if (completion) { + completion(NO, parseError); + } + return; + } + + NSInteger code = [json[@"code"] integerValue]; + if (code != 0) { + NSString *message = json[@"message"] ?: @"请求失败"; + NSError *bizError = [NSError errorWithDomain:@"AiVM" + code:code + userInfo:@{NSLocalizedDescriptionKey : message}]; + if (completion) { + completion(NO, bizError); + } + return; + } + + if (completion) { + completion(YES, nil); + } + }]; +} + @end