diff --git a/Shared/KBAPI.h b/Shared/KBAPI.h index ce98b77..f16fd0a 100644 --- a/Shared/KBAPI.h +++ b/Shared/KBAPI.h @@ -45,6 +45,10 @@ #define API_CHARACTER_DEL_USER_CHARACTER @"/character/delUserCharacter" // 删除用户人设 #define API_CHARACTER_ADD_USER_CHARACTER @"/character/addUserCharacter" // 添加用户人设(假定路径,如有不同请按后端实际修改) +/// Theme shop +#define API_THEME_LIST_ALL_STYLES @"/themes/listAllStyles" // 查询所有主题风格 +#define API_THEME_LIST_BY_STYLE @"/themes/listByStyle" // 按风格查询主题列表 + /// pay #define API_VALIDATE_RECEIPT @"/api/apple/validate-receipt" // 排行榜标签列表 diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 310cd4f..530f897 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -116,6 +116,9 @@ 0498BD8F2EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */; }; 0498BD902EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */; }; 0498BDDA2EE7ECEA006CC1D5 /* WJXEventSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDD82EE7ECEA006CC1D5 /* WJXEventSource.m */; }; + 0498BDDE2EE81508006CC1D5 /* KBShopVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDDD2EE81508006CC1D5 /* KBShopVM.m */; }; + 0498BDE12EEA87C9006CC1D5 /* KBShopStyleModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDE02EEA87C8006CC1D5 /* KBShopStyleModel.m */; }; + 0498BDE42EEA885D006CC1D5 /* KBShopThemeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDE32EEA885D006CC1D5 /* KBShopThemeModel.m */; }; 049FB20B2EC1C13800FAB05D /* KBSkinBottomActionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB20A2EC1C13800FAB05D /* KBSkinBottomActionView.m */; }; 049FB20E2EC1CD2800FAB05D /* KBAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB20D2EC1CD2800FAB05D /* KBAlert.m */; }; 049FB2112EC1F72F00FAB05D /* KBMyListCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2102EC1F72F00FAB05D /* KBMyListCell.m */; }; @@ -408,6 +411,12 @@ 0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyMainModel.m; sourceTree = ""; }; 0498BDD72EE7ECEA006CC1D5 /* WJXEventSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WJXEventSource.h; sourceTree = ""; }; 0498BDD82EE7ECEA006CC1D5 /* WJXEventSource.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WJXEventSource.m; sourceTree = ""; }; + 0498BDDC2EE81508006CC1D5 /* KBShopVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBShopVM.h; sourceTree = ""; }; + 0498BDDD2EE81508006CC1D5 /* KBShopVM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBShopVM.m; sourceTree = ""; }; + 0498BDDF2EEA87C8006CC1D5 /* KBShopStyleModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBShopStyleModel.h; sourceTree = ""; }; + 0498BDE02EEA87C8006CC1D5 /* KBShopStyleModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBShopStyleModel.m; sourceTree = ""; }; + 0498BDE22EEA885D006CC1D5 /* KBShopThemeModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBShopThemeModel.h; sourceTree = ""; }; + 0498BDE32EEA885D006CC1D5 /* KBShopThemeModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBShopThemeModel.m; sourceTree = ""; }; 049FB2092EC1C13800FAB05D /* KBSkinBottomActionView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinBottomActionView.h; sourceTree = ""; }; 049FB20A2EC1C13800FAB05D /* KBSkinBottomActionView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinBottomActionView.m; sourceTree = ""; }; 049FB20C2EC1CD2800FAB05D /* KBAlert.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAlert.h; sourceTree = ""; }; @@ -765,6 +774,10 @@ 047C654A2EBCBA880035E841 /* M */ = { isa = PBXGroup; children = ( + 0498BDDF2EEA87C8006CC1D5 /* KBShopStyleModel.h */, + 0498BDE02EEA87C8006CC1D5 /* KBShopStyleModel.m */, + 0498BDE22EEA885D006CC1D5 /* KBShopThemeModel.h */, + 0498BDE32EEA885D006CC1D5 /* KBShopThemeModel.m */, ); path = M; sourceTree = ""; @@ -800,6 +813,7 @@ 047C654D2EBCBA880035E841 /* Shop */ = { isa = PBXGroup; children = ( + 0498BDDB2EE814E3006CC1D5 /* VM */, 047C654A2EBCBA880035E841 /* M */, 047C654B2EBCBA880035E841 /* V */, 047C654C2EBCBA880035E841 /* VC */, @@ -921,6 +935,15 @@ path = WJXEventSource; sourceTree = ""; }; + 0498BDDB2EE814E3006CC1D5 /* VM */ = { + isa = PBXGroup; + children = ( + 0498BDDC2EE81508006CC1D5 /* KBShopVM.h */, + 0498BDDD2EE81508006CC1D5 /* KBShopVM.m */, + ); + path = VM; + sourceTree = ""; + }; 049FB2162EC20A6600FAB05D /* BMLongPressDragCellCollectionView */ = { isa = PBXGroup; children = ( @@ -1820,6 +1843,7 @@ 04791F922ED48010004E8522 /* KBNoticeVC.m in Sources */, 04FC970F2EB334F8007BD342 /* KBWebImageManager.m in Sources */, 04FC95CF2EB1E7A1007BD342 /* HomeVC.m in Sources */, + 0498BDDE2EE81508006CC1D5 /* KBShopVM.m in Sources */, 049FB2112EC1F72F00FAB05D /* KBMyListCell.m in Sources */, A1B2D7022EB8C00100000001 /* KBLangTestVC.m in Sources */, 04122F6D2EC5F40800EF7AB3 /* NSObject+FGIsNullOrEmpty.m in Sources */, @@ -1831,6 +1855,7 @@ 048908DA2EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m in Sources */, 04C6EABF2EAF86530089C901 /* main.m in Sources */, 0498BD6E2EE0285D006CC1D5 /* KBForgetVerPwdVC.m in Sources */, + 0498BDE12EEA87C9006CC1D5 /* KBShopStyleModel.m in Sources */, 04FC95CC2EB1E780007BD342 /* BaseTabBarController.m in Sources */, 0479205A2EDEE1FC004E8522 /* CRBoxTextView.m in Sources */, 0479205B2EDEE1FC004E8522 /* CRSecrectImageView.m in Sources */, @@ -1847,6 +1872,7 @@ 049FB2432EC4BBB700FAB05D /* KBLoginPopView.m in Sources */, 048908CC2EBE373500FABA60 /* KBSearchBarView.m in Sources */, 04122F872EC6198C00EF7AB3 /* WMDragView.m in Sources */, + 0498BDE42EEA885D006CC1D5 /* KBShopThemeModel.m in Sources */, 048908CD2EBE373500FABA60 /* KBSearchSectionHeader.m in Sources */, 049FB2202EC30D2700FAB05D /* HomeRankDetailPopView.m in Sources */, 048908CE2EBE373500FABA60 /* KBSkinCardCell.m in Sources */, diff --git a/keyBoard/Class/Search/V/KBSkinCardCell.m b/keyBoard/Class/Search/V/KBSkinCardCell.m index 6a40c92..79a97ac 100644 --- a/keyBoard/Class/Search/V/KBSkinCardCell.m +++ b/keyBoard/Class/Search/V/KBSkinCardCell.m @@ -49,7 +49,8 @@ - (void)configWithTitle:(NSString *)title imageURL:(NSString *)url price:(NSString *)price { self.titleLabel.text = title.length ? title : @"Dopamine"; - [self.priceBtn setTitle:@"20" forState:UIControlStateNormal]; + NSString *priceText = price.length ? price : @"20"; + [self.priceBtn setTitle:priceText forState:UIControlStateNormal]; // 简化:本地展示占位色,无网络图 self.coverView.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1.0]; @@ -84,4 +85,3 @@ } @end - diff --git a/keyBoard/Class/Shop/M/KBShopStyleModel.h b/keyBoard/Class/Shop/M/KBShopStyleModel.h new file mode 100644 index 0000000..ae3844d --- /dev/null +++ b/keyBoard/Class/Shop/M/KBShopStyleModel.h @@ -0,0 +1,20 @@ +// +// KBShopStyleModel.h +// keyBoard +// +// Created by Mac on 2025/12/11. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface KBShopStyleModel : NSObject +/// 风格 id +@property (nonatomic, copy, nullable) NSString *styleId; +/// 风格名称 +@property (nonatomic, copy, nullable) NSString *styleName; +@end + + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Shop/M/KBShopStyleModel.m b/keyBoard/Class/Shop/M/KBShopStyleModel.m new file mode 100644 index 0000000..17f1615 --- /dev/null +++ b/keyBoard/Class/Shop/M/KBShopStyleModel.m @@ -0,0 +1,18 @@ +// +// KBShopStyleModel.m +// keyBoard +// +// Created by Mac on 2025/12/11. +// + +#import "KBShopStyleModel.h" + +@implementation KBShopStyleModel ++ (NSDictionary *)mj_replacedKeyFromPropertyName { + return @{ + @"styleId": @"id", + @"styleName": @"styleName" + }; +} + +@end diff --git a/keyBoard/Class/Shop/M/KBShopThemeModel.h b/keyBoard/Class/Shop/M/KBShopThemeModel.h new file mode 100644 index 0000000..f1936c2 --- /dev/null +++ b/keyBoard/Class/Shop/M/KBShopThemeModel.h @@ -0,0 +1,34 @@ +// +// KBShopThemeModel.h +// keyBoard +// +// Created by Mac on 2025/12/11. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface KBShopThemeModel : NSObject +/// 主题 id +@property (nonatomic, copy, nullable) NSString *themeId; +/// 主题名称 +@property (nonatomic, copy, nullable) NSString *themeName; +/// 价格(单位:接口返回的货币) +@property (nonatomic, assign) CGFloat themePrice; +/// 标签或风格名称 +@property (nonatomic, copy, nullable) NSString *themeTag; +/// 下载量描述 +@property (nonatomic, copy, nullable) NSString *themeDownload; +/// 风格类型(接口返回 themeStyle) +@property (nonatomic, assign) NSInteger themeStyle; +/// 是否上架 +@property (nonatomic, assign, getter=isThemeStatus) BOOL themeStatus; +/// 购买次数 +@property (nonatomic, assign) NSInteger themePurchasesNumber; +/// 预览图 +@property (nonatomic, copy, nullable) NSString *themePreviewImageUrl; + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Shop/M/KBShopThemeModel.m b/keyBoard/Class/Shop/M/KBShopThemeModel.m new file mode 100644 index 0000000..205b694 --- /dev/null +++ b/keyBoard/Class/Shop/M/KBShopThemeModel.m @@ -0,0 +1,16 @@ +// +// KBShopThemeModel.m +// keyBoard +// +// Created by Mac on 2025/12/11. +// + +#import "KBShopThemeModel.h" + +@implementation KBShopThemeModel ++ (NSDictionary *)mj_replacedKeyFromPropertyName { + return @{ + @"themeId": @"id", + }; +} +@end diff --git a/keyBoard/Class/Shop/VC/KBShopItemVC.h b/keyBoard/Class/Shop/VC/KBShopItemVC.h index cf5e84c..8757135 100644 --- a/keyBoard/Class/Shop/VC/KBShopItemVC.h +++ b/keyBoard/Class/Shop/VC/KBShopItemVC.h @@ -10,17 +10,25 @@ NS_ASSUME_NONNULL_BEGIN +@class KBShopStyleModel; +@class KBShopVM; +@class KBShopThemeModel; + @interface KBShopItemVC : UIViewController /// 列表:使用 UICollectionView 展示两列皮肤卡片 @property (nonatomic, strong) UICollectionView *collectionView; -/// 数据源:简单字符串作为标题(演示用) -@property (nonatomic, strong) NSMutableArray *dataSource; +/// 数据源:主题列表 +@property (nonatomic, strong) NSMutableArray *dataSource; /// 是否需要上拉加载更多 @property (nonatomic, assign) BOOL isNeedFooter; /// 是否需要下拉刷新 @property (nonatomic, assign) BOOL isNeedHeader; /// 首次是否已刷新过(避免重复触发) @property (nonatomic, assign) BOOL isHeaderRefreshed; // 默认为 YES +/// 当前所属风格 +@property (nonatomic, strong, nullable) KBShopStyleModel *style; +/// 可复用的 ShopVM(由外部传入,便于共用缓存/网络配置) +@property (nonatomic, strong, nullable) KBShopVM *shopViewModel; @end NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Shop/VC/KBShopItemVC.m b/keyBoard/Class/Shop/VC/KBShopItemVC.m index b488d37..8f27cc9 100644 --- a/keyBoard/Class/Shop/VC/KBShopItemVC.m +++ b/keyBoard/Class/Shop/VC/KBShopItemVC.m @@ -11,9 +11,12 @@ #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 @@ -21,6 +24,7 @@ - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor clearColor]; + self.dataSource = [NSMutableArray array]; // 懒加载 collectionView,并添加到视图 [self.view addSubview:self.collectionView]; @@ -28,26 +32,19 @@ make.edges.equalTo(self.view); // mas 布局:铺满 }]; - // 刷新组件(演示:2 秒后结束) KBWeakSelf if (self.isNeedHeader) { self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [weakSelf.collectionView.mj_header endRefreshing]; - }); + [weakSelf fetchThemes]; }]; } if (self.isNeedFooter) { self.collectionView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{ - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [weakSelf.dataSource addObject:KBLocalized(@"Loaded more successfully")]; // 模拟新增一条 - [weakSelf.collectionView reloadData]; - [weakSelf.collectionView.mj_footer endRefreshing]; - }); + [weakSelf fetchThemes]; }]; } - self.collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + self.collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; [self beginFirstRefresh]; } @@ -56,23 +53,58 @@ // 首次进入自动触发一次刷新(如果需要) - (void)beginFirstRefresh { - if (!self.isHeaderRefreshed) { - [self beginRefreshImmediately]; + if (self.isHeaderRefreshed || self.isLoading) { + return; } + if (!self.style.styleId.length) { + return; + } + [self beginRefreshImmediately]; } - (void)beginRefreshImmediately { - if (self.isNeedHeader) { + if (self.isNeedHeader && !self.collectionView.mj_header.isRefreshing) { [self.collectionView.mj_header beginRefreshing]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - self.isHeaderRefreshed = YES; - [self.collectionView reloadData]; - [self.collectionView.mj_header endRefreshing]; - }); - } else { - self.isHeaderRefreshed = YES; - [self.collectionView reloadData]; } + [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 @@ -84,8 +116,10 @@ - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { KBSkinCardCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"KBSkinCardCell" forIndexPath:indexPath]; - NSString *title = (indexPath.item < self.dataSource.count) ? self.dataSource[indexPath.item] : @"Dopamine"; - [cell configWithTitle:title imageURL:nil price:@"20"]; // 价格写死 20(演示) + 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; } @@ -117,8 +151,28 @@ [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 { - NSString *title = (indexPath.item < self.dataSource.count) ? self.dataSource[indexPath.item] : KBLocalized(@"专属皮肤002"); + 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 @@ -128,6 +182,13 @@ [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); diff --git a/keyBoard/Class/Shop/VC/KBShopVC.m b/keyBoard/Class/Shop/VC/KBShopVC.m index 8f108fc..8c6f41b 100644 --- a/keyBoard/Class/Shop/VC/KBShopVC.m +++ b/keyBoard/Class/Shop/VC/KBShopVC.m @@ -18,6 +18,8 @@ #import "KBWebViewViewController.h" +#import "KBShopVM.h" +#import "KBHUD.h" static const CGFloat JXTableHeaderViewHeight = (323); static const CGFloat JXheightForHeaderInSection = 50; @@ -33,11 +35,13 @@ static const CGFloat JXheightForHeaderInSection = 50; @property (nonatomic, strong) JXCategoryTitleView *categoryView; @property (nonatomic, strong) NSArray *titles; +@property (nonatomic, copy) NSArray *styles; @property (nonatomic, strong) UIImageView *bgImageView; // 全屏背景图 @property (nonatomic, strong) UIButton *searchBtn; @property (nonatomic, strong) UIButton *skinBtn; // 记录当前分类条是否为白底,避免重复设置 @property (nonatomic, assign) BOOL categoryIsWhite; +@property (nonatomic, strong) KBShopVM *shopVM; @end @@ -65,6 +69,7 @@ static const CGFloat JXheightForHeaderInSection = 50; make.width.height.mas_equalTo(25); }]; + [self fetchShopStylesWithHUD:YES]; } // 系统导航栏显隐由 Base 统一管理(全局隐藏),该 VC 不再手动切换,避免闪烁。 @@ -76,10 +81,8 @@ static const CGFloat JXheightForHeaderInSection = 50; self.view.backgroundColor = [UIColor whiteColor]; self.navigationController.navigationBar.translucent = false; self.edgesForExtendedLayout = UIRectEdgeNone; - _titles = @[KBLocalized(@"能力"), KBLocalized(@"爱好"), KBLocalized(@"队友"), - KBLocalized(@"能力2"), KBLocalized(@"爱好2"), KBLocalized(@"队友2"), - KBLocalized(@"能力"), KBLocalized(@"爱好"), KBLocalized(@"队友"), - KBLocalized(@"能力2"), KBLocalized(@"爱好2"), KBLocalized(@"队友2")]; + self.styles = @[]; + _titles = @[]; _userHeaderView = [[KBShopHeadView alloc] init]; _categoryView = (JXCategoryTitleView *)[[KBCategoryTitleView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, JXheightForHeaderInSection)]; @@ -133,13 +136,7 @@ static const CGFloat JXheightForHeaderInSection = 50; __weak typeof(self)weakSelf = self; self.pagerView.mainTableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - self.categoryView.titles = @[KBLocalized(@"高级能力"), KBLocalized(@"高级爱好"), KBLocalized(@"高级队友")]; - self.categoryView.defaultSelectedIndex = 0; - [self.categoryView reloadData]; - [self.pagerView reloadData]; - [weakSelf.pagerView.mainTableView.mj_header endRefreshing]; - }); + [weakSelf fetchShopStylesWithHUD:NO]; }]; self.pagerView.pinSectionHeaderVerticalOffset = KB_NAV_TOTAL_HEIGHT; @@ -194,19 +191,9 @@ static const CGFloat JXheightForHeaderInSection = 50; list.title = self.titles[index]; list.isNeedHeader = self.isNeedHeader; list.isNeedFooter = self.isNeedFooter; - if (index == 0) { - list.dataSource = @[KBLocalized(@"橡胶火箭"), KBLocalized(@"橡胶火箭炮"), KBLocalized(@"橡胶机关枪"), KBLocalized(@"橡胶子弹"), - KBLocalized(@"橡胶攻城炮"), KBLocalized(@"橡胶象枪"), KBLocalized(@"橡胶象枪乱打"), KBLocalized(@"橡胶灰熊铳"), - KBLocalized(@"橡胶雷神象枪"), KBLocalized(@"橡胶猿王枪"), KBLocalized(@"橡胶犀·榴弹炮"), KBLocalized(@"橡胶大蛇炮"), - KBLocalized(@"橡胶火箭"), KBLocalized(@"橡胶火箭炮"), KBLocalized(@"橡胶机关枪"), KBLocalized(@"橡胶子弹"), - KBLocalized(@"橡胶攻城炮"), KBLocalized(@"橡胶象枪"), KBLocalized(@"橡胶象枪乱打"), KBLocalized(@"橡胶灰熊铳"), - KBLocalized(@"橡胶雷神象枪"), KBLocalized(@"橡胶猿王枪"), KBLocalized(@"橡胶犀·榴弹炮"), KBLocalized(@"橡胶大蛇炮")].mutableCopy; - }else if (index == 1) { - list.dataSource = @[KBLocalized(@"吃烤肉"), KBLocalized(@"吃鸡腿肉"), KBLocalized(@"吃牛肉"), KBLocalized(@"各种肉")].mutableCopy; - }else { - list.dataSource = @[KBLocalized(@"【剑士】罗罗诺亚·索隆"), KBLocalized(@"【航海士】娜美"), KBLocalized(@"【狙击手】乌索普"), - KBLocalized(@"【厨师】香吉士"), KBLocalized(@"【船医】托尼托尼·乔巴"), KBLocalized(@"【船匠】 弗兰奇"), - KBLocalized(@"【音乐家】布鲁克"), KBLocalized(@"【考古学家】妮可·罗宾")].mutableCopy; + list.shopViewModel = self.shopVM; + if (index < self.styles.count) { + list.style = self.styles[index]; } return list; } @@ -277,4 +264,46 @@ static const CGFloat JXheightForHeaderInSection = 50; } return _skinBtn; } + +- (KBShopVM *)shopVM { + if (!_shopVM) { + _shopVM = [[KBShopVM alloc] init]; + } + return _shopVM; +} + +- (void)fetchShopStylesWithHUD:(BOOL)showHUD { + if (showHUD) { + [KBHUD show]; + } + __weak typeof(self) weakSelf = self; + [self.shopVM fetchAllStylesWithCompletion:^(NSArray * _Nullable styles, NSError * _Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (showHUD) { + [KBHUD dismiss]; + } + [weakSelf.pagerView.mainTableView.mj_header endRefreshing]; + if (error) { + NSString *msg = error.localizedDescription ?: KBLocalized(@"Network error"); + [KBHUD showInfo:msg]; + return; + } + [weakSelf applyStyles:styles]; + }); + }]; +} + +- (void)applyStyles:(NSArray *)styles { + self.styles = styles ?: @[]; + NSMutableArray *names = [NSMutableArray array]; + for (KBShopStyleModel *style in self.styles) { + NSString *name = style.styleName.length ? style.styleName : KBLocalized(@"Themes"); + [names addObject:name]; + } + self.titles = names.copy; + self.categoryView.titles = self.titles; + self.categoryView.defaultSelectedIndex = 0; + [self.categoryView reloadData]; + [self.pagerView reloadData]; +} @end diff --git a/keyBoard/Class/Shop/VM/KBShopVM.h b/keyBoard/Class/Shop/VM/KBShopVM.h new file mode 100644 index 0000000..8af5615 --- /dev/null +++ b/keyBoard/Class/Shop/VM/KBShopVM.h @@ -0,0 +1,34 @@ +// +// KBShopVM.h +// keyBoard +// +// Created by Mac on 2025/12/9. +// + +#import +#import +#import "KBShopStyleModel.h" +#import "KBShopThemeModel.h" + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - Models + +typedef void(^KBShopStylesCompletion)(NSArray *_Nullable styles, + NSError *_Nullable error); +typedef void(^KBShopThemesCompletion)(NSArray *_Nullable themes, + NSError *_Nullable error); + +@interface KBShopVM : NSObject +@property (nonatomic, copy, readonly, nullable) NSArray *styles; + +/// 查询全部主题风格 +- (void)fetchAllStylesWithCompletion:(KBShopStylesCompletion)completion; + +/// 按风格请求主题列表 +- (void)fetchThemesForStyleId:(nullable NSString *)styleId + completion:(KBShopThemesCompletion)completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Shop/VM/KBShopVM.m b/keyBoard/Class/Shop/VM/KBShopVM.m new file mode 100644 index 0000000..3d81d7c --- /dev/null +++ b/keyBoard/Class/Shop/VM/KBShopVM.m @@ -0,0 +1,85 @@ +// +// KBShopVM.m +// keyBoard +// +// Created by Mac on 2025/12/9. +// + +#import "KBShopVM.h" +#import "KBNetworkManager.h" +#import "KBAPI.h" +#import "KBBizCode.h" +#import + +@interface KBShopVM () +@property (nonatomic, copy, readwrite, nullable) NSArray *styles; +@end + + +@implementation KBShopVM + +- (NSError *)kb_invalidResponseError { + return [NSError errorWithDomain:KBNetworkErrorDomain + code:KBNetworkErrorInvalidResponse + userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid response")}]; +} + +- (NSError *)kb_invalidParameterError { + return [NSError errorWithDomain:KBNetworkErrorDomain + code:KBNetworkErrorInvalidResponse + userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid parameter")}]; +} + +- (void)fetchAllStylesWithCompletion:(KBShopStylesCompletion)completion { + [[KBNetworkManager shared] GET:API_THEME_LIST_ALL_STYLES + parameters:nil + headers:nil + autoShowBusinessError:NO + completion:^(NSDictionary * _Nullable json, + NSURLResponse * _Nullable response, + NSError * _Nullable error) { + if (error) { + if (completion) completion(nil, error); + return; + } + id dataObj = json[KBData] ?: json[@"data"]; + if (![dataObj isKindOfClass:[NSArray class]]) { + if (completion) completion(nil, [self kb_invalidResponseError]); + return; + } + NSArray *list = [KBShopStyleModel mj_objectArrayWithKeyValuesArray:(NSArray *)dataObj]; + self.styles = list; + if (completion) completion(list, nil); + }]; +} + +- (void)fetchThemesForStyleId:(nullable NSString *)styleId + completion:(KBShopThemesCompletion)completion { + if (styleId.length == 0) { + if (completion) completion(nil, [self kb_invalidParameterError]); + return; + } + NSMutableDictionary *params = [NSMutableDictionary dictionary]; + params[@"themeStyle"] = @([styleId integerValue]); + [[KBNetworkManager shared] GET:API_THEME_LIST_BY_STYLE + parameters:params + headers:nil + autoShowBusinessError:NO + completion:^(NSDictionary * _Nullable json, + NSURLResponse * _Nullable response, + NSError * _Nullable error) { + if (error) { + if (completion) completion(nil, error); + return; + } + id dataObj = json[KBData] ?: json[@"data"]; + if (![dataObj isKindOfClass:[NSArray class]]) { + if (completion) completion(nil, [self kb_invalidResponseError]); + return; + } + NSArray *list = [KBShopThemeModel mj_objectArrayWithKeyValuesArray:(NSArray *)dataObj]; + if (completion) completion(list, nil); + }]; +} + +@end