2
This commit is contained in:
@@ -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" // 购买主题
|
||||
|
||||
@@ -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<KBSkinDownloadRecord *> *)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
|
||||
|
||||
@@ -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<NSString *> *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<KBSkinDownloadRecord *> *)installedSkinRecords {
|
||||
NSString *root = [self kb_skinsRootPath];
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
BOOL isDir = NO;
|
||||
if (![fm fileExistsAtPath:root isDirectory:&isDir] || !isDir) {
|
||||
return @[];
|
||||
}
|
||||
NSArray<NSString *> *entries = [fm contentsOfDirectoryAtPath:root error:NULL] ?: @[];
|
||||
NSMutableArray<KBSkinDownloadRecord *> *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<NSString *,NSString *> *)defaultIconShortNames {
|
||||
static NSDictionary<NSString *, NSString *> *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
|
||||
}
|
||||
|
||||
@@ -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(@"应用皮肤失败"))];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (nonatomic, assign) CGFloat themePrice;
|
||||
@property (nonatomic, copy, nullable) NSArray<NSString *> *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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#import "MySkinCell.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#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 {
|
||||
|
||||
@@ -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<KBMyTheme *> * _Nullable themes, NSError * _Nullable error) {
|
||||
[self.viewModel fetchDownloadedThemesWithCompletion:^(NSArray<KBMyTheme *> * _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<NSIndexPath *> *selectedIndexPaths = [[self.collectionView indexPathsForSelectedItems] sortedArrayUsingSelector:@selector(compare:)];
|
||||
if (selectedIndexPaths.count == 0) return;
|
||||
|
||||
NSMutableArray<NSNumber *> *themeIds = [NSMutableArray arrayWithCapacity:selectedIndexPaths.count];
|
||||
NSMutableArray<NSString *> *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
|
||||
[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];
|
||||
|
||||
@@ -33,10 +33,10 @@ 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<NSNumber *> *)themeIds
|
||||
/// 本地已下载主题列表
|
||||
- (void)fetchDownloadedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion;
|
||||
/// 删除本地主题资源
|
||||
- (void)deleteDownloadedThemesWithIds:(NSArray<NSString *> *)themeIds
|
||||
completion:(KBDeleteThemesCompletion)completion;
|
||||
/// 更新用户人设排序
|
||||
- (void)updateUserCharacterSortWithSortArray:(NSArray<NSNumber *> *)sortArray
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#import "KBAPI.h"
|
||||
//#import <MJExtension/MJExtension.h>
|
||||
#import "KBMyMainModel.h"
|
||||
#import "KBSkinInstallBridge.h"
|
||||
|
||||
NSString * const KBUserCharacterDeletedNotification = @"KBUserCharacterDeletedNotification";
|
||||
|
||||
@@ -86,35 +87,25 @@ 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<KBSkinDownloadRecord *> *records = [KBSkinInstallBridge installedSkinRecords];
|
||||
NSMutableArray<KBMyTheme *> *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<KBMyTheme *> *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<NSNumber *> *)themeIds
|
||||
- (void)deleteDownloadedThemesWithIds:(NSArray<NSString *> *)themeIds
|
||||
completion:(KBDeleteThemesCompletion)completion {
|
||||
if (themeIds.count == 0) {
|
||||
if (completion) {
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// 更新用户人设排序
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user