JXCommentViewController.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. //
  2. // JXCommentViewController.m
  3. // AICity
  4. //
  5. // Created by TogetherWatch on 2025-10-20.
  6. //
  7. #import "JXCommentViewController.h"
  8. #import "JXAPIService.h"
  9. #import "JXCommentContent.h"
  10. #import "CommentItemCell.h"
  11. #import "CommentRpItemCell.h"
  12. // Cell复用标识符
  13. static NSString * const kCommentCellIdentifier = @"JXCommentCell";
  14. @interface JXCommentViewController () <UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate>
  15. #pragma mark - UI组件
  16. @property (nonatomic, strong) UIView *headerView;
  17. @property (nonatomic, strong) UILabel *titleLabel;
  18. @property (nonatomic, strong) UIButton *closeButton;
  19. @property (nonatomic, strong) UITableView *tableView;
  20. @property (nonatomic, strong) UIRefreshControl *refreshControl;
  21. @property (nonatomic, strong) UIView *inputContainerView;
  22. @property (nonatomic, strong) UITextField *inputTextField;
  23. @property (nonatomic, strong) UIButton *submitButton;
  24. #pragma mark - 数据
  25. @property (nonatomic, copy) NSString *dramaId;
  26. @property (nonatomic, strong) NSMutableArray<JXCommentContent *> *commentList;
  27. @property (nonatomic, assign) NSInteger currentPage;
  28. @property (nonatomic, assign) BOOL isLoading;
  29. @property (nonatomic, assign) BOOL hasMoreData;
  30. // 回复相关
  31. @property (nonatomic, assign) long long replyToCommentId;
  32. @property (nonatomic, copy) NSString *replyToUserName;
  33. @end
  34. @implementation JXCommentViewController
  35. #pragma mark - 初始化
  36. - (instancetype)initWithDramaId:(NSString *)dramaId {
  37. self = [super init];
  38. if (self) {
  39. _dramaId = dramaId;
  40. _currentPage = 1;
  41. _hasMoreData = YES;
  42. _commentList = [NSMutableArray array];
  43. _replyToCommentId = 0;
  44. }
  45. return self;
  46. }
  47. #pragma mark - 生命周期
  48. - (void)viewDidLoad {
  49. [super viewDidLoad];
  50. self.view.backgroundColor = [UIColor whiteColor];
  51. [self setupUI];
  52. [self loadComments];
  53. // 监听键盘
  54. [[NSNotificationCenter defaultCenter] addObserver:self
  55. selector:@selector(keyboardWillShow:)
  56. name:UIKeyboardWillShowNotification
  57. object:nil];
  58. [[NSNotificationCenter defaultCenter] addObserver:self
  59. selector:@selector(keyboardWillHide:)
  60. name:UIKeyboardWillHideNotification
  61. object:nil];
  62. }
  63. - (void)dealloc {
  64. [[NSNotificationCenter defaultCenter] removeObserver:self];
  65. }
  66. #pragma mark - UI设置
  67. - (void)setupUI {
  68. // 顶部标题栏
  69. [self setupHeader];
  70. // 评论列表
  71. [self setupTableView];
  72. // 底部输入框
  73. [self setupInputContainer];
  74. }
  75. - (void)setupHeader {
  76. self.headerView = [[UIView alloc] init];
  77. self.headerView.backgroundColor = [UIColor whiteColor];
  78. [self.view addSubview:self.headerView];
  79. // 标题
  80. self.titleLabel = [[UILabel alloc] init];
  81. self.titleLabel.text = @"全部评论";
  82. self.titleLabel.font = [UIFont boldSystemFontOfSize:15];
  83. self.titleLabel.textColor = [UIColor colorWithRed:1/255.0 green:1/255.0 blue:1/255.0 alpha:1];
  84. [self.headerView addSubview:self.titleLabel];
  85. // 关闭按钮
  86. self.closeButton = [UIButton buttonWithType:UIButtonTypeSystem];
  87. [self.closeButton setImage:[UIImage imageNamed:@"关闭-1"] forState:UIControlStateNormal];
  88. self.closeButton.tintColor = [UIColor grayColor];
  89. [self.closeButton addTarget:self action:@selector(handleClose) forControlEvents:UIControlEventTouchUpInside];
  90. [self.headerView addSubview:self.closeButton];
  91. // 约束
  92. self.headerView.translatesAutoresizingMaskIntoConstraints = NO;
  93. self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
  94. self.closeButton.translatesAutoresizingMaskIntoConstraints = NO;
  95. [NSLayoutConstraint activateConstraints:@[
  96. [self.headerView.topAnchor constraintEqualToAnchor:self.view.topAnchor],
  97. [self.headerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
  98. [self.headerView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
  99. [self.headerView.heightAnchor constraintEqualToConstant:50],
  100. [self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
  101. [self.titleLabel.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor constant:16],
  102. [self.titleLabel.heightAnchor constraintEqualToConstant:50],
  103. [self.titleLabel.widthAnchor constraintEqualToConstant:200],
  104. [self.closeButton.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor constant:-8],
  105. [self.closeButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
  106. [self.closeButton.widthAnchor constraintEqualToConstant:50],
  107. [self.closeButton.heightAnchor constraintEqualToConstant:50]
  108. ]];
  109. }
  110. - (void)setupTableView {
  111. self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
  112. self.tableView.delegate = self;
  113. self.tableView.dataSource = self;
  114. self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
  115. self.tableView.backgroundColor = [UIColor whiteColor];
  116. [self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass(CommentItemCell.class) bundle:nil] forCellReuseIdentifier:NSStringFromClass(CommentItemCell.class)];
  117. [self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass(CommentRpItemCell.class) bundle:nil] forCellReuseIdentifier:NSStringFromClass(CommentRpItemCell.class)];
  118. [self.view addSubview:self.tableView];
  119. // 下拉刷新
  120. self.refreshControl = [[UIRefreshControl alloc] init];
  121. [self.refreshControl addTarget:self action:@selector(handleRefresh) forControlEvents:UIControlEventValueChanged];
  122. [self.tableView addSubview:self.refreshControl];
  123. // 约束
  124. self.tableView.translatesAutoresizingMaskIntoConstraints = NO;
  125. [NSLayoutConstraint activateConstraints:@[
  126. [self.tableView.topAnchor constraintEqualToAnchor:self.headerView.bottomAnchor],
  127. [self.tableView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
  128. [self.tableView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
  129. [self.tableView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:-70]
  130. ]];
  131. }
  132. - (void)setupInputContainer {
  133. self.inputContainerView = [[UIView alloc] init];
  134. self.inputContainerView.backgroundColor = UIColor.whiteColor;
  135. [self.view addSubview:self.inputContainerView];
  136. // 输入框
  137. self.inputTextField = [[UITextField alloc] init];
  138. self.inputTextField.placeholder = @"说点什么...";
  139. self.inputTextField.backgroundColor = RGBACOLOR(241, 242, 244, 1);
  140. self.inputTextField.layer.cornerRadius = 20;
  141. self.inputTextField.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 12, 0)];
  142. self.inputTextField.leftViewMode = UITextFieldViewModeAlways;
  143. self.inputTextField.delegate = self;
  144. [self.inputTextField addTarget:self action:@selector(textFieldDidChange) forControlEvents:UIControlEventEditingChanged];
  145. [self.inputContainerView addSubview:self.inputTextField];
  146. // 约束
  147. self.inputContainerView.translatesAutoresizingMaskIntoConstraints = NO;
  148. self.inputTextField.translatesAutoresizingMaskIntoConstraints = NO;
  149. [NSLayoutConstraint activateConstraints:@[
  150. [self.inputContainerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
  151. [self.inputContainerView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
  152. [self.inputContainerView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:-safebottom],
  153. [self.inputContainerView.heightAnchor constraintEqualToConstant:50],
  154. [self.inputTextField.leadingAnchor constraintEqualToAnchor:self.inputContainerView.leadingAnchor constant:16],
  155. [self.inputTextField.rightAnchor constraintEqualToAnchor:self.inputContainerView.rightAnchor constant:-16],
  156. [self.inputTextField.centerYAnchor constraintEqualToAnchor:self.inputContainerView.centerYAnchor],
  157. [self.inputTextField.heightAnchor constraintEqualToConstant:40],
  158. ]];
  159. }
  160. #pragma mark - 数据加载
  161. - (void)loadComments {
  162. if (self.isLoading) {
  163. return;
  164. }
  165. self.isLoading = YES;
  166. [[JXAPIService sharedService] getCommentsWithDramaId:self.dramaId
  167. page:self.currentPage
  168. pageSize:20
  169. success:^(id response) {
  170. self.isLoading = NO;
  171. [self.refreshControl endRefreshing];
  172. NSDictionary *data = response[@"data"];
  173. NSArray *list = data[@"list"];
  174. long long totalCount = [data[@"total"] longLongValue];
  175. // 更新标题
  176. self.titleLabel.text = [NSString stringWithFormat:@"全部评论"];
  177. if (list.count == 0) {
  178. self.hasMoreData = NO;
  179. return;
  180. }
  181. // 转换为模型
  182. NSMutableArray *models = [NSMutableArray array];
  183. for (NSDictionary *dict in list) {
  184. JXCommentContent *comment = [[JXCommentContent alloc] initWithDictionary:dict];
  185. [models addObject:comment];
  186. }
  187. if (self.currentPage == 1) {
  188. self.commentList = models;
  189. } else {
  190. [self.commentList addObjectsFromArray:models];
  191. }
  192. [self.tableView reloadData];
  193. } failure:^(NSError *error) {
  194. self.isLoading = NO;
  195. [self.refreshControl endRefreshing];
  196. NSLog(@"[JXComment] 加载评论失败: %@", error.localizedDescription);
  197. }];
  198. }
  199. - (void)handleRefresh {
  200. self.currentPage = 1;
  201. self.hasMoreData = YES;
  202. [self loadComments];
  203. }
  204. - (void)loadMoreComments {
  205. if (!self.hasMoreData || self.isLoading) {
  206. return;
  207. }
  208. self.currentPage++;
  209. [self loadComments];
  210. }
  211. #pragma mark - UITableViewDataSource
  212. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
  213. return 5;
  214. }
  215. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  216. // return self.commentList.count;
  217. return 3;
  218. }
  219. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  220. if (indexPath.row == 0) {
  221. CommentItemCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass(CommentItemCell.class) forIndexPath:indexPath];
  222. cell.selectionStyle = UITableViewCellSelectionStyleNone;
  223. return cell;
  224. }
  225. CommentRpItemCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass(CommentRpItemCell.class) forIndexPath:indexPath];
  226. cell.selectionStyle = UITableViewCellSelectionStyleNone;
  227. // JXCommentContent *comment = self.commentList[indexPath.row];
  228. //
  229. // // 临时简单展示(后续可创建自定义Cell)
  230. // cell.textLabel.numberOfLines = 0;
  231. // NSString *likeText = comment.isLiked ? @"❤️" : @"🤍";
  232. // cell.textLabel.text = [NSString stringWithFormat:@"%@ · %@\n%@\n%@ %lld 💬 %lld",
  233. // comment.userName,
  234. // [comment formattedTime],
  235. // comment.content,
  236. // likeText,
  237. // comment.likeCount,
  238. // comment.replyCount];
  239. return cell;
  240. }
  241. #pragma mark - UITableViewDelegate
  242. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  243. return UITableViewAutomaticDimension;
  244. }
  245. - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
  246. return 80;
  247. }
  248. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  249. // [tableView deselectRowAtIndexPath:indexPath animated:YES];
  250. return;
  251. if (indexPath.row == 0) {
  252. JXCommentContent *comment = self.commentList[indexPath.row];
  253. // 显示操作菜单(点赞、回复)
  254. UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
  255. message:nil
  256. preferredStyle:UIAlertControllerStyleActionSheet];
  257. // 点赞
  258. NSString *likeTitle = comment.isLiked ? @"取消点赞" : @"点赞";
  259. [alert addAction:[UIAlertAction actionWithTitle:likeTitle
  260. style:UIAlertActionStyleDefault
  261. handler:^(UIAlertAction *action) {
  262. [self handleLikeComment:comment];
  263. }]];
  264. // 回复
  265. [alert addAction:[UIAlertAction actionWithTitle:@"回复"
  266. style:UIAlertActionStyleDefault
  267. handler:^(UIAlertAction *action) {
  268. [self handleReplyComment:comment];
  269. }]];
  270. [alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]];
  271. [self presentViewController:alert animated:YES completion:nil];
  272. }
  273. }
  274. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  275. // 预加载
  276. CGFloat offsetY = scrollView.contentOffset.y;
  277. CGFloat contentHeight = scrollView.contentSize.height;
  278. CGFloat scrollViewHeight = scrollView.bounds.size.height;
  279. if (offsetY + scrollViewHeight >= contentHeight - 200) {
  280. // [self loadMoreComments];
  281. }
  282. }
  283. #pragma mark - 评论交互
  284. - (void)handleLikeComment:(JXCommentContent *)comment {
  285. [[JXAPIService sharedService] likeCommentWithCommentId:comment.commentId
  286. success:^(id response) {
  287. // 更新UI
  288. BOOL isLiked = [response[@"data"][@"isLiked"] boolValue];
  289. long long likeCount = [response[@"data"][@"likeCount"] longLongValue];
  290. comment.isLiked = isLiked;
  291. comment.likeCount = likeCount;
  292. [self.tableView reloadData];
  293. } failure:^(NSError *error) {
  294. NSLog(@"[JXComment] 点赞评论失败: %@", error.localizedDescription);
  295. }];
  296. }
  297. - (void)handleReplyComment:(JXCommentContent *)comment {
  298. self.replyToCommentId = comment.commentId;
  299. self.replyToUserName = comment.userName;
  300. self.inputTextField.placeholder = [NSString stringWithFormat:@"回复 @%@:", comment.userName];
  301. [self.inputTextField becomeFirstResponder];
  302. }
  303. - (void)textFieldDidChange {
  304. NSString *text = self.inputTextField.text;
  305. self.submitButton.enabled = (text.length > 0);
  306. }
  307. - (void)handleSubmit {
  308. NSString *content = self.inputTextField.text;
  309. if (content.length == 0) {
  310. return;
  311. }
  312. if (self.replyToCommentId > 0) {
  313. // 回复评论
  314. [[JXAPIService sharedService] replyCommentWithCommentId:self.replyToCommentId
  315. content:content
  316. success:^(id response) {
  317. NSLog(@"[JXComment] 回复成功");
  318. [self resetInput];
  319. [self handleRefresh];
  320. } failure:^(NSError *error) {
  321. NSLog(@"[JXComment] 回复失败: %@", error.localizedDescription);
  322. }];
  323. } else {
  324. // 提交新评论
  325. [[JXAPIService sharedService] submitCommentWithDramaId:self.dramaId
  326. content:content
  327. success:^(id response) {
  328. NSLog(@"[JXComment] 评论提交成功");
  329. [self resetInput];
  330. [self handleRefresh];
  331. } failure:^(NSError *error) {
  332. NSLog(@"[JXComment] 评论提交失败: %@", error.localizedDescription);
  333. }];
  334. }
  335. }
  336. - (void)resetInput {
  337. self.inputTextField.text = @"";
  338. self.inputTextField.placeholder = @"说点什么...";
  339. self.submitButton.enabled = NO;
  340. self.replyToCommentId = 0;
  341. self.replyToUserName = nil;
  342. [self.inputTextField resignFirstResponder];
  343. }
  344. #pragma mark - 键盘处理
  345. - (void)keyboardWillShow:(NSNotification *)notification {
  346. NSDictionary *userInfo = notification.userInfo;
  347. CGRect keyboardFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
  348. CGFloat keyboardHeight = keyboardFrame.size.height;
  349. [UIView animateWithDuration:0.3 animations:^{
  350. self.inputContainerView.transform = CGAffineTransformMakeTranslation(0, -keyboardHeight);
  351. self.tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0);
  352. }];
  353. }
  354. - (void)keyboardWillHide:(NSNotification *)notification {
  355. [UIView animateWithDuration:0.3 animations:^{
  356. self.inputContainerView.transform = CGAffineTransformIdentity;
  357. self.tableView.contentInset = UIEdgeInsetsZero;
  358. }];
  359. }
  360. #pragma mark - 关闭
  361. - (void)handleClose {
  362. [self dismissViewControllerAnimated:YES completion:nil];
  363. }
  364. @end