This commit is contained in:
2025-11-20 18:23:56 +08:00
parent b23927968f
commit 75d2e4072a
7 changed files with 140 additions and 87 deletions

View File

@@ -133,25 +133,36 @@ typedef NS_ENUM(NSUInteger, KBSkinBridgeErrorCode) {
return NO;
}
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:AppGroup];
if (!containerURL) {
if (error) {
*error = [NSError errorWithDomain:kKBSkinBridgeErrorDomain
code:KBSkinBridgeErrorContainerUnavailable
userInfo:@{NSLocalizedDescriptionKey: @"App Group container unavailable"}];
// App Group退 Caches
NSFileManager *fm = [NSFileManager defaultManager];
NSString *baseRoot = nil;
NSURL *containerURL = [fm containerURLForSecurityApplicationGroupIdentifier:AppGroup];
if (containerURL.path.length > 0) {
// Skins/.kb_write_test
NSString *testDir = [[containerURL.path stringByAppendingPathComponent:@"Skins"]
stringByAppendingPathComponent:@".kb_write_test"];
NSError *probeError = nil;
BOOL canWrite = [fm createDirectoryAtPath:testDir
withIntermediateDirectories:YES
attributes:nil
error:&probeError];
if (canWrite) {
baseRoot = containerURL.path;
[fm removeItemAtPath:testDir error:NULL];
}
return NO;
}
if (baseRoot.length == 0) {
NSArray<NSString *> *dirs = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
baseRoot = dirs.firstObject ?: NSTemporaryDirectory();
}
NSString *skinsRoot = [containerURL.path stringByAppendingPathComponent:@"Skins"];
NSString *skinsRoot = [baseRoot stringByAppendingPathComponent:@"Skins"];
NSString *skinRoot = [skinsRoot stringByAppendingPathComponent:skinId];
NSString *iconsDir = [skinRoot stringByAppendingPathComponent:@"icons"];
[[NSFileManager defaultManager] createDirectoryAtPath:iconsDir
withIntermediateDirectories:YES
attributes:nil
error:NULL];
NSFileManager *fm = [NSFileManager defaultManager];
[fm createDirectoryAtPath:iconsDir
withIntermediateDirectories:YES
attributes:nil
error:NULL];
BOOL isDir = NO;
BOOL hasIconsDir = [fm fileExistsAtPath:iconsDir isDirectory:&isDir] && isDir;
NSArray *contents = hasIconsDir ? [fm contentsOfDirectoryAtPath:iconsDir error:NULL] : nil;

View File

@@ -59,18 +59,38 @@ static NSString * const kKBSkinThemeStoreKey = @"KBSkinThemeCurrent";
@implementation KBSkinManager
/// skinId App Group Skins/<skinId>/...
/// App Group Caches
+ (NSArray<NSString *> *)kb_candidateBaseRoots {
NSMutableArray<NSString *> *roots = [NSMutableArray array];
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:AppGroup];
if (containerURL.path.length > 0) {
[roots addObject:containerURL.path];
}
NSArray<NSString *> *dirs = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *caches = dirs.firstObject;
if (caches.length > 0) {
[roots addObject:caches];
}
return roots;
}
/// skinId Skins/<skinId>/...
/// nil / @"default"
+ (BOOL)kb_hasAssetsForSkinId:(NSString *)skinId {
if (skinId.length == 0 || [skinId isEqualToString:@"default"]) {
return YES;
}
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:AppGroup];
if (!containerURL) return NO;
NSString *skinsRoot = [containerURL.path stringByAppendingPathComponent:@"Skins"];
NSString *skinRoot = [skinsRoot stringByAppendingPathComponent:skinId];
BOOL isDir = NO;
return [[NSFileManager defaultManager] fileExistsAtPath:skinRoot isDirectory:&isDir] && isDir;
NSArray<NSString *> *roots = [self kb_candidateBaseRoots];
NSFileManager *fm = [NSFileManager defaultManager];
for (NSString *base in roots) {
NSString *skinsRoot = [base stringByAppendingPathComponent:@"Skins"];
NSString *skinRoot = [skinsRoot stringByAppendingPathComponent:skinId];
BOOL isDir = NO;
if ([fm fileExistsAtPath:skinRoot isDirectory:&isDir] && isDir) {
return YES;
}
}
return NO;
}
+ (instancetype)shared {
@@ -176,27 +196,25 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
}
- (UIImage *)currentBackgroundImage {
// App Group skinId
// AppGroup
// Keychain backgroundImageData
NSString *skinId = self.current.skinId;
if (skinId.length == 0) return nil;
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:AppGroup];
if (!containerURL) return nil;
NSString *relative = [NSString stringWithFormat:@"Skins/%@/background.png", skinId];
NSString *bgPath = [[containerURL.path stringByAppendingPathComponent:relative] stringByStandardizingPath];
NSArray<NSString *> *roots = [self.class kb_candidateBaseRoots];
NSFileManager *fm = [NSFileManager defaultManager];
BOOL isDir = NO;
if (![fm fileExistsAtPath:bgPath isDirectory:&isDir] || isDir) {
return nil;
}
NSString *relative = [NSString stringWithFormat:@"Skins/%@/background.png", skinId];
NSData *data = [NSData dataWithContentsOfFile:bgPath];
if (data.length == 0) return nil;
return [UIImage imageWithData:data scale:[UIScreen mainScreen].scale] ?: nil;
for (NSString *base in roots) {
NSString *bgPath = [[base stringByAppendingPathComponent:relative] stringByStandardizingPath];
BOOL isDir = NO;
if (![fm fileExistsAtPath:bgPath isDirectory:&isDir] || isDir) {
continue;
}
NSData *data = [NSData dataWithContentsOfFile:bgPath];
if (data.length == 0) continue;
UIImage *img = [UIImage imageWithData:data scale:[UIScreen mainScreen].scale];
if (img) return img;
}
return nil;
}
- (BOOL)shouldHideKeyTextForIdentifier:(NSString *)identifier {
@@ -257,12 +275,19 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
// keyIconMap value
if (value.length > 0) {
if ([value containsString:@"/"]) {
// App Group
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:AppGroup];
if (!containerURL) return nil;
NSString *fullPath = [[containerURL.path stringByAppendingPathComponent:value] stringByStandardizingPath];
if (![[NSFileManager defaultManager] fileExistsAtPath:fullPath]) return nil;
return [UIImage imageWithContentsOfFile:fullPath];
// App Group / Caches
NSArray<NSString *> *roots = [self.class kb_candidateBaseRoots];
NSFileManager *fm = [NSFileManager defaultManager];
for (NSString *base in roots) {
NSString *fullPath = [[base stringByAppendingPathComponent:value] stringByStandardizingPath];
BOOL isDir = NO;
if (![fm fileExistsAtPath:fullPath isDirectory:&isDir] || isDir) {
continue;
}
UIImage *img = [UIImage imageWithContentsOfFile:fullPath];
if (img) return img;
}
return nil;
}
// Assets
return [UIImage imageNamed:value];
@@ -272,28 +297,34 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
// Skins/<skinId>/icons/(identifier[_upper/_lower]).png
NSString *skinId = self.current.skinId;
if (skinId.length == 0 || identifier.length == 0) return nil;
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:AppGroup];
if (!containerURL) return nil;
NSArray<NSString *> *roots = [self.class kb_candidateBaseRoots];
NSFileManager *fm = [NSFileManager defaultManager];
//
if (caseVariant == 2) {
NSString *relativeUpper = [NSString stringWithFormat:@"Skins/%@/icons/%@_upper.png", skinId, identifier];
NSString *fullUpper = [[containerURL.path stringByAppendingPathComponent:relativeUpper] stringByStandardizingPath];
if ([[NSFileManager defaultManager] fileExistsAtPath:fullUpper]) {
return [UIImage imageWithContentsOfFile:fullUpper];
}
} else if (caseVariant == 1) {
NSString *relativeLower = [NSString stringWithFormat:@"Skins/%@/icons/%@_lower.png", skinId, identifier];
NSString *fullLower = [[containerURL.path stringByAppendingPathComponent:relativeLower] stringByStandardizingPath];
if ([[NSFileManager defaultManager] fileExistsAtPath:fullLower]) {
return [UIImage imageWithContentsOfFile:fullLower];
if (caseVariant == 2 || caseVariant == 1) {
NSString *suffix = (caseVariant == 2) ? @"_upper" : @"_lower";
NSString *relative = [NSString stringWithFormat:@"Skins/%@/icons/%@%@.png", skinId, identifier, suffix];
for (NSString *base in roots) {
NSString *fullPath = [[base stringByAppendingPathComponent:relative] stringByStandardizingPath];
BOOL isDir = NO;
if ([fm fileExistsAtPath:fullPath isDirectory:&isDir] && !isDir) {
UIImage *img = [UIImage imageWithContentsOfFile:fullPath];
if (img) return img;
}
}
}
// 退 idSkins/<skinId>/icons/<identifier>.png
NSString *relative = [NSString stringWithFormat:@"Skins/%@/icons/%@.png", skinId, identifier];
NSString *fullPath = [[containerURL.path stringByAppendingPathComponent:relative] stringByStandardizingPath];
if (![[NSFileManager defaultManager] fileExistsAtPath:fullPath]) return nil;
return [UIImage imageWithContentsOfFile:fullPath];
for (NSString *base in roots) {
NSString *fullPath = [[base stringByAppendingPathComponent:relative] stringByStandardizingPath];
BOOL isDir = NO;
if ([fm fileExistsAtPath:fullPath isDirectory:&isDir] && !isDir) {
UIImage *img = [UIImage imageWithContentsOfFile:fullPath];
if (img) return img;
}
}
return nil;
}
+ (UIColor *)colorFromHexString:(NSString *)hex defaultColor:(UIColor *)fallback {