3
This commit is contained in:
@@ -27,6 +27,12 @@ static NSString * const kKBSkinAccount = @"current"; // Keychain ac
|
||||
if (self.backgroundImageData) {
|
||||
[coder encodeObject:self.backgroundImageData forKey:@"backgroundImageData"];
|
||||
}
|
||||
if (self.hiddenKeyTextIdentifiers.count > 0) {
|
||||
[coder encodeObject:self.hiddenKeyTextIdentifiers forKey:@"hiddenKeyTextIdentifiers"];
|
||||
}
|
||||
if (self.keyIconMap.count > 0) {
|
||||
[coder encodeObject:self.keyIconMap forKey:@"keyIconMap"];
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||
@@ -39,6 +45,9 @@ static NSString * const kKBSkinAccount = @"current"; // Keychain ac
|
||||
_keyHighlightBackground = [coder decodeObjectOfClass:UIColor.class forKey:@"keyHighlightBackground"] ?: [UIColor colorWithWhite:0.85 alpha:1.0];
|
||||
_accentColor = [coder decodeObjectOfClass:UIColor.class forKey:@"accentColor"] ?: [UIColor colorWithRed:0.77 green:0.93 blue:0.82 alpha:1.0];
|
||||
_backgroundImageData = [coder decodeObjectOfClass:NSData.class forKey:@"backgroundImageData"];
|
||||
// 这两个字段是新增的,旧数据没有也没关系
|
||||
_hiddenKeyTextIdentifiers = [coder decodeObjectOfClass:NSArray.class forKey:@"hiddenKeyTextIdentifiers"];
|
||||
_keyIconMap = [coder decodeObjectOfClass:NSDictionary.class forKey:@"keyIconMap"];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -91,6 +100,16 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
|
||||
t.keyTextColor = [self.class colorFromHexString:json[@"key_text"] defaultColor:[self.class defaultTheme].keyTextColor];
|
||||
t.keyHighlightBackground = [self.class colorFromHexString:json[@"key_highlight"] defaultColor:[self.class defaultTheme].keyHighlightBackground];
|
||||
t.accentColor = [self.class colorFromHexString:json[@"accent"] defaultColor:[self.class defaultTheme].accentColor];
|
||||
// 可选:hidden_keys 为需要隐藏文本的按键标识数组
|
||||
id hidden = json[@"hidden_keys"];
|
||||
if ([hidden isKindOfClass:NSArray.class]) {
|
||||
t.hiddenKeyTextIdentifiers = hidden;
|
||||
}
|
||||
// 可选:key_icons 为 按键标识 -> 图标名/文件名 的字典
|
||||
id icons = json[@"key_icons"];
|
||||
if ([icons isKindOfClass:NSDictionary.class]) {
|
||||
t.keyIconMap = icons;
|
||||
}
|
||||
return [self applyTheme:t];
|
||||
}
|
||||
|
||||
@@ -121,6 +140,9 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
|
||||
t.keyTextColor = base.keyTextColor ?: [self.class defaultTheme].keyTextColor;
|
||||
t.keyHighlightBackground = base.keyHighlightBackground ?: [self.class defaultTheme].keyHighlightBackground;
|
||||
t.accentColor = base.accentColor ?: [self.class defaultTheme].accentColor;
|
||||
// 继承按键文本隐藏配置和图标映射,避免仅切换背景时丢失这些设置
|
||||
t.hiddenKeyTextIdentifiers = base.hiddenKeyTextIdentifiers;
|
||||
t.keyIconMap = base.keyIconMap;
|
||||
t.backgroundImageData = imageData;
|
||||
return [self applyTheme:t];
|
||||
}
|
||||
@@ -131,6 +153,103 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
|
||||
return [UIImage imageWithData:d scale:[UIScreen mainScreen].scale] ?: nil;
|
||||
}
|
||||
|
||||
- (BOOL)shouldHideKeyTextForIdentifier:(NSString *)identifier {
|
||||
if (identifier.length == 0) return NO;
|
||||
NSArray<NSString *> *list = self.current.hiddenKeyTextIdentifiers;
|
||||
if (list.count == 0) return NO;
|
||||
// 简单线性查找,数量有限足够;如需可改为 NSSet 缓存。
|
||||
for (NSString *s in list) {
|
||||
if ([s isKindOfClass:NSString.class] && [s isEqualToString:identifier]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)iconNameForKeyIdentifier:(NSString *)identifier {
|
||||
if (identifier.length == 0) return nil;
|
||||
NSDictionary<NSString *, NSString *> *map = self.current.keyIconMap;
|
||||
if (map.count == 0) return nil;
|
||||
NSString *name = map[identifier];
|
||||
if (![name isKindOfClass:NSString.class] || name.length == 0) return nil;
|
||||
return name;
|
||||
}
|
||||
|
||||
- (UIImage *)iconImageForKeyIdentifier:(NSString *)identifier {
|
||||
return [self iconImageForKeyIdentifier:identifier caseVariant:0];
|
||||
}
|
||||
|
||||
- (UIImage *)iconImageForKeyIdentifier:(NSString *)identifier caseVariant:(NSInteger)caseVariant {
|
||||
NSDictionary<NSString *, NSString *> *map = self.current.keyIconMap;
|
||||
NSString *value = nil;
|
||||
|
||||
if (identifier.length > 0 && map.count > 0) {
|
||||
// 1) 大小写变体优先:letter_q_upper / letter_q_lower
|
||||
if (caseVariant == 2) { // upper
|
||||
NSString *keyUpper = [identifier stringByAppendingString:@"_upper"];
|
||||
NSString *candidate = map[keyUpper];
|
||||
if ([candidate isKindOfClass:NSString.class] && candidate.length > 0) {
|
||||
value = candidate;
|
||||
}
|
||||
} else if (caseVariant == 1) { // lower
|
||||
NSString *keyLower = [identifier stringByAppendingString:@"_lower"];
|
||||
NSString *candidate = map[keyLower];
|
||||
if ([candidate isKindOfClass:NSString.class] && candidate.length > 0) {
|
||||
value = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
// 2) 若未配置大小写专用图标,则回退到基础 id(兼容旧数据:letter_q)
|
||||
if (value.length == 0) {
|
||||
NSString *candidate = map[identifier];
|
||||
if ([candidate isKindOfClass:NSString.class] && candidate.length > 0) {
|
||||
value = candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 若在 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];
|
||||
}
|
||||
// 否则按本地 Assets 名称加载(兼容旧实现)
|
||||
return [UIImage imageNamed:value];
|
||||
}
|
||||
|
||||
// 兜底:若 keyIconMap 中没有该键,则按照约定的命名规则直接从 App Group 读取:
|
||||
// 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;
|
||||
// 先尝试大小写后缀
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
// 最后回退到基础 id:Skins/<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];
|
||||
}
|
||||
|
||||
+ (UIColor *)colorFromHexString:(NSString *)hex defaultColor:(UIColor *)fallback {
|
||||
if (![hex isKindOfClass:NSString.class] || hex.length == 0) return fallback;
|
||||
NSString *s = [[hex stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] lowercaseString];
|
||||
|
||||
Reference in New Issue
Block a user