From 45695364e918adb715cf79f4f6e57b0380e3ad2b Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Thu, 11 Dec 2025 13:40:32 +0800 Subject: [PATCH] 1 --- Shared/KBAPI.h | 1 + .../Localization/en.lproj/Localizable.strings | 2 +- .../zh-Hans.lproj/Localizable.strings | 1 + keyBoard.xcodeproj/project.pbxproj | 8 +++ keyBoard/Class/Base/V/BaseTableView.m | 4 +- .../Class/Base/V/UIScrollView+KBEmptyView.m | 2 +- keyBoard/Class/Me/M/KBMyTheme.h | 25 ++++++++ keyBoard/Class/Me/M/KBMyTheme.m | 17 ++++++ keyBoard/Class/Me/VC/MySkinVC.m | 60 +++++++++++-------- keyBoard/Class/Me/VM/KBMyVM.h | 4 ++ keyBoard/Class/Me/VM/KBMyVM.m | 28 +++++++++ 11 files changed, 123 insertions(+), 29 deletions(-) create mode 100644 keyBoard/Class/Me/M/KBMyTheme.h create mode 100644 keyBoard/Class/Me/M/KBMyTheme.m diff --git a/Shared/KBAPI.h b/Shared/KBAPI.h index f16fd0a..6613713 100644 --- a/Shared/KBAPI.h +++ b/Shared/KBAPI.h @@ -48,6 +48,7 @@ /// Theme shop #define API_THEME_LIST_ALL_STYLES @"/themes/listAllStyles" // 查询所有主题风格 #define API_THEME_LIST_BY_STYLE @"/themes/listByStyle" // 按风格查询主题列表 +#define API_THEME_PURCHASED @"/themes/purchased" // 查询已购买主题 /// pay #define API_VALIDATE_RECEIPT @"/api/apple/validate-receipt" // 排行榜标签列表 diff --git a/Shared/Localization/en.lproj/Localizable.strings b/Shared/Localization/en.lproj/Localizable.strings index e7f156b..84b943e 100644 --- a/Shared/Localization/en.lproj/Localizable.strings +++ b/Shared/Localization/en.lproj/Localizable.strings @@ -195,6 +195,7 @@ "Pay clicked" = "Pay clicked"; "Points Recharge" = "Points Recharge"; "My Points" = "My Points"; +"No data" = "No data"; // Example categories/items "能力" = "Ability"; @@ -250,7 +251,6 @@ // Misc "测试" = "Test"; -"暂无数据" = "No data yet"; "这里是设置内容占位" = "Settings content placeholder"; "设置" = "Settings"; "使用引导" = "Usage Guide"; diff --git a/Shared/Localization/zh-Hans.lproj/Localizable.strings b/Shared/Localization/zh-Hans.lproj/Localizable.strings index a6affd1..02e2632 100644 --- a/Shared/Localization/zh-Hans.lproj/Localizable.strings +++ b/Shared/Localization/zh-Hans.lproj/Localizable.strings @@ -193,6 +193,7 @@ "Pay clicked" = "点击支付"; "Points Recharge" = "积分充值"; "My Points" = "我的积分"; +"No data" = "暂无数据"; // 示例商品/分类 "能力" = "能力"; diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 530f897..7161c06 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -115,6 +115,8 @@ 0498BD8C2EE69E15006CC1D5 /* KBTagItemModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD8A2EE69E15006CC1D5 /* KBTagItemModel.m */; }; 0498BD8F2EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */; }; 0498BD902EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */; }; + 550CB2630FA4A7B4B9782EFA /* KBMyTheme.m in Sources */ = {isa = PBXBuildFile; fileRef = 180D662EC4DB3A7FFF83FF18 /* KBMyTheme.m */; }; + 471CAD3574798685B72ADD55 /* KBMyTheme.m in Sources */ = {isa = PBXBuildFile; fileRef = 180D662EC4DB3A7FFF83FF18 /* KBMyTheme.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 */; }; @@ -409,6 +411,8 @@ 0498BD8A2EE69E15006CC1D5 /* KBTagItemModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBTagItemModel.m; sourceTree = ""; }; 0498BD8D2EE6A3BD006CC1D5 /* KBMyMainModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBMyMainModel.h; sourceTree = ""; }; 0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyMainModel.m; sourceTree = ""; }; + 7ECBD0E320F971D0FBEDD7BC /* KBMyTheme.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBMyTheme.h; sourceTree = ""; }; + 180D662EC4DB3A7FFF83FF18 /* KBMyTheme.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyTheme.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 = ""; }; @@ -1175,6 +1179,8 @@ 0498BD8A2EE69E15006CC1D5 /* KBTagItemModel.m */, 0498BD8D2EE6A3BD006CC1D5 /* KBMyMainModel.h */, 0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */, + 7ECBD0E320F971D0FBEDD7BC /* KBMyTheme.h */, + 180D662EC4DB3A7FFF83FF18 /* KBMyTheme.m */, ); path = M; sourceTree = ""; @@ -1752,6 +1758,7 @@ 04FC95672EB0546C007BD342 /* KBKey.m in Sources */, A1B2C3F42EB35A9900000001 /* KBFullAccessGuideView.m in Sources */, 0498BD8F2EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */, + 550CB2630FA4A7B4B9782EFA /* KBMyTheme.m in Sources */, 0498BDDA2EE7ECEA006CC1D5 /* WJXEventSource.m in Sources */, 04D1F6B22EDFF10A00B12345 /* KBSkinInstallBridge.m in Sources */, A1B2C4002EB4A0A100000003 /* KBAuthManager.m in Sources */, @@ -1796,6 +1803,7 @@ 04A9FE1B2EB892460020DB6D /* KBLocalizationManager.m in Sources */, 048908BC2EBE1FCB00FABA60 /* BaseViewController.m in Sources */, 0498BD902EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */, + 471CAD3574798685B72ADD55 /* KBMyTheme.m in Sources */, 04FC95D72EB1EA16007BD342 /* BaseTableView.m in Sources */, 0498BD712EE02A41006CC1D5 /* KBForgetPwdNewPwdVC.m in Sources */, 048908EF2EBF861800FABA60 /* KBSkinSectionTitleCell.m in Sources */, diff --git a/keyBoard/Class/Base/V/BaseTableView.m b/keyBoard/Class/Base/V/BaseTableView.m index 231e61c..afb427a 100644 --- a/keyBoard/Class/Base/V/BaseTableView.m +++ b/keyBoard/Class/Base/V/BaseTableView.m @@ -53,7 +53,7 @@ _useEmptyDataSet = YES; // 默认开启 _emptyShouldAllowScroll = YES; // 默认允许滚动 _emptyVerticalOffset = 0; // 默认不偏移 - _emptyTitleText = KBLocalized(@"暂无数据"); // 默认标题 + _emptyTitleText = KBLocalized(@"No data"); // 默认标题 #if KB_HAS_DZN self.emptyDataSetSource = self; @@ -148,7 +148,7 @@ self.useEmptyDataSet = NO; // 默认文案 - NSString *t = title ?: KBLocalized(@"暂无数据"); + NSString *t = title ?: KBLocalized(@"No data"); LYEmptyView *ev = nil; if (buttonTitle.length > 0) { diff --git a/keyBoard/Class/Base/V/UIScrollView+KBEmptyView.m b/keyBoard/Class/Base/V/UIScrollView+KBEmptyView.m index 454a166..5302e9c 100644 --- a/keyBoard/Class/Base/V/UIScrollView+KBEmptyView.m +++ b/keyBoard/Class/Base/V/UIScrollView+KBEmptyView.m @@ -22,7 +22,7 @@ tapHandler:(KBEmptyAction)tapHandler buttonHandler:(KBEmptyAction)buttonHandler { #if KB_HAS_LY - NSString *t = title ?: KBLocalized(@"暂无数据"); + NSString *t = title ?: KBLocalized(@"No data"); LYEmptyView *ev = nil; if (buttonTitle.length > 0) { diff --git a/keyBoard/Class/Me/M/KBMyTheme.h b/keyBoard/Class/Me/M/KBMyTheme.h new file mode 100644 index 0000000..4722e9e --- /dev/null +++ b/keyBoard/Class/Me/M/KBMyTheme.h @@ -0,0 +1,25 @@ +// +// KBMyTheme.h +// keyBoard +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/// “我的皮肤”页已购主题模型 +@interface KBMyTheme : NSObject +@property (nonatomic, copy, nullable) NSString *themeId; +@property (nonatomic, copy, nullable) NSString *themeName; +@property (nonatomic, assign) CGFloat themePrice; +@property (nonatomic, copy, nullable) NSArray *themeTag; +@property (nonatomic, copy, nullable) NSString *themeDownload; +@property (nonatomic, assign) NSInteger themeStyle; +@property (nonatomic, assign) BOOL themeStatus; +@property (nonatomic, assign) NSInteger themePurchasesNumber; +@property (nonatomic, assign) NSInteger sort; +@property (nonatomic, assign) BOOL isFree; +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Me/M/KBMyTheme.m b/keyBoard/Class/Me/M/KBMyTheme.m new file mode 100644 index 0000000..d5f27dd --- /dev/null +++ b/keyBoard/Class/Me/M/KBMyTheme.m @@ -0,0 +1,17 @@ +// +// KBMyTheme.m +// keyBoard +// + +#import "KBMyTheme.h" +#import + +@implementation KBMyTheme + ++ (NSDictionary *)mj_replacedKeyFromPropertyName { + return @{ + @"themeId": @"id" + }; +} + +@end diff --git a/keyBoard/Class/Me/VC/MySkinVC.m b/keyBoard/Class/Me/VC/MySkinVC.m index 533b160..49f20ba 100644 --- a/keyBoard/Class/Me/VC/MySkinVC.m +++ b/keyBoard/Class/Me/VC/MySkinVC.m @@ -11,6 +11,9 @@ #import "UIScrollView+KBEmptyView.h" // 统一空态封装(LYEmptyView) #import "MySkinCell.h" +#import "KBMyVM.h" +#import "KBMyTheme.h" +#import "KBHUD.h" static NSString * const kMySkinCellId = @"kMySkinCellId"; @@ -20,8 +23,8 @@ static NSString * const kMySkinCellId = @"kMySkinCellId"; @property (nonatomic, strong) UILabel *selectedLabel; // 已选择数量 @property (nonatomic, strong) UIButton *deleteButton; // 删除 -@property (nonatomic, strong) NSMutableArray *data; // 简单数据源 -@property (nonatomic, assign) NSInteger loadCount; // 刷新计数(用于演示空/有数据切换) +@property (nonatomic, strong) NSMutableArray *data; // 已购主题 +@property (nonatomic, strong) KBMyVM *viewModel; @property (nonatomic, assign, getter=isEditingMode) BOOL editingMode; // 是否编辑态 @end @@ -62,9 +65,9 @@ static NSString * const kMySkinCellId = @"kMySkinCellId"; // 空态视图(LYEmptyView)统一样式 + 重试按钮 KBWeakSelf [self.collectionView kb_makeDefaultEmptyViewWithImage:nil - title:KBLocalized(@"No skins yet") + title:KBLocalized(@"No data") detail:KBLocalized(@"Pull down to refresh") - buttonTitle:KBLocalized(@"Retry") + buttonTitle:KBLocalized(@"") tapHandler:nil buttonHandler:^{ [weakSelf.collectionView.mj_header beginRefreshing]; }]; [self.collectionView kb_setLYAutoShowEnabled:NO]; // 采用手动控制显隐 @@ -73,7 +76,7 @@ static NSString * const kMySkinCellId = @"kMySkinCellId"; [self.collectionView kb_endLoadingForEmpty]; // 下拉刷新(演示网络加载 + 空态切换) - self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(fetchData)]; + self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(fetchPurchasedThemes)]; // 首次进入自动刷新 [self.collectionView.mj_header beginRefreshing]; @@ -82,25 +85,25 @@ static NSString * const kMySkinCellId = @"kMySkinCellId"; self.bottomView.hidden = YES; } -#pragma mark - Data - -- (void)fetchData { - // 模拟网络延迟 1.0s - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - self.loadCount += 1; - - // 交替返回:奇数次空数据,偶数次有数据(演示空态/非空切换) - [self.data removeAllObjects]; - if (self.loadCount % 2 == 0) { - for (int i = 0; i < 8; i++) { - [self.data addObject:@{ @"title": @"Dopamine" }]; +- (void)fetchPurchasedThemes { + KBWeakSelf + [self.viewModel fetchPurchasedThemesWithCompletion:^(NSArray * _Nullable themes, NSError * _Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([weakSelf.collectionView.mj_header isRefreshing]) { + [weakSelf.collectionView.mj_header endRefreshing]; } - } - - [self.collectionView reloadData]; - [self.collectionView kb_endLoadingForEmpty]; // 根据数据源显示/隐藏 emptyView - [self.collectionView.mj_header endRefreshing]; - }); + if (error) { + [weakSelf.collectionView kb_endLoadingForEmpty]; + return; + } + [weakSelf.data removeAllObjects]; + if (themes.count > 0) { + [weakSelf.data addObjectsFromArray:themes]; + } + [weakSelf.collectionView reloadData]; + [weakSelf.collectionView kb_endLoadingForEmpty]; + }); + }]; } #pragma mark - Actions @@ -190,8 +193,8 @@ static NSString * const kMySkinCellId = @"kMySkinCellId"; - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { MySkinCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kMySkinCellId forIndexPath:indexPath]; - NSDictionary *d = self.data[indexPath.item]; - [cell configWithTitle:d[@"title"] image:nil]; + KBMyTheme *theme = self.data[indexPath.item]; + [cell configWithTitle:theme.themeName image:nil]; cell.editing = self.isEditingMode; // 控制是否显示选择圆点 // 同步选中状态(复用时) BOOL selected = [[collectionView indexPathsForSelectedItems] containsObject:indexPath]; @@ -306,4 +309,11 @@ static NSString * const kMySkinCellId = @"kMySkinCellId"; return _deleteButton; } +- (KBMyVM *)viewModel { + if (!_viewModel) { + _viewModel = [[KBMyVM alloc] init]; + } + return _viewModel; +} + @end diff --git a/keyBoard/Class/Me/VM/KBMyVM.h b/keyBoard/Class/Me/VM/KBMyVM.h index 0c065ad..4b94e51 100644 --- a/keyBoard/Class/Me/VM/KBMyVM.h +++ b/keyBoard/Class/Me/VM/KBMyVM.h @@ -7,6 +7,7 @@ #import #import "KBCharacter.h" +#import "KBMyTheme.h" @class KBUser; NS_ASSUME_NONNULL_BEGIN @@ -22,6 +23,7 @@ typedef void(^KBUpLoadAvatarCompletion)(BOOL success, NSError * _Nullable error) typedef void(^KBUpdateUserInfoCompletion)(BOOL success, NSError * _Nullable error); typedef void(^KBUpdateCharacterSortCompletion)(BOOL success, NSError * _Nullable error); typedef void(^KBDeleteUserCharacterCompletion)(BOOL success, NSError * _Nullable error); +typedef void(^KBMyPurchasedThemesCompletion)(NSArray *_Nullable themes, NSError *_Nullable error); @interface KBMyVM : NSObject @@ -30,6 +32,8 @@ typedef void(^KBDeleteUserCharacterCompletion)(BOOL success, NSError * _Nullable /// 用户人设列表(/character/listByUser) - (void)fetchCharacterListByUserWithCompletion:(KBCharacterListCompletion)completion; +/// 已购买主题列表(/themes/purchased) +- (void)fetchPurchasedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion; /// 更新用户人设排序 - (void)updateUserCharacterSortWithSortArray:(NSArray *)sortArray completion:(KBUpdateCharacterSortCompletion)completion; diff --git a/keyBoard/Class/Me/VM/KBMyVM.m b/keyBoard/Class/Me/VM/KBMyVM.m index cb8e624..b407ae0 100644 --- a/keyBoard/Class/Me/VM/KBMyVM.m +++ b/keyBoard/Class/Me/VM/KBMyVM.m @@ -86,6 +86,34 @@ NSString * const KBUserCharacterDeletedNotification = @"KBUserCharacterDeletedNo }]; } +- (void)fetchPurchasedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion { + [[KBNetworkManager shared] GET:API_THEME_PURCHASED + parameters:nil + headers:nil + autoShowBusinessError:NO + completion:^(NSDictionary *jsonOrData, NSURLResponse * _Nullable response, NSError * _Nullable error) { + if (error) { + NSString *msg = KBBizMessageFromJSONObject(jsonOrData) ?: error.localizedDescription ?: KBLocalized(@"Network error"); + [KBHUD showInfo:msg]; + if (completion) completion(nil, error); + return; + } + + id dataObj = jsonOrData[KBData] ?: jsonOrData[@"data"]; + if (![dataObj isKindOfClass:[NSArray class]]) { + NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain + code:KBNetworkErrorInvalidResponse + userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid response")}]; + [KBHUD showInfo:e.localizedDescription]; + if (completion) completion(nil, e); + return; + } + + NSArray *themes = [KBMyTheme mj_objectArrayWithKeyValuesArray:(NSArray *)dataObj]; + if (completion) completion(themes, nil); + }]; +} + /// 更新用户人设排序 - (void)updateUserCharacterSortWithSortArray:(NSArray *)sortArray completion:(KBUpdateCharacterSortCompletion)completion {