From 577b74919861e23ffafe46a8e55d9741780c3653 Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Thu, 11 Dec 2025 19:43:55 +0800 Subject: [PATCH] 2 --- Shared/KBAPI.h | 1 - Shared/KBSkinInstallBridge.h | 22 +++ Shared/KBSkinInstallBridge.m | 146 +++++++++++++++++++ keyBoard/Class/Manager/KBSkinService.m | 7 + keyBoard/Class/Me/M/KBMyTheme.h | 1 + keyBoard/Class/Me/V/KBSkinDetailHeaderCell.m | 12 +- keyBoard/Class/Me/V/MySkinCell.h | 5 +- keyBoard/Class/Me/V/MySkinCell.m | 6 +- keyBoard/Class/Me/VC/MySkinVC.m | 40 ++--- keyBoard/Class/Me/VM/KBMyVM.h | 10 +- keyBoard/Class/Me/VM/KBMyVM.m | 71 +++++---- keyBoard/Class/Shop/VC/KBSkinDetailVC.m | 3 + 12 files changed, 244 insertions(+), 80 deletions(-) diff --git a/Shared/KBAPI.h b/Shared/KBAPI.h index b60ff8a..a6fbf56 100644 --- a/Shared/KBAPI.h +++ b/Shared/KBAPI.h @@ -49,7 +49,6 @@ #define API_THEME_LIST_ALL_STYLES @"/themes/listAllStyles" // 查询所有主题风格 #define API_THEME_LIST_BY_STYLE @"/themes/listByStyle" // 按风格查询主题列表 #define API_THEME_PURCHASED @"/themes/purchased" // 查询已购买主题 -#define API_THEME_BATCH_DELETE @"/user-themes/batch-delete" // 批量删除用户主题 #define API_WALLET_BALANCE @"/wallet/balance" // 查询钱包余额 #define API_THEME_DETAIL @"/themes/detail" // 查询主题详情 #define API_THEME_PURCHASE @"/themes/purchase" // 购买主题 diff --git a/Shared/KBSkinInstallBridge.h b/Shared/KBSkinInstallBridge.h index 2df14fc..4c6f7d1 100644 --- a/Shared/KBSkinInstallBridge.h +++ b/Shared/KBSkinInstallBridge.h @@ -24,6 +24,15 @@ typedef NS_ENUM(NSUInteger, KBSkinBridgeErrorCode) { typedef void (^KBSkinInstallConsumeCompletion)(BOOL success, NSError * _Nullable error); +/// 已安装的皮肤记录,用于“我的皮肤”列表展示。 +@interface KBSkinDownloadRecord : NSObject +@property (nonatomic, copy) NSString *skinId; +@property (nonatomic, copy) NSString *name; +@property (nonatomic, copy, nullable) NSString *previewImage; +@property (nonatomic, copy, nullable) NSString *zipURL; +@property (nonatomic, assign) NSTimeInterval installedAt; +@end + @interface KBSkinInstallBridge : NSObject /// 默认图标短文件名映射(从 KBSkinIconMap.strings 读取)。 @@ -56,6 +65,19 @@ typedef void (^KBSkinInstallConsumeCompletion)(BOOL success, NSError * _Nullable + (void)consumePendingRequestFromBundle:(NSBundle *)bundle completion:(nullable KBSkinInstallConsumeCompletion)completion; +/// 枚举当前 App Group 中所有已解压的皮肤信息。 ++ (NSArray *)installedSkinRecords; + +/// 删除指定皮肤的本地资源目录(用于“我的皮肤 - 删除”)。 ++ (BOOL)removeInstalledSkinWithId:(NSString *)skinId + error:(NSError * _Nullable __autoreleasing *)error; + +/// 记录皮肤元数据(下载完成后调用,方便“我的皮肤”读取)。 ++ (void)recordInstalledSkinWithId:(NSString *)skinId + name:(NSString *)name + preview:(nullable NSString *)preview + zipURL:(nullable NSString *)zipURL; + @end NS_ASSUME_NONNULL_END diff --git a/Shared/KBSkinInstallBridge.m b/Shared/KBSkinInstallBridge.m index 30a44d4..bf75e33 100644 --- a/Shared/KBSkinInstallBridge.m +++ b/Shared/KBSkinInstallBridge.m @@ -23,9 +23,142 @@ static NSString * const kKBSkinPendingZipKey = @"zipName"; static NSString * const kKBSkinPendingKindKey = @"kind"; static NSString * const kKBSkinPendingTimestampKey = @"timestamp"; static NSString * const kKBSkinPendingIconShortKey = @"iconShortNames"; +static NSString * const kKBSkinMetadataFileName = @"metadata.plist"; +static NSString * const kKBSkinMetadataNameKey = @"name"; +static NSString * const kKBSkinMetadataPreviewKey = @"preview"; +static NSString * const kKBSkinMetadataZipKey = @"zip_url"; +static NSString * const kKBSkinMetadataInstalledKey = @"installed_at"; + +@interface KBSkinDownloadRecord () +- (instancetype)initWithSkinId:(NSString *)skinId metadata:(NSDictionary *)metadata; +@end + +@implementation KBSkinDownloadRecord + +- (instancetype)initWithSkinId:(NSString *)skinId metadata:(NSDictionary *)metadata { + if (self = [super init]) { + _skinId = skinId.length ? skinId : @""; + NSString *name = [metadata[kKBSkinMetadataNameKey] isKindOfClass:NSString.class] ? metadata[kKBSkinMetadataNameKey] : nil; + _name = name.length > 0 ? name : (_skinId.length ? _skinId : @""); + NSString *preview = [metadata[kKBSkinMetadataPreviewKey] isKindOfClass:NSString.class] ? metadata[kKBSkinMetadataPreviewKey] : nil; + _previewImage = preview.length > 0 ? preview : nil; + NSString *zip = [metadata[kKBSkinMetadataZipKey] isKindOfClass:NSString.class] ? metadata[kKBSkinMetadataZipKey] : nil; + _zipURL = zip.length > 0 ? zip : nil; + NSTimeInterval installed = 0; + id installObj = metadata[kKBSkinMetadataInstalledKey]; + if ([installObj respondsToSelector:@selector(doubleValue)]) { + installed = [installObj doubleValue]; + } + if (installed <= 0) { + installed = [[NSDate date] timeIntervalSince1970]; + } + _installedAt = installed; + } + return self; +} + +@end @implementation KBSkinInstallBridge ++ (NSString *)kb_skinsRootPath { + NSFileManager *fm = [NSFileManager defaultManager]; + NSURL *containerURL = [fm containerURLForSecurityApplicationGroupIdentifier:AppGroup]; + NSString *root = containerURL.path; + if (root.length == 0) { + NSArray *dirs = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + root = dirs.firstObject ?: NSTemporaryDirectory(); + } + return [root stringByAppendingPathComponent:@"Skins"]; +} + ++ (NSString *)kb_metadataPathForSkinId:(NSString *)skinId { + if (skinId.length == 0) { return nil; } + NSString *skinRoot = [[self kb_skinsRootPath] stringByAppendingPathComponent:skinId]; + return [skinRoot stringByAppendingPathComponent:kKBSkinMetadataFileName]; +} + ++ (void)kb_storeMetadataForSkinId:(NSString *)skinId + name:(NSString *)name + preview:(NSString *)preview + zipURL:(NSString *)zipURL { + if (skinId.length == 0) { return; } + dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ + NSString *metaPath = [self kb_metadataPathForSkinId:skinId]; + if (metaPath.length == 0) { return; } + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *dir = [metaPath stringByDeletingLastPathComponent]; + [fm createDirectoryAtPath:dir withIntermediateDirectories:YES attributes:nil error:nil]; + NSTimeInterval now = [[NSDate date] timeIntervalSince1970]; + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + dict[@"id"] = skinId; + if (name.length > 0) { + dict[kKBSkinMetadataNameKey] = name; + } + if (preview.length > 0) { + dict[kKBSkinMetadataPreviewKey] = preview; + } + if (zipURL.length > 0) { + dict[kKBSkinMetadataZipKey] = zipURL; + } + dict[kKBSkinMetadataInstalledKey] = @(now); + [dict writeToFile:metaPath atomically:YES]; + }); +} + ++ (NSArray *)installedSkinRecords { + NSString *root = [self kb_skinsRootPath]; + NSFileManager *fm = [NSFileManager defaultManager]; + BOOL isDir = NO; + if (![fm fileExistsAtPath:root isDirectory:&isDir] || !isDir) { + return @[]; + } + NSArray *entries = [fm contentsOfDirectoryAtPath:root error:NULL] ?: @[]; + NSMutableArray *records = [NSMutableArray array]; + for (NSString *entry in entries) { + if (entry.length == 0 || [entry hasPrefix:@"."]) { continue; } + NSString *path = [root stringByAppendingPathComponent:entry]; + BOOL isSubDir = NO; + if (![fm fileExistsAtPath:path isDirectory:&isSubDir] || !isSubDir) { continue; } + NSString *metaPath = [path stringByAppendingPathComponent:kKBSkinMetadataFileName]; + NSDictionary *meta = [NSDictionary dictionaryWithContentsOfFile:metaPath] ?: @{}; + KBSkinDownloadRecord *record = [[KBSkinDownloadRecord alloc] initWithSkinId:entry metadata:meta]; + [records addObject:record]; + } + [records sortUsingComparator:^NSComparisonResult(KBSkinDownloadRecord *obj1, KBSkinDownloadRecord *obj2) { + if (obj1.installedAt == obj2.installedAt) { return NSOrderedSame; } + return (obj1.installedAt > obj2.installedAt) ? NSOrderedAscending : NSOrderedDescending; + }]; + return records.copy; +} + ++ (BOOL)removeInstalledSkinWithId:(NSString *)skinId + error:(NSError * __autoreleasing *)error { + if (skinId.length == 0) { return YES; } + NSString *root = [self kb_skinsRootPath]; + NSString *skinPath = [root stringByAppendingPathComponent:skinId]; + NSFileManager *fm = [NSFileManager defaultManager]; + BOOL isDir = NO; + if (![fm fileExistsAtPath:skinPath isDirectory:&isDir] || !isDir) { + return YES; + } + BOOL ok = [fm removeItemAtPath:skinPath error:error]; + if (ok) { + NSString *currentId = [KBSkinManager shared].current.skinId; + if ([currentId isKindOfClass:NSString.class] && [currentId isEqualToString:skinId]) { + [[KBSkinManager shared] resetToDefault]; + } + } + return ok; +} + ++ (void)recordInstalledSkinWithId:(NSString *)skinId + name:(NSString *)name + preview:(NSString *)preview + zipURL:(NSString *)zipURL { + [self kb_storeMetadataForSkinId:skinId name:name preview:preview zipURL:zipURL]; +} + + (NSDictionary *)defaultIconShortNames { static NSDictionary *map; static dispatch_once_t onceToken; @@ -302,6 +435,13 @@ static NSString * const kKBSkinPendingIconShortKey = @"iconShortNames"; userInfo:nil]; } if (completion) completion(ok, finalError); + if (ok) { + NSString *preview = [skinJSON[@"preview"] isKindOfClass:NSString.class] ? skinJSON[@"preview"] : nil; + [self recordInstalledSkinWithId:skinId + name:name ?: skinId + preview:preview + zipURL:zipURL]; + } }); } @@ -538,6 +678,12 @@ static NSString * const kKBSkinPendingIconShortKey = @"iconShortNames"; code:KBSkinBridgeErrorApplyFailed userInfo:nil]; } + if (ok) { + [self recordInstalledSkinWithId:skinId + name:name ?: skinId + preview:nil + zipURL:zipName]; + } return ok; #endif } diff --git a/keyBoard/Class/Manager/KBSkinService.m b/keyBoard/Class/Manager/KBSkinService.m index 49481e1..24acdb2 100644 --- a/keyBoard/Class/Manager/KBSkinService.m +++ b/keyBoard/Class/Manager/KBSkinService.m @@ -274,6 +274,13 @@ } if (completion) completion(ok); + if (ok) { + NSString *preview = [skin[@"preview"] isKindOfClass:NSString.class] ? skin[@"preview"] : nil; + [KBSkinInstallBridge recordInstalledSkinWithId:skinId + name:name + preview:preview + zipURL:zipName]; + } [KBHUD showInfo:(ok ? KBLocalized(@"已应用,切到键盘查看") : KBLocalized(@"应用皮肤失败"))]; }); } diff --git a/keyBoard/Class/Me/M/KBMyTheme.h b/keyBoard/Class/Me/M/KBMyTheme.h index 4722e9e..12557df 100644 --- a/keyBoard/Class/Me/M/KBMyTheme.h +++ b/keyBoard/Class/Me/M/KBMyTheme.h @@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) CGFloat themePrice; @property (nonatomic, copy, nullable) NSArray *themeTag; @property (nonatomic, copy, nullable) NSString *themeDownload; +@property (nonatomic, copy, nullable) NSString *themePreviewImageUrl; @property (nonatomic, assign) NSInteger themeStyle; @property (nonatomic, assign) BOOL themeStatus; @property (nonatomic, assign) NSInteger themePurchasesNumber; diff --git a/keyBoard/Class/Me/V/KBSkinDetailHeaderCell.m b/keyBoard/Class/Me/V/KBSkinDetailHeaderCell.m index d87b223..c2d2b91 100644 --- a/keyBoard/Class/Me/V/KBSkinDetailHeaderCell.m +++ b/keyBoard/Class/Me/V/KBSkinDetailHeaderCell.m @@ -45,12 +45,12 @@ NSString *downloadText = [NSString stringWithFormat:@"%@: %@", KBLocalized(@"Download"), download]; self.leftLabel.text = title; self.rightLabel.text = downloadText; - UIImage *placeholder = [UIImage imageNamed:@"shop_headbigBg_icon"]; - if (detail.themePreviewImageUrl.length) { - [self.coverView kb_setImageURL:detail.themePreviewImageUrl placeholder:placeholder]; - } else { - self.coverView.image = placeholder; - } +// UIImage *placeholder = [UIImage imageNamed:@"shop_headbigBg_icon"]; +// if (detail.themePreviewImageUrl.length) { + [self.coverView kb_setImageURL:detail.themePreviewImageUrl placeholder:KBPlaceholderImage]; +// } else { +// self.coverView.image = placeholder; +// } } #pragma mark - Lazy diff --git a/keyBoard/Class/Me/V/MySkinCell.h b/keyBoard/Class/Me/V/MySkinCell.h index e8d1359..6ca4f22 100644 --- a/keyBoard/Class/Me/V/MySkinCell.h +++ b/keyBoard/Class/Me/V/MySkinCell.h @@ -13,8 +13,8 @@ NS_ASSUME_NONNULL_BEGIN /// 是否处于编辑态(显示左上角选择圆点) @property (nonatomic, assign, getter=isEditing) BOOL editing; -/// 配置显示内容(演示仅传标题与占位图) -- (void)configWithTitle:(NSString *)title image:(nullable UIImage *)image; +/// 配置显示内容(标题 + 远程预览图) +- (void)configWithTitle:(NSString *)title imageURL:(nullable NSString *)imageURL; /// 根据选中状态刷新打勾样式(供外部在 select/deselect 时调用) - (void)updateSelected:(BOOL)selected; @@ -22,4 +22,3 @@ NS_ASSUME_NONNULL_BEGIN @end NS_ASSUME_NONNULL_END - diff --git a/keyBoard/Class/Me/V/MySkinCell.m b/keyBoard/Class/Me/V/MySkinCell.m index a6645b5..9499377 100644 --- a/keyBoard/Class/Me/V/MySkinCell.m +++ b/keyBoard/Class/Me/V/MySkinCell.m @@ -6,6 +6,7 @@ #import "MySkinCell.h" #import #import "UIColor+Extension.h" +#import "UIImageView+KBWebImage.h" // 左上角小圆点 + 对勾视图(无需切图,用 CAShapeLayer 绘制) @interface KBSelectDotView : UIView @@ -122,10 +123,11 @@ [self updateSelected:selected]; } -- (void)configWithTitle:(NSString *)title image:(UIImage *)image { +- (void)configWithTitle:(NSString *)title imageURL:(NSString *)imageURL { self.titleLabel.text = title.length ? title : @"Dopamine"; - // 简化:展示本地占位色 self.coverView.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1.0]; + UIImage *placeholder = [UIImage imageNamed:@"my_skin_placeholder"]; + [self.coverView kb_setImageURL:imageURL placeholder:placeholder]; } - (void)setEditing:(BOOL)editing { diff --git a/keyBoard/Class/Me/VC/MySkinVC.m b/keyBoard/Class/Me/VC/MySkinVC.m index ba9bb7c..7ffcf3b 100644 --- a/keyBoard/Class/Me/VC/MySkinVC.m +++ b/keyBoard/Class/Me/VC/MySkinVC.m @@ -76,7 +76,7 @@ static NSString * const kMySkinCellId = @"kMySkinCellId"; [self.collectionView kb_endLoadingForEmpty]; // 下拉刷新(演示网络加载 + 空态切换) - self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(fetchPurchasedThemes)]; + self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(fetchDownloadedThemes)]; // 首次进入自动刷新 [self.collectionView.mj_header beginRefreshing]; @@ -85,9 +85,9 @@ static NSString * const kMySkinCellId = @"kMySkinCellId"; self.bottomView.hidden = YES; } -- (void)fetchPurchasedThemes { +- (void)fetchDownloadedThemes { KBWeakSelf - [self.viewModel fetchPurchasedThemesWithCompletion:^(NSArray * _Nullable themes, NSError * _Nullable error) { + [self.viewModel fetchDownloadedThemesWithCompletion:^(NSArray * _Nullable themes, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ if ([weakSelf.collectionView.mj_header isRefreshing]) { [weakSelf.collectionView.mj_header endRefreshing]; @@ -154,29 +154,19 @@ static NSString * const kMySkinCellId = @"kMySkinCellId"; NSArray *selectedIndexPaths = [[self.collectionView indexPathsForSelectedItems] sortedArrayUsingSelector:@selector(compare:)]; if (selectedIndexPaths.count == 0) return; - NSMutableArray *themeIds = [NSMutableArray arrayWithCapacity:selectedIndexPaths.count]; + NSMutableArray *themeIds = [NSMutableArray arrayWithCapacity:selectedIndexPaths.count]; for (NSIndexPath *ip in selectedIndexPaths) { if (ip.item >= self.data.count) { continue; } KBMyTheme *theme = self.data[ip.item]; id themeIdValue = theme.themeId; - NSNumber *numberId = nil; - if ([themeIdValue isKindOfClass:[NSNumber class]]) { - numberId = (NSNumber *)themeIdValue; - } else if ([themeIdValue isKindOfClass:[NSString class]]) { - NSString *idString = (NSString *)themeIdValue; - if (idString.length > 0) { - static NSCharacterSet *nonDigitSet; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - nonDigitSet = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; - }); - if ([idString rangeOfCharacterFromSet:nonDigitSet].location == NSNotFound) { - numberId = @([idString longLongValue]); - } - } + NSString *stringId = nil; + if ([themeIdValue isKindOfClass:[NSString class]]) { + stringId = [(NSString *)themeIdValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + } else if ([themeIdValue respondsToSelector:@selector(stringValue)]) { + stringId = [[themeIdValue stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } - if (numberId) { - [themeIds addObject:numberId]; + if (stringId.length > 0) { + [themeIds addObject:stringId]; } } @@ -187,12 +177,12 @@ static NSString * const kMySkinCellId = @"kMySkinCellId"; [KBHUD show]; KBWeakSelf - [self.viewModel deletePurchasedThemesWithThemeIds:themeIds - completion:^(BOOL success, NSError * _Nullable error) { + [self.viewModel deleteDownloadedThemesWithIds:themeIds + completion:^(BOOL success, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ [KBHUD dismiss]; if (!success) { - NSString *msg = error.localizedDescription ?: KBLocalized(@"Network error"); + NSString *msg = error.localizedDescription ?: KBLocalized(@"Operation failed"); [KBHUD showInfo:msg]; return; } @@ -242,7 +232,7 @@ static NSString * const kMySkinCellId = @"kMySkinCellId"; - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { MySkinCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kMySkinCellId forIndexPath:indexPath]; KBMyTheme *theme = self.data[indexPath.item]; - [cell configWithTitle:theme.themeName image:nil]; + [cell configWithTitle:theme.themeName imageURL:theme.themePreviewImageUrl]; cell.editing = self.isEditingMode; // 控制是否显示选择圆点 // 同步选中状态(复用时) BOOL selected = [[collectionView indexPathsForSelectedItems] containsObject:indexPath]; diff --git a/keyBoard/Class/Me/VM/KBMyVM.h b/keyBoard/Class/Me/VM/KBMyVM.h index daafd05..e0c195d 100644 --- a/keyBoard/Class/Me/VM/KBMyVM.h +++ b/keyBoard/Class/Me/VM/KBMyVM.h @@ -33,11 +33,11 @@ typedef void(^KBDeleteThemesCompletion)(BOOL success, NSError *_Nullable error); /// 用户人设列表(/character/listByUser) - (void)fetchCharacterListByUserWithCompletion:(KBCharacterListCompletion)completion; -/// 已购买主题列表(/themes/purchased) -- (void)fetchPurchasedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion; -/// 批量删除主题(/user-themes/batch-delete) -- (void)deletePurchasedThemesWithThemeIds:(NSArray *)themeIds - completion:(KBDeleteThemesCompletion)completion; +/// 本地已下载主题列表 +- (void)fetchDownloadedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion; +/// 删除本地主题资源 +- (void)deleteDownloadedThemesWithIds:(NSArray *)themeIds + completion:(KBDeleteThemesCompletion)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 9d5af94..bf6ed9c 100644 --- a/keyBoard/Class/Me/VM/KBMyVM.m +++ b/keyBoard/Class/Me/VM/KBMyVM.m @@ -12,6 +12,7 @@ #import "KBAPI.h" //#import #import "KBMyMainModel.h" +#import "KBSkinInstallBridge.h" NSString * const KBUserCharacterDeletedNotification = @"KBUserCharacterDeletedNotification"; @@ -86,36 +87,26 @@ 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; +- (void)fetchDownloadedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion { + dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ + NSArray *records = [KBSkinInstallBridge installedSkinRecords]; + NSMutableArray *themes = [NSMutableArray arrayWithCapacity:records.count]; + for (KBSkinDownloadRecord *record in records) { + KBMyTheme *theme = [KBMyTheme new]; + theme.themeId = record.skinId; + theme.themeName = record.name; + theme.themeDownload = record.zipURL; + theme.themePreviewImageUrl = record.previewImage; + [themes addObject:theme]; } - - 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); - }]; + dispatch_async(dispatch_get_main_queue(), ^{ + if (completion) completion(themes.copy, nil); + }); + }); } -- (void)deletePurchasedThemesWithThemeIds:(NSArray *)themeIds - completion:(KBDeleteThemesCompletion)completion { +- (void)deleteDownloadedThemesWithIds:(NSArray *)themeIds + completion:(KBDeleteThemesCompletion)completion { if (themeIds.count == 0) { if (completion) { NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain @@ -126,18 +117,22 @@ NSString * const KBUserCharacterDeletedNotification = @"KBUserCharacterDeletedNo return; } - NSDictionary *params = @{@"themeIds": themeIds}; - [[KBNetworkManager shared] POST:API_THEME_BATCH_DELETE - jsonBody:params - headers:nil - autoShowBusinessError:YES - completion:^(NSDictionary * _Nullable json, - NSURLResponse * _Nullable response, - NSError * _Nullable error) { - if (completion) { - completion(error == nil, error); + dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ + NSError *lastError = nil; + for (NSString *skinId in themeIds) { + if (skinId.length == 0) { continue; } + BOOL ok = [KBSkinInstallBridge removeInstalledSkinWithId:skinId error:&lastError]; + if (!ok) { + break; + } } - }]; + BOOL success = (lastError == nil); + dispatch_async(dispatch_get_main_queue(), ^{ + if (completion) { + completion(success, lastError); + } + }); + }); } /// 更新用户人设排序 diff --git a/keyBoard/Class/Shop/VC/KBSkinDetailVC.m b/keyBoard/Class/Shop/VC/KBSkinDetailVC.m index 482ab79..e568484 100644 --- a/keyBoard/Class/Shop/VC/KBSkinDetailVC.m +++ b/keyBoard/Class/Shop/VC/KBSkinDetailVC.m @@ -264,6 +264,9 @@ typedef NS_ENUM(NSInteger, KBSkinDetailSection) { skin[@"zip_url"] = self.detailModel.themeDownloadUrl ? self.detailModel.themeDownloadUrl : @""; + if (self.detailModel.themePreviewImageUrl.length > 0) { + skin[@"preview"] = self.detailModel.themePreviewImageUrl; + } [[KBSkinService shared] applySkinWithJSON:skin fromViewController:self