From b8cc38aa61555e9af7d5b12c25678c068994613b Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Tue, 25 Nov 2025 18:54:53 +0800 Subject: [PATCH] 3 --- CustomKeyboard/Resource/Christmas.zip | Bin 3205472 -> 3240239 bytes Shared/KBSkinInstallBridge.h | 24 +- Shared/KBSkinInstallBridge.m | 267 ++++++++++++++++-- .../Localization/en.lproj/Localizable.strings | 3 + .../zh-Hans.lproj/Localizable.strings | 3 + keyBoard.xcodeproj/project.pbxproj | 8 +- keyBoard/Class/Manager/KBSkinService.m | 185 ++---------- keyBoard/Class/Me/VC/KBPersonInfoVC.m | 8 +- 8 files changed, 303 insertions(+), 195 deletions(-) diff --git a/CustomKeyboard/Resource/Christmas.zip b/CustomKeyboard/Resource/Christmas.zip index cf469e80439dbf765f565d0bbd3c945ede6a4b22..e9d8ea44dc8aa59b0bf0570b65ed68c6d6b5a15e 100644 GIT binary patch delta 37031 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 UkJJl4SMl3W *)defaultIconShortNames; +/// 主 App / 键盘扩展:通过远程 zip_url 下载并安装一套皮肤。 +/// - skinJSON 结构与后端约定一致,至少包含: +/// * id: 皮肤唯一标识 +/// * name: 展示名称(可选,缺省为 id) +/// * zip_url: 远程 Zip 地址(http/https) +/// * key_icons: 按键 -> 图标“短文件名”映射(可选,不传则使用 defaultIconShortNames) +/// - 内部会将 Zip 解压到 App Group/Skins//...,并使用 KBSkinManager 应用主题与背景图。 +/// - 应用成功后,KBSkinManager 会广播皮肤变更通知,键盘扩展可立即感知。 ++ (void)installRemoteSkinWithJSON:(NSDictionary *)skinJSON + completion:(nullable KBSkinInstallConsumeCompletion)completion; + /// 主 App 侧:记录一个“从 bundle 解压皮肤”的请求,写入 App Group 并广播 Darwin 通知。 + (void)publishBundleSkinRequestWithId:(NSString *)skinId name:(NSString *)name @@ -36,4 +59,3 @@ typedef void (^KBSkinInstallConsumeCompletion)(BOOL success, NSError * _Nullable @end NS_ASSUME_NONNULL_END - diff --git a/Shared/KBSkinInstallBridge.m b/Shared/KBSkinInstallBridge.m index fd900eb..1681d0d 100644 --- a/Shared/KBSkinInstallBridge.m +++ b/Shared/KBSkinInstallBridge.m @@ -6,11 +6,15 @@ #import "KBConfig.h" #import "KBSkinManager.h" +#if __has_include("KBNetworkManager.h") +#import "KBNetworkManager.h" +#endif #if __has_include() #import #endif NSString * const KBDarwinSkinInstallRequestNotification = @"com.loveKey.nyx.skin.install.request"; +NSErrorDomain const KBSkinBridgeErrorDomain = @"com.loveKey.nyx.skin.bridge"; static NSString * const kKBSkinPendingRequestKey = @"com.loveKey.nyx.skin.pending"; static NSString * const kKBSkinPendingSkinIdKey = @"skinId"; @@ -20,16 +24,6 @@ static NSString * const kKBSkinPendingKindKey = @"kind"; static NSString * const kKBSkinPendingTimestampKey = @"timestamp"; static NSString * const kKBSkinPendingIconShortKey = @"iconShortNames"; -static NSString * const kKBSkinBridgeErrorDomain = @"com.loveKey.nyx.skin.bridge"; - -typedef NS_ENUM(NSUInteger, KBSkinBridgeErrorCode) { - KBSkinBridgeErrorInvalidPayload = 1, - KBSkinBridgeErrorContainerUnavailable, - KBSkinBridgeErrorZipMissing, - KBSkinBridgeErrorUnzipFailed, - KBSkinBridgeErrorApplyFailed, -}; - @implementation KBSkinInstallBridge + (NSDictionary *)defaultIconShortNames { @@ -54,6 +48,245 @@ typedef NS_ENUM(NSUInteger, KBSkinBridgeErrorCode) { return [[NSUserDefaults alloc] initWithSuiteName:AppGroup]; } ++ (void)installRemoteSkinWithJSON:(NSDictionary *)skinJSON + completion:(KBSkinInstallConsumeCompletion)completion { + if (![skinJSON isKindOfClass:NSDictionary.class] || skinJSON.count == 0) { + if (completion) { + NSError *err = [NSError errorWithDomain:KBSkinBridgeErrorDomain + code:KBSkinBridgeErrorInvalidPayload + userInfo:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ completion(NO, err); }); + } + return; + } + + NSString *skinId = skinJSON[@"id"] ?: @"remote"; + NSString *name = skinJSON[@"name"] ?: skinId; + NSString *zipURL = skinJSON[@"zip_url"] ?: @""; + + // key_icons 可选: + // - 若后端提供 key_icons,则优先使用服务端映射; + // - 若未提供,则回退到本地默认映射,这样后端只需返回 id/name/zip_url。 + NSDictionary *iconShortNames = nil; + if ([skinJSON[@"key_icons"] isKindOfClass:NSDictionary.class]) { + iconShortNames = skinJSON[@"key_icons"]; + } else { + iconShortNames = [self defaultIconShortNames]; + } + + NSFileManager *fm = [NSFileManager defaultManager]; + NSURL *containerURL = [fm containerURLForSecurityApplicationGroupIdentifier:AppGroup]; + if (!containerURL) { + if (completion) { + NSError *err = [NSError errorWithDomain:KBSkinBridgeErrorDomain + code:KBSkinBridgeErrorContainerUnavailable + userInfo:@{NSLocalizedDescriptionKey: @"Shared container unavailable"}]; + dispatch_async(dispatch_get_main_queue(), ^{ completion(NO, err); }); + } + return; + } + + NSString *skinsRoot = [containerURL.path stringByAppendingPathComponent:@"Skins"]; + NSString *skinRoot = [skinsRoot stringByAppendingPathComponent:skinId]; + NSString *iconsDir = [skinRoot stringByAppendingPathComponent:@"icons"]; + [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; + BOOL hasCachedAssets = (contents.count > 0); + + NSString *bgPath = [skinRoot stringByAppendingPathComponent:@"background.png"]; + + dispatch_group_t group = dispatch_group_create(); + __block BOOL zipOK = YES; + __block NSError *innerError = nil; + +#if __has_include() + // 若本地尚未缓存该皮肤资源且提供了 zip_url,则通过网络下载并解压 Zip 包。 + if (!hasCachedAssets && zipURL.length > 0) { + dispatch_group_enter(group); + + void (^handleZipData)(NSData *) = ^(NSData *data) { + if (data.length == 0) { + zipOK = NO; + if (!innerError) { + innerError = [NSError errorWithDomain:KBSkinBridgeErrorDomain + code:KBSkinBridgeErrorZipMissing + userInfo:@{NSLocalizedDescriptionKey: @"Zip data is empty"}]; + } + dispatch_group_leave(group); + return; + } + // 将 Zip 写入临时路径再解压 + [fm createDirectoryAtPath:skinRoot + withIntermediateDirectories:YES + attributes:nil + error:NULL]; + NSString *zipPath = [skinRoot stringByAppendingPathComponent:@"skin.zip"]; + if (![data writeToFile:zipPath atomically:YES]) { + zipOK = NO; + if (!innerError) { + innerError = [NSError errorWithDomain:KBSkinBridgeErrorDomain + code:KBSkinBridgeErrorUnzipFailed + userInfo:@{NSLocalizedDescriptionKey: @"Failed to write zip file"}]; + } + dispatch_group_leave(group); + return; + } + + NSError *unzipError = nil; + BOOL ok = [SSZipArchive unzipFileAtPath:zipPath + toDestination:skinRoot + overwrite:YES + password:nil + error:&unzipError]; + [fm removeItemAtPath:zipPath error:nil]; + if (!ok || unzipError) { + zipOK = NO; + if (!innerError) { + innerError = unzipError ?: [NSError errorWithDomain:KBSkinBridgeErrorDomain + code:KBSkinBridgeErrorUnzipFailed + userInfo:nil]; + } + dispatch_group_leave(group); + return; + } + + // 兼容“额外包一层目录”的压缩结构: + // 若 Skins//icons 为空,但存在 Skins//<子目录>/icons, + // 则将实际 icons 与 background.png 上移到预期位置。 + BOOL isDir2 = NO; + NSArray *iconsContent = [fm contentsOfDirectoryAtPath:iconsDir error:NULL]; + BOOL iconsValid = ([fm fileExistsAtPath:iconsDir isDirectory:&isDir2] && isDir2 && iconsContent.count > 0); + if (!iconsValid) { + NSArray *subItems = [fm contentsOfDirectoryAtPath:skinRoot error:NULL]; + for (NSString *subName in subItems) { + if ([subName isEqualToString:@"icons"] || [subName isEqualToString:@"__MACOSX"]) continue; + NSString *nestedRoot = [skinRoot stringByAppendingPathComponent:subName]; + BOOL isDirNested = NO; + if (![fm fileExistsAtPath:nestedRoot isDirectory:&isDirNested] || !isDirNested) continue; + + NSString *nestedIcons = [nestedRoot stringByAppendingPathComponent:@"icons"]; + BOOL isDirNestedIcons = NO; + if ([fm fileExistsAtPath:nestedIcons isDirectory:&isDirNestedIcons] && isDirNestedIcons) { + NSArray *nestedFiles = [fm contentsOfDirectoryAtPath:nestedIcons error:NULL]; + if (nestedFiles.count > 0) { + // 确保目标 icons 目录存在 + [fm createDirectoryAtPath:iconsDir + withIntermediateDirectories:YES + attributes:nil + error:NULL]; + // 将 icons 下所有文件上移一层 + for (NSString *fn in nestedFiles) { + NSString *from = [nestedIcons stringByAppendingPathComponent:fn]; + NSString *to = [iconsDir stringByAppendingPathComponent:fn]; + [fm removeItemAtPath:to error:nil]; + [fm moveItemAtPath:from toPath:to error:nil]; + } + } + } + + // 处理 background.png:若在子目录下存在,则上移到 skinRoot + NSString *nestedBg = [nestedRoot stringByAppendingPathComponent:@"background.png"]; + if ([fm fileExistsAtPath:nestedBg]) { + [fm removeItemAtPath:bgPath error:nil]; + [fm moveItemAtPath:nestedBg toPath:bgPath error:nil]; + } + } + } + dispatch_group_leave(group); + }; + +#if __has_include("KBNetworkManager.h") + // 远程下载(http/https) + [[KBNetworkManager shared] GET:zipURL parameters:nil headers:nil completion:^(id jsonOrData, NSURLResponse *response, NSError *error) { + NSData *data = ([jsonOrData isKindOfClass:NSData.class] ? (NSData *)jsonOrData : nil); + if (error || data.length == 0) { + zipOK = NO; + if (!innerError) { + innerError = error ?: [NSError errorWithDomain:KBSkinBridgeErrorDomain + code:KBSkinBridgeErrorZipMissing + userInfo:@{NSLocalizedDescriptionKey: @"Failed to download zip"}]; + } + dispatch_group_leave(group); + return; + } + handleZipData(data); + }]; +#else + // 无 KBNetworkManager 时,退回到简单的 dataWithContentsOfURL 下载(阻塞当前线程) + dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ + NSURL *url = [NSURL URLWithString:zipURL]; + NSData *data = url ? [NSData dataWithContentsOfURL:url] : nil; + if (!data) { + zipOK = NO; + if (!innerError) { + innerError = [NSError errorWithDomain:KBSkinBridgeErrorDomain + code:KBSkinBridgeErrorZipMissing + userInfo:@{NSLocalizedDescriptionKey: @"Failed to download zip"}]; + } + dispatch_group_leave(group); + } else { + handleZipData(data); + } + }); +#endif + } +#else + zipOK = NO; + innerError = [NSError errorWithDomain:KBSkinBridgeErrorDomain + code:KBSkinBridgeErrorUnzipFailed + userInfo:@{NSLocalizedDescriptionKey: @"SSZipArchive not available"}]; +#endif + + // 解压与下载完成后,构造主题并应用 + dispatch_group_notify(group, dispatch_get_main_queue(), ^{ + // 构造 key_icons -> App Group 相对路径 映射 + NSMutableDictionary *iconPathMap = [NSMutableDictionary dictionary]; + [iconShortNames enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, NSString *shortName, BOOL *stop) { + if (![shortName isKindOfClass:NSString.class] || shortName.length == 0) return; + NSString *fileName = shortName; + // 若未带扩展名,默认按 .png 处理 + if (fileName.pathExtension.length == 0) { + fileName = [fileName stringByAppendingPathExtension:@"png"]; + } + NSString *relative = [NSString stringWithFormat:@"Skins/%@/icons/%@", skinId, fileName]; + iconPathMap[identifier] = relative; + }]; + + NSMutableDictionary *themeJSON = [skinJSON mutableCopy]; + themeJSON[@"id"] = skinId; + if (iconPathMap.count > 0) { + themeJSON[@"key_icons"] = iconPathMap.copy; + } + + BOOL themeOK = [[KBSkinManager shared] applyThemeFromJSON:themeJSON]; + + // 背景图优先从 Zip 解压出的 background.png 读取 + NSData *bgData = [NSData dataWithContentsOfFile:bgPath]; + BOOL ok = themeOK; + if (bgData.length > 0) { + ok = [[KBSkinManager shared] applyImageSkinWithData:bgData skinId:skinId name:name]; + } + + if (!zipOK && !hasCachedAssets) { + ok = NO; + } + + NSError *finalError = nil; + if (!ok) { + finalError = innerError ?: [NSError errorWithDomain:KBSkinBridgeErrorDomain + code:KBSkinBridgeErrorApplyFailed + userInfo:nil]; + } + if (completion) completion(ok, finalError); + }); +} + + (void)publishBundleSkinRequestWithId:(NSString *)skinId name:(NSString *)name zipName:(NSString *)zipName @@ -115,7 +348,7 @@ typedef NS_ENUM(NSUInteger, KBSkinBridgeErrorCode) { error:(NSError * __autoreleasing *)error { #if !__has_include() if (error) { - *error = [NSError errorWithDomain:kKBSkinBridgeErrorDomain + *error = [NSError errorWithDomain:KBSkinBridgeErrorDomain code:KBSkinBridgeErrorUnzipFailed userInfo:@{NSLocalizedDescriptionKey: @"SSZipArchive not available"}]; } @@ -126,7 +359,7 @@ typedef NS_ENUM(NSUInteger, KBSkinBridgeErrorCode) { NSString *zipName = payload[kKBSkinPendingZipKey]; if (skinId.length == 0 || zipName.length == 0) { if (error) { - *error = [NSError errorWithDomain:kKBSkinBridgeErrorDomain + *error = [NSError errorWithDomain:KBSkinBridgeErrorDomain code:KBSkinBridgeErrorInvalidPayload userInfo:nil]; } @@ -192,9 +425,9 @@ typedef NS_ENUM(NSUInteger, KBSkinBridgeErrorCode) { } if (zipPath.length == 0) { if (error) { - *error = [NSError errorWithDomain:kKBSkinBridgeErrorDomain - code:KBSkinBridgeErrorZipMissing - userInfo:@{NSLocalizedDescriptionKey: @"Zip resource not found"}]; + *error = [NSError errorWithDomain:KBSkinBridgeErrorDomain + code:KBSkinBridgeErrorZipMissing + userInfo:@{NSLocalizedDescriptionKey: @"Zip resource not found"}]; } return NO; } @@ -207,7 +440,7 @@ typedef NS_ENUM(NSUInteger, KBSkinBridgeErrorCode) { error:&unzipError]; if (!ok || unzipError) { if (error) { - *error = unzipError ?: [NSError errorWithDomain:kKBSkinBridgeErrorDomain + *error = unzipError ?: [NSError errorWithDomain:KBSkinBridgeErrorDomain code:KBSkinBridgeErrorUnzipFailed userInfo:nil]; } @@ -283,7 +516,7 @@ typedef NS_ENUM(NSUInteger, KBSkinBridgeErrorCode) { } if (!ok && error) { - *error = [NSError errorWithDomain:kKBSkinBridgeErrorDomain + *error = [NSError errorWithDomain:KBSkinBridgeErrorDomain code:KBSkinBridgeErrorApplyFailed userInfo:nil]; } diff --git a/Shared/Localization/en.lproj/Localizable.strings b/Shared/Localization/en.lproj/Localizable.strings index c25c1c0..078ec6b 100644 --- a/Shared/Localization/en.lproj/Localizable.strings +++ b/Shared/Localization/en.lproj/Localizable.strings @@ -106,6 +106,9 @@ "Notification Setting" = "Notification Setting"; "Please Enter The Content" = "Please Enter The Content"; "Commit" = "Commit"; +"Nickname" = "Nickname"; +"Gender" = "Gender"; +"User ID" = "User ID"; // Search & history diff --git a/Shared/Localization/zh-Hans.lproj/Localizable.strings b/Shared/Localization/zh-Hans.lproj/Localizable.strings index 7ebc6aa..96b32af 100644 --- a/Shared/Localization/zh-Hans.lproj/Localizable.strings +++ b/Shared/Localization/zh-Hans.lproj/Localizable.strings @@ -107,6 +107,9 @@ "Notification Setting" = "通知设置"; "Please Enter The Content" = "请输入反馈内容"; "Commit" = "提交"; +"Nickname" = "用户名"; +"Gender" = "性别"; +"User ID" = "用户ID"; // 搜索与历史(英文 key) "Clear history" = "清空历史"; diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index fc9fdb9..e039137 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -52,7 +52,7 @@ 04791F952ED48028004E8522 /* KBFeedBackVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 04791F942ED48028004E8522 /* KBFeedBackVC.m */; }; 04791F982ED49CE7004E8522 /* KBFont.m in Sources */ = {isa = PBXBuildFile; fileRef = 04791F972ED49CE7004E8522 /* KBFont.m */; }; 04791F992ED49CE7004E8522 /* KBFont.m in Sources */ = {isa = PBXBuildFile; fileRef = 04791F972ED49CE7004E8522 /* KBFont.m */; }; - 04791FF52ED5A487004E8522 /* Christmas.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04791FF42ED5A487004E8522 /* Christmas.zip */; }; + 04791FF72ED5B985004E8522 /* Christmas.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04791FF62ED5B985004E8522 /* Christmas.zip */; }; 047C650D2EBC8A840035E841 /* KBPanModalView.m in Sources */ = {isa = PBXBuildFile; fileRef = 047C650C2EBC8A840035E841 /* KBPanModalView.m */; }; 047C65102EBCA8DD0035E841 /* HomeRankContentVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 047C650F2EBCA8DD0035E841 /* HomeRankContentVC.m */; }; 047C65502EBCBA9E0035E841 /* KBShopVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 047C654F2EBCBA9E0035E841 /* KBShopVC.m */; }; @@ -263,7 +263,7 @@ 04791F942ED48028004E8522 /* KBFeedBackVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFeedBackVC.m; sourceTree = ""; }; 04791F962ED49CE7004E8522 /* KBFont.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFont.h; sourceTree = ""; }; 04791F972ED49CE7004E8522 /* KBFont.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFont.m; sourceTree = ""; }; - 04791FF42ED5A487004E8522 /* Christmas.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = Christmas.zip; sourceTree = ""; }; + 04791FF62ED5B985004E8522 /* Christmas.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = Christmas.zip; sourceTree = ""; }; 047C650B2EBC8A840035E841 /* KBPanModalView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBPanModalView.h; sourceTree = ""; }; 047C650C2EBC8A840035E841 /* KBPanModalView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBPanModalView.m; sourceTree = ""; }; 047C650E2EBCA8DD0035E841 /* HomeRankContentVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HomeRankContentVC.h; sourceTree = ""; }; @@ -501,7 +501,7 @@ 041007D12ECE012000D203BB /* KBSkinIconMap.strings */, 041007D32ECE012500D203BB /* 002.zip */, 046131102ECF3A6E00A6FADF /* fense.zip */, - 04791FF42ED5A487004E8522 /* Christmas.zip */, + 04791FF62ED5B985004E8522 /* Christmas.zip */, ); path = Resource; sourceTree = ""; @@ -1453,7 +1453,7 @@ 046131112ECF3A6E00A6FADF /* fense.zip in Resources */, 041007D42ECE012500D203BB /* 002.zip in Resources */, 041007D22ECE012000D203BB /* KBSkinIconMap.strings in Resources */, - 04791FF52ED5A487004E8522 /* Christmas.zip in Resources */, + 04791FF72ED5B985004E8522 /* Christmas.zip in Resources */, 04286A0B2ECD88B400CE730C /* KeyboardAssets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/keyBoard/Class/Manager/KBSkinService.m b/keyBoard/Class/Manager/KBSkinService.m index c34ac3e..2b8dbcf 100644 --- a/keyBoard/Class/Manager/KBSkinService.m +++ b/keyBoard/Class/Manager/KBSkinService.m @@ -77,178 +77,25 @@ /// - skinJSON.key_icons 的 value 填写 Zip 内的图标“短文件名”(不含路径,可不含扩展名),例如 "key_a" /// 应用时会被转换为相对 App Group 根目录的路径:Skins//icons/.png - (void)kb_applySkinUsingRemoteIcons:(NSDictionary *)skin completion:(KBSkinApplyCompletion)completion { - NSString *skinId = skin[@"id"] ?: @"remote"; - NSString *name = skin[@"name"] ?: skinId; - NSString *zipURL = skin[@"zip_url"] ?: @""; // 新协议:远程 Zip 包地址 - - // key_icons 可选: - // - 若后端提供 key_icons,则优先使用服务端映射; - // - 若未提供,则回退到本地默认映射(KBSkinInstallBridge.defaultIconShortNames),这样后端只需返回 id/name/zip_url。 - NSDictionary *iconShortNames = nil; - if ([skin[@"key_icons"] isKindOfClass:NSDictionary.class]) { - iconShortNames = skin[@"key_icons"]; - } else { - iconShortNames = [KBSkinInstallBridge defaultIconShortNames]; - } - - NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:AppGroup]; - if (!containerURL) { - if (completion) completion(NO); - [KBHUD showInfo:KBLocalized(@"无法访问共享容器,应用皮肤失败")]; - return; - } - - NSString *skinsRoot = [containerURL.path 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]; - BOOL isDir = NO; - BOOL hasIconsDir = [fm fileExistsAtPath:iconsDir isDirectory:&isDir] && isDir; - NSArray *contents = hasIconsDir ? [fm contentsOfDirectoryAtPath:iconsDir error:NULL] : nil; - BOOL hasCachedAssets = (contents.count > 0); - - NSString *bgPath = [skinRoot stringByAppendingPathComponent:@"background.png"]; - - dispatch_group_t group = dispatch_group_create(); - __block BOOL zipOK = YES; - -#if __has_include() - // 若本地尚未缓存该皮肤资源且提供了 zip_url,则通过网络下载并解压 Zip 包。 - if (!hasCachedAssets && zipURL.length > 0) { - dispatch_group_enter(group); - - void (^handleZipData)(NSData *) = ^(NSData *data) { - if (data.length == 0) { - zipOK = NO; - dispatch_group_leave(group); - return; - } - // 将 Zip 写入临时路径再解压 - [[NSFileManager defaultManager] createDirectoryAtPath:skinRoot - withIntermediateDirectories:YES - attributes:nil - error:NULL]; - NSString *zipPath = [skinRoot stringByAppendingPathComponent:@"skin.zip"]; - if (![data writeToFile:zipPath atomically:YES]) { - zipOK = NO; - dispatch_group_leave(group); - return; - } - - NSError *unzipError = nil; - BOOL ok = [SSZipArchive unzipFileAtPath:zipPath - toDestination:skinRoot - overwrite:YES - password:nil - error:&unzipError]; - [fm removeItemAtPath:zipPath error:nil]; - if (!ok || unzipError) { - zipOK = NO; - dispatch_group_leave(group); - return; - } - - // 兼容“额外包一层目录”的压缩结构: - // 若 Skins//icons 为空,但存在 Skins//<子目录>/icons, - // 则将实际 icons 与 background.png 上移到预期位置。 - BOOL isDir2 = NO; - NSArray *iconsContent = [fm contentsOfDirectoryAtPath:iconsDir error:NULL]; - BOOL iconsValid = ([fm fileExistsAtPath:iconsDir isDirectory:&isDir2] && isDir2 && iconsContent.count > 0); - if (!iconsValid) { - NSArray *subItems = [fm contentsOfDirectoryAtPath:skinRoot error:NULL]; - for (NSString *name in subItems) { - if ([name isEqualToString:@"icons"] || [name isEqualToString:@"__MACOSX"]) continue; - NSString *nestedRoot = [skinRoot stringByAppendingPathComponent:name]; - BOOL isDirNested = NO; - if (![fm fileExistsAtPath:nestedRoot isDirectory:&isDirNested] || !isDirNested) continue; - - NSString *nestedIcons = [nestedRoot stringByAppendingPathComponent:@"icons"]; - BOOL isDirNestedIcons = NO; - if ([fm fileExistsAtPath:nestedIcons isDirectory:&isDirNestedIcons] && isDirNestedIcons) { - NSArray *nestedFiles = [fm contentsOfDirectoryAtPath:nestedIcons error:NULL]; - if (nestedFiles.count > 0) { - // 确保目标 icons 目录存在 - [fm createDirectoryAtPath:iconsDir - withIntermediateDirectories:YES - attributes:nil - error:NULL]; - // 将 icons 下所有文件上移一层 - for (NSString *fn in nestedFiles) { - NSString *from = [nestedIcons stringByAppendingPathComponent:fn]; - NSString *to = [iconsDir stringByAppendingPathComponent:fn]; - [fm removeItemAtPath:to error:nil]; - [fm moveItemAtPath:from toPath:to error:nil]; - } - } - } - - // 处理 background.png:若在子目录下存在,则上移到 skinRoot - NSString *nestedBg = [nestedRoot stringByAppendingPathComponent:@"background.png"]; - if ([fm fileExistsAtPath:nestedBg]) { - [fm removeItemAtPath:bgPath error:nil]; - [fm moveItemAtPath:nestedBg toPath:bgPath error:nil]; - } - } - } - dispatch_group_leave(group); - }; - - // 远程下载(http/https) - [[KBNetworkManager shared] GET:zipURL parameters:nil headers:nil completion:^(id jsonOrData, NSURLResponse *response, NSError *error) { - NSData *data = ([jsonOrData isKindOfClass:NSData.class] ? (NSData *)jsonOrData : nil); - if (error || data.length == 0) { - zipOK = NO; - dispatch_group_leave(group); - return; - } - handleZipData(data); - }]; - } -#else - zipOK = NO; -#endif - - dispatch_group_notify(group, dispatch_get_main_queue(), ^{ - // 构造 key_icons -> App Group 相对路径 映射 - NSMutableDictionary *iconPathMap = [NSMutableDictionary dictionary]; - [iconShortNames enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, NSString *shortName, BOOL *stop) { - if (![shortName isKindOfClass:NSString.class] || shortName.length == 0) return; - NSString *fileName = shortName; - // 若未带扩展名,默认按 .png 处理 - if (fileName.pathExtension.length == 0) { - fileName = [fileName stringByAppendingPathExtension:@"png"]; - } - NSString *relative = [NSString stringWithFormat:@"Skins/%@/icons/%@", skinId, fileName]; - iconPathMap[identifier] = relative; - }]; - - NSMutableDictionary *themeJSON = [skin mutableCopy]; - themeJSON[@"id"] = skinId; - if (iconPathMap.count > 0) { - themeJSON[@"key_icons"] = iconPathMap.copy; + [KBSkinInstallBridge installRemoteSkinWithJSON:skin + completion:^(BOOL success, NSError * _Nullable error) { + if (completion) { + completion(success); } - - BOOL themeOK = [[KBSkinManager shared] applyThemeFromJSON:themeJSON]; - - // 背景图优先从 Zip 解压出的 background.png 读取 - NSData *bgData = [NSData dataWithContentsOfFile:bgPath]; - BOOL ok = themeOK; - if (bgData.length > 0) { - ok = [[KBSkinManager shared] applyImageSkinWithData:bgData skinId:skinId name:name]; + if (!success && error) { + NSLog(@"[KBSkinService] remote skin install failed: %@", error); } - - if (!zipOK && !hasCachedAssets) { - ok = NO; + NSString *message = nil; + if (success) { + message = KBLocalized(@"已应用,切到键盘查看"); + } else if ([error.domain isEqualToString:KBSkinBridgeErrorDomain] && + error.code == KBSkinBridgeErrorContainerUnavailable) { + message = KBLocalized(@"无法访问共享容器,应用皮肤失败"); + } else { + message = KBLocalized(@"应用皮肤失败"); } - - if (completion) completion(ok); - [KBHUD showInfo:(ok ? KBLocalized(@"已应用,切到键盘查看") : KBLocalized(@"应用皮肤失败"))]; - }); + [KBHUD showInfo:message]; + }]; } /// 本地 bundle 模式:不走网络,skin[@"zip_url"] 直接为 bundle 内 zip 文件名(可带/不带扩展名)。 diff --git a/keyBoard/Class/Me/VC/KBPersonInfoVC.m b/keyBoard/Class/Me/VC/KBPersonInfoVC.m index 3781f98..60e9cdb 100644 --- a/keyBoard/Class/Me/VC/KBPersonInfoVC.m +++ b/keyBoard/Class/Me/VC/KBPersonInfoVC.m @@ -43,15 +43,15 @@ - (void)viewDidLoad { [super viewDidLoad]; - self.kb_titleLabel.text = @"Settings"; // 导航标题 + self.kb_titleLabel.text = KBLocalized(@"Settings"); // 导航标题 self.kb_navView.backgroundColor = [UIColor clearColor]; self.view.backgroundColor = [UIColor colorWithHex:0xF8F8F8]; // 构造数据 self.items = @[ - @{ @"title": @"Nickname", @"value": @"Nickname", @"arrow": @YES, @"copy": @NO }, - @{ @"title": @"Gender", @"value": @"Choose", @"arrow": @YES, @"copy": @NO }, - @{ @"title": @"User ID", @"value": @"8888888", @"arrow": @NO, @"copy": @YES }, + @{ @"title": KBLocalized(@"Nickname"), @"value": @"Nickname", @"arrow": @YES, @"copy": @NO }, + @{ @"title": KBLocalized(@"Gender"), @"value": @"Choose", @"arrow": @YES, @"copy": @NO }, + @{ @"title": KBLocalized(@"User ID"), @"value": @"8888888", @"arrow": @NO, @"copy": @YES }, ]; [self.view addSubview:self.tableView];