// // KBShopItemVC.m // keyBoard // // Created by Mac on 2025/11/9. // #import "KBShopItemVC.h" #import #import #import "KBSkinCardCell.h" #import "KBSkinInstallBridge.h" #import "KBHUD.h" #import "KBShopVM.h" @interface KBShopItemVC () @property (nonatomic, copy) void(^scrollCallback)(UIScrollView *scrollView); @property (nonatomic, strong) KBShopVM *internalViewModel; @property (nonatomic, assign, getter=isLoading) BOOL loading; @end @implementation KBShopItemVC - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor clearColor]; self.dataSource = [NSMutableArray array]; // 懒加载 collectionView,并添加到视图 [self.view addSubview:self.collectionView]; [self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self.view); // mas 布局:铺满 }]; KBWeakSelf if (self.isNeedHeader) { self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ [weakSelf fetchThemes]; }]; } if (self.isNeedFooter) { self.collectionView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{ [weakSelf fetchThemes]; }]; } self.collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; [self beginFirstRefresh]; } #pragma mark - 刷新控制 // 首次进入自动触发一次刷新(如果需要) - (void)beginFirstRefresh { if (self.isHeaderRefreshed || self.isLoading) { return; } if (!self.style.styleId.length) { return; } [self beginRefreshImmediately]; } - (void)beginRefreshImmediately { if (self.isNeedHeader && !self.collectionView.mj_header.isRefreshing) { [self.collectionView.mj_header beginRefreshing]; } [self fetchThemes]; } - (void)fetchThemes { if (self.isLoading) { return; } NSString *styleId = self.style.styleId; if (!styleId.length) { return; } self.loading = YES; KBShopVM *vm = self.shopViewModel; if (!vm) { if (!self.internalViewModel) { self.internalViewModel = [[KBShopVM alloc] init]; } vm = self.internalViewModel; } KBWeakSelf [vm fetchThemesForStyleId:styleId completion:^(NSArray * _Nullable themes, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ weakSelf.loading = NO; weakSelf.isHeaderRefreshed = YES; if (error) { NSString *msg = error.localizedDescription ?: KBLocalized(@"Network error"); [KBHUD showInfo:msg]; } else { weakSelf.dataSource = themes.count ? themes.mutableCopy : [NSMutableArray array]; [weakSelf.collectionView reloadData]; } if ([weakSelf.collectionView.mj_header isRefreshing]) { [weakSelf.collectionView.mj_header endRefreshing]; } if ([weakSelf.collectionView.mj_footer isRefreshing]) { [weakSelf.collectionView.mj_footer endRefreshing]; } }); }]; } #pragma mark - UICollectionView DataSource - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { if (!self.isHeaderRefreshed) return 0; return self.dataSource.count; } - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { KBSkinCardCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"KBSkinCardCell" forIndexPath:indexPath]; KBShopThemeModel *theme = (indexPath.item < self.dataSource.count) ? self.dataSource[indexPath.item] : nil; NSString *title = theme.themeName.length ? theme.themeName : KBLocalized(@"Themes"); NSString *price = [self kb_priceStringForTheme:theme]; [cell configWithTitle:title imageURL:nil price:price]; return cell; } #pragma mark - UICollectionView DelegateFlowLayout - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { // 两列布局:左右 16 间距,中间列间距 12 CGFloat insetLR = 16.0; CGFloat spacing = 12.0; CGFloat contentW = collectionView.bounds.size.width - insetLR * 2; CGFloat itemW = floor((contentW - spacing) / 2.0); // CGFloat itemH = itemW * 0.75 + 56; // KBSkinCardCell 内部高度估算 return CGSizeMake(itemW, KBFit(197)); } - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { return UIEdgeInsetsMake(12, 16, 12, 16); } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return 12.0; } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { return 12.0; } - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{ [self kb_handleShopTapAtIndexPath:indexPath]; } - (NSString *)kb_priceStringForTheme:(KBShopThemeModel *)theme { if (!theme) { return @""; } if (theme.themePrice <= 0.0f) { return KBLocalized(@"Free"); } static NSNumberFormatter *formatter; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ formatter = [[NSNumberFormatter alloc] init]; formatter.minimumFractionDigits = 0; formatter.maximumFractionDigits = 2; formatter.minimumIntegerDigits = 1; }); NSString *priceString = [formatter stringFromNumber:@(theme.themePrice)]; return priceString ?: [NSString stringWithFormat:@"%.2f", theme.themePrice]; } - (void)kb_handleShopTapAtIndexPath:(NSIndexPath *)indexPath { KBShopThemeModel *theme = (indexPath.item < self.dataSource.count) ? self.dataSource[indexPath.item] : nil; NSString *title = theme.themeName.length ? theme.themeName : KBLocalized(@"专属皮肤002"); // 将需求固定到 002.zip,本地写死皮肤 id,便于键盘扩展识别并解压。 static NSString * const kKBBundleSkinId002 = @"bundle_skin_fense"; [KBSkinInstallBridge publishBundleSkinRequestWithId:kKBBundleSkinId002 name:title ?: kKBBundleSkinId002 zipName:@"fense.zip" iconShortNames:nil]; [KBHUD showInfo:KBLocalized(@"已通知键盘解压,切换到自定义键盘即可生效")]; } - (void)setStyle:(KBShopStyleModel *)style { _style = style; if (self.isViewLoaded && !self.isHeaderRefreshed) { [self beginFirstRefresh]; } } #pragma mark - UIScrollView Delegate(转发给分页容器) - (void)scrollViewDidScroll:(UIScrollView *)scrollView { !self.scrollCallback ?: self.scrollCallback(scrollView); } #pragma mark - JXPagingViewListViewDelegate - (UIView *)listView { return self.view; } - (UIScrollView *)listScrollView { return self.collectionView; } - (void)listViewDidScrollCallback:(void (^)(UIScrollView *))callback { self.scrollCallback = callback; } - (void)listWillAppear { NSLog(@"%@:%@", self.title, NSStringFromSelector(_cmd)); } - (void)listDidAppear { NSLog(@"%@:%@", self.title, NSStringFromSelector(_cmd)); } - (void)listWillDisappear { NSLog(@"%@:%@", self.title, NSStringFromSelector(_cmd)); } - (void)listDidDisappear { NSLog(@"%@:%@", self.title, NSStringFromSelector(_cmd)); } #pragma mark - Lazy - (UICollectionView *)collectionView { if (!_collectionView) { UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; layout.scrollDirection = UICollectionViewScrollDirectionVertical; _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; _collectionView.backgroundColor = [UIColor clearColor]; _collectionView.dataSource = self; _collectionView.delegate = self; // 注册皮肤卡片 cell [_collectionView registerClass:KBSkinCardCell.class forCellWithReuseIdentifier:@"KBSkinCardCell"]; // 复用标识 } return _collectionView; } @end