JXShortDramaViewController.m 34 KB

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