// // NewHomeViewController.m // AICity // // 新版首页控制器实现 // #import "NewHomeViewController.h" #import "JXBannerView.h" #import "JXHotRecommendView.h" #import "JXAPIService.h" #import "JXShortDramaViewController.h" #import "SearchCommonBarView.h" #import "SearchResultViewController.h" #import #import #import static NSString * const kHomeGridCellIdentifier = @"HomeGridCell"; typedef NS_ENUM(NSInteger, HomeSectionType) { HomeSectionTypeBanner = 0, // Banner 轮播 HomeSectionTypeHotRecommend, // 热门推荐 HomeSectionTypeTodayRecommend // 今日推荐(3列网格) }; #pragma mark - Grid Cell @interface HomeGridCell : UICollectionViewCell @property (nonatomic, strong) UIImageView *coverImageView; @property (nonatomic, strong) UILabel *titleLabel; @end @implementation HomeGridCell - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.coverImageView = [[UIImageView alloc] init]; self.coverImageView.contentMode = UIViewContentModeScaleAspectFill; self.coverImageView.clipsToBounds = YES; self.coverImageView.layer.cornerRadius = 8; [self.contentView addSubview:self.coverImageView]; self.titleLabel = [[UILabel alloc] init]; self.titleLabel.font = [UIFont systemFontOfSize:12]; self.titleLabel.textColor = [UIColor blackColor]; self.titleLabel.numberOfLines = 2; [self.contentView addSubview:self.titleLabel]; [self.coverImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.left.right.equalTo(self.contentView); make.height.equalTo(self.coverImageView.mas_width).multipliedBy(1.5); }]; [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.coverImageView.mas_bottom).offset(4); make.left.right.equalTo(self.contentView); }]; } return self; } @end #pragma mark - New Home ViewController @interface NewHomeViewController () @property (nonatomic, strong) SearchCommonBarView *searchBar; @property (nonatomic, strong) UICollectionView *collectionView; // Data @property (nonatomic, strong) JXHomeHeaderData *headerData; @property (nonatomic, strong) NSMutableArray *todayRecommendList; @property (nonatomic, assign) NSInteger currentPage; // Views @property (nonatomic, strong) JXBannerView *bannerView; @property (nonatomic, strong) JXHotRecommendView *hotRecommendView; @end @implementation NewHomeViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; self.todayRecommendList = [NSMutableArray array]; self.currentPage = 1; [self setupUI]; [self loadHomeData]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.navigationController setNavigationBarHidden:YES animated:animated]; // 启动 Banner 自动滚动 if (self.bannerView) { [self.bannerView startAutoScroll]; } } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // 停止 Banner 自动滚动 if (self.bannerView) { [self.bannerView stopAutoScroll]; } } - (void)setupUI { // 搜索栏 self.searchBar = [[SearchCommonBarView alloc] init]; __weak typeof(self) weakSelf = self; self.searchBar.searchAction = ^(NSString * _Nonnull text) { [weakSelf performSearchWithKeyword:text]; }; [self.view addSubview:self.searchBar]; // CollectionView UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; layout.minimumLineSpacing = 12; layout.minimumInteritemSpacing = 8; layout.sectionInset = UIEdgeInsetsMake(12, 12, 12, 12); self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; self.collectionView.delegate = self; self.collectionView.dataSource = self; self.collectionView.backgroundColor = [UIColor whiteColor]; self.collectionView.alwaysBounceVertical = YES; [self.collectionView registerClass:[HomeGridCell class] forCellWithReuseIdentifier:kHomeGridCellIdentifier]; [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"CustomCell"]; [self.view addSubview:self.collectionView]; // 初始化数据模型 if (!self.headerData) { self.headerData = [[JXHomeHeaderData alloc] init]; self.headerData.banners = @[]; self.headerData.hotMovies = @[]; self.headerData.hotDramas = @[]; NSLog(@"[NewHomeViewController] Initialized empty headerData"); } // Layout [self.searchBar mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop); make.left.right.equalTo(self.view); make.height.mas_equalTo(44); }]; [self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.searchBar.mas_bottom); make.left.right.bottom.equalTo(self.view); }]; // 下拉刷新 self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ [weakSelf refreshData]; }]; // 上拉加载 self.collectionView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{ [weakSelf loadMoreData]; }]; } #pragma mark - Data Loading - (void)loadHomeData { // 加载首页头部数据 NSLog(@"[NewHomeViewController] Starting to load home data"); // 设置加载超时保护(15秒) dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (!self.headerData || self.headerData.banners.count == 0) { NSLog(@"[NewHomeViewController] Home data loading timeout, using empty data"); [self showErrorRecoveryUI:@"首页数据加载超时,显示空内容"]; } }); [[JXAPIService sharedService] getHomeHeaderDataWithSuccess:^(JXHomeHeaderData *headerData) { self.headerData = headerData; NSLog(@"[NewHomeViewController] Header data loaded: banners=%lu, hotMovies=%lu, hotDramas=%lu", (unsigned long)headerData.banners.count, (unsigned long)headerData.hotMovies.count, (unsigned long)headerData.hotDramas.count); [self.collectionView reloadData]; } failure:^(NSError *error) { NSLog(@"[NewHomeViewController] Failed to load header data: %@", error); [self handleLoadingError:error section:@"首页头部数据"]; }]; // 加载今日推荐 [self loadTodayRecommend]; } - (void)loadTodayRecommend { NSLog(@"[NewHomeViewController] Starting to load today recommend, page=%ld", (long)self.currentPage); // 设置加载超时保护(10秒) dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (self.todayRecommendList.count == 0 && self.currentPage == 1) { NSLog(@"[NewHomeViewController] Today recommend loading timeout"); [self showErrorRecoveryUI:@"推荐内容加载超时"]; [self.collectionView.mj_header endRefreshing]; [self.collectionView.mj_footer endRefreshing]; } }); [[JXAPIService sharedService] getDailyRecommendWithPage:self.currentPage pageSize:9 success:^(id responseObject) { if ([responseObject[@"code"] intValue] == 0 || [responseObject[@"code"] intValue] == 200) { NSArray *list = responseObject[@"data"][@"list"]; if (list && [list isKindOfClass:[NSArray class]]) { if (self.currentPage == 1) { [self.todayRecommendList removeAllObjects]; } [self.todayRecommendList addObjectsFromArray:list]; [self.collectionView reloadData]; NSLog(@"[NewHomeViewController] Today recommend loaded: page=%ld, count=%lu", (long)self.currentPage, (unsigned long)list.count); } } [self.collectionView.mj_header endRefreshing]; [self.collectionView.mj_footer endRefreshing]; } failure:^(NSError *error) { NSLog(@"[NewHomeViewController] Failed to load today recommend: %@", error); [self handleLoadingError:error section:@"推荐内容"]; [self.collectionView.mj_header endRefreshing]; [self.collectionView.mj_footer endRefreshing]; }]; } #pragma mark - Error Recovery - (void)handleLoadingError:(NSError *)error section:(NSString *)sectionName { // 记录错误信息 NSLog(@"[NewHomeViewController] Loading error - Section: %@, Error: %@", sectionName, error.localizedDescription); // 区分错误类型 NSString *errorDesc = @"加载失败,请检查网络连接"; if ([error.domain isEqualToString:NSURLErrorDomain]) { switch (error.code) { case NSURLErrorTimedOut: errorDesc = @"加载超时,请重试"; break; case NSURLErrorNotConnectedToInternet: case NSURLErrorNetworkConnectionLost: errorDesc = @"网络连接失败"; break; case NSURLErrorCannotFindHost: errorDesc = @"无法连接到服务器"; break; default: break; } } // 触发降级显示和用户提示 [self showErrorRecoveryUI:errorDesc]; } - (void)showErrorRecoveryUI:(NSString *)errorMessage { // 初始化为空数据(降级显示) if (!self.headerData) { self.headerData = [[JXHomeHeaderData alloc] init]; self.headerData.banners = @[]; self.headerData.hotMovies = @[]; self.headerData.hotDramas = @[]; NSLog(@"[NewHomeViewController] Degradation mode: empty data initialized"); } // 重新加载 UI [self.collectionView reloadData]; // 显示错误提示(弱通知方式,不阻挡 UI) dispatch_async(dispatch_get_main_queue(), ^{ // 可选:显示简短的 toast 或横幅提示 NSLog(@"[NewHomeViewController] Show degradation notice: %@", errorMessage); // 如果有必要可以显示一个小的错误横幅 // [self showErrorBanner:errorMessage]; }); } // 显示错误提示 banner(可选,当需要时取消注释) - (void)showErrorBanner:(NSString *)message { // 创建错误提示 banner UIView *bannerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 44)]; bannerView.backgroundColor = [UIColor colorWithRed:1.0 green:0.3 blue:0.3 alpha:0.9]; bannerView.tag = 9999; UILabel *label = [[UILabel alloc] initWithFrame:CGRectInset(bannerView.bounds, 12, 0)]; label.text = message; label.textColor = [UIColor whiteColor]; label.font = [UIFont systemFontOfSize:14]; label.textAlignment = NSTextAlignmentCenter; label.numberOfLines = 1; [bannerView addSubview:label]; [self.view addSubview:bannerView]; // 3 秒后自动移除 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [UIView animateWithDuration:0.3 animations:^{ bannerView.alpha = 0; } completion:^(BOOL finished) { [bannerView removeFromSuperview]; }]; }); } - (void)refreshData { self.currentPage = 1; [self loadHomeData]; } - (void)loadMoreData { if (self.currentPage >= 10) { [self.collectionView.mj_footer endRefreshingWithNoMoreData]; return; } self.currentPage++; [self loadTodayRecommend]; } #pragma mark - Search - (void)performSearchWithKeyword:(NSString *)keyword { if (keyword.length == 0) { return; } SearchResultViewController *vc = [[SearchResultViewController alloc] init]; vc.txt = keyword; vc.hidesBottomBarWhenPushed = YES; [self.navigationController pushViewController:vc animated:YES]; } #pragma mark - UICollectionViewDataSource - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return 3; // Banner + HotRecommend + TodayRecommend } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { if (section == HomeSectionTypeBanner) { return (self.headerData.banners.count > 0) ? 1 : 0; } else if (section == HomeSectionTypeHotRecommend) { NSArray *hotItems = [self.headerData getAllHotRecommends]; return (hotItems.count > 0) ? 1 : 0; } else { return self.todayRecommendList.count; } } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == HomeSectionTypeTodayRecommend) { // 今日推荐网格 HomeGridCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kHomeGridCellIdentifier forIndexPath:indexPath]; // 防护检查:确保数据存在 if (indexPath.item >= self.todayRecommendList.count) { NSLog(@"[NewHomeViewController] Warning: Index out of bounds for todayRecommendList"); cell.titleLabel.text = @""; return cell; } NSDictionary *item = self.todayRecommendList[indexPath.item]; if (!item || ![item isKindOfClass:[NSDictionary class]]) { NSLog(@"[NewHomeViewController] Warning: Invalid item data at index %ld", (long)indexPath.item); cell.titleLabel.text = @""; return cell; } cell.titleLabel.text = item[@"name"] ?: @""; NSString *coverUrl = item[@"v_cover_url"] ?: item[@"h_cover_url"]; if (coverUrl) { [cell.coverImageView sd_setImageWithURL:[NSURL URLWithString:coverUrl] placeholderImage:nil]; } return cell; } else { // Banner 和 HotRecommend 使用自定义 View UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CustomCell" forIndexPath:indexPath]; // 清除旧的子视图 for (UIView *view in cell.contentView.subviews) { [view removeFromSuperview]; } if (indexPath.section == HomeSectionTypeBanner) { // Banner if (!self.bannerView) { self.bannerView = [[JXBannerView alloc] init]; __weak typeof(self) weakSelf = self; self.bannerView.onBannerClick = ^(JXBannerItem *item) { [weakSelf handleItemClick:item]; }; } // 防护检查 if (self.headerData && self.headerData.banners && self.headerData.banners.count > 0) { [self.bannerView updateBanners:self.headerData.banners]; } else { [self.bannerView updateBanners:@[]]; } [cell.contentView addSubview:self.bannerView]; [self.bannerView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(cell.contentView); }]; } else if (indexPath.section == HomeSectionTypeHotRecommend) { // 热门推荐 if (!self.hotRecommendView) { self.hotRecommendView = [[JXHotRecommendView alloc] init]; __weak typeof(self) weakSelf = self; self.hotRecommendView.onItemClick = ^(JXBannerItem *item) { [weakSelf handleItemClick:item]; }; } // 防护检查 NSArray *hotItems = @[]; if (self.headerData) { NSArray *temp = [self.headerData getAllHotRecommends]; if (temp) { hotItems = temp; } } [self.hotRecommendView updateItems:hotItems]; [cell.contentView addSubview:self.hotRecommendView]; [self.hotRecommendView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(cell.contentView); }]; } return cell; } } #pragma mark - UICollectionViewDelegate - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == HomeSectionTypeTodayRecommend) { // 今日推荐点击 // 防护检查 if (indexPath.item >= self.todayRecommendList.count) { NSLog(@"[NewHomeViewController] Warning: Index out of bounds in didSelectItemAtIndexPath"); return; } NSDictionary *item = self.todayRecommendList[indexPath.item]; if (!item || ![item isKindOfClass:[NSDictionary class]]) { NSLog(@"[NewHomeViewController] Warning: Invalid item data in didSelectItemAtIndexPath"); return; } JXBannerItem *bannerItem = [JXBannerItem modelWithDictionary:item]; if (bannerItem) { [self handleItemClick:bannerItem]; } } } #pragma mark - UICollectionViewDelegateFlowLayout - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { CGFloat width = collectionView.bounds.size.width; if (indexPath.section == HomeSectionTypeBanner) { // Banner: 全宽,16:9 比例 return CGSizeMake(width, width * 9.0 / 16.0); } else if (indexPath.section == HomeSectionTypeHotRecommend) { // 热门推荐: 全宽,固定高度 return CGSizeMake(width, 240); } else { // 今日推荐: 3列网格 CGFloat itemWidth = (width - 12 * 2 - 8 * 2) / 3.0; // 减去左右边距和列间距 return CGSizeMake(itemWidth, itemWidth * 1.7); // 封面 + 标题 } } - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { if (section == HomeSectionTypeBanner || section == HomeSectionTypeHotRecommend) { return UIEdgeInsetsZero; } return UIEdgeInsetsMake(12, 12, 12, 12); } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return 12; } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { if (section == HomeSectionTypeTodayRecommend) { return 8; } return 0; } #pragma mark - Handle Click - (void)handleItemClick:(JXBannerItem *)item { // 判断是短剧还是影片 if ([item.categories containsString:@"短剧"]) { NSLog(@"[NewHomeViewController] 点击短剧: jxDramaId=%ld, name=%@", (long)item.itemId, item.name); // 跳转到短剧播放器 JXShortDramaViewController *vc = [[JXShortDramaViewController alloc] init]; // TODO: 设置初始短剧ID和集数 vc.hidesBottomBarWhenPushed = YES; [self.navigationController pushViewController:vc animated:YES]; } else { NSLog(@"[NewHomeViewController] 点击影片: id=%ld, name=%@", (long)item.itemId, item.name); // TODO: 跳转到普通影片播放器 // MoviePlayActivity.launchMoviePlay(requireContext(), item.itemId) } } @end