This commit is contained in:
2025-12-11 13:16:06 +08:00
parent e39104c431
commit d348b35984
12 changed files with 387 additions and 52 deletions

View File

@@ -45,6 +45,10 @@
#define API_CHARACTER_DEL_USER_CHARACTER @"/character/delUserCharacter" // 删除用户人设 #define API_CHARACTER_DEL_USER_CHARACTER @"/character/delUserCharacter" // 删除用户人设
#define API_CHARACTER_ADD_USER_CHARACTER @"/character/addUserCharacter" // 添加用户人设(假定路径,如有不同请按后端实际修改) #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 /// pay
#define API_VALIDATE_RECEIPT @"/api/apple/validate-receipt" // 排行榜标签列表 #define API_VALIDATE_RECEIPT @"/api/apple/validate-receipt" // 排行榜标签列表

View File

@@ -116,6 +116,9 @@
0498BD8F2EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */; }; 0498BD8F2EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */; };
0498BD902EE6A3BD006CC1D5 /* 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 */; }; 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 */; }; 049FB20B2EC1C13800FAB05D /* KBSkinBottomActionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB20A2EC1C13800FAB05D /* KBSkinBottomActionView.m */; };
049FB20E2EC1CD2800FAB05D /* KBAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB20D2EC1CD2800FAB05D /* KBAlert.m */; }; 049FB20E2EC1CD2800FAB05D /* KBAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB20D2EC1CD2800FAB05D /* KBAlert.m */; };
049FB2112EC1F72F00FAB05D /* KBMyListCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2102EC1F72F00FAB05D /* KBMyListCell.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 = "<group>"; }; 0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyMainModel.m; sourceTree = "<group>"; };
0498BDD72EE7ECEA006CC1D5 /* WJXEventSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WJXEventSource.h; sourceTree = "<group>"; }; 0498BDD72EE7ECEA006CC1D5 /* WJXEventSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WJXEventSource.h; sourceTree = "<group>"; };
0498BDD82EE7ECEA006CC1D5 /* WJXEventSource.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WJXEventSource.m; sourceTree = "<group>"; }; 0498BDD82EE7ECEA006CC1D5 /* WJXEventSource.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WJXEventSource.m; sourceTree = "<group>"; };
0498BDDC2EE81508006CC1D5 /* KBShopVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBShopVM.h; sourceTree = "<group>"; };
0498BDDD2EE81508006CC1D5 /* KBShopVM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBShopVM.m; sourceTree = "<group>"; };
0498BDDF2EEA87C8006CC1D5 /* KBShopStyleModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBShopStyleModel.h; sourceTree = "<group>"; };
0498BDE02EEA87C8006CC1D5 /* KBShopStyleModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBShopStyleModel.m; sourceTree = "<group>"; };
0498BDE22EEA885D006CC1D5 /* KBShopThemeModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBShopThemeModel.h; sourceTree = "<group>"; };
0498BDE32EEA885D006CC1D5 /* KBShopThemeModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBShopThemeModel.m; sourceTree = "<group>"; };
049FB2092EC1C13800FAB05D /* KBSkinBottomActionView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinBottomActionView.h; sourceTree = "<group>"; }; 049FB2092EC1C13800FAB05D /* KBSkinBottomActionView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinBottomActionView.h; sourceTree = "<group>"; };
049FB20A2EC1C13800FAB05D /* KBSkinBottomActionView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinBottomActionView.m; sourceTree = "<group>"; }; 049FB20A2EC1C13800FAB05D /* KBSkinBottomActionView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinBottomActionView.m; sourceTree = "<group>"; };
049FB20C2EC1CD2800FAB05D /* KBAlert.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAlert.h; sourceTree = "<group>"; }; 049FB20C2EC1CD2800FAB05D /* KBAlert.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAlert.h; sourceTree = "<group>"; };
@@ -765,6 +774,10 @@
047C654A2EBCBA880035E841 /* M */ = { 047C654A2EBCBA880035E841 /* M */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0498BDDF2EEA87C8006CC1D5 /* KBShopStyleModel.h */,
0498BDE02EEA87C8006CC1D5 /* KBShopStyleModel.m */,
0498BDE22EEA885D006CC1D5 /* KBShopThemeModel.h */,
0498BDE32EEA885D006CC1D5 /* KBShopThemeModel.m */,
); );
path = M; path = M;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -800,6 +813,7 @@
047C654D2EBCBA880035E841 /* Shop */ = { 047C654D2EBCBA880035E841 /* Shop */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0498BDDB2EE814E3006CC1D5 /* VM */,
047C654A2EBCBA880035E841 /* M */, 047C654A2EBCBA880035E841 /* M */,
047C654B2EBCBA880035E841 /* V */, 047C654B2EBCBA880035E841 /* V */,
047C654C2EBCBA880035E841 /* VC */, 047C654C2EBCBA880035E841 /* VC */,
@@ -921,6 +935,15 @@
path = WJXEventSource; path = WJXEventSource;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
0498BDDB2EE814E3006CC1D5 /* VM */ = {
isa = PBXGroup;
children = (
0498BDDC2EE81508006CC1D5 /* KBShopVM.h */,
0498BDDD2EE81508006CC1D5 /* KBShopVM.m */,
);
path = VM;
sourceTree = "<group>";
};
049FB2162EC20A6600FAB05D /* BMLongPressDragCellCollectionView */ = { 049FB2162EC20A6600FAB05D /* BMLongPressDragCellCollectionView */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -1820,6 +1843,7 @@
04791F922ED48010004E8522 /* KBNoticeVC.m in Sources */, 04791F922ED48010004E8522 /* KBNoticeVC.m in Sources */,
04FC970F2EB334F8007BD342 /* KBWebImageManager.m in Sources */, 04FC970F2EB334F8007BD342 /* KBWebImageManager.m in Sources */,
04FC95CF2EB1E7A1007BD342 /* HomeVC.m in Sources */, 04FC95CF2EB1E7A1007BD342 /* HomeVC.m in Sources */,
0498BDDE2EE81508006CC1D5 /* KBShopVM.m in Sources */,
049FB2112EC1F72F00FAB05D /* KBMyListCell.m in Sources */, 049FB2112EC1F72F00FAB05D /* KBMyListCell.m in Sources */,
A1B2D7022EB8C00100000001 /* KBLangTestVC.m in Sources */, A1B2D7022EB8C00100000001 /* KBLangTestVC.m in Sources */,
04122F6D2EC5F40800EF7AB3 /* NSObject+FGIsNullOrEmpty.m in Sources */, 04122F6D2EC5F40800EF7AB3 /* NSObject+FGIsNullOrEmpty.m in Sources */,
@@ -1831,6 +1855,7 @@
048908DA2EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m in Sources */, 048908DA2EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m in Sources */,
04C6EABF2EAF86530089C901 /* main.m in Sources */, 04C6EABF2EAF86530089C901 /* main.m in Sources */,
0498BD6E2EE0285D006CC1D5 /* KBForgetVerPwdVC.m in Sources */, 0498BD6E2EE0285D006CC1D5 /* KBForgetVerPwdVC.m in Sources */,
0498BDE12EEA87C9006CC1D5 /* KBShopStyleModel.m in Sources */,
04FC95CC2EB1E780007BD342 /* BaseTabBarController.m in Sources */, 04FC95CC2EB1E780007BD342 /* BaseTabBarController.m in Sources */,
0479205A2EDEE1FC004E8522 /* CRBoxTextView.m in Sources */, 0479205A2EDEE1FC004E8522 /* CRBoxTextView.m in Sources */,
0479205B2EDEE1FC004E8522 /* CRSecrectImageView.m in Sources */, 0479205B2EDEE1FC004E8522 /* CRSecrectImageView.m in Sources */,
@@ -1847,6 +1872,7 @@
049FB2432EC4BBB700FAB05D /* KBLoginPopView.m in Sources */, 049FB2432EC4BBB700FAB05D /* KBLoginPopView.m in Sources */,
048908CC2EBE373500FABA60 /* KBSearchBarView.m in Sources */, 048908CC2EBE373500FABA60 /* KBSearchBarView.m in Sources */,
04122F872EC6198C00EF7AB3 /* WMDragView.m in Sources */, 04122F872EC6198C00EF7AB3 /* WMDragView.m in Sources */,
0498BDE42EEA885D006CC1D5 /* KBShopThemeModel.m in Sources */,
048908CD2EBE373500FABA60 /* KBSearchSectionHeader.m in Sources */, 048908CD2EBE373500FABA60 /* KBSearchSectionHeader.m in Sources */,
049FB2202EC30D2700FAB05D /* HomeRankDetailPopView.m in Sources */, 049FB2202EC30D2700FAB05D /* HomeRankDetailPopView.m in Sources */,
048908CE2EBE373500FABA60 /* KBSkinCardCell.m in Sources */, 048908CE2EBE373500FABA60 /* KBSkinCardCell.m in Sources */,

View File

@@ -49,7 +49,8 @@
- (void)configWithTitle:(NSString *)title imageURL:(NSString *)url price:(NSString *)price { - (void)configWithTitle:(NSString *)title imageURL:(NSString *)url price:(NSString *)price {
self.titleLabel.text = title.length ? title : @"Dopamine"; 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]; self.coverView.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1.0];
@@ -84,4 +85,3 @@
} }
@end @end

View File

@@ -0,0 +1,20 @@
//
// KBShopStyleModel.h
// keyBoard
//
// Created by Mac on 2025/12/11.
//
#import <Foundation/Foundation.h>
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

View File

@@ -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

View File

@@ -0,0 +1,34 @@
//
// KBShopThemeModel.h
// keyBoard
//
// Created by Mac on 2025/12/11.
//
#import <Foundation/Foundation.h>
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

View File

@@ -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

View File

@@ -10,17 +10,25 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@class KBShopStyleModel;
@class KBShopVM;
@class KBShopThemeModel;
@interface KBShopItemVC : UIViewController<JXPagerViewListViewDelegate> @interface KBShopItemVC : UIViewController<JXPagerViewListViewDelegate>
/// 列表:使用 UICollectionView 展示两列皮肤卡片 /// 列表:使用 UICollectionView 展示两列皮肤卡片
@property (nonatomic, strong) UICollectionView *collectionView; @property (nonatomic, strong) UICollectionView *collectionView;
/// 数据源:简单字符串作为标题(演示用) /// 数据源:主题列表
@property (nonatomic, strong) NSMutableArray<NSString *> *dataSource; @property (nonatomic, strong) NSMutableArray<KBShopThemeModel *> *dataSource;
/// 是否需要上拉加载更多 /// 是否需要上拉加载更多
@property (nonatomic, assign) BOOL isNeedFooter; @property (nonatomic, assign) BOOL isNeedFooter;
/// 是否需要下拉刷新 /// 是否需要下拉刷新
@property (nonatomic, assign) BOOL isNeedHeader; @property (nonatomic, assign) BOOL isNeedHeader;
/// 首次是否已刷新过(避免重复触发) /// 首次是否已刷新过(避免重复触发)
@property (nonatomic, assign) BOOL isHeaderRefreshed; // 默认为 YES @property (nonatomic, assign) BOOL isHeaderRefreshed; // 默认为 YES
/// 当前所属风格
@property (nonatomic, strong, nullable) KBShopStyleModel *style;
/// 可复用的 ShopVM由外部传入便于共用缓存/网络配置)
@property (nonatomic, strong, nullable) KBShopVM *shopViewModel;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@@ -11,9 +11,12 @@
#import "KBSkinCardCell.h" #import "KBSkinCardCell.h"
#import "KBSkinInstallBridge.h" #import "KBSkinInstallBridge.h"
#import "KBHUD.h" #import "KBHUD.h"
#import "KBShopVM.h"
@interface KBShopItemVC ()<UICollectionViewDataSource, UICollectionViewDelegateFlowLayout> @interface KBShopItemVC ()<UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property (nonatomic, copy) void(^scrollCallback)(UIScrollView *scrollView); @property (nonatomic, copy) void(^scrollCallback)(UIScrollView *scrollView);
@property (nonatomic, strong) KBShopVM *internalViewModel;
@property (nonatomic, assign, getter=isLoading) BOOL loading;
@end @end
@implementation KBShopItemVC @implementation KBShopItemVC
@@ -21,6 +24,7 @@
- (void)viewDidLoad { - (void)viewDidLoad {
[super viewDidLoad]; [super viewDidLoad];
self.view.backgroundColor = [UIColor clearColor]; self.view.backgroundColor = [UIColor clearColor];
self.dataSource = [NSMutableArray array];
// collectionView // collectionView
[self.view addSubview:self.collectionView]; [self.view addSubview:self.collectionView];
@@ -28,22 +32,15 @@
make.edges.equalTo(self.view); // mas make.edges.equalTo(self.view); // mas
}]; }];
// 2
KBWeakSelf KBWeakSelf
if (self.isNeedHeader) { if (self.isNeedHeader) {
self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [weakSelf fetchThemes];
[weakSelf.collectionView.mj_header endRefreshing];
});
}]; }];
} }
if (self.isNeedFooter) { if (self.isNeedFooter) {
self.collectionView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{ self.collectionView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [weakSelf fetchThemes];
[weakSelf.dataSource addObject:KBLocalized(@"Loaded more successfully")]; //
[weakSelf.collectionView reloadData];
[weakSelf.collectionView.mj_footer endRefreshing];
});
}]; }];
} }
@@ -56,23 +53,58 @@
// //
- (void)beginFirstRefresh { - (void)beginFirstRefresh {
if (!self.isHeaderRefreshed) { if (self.isHeaderRefreshed || self.isLoading) {
[self beginRefreshImmediately]; return;
} }
if (!self.style.styleId.length) {
return;
}
[self beginRefreshImmediately];
} }
- (void)beginRefreshImmediately { - (void)beginRefreshImmediately {
if (self.isNeedHeader) { if (self.isNeedHeader && !self.collectionView.mj_header.isRefreshing) {
[self.collectionView.mj_header beginRefreshing]; [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<KBShopThemeModel *> * _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 #pragma mark - UICollectionView DataSource
@@ -84,8 +116,10 @@
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
KBSkinCardCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"KBSkinCardCell" forIndexPath:indexPath]; KBSkinCardCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"KBSkinCardCell" forIndexPath:indexPath];
NSString *title = (indexPath.item < self.dataSource.count) ? self.dataSource[indexPath.item] : @"Dopamine"; KBShopThemeModel *theme = (indexPath.item < self.dataSource.count) ? self.dataSource[indexPath.item] : nil;
[cell configWithTitle:title imageURL:nil price:@"20"]; // 20 NSString *title = theme.themeName.length ? theme.themeName : KBLocalized(@"Themes");
NSString *price = [self kb_priceStringForTheme:theme];
[cell configWithTitle:title imageURL:nil price:price];
return cell; return cell;
} }
@@ -117,8 +151,28 @@
[self kb_handleShopTapAtIndexPath: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 { - (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便 // 002.zip id便
static NSString * const kKBBundleSkinId002 = @"bundle_skin_fense"; static NSString * const kKBBundleSkinId002 = @"bundle_skin_fense";
[KBSkinInstallBridge publishBundleSkinRequestWithId:kKBBundleSkinId002 [KBSkinInstallBridge publishBundleSkinRequestWithId:kKBBundleSkinId002
@@ -128,6 +182,13 @@
[KBHUD showInfo:KBLocalized(@"已通知键盘解压,切换到自定义键盘即可生效")]; [KBHUD showInfo:KBLocalized(@"已通知键盘解压,切换到自定义键盘即可生效")];
} }
- (void)setStyle:(KBShopStyleModel *)style {
_style = style;
if (self.isViewLoaded && !self.isHeaderRefreshed) {
[self beginFirstRefresh];
}
}
#pragma mark - UIScrollView Delegate #pragma mark - UIScrollView Delegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
!self.scrollCallback ?: self.scrollCallback(scrollView); !self.scrollCallback ?: self.scrollCallback(scrollView);

View File

@@ -18,6 +18,8 @@
#import "KBWebViewViewController.h" #import "KBWebViewViewController.h"
#import "KBShopVM.h"
#import "KBHUD.h"
static const CGFloat JXTableHeaderViewHeight = (323); static const CGFloat JXTableHeaderViewHeight = (323);
static const CGFloat JXheightForHeaderInSection = 50; static const CGFloat JXheightForHeaderInSection = 50;
@@ -33,11 +35,13 @@ static const CGFloat JXheightForHeaderInSection = 50;
@property (nonatomic, strong) JXCategoryTitleView *categoryView; @property (nonatomic, strong) JXCategoryTitleView *categoryView;
@property (nonatomic, strong) NSArray <NSString *> *titles; @property (nonatomic, strong) NSArray <NSString *> *titles;
@property (nonatomic, copy) NSArray<KBShopStyleModel *> *styles;
@property (nonatomic, strong) UIImageView *bgImageView; // @property (nonatomic, strong) UIImageView *bgImageView; //
@property (nonatomic, strong) UIButton *searchBtn; @property (nonatomic, strong) UIButton *searchBtn;
@property (nonatomic, strong) UIButton *skinBtn; @property (nonatomic, strong) UIButton *skinBtn;
// //
@property (nonatomic, assign) BOOL categoryIsWhite; @property (nonatomic, assign) BOOL categoryIsWhite;
@property (nonatomic, strong) KBShopVM *shopVM;
@end @end
@@ -65,6 +69,7 @@ static const CGFloat JXheightForHeaderInSection = 50;
make.width.height.mas_equalTo(25); make.width.height.mas_equalTo(25);
}]; }];
[self fetchShopStylesWithHUD:YES];
} }
// Base VC // Base VC
@@ -76,10 +81,8 @@ static const CGFloat JXheightForHeaderInSection = 50;
self.view.backgroundColor = [UIColor whiteColor]; self.view.backgroundColor = [UIColor whiteColor];
self.navigationController.navigationBar.translucent = false; self.navigationController.navigationBar.translucent = false;
self.edgesForExtendedLayout = UIRectEdgeNone; self.edgesForExtendedLayout = UIRectEdgeNone;
_titles = @[KBLocalized(@"能力"), KBLocalized(@"爱好"), KBLocalized(@"队友"), self.styles = @[];
KBLocalized(@"能力2"), KBLocalized(@"爱好2"), KBLocalized(@"队友2"), _titles = @[];
KBLocalized(@"能力"), KBLocalized(@"爱好"), KBLocalized(@"队友"),
KBLocalized(@"能力2"), KBLocalized(@"爱好2"), KBLocalized(@"队友2")];
_userHeaderView = [[KBShopHeadView alloc] init]; _userHeaderView = [[KBShopHeadView alloc] init];
_categoryView = (JXCategoryTitleView *)[[KBCategoryTitleView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, JXheightForHeaderInSection)]; _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; __weak typeof(self)weakSelf = self;
self.pagerView.mainTableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ self.pagerView.mainTableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [weakSelf fetchShopStylesWithHUD:NO];
self.categoryView.titles = @[KBLocalized(@"高级能力"), KBLocalized(@"高级爱好"), KBLocalized(@"高级队友")];
self.categoryView.defaultSelectedIndex = 0;
[self.categoryView reloadData];
[self.pagerView reloadData];
[weakSelf.pagerView.mainTableView.mj_header endRefreshing];
});
}]; }];
self.pagerView.pinSectionHeaderVerticalOffset = KB_NAV_TOTAL_HEIGHT; self.pagerView.pinSectionHeaderVerticalOffset = KB_NAV_TOTAL_HEIGHT;
@@ -194,19 +191,9 @@ static const CGFloat JXheightForHeaderInSection = 50;
list.title = self.titles[index]; list.title = self.titles[index];
list.isNeedHeader = self.isNeedHeader; list.isNeedHeader = self.isNeedHeader;
list.isNeedFooter = self.isNeedFooter; list.isNeedFooter = self.isNeedFooter;
if (index == 0) { list.shopViewModel = self.shopVM;
list.dataSource = @[KBLocalized(@"橡胶火箭"), KBLocalized(@"橡胶火箭炮"), KBLocalized(@"橡胶机关枪"), KBLocalized(@"橡胶子弹"), if (index < self.styles.count) {
KBLocalized(@"橡胶攻城炮"), KBLocalized(@"橡胶象枪"), KBLocalized(@"橡胶象枪乱打"), KBLocalized(@"橡胶灰熊铳"), list.style = self.styles[index];
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;
} }
return list; return list;
} }
@@ -277,4 +264,46 @@ static const CGFloat JXheightForHeaderInSection = 50;
} }
return _skinBtn; 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<KBShopStyleModel *> * _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<KBShopStyleModel *> *)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 @end

View File

@@ -0,0 +1,34 @@
//
// KBShopVM.h
// keyBoard
//
// Created by Mac on 2025/12/9.
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import "KBShopStyleModel.h"
#import "KBShopThemeModel.h"
NS_ASSUME_NONNULL_BEGIN
#pragma mark - Models
typedef void(^KBShopStylesCompletion)(NSArray<KBShopStyleModel *> *_Nullable styles,
NSError *_Nullable error);
typedef void(^KBShopThemesCompletion)(NSArray<KBShopThemeModel *> *_Nullable themes,
NSError *_Nullable error);
@interface KBShopVM : NSObject
@property (nonatomic, copy, readonly, nullable) NSArray<KBShopStyleModel *> *styles;
/// 查询全部主题风格
- (void)fetchAllStylesWithCompletion:(KBShopStylesCompletion)completion;
/// 按风格请求主题列表
- (void)fetchThemesForStyleId:(nullable NSString *)styleId
completion:(KBShopThemesCompletion)completion;
@end
NS_ASSUME_NONNULL_END

View File

@@ -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 <MJExtension/MJExtension.h>
@interface KBShopVM ()
@property (nonatomic, copy, readwrite, nullable) NSArray<KBShopStyleModel *> *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<KBShopStyleModel *> *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<KBShopThemeModel *> *list = [KBShopThemeModel mj_objectArrayWithKeyValuesArray:(NSArray *)dataObj];
if (completion) completion(list, nil);
}];
}
@end