JXFavoriteViewController.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. //
  2. // JXFavoriteViewController.m
  3. // AICity
  4. //
  5. // Created by TogetherWatch on 2025-10-20.
  6. //
  7. #import "JXFavoriteViewController.h"
  8. #import "JXAPIService.h"
  9. #import "JXDrama.h"
  10. // Cell复用标识符
  11. static NSString * const kFavoriteCellIdentifier = @"FavoriteCell";
  12. @interface JXFavoriteViewController () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
  13. #pragma mark - UI组件
  14. @property (nonatomic, strong) UICollectionView *collectionView;
  15. @property (nonatomic, strong) UICollectionViewFlowLayout *flowLayout;
  16. @property (nonatomic, strong) UIRefreshControl *refreshControl;
  17. @property (nonatomic, strong) UIView *emptyView;
  18. @property (nonatomic, strong) UIBarButtonItem *editButton;
  19. #pragma mark - 数据
  20. @property (nonatomic, strong) NSMutableArray<JXDrama *> *favoriteList;
  21. @property (nonatomic, strong) NSMutableSet<NSIndexPath *> *selectedIndexPaths;
  22. #pragma mark - 状态
  23. @property (nonatomic, assign) BOOL isLoading;
  24. @property (nonatomic, assign) BOOL hasMoreData;
  25. @property (nonatomic, assign) NSInteger currentPage;
  26. @property (nonatomic, assign) BOOL isEditMode;
  27. @end
  28. @implementation JXFavoriteViewController
  29. #pragma mark - 初始化
  30. - (instancetype)init {
  31. self = [super init];
  32. if (self) {
  33. _favoriteList = [NSMutableArray array];
  34. _selectedIndexPaths = [NSMutableSet set];
  35. _currentPage = 1;
  36. _hasMoreData = YES;
  37. }
  38. return self;
  39. }
  40. #pragma mark - 生命周期
  41. - (void)viewDidLoad {
  42. [super viewDidLoad];
  43. self.view.backgroundColor = [UIColor whiteColor];
  44. self.title = @"我的收藏";
  45. [self setupUI];
  46. [self loadFavorites];
  47. }
  48. #pragma mark - UI设置
  49. - (void)setupUI {
  50. // 编辑按钮
  51. self.editButton = [[UIBarButtonItem alloc] initWithTitle:@"管理"
  52. style:UIBarButtonItemStylePlain
  53. target:self
  54. action:@selector(handleEdit)];
  55. self.navigationItem.rightBarButtonItem = self.editButton;
  56. // 创建2列网格布局
  57. self.flowLayout = [[UICollectionViewFlowLayout alloc] init];
  58. self.flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
  59. self.flowLayout.minimumLineSpacing = 12;
  60. self.flowLayout.minimumInteritemSpacing = 12;
  61. self.flowLayout.sectionInset = UIEdgeInsetsMake(12, 16, 12, 16);
  62. self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:self.flowLayout];
  63. self.collectionView.backgroundColor = [UIColor whiteColor];
  64. self.collectionView.delegate = self;
  65. self.collectionView.dataSource = self;
  66. self.collectionView.allowsMultipleSelection = YES;
  67. [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:kFavoriteCellIdentifier];
  68. [self.view addSubview:self.collectionView];
  69. // 下拉刷新
  70. self.refreshControl = [[UIRefreshControl alloc] init];
  71. [self.refreshControl addTarget:self action:@selector(handleRefresh) forControlEvents:UIControlEventValueChanged];
  72. [self.collectionView addSubview:self.refreshControl];
  73. // 空状态
  74. [self setupEmptyView];
  75. // 约束
  76. self.collectionView.translatesAutoresizingMaskIntoConstraints = NO;
  77. [NSLayoutConstraint activateConstraints:@[
  78. [self.collectionView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
  79. [self.collectionView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
  80. [self.collectionView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
  81. [self.collectionView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor]
  82. ]];
  83. }
  84. - (void)setupEmptyView {
  85. self.emptyView = [[UIView alloc] init];
  86. self.emptyView.hidden = YES;
  87. [self.view addSubview:self.emptyView];
  88. UILabel *emptyLabel = [[UILabel alloc] init];
  89. emptyLabel.text = @"暂无收藏\n快去收藏喜欢的短剧吧~";
  90. emptyLabel.textColor = [UIColor grayColor];
  91. emptyLabel.font = [UIFont systemFontOfSize:14];
  92. emptyLabel.textAlignment = NSTextAlignmentCenter;
  93. emptyLabel.numberOfLines = 0;
  94. [self.emptyView addSubview:emptyLabel];
  95. self.emptyView.translatesAutoresizingMaskIntoConstraints = NO;
  96. emptyLabel.translatesAutoresizingMaskIntoConstraints = NO;
  97. [NSLayoutConstraint activateConstraints:@[
  98. [self.emptyView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
  99. [self.emptyView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor],
  100. [emptyLabel.centerXAnchor constraintEqualToAnchor:self.emptyView.centerXAnchor],
  101. [emptyLabel.centerYAnchor constraintEqualToAnchor:self.emptyView.centerYAnchor]
  102. ]];
  103. }
  104. #pragma mark - 数据加载
  105. - (void)loadFavorites {
  106. if (self.isLoading) {
  107. return;
  108. }
  109. self.isLoading = YES;
  110. // TODO: 调用API获取收藏列表
  111. // 暂时使用示例数据
  112. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  113. self.isLoading = NO;
  114. [self.refreshControl endRefreshing];
  115. // 示例数据
  116. if (self.currentPage == 1) {
  117. self.favoriteList = [@[] mutableCopy];
  118. }
  119. [self.collectionView reloadData];
  120. self.emptyView.hidden = (self.favoriteList.count > 0);
  121. self.editButton.enabled = (self.favoriteList.count > 0);
  122. });
  123. }
  124. - (void)loadMoreFavorites {
  125. if (!self.hasMoreData || self.isLoading) {
  126. return;
  127. }
  128. self.currentPage++;
  129. [self loadFavorites];
  130. }
  131. - (void)handleRefresh {
  132. self.currentPage = 1;
  133. self.hasMoreData = YES;
  134. [self loadFavorites];
  135. }
  136. #pragma mark - 编辑模式
  137. - (void)handleEdit {
  138. self.isEditMode = !self.isEditMode;
  139. if (self.isEditMode) {
  140. // 进入编辑模式
  141. self.editButton.title = @"取消";
  142. // 显示底部工具栏
  143. [self showBottomToolbar];
  144. } else {
  145. // 退出编辑模式
  146. self.editButton.title = @"管理";
  147. [self.selectedIndexPaths removeAllObjects];
  148. [self.collectionView reloadData];
  149. // 隐藏底部工具栏
  150. [self hideBottomToolbar];
  151. }
  152. }
  153. - (void)showBottomToolbar {
  154. UIToolbar *toolbar = [[UIToolbar alloc] init];
  155. toolbar.frame = CGRectMake(0, self.view.bounds.size.height - 44, self.view.bounds.size.width, 44);
  156. toolbar.tag = 999;
  157. UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
  158. UIBarButtonItem *deleteButton = [[UIBarButtonItem alloc] initWithTitle:@"删除"
  159. style:UIBarButtonItemStylePlain
  160. target:self
  161. action:@selector(handleBatchDelete)];
  162. deleteButton.tintColor = [UIColor systemRedColor];
  163. toolbar.items = @[flexSpace, deleteButton, flexSpace];
  164. [self.view addSubview:toolbar];
  165. }
  166. - (void)hideBottomToolbar {
  167. UIView *toolbar = [self.view viewWithTag:999];
  168. [toolbar removeFromSuperview];
  169. }
  170. - (void)handleBatchDelete {
  171. if (self.selectedIndexPaths.count == 0) {
  172. return;
  173. }
  174. NSString *message = [NSString stringWithFormat:@"确定要取消收藏选中的%lu部短剧吗?", (unsigned long)self.selectedIndexPaths.count];
  175. UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"批量取消收藏"
  176. message:message
  177. preferredStyle:UIAlertControllerStyleAlert];
  178. [alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]];
  179. [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {
  180. [self performBatchDelete];
  181. }]];
  182. [self presentViewController:alert animated:YES completion:nil];
  183. }
  184. - (void)performBatchDelete {
  185. // 按索引降序排序,从后往前删除
  186. NSArray *sortedIndexPaths = [[self.selectedIndexPaths allObjects] sortedArrayUsingComparator:^NSComparisonResult(NSIndexPath *obj1, NSIndexPath *obj2) {
  187. return [@(obj2.item) compare:@(obj1.item)];
  188. }];
  189. for (NSIndexPath *indexPath in sortedIndexPaths) {
  190. if (indexPath.item < self.favoriteList.count) {
  191. [self.favoriteList removeObjectAtIndex:indexPath.item];
  192. }
  193. }
  194. [self.selectedIndexPaths removeAllObjects];
  195. [self.collectionView reloadData];
  196. // 如果列表为空,退出编辑模式
  197. if (self.favoriteList.count == 0) {
  198. [self handleEdit];
  199. self.emptyView.hidden = NO;
  200. }
  201. }
  202. #pragma mark - UICollectionViewDataSource
  203. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
  204. return self.favoriteList.count;
  205. }
  206. - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
  207. UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kFavoriteCellIdentifier forIndexPath:indexPath];
  208. // 清除旧内容
  209. for (UIView *subview in cell.contentView.subviews) {
  210. [subview removeFromSuperview];
  211. }
  212. JXDrama *drama = self.favoriteList[indexPath.item];
  213. // 封面图(占位)
  214. UIView *coverView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, cell.contentView.bounds.size.width, cell.contentView.bounds.size.width * 1.4)];
  215. coverView.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0];
  216. [cell.contentView addSubview:coverView];
  217. // 标题
  218. UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, coverView.frame.size.height + 8, cell.contentView.bounds.size.width, 40)];
  219. titleLabel.text = drama.title;
  220. titleLabel.font = [UIFont systemFontOfSize:14];
  221. titleLabel.numberOfLines = 2;
  222. [cell.contentView addSubview:titleLabel];
  223. // 编辑模式:显示选中状态
  224. if (self.isEditMode) {
  225. BOOL isSelected = [self.selectedIndexPaths containsObject:indexPath];
  226. UIView *checkmark = [[UIView alloc] initWithFrame:CGRectMake(cell.contentView.bounds.size.width - 30, 10, 24, 24)];
  227. checkmark.backgroundColor = isSelected ? [UIColor systemBlueColor] : [UIColor colorWithWhite:0.8 alpha:0.8];
  228. checkmark.layer.cornerRadius = 12;
  229. [cell.contentView addSubview:checkmark];
  230. if (isSelected) {
  231. UILabel *checkLabel = [[UILabel alloc] initWithFrame:checkmark.bounds];
  232. checkLabel.text = @"✓";
  233. checkLabel.textColor = [UIColor whiteColor];
  234. checkLabel.font = [UIFont boldSystemFontOfSize:16];
  235. checkLabel.textAlignment = NSTextAlignmentCenter;
  236. [checkmark addSubview:checkLabel];
  237. }
  238. }
  239. cell.backgroundColor = [UIColor whiteColor];
  240. cell.layer.cornerRadius = 8;
  241. return cell;
  242. }
  243. #pragma mark - UICollectionViewDelegateFlowLayout
  244. - (CGSize)collectionView:(UICollectionView *)collectionView
  245. layout:(UICollectionViewLayout *)collectionViewLayout
  246. sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
  247. CGFloat totalWidth = collectionView.bounds.size.width;
  248. CGFloat spacing = 12;
  249. CGFloat inset = 16;
  250. CGFloat availableWidth = totalWidth - (inset * 2) - spacing;
  251. CGFloat itemWidth = availableWidth / 2;
  252. CGFloat itemHeight = itemWidth * 1.4 + 60;
  253. return CGSizeMake(itemWidth, itemHeight);
  254. }
  255. #pragma mark - UICollectionViewDelegate
  256. - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
  257. if (self.isEditMode) {
  258. // 编辑模式:切换选中状态
  259. if ([self.selectedIndexPaths containsObject:indexPath]) {
  260. [self.selectedIndexPaths removeObject:indexPath];
  261. } else {
  262. [self.selectedIndexPaths addObject:indexPath];
  263. }
  264. [collectionView reloadItemsAtIndexPaths:@[indexPath]];
  265. } else {
  266. // 普通模式:跳转到详情或播放
  267. JXDrama *drama = self.favoriteList[indexPath.item];
  268. NSLog(@"[JXFavorite] 点击短剧: %@", drama.title);
  269. // TODO: 跳转到播放页或详情页
  270. }
  271. }
  272. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  273. // 预加载
  274. CGFloat offsetY = scrollView.contentOffset.y;
  275. CGFloat contentHeight = scrollView.contentSize.height;
  276. CGFloat scrollViewHeight = scrollView.bounds.size.height;
  277. if (offsetY + scrollViewHeight >= contentHeight - 200) {
  278. [self loadMoreFavorites];
  279. }
  280. }
  281. @end