JXShortDramaViewController.m 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858
  1. //
  2. // JXShortDramaViewController.m
  3. // AICity
  4. //
  5. // Created by TogetherWatch on 2025-10-20.
  6. //
  7. #import "JXShortDramaViewController.h"
  8. #import "JXShortDramaCell.h"
  9. #import "JXCommentViewController.h"
  10. #import "JXCollectionViewController.h"
  11. #import "JXAPIService.h"
  12. #import "JXDramaContent.h"
  13. #import "JXCommentContent.h"
  14. #import "JXEpisodeInfo.h"
  15. #import "GuestHelper.h"
  16. #import "JXDetailViewController.h" // Added for detail view
  17. // 复用标识符
  18. static NSString * const kDramaCellIdentifier = @"JXShortDramaCell";
  19. @interface JXShortDramaViewController () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, JXShortDramaCellDelegate, JXCollectionViewControllerDelegate>
  20. #pragma mark - UI组件
  21. @property (nonatomic, strong) UICollectionView *collectionView;
  22. @property (nonatomic, strong) UICollectionViewFlowLayout *flowLayout;
  23. @property (nonatomic, strong) UIRefreshControl *refreshControl;
  24. #pragma mark - 数据
  25. @property (nonatomic, strong) NSMutableArray<JXDramaContent *> *dramaList;
  26. @property (nonatomic, copy) NSString *categoryId;
  27. @property (nonatomic, assign) NSInteger currentPage;
  28. @property (nonatomic, assign) NSInteger currentIndex; // 当前播放的索引
  29. #pragma mark - 状态
  30. @property (nonatomic, assign) BOOL isLoading;
  31. @property (nonatomic, assign) BOOL hasMoreData;
  32. @property (nonatomic, assign) BOOL hasUserVisited; // 用户是否已访问过
  33. @property (nonatomic, assign) BOOL isFirstLoad; // 是否首次加载
  34. @end
  35. @implementation JXShortDramaViewController
  36. #pragma mark - 初始化
  37. - (instancetype)initWithCategoryId:(NSString *)categoryId {
  38. self = [super init];
  39. if (self) {
  40. _categoryId = categoryId;
  41. _currentPage = 1;
  42. _currentIndex = 0;
  43. _hasMoreData = YES;
  44. _hasUserVisited = NO; // 初始化为未访问
  45. _isFirstLoad = YES; // 初始化为首次加载
  46. _dramaList = [NSMutableArray array];
  47. }
  48. return self;
  49. }
  50. - (instancetype)init {
  51. return [self initWithCategoryId:nil];
  52. }
  53. #pragma mark - 生命周期
  54. - (void)viewDidLoad {
  55. [super viewDidLoad];
  56. self.view.backgroundColor = [UIColor blackColor];
  57. [self setupUI];
  58. [self loadInitialData];
  59. }
  60. - (void)viewWillAppear:(BOOL)animated {
  61. [super viewWillAppear:animated];
  62. // 隐藏导航栏(全屏沉浸式)
  63. [self.navigationController setNavigationBarHidden:YES animated:animated];
  64. // 标记用户已访问
  65. self.hasUserVisited = YES;
  66. // 如果是首次加载且有数据,开始播放
  67. if (self.isFirstLoad && self.dramaList.count > 0) {
  68. [self playVideoAtIndex:0];
  69. self.isFirstLoad = NO;
  70. } else {
  71. // 非首次,恢复当前视频播放
  72. [self resumeCurrentVideo];
  73. }
  74. }
  75. - (void)viewWillDisappear:(BOOL)animated {
  76. [super viewWillDisappear:animated];
  77. // 暂停所有视频
  78. [self pauseAllVideos];
  79. }
  80. - (void)dealloc {
  81. NSLog(@"[JXShortDrama] dealloc");
  82. }
  83. #pragma mark - UI设置
  84. - (void)setupUI {
  85. // 创建FlowLayout(垂直分页)
  86. self.flowLayout = [[UICollectionViewFlowLayout alloc] init];
  87. self.flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
  88. self.flowLayout.minimumLineSpacing = 0;
  89. self.flowLayout.minimumInteritemSpacing = 0;
  90. // 创建CollectionView
  91. self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds
  92. collectionViewLayout:self.flowLayout];
  93. self.collectionView.backgroundColor = [UIColor blackColor];
  94. self.collectionView.delegate = self;
  95. self.collectionView.dataSource = self;
  96. self.collectionView.pagingEnabled = YES; // 分页滚动
  97. self.collectionView.showsVerticalScrollIndicator = NO;
  98. self.collectionView.showsHorizontalScrollIndicator = NO;
  99. // 注册Cell
  100. [self.collectionView registerClass:[JXShortDramaCell class]
  101. forCellWithReuseIdentifier:kDramaCellIdentifier];
  102. [self.view addSubview:self.collectionView];
  103. // 添加下拉刷新
  104. self.refreshControl = [[UIRefreshControl alloc] init];
  105. self.refreshControl.tintColor = [UIColor whiteColor];
  106. [self.refreshControl addTarget:self
  107. action:@selector(handleRefresh)
  108. forControlEvents:UIControlEventValueChanged];
  109. [self.collectionView addSubview:self.refreshControl];
  110. }
  111. #pragma mark - 数据加载
  112. - (void)loadInitialData {
  113. self.currentPage = 1;
  114. [self loadDramaListWithPage:1 append:NO];
  115. }
  116. - (void)loadMoreData {
  117. if (self.isLoading || !self.hasMoreData) {
  118. return;
  119. }
  120. self.currentPage++;
  121. [self loadDramaListWithPage:self.currentPage append:YES];
  122. }
  123. - (void)loadDramaListWithPage:(NSInteger)page append:(BOOL)append {
  124. if (self.isLoading) {
  125. return;
  126. }
  127. self.isLoading = YES;
  128. [[JXAPIService sharedService] getDramaListWithCategoryId:self.categoryId
  129. page:page
  130. pageSize:10
  131. success:^(id response) {
  132. self.isLoading = NO;
  133. [self.refreshControl endRefreshing];
  134. NSDictionary *data = response[@"data"];
  135. NSArray *items = data[@"items"]; // 后端返回的字段是 "items" 不是 "list"
  136. if (items.count == 0) {
  137. self.hasMoreData = NO;
  138. return;
  139. }
  140. // 转换为模型
  141. NSMutableArray *models = [NSMutableArray array];
  142. for (NSDictionary *dict in items) {
  143. JXDramaContent *drama = [[JXDramaContent alloc] initWithDictionary:dict];
  144. [models addObject:drama];
  145. }
  146. if (append) {
  147. [self.dramaList addObjectsFromArray:models];
  148. } else {
  149. self.dramaList = models;
  150. }
  151. [self.collectionView reloadData];
  152. [self.collectionView setContentOffset:CGPointMake(0, 0) animated:YES];
  153. // 首次加载后,只有用户已访问才自动播放
  154. if (!append && models.count > 0) {
  155. if (self.hasUserVisited) {
  156. // 获取第一个短剧的详情(包含播放源)
  157. // 注:使用 jx_drama_id(剧星平台ID)而不是本地 dramaId
  158. JXDramaContent *firstDrama = models[0];
  159. [[JXAPIService sharedService] getDramaDetailWithDramaId:[@(firstDrama.dramaId) stringValue]
  160. success:^(id response) {
  161. NSDictionary *data = response[@"data"];
  162. NSDictionary *dramaDict = data[@"drama"];
  163. NSArray *episodes = data[@"episodes"];
  164. if (dramaDict) {
  165. firstDrama.videoUrl = dramaDict[@"video_url"];
  166. // 从第一集中提取播放源(tcplayer字段在episodes中)
  167. if (episodes && episodes.count > 0) {
  168. NSDictionary *firstEpisode = episodes[0];
  169. firstDrama.appId = firstEpisode[@"tcplayer_app_id"];
  170. firstDrama.fileId = firstEpisode[@"tcplayer_file_id"];
  171. firstDrama.psign = firstEpisode[@"tcplayer_sign"] ?: firstEpisode[@"tcplayer_sign_265"] ?: firstEpisode[@"tcplayer_sign_264"];
  172. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  173. [self playVideoAtIndex:0];
  174. });
  175. } else {
  176. // 如果详情接口没返回episodes,尝试单独获取剧集列表
  177. [[JXAPIService sharedService] getEpisodesWithDramaId:[@(firstDrama.dramaId) stringValue]
  178. success:^(id episodesResponse) {
  179. NSArray *episodesList = episodesResponse[@"data"][@"episodes"];
  180. if (episodesList && episodesList.count > 0) {
  181. NSDictionary *firstEp = episodesList[0];
  182. firstDrama.appId = firstEp[@"tcplayer_app_id"];
  183. firstDrama.fileId = firstEp[@"tcplayer_file_id"];
  184. firstDrama.psign = firstEp[@"tcplayer_sign"] ?: firstEp[@"tcplayer_sign_265"] ?: firstEp[@"tcplayer_sign_264"];
  185. }
  186. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  187. [self playVideoAtIndex:0];
  188. });
  189. } failure:^(NSError *error) {
  190. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  191. [self playVideoAtIndex:0];
  192. });
  193. }];
  194. }
  195. }
  196. } failure:^(NSError *error) {
  197. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  198. [self playVideoAtIndex:0];
  199. });
  200. }];
  201. self.isFirstLoad = NO;
  202. }
  203. }
  204. } failure:^(NSError *error) {
  205. self.isLoading = NO;
  206. [self.refreshControl endRefreshing];
  207. NSLog(@"[JXShortDrama] 加载失败: %@", error.localizedDescription);
  208. // TODO: 显示错误提示
  209. }];
  210. }
  211. - (void)handleRefresh {
  212. self.hasMoreData = YES;
  213. [self loadInitialData];
  214. }
  215. #pragma mark - UICollectionViewDataSource
  216. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
  217. return self.dramaList.count;
  218. }
  219. - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
  220. cellForItemAtIndexPath:(NSIndexPath *)indexPath {
  221. JXShortDramaCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kDramaCellIdentifier
  222. forIndexPath:indexPath];
  223. // 设置代理
  224. cell.delegate = self;
  225. NSLog(@"[JXShortDrama] ✅ cellForItemAtIndexPath - Cell delegate 已设置,index: %ld", (long)indexPath.item);
  226. // 配置数据
  227. JXDramaContent *drama = self.dramaList[indexPath.item];
  228. [cell configureWithDrama:drama];
  229. NSLog(@"[JXShortDrama] ✅ cellForItemAtIndexPath - Drama 已配置,dramaId: %lld, title: %@", drama.dramaId, drama.title);
  230. return cell;
  231. }
  232. #pragma mark - UICollectionViewDelegateFlowLayout
  233. - (CGSize)collectionView:(UICollectionView *)collectionView
  234. layout:(UICollectionViewLayout *)collectionViewLayout
  235. sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
  236. // 每个Cell占满全屏
  237. return self.view.bounds.size;
  238. }
  239. #pragma mark - UIScrollViewDelegate
  240. - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
  241. // 计算当前显示的索引
  242. CGFloat pageHeight = scrollView.bounds.size.height;
  243. NSInteger newIndex = (NSInteger)(scrollView.contentOffset.y / pageHeight);
  244. NSLog(@"[JXShortDrama] ========== 滑动结束 ==========");
  245. NSLog(@"[JXShortDrama] 旧索引: %ld, 新索引: %ld", (long)self.currentIndex, (long)newIndex);
  246. if (newIndex != self.currentIndex) {
  247. // 停止旧视频
  248. NSLog(@"[JXShortDrama] 停止旧视频: 索引 %ld", (long)self.currentIndex);
  249. [self stopVideoAtIndex:self.currentIndex];
  250. // 播放新视频
  251. NSLog(@"[JXShortDrama] 播放新视频: 索引 %ld", (long)newIndex);
  252. self.currentIndex = newIndex;
  253. [self playVideoAtIndex:newIndex];
  254. } else {
  255. // 索引没变,说明用户滑动后又回到原位
  256. // 需要恢复播放(之前在scrollViewWillBeginDragging中暂停了)
  257. NSLog(@"[JXShortDrama] 索引未变化,恢复当前视频播放");
  258. [self resumeCurrentVideo];
  259. }
  260. // 预加载逻辑
  261. if (newIndex >= self.dramaList.count - 3 && self.hasMoreData) {
  262. NSLog(@"[JXShortDrama] 触发预加载逻辑");
  263. [self loadMoreData];
  264. }
  265. }
  266. - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
  267. // 用户开始滑动时,暂停当前视频
  268. [self pauseCurrentVideo];
  269. }
  270. #pragma mark - 播放器管理
  271. - (void)playVideoAtIndex:(NSInteger)index {
  272. NSLog(@"[JXShortDrama] playVideoAtIndex:%ld", (long)index);
  273. if (index < 0 || index >= self.dramaList.count) {
  274. NSLog(@"[JXShortDrama] ❌ 索引越界: index=%ld, count=%ld", (long)index, (long)self.dramaList.count);
  275. return;
  276. }
  277. JXDramaContent *drama = self.dramaList[index];
  278. // 检查是否已有播放信息
  279. if ([self hasPlayInfo:drama]) {
  280. // 已有播放信息,直接播放
  281. [self doPlayVideoAtIndex:index];
  282. } else {
  283. // 需要获取播放信息
  284. NSLog(@"[JXShortDrama] 需要获取播放信息: dramaId=%lld", drama.dramaId);
  285. [self loadPlayInfoForDrama:drama atIndex:index];
  286. }
  287. }
  288. - (BOOL)hasPlayInfo:(JXDramaContent *)drama {
  289. // 检查是否有播放所需的信息
  290. if ([drama isFileIdMode]) {
  291. return drama.fileId && drama.appId && drama.psign;
  292. } else if ([drama isUrlMode]) {
  293. return drama.videoUrl;
  294. }
  295. return NO;
  296. }
  297. - (void)doPlayVideoAtIndex:(NSInteger)index {
  298. // 启动播放
  299. JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
  300. if (cell) {
  301. NSLog(@"[JXShortDrama] 找到Cell,开始播放");
  302. [cell startPlay];
  303. } else {
  304. NSLog(@"[JXShortDrama] ❌ 未找到Cell");
  305. }
  306. }
  307. - (void)loadPlayInfoForDrama:(JXDramaContent *)drama atIndex:(NSInteger)index {
  308. // 显示加载指示器
  309. [self showLoadingIndicatorForCellAtIndex:index];
  310. // 获取剧集详情,包含播放信息
  311. [[JXAPIService sharedService] getDramaDetailWithDramaId:[@(drama.dramaId) stringValue]
  312. success:^(id response) {
  313. NSLog(@"[JXShortDrama] 获取到剧集详情响应: %@", response);
  314. NSDictionary *data = response[@"data"];
  315. NSArray *episodes = data[@"episodes"];
  316. NSLog(@"[JXShortDrama] 解析数据:");
  317. NSLog(@"[JXShortDrama] - 完整响应: %@", response);
  318. NSLog(@"[JXShortDrama] - data字典: %@", data);
  319. NSLog(@"[JXShortDrama] - episodes数组: %@", episodes);
  320. NSLog(@"[JXShortDrama] - episodes数量: %lu", (unsigned long)episodes.count);
  321. if (episodes) {
  322. for (int i = 0; i < MIN(2, episodes.count); i++) {
  323. NSDictionary *episode = episodes[i];
  324. NSLog(@"[JXShortDrama] - 第%d集: %@", i+1, episode);
  325. }
  326. }
  327. if (episodes && episodes.count > 0) {
  328. NSDictionary *firstEpisode = episodes[0];
  329. // 提取播放信息
  330. drama.appId = firstEpisode[@"tcplayer_app_id"];
  331. drama.fileId = firstEpisode[@"tcplayer_file_id"];
  332. // 优先使用H.264格式
  333. drama.psign = firstEpisode[@"tcplayer_sign_264"] ?:
  334. firstEpisode[@"tcplayer_sign"] ?:
  335. firstEpisode[@"tcplayer_sign_265"];
  336. // 如果都没有,尝试其他可能的字段名
  337. if (!drama.psign) {
  338. drama.psign = firstEpisode[@"sign"] ?: firstEpisode[@"psign"];
  339. }
  340. // 备用URL模式
  341. drama.videoUrl = firstEpisode[@"video_url"];
  342. NSLog(@"[JXShortDrama] 播放信息已加载: appId=%@, fileId=%@, psign长度=%lu",
  343. drama.appId, drama.fileId, (unsigned long)drama.psign.length);
  344. // 隐藏加载指示器
  345. [self hideLoadingIndicatorForCellAtIndex:index];
  346. // 现在可以播放了
  347. [self doPlayVideoAtIndex:index];
  348. } else {
  349. [self hideLoadingIndicatorForCellAtIndex:index];
  350. NSLog(@"[JXShortDrama] ❌ 获取播放信息失败: 剧集数据为空");
  351. }
  352. } failure:^(NSError *error) {
  353. [self hideLoadingIndicatorForCellAtIndex:index];
  354. NSLog(@"[JXShortDrama] ❌ 获取播放信息失败: %@", error.localizedDescription);
  355. }];
  356. }
  357. - (void)stopVideoAtIndex:(NSInteger)index {
  358. if (index < 0 || index >= self.dramaList.count) {
  359. return;
  360. }
  361. JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
  362. [cell stopPlay];
  363. }
  364. - (void)pauseCurrentVideo {
  365. if (self.currentIndex < 0 || self.currentIndex >= self.dramaList.count) {
  366. return;
  367. }
  368. JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.currentIndex inSection:0]];
  369. [cell pausePlay];
  370. }
  371. - (void)resumeCurrentVideo {
  372. if (self.currentIndex < 0 || self.currentIndex >= self.dramaList.count) {
  373. return;
  374. }
  375. JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.currentIndex inSection:0]];
  376. [cell resumePlay];
  377. }
  378. - (void)pauseAllVideos {
  379. // 暂停所有可见的Cell
  380. for (NSIndexPath *indexPath in self.collectionView.indexPathsForVisibleItems) {
  381. JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
  382. [cell pausePlay];
  383. }
  384. }
  385. #pragma mark - 交互按钮处理
  386. - (void)handleLikeButtonAtIndex:(NSInteger)index {
  387. // 登录检查
  388. BOOL isLoggedIn = [[GuestHelper sharedHelper] checkLoginWithViewController:self action:^{
  389. // 登录成功后重新执行点赞操作
  390. // [self handleLikeButtonAtIndex:index];
  391. }];
  392. if (!isLoggedIn) {
  393. return; // 未登录,等待用户登录
  394. }
  395. JXDramaContent *drama = self.dramaList[index];
  396. // 点赞动画
  397. [self animateLikeButtonAtIndex:index];
  398. [[JXAPIService sharedService] toggleLikeWithDramaId:[@(drama.dramaId) stringValue]
  399. episodeId:drama.episodeId
  400. success:^(id response) {
  401. // 更新UI
  402. BOOL isLiked = [response[@"data"][@"isLiked"] boolValue];
  403. long long likeCount = [response[@"data"][@"likeCount"] longLongValue];
  404. drama.isLiked = isLiked;
  405. drama.likeCount = likeCount;
  406. // 更新Cell UI(直接更新,不刷新列表)
  407. [self updateLikeCountUI:likeCount isLiked:isLiked atIndex:index];
  408. } failure:^(NSError *error) {
  409. NSLog(@"[JXShortDrama] 点赞失败: %@", error.localizedDescription);
  410. }];
  411. }
  412. - (void)handleFavoriteButtonAtIndex:(NSInteger)index {
  413. // 登录检查
  414. BOOL isLoggedIn = [[GuestHelper sharedHelper] checkLoginWithViewController:self action:^{
  415. // 登录成功后重新执行收藏操作
  416. // [self handleFavoriteButtonAtIndex:index];
  417. }];
  418. if (!isLoggedIn) {
  419. return; // 未登录,等待用户登录
  420. }
  421. JXDramaContent *drama = self.dramaList[index];
  422. // 收藏动画
  423. [self animateFavoriteButtonAtIndex:index];
  424. [[JXAPIService sharedService] toggleFavoriteWithDramaId:[@(drama.dramaId) stringValue]
  425. success:^(id response) {
  426. // 更新UI
  427. BOOL isFavorited = [response[@"data"][@"isFavorited"] boolValue];
  428. long long favoriteCount = [response[@"data"][@"favoriteCount"] longLongValue];
  429. drama.isFavorited = isFavorited;
  430. drama.favoriteCount = favoriteCount;
  431. // 更新Cell UI(直接更新,不刷新列表)
  432. [self updateFavoriteCountUI:favoriteCount isFavorited:isFavorited atIndex:index];
  433. } failure:^(NSError *error) {
  434. NSLog(@"[JXShortDrama] 收藏失败: %@", error.localizedDescription);
  435. }];
  436. }
  437. - (void)handleCommentButtonAtIndex:(NSInteger)index {
  438. NSLog(@"[JXShortDrama] ========== 💬 handleCommentButtonAtIndex 开始 ==========");
  439. NSLog(@"[JXShortDrama] 💬 index: %ld", (long)index);
  440. NSLog(@"[JXShortDrama] 💬 dramaList count: %lu", (unsigned long)self.dramaList.count);
  441. // 登录检查
  442. NSLog(@"[JXShortDrama] 💬 开始登录检查...");
  443. BOOL isLoggedIn = [[GuestHelper sharedHelper] checkLoginWithViewController:self action:^{
  444. NSLog(@"[JXShortDrama] 💬 登录完成,重新调用 handleCommentButtonAtIndex");
  445. // 登录成功后重新打开评论
  446. }];
  447. NSLog(@"[JXShortDrama] 💬 登录检查结果: %@", isLoggedIn ? @"已登录 ✅" : @"未登录,等待登录");
  448. if (!isLoggedIn) {
  449. NSLog(@"[JXShortDrama] 💬 用户未登录,等待登录完成后再处理");
  450. return; // 未登录,等待用户登录
  451. }
  452. NSLog(@"[JXShortDrama] 💬 用户已登录,获取 drama 数据");
  453. if (index < 0 || index >= self.dramaList.count) {
  454. NSLog(@"[JXShortDrama] ❌ 索引越界,index: %ld, count: %lu", (long)index, (unsigned long)self.dramaList.count);
  455. return;
  456. }
  457. JXDramaContent *drama = self.dramaList[index];
  458. NSLog(@"[JXShortDrama] 💬 drama: %@, dramaId: %lld", drama.title, drama.dramaId);
  459. // 显示评论弹窗
  460. NSLog(@"[JXShortDrama] 💬 显示评论弹窗...");
  461. [self showCommentDialogWithDramaId:[@(drama.dramaId) stringValue]];
  462. NSLog(@"[JXShortDrama] 💬 评论弹窗已显示");
  463. }
  464. - (void)handleCollectionButtonAtIndex:(NSInteger)index {
  465. JXDramaContent *drama = self.dramaList[index];
  466. // 显示合集弹窗
  467. [self showCollectionDialogWithDramaId:[@(drama.dramaId) stringValue]];
  468. }
  469. #pragma mark - JXShortDramaCellDelegate
  470. - (void)shortDramaCellDidTapLike:(UICollectionViewCell *)cell {
  471. NSLog(@"[JXShortDramaViewController] 📍 shortDramaCellDidTapLike 被调用");
  472. NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
  473. NSLog(@"[JXShortDramaViewController] indexPath: %@", indexPath);
  474. if (indexPath) {
  475. [self handleLikeButtonAtIndex:indexPath.item];
  476. } else {
  477. NSLog(@"[JXShortDramaViewController] ❌ 无法获取 indexPath");
  478. }
  479. }
  480. - (void)shortDramaCellDidTapFavorite:(UICollectionViewCell *)cell {
  481. NSLog(@"[JXShortDramaViewController] 📍 shortDramaCellDidTapFavorite 被调用");
  482. NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
  483. NSLog(@"[JXShortDramaViewController] indexPath: %@", indexPath);
  484. if (indexPath) {
  485. [self handleFavoriteButtonAtIndex:indexPath.item];
  486. } else {
  487. NSLog(@"[JXShortDramaViewController] ❌ 无法获取 indexPath");
  488. }
  489. }
  490. - (void)shortDramaCellDidTapComment:(UICollectionViewCell *)cell {
  491. NSLog(@"[JXShortDramaViewController] ========== 📍 shortDramaCellDidTapComment 被调用 ==========");
  492. NSLog(@"[JXShortDramaViewController] 📍 cell: %@", cell);
  493. NSLog(@"[JXShortDramaViewController] 📍 cell class: %@", [cell class]);
  494. NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
  495. NSLog(@"[JXShortDramaViewController] 📍 indexPath: %@", indexPath);
  496. if (indexPath) {
  497. NSLog(@"[JXShortDramaViewController] 📍 indexPath.item: %ld", (long)indexPath.item);
  498. [self handleCommentButtonAtIndex:indexPath.item];
  499. } else {
  500. NSLog(@"[JXShortDramaViewController] ❌ 无法获取 indexPath");
  501. }
  502. }
  503. - (void)shortDramaCellDidTapCollection:(UICollectionViewCell *)cell {
  504. NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
  505. if (indexPath) {
  506. [self handleCollectionButtonAtIndex:indexPath.item];
  507. }
  508. }
  509. - (void)shortDramaCellDidTapDetail:(UICollectionViewCell *)cell {
  510. NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
  511. if (indexPath) {
  512. JXDramaContent *drama = self.dramaList[indexPath.item];
  513. NSLog(@"🎬 点击查看全部/详情,dramaId: %lld", drama.dramaId);
  514. // 跳转到详情页
  515. JXDetailViewController *detailVC = [[JXDetailViewController alloc] initWithDramaId:[@(drama.dramaId) stringValue]];
  516. [self.navigationController pushViewController:detailVC animated:YES];
  517. }
  518. }
  519. // 加载指示器管理
  520. - (void)showLoadingIndicatorForCellAtIndex:(NSInteger)index {
  521. NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
  522. JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
  523. if (cell) {
  524. // 在Cell上显示加载指示器
  525. UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
  526. indicator.center = CGPointMake(cell.bounds.size.width / 2, cell.bounds.size.height / 2);
  527. indicator.tag = 999; // 用于标识和移除
  528. [cell.contentView addSubview:indicator];
  529. [indicator startAnimating];
  530. // 添加半透明背景
  531. UIView *backgroundView = [[UIView alloc] initWithFrame:cell.bounds];
  532. backgroundView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
  533. backgroundView.tag = 998;
  534. [cell.contentView insertSubview:backgroundView belowSubview:indicator];
  535. }
  536. }
  537. - (void)hideLoadingIndicatorForCellAtIndex:(NSInteger)index {
  538. NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
  539. JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
  540. if (cell) {
  541. // 移除加载指示器
  542. UIActivityIndicatorView *indicator = (UIActivityIndicatorView *)[cell.contentView viewWithTag:999];
  543. [indicator removeFromSuperview];
  544. // 移除背景
  545. UIView *backgroundView = (UIView *)[cell.contentView viewWithTag:998];
  546. [backgroundView removeFromSuperview];
  547. }
  548. }
  549. - (void)shortDramaCellDidFailToPlay:(UICollectionViewCell *)cell {
  550. NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
  551. if (indexPath) {
  552. JXDramaContent *drama = self.dramaList[indexPath.item];
  553. NSLog(@"[JXShortDrama] 播放失败,重新获取剧集数据: dramaId=%lld", drama.dramaId);
  554. // 显示加载指示器
  555. [self showLoadingIndicatorForCellAtIndex:indexPath.item];
  556. // 重新获取剧集详情,确保获取最新的播放签名
  557. [[JXAPIService sharedService] getDramaDetailWithDramaId:[@(drama.dramaId) stringValue]
  558. success:^(id response) {
  559. NSDictionary *data = response[@"data"];
  560. NSArray *episodes = data[@"episodes"];
  561. if (episodes && episodes.count > 0) {
  562. NSDictionary *firstEpisode = episodes[0];
  563. // 更新drama对象的播放数据
  564. drama.appId = firstEpisode[@"tcplayer_app_id"];
  565. drama.fileId = firstEpisode[@"tcplayer_file_id"];
  566. // 优先使用H.264格式(更兼容)
  567. drama.psign = firstEpisode[@"tcplayer_sign_264"] ?:
  568. firstEpisode[@"tcplayer_sign"] ?:
  569. firstEpisode[@"tcplayer_sign_265"];
  570. NSLog(@"[JXShortDrama] 剧集数据已刷新,重新尝试播放: appId=%@, fileId=%@",
  571. drama.appId, drama.fileId);
  572. // 隐藏加载指示器
  573. [self hideLoadingIndicatorForCellAtIndex:indexPath.item];
  574. // 重新尝试播放
  575. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  576. [self playVideoAtIndex:indexPath.item];
  577. });
  578. } else {
  579. [self hideLoadingIndicatorForCellAtIndex:indexPath.item];
  580. NSLog(@"[JXShortDrama] 刷新后无可用剧集数据");
  581. }
  582. } failure:^(NSError *error) {
  583. [self hideLoadingIndicatorForCellAtIndex:indexPath.item];
  584. NSLog(@"[JXShortDrama] 刷新剧集数据失败: %@", error.localizedDescription);
  585. }];
  586. }
  587. }
  588. #pragma mark - 弹窗显示
  589. - (void)showCommentDialogWithDramaId:(NSString *)dramaId {
  590. NSLog(@"[JXShortDrama] ========== 🗨️ showCommentDialogWithDramaId 开始 ==========");
  591. NSLog(@"[JXShortDrama] 🗨️ dramaId: %@", dramaId);
  592. NSLog(@"[JXShortDrama] 🗨️ 创建 JXCommentViewController...");
  593. JXCommentViewController *commentVC = [[JXCommentViewController alloc] initWithDramaId:dramaId];
  594. NSLog(@"[JXShortDrama] 🗨️ commentVC 已创建: %@", commentVC);
  595. // 设置弹窗样式(底部弹出)
  596. if (@available(iOS 15.0, *)) {
  597. NSLog(@"[JXShortDrama] 🗨️ 配置 iOS 15+ 的 Sheet 弹窗样式");
  598. UISheetPresentationController *sheet = commentVC.sheetPresentationController;
  599. sheet.detents = @[
  600. [UISheetPresentationControllerDetent mediumDetent],
  601. [UISheetPresentationControllerDetent largeDetent]
  602. ];
  603. sheet.prefersGrabberVisible = YES;
  604. NSLog(@"[JXShortDrama] 🗨️ Sheet 样式配置完成");
  605. } else {
  606. NSLog(@"[JXShortDrama] 🗨️ iOS 版本 < 15.0,跳过 Sheet 样式配置");
  607. }
  608. NSLog(@"[JXShortDrama] 🗨️ 正在 present commentVC...");
  609. [self presentViewController:commentVC animated:YES completion:^{
  610. NSLog(@"[JXShortDrama] 🗨️ commentVC present 完成!");
  611. }];
  612. }
  613. - (void)showCollectionDialogWithDramaId:(NSString *)dramaId {
  614. JXCollectionViewController *collectionVC = [[JXCollectionViewController alloc] initWithDramaId:dramaId];
  615. collectionVC.delegate = self;
  616. // 设置弹窗样式(底部弹出)
  617. if (@available(iOS 15.0, *)) {
  618. UISheetPresentationController *sheet = collectionVC.sheetPresentationController;
  619. sheet.detents = @[
  620. [UISheetPresentationControllerDetent mediumDetent],
  621. [UISheetPresentationControllerDetent largeDetent]
  622. ];
  623. sheet.prefersGrabberVisible = YES;
  624. }
  625. [self presentViewController:collectionVC animated:YES completion:nil];
  626. }
  627. #pragma mark - JXCollectionViewControllerDelegate
  628. - (void)collectionViewControllerDidSelectEpisode:(NSString *)episodeId {
  629. NSLog(@"[JXShortDrama] 选择播放剧集: %@", episodeId);
  630. JXDramaContent *drama = self.dramaList[self.currentIndex];
  631. JXDetailViewController *detailVC = [[JXDetailViewController alloc] initWithDramaId:[NSString stringWithFormat:@"%lld",drama.dramaId]];
  632. detailVC.playIndex = (episodeId.intValue-1 < 0) ? 0 : episodeId.intValue-1;
  633. [self.navigationController pushViewController:detailVC animated:YES];
  634. // JXDetailViewController *detailVC = [[JXDetailViewController alloc] initWithDramaId:episodeId];
  635. // [self.navigationController pushViewController:detailVC animated:YES];
  636. // TODO: 切换到选定的剧集播放
  637. // 可以找到对应的短剧,然后滚动到该位置并播放
  638. }
  639. #pragma mark - 动画效果
  640. - (void)animateLikeButtonAtIndex:(NSInteger)index {
  641. NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
  642. JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
  643. if (cell && [cell respondsToSelector:@selector(likeButton)]) {
  644. UIButton *likeButton = [cell valueForKey:@"likeButton"];
  645. if (likeButton) {
  646. // 缩放动画:放大 -> 恢复
  647. [UIView animateWithDuration:0.15 animations:^{
  648. likeButton.transform = CGAffineTransformMakeScale(1.3, 1.3);
  649. } completion:^(BOOL finished) {
  650. [UIView animateWithDuration:0.15 animations:^{
  651. likeButton.transform = CGAffineTransformIdentity;
  652. }];
  653. }];
  654. }
  655. }
  656. }
  657. - (void)animateFavoriteButtonAtIndex:(NSInteger)index {
  658. NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
  659. JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
  660. if (cell && [cell respondsToSelector:@selector(favoriteButton)]) {
  661. UIButton *favoriteButton = [cell valueForKey:@"favoriteButton"];
  662. if (favoriteButton) {
  663. // 缩放动画:放大 -> 恢复
  664. [UIView animateWithDuration:0.15 animations:^{
  665. favoriteButton.transform = CGAffineTransformMakeScale(1.3, 1.3);
  666. } completion:^(BOOL finished) {
  667. [UIView animateWithDuration:0.15 animations:^{
  668. favoriteButton.transform = CGAffineTransformIdentity;
  669. }];
  670. }];
  671. }
  672. }
  673. }
  674. #pragma mark - UI 更新(避免视频停止)
  675. - (void)updateLikeCountUI:(NSInteger)likeCount isLiked:(BOOL)isLiked atIndex:(NSInteger)index {
  676. NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
  677. JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
  678. if (cell && [cell respondsToSelector:@selector(updateLikeCount:isLiked:)]) {
  679. [cell updateLikeCount:likeCount isLiked:isLiked];
  680. }
  681. }
  682. - (void)updateFavoriteCountUI:(NSInteger)favoriteCount isFavorited:(BOOL)isFavorited atIndex:(NSInteger)index {
  683. NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
  684. JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
  685. if (cell && [cell respondsToSelector:@selector(updateFavoriteCount:isFavorited:)]) {
  686. [cell updateFavoriteCount:favoriteCount isFavorited:isFavorited];
  687. }
  688. }
  689. @end