From 1ecb7d60e5093020f31c8a4795972b59de47a84d Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Wed, 17 Dec 2025 20:30:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Shared/KBAPI.h | 1 + keyBoard.xcodeproj/project.pbxproj | 66 +++++++++++------ keyBoard/Class/Search/M/KBSearchThemeModel.h | 34 +++++++++ keyBoard/Class/Search/M/KBSearchThemeModel.m | 27 +++++++ keyBoard/Class/Search/VC/KBSearchResultVC.m | 63 ++++++++++------ keyBoard/Class/Search/VC/KBSearchVC.m | 52 ++++++++++--- keyBoard/Class/Search/VM/KBSearchVM.h | 32 ++++++++ keyBoard/Class/Search/VM/KBSearchVM.m | 77 ++++++++++++++++++++ 8 files changed, 293 insertions(+), 59 deletions(-) create mode 100644 keyBoard/Class/Search/M/KBSearchThemeModel.h create mode 100644 keyBoard/Class/Search/M/KBSearchThemeModel.m create mode 100644 keyBoard/Class/Search/VM/KBSearchVM.h create mode 100644 keyBoard/Class/Search/VM/KBSearchVM.m diff --git a/Shared/KBAPI.h b/Shared/KBAPI.h index cda9cf9..2798147 100644 --- a/Shared/KBAPI.h +++ b/Shared/KBAPI.h @@ -54,6 +54,7 @@ #define API_THEME_PURCHASE @"/themes/purchase" // 购买主题 #define API_THEME_DOWNLOAD @"/themes/download" // 主题下载信息 #define API_THEME_RECOMMENDED @"/themes/recommended" // 推荐主题列表 +#define API_THEME_SEARCH @"/themes/search" // 搜索主题(themeName) /// pay #define API_VALIDATE_RECEIPT @"/apple/validate-receipt" // 排行榜标签列表 diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 75e2182..222ba43 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -95,11 +95,13 @@ 048908CE2EBE373500FABA60 /* KBSkinCardCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908C92EBE373500FABA60 /* KBSkinCardCell.m */; }; 048908CF2EBE373500FABA60 /* KBTagCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908CB2EBE373500FABA60 /* KBTagCell.m */; }; 048908D22EBF611D00FABA60 /* KBHistoryMoreCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908D12EBF611D00FABA60 /* KBHistoryMoreCell.m */; }; - 048908DA2EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908D82EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m */; }; - 048908DD2EBF67EB00FABA60 /* KBSearchResultVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908DC2EBF67EB00FABA60 /* KBSearchResultVC.m */; }; - 048908E02EBF73DC00FABA60 /* MySkinVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908DF2EBF73DC00FABA60 /* MySkinVC.m */; }; - 048908E32EBF760000FABA60 /* MySkinCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908E22EBF760000FABA60 /* MySkinCell.m */; }; - 048908E32EBF821700FABA60 /* KBSkinDetailVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908E22EBF821700FABA60 /* KBSkinDetailVC.m */; }; + 048908DA2EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908D82EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m */; }; + 048908DD2EBF67EB00FABA60 /* KBSearchResultVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908DC2EBF67EB00FABA60 /* KBSearchResultVC.m */; }; + 05A1B2D12F5B1A2B3C4D5E60 /* KBSearchVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A1B2C52F5B1A2B3C4D5E60 /* KBSearchVM.m */; }; + 05A1B2D22F5B1A2B3C4D5E60 /* KBSearchThemeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A1B2C72F5B1A2B3C4D5E60 /* KBSearchThemeModel.m */; }; + 048908E02EBF73DC00FABA60 /* MySkinVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908DF2EBF73DC00FABA60 /* MySkinVC.m */; }; + 048908E32EBF760000FABA60 /* MySkinCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908E22EBF760000FABA60 /* MySkinCell.m */; }; + 048908E32EBF821700FABA60 /* KBSkinDetailVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908E22EBF821700FABA60 /* KBSkinDetailVC.m */; }; 048908E62EBF841B00FABA60 /* KBSkinDetailTagCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908E52EBF841B00FABA60 /* KBSkinDetailTagCell.m */; }; 048908E92EBF843000FABA60 /* KBSkinDetailHeaderCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908E82EBF843000FABA60 /* KBSkinDetailHeaderCell.m */; }; 048908EC2EBF849300FABA60 /* KBSkinTagsContainerCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908EB2EBF849300FABA60 /* KBSkinTagsContainerCell.m */; }; @@ -385,12 +387,16 @@ 048908D02EBF611D00FABA60 /* KBHistoryMoreCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBHistoryMoreCell.h; sourceTree = ""; }; 048908D12EBF611D00FABA60 /* KBHistoryMoreCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBHistoryMoreCell.m; sourceTree = ""; }; 048908D72EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UICollectionViewLeftAlignedLayout.h; sourceTree = ""; }; - 048908D82EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UICollectionViewLeftAlignedLayout.m; sourceTree = ""; }; - 048908DB2EBF67EB00FABA60 /* KBSearchResultVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSearchResultVC.h; sourceTree = ""; }; - 048908DC2EBF67EB00FABA60 /* KBSearchResultVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSearchResultVC.m; sourceTree = ""; }; - 048908DE2EBF73DC00FABA60 /* MySkinVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinVC.h; sourceTree = ""; }; - 048908DF2EBF73DC00FABA60 /* MySkinVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MySkinVC.m; sourceTree = ""; }; - 048908E12EBF760000FABA60 /* MySkinCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinCell.h; sourceTree = ""; }; + 048908D82EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UICollectionViewLeftAlignedLayout.m; sourceTree = ""; }; + 048908DB2EBF67EB00FABA60 /* KBSearchResultVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSearchResultVC.h; sourceTree = ""; }; + 048908DC2EBF67EB00FABA60 /* KBSearchResultVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSearchResultVC.m; sourceTree = ""; }; + 05A1B2C42F5B1A2B3C4D5E60 /* KBSearchVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSearchVM.h; sourceTree = ""; }; + 05A1B2C52F5B1A2B3C4D5E60 /* KBSearchVM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSearchVM.m; sourceTree = ""; }; + 05A1B2C62F5B1A2B3C4D5E60 /* KBSearchThemeModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSearchThemeModel.h; sourceTree = ""; }; + 05A1B2C72F5B1A2B3C4D5E60 /* KBSearchThemeModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSearchThemeModel.m; sourceTree = ""; }; + 048908DE2EBF73DC00FABA60 /* MySkinVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinVC.h; sourceTree = ""; }; + 048908DF2EBF73DC00FABA60 /* MySkinVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MySkinVC.m; sourceTree = ""; }; + 048908E12EBF760000FABA60 /* MySkinCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinCell.h; sourceTree = ""; }; 048908E12EBF821700FABA60 /* KBSkinDetailVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinDetailVC.h; sourceTree = ""; }; 048908E22EBF760000FABA60 /* MySkinCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MySkinCell.m; sourceTree = ""; }; 048908E22EBF821700FABA60 /* KBSkinDetailVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinDetailVC.m; sourceTree = ""; }; @@ -926,13 +932,15 @@ path = Common; sourceTree = ""; }; - 048908BD2EBE329D00FABA60 /* M */ = { - isa = PBXGroup; - children = ( - ); - path = M; - sourceTree = ""; - }; + 048908BD2EBE329D00FABA60 /* M */ = { + isa = PBXGroup; + children = ( + 05A1B2C62F5B1A2B3C4D5E60 /* KBSearchThemeModel.h */, + 05A1B2C72F5B1A2B3C4D5E60 /* KBSearchThemeModel.m */, + ); + path = M; + sourceTree = ""; + }; 048908BE2EBE329D00FABA60 /* V */ = { isa = PBXGroup; children = ( @@ -964,6 +972,7 @@ 048908C02EBE329D00FABA60 /* Search */ = { isa = PBXGroup; children = ( + 04DC5CED2EF2D2C400F1AC80 /* VM */, 048908BD2EBE329D00FABA60 /* M */, 048908BE2EBE329D00FABA60 /* V */, 048908BF2EBE329D00FABA60 /* VC */, @@ -1178,6 +1187,15 @@ path = Buy; sourceTree = ""; }; + 04DC5CED2EF2D2C400F1AC80 /* VM */ = { + isa = PBXGroup; + children = ( + 05A1B2C42F5B1A2B3C4D5E60 /* KBSearchVM.h */, + 05A1B2C52F5B1A2B3C4D5E60 /* KBSearchVM.m */, + ); + path = VM; + sourceTree = ""; + }; 04FC95662EB0546C007BD342 /* Model */ = { isa = PBXGroup; children = ( @@ -1958,11 +1976,13 @@ 0477BDF72EBC63A80055D639 /* KBTestVC.m in Sources */, 04122F7E2EC5FC5500EF7AB3 /* KBJfPayCell.m in Sources */, 049FB2402EC4B6EF00FAB05D /* KBULBridgeNotification.m in Sources */, - 04FC95C92EB1E4C9007BD342 /* BaseNavigationController.m in Sources */, - 048908DD2EBF67EB00FABA60 /* KBSearchResultVC.m in Sources */, - 047C65102EBCA8DD0035E841 /* HomeRankContentVC.m in Sources */, - 047C655C2EBCD0F80035E841 /* UIView+KBShadow.m in Sources */, - 049FB2262EC3136D00FAB05D /* KBPersonInfoItemCell.m in Sources */, + 04FC95C92EB1E4C9007BD342 /* BaseNavigationController.m in Sources */, + 048908DD2EBF67EB00FABA60 /* KBSearchResultVC.m in Sources */, + 05A1B2D12F5B1A2B3C4D5E60 /* KBSearchVM.m in Sources */, + 05A1B2D22F5B1A2B3C4D5E60 /* KBSearchThemeModel.m in Sources */, + 047C65102EBCA8DD0035E841 /* HomeRankContentVC.m in Sources */, + 047C655C2EBCD0F80035E841 /* UIView+KBShadow.m in Sources */, + 049FB2262EC3136D00FAB05D /* KBPersonInfoItemCell.m in Sources */, 048908C32EBE32B800FABA60 /* KBSearchVC.m in Sources */, 049FB20B2EC1C13800FAB05D /* KBSkinBottomActionView.m in Sources */, 047C655E2EBCD5B20035E841 /* UIImage+KBColor.m in Sources */, diff --git a/keyBoard/Class/Search/M/KBSearchThemeModel.h b/keyBoard/Class/Search/M/KBSearchThemeModel.h new file mode 100644 index 0000000..dea5388 --- /dev/null +++ b/keyBoard/Class/Search/M/KBSearchThemeModel.h @@ -0,0 +1,34 @@ +// +// KBSearchThemeModel.h +// keyBoard +// +// Created by Mac on 2025/12/17. +// + +#import +#import + +@class KBShopThemeTagModel; + +NS_ASSUME_NONNULL_BEGIN + +/// /themes/search 返回的主题模型 +@interface KBSearchThemeModel : NSObject + +@property (nonatomic, copy, nullable) NSString *themeId; +@property (nonatomic, copy, nullable) NSString *themeName; +@property (nonatomic, assign) CGFloat themePrice; +@property (nonatomic, strong, nullable) NSArray *themeTag; +@property (nonatomic, copy, nullable) NSString *themeDownload; +@property (nonatomic, assign) NSInteger themeStyle; +@property (nonatomic, copy, nullable) NSString *themePreviewImageUrl; +@property (nonatomic, copy, nullable) NSString *themeDownloadUrl; +@property (nonatomic, assign) NSInteger themePurchasesNumber; +@property (nonatomic, assign) NSInteger sort; +@property (nonatomic, assign) BOOL isFree; +@property (nonatomic, assign) BOOL isPurchased; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/keyBoard/Class/Search/M/KBSearchThemeModel.m b/keyBoard/Class/Search/M/KBSearchThemeModel.m new file mode 100644 index 0000000..2a4aaeb --- /dev/null +++ b/keyBoard/Class/Search/M/KBSearchThemeModel.m @@ -0,0 +1,27 @@ +// +// KBSearchThemeModel.m +// keyBoard +// +// Created by Mac on 2025/12/17. +// + +#import "KBSearchThemeModel.h" +#import "KBShopThemeTagModel.h" +#import + +@implementation KBSearchThemeModel + ++ (NSDictionary *)mj_objectClassInArray { + return @{ + @"themeTag": KBShopThemeTagModel.class + }; +} + ++ (NSDictionary *)mj_replacedKeyFromPropertyName { + return @{ + @"themeId": @"id", + }; +} + +@end + diff --git a/keyBoard/Class/Search/VC/KBSearchResultVC.m b/keyBoard/Class/Search/VC/KBSearchResultVC.m index c63c07b..ce82611 100644 --- a/keyBoard/Class/Search/VC/KBSearchResultVC.m +++ b/keyBoard/Class/Search/VC/KBSearchResultVC.m @@ -6,6 +6,8 @@ #import "KBSearchResultVC.h" #import "KBSearchBarView.h" #import "KBSkinCardCell.h" +#import "KBSearchVM.h" +#import "KBSearchThemeModel.h" static NSString * const kResultCellId = @"KBSkinCardCell"; @@ -20,8 +22,9 @@ static NSString * const kResultCellId = @"KBSkinCardCell"; @property (nonatomic, strong) UICollectionView *collectionView; @property (nonatomic, strong) UICollectionViewFlowLayout *flowLayout; -// 数据源(示例数据,实际项目中由网络返回) -@property (nonatomic, strong) NSMutableArray *resultItems; // @{title, price} +// 数据源 +@property (nonatomic, strong) NSMutableArray *resultItems; +@property (nonatomic, strong) KBSearchVM *viewModel; @end @@ -66,9 +69,6 @@ static NSString * const kResultCellId = @"KBSkinCardCell"; if (self.defaultKeyword.length > 0) { [self.searchBarView updateKeyword:self.defaultKeyword]; [self performSearch:self.defaultKeyword]; - } else { - // 填充一些示例数据 - [self loadMockData]; } } @@ -76,23 +76,22 @@ static NSString * const kResultCellId = @"KBSkinCardCell"; #pragma mark - Private -/// 执行搜索(示例:本地生成一些数据) +/// 执行搜索 - (void)performSearch:(NSString *)keyword { - // 这里可以发起网络请求;演示中生成 10 条假数据 - [self.resultItems removeAllObjects]; - for (int i = 0; i < 10; i++) { - [self.resultItems addObject:@{ @"title": @"Dopamine", @"price": @"20" }]; - } - [self.collectionView reloadData]; -} - -/// 示例数据 -- (void)loadMockData { - [self.resultItems removeAllObjects]; - for (int i = 0; i < 12; i++) { - [self.resultItems addObject:@{ @"title": @"Dopamine", @"price": @"20" }]; - } - [self.collectionView reloadData]; + __weak typeof(self) weakSelf = self; + [self.viewModel searchThemesWithName:keyword completion:^(NSArray * _Nullable themes, NSError * _Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (error) { + NSLog(@"[KBSearchResultVC] search failed: %@", error); + return; + } + [weakSelf.resultItems removeAllObjects]; + if (themes.count > 0) { + [weakSelf.resultItems addObjectsFromArray:themes]; + } + [weakSelf.collectionView reloadData]; + }); + }]; } #pragma mark - UICollectionViewDataSource @@ -107,8 +106,10 @@ static NSString * const kResultCellId = @"KBSkinCardCell"; - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { KBSkinCardCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kResultCellId forIndexPath:indexPath]; - NSDictionary *it = self.resultItems[indexPath.item]; - [cell configWithTitle:it[@"title"] imageURL:nil price:it[@"price"]]; + KBSearchThemeModel *model = self.resultItems[indexPath.item]; + [cell configWithTitle:model.themeName ?: @"" + imageURL:model.themePreviewImageUrl + price:[self priceTextForTheme:model]]; return cell; } @@ -150,6 +151,13 @@ static NSString * const kResultCellId = @"KBSkinCardCell"; return _searchBarView; } +- (NSString *)priceTextForTheme:(KBSearchThemeModel *)model { + if (model.themePrice > 0.0) { + return [NSString stringWithFormat:@"%.2f", model.themePrice]; + } + return @"0"; +} + - (UIView *)topBar { if (!_topBar) { _topBar = [[UIView alloc] init]; @@ -199,11 +207,18 @@ static NSString * const kResultCellId = @"KBSkinCardCell"; return _collectionView; } -- (NSMutableArray *)resultItems { +- (NSMutableArray *)resultItems { if (!_resultItems) { _resultItems = [NSMutableArray array]; } return _resultItems; } +- (KBSearchVM *)viewModel { + if (!_viewModel) { + _viewModel = [[KBSearchVM alloc] init]; + } + return _viewModel; +} + @end diff --git a/keyBoard/Class/Search/VC/KBSearchVC.m b/keyBoard/Class/Search/VC/KBSearchVC.m index 787181a..223198d 100644 --- a/keyBoard/Class/Search/VC/KBSearchVC.m +++ b/keyBoard/Class/Search/VC/KBSearchVC.m @@ -13,6 +13,8 @@ #import "KBHistoryMoreCell.h" #import "KBSearchResultVC.h" #import "UICollectionViewLeftAlignedLayout.h" +#import "KBSearchVM.h" +#import "KBShopThemeModel.h" static NSString * const kTagCellId = @"KBTagCell"; @@ -37,9 +39,10 @@ typedef NS_ENUM(NSInteger, KBSearchSection) { // 数据 @property (nonatomic, strong) NSMutableArray *historyWords; // 历史搜索 -@property (nonatomic, strong) NSArray *recommendItems; // 推荐数据(title/price) +@property (nonatomic, copy) NSArray *recommendedThemes; // 推荐主题 @property (nonatomic, assign) BOOL historyExpanded; // 是否展开所有历史 @property (nonatomic, assign) CGFloat lastCollectionWidth; // 记录宽度变化,用于重新计算 +@property (nonatomic, strong) KBSearchVM *viewModel; @end @implementation KBSearchVC @@ -89,16 +92,10 @@ typedef NS_ENUM(NSInteger, KBSearchSection) { KBLocalized(@"水果萝卜"), KBLocalized(@"熟冻帝王蟹"), KBLocalized(@"赣南脐橙")] mutableCopy]; - self.recommendItems = @[ - @{@"title":@"Dopamine", @"price":@"20"}, - @{@"title":@"Dopamine", @"price":@"20"}, - @{@"title":@"Dopamine", @"price":@"20"}, - @{@"title":@"Dopamine", @"price":@"20"}, - @{@"title":@"Dopamine", @"price":@"20"}, - @{@"title":@"Dopamine", @"price":@"20"}, - ]; + self.recommendedThemes = @[]; [self.collectionView reloadData]; + [self fetchRecommendedThemes]; } - (void)viewDidLayoutSubviews { @@ -252,7 +249,7 @@ typedef NS_ENUM(NSInteger, KBSearchSection) { NSArray *list = [self currentDisplayHistory]; return list.count; // 历史最多两行;可能包含“更多”占位 } - return self.recommendItems.count; + return self.recommendedThemes.count; } - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { @@ -269,8 +266,10 @@ typedef NS_ENUM(NSInteger, KBSearchSection) { } } KBSkinCardCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kSkinCellId forIndexPath:indexPath]; - NSDictionary *it = self.recommendItems[indexPath.item]; - [cell configWithTitle:it[@"title"] imageURL:nil price:it[@"price"]]; + KBShopThemeModel *model = self.recommendedThemes[indexPath.item]; + [cell configWithTitle:model.themeName ?: @"" + imageURL:model.themePreviewImageUrl + price:[self priceTextForTheme:model]]; return cell; } @@ -398,6 +397,28 @@ typedef NS_ENUM(NSInteger, KBSearchSection) { - (void)onTapBack { [self.navigationController popViewControllerAnimated:YES]; } +- (void)fetchRecommendedThemes { + __weak typeof(self) weakSelf = self; + [self.viewModel fetchRecommendedThemesWithCompletion:^(NSArray * _Nullable themes, NSError * _Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (error) { + NSLog(@"[KBSearchVC] fetch recommended failed: %@", error); + return; + } + weakSelf.recommendedThemes = themes ?: @[]; + NSIndexSet *sections = [NSIndexSet indexSetWithIndex:KBSearchSectionRecommend]; + [weakSelf.collectionView reloadSections:sections]; + }); + }]; +} + +- (NSString *)priceTextForTheme:(KBShopThemeModel *)model { + if (model.themePrice > 0.0) { + return [NSString stringWithFormat:@"%.2f", model.themePrice]; + } + return @"0"; +} + - (UICollectionViewLeftAlignedLayout *)flowLayout { if (!_flowLayout) { _flowLayout = [[UICollectionViewLeftAlignedLayout alloc] init]; @@ -423,4 +444,11 @@ typedef NS_ENUM(NSInteger, KBSearchSection) { return _collectionView; } +- (KBSearchVM *)viewModel { + if (!_viewModel) { + _viewModel = [[KBSearchVM alloc] init]; + } + return _viewModel; +} + @end diff --git a/keyBoard/Class/Search/VM/KBSearchVM.h b/keyBoard/Class/Search/VM/KBSearchVM.h new file mode 100644 index 0000000..4e912a0 --- /dev/null +++ b/keyBoard/Class/Search/VM/KBSearchVM.h @@ -0,0 +1,32 @@ +// +// KBSearchVM.h +// keyBoard +// +// Created by Mac on 2025/12/17. +// + +#import + +@class KBShopThemeModel; +@class KBSearchThemeModel; + +NS_ASSUME_NONNULL_BEGIN + +typedef void(^KBSearchRecommendedCompletion)(NSArray *_Nullable themes, + NSError *_Nullable error); +typedef void(^KBSearchThemesCompletion)(NSArray *_Nullable themes, + NSError *_Nullable error); + +@interface KBSearchVM : NSObject + +/// 推荐主题列表(复用 KBShopVM 的 API_THEME_RECOMMENDED) +- (void)fetchRecommendedThemesWithCompletion:(KBSearchRecommendedCompletion)completion; + +/// 搜索主题:GET /themes/search?themeName=xxx +- (void)searchThemesWithName:(NSString *)themeName + completion:(KBSearchThemesCompletion)completion; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/keyBoard/Class/Search/VM/KBSearchVM.m b/keyBoard/Class/Search/VM/KBSearchVM.m new file mode 100644 index 0000000..9b0def7 --- /dev/null +++ b/keyBoard/Class/Search/VM/KBSearchVM.m @@ -0,0 +1,77 @@ +// +// KBSearchVM.m +// keyBoard +// +// Created by Mac on 2025/12/17. +// + +#import "KBSearchVM.h" +#import "KBShopVM.h" +#import "KBNetworkManager.h" +#import "KBAPI.h" +#import "KBBizCode.h" +#import "KBSearchThemeModel.h" +#import + +@interface KBSearchVM () +@property (nonatomic, strong) KBShopVM *shopVM; +@end + +@implementation KBSearchVM + +- (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)fetchRecommendedThemesWithCompletion:(KBSearchRecommendedCompletion)completion { + [self.shopVM fetchRecommendedThemesWithCompletion:^(NSArray * _Nullable themes, NSError * _Nullable error) { + if (completion) completion(themes, error); + }]; +} + +- (void)searchThemesWithName:(NSString *)themeName + completion:(KBSearchThemesCompletion)completion { + NSString *trim = [themeName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if (trim.length == 0) { + if (completion) completion(nil, [self kb_invalidParameterError]); + return; + } + NSDictionary *params = @{@"themeName": trim}; + [[KBNetworkManager shared] GET:API_THEME_SEARCH + 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 = [KBSearchThemeModel mj_objectArrayWithKeyValuesArray:(NSArray *)dataObj]; + if (completion) completion(list, nil); + }]; +} + +- (KBShopVM *)shopVM { + if (!_shopVM) { + _shopVM = [[KBShopVM alloc] init]; + } + return _shopVM; +} + +@end +