JXShortDramaViewController.m 34 KB

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