This commit is contained in:
2025-12-11 20:40:49 +08:00
parent 577b749198
commit 35597f89ca
7 changed files with 198 additions and 26 deletions

View File

@@ -30,6 +30,7 @@ typedef void (^KBSkinInstallConsumeCompletion)(BOOL success, NSError * _Nullable
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy, nullable) NSString *previewImage;
@property (nonatomic, copy, nullable) NSString *zipURL;
@property (nonatomic, strong, nullable) NSDictionary *themeJSON;
@property (nonatomic, assign) NSTimeInterval installedAt;
@end
@@ -76,7 +77,12 @@ typedef void (^KBSkinInstallConsumeCompletion)(BOOL success, NSError * _Nullable
+ (void)recordInstalledSkinWithId:(NSString *)skinId
name:(NSString *)name
preview:(nullable NSString *)preview
zipURL:(nullable NSString *)zipURL;
zipURL:(nullable NSString *)zipURL
themeJSON:(nullable NSDictionary *)themeJSON;
/// 重新应用现有皮肤(从 App Group 读取主题配置与背景图)。
+ (BOOL)applyInstalledSkinWithId:(NSString *)skinId
error:(NSError * _Nullable __autoreleasing *)error;
@end

View File

@@ -28,6 +28,7 @@ static NSString * const kKBSkinMetadataNameKey = @"name";
static NSString * const kKBSkinMetadataPreviewKey = @"preview";
static NSString * const kKBSkinMetadataZipKey = @"zip_url";
static NSString * const kKBSkinMetadataInstalledKey = @"installed_at";
static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
@interface KBSkinDownloadRecord ()
- (instancetype)initWithSkinId:(NSString *)skinId metadata:(NSDictionary *)metadata;
@@ -53,6 +54,8 @@ static NSString * const kKBSkinMetadataInstalledKey = @"installed_at";
installed = [[NSDate date] timeIntervalSince1970];
}
_installedAt = installed;
NSDictionary *theme = [metadata[kKBSkinMetadataThemeKey] isKindOfClass:NSDictionary.class] ? metadata[kKBSkinMetadataThemeKey] : nil;
_themeJSON = theme;
}
return self;
}
@@ -78,10 +81,21 @@ static NSString * const kKBSkinMetadataInstalledKey = @"installed_at";
return [skinRoot stringByAppendingPathComponent:kKBSkinMetadataFileName];
}
+ (NSDictionary *)kb_metadataForSkinId:(NSString *)skinId {
NSString *metaPath = [self kb_metadataPathForSkinId:skinId];
if (metaPath.length == 0) { return nil; }
NSDictionary *meta = [NSDictionary dictionaryWithContentsOfFile:metaPath];
if ([meta isKindOfClass:NSDictionary.class]) {
return meta;
}
return nil;
}
+ (void)kb_storeMetadataForSkinId:(NSString *)skinId
name:(NSString *)name
preview:(NSString *)preview
zipURL:(NSString *)zipURL {
zipURL:(NSString *)zipURL
themeJSON:(NSDictionary *)themeJSON {
if (skinId.length == 0) { return; }
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
NSString *metaPath = [self kb_metadataPathForSkinId:skinId];
@@ -101,6 +115,9 @@ static NSString * const kKBSkinMetadataInstalledKey = @"installed_at";
if (zipURL.length > 0) {
dict[kKBSkinMetadataZipKey] = zipURL;
}
if (themeJSON.count > 0) {
dict[kKBSkinMetadataThemeKey] = themeJSON;
}
dict[kKBSkinMetadataInstalledKey] = @(now);
[dict writeToFile:metaPath atomically:YES];
});
@@ -146,7 +163,9 @@ static NSString * const kKBSkinMetadataInstalledKey = @"installed_at";
if (ok) {
NSString *currentId = [KBSkinManager shared].current.skinId;
if ([currentId isKindOfClass:NSString.class] && [currentId isEqualToString:skinId]) {
[[KBSkinManager shared] resetToDefault];
dispatch_async(dispatch_get_main_queue(), ^{
[[KBSkinManager shared] resetToDefault];
});
}
}
return ok;
@@ -155,8 +174,13 @@ static NSString * const kKBSkinMetadataInstalledKey = @"installed_at";
+ (void)recordInstalledSkinWithId:(NSString *)skinId
name:(NSString *)name
preview:(NSString *)preview
zipURL:(NSString *)zipURL {
[self kb_storeMetadataForSkinId:skinId name:name preview:preview zipURL:zipURL];
zipURL:(NSString *)zipURL
themeJSON:(NSDictionary *)themeJSON {
NSMutableDictionary *jsonToSave = themeJSON ? [themeJSON mutableCopy] : nil;
if (jsonToSave.count > 0 && skinId.length > 0) {
jsonToSave[@"id"] = skinId;
}
[self kb_storeMetadataForSkinId:skinId name:name preview:preview zipURL:zipURL themeJSON:jsonToSave];
}
+ (NSDictionary<NSString *,NSString *> *)defaultIconShortNames {
@@ -440,7 +464,8 @@ static NSString * const kKBSkinMetadataInstalledKey = @"installed_at";
[self recordInstalledSkinWithId:skinId
name:name ?: skinId
preview:preview
zipURL:zipURL];
zipURL:zipURL
themeJSON:themeJSON];
}
});
}
@@ -682,10 +707,63 @@ static NSString * const kKBSkinMetadataInstalledKey = @"installed_at";
[self recordInstalledSkinWithId:skinId
name:name ?: skinId
preview:nil
zipURL:zipName];
zipURL:zipName
themeJSON:themeJSON];
}
return ok;
#endif
}
+ (BOOL)applyInstalledSkinWithId:(NSString *)skinId
error:(NSError *__autoreleasing *)error {
if (skinId.length == 0) {
if (error) {
*error = [NSError errorWithDomain:KBSkinBridgeErrorDomain
code:KBSkinBridgeErrorInvalidPayload
userInfo:@{NSLocalizedDescriptionKey: @"Invalid skin id"}];
}
return NO;
}
NSDictionary *meta = [self kb_metadataForSkinId:skinId];
NSDictionary *themeJSON = [meta[kKBSkinMetadataThemeKey] isKindOfClass:NSDictionary.class] ? meta[kKBSkinMetadataThemeKey] : nil;
if (themeJSON.count == 0) {
if (error) {
*error = [NSError errorWithDomain:KBSkinBridgeErrorDomain
code:KBSkinBridgeErrorInvalidPayload
userInfo:@{NSLocalizedDescriptionKey: @"Theme data missing"}];
}
return NO;
}
NSString *name = [meta[kKBSkinMetadataNameKey] isKindOfClass:NSString.class] ? meta[kKBSkinMetadataNameKey] : skinId;
NSString *bgPath = [[[self kb_skinsRootPath] stringByAppendingPathComponent:skinId] stringByAppendingPathComponent:@"background.png"];
NSData *bgData = [NSData dataWithContentsOfFile:bgPath];
__block NSError *applyError = nil;
__block BOOL applyOK = NO;
void (^applyBlock)(void) = ^{
BOOL themeOK = [[KBSkinManager shared] applyThemeFromJSON:themeJSON];
BOOL ok = themeOK;
if (bgData.length > 0) {
ok = [[KBSkinManager shared] applyImageSkinWithData:bgData skinId:skinId name:name ?: skinId];
}
if (!ok) {
applyError = [NSError errorWithDomain:KBSkinBridgeErrorDomain
code:KBSkinBridgeErrorApplyFailed
userInfo:@{NSLocalizedDescriptionKey: @"Failed to apply skin"}];
}
applyOK = ok;
};
if ([NSThread isMainThread]) {
applyBlock();
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
applyBlock();
});
}
if (!applyOK && error) {
*error = applyError;
}
return applyOK;
}
@end