2
This commit is contained in:
@@ -49,7 +49,6 @@
|
|||||||
#define API_THEME_LIST_ALL_STYLES @"/themes/listAllStyles" // 查询所有主题风格
|
#define API_THEME_LIST_ALL_STYLES @"/themes/listAllStyles" // 查询所有主题风格
|
||||||
#define API_THEME_LIST_BY_STYLE @"/themes/listByStyle" // 按风格查询主题列表
|
#define API_THEME_LIST_BY_STYLE @"/themes/listByStyle" // 按风格查询主题列表
|
||||||
#define API_THEME_PURCHASED @"/themes/purchased" // 查询已购买主题
|
#define API_THEME_PURCHASED @"/themes/purchased" // 查询已购买主题
|
||||||
#define API_THEME_BATCH_DELETE @"/user-themes/batch-delete" // 批量删除用户主题
|
|
||||||
#define API_WALLET_BALANCE @"/wallet/balance" // 查询钱包余额
|
#define API_WALLET_BALANCE @"/wallet/balance" // 查询钱包余额
|
||||||
#define API_THEME_DETAIL @"/themes/detail" // 查询主题详情
|
#define API_THEME_DETAIL @"/themes/detail" // 查询主题详情
|
||||||
#define API_THEME_PURCHASE @"/themes/purchase" // 购买主题
|
#define API_THEME_PURCHASE @"/themes/purchase" // 购买主题
|
||||||
|
|||||||
@@ -24,6 +24,15 @@ typedef NS_ENUM(NSUInteger, KBSkinBridgeErrorCode) {
|
|||||||
|
|
||||||
typedef void (^KBSkinInstallConsumeCompletion)(BOOL success, NSError * _Nullable error);
|
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
|
@interface KBSkinInstallBridge : NSObject
|
||||||
|
|
||||||
/// 默认图标短文件名映射(从 KBSkinIconMap.strings 读取)。
|
/// 默认图标短文件名映射(从 KBSkinIconMap.strings 读取)。
|
||||||
@@ -56,6 +65,19 @@ typedef void (^KBSkinInstallConsumeCompletion)(BOOL success, NSError * _Nullable
|
|||||||
+ (void)consumePendingRequestFromBundle:(NSBundle *)bundle
|
+ (void)consumePendingRequestFromBundle:(NSBundle *)bundle
|
||||||
completion:(nullable KBSkinInstallConsumeCompletion)completion;
|
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
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -23,9 +23,142 @@ static NSString * const kKBSkinPendingZipKey = @"zipName";
|
|||||||
static NSString * const kKBSkinPendingKindKey = @"kind";
|
static NSString * const kKBSkinPendingKindKey = @"kind";
|
||||||
static NSString * const kKBSkinPendingTimestampKey = @"timestamp";
|
static NSString * const kKBSkinPendingTimestampKey = @"timestamp";
|
||||||
static NSString * const kKBSkinPendingIconShortKey = @"iconShortNames";
|
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
|
@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 {
|
+ (NSDictionary<NSString *,NSString *> *)defaultIconShortNames {
|
||||||
static NSDictionary<NSString *, NSString *> *map;
|
static NSDictionary<NSString *, NSString *> *map;
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
@@ -302,6 +435,13 @@ static NSString * const kKBSkinPendingIconShortKey = @"iconShortNames";
|
|||||||
userInfo:nil];
|
userInfo:nil];
|
||||||
}
|
}
|
||||||
if (completion) completion(ok, finalError);
|
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
|
code:KBSkinBridgeErrorApplyFailed
|
||||||
userInfo:nil];
|
userInfo:nil];
|
||||||
}
|
}
|
||||||
|
if (ok) {
|
||||||
|
[self recordInstalledSkinWithId:skinId
|
||||||
|
name:name ?: skinId
|
||||||
|
preview:nil
|
||||||
|
zipURL:zipName];
|
||||||
|
}
|
||||||
return ok;
|
return ok;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,6 +274,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (completion) completion(ok);
|
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(@"应用皮肤失败"))];
|
[KBHUD showInfo:(ok ? KBLocalized(@"已应用,切到键盘查看") : KBLocalized(@"应用皮肤失败"))];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
@property (nonatomic, assign) CGFloat themePrice;
|
@property (nonatomic, assign) CGFloat themePrice;
|
||||||
@property (nonatomic, copy, nullable) NSArray<NSString *> *themeTag;
|
@property (nonatomic, copy, nullable) NSArray<NSString *> *themeTag;
|
||||||
@property (nonatomic, copy, nullable) NSString *themeDownload;
|
@property (nonatomic, copy, nullable) NSString *themeDownload;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *themePreviewImageUrl;
|
||||||
@property (nonatomic, assign) NSInteger themeStyle;
|
@property (nonatomic, assign) NSInteger themeStyle;
|
||||||
@property (nonatomic, assign) BOOL themeStatus;
|
@property (nonatomic, assign) BOOL themeStatus;
|
||||||
@property (nonatomic, assign) NSInteger themePurchasesNumber;
|
@property (nonatomic, assign) NSInteger themePurchasesNumber;
|
||||||
|
|||||||
@@ -45,12 +45,12 @@
|
|||||||
NSString *downloadText = [NSString stringWithFormat:@"%@: %@", KBLocalized(@"Download"), download];
|
NSString *downloadText = [NSString stringWithFormat:@"%@: %@", KBLocalized(@"Download"), download];
|
||||||
self.leftLabel.text = title;
|
self.leftLabel.text = title;
|
||||||
self.rightLabel.text = downloadText;
|
self.rightLabel.text = downloadText;
|
||||||
UIImage *placeholder = [UIImage imageNamed:@"shop_headbigBg_icon"];
|
// UIImage *placeholder = [UIImage imageNamed:@"shop_headbigBg_icon"];
|
||||||
if (detail.themePreviewImageUrl.length) {
|
// if (detail.themePreviewImageUrl.length) {
|
||||||
[self.coverView kb_setImageURL:detail.themePreviewImageUrl placeholder:placeholder];
|
[self.coverView kb_setImageURL:detail.themePreviewImageUrl placeholder:KBPlaceholderImage];
|
||||||
} else {
|
// } else {
|
||||||
self.coverView.image = placeholder;
|
// self.coverView.image = placeholder;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Lazy
|
#pragma mark - Lazy
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
/// 是否处于编辑态(显示左上角选择圆点)
|
/// 是否处于编辑态(显示左上角选择圆点)
|
||||||
@property (nonatomic, assign, getter=isEditing) BOOL editing;
|
@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 时调用)
|
/// 根据选中状态刷新打勾样式(供外部在 select/deselect 时调用)
|
||||||
- (void)updateSelected:(BOOL)selected;
|
- (void)updateSelected:(BOOL)selected;
|
||||||
@@ -22,4 +22,3 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#import "MySkinCell.h"
|
#import "MySkinCell.h"
|
||||||
#import <Masonry/Masonry.h>
|
#import <Masonry/Masonry.h>
|
||||||
#import "UIColor+Extension.h"
|
#import "UIColor+Extension.h"
|
||||||
|
#import "UIImageView+KBWebImage.h"
|
||||||
|
|
||||||
// 左上角小圆点 + 对勾视图(无需切图,用 CAShapeLayer 绘制)
|
// 左上角小圆点 + 对勾视图(无需切图,用 CAShapeLayer 绘制)
|
||||||
@interface KBSelectDotView : UIView
|
@interface KBSelectDotView : UIView
|
||||||
@@ -122,10 +123,11 @@
|
|||||||
[self updateSelected:selected];
|
[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.titleLabel.text = title.length ? title : @"Dopamine";
|
||||||
// 简化:展示本地占位色
|
|
||||||
self.coverView.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1.0];
|
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 {
|
- (void)setEditing:(BOOL)editing {
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ static NSString * const kMySkinCellId = @"kMySkinCellId";
|
|||||||
[self.collectionView kb_endLoadingForEmpty];
|
[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];
|
[self.collectionView.mj_header beginRefreshing];
|
||||||
@@ -85,9 +85,9 @@ static NSString * const kMySkinCellId = @"kMySkinCellId";
|
|||||||
self.bottomView.hidden = YES;
|
self.bottomView.hidden = YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)fetchPurchasedThemes {
|
- (void)fetchDownloadedThemes {
|
||||||
KBWeakSelf
|
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(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
if ([weakSelf.collectionView.mj_header isRefreshing]) {
|
if ([weakSelf.collectionView.mj_header isRefreshing]) {
|
||||||
[weakSelf.collectionView.mj_header endRefreshing];
|
[weakSelf.collectionView.mj_header endRefreshing];
|
||||||
@@ -154,29 +154,19 @@ static NSString * const kMySkinCellId = @"kMySkinCellId";
|
|||||||
NSArray<NSIndexPath *> *selectedIndexPaths = [[self.collectionView indexPathsForSelectedItems] sortedArrayUsingSelector:@selector(compare:)];
|
NSArray<NSIndexPath *> *selectedIndexPaths = [[self.collectionView indexPathsForSelectedItems] sortedArrayUsingSelector:@selector(compare:)];
|
||||||
if (selectedIndexPaths.count == 0) return;
|
if (selectedIndexPaths.count == 0) return;
|
||||||
|
|
||||||
NSMutableArray<NSNumber *> *themeIds = [NSMutableArray arrayWithCapacity:selectedIndexPaths.count];
|
NSMutableArray<NSString *> *themeIds = [NSMutableArray arrayWithCapacity:selectedIndexPaths.count];
|
||||||
for (NSIndexPath *ip in selectedIndexPaths) {
|
for (NSIndexPath *ip in selectedIndexPaths) {
|
||||||
if (ip.item >= self.data.count) { continue; }
|
if (ip.item >= self.data.count) { continue; }
|
||||||
KBMyTheme *theme = self.data[ip.item];
|
KBMyTheme *theme = self.data[ip.item];
|
||||||
id themeIdValue = theme.themeId;
|
id themeIdValue = theme.themeId;
|
||||||
NSNumber *numberId = nil;
|
NSString *stringId = nil;
|
||||||
if ([themeIdValue isKindOfClass:[NSNumber class]]) {
|
if ([themeIdValue isKindOfClass:[NSString class]]) {
|
||||||
numberId = (NSNumber *)themeIdValue;
|
stringId = [(NSString *)themeIdValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||||
} else if ([themeIdValue isKindOfClass:[NSString class]]) {
|
} else if ([themeIdValue respondsToSelector:@selector(stringValue)]) {
|
||||||
NSString *idString = (NSString *)themeIdValue;
|
stringId = [[themeIdValue stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||||
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]);
|
|
||||||
}
|
}
|
||||||
}
|
if (stringId.length > 0) {
|
||||||
}
|
[themeIds addObject:stringId];
|
||||||
if (numberId) {
|
|
||||||
[themeIds addObject:numberId];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,12 +177,12 @@ static NSString * const kMySkinCellId = @"kMySkinCellId";
|
|||||||
|
|
||||||
[KBHUD show];
|
[KBHUD show];
|
||||||
KBWeakSelf
|
KBWeakSelf
|
||||||
[self.viewModel deletePurchasedThemesWithThemeIds:themeIds
|
[self.viewModel deleteDownloadedThemesWithIds:themeIds
|
||||||
completion:^(BOOL success, NSError * _Nullable error) {
|
completion:^(BOOL success, NSError * _Nullable error) {
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[KBHUD dismiss];
|
[KBHUD dismiss];
|
||||||
if (!success) {
|
if (!success) {
|
||||||
NSString *msg = error.localizedDescription ?: KBLocalized(@"Network error");
|
NSString *msg = error.localizedDescription ?: KBLocalized(@"Operation failed");
|
||||||
[KBHUD showInfo:msg];
|
[KBHUD showInfo:msg];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -242,7 +232,7 @@ static NSString * const kMySkinCellId = @"kMySkinCellId";
|
|||||||
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
MySkinCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kMySkinCellId forIndexPath:indexPath];
|
MySkinCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kMySkinCellId forIndexPath:indexPath];
|
||||||
KBMyTheme *theme = self.data[indexPath.item];
|
KBMyTheme *theme = self.data[indexPath.item];
|
||||||
[cell configWithTitle:theme.themeName image:nil];
|
[cell configWithTitle:theme.themeName imageURL:theme.themePreviewImageUrl];
|
||||||
cell.editing = self.isEditingMode; // 控制是否显示选择圆点
|
cell.editing = self.isEditingMode; // 控制是否显示选择圆点
|
||||||
// 同步选中状态(复用时)
|
// 同步选中状态(复用时)
|
||||||
BOOL selected = [[collectionView indexPathsForSelectedItems] containsObject:indexPath];
|
BOOL selected = [[collectionView indexPathsForSelectedItems] containsObject:indexPath];
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ typedef void(^KBDeleteThemesCompletion)(BOOL success, NSError *_Nullable error);
|
|||||||
|
|
||||||
/// 用户人设列表(/character/listByUser)
|
/// 用户人设列表(/character/listByUser)
|
||||||
- (void)fetchCharacterListByUserWithCompletion:(KBCharacterListCompletion)completion;
|
- (void)fetchCharacterListByUserWithCompletion:(KBCharacterListCompletion)completion;
|
||||||
/// 已购买主题列表(/themes/purchased)
|
/// 本地已下载主题列表
|
||||||
- (void)fetchPurchasedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion;
|
- (void)fetchDownloadedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion;
|
||||||
/// 批量删除主题(/user-themes/batch-delete)
|
/// 删除本地主题资源
|
||||||
- (void)deletePurchasedThemesWithThemeIds:(NSArray<NSNumber *> *)themeIds
|
- (void)deleteDownloadedThemesWithIds:(NSArray<NSString *> *)themeIds
|
||||||
completion:(KBDeleteThemesCompletion)completion;
|
completion:(KBDeleteThemesCompletion)completion;
|
||||||
/// 更新用户人设排序
|
/// 更新用户人设排序
|
||||||
- (void)updateUserCharacterSortWithSortArray:(NSArray<NSNumber *> *)sortArray
|
- (void)updateUserCharacterSortWithSortArray:(NSArray<NSNumber *> *)sortArray
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#import "KBAPI.h"
|
#import "KBAPI.h"
|
||||||
//#import <MJExtension/MJExtension.h>
|
//#import <MJExtension/MJExtension.h>
|
||||||
#import "KBMyMainModel.h"
|
#import "KBMyMainModel.h"
|
||||||
|
#import "KBSkinInstallBridge.h"
|
||||||
|
|
||||||
NSString * const KBUserCharacterDeletedNotification = @"KBUserCharacterDeletedNotification";
|
NSString * const KBUserCharacterDeletedNotification = @"KBUserCharacterDeletedNotification";
|
||||||
|
|
||||||
@@ -86,35 +87,25 @@ NSString * const KBUserCharacterDeletedNotification = @"KBUserCharacterDeletedNo
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)fetchPurchasedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion {
|
- (void)fetchDownloadedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion {
|
||||||
[[KBNetworkManager shared] GET:API_THEME_PURCHASED
|
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
||||||
parameters:nil
|
NSArray<KBSkinDownloadRecord *> *records = [KBSkinInstallBridge installedSkinRecords];
|
||||||
headers:nil
|
NSMutableArray<KBMyTheme *> *themes = [NSMutableArray arrayWithCapacity:records.count];
|
||||||
autoShowBusinessError:NO
|
for (KBSkinDownloadRecord *record in records) {
|
||||||
completion:^(NSDictionary *jsonOrData, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
KBMyTheme *theme = [KBMyTheme new];
|
||||||
if (error) {
|
theme.themeId = record.skinId;
|
||||||
NSString *msg = KBBizMessageFromJSONObject(jsonOrData) ?: error.localizedDescription ?: KBLocalized(@"Network error");
|
theme.themeName = record.name;
|
||||||
[KBHUD showInfo:msg];
|
theme.themeDownload = record.zipURL;
|
||||||
if (completion) completion(nil, error);
|
theme.themePreviewImageUrl = record.previewImage;
|
||||||
return;
|
[themes addObject:theme];
|
||||||
|
}
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
if (completion) completion(themes.copy, nil);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
id dataObj = jsonOrData[KBData] ?: jsonOrData[@"data"];
|
- (void)deleteDownloadedThemesWithIds:(NSArray<NSString *> *)themeIds
|
||||||
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);
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)deletePurchasedThemesWithThemeIds:(NSArray<NSNumber *> *)themeIds
|
|
||||||
completion:(KBDeleteThemesCompletion)completion {
|
completion:(KBDeleteThemesCompletion)completion {
|
||||||
if (themeIds.count == 0) {
|
if (themeIds.count == 0) {
|
||||||
if (completion) {
|
if (completion) {
|
||||||
@@ -126,18 +117,22 @@ NSString * const KBUserCharacterDeletedNotification = @"KBUserCharacterDeletedNo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSDictionary *params = @{@"themeIds": themeIds};
|
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
||||||
[[KBNetworkManager shared] POST:API_THEME_BATCH_DELETE
|
NSError *lastError = nil;
|
||||||
jsonBody:params
|
for (NSString *skinId in themeIds) {
|
||||||
headers:nil
|
if (skinId.length == 0) { continue; }
|
||||||
autoShowBusinessError:YES
|
BOOL ok = [KBSkinInstallBridge removeInstalledSkinWithId:skinId error:&lastError];
|
||||||
completion:^(NSDictionary * _Nullable json,
|
if (!ok) {
|
||||||
NSURLResponse * _Nullable response,
|
break;
|
||||||
NSError * _Nullable error) {
|
|
||||||
if (completion) {
|
|
||||||
completion(error == nil, error);
|
|
||||||
}
|
}
|
||||||
}];
|
}
|
||||||
|
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 : @"";
|
skin[@"zip_url"] = self.detailModel.themeDownloadUrl ? self.detailModel.themeDownloadUrl : @"";
|
||||||
|
if (self.detailModel.themePreviewImageUrl.length > 0) {
|
||||||
|
skin[@"preview"] = self.detailModel.themePreviewImageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
[[KBSkinService shared] applySkinWithJSON:skin
|
[[KBSkinService shared] applySkinWithJSON:skin
|
||||||
fromViewController:self
|
fromViewController:self
|
||||||
|
|||||||
Reference in New Issue
Block a user