diff --git a/CustomKeyboard/View/KBToolBar.m b/CustomKeyboard/View/KBToolBar.m index 44ddb27..56a6cf9 100644 --- a/CustomKeyboard/View/KBToolBar.m +++ b/CustomKeyboard/View/KBToolBar.m @@ -42,6 +42,10 @@ selector:@selector(kb_undoStateChanged) name:KBBackspaceUndoStateDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(kb_skinDidChange) + name:KBSkinDidChangeNotification + object:nil]; } return self; } @@ -183,9 +187,14 @@ KBSkinManager *skinManager = [KBSkinManager shared]; UIImage *icon = [skinManager iconImageForKeyIdentifier:kKBAIKeyIdentifier caseVariant:0]; NSString *skinId = skinManager.current.skinId ?: @""; - BOOL usingDefaultSkin = (skinId.length == 0 || [skinId isEqualToString:@"default"]); - if (!icon && usingDefaultSkin) { + + NSLog(@"[KBToolBar] kb_updateAIButtonAppearance: skinId=%@ icon=%@", + skinId, icon ? @"有" : @"nil"); + + // 如果当前皮肤没有 ai 图标,使用默认图标 + if (!icon) { icon = [UIImage imageNamed:@"ai_key_icon"]; + NSLog(@"[KBToolBar] fallback to bundled ai_key_icon: %@", icon ? @"有" : @"nil"); } if (icon) { @@ -472,6 +481,10 @@ [self kb_updateUndoVisibilityAnimated:YES]; } +- (void)kb_skinDidChange { + [self kb_applyTheme]; +} + - (void)kb_updateUndoVisibilityAnimated:(BOOL)animated { BOOL visible = [KBBackspaceUndoManager shared].hasUndo; if (self.kbUndoVisible == visible) { return; } diff --git a/Shared/KBSkinInstallBridge.m b/Shared/KBSkinInstallBridge.m index 4a300d0..39a7532 100644 --- a/Shared/KBSkinInstallBridge.m +++ b/Shared/KBSkinInstallBridge.m @@ -959,21 +959,67 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json"; NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: currentSkin=%@ targetSkin=%@ lang=%@", currentSkinId, targetSkinId, languageCode); - if ([targetSkinId isEqualToString:currentSkinId]) { - NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: already on target skin, just refresh icon map"); - } else { - BOOL hasTargetSkin = [KBSkinManager kb_hasAssetsForSkinId:targetSkinId]; - NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: hasTargetSkin=%d", hasTargetSkin); - - if (hasTargetSkin) { - NSError *applyError = nil; - BOOL applied = [self applyInstalledSkinWithId:targetSkinId error:&applyError]; - NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: switched to %@ applied=%d error=%@", - targetSkinId, applied, applyError); - return applied; + // 检查目标皮肤是否存在 + BOOL hasTargetSkin = [KBSkinManager kb_hasAssetsForSkinId:targetSkinId]; + NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: hasTargetSkin=%d", hasTargetSkin); + + if (hasTargetSkin) { + // 先获取目标皮肤的图标映射 + NSDictionary *iconShortNames = [self iconShortNamesForLanguageCode:languageCode]; + if (iconShortNames.count == 0) { + iconShortNames = @{}; } + + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *targetSkinRoot = [[self kb_skinsRootPath] stringByAppendingPathComponent:targetSkinId]; + NSString *targetIconsDir = [targetSkinRoot stringByAppendingPathComponent:@"icons"]; + + NSMutableDictionary *iconPathMap = [NSMutableDictionary dictionary]; + [iconShortNames enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, NSString *shortName, BOOL *stop) { + if (![shortName isKindOfClass:NSString.class] || shortName.length == 0) return; + NSString *fileName = shortName; + if (fileName.pathExtension.length == 0) { + fileName = [fileName stringByAppendingPathExtension:@"png"]; + } + NSString *fullPath = [targetIconsDir stringByAppendingPathComponent:fileName]; + BOOL exists = [fm fileExistsAtPath:fullPath]; + if ([identifier isEqualToString:@"ai"] || [identifier isEqualToString:@"key_revoke"]) { + NSLog(@"[SkinBridge] Checking icon: id=%@ shortName=%@ fullPath=%@ exists=%d", + identifier, shortName, fullPath, exists); + } + if (exists) { + NSString *relative = [NSString stringWithFormat:@"Skins/%@/icons/%@", targetSkinId, fileName]; + iconPathMap[identifier] = relative; + } + }]; + + NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: targetSkin=%@ iconCount=%lu", + targetSkinId, (unsigned long)iconPathMap.count); + + // 构建主题 JSON + NSDictionary *meta = [self kb_metadataForSkinId:targetSkinId]; + NSMutableDictionary *themeJSON = [NSMutableDictionary dictionary]; + themeJSON[@"id"] = targetSkinId; + NSString *name = [meta[kKBSkinMetadataNameKey] isKindOfClass:NSString.class] ? meta[kKBSkinMetadataNameKey] : targetSkinId; + themeJSON[@"name"] = name; + if (iconPathMap.count > 0) { + themeJSON[@"key_icons"] = iconPathMap.copy; + } + + NSString *bgPath = [targetSkinRoot stringByAppendingPathComponent:@"background.png"]; + NSData *bgData = [NSData dataWithContentsOfFile:bgPath]; + + BOOL themeOK = [[KBSkinManager shared] applyThemeFromJSON:themeJSON]; + if (bgData.length > 0) { + [[KBSkinManager shared] applyImageSkinWithData:bgData skinId:targetSkinId name:name]; + } + + NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: switched to %@ applied=%d iconCount=%lu", + targetSkinId, themeOK, (unsigned long)iconPathMap.count); + return themeOK; } + // 如果目标皮肤不存在,尝试更新当前皮肤的图标映射 if (currentSkinId.length == 0 || [currentSkinId isEqualToString:@"default"]) { NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: no custom skin applied, skip"); return NO; diff --git a/Shared/KBSkinManager.m b/Shared/KBSkinManager.m index 62ec0e7..c637f62 100644 --- a/Shared/KBSkinManager.m +++ b/Shared/KBSkinManager.m @@ -355,6 +355,7 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer, @"shift", @"backspace", @"mode_123", @"mode_abc", @"symbols_toggle_more", @"symbols_toggle_123", @"return", @"space", @"emoji_panel", @"letter_q", + @"ai", @"key_revoke", nil]; }); BOOL shouldLog = [kb_debugIconIds containsObject:identifier]; @@ -388,6 +389,13 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer, } } +#if DEBUG + if (shouldLog) { + NSLog(@"[SkinManager] iconImageForKey: id='%@' skin='%@' mapCount=%lu value='%@'", + identifier, self.current.skinId ?: @"default", (unsigned long)map.count, value ?: @"nil"); + } +#endif + // 调试日志:打印未找到映射的按键 ID if (value.length == 0 && identifier.length > 0) { static NSMutableSet *kb_loggedMissingIds = nil; diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index b5d12c7..b98e9b1 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -45,7 +45,7 @@ 043213B12F556DF80065C888 /* KBSkinIconMap_es.strings in Resources */ = {isa = PBXBuildFile; fileRef = 043213AB2F556DF80065C888 /* KBSkinIconMap_es.strings */; }; 043213B22F556DF80065C888 /* KBSkinIconMap_zh_hant.strings in Resources */ = {isa = PBXBuildFile; fileRef = 043213AE2F556DF80065C888 /* KBSkinIconMap_zh_hant.strings */; }; 043213B62F5582710065C888 /* spanish_words.json in Resources */ = {isa = PBXBuildFile; fileRef = 043213B52F5582710065C888 /* spanish_words.json */; }; - 043213B82F558BC20065C888 /* 西班牙初始皮肤.zip in Resources */ = {isa = PBXBuildFile; fileRef = 043213B72F558BC20065C888 /* 西班牙初始皮肤.zip */; }; + 043213BA2F55C5790065C888 /* 西班牙初始皮肤.zip in Resources */ = {isa = PBXBuildFile; fileRef = 043213B92F55C5790065C888 /* 西班牙初始皮肤.zip */; }; 043FBCD22EAF97630036AFE1 /* KBPermissionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 04C6EAE12EAF940F0089C901 /* KBPermissionViewController.m */; }; 0450AA742EF013D000B6AF06 /* KBEmojiCollectionCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0450AA732EF013D000B6AF06 /* KBEmojiCollectionCell.m */; }; 0450AAE22EF03D5100B6AF06 /* KBPerson.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450AAE12EF03D5100B6AF06 /* KBPerson.swift */; }; @@ -403,7 +403,7 @@ 043213AD2F556DF80065C888 /* KBSkinIconMap_pt.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = KBSkinIconMap_pt.strings; sourceTree = ""; }; 043213AE2F556DF80065C888 /* KBSkinIconMap_zh_hant.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = KBSkinIconMap_zh_hant.strings; sourceTree = ""; }; 043213B52F5582710065C888 /* spanish_words.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = spanish_words.json; sourceTree = ""; }; - 043213B72F558BC20065C888 /* 西班牙初始皮肤.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = "西班牙初始皮肤.zip"; sourceTree = ""; }; + 043213B92F55C5790065C888 /* 西班牙初始皮肤.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = "西班牙初始皮肤.zip"; sourceTree = ""; }; 0450AA722EF013D000B6AF06 /* KBEmojiCollectionCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBEmojiCollectionCell.h; sourceTree = ""; }; 0450AA732EF013D000B6AF06 /* KBEmojiCollectionCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBEmojiCollectionCell.m; sourceTree = ""; }; 0450AAE02EF03D5100B6AF06 /* keyBoard-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "keyBoard-Bridging-Header.h"; sourceTree = ""; }; @@ -1298,12 +1298,12 @@ 047C652C2EBCAAAC0035E841 /* Resource */ = { isa = PBXGroup; children = ( - 043213B72F558BC20065C888 /* 西班牙初始皮肤.zip */, 0479200A2ED87CEE004E8522 /* permiss_video.mp4 */, 047920102ED98E7D004E8522 /* permiss_video_2.mp4 */, 047920062ED86ABC004E8522 /* kb_guide_keyboard.gif */, 045ED5292F540FBE00131114 /* normal_hei_them.zip */, 045ED52A2F540FBE00131114 /* normal_them.zip */, + 043213B92F55C5790065C888 /* 西班牙初始皮肤.zip */, ); path = Resource; sourceTree = ""; @@ -2336,7 +2336,7 @@ 04C6EABC2EAF86530089C901 /* LaunchScreen.storyboard in Resources */, 04E0394F2F236E75002CA5A0 /* KBChatTableView_Usage.md in Resources */, 04286A132ECDEBF900CE730C /* KBSkinIconMap.strings in Resources */, - 043213B82F558BC20065C888 /* 西班牙初始皮肤.zip in Resources */, + 043213BA2F55C5790065C888 /* 西班牙初始皮肤.zip in Resources */, 04C6EABD2EAF86530089C901 /* Main.storyboard in Resources */, 046086CB2F1A092500757C95 /* comments_mock.json in Resources */, 04E038E32F20E500002CA5A0 /* deepgramAPI.md in Resources */, diff --git a/keyBoard/Class/Resource/西班牙初始皮肤.zip b/keyBoard/Class/Resource/西班牙初始皮肤.zip index 8a841f5..9fcda65 100644 Binary files a/keyBoard/Class/Resource/西班牙初始皮肤.zip and b/keyBoard/Class/Resource/西班牙初始皮肤.zip differ