| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- //
- // 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 <Masonry/Masonry.h>
- #import <MJRefresh/MJRefresh.h>
- #import <SDWebImage/SDWebImage.h>
- 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 () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
- @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
|