This commit is contained in:
2025-12-11 19:43:55 +08:00
parent cccced6afa
commit 577b749198
12 changed files with 244 additions and 80 deletions

View File

@@ -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(@"应用皮肤失败"))];
});
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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
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];

View File

@@ -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<NSNumber *> *)themeIds
completion:(KBDeleteThemesCompletion)completion;
/// 本地已下载主题列表
- (void)fetchDownloadedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion;
/// 删除本地主题资源
- (void)deleteDownloadedThemesWithIds:(NSArray<NSString *> *)themeIds
completion:(KBDeleteThemesCompletion)completion;
/// 更新用户人设排序
- (void)updateUserCharacterSortWithSortArray:(NSArray<NSNumber *> *)sortArray
completion:(KBUpdateCharacterSortCompletion)completion;

View File

@@ -12,6 +12,7 @@
#import "KBAPI.h"
//#import <MJExtension/MJExtension.h>
#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<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
completion:(KBDeleteThemesCompletion)completion {
- (void)deleteDownloadedThemesWithIds:(NSArray<NSString *> *)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);
}
});
});
}
///

View File

@@ -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