66 Commits

Author SHA1 Message Date
47291934a2 应用的皮肤不能删除 2026-02-28 21:23:38 +08:00
e619f48f93 移除主App后台音频声明负责审查风险 2026-02-28 18:21:36 +08:00
f55a70681c 处理KBFunctionTagCell正在执行又可以点击别的 2026-02-28 16:03:05 +08:00
cb86f7c32c 处理header 2026-02-28 15:38:12 +08:00
40ef964b8c 添加注销账号 2026-02-28 14:50:27 +08:00
4269fde923 1 2026-02-27 16:28:15 +08:00
c3e037e070 添加隐私,注销功能 2026-02-27 14:49:46 +08:00
a711be4c4d 跨进程 键盘用ai 在主应用里也要显示 2026-02-26 21:47:22 +08:00
69bd2b2af9 1:修改ios26tabbar的问题
2:修改键盘AI点击必须要登录装填
2026-02-26 19:38:17 +08:00
82222afd76 修复 KBChatPanel 发送内容校验 2026-02-25 20:16:31 +08:00
92ca5c6180 Fix KBAICommentInputView弹出位置 2026-02-25 17:13:25 +08:00
851c0d9531 去除假的用户信息 2026-02-25 11:15:04 +08:00
1c9013bede 新增获取客服接口 2026-02-24 20:45:15 +08:00
0a16a4f240 修改KBKeyboardPanelModeFunction 必须要登录状态 2026-02-24 18:04:13 +08:00
27d4b2b817 添加hud容错处理 2026-02-24 16:23:57 +08:00
bc623676ca 修改在手机信息页面,复制短信后,键盘按钮不存在, 背景也不存在 2026-02-24 15:24:23 +08:00
5edf1751ff 修改sign。
键盘里ai回复的bug
2026-02-24 14:59:06 +08:00
0ac47925fd 先提交 2026-02-24 13:38:51 +08:00
635ad932c7 修改vip 2026-02-12 20:06:44 +08:00
cbe0a53cac 删除无用的国家化 2026-02-11 21:16:15 +08:00
5c273c3963 修改bug 2026-02-11 21:09:37 +08:00
c9743cb363 处理第一次不滑动界面不传递数据的问题 2026-02-11 20:52:56 +08:00
f0cb69948e bug修复 2026-02-11 19:40:41 +08:00
0144f9cc6d 1 2026-02-11 19:31:12 +08:00
ae4070ae88 1 2026-02-11 19:18:26 +08:00
a83fd918a8 删除无关代码 2026-02-11 18:58:30 +08:00
4168da618e 1 2026-02-11 18:29:00 +08:00
d2ffada83f 添加KeyboardViewControllerHelp文件夹 2026-02-10 19:41:32 +08:00
76d387e08b 修改UI遮挡后面爱心和评论的问题 2026-02-10 19:15:05 +08:00
ea0df4fb19 修改UI 2026-02-10 19:12:09 +08:00
02323fb5f1 处理键盘闪的问题 2026-02-10 18:53:31 +08:00
3c71797b7b 处理键盘崩溃 2026-02-10 13:22:19 +08:00
4c57f16058 1 2026-02-10 10:21:21 +08:00
cb2e8467a7 处理UI 2026-02-09 19:31:47 +08:00
4dfd6f5cbb 处理UI 2026-02-09 16:53:30 +08:00
e4223b3a4c 处理国际化 2026-02-09 16:30:00 +08:00
3d19403539 修改bug 2026-02-09 14:24:31 +08:00
3cb02d5b76 处理键盘ai功能 2026-02-05 16:01:21 +08:00
750b391100 处理键盘语音svip权限弹窗,跳转主app充值 2026-02-05 14:10:24 +08:00
faccf6f10f 1 2026-02-04 23:40:06 +08:00
35b1fc0f1e 处理vip逻辑 2026-02-04 21:49:28 +08:00
b73f225d15 语音vip限制ai弹窗 2026-02-04 20:23:20 +08:00
dd59094a16 处理了评论新增评论,在聊天里的评论数也一同改变 2026-02-04 19:31:30 +08:00
bacaf537f3 调用删除接口 2026-02-04 19:01:58 +08:00
619d356d31 新增接口 2026-02-04 18:45:57 +08:00
db9f07d199 优化长按聊天记录 如果是自己 不要显示举报 2026-02-04 18:39:25 +08:00
3ed120106e 处理二级评论回复用户名称显示问题 2026-02-04 18:29:12 +08:00
ff4edab820 1 2026-02-04 18:10:39 +08:00
3e30f619b9 新增删除弹窗,修改bug 2026-02-04 16:57:19 +08:00
533e23ebfe 1 2026-02-04 16:03:39 +08:00
85fb407717 处理支付 2026-02-04 15:57:22 +08:00
c1b50b407d 2 2026-02-04 15:40:45 +08:00
7c7e2477cb 1 2026-02-04 15:20:53 +08:00
faae0297cb 1 2026-02-04 15:09:03 +08:00
e50eaecbd9 1 2026-02-04 14:59:02 +08:00
879dbb860c 2 2026-02-04 13:14:26 +08:00
b4e4b7b606 处理svip 2026-02-04 12:48:18 +08:00
68a610e0a8 处理kbpayvip 2026-02-04 12:33:01 +08:00
305326aa9a 添加placehold颜色 2026-02-03 20:51:48 +08:00
61095a379f 处理网络 2026-02-03 20:22:28 +08:00
822a814f85 处理搜索bug 2026-02-03 18:03:21 +08:00
0bd0392191 1 2026-02-03 17:00:42 +08:00
b9663037f5 添加侧边栏 2026-02-03 16:54:38 +08:00
a0923c8572 添加消息长按弹窗 2026-02-03 15:53:07 +08:00
d482cfcb7d 处理滚动 2026-02-03 15:01:08 +08:00
9e6d2906f8 修改KBChatUserMessageCell文字居中问题 2026-02-03 14:40:39 +08:00
235 changed files with 10596 additions and 6985 deletions

View File

@@ -0,0 +1,13 @@
{
"permissions": {
"allow": [
"WebSearch",
"Bash(git checkout:*)",
"Bash(xcodebuild:*)",
"Bash(plutil:*)",
"Bash(find:*)",
"Bash(ls:*)",
"Bash(wc:*)"
]
}
}

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ai_limit_close@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ai_limit_close@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ai_limit_goto@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ai_limit_goto@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ai_limit_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ai_limit_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,712 @@
//
// KeyboardViewController+Chat.m
// CustomKeyboard
//
// Created by Codex on 2026/02/22.
//
#import "KeyboardViewController+Private.h"
#import "KBChatLimitPopView.h"
#import "KBChatMessage.h"
#import "KBChatPanelView.h"
#import "KBFullAccessManager.h"
#import "KBHostAppLauncher.h"
#import "KBInputBufferManager.h"
#import "KBNetworkManager.h"
#import "KBVM.h"
#import "Masonry.h"
#import <AVFoundation/AVFoundation.h>
static const NSUInteger kKBChatMessageLimit = 6;
@implementation KeyboardViewController (Chat)
#pragma mark - KBChatPanelViewDelegate
- (void)chatPanelView:(KBChatPanelView *)view didSendText:(NSString *)text {
NSString *trim =
[text stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (trim.length == 0) {
return;
}
[self kb_sendChatText:trim];
}
- (void)chatPanelView:(KBChatPanelView *)view
didTapMessage:(KBChatMessage *)message {
if (message.audioFilePath.length == 0) {
return;
}
[self kb_playChatAudioAtPath:message.audioFilePath];
}
- (void)chatPanelView:(KBChatPanelView *)view
didTapVoiceButtonForMessage:(KBChatMessage *)message {
if (!message)
return;
// audioData
if (message.audioData && message.audioData.length > 0) {
[self kb_playChatAudioData:message.audioData];
return;
}
// audioFilePath
if (message.audioFilePath.length > 0) {
[self kb_playChatAudioAtPath:message.audioFilePath];
return;
}
NSLog(@"[Keyboard] 没有音频数据可播放");
}
- (void)chatPanelViewDidTapClose:(KBChatPanelView *)view {
// chatPanelView
[view kb_reloadWithMessages:@[]];
if (self.chatAudioPlayer.isPlaying) {
[self.chatAudioPlayer stop];
}
self.chatAudioPlayer = nil;
[self kb_setPanelMode:KBKeyboardPanelModeMain animated:YES];
}
#pragma mark - Chat Helpers
- (void)kb_handleChatSendAction {
if (!self.chatPanelVisible) {
return;
}
[[KBInputBufferManager shared]
refreshFromProxyIfPossible:self.textDocumentProxy];
NSString *fullText = [KBInputBufferManager shared].liveText ?: @"";
// 宿线
NSString *baseline = self.chatPanelBaselineText ?: @"";
NSString *rawText = fullText;
if (baseline.length > 0 && [fullText hasPrefix:baseline]) {
rawText = [fullText substringFromIndex:baseline.length];
}
NSString *trim =
[rawText stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *textToClear = rawText;
if (trim.length == 0) {
//
//
NSString *fullTrim =
[fullText stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (fullTrim.length > 0) {
trim = fullTrim;
textToClear = fullText;
}
}
if (trim.length == 0) {
[KBHUD showInfo:KBLocalized(@"请输入内容")];
return;
}
[self kb_sendChatText:trim];
//
[self kb_clearHostInputForText:textToClear];
}
- (void)kb_sendChatText:(NSString *)text {
if (text.length == 0) {
return;
}
NSLog(@"[KB] 发送消息: %@", text);
KBChatMessage *outgoing = [KBChatMessage userMessageWithText:text];
outgoing.avatarURL = [self kb_sharedUserAvatarURL];
[self.chatPanelView kb_addUserMessage:text];
[self kb_prefetchAvatarForMessage:outgoing];
if (![[KBFullAccessManager shared] ensureFullAccessOrGuideInView:self.view]) {
[KBHUD showInfo:KBLocalized(@"请开启完全访问后使用")];
return;
}
// loading
[self.chatPanelView kb_addLoadingAssistantMessage];
//
[self kb_requestChatMessageWithContent:text];
}
#pragma mark - Chat Limit Pop
- (void)kb_showChatLimitPopWithMessage:(NSString *)message {
[self kb_dismissChatLimitPop];
UIControl *mask = [[UIControl alloc] init];
mask.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4];
mask.alpha = 0.0;
[mask addTarget:self
action:@selector(kb_dismissChatLimitPop)
forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:mask];
[mask mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.contentView);
}];
CGFloat width = 252.0;
CGFloat height = 252.0 + 18.0 + 53.0 + 18.0 + 28.0;
KBChatLimitPopView *content =
[[KBChatLimitPopView alloc] initWithFrame:CGRectMake(0, 0, width, height)];
content.message = message ?: @"";
content.delegate = self;
[mask addSubview:content];
[content mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(mask);
make.width.mas_equalTo(width);
make.height.mas_equalTo(height);
}];
self.chatLimitMaskView = mask;
[self.contentView bringSubviewToFront:mask];
[UIView animateWithDuration:0.18
animations:^{
mask.alpha = 1.0;
}];
}
- (void)kb_dismissChatLimitPop {
if (!self.chatLimitMaskView) {
return;
}
UIControl *mask = self.chatLimitMaskView;
self.chatLimitMaskView = nil;
[UIView animateWithDuration:0.15
animations:^{
mask.alpha = 0.0;
}
completion:^(__unused BOOL finished) {
[mask removeFromSuperview];
}];
}
- (void)kb_clearHostInputForText:(NSString *)text {
if (text.length == 0) {
return;
}
NSUInteger count = [self kb_composedCharacterCountForString:text];
for (NSUInteger i = 0; i < count; i++) {
[self.textDocumentProxy deleteBackward];
}
[[KBInputBufferManager shared] clearAllLiveText];
[self kb_clearCurrentWord];
}
- (NSUInteger)kb_composedCharacterCountForString:(NSString *)text {
if (text.length == 0) {
return 0;
}
__block NSUInteger count = 0;
[text enumerateSubstringsInRange:NSMakeRange(0, text.length)
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(__unused NSString *substring,
__unused NSRange substringRange,
__unused NSRange enclosingRange,
__unused BOOL *stop) {
count += 1;
}];
return count;
}
- (NSString *)kb_sharedUserAvatarURL {
NSUserDefaults *ud = [[NSUserDefaults alloc] initWithSuiteName:AppGroup];
NSString *url = [ud stringForKey:AppGroup_UserAvatarURL];
return url ?: @"";
}
- (void)kb_prefetchAvatarForMessage:(KBChatMessage *)message {
if (!message || message.avatarImage) {
return;
}
NSString *urlString = message.avatarURL ?: @"";
if (urlString.length == 0) {
return;
}
if (![[KBFullAccessManager shared] hasFullAccess]) {
return;
}
__weak typeof(self) weakSelf = self;
[[KBVM shared] downloadAvatarFromURL:urlString
completion:^(UIImage *image, NSError *error) {
__strong typeof(weakSelf) self = weakSelf;
if (!self || !image)
return;
message.avatarImage = image;
[self kb_reloadChatRowForMessage:message];
}];
}
- (void)kb_reloadChatRowForMessage:(KBChatMessage *)message {
//
//
//
}
- (void)kb_requestChatAudioForText:(NSString *)text {
NSString *mockPath = [self kb_mockChatAudioPath];
if (mockPath.length > 0) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.35 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
NSString *displayText = KBLocalized(@"语音回复");
KBChatMessage *incoming =
[KBChatMessage messageWithText:displayText
outgoing:NO
audioFilePath:mockPath];
incoming.displayName = KBLocalized(@"AI助手");
[self kb_appendChatMessage:incoming];
[self kb_playChatAudioAtPath:mockPath];
});
return;
}
NSDictionary *payload = @{@"message" : text ?: @""};
__weak typeof(self) weakSelf = self;
[[KBNetworkManager shared] POST:API_AI_TALK
jsonBody:payload
headers:nil
completion:^(NSDictionary *json, NSURLResponse *response,
NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) self = weakSelf;
if (!self) {
return;
}
if (error) {
NSString *tip = error.localizedDescription
?: KBLocalized(@"请求失败");
[KBHUD showInfo:tip];
return;
}
NSString *displayText =
[self kb_chatTextFromJSON:json];
NSString *audioURL =
[self kb_chatAudioURLFromJSON:json];
NSString *audioBase64 =
[self kb_chatAudioBase64FromJSON:json];
if (audioURL.length > 0) {
[self kb_downloadChatAudioFromURL:audioURL
displayText:displayText];
return;
}
if (audioBase64.length > 0) {
NSData *data = [[NSData alloc]
initWithBase64EncodedString:audioBase64
options:0];
if (data.length == 0) {
[KBHUD showInfo:KBLocalized(@"音频数据解析失败")];
return;
}
[self kb_handleChatAudioData:data
fileExtension:@"m4a"
displayText:displayText];
return;
}
[KBHUD showInfo:KBLocalized(@"未获取到音频文件")];
});
}];
}
#pragma mark - New Chat API (with typewriter effect and audio preload)
/// audioId
- (void)kb_requestChatMessageWithContent:(NSString *)content {
if (content.length == 0) {
[self.chatPanelView kb_removeLoadingAssistantMessage];
return;
}
NSInteger companionId = [[KBVM shared] selectedCompanionIdFromAppGroup];
NSLog(@"[KB] 请求聊天: companionId=%ld", (long)companionId);
__weak typeof(self) weakSelf = self;
[[KBVM shared] sendChatMessageWithContent:content
companionId:companionId
completion:^(KBChatResponse *response) {
__strong typeof(weakSelf) self = weakSelf;
if (!self)
return;
if (response.code != 0) {
if (response.code == 50030) {
NSLog(@"[KB] ⚠️ 次数用尽: %@",
response.message);
[self.chatPanelView
kb_removeLoadingAssistantMessage];
[self kb_showChatLimitPopWithMessage:
response.message];
return;
}
NSLog(@"[KB] ❌ 请求失败: %@",
response.message);
[self.chatPanelView
kb_removeLoadingAssistantMessage];
[KBHUD showInfo:response.message
?: KBLocalized(@"请求失败")];
return;
}
NSLog(@"[KB] ✅ 收到回复: %@",
response.data.aiResponse);
if (response.data.aiResponse.length == 0) {
[self.chatPanelView
kb_removeLoadingAssistantMessage];
[KBHUD showInfo:KBLocalized(@"未获取到回复内容")];
return;
}
// AI
NSLog(@"[KB] 准备添加 AI 消息");
[self.chatPanelView
kb_addAssistantMessage:response.data.aiResponse
audioId:response.data.audioId];
NSLog(@"[KB] AI 消息添加完成");
// App persona
[self kb_notifyMainAppChatUpdatedWithCompanionId:companionId];
// audioId
if (response.data.audioId.length > 0) {
[self kb_preloadAudioWithAudioId:
response.data.audioId];
}
}];
}
/// AppGroup persona companionId
- (NSInteger)kb_selectedCompanionId {
return [[KBVM shared] selectedCompanionIdFromAppGroup];
}
#pragma mark - Audio Preload
/// audioURL
- (void)kb_preloadAudioWithAudioId:(NSString *)audioId {
if (audioId.length == 0)
return;
NSLog(@"[Keyboard] 开始预加载音频audioId: %@", audioId);
__weak typeof(self) weakSelf = self;
[[KBVM shared] pollAudioURLWithAudioId:audioId
maxRetries:10
interval:1.0
completion:^(KBAudioResponse *response) {
__strong typeof(weakSelf) self = weakSelf;
if (!self)
return;
if (!response.success ||
response.audioURL.length == 0) {
NSLog(@"[Keyboard] ❌ 预加载音频 URL 获取失败: %@",
response.errorMessage);
return;
}
NSLog(@"[Keyboard] ✅ 预加载音频 URL 获取成功");
//
[[KBVM shared]
downloadAudioFromURL:response.audioURL
completion:^(
KBAudioResponse *audioResponse) {
if (!audioResponse.success) {
NSLog(@"[Keyboard] ❌ 预加载音频下载失败: %@",
audioResponse.errorMessage);
return;
}
// AI
[self.chatPanelView
kb_updateLastAssistantMessageWithAudioData:
audioResponse.audioData
duration:
audioResponse.duration];
NSLog(@"[Keyboard] ✅ 预加载音频完成,音频时长: %.2f秒",
audioResponse.duration);
}];
}];
}
- (void)kb_downloadChatAudioFromURL:(NSString *)audioURL
displayText:(NSString *)displayText {
__weak typeof(self) weakSelf = self;
[[KBVM shared] downloadAudioFromURL:audioURL
completion:^(KBAudioResponse *response) {
__strong typeof(weakSelf) self = weakSelf;
if (!self)
return;
if (!response.success) {
[KBHUD showInfo:response.errorMessage
?: KBLocalized(@"下载失败")];
return;
}
if (!response.audioData ||
response.audioData.length == 0) {
[KBHUD showInfo:KBLocalized(@"未获取到音频数据")];
return;
}
NSString *ext = @"m4a";
NSURL *url = [NSURL URLWithString:audioURL];
if (url.pathExtension.length > 0) {
ext = url.pathExtension;
}
[self kb_handleChatAudioData:response.audioData
fileExtension:ext
displayText:displayText];
}];
}
- (void)kb_handleChatAudioData:(NSData *)data
fileExtension:(NSString *)extension
displayText:(NSString *)displayText {
if (data.length == 0) {
[KBHUD showInfo:KBLocalized(@"音频数据为空")];
return;
}
NSString *ext = extension.length > 0 ? extension : @"m4a";
NSString *fileName = [NSString
stringWithFormat:@"kb_chat_%@.%@",
@((long long)([NSDate date].timeIntervalSince1970 *
1000)),
ext];
NSString *filePath =
[NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
if (![data writeToFile:filePath atomically:YES]) {
[KBHUD showInfo:KBLocalized(@"音频保存失败")];
return;
}
NSString *text =
displayText.length > 0 ? displayText : KBLocalized(@"语音消息");
KBChatMessage *incoming =
[KBChatMessage messageWithText:text outgoing:NO audioFilePath:filePath];
incoming.displayName = KBLocalized(@"AI助手");
[self kb_appendChatMessage:incoming];
}
- (void)kb_appendChatMessage:(KBChatMessage *)message {
if (!message) {
return;
}
[self.chatMessages addObject:message];
if (self.chatMessages.count > kKBChatMessageLimit) {
NSUInteger overflow = self.chatMessages.count - kKBChatMessageLimit;
NSArray<KBChatMessage *> *removed =
[self.chatMessages subarrayWithRange:NSMakeRange(0, overflow)];
[self.chatMessages removeObjectsInRange:NSMakeRange(0, overflow)];
for (KBChatMessage *msg in removed) {
if (msg.audioFilePath.length > 0) {
NSString *tmpRoot = NSTemporaryDirectory();
if (tmpRoot.length > 0 && [msg.audioFilePath hasPrefix:tmpRoot]) {
[[NSFileManager defaultManager] removeItemAtPath:msg.audioFilePath
error:nil];
}
}
}
}
[self.chatPanelView kb_reloadWithMessages:self.chatMessages];
}
- (NSString *)kb_mockChatAudioPath {
NSString *path = [[NSBundle mainBundle] pathForResource:@"ai_test"
ofType:@"m4a"];
return path ?: @"";
}
- (NSString *)kb_chatTextFromJSON:(NSDictionary *)json {
NSDictionary *data = [self kb_chatDataDictionaryFromJSON:json];
NSString *text =
[self kb_stringValueInDict:data keys:@[ @"text", @"message", @"content" ]];
if (text.length == 0) {
text = [self kb_stringValueInDict:json
keys:@[ @"text", @"message", @"content" ]];
}
return text ?: @"";
}
- (NSString *)kb_chatAudioURLFromJSON:(NSDictionary *)json {
NSDictionary *data = [self kb_chatDataDictionaryFromJSON:json];
NSArray<NSString *> *keys =
@[ @"audioUrl", @"audioURL", @"audio_url", @"url", @"fileUrl",
@"file_url", @"audioFileUrl", @"audio_file_url" ];
NSString *url = [self kb_stringValueInDict:data keys:keys];
if (url.length == 0) {
url = [self kb_stringValueInDict:json keys:keys];
}
return url ?: @"";
}
- (NSString *)kb_chatAudioBase64FromJSON:(NSDictionary *)json {
NSDictionary *data = [self kb_chatDataDictionaryFromJSON:json];
NSArray<NSString *> *keys =
@[ @"audioBase64", @"audio_base64", @"audioData", @"audio_data",
@"base64" ];
NSString *b64 = [self kb_stringValueInDict:data keys:keys];
if (b64.length == 0) {
b64 = [self kb_stringValueInDict:json keys:keys];
}
return b64 ?: @"";
}
- (NSDictionary *)kb_chatDataDictionaryFromJSON:(NSDictionary *)json {
if (![json isKindOfClass:[NSDictionary class]]) {
return @{};
}
id dataObj = json[@"data"] ?: json[@"result"] ?: json[@"response"];
if ([dataObj isKindOfClass:[NSDictionary class]]) {
return (NSDictionary *)dataObj;
}
return @{};
}
- (NSString *)kb_stringValueInDict:(NSDictionary *)dict
keys:(NSArray<NSString *> *)keys {
if (![dict isKindOfClass:[NSDictionary class]]) {
return @"";
}
for (NSString *key in keys) {
id value = dict[key];
if ([value isKindOfClass:[NSString class]] &&
((NSString *)value).length > 0) {
return (NSString *)value;
}
}
return @"";
}
- (void)kb_playChatAudioAtPath:(NSString *)path {
if (path.length == 0) {
return;
}
NSURL *url = [NSURL fileURLWithPath:path];
if (![NSFileManager.defaultManager fileExistsAtPath:path]) {
[KBHUD showInfo:KBLocalized(@"音频文件不存在")];
return;
}
if (self.chatAudioPlayer && self.chatAudioPlayer.isPlaying) {
NSURL *currentURL = self.chatAudioPlayer.url;
if ([currentURL isEqual:url]) {
[self.chatAudioPlayer stop];
self.chatAudioPlayer = nil;
return;
}
[self.chatAudioPlayer stop];
self.chatAudioPlayer = nil;
}
NSError *sessionError = nil;
AVAudioSession *session = [AVAudioSession sharedInstance];
if ([session respondsToSelector:@selector(setCategory:options:error:)]) {
[session setCategory:AVAudioSessionCategoryPlayback
withOptions:AVAudioSessionCategoryOptionDuckOthers
error:&sessionError];
} else {
[session setCategory:AVAudioSessionCategoryPlayback error:&sessionError];
}
[session setActive:YES error:nil];
NSError *playerError = nil;
AVAudioPlayer *player =
[[AVAudioPlayer alloc] initWithContentsOfURL:url error:&playerError];
if (playerError || !player) {
[KBHUD showInfo:KBLocalized(@"音频播放失败")];
return;
}
self.chatAudioPlayer = player;
[player prepareToPlay];
[player play];
}
///
- (void)kb_playChatAudioData:(NSData *)audioData {
if (!audioData || audioData.length == 0) {
NSLog(@"[Keyboard] 音频数据为空");
return;
}
//
if (self.chatAudioPlayer && self.chatAudioPlayer.isPlaying) {
[self.chatAudioPlayer stop];
self.chatAudioPlayer = nil;
}
//
NSError *sessionError = nil;
AVAudioSession *session = [AVAudioSession sharedInstance];
if ([session respondsToSelector:@selector(setCategory:options:error:)]) {
[session setCategory:AVAudioSessionCategoryPlayback
withOptions:AVAudioSessionCategoryOptionDuckOthers
error:&sessionError];
} else {
[session setCategory:AVAudioSessionCategoryPlayback error:&sessionError];
}
[session setActive:YES error:nil];
//
NSError *playerError = nil;
AVAudioPlayer *player =
[[AVAudioPlayer alloc] initWithData:audioData error:&playerError];
if (playerError || !player) {
NSLog(@"[Keyboard] 音频播放器初始化失败: %@",
playerError.localizedDescription);
[KBHUD showInfo:KBLocalized(@"音频播放失败")];
return;
}
self.chatAudioPlayer = player;
player.volume = 1.0;
[player prepareToPlay];
[player play];
NSLog(@"[Keyboard] 开始播放音频,时长: %.2f秒", player.duration);
}
#pragma mark - Notify Main App
/// App persona
- (void)kb_notifyMainAppChatUpdatedWithCompanionId:(NSInteger)companionId {
NSUserDefaults *ud = [[NSUserDefaults alloc] initWithSuiteName:AppGroup];
[ud setInteger:companionId forKey:AppGroup_ChatUpdatedCompanionId];
[ud synchronize];
CFNotificationCenterPostNotification(
CFNotificationCenterGetDarwinNotifyCenter(),
(__bridge CFStringRef)kKBDarwinChatUpdated,
NULL, NULL, true);
NSLog(@"[KB] 已通知主 App 刷新 companionId=%ld 的聊天记录", (long)companionId);
}
#pragma mark - KBChatLimitPopViewDelegate
- (void)chatLimitPopViewDidTapCancel:(KBChatLimitPopView *)view {
[self kb_dismissChatLimitPop];
}
- (void)chatLimitPopViewDidTapRecharge:(KBChatLimitPopView *)view {
[self kb_dismissChatLimitPop];
NSString *urlString =
[NSString stringWithFormat:@"%@://recharge?src=keyboard&vipType=svip",
KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:urlString];
BOOL success = [KBHostAppLauncher openHostAppURL:scheme
fromResponder:self.view];
if (!success) {
[KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")];
}
}
@end

View File

@@ -0,0 +1,96 @@
//
// KeyboardViewController+Layout.m
// CustomKeyboard
//
// Created by Codex on 2026/02/22.
//
#import "KeyboardViewController+Private.h"
// 375 稿
static const CGFloat kKBKeyboardBaseHeight = 250.0f;
static const CGFloat kKBChatPanelHeight = 180;
@implementation KeyboardViewController (Layout)
- (CGFloat)kb_portraitWidth {
CGSize s = [UIScreen mainScreen].bounds.size;
return MIN(s.width, s.height);
}
- (CGFloat)kb_keyboardHeightForWidth:(CGFloat)width {
if (width <= 0) {
width = KB_DESIGN_WIDTH;
}
CGFloat scale = width / KB_DESIGN_WIDTH;
CGFloat baseHeight = kKBKeyboardBaseHeight * scale;
CGFloat chatHeight = kKBChatPanelHeight * scale;
if (self.chatPanelVisible) {
return baseHeight + chatHeight;
}
return baseHeight;
}
- (CGFloat)kb_keyboardBaseHeightForWidth:(CGFloat)width {
if (width <= 0) {
width = KB_DESIGN_WIDTH;
}
CGFloat scale = width / KB_DESIGN_WIDTH;
return kKBKeyboardBaseHeight * scale;
}
- (CGFloat)kb_chatPanelHeightForWidth:(CGFloat)width {
if (width <= 0) {
width = KB_DESIGN_WIDTH;
}
CGFloat scale = width / KB_DESIGN_WIDTH;
return kKBChatPanelHeight * scale;
}
- (void)kb_updateKeyboardLayoutIfNeeded {
CGFloat portraitWidth = [self kb_portraitWidth];
CGFloat keyboardHeight = [self kb_keyboardHeightForWidth:portraitWidth];
CGFloat keyboardBaseHeight =
[self kb_keyboardBaseHeightForWidth:portraitWidth];
CGFloat chatPanelHeight = [self kb_chatPanelHeightForWidth:portraitWidth];
CGFloat containerWidth = CGRectGetWidth(self.view.superview.bounds);
if (containerWidth <= 0) {
containerWidth = CGRectGetWidth(self.view.window.bounds);
}
if (containerWidth <= 0) {
containerWidth = CGRectGetWidth([UIScreen mainScreen].bounds);
}
BOOL widthChanged = (fabs(self.kb_lastPortraitWidth - portraitWidth) >= 0.5);
BOOL heightChanged =
(fabs(self.kb_lastKeyboardHeight - keyboardHeight) >= 0.5);
if (!widthChanged && !heightChanged && containerWidth > 0 &&
self.kb_widthConstraint.constant == containerWidth) {
return;
}
self.kb_lastPortraitWidth = portraitWidth;
self.kb_lastKeyboardHeight = keyboardHeight;
if (self.kb_heightConstraint) {
self.kb_heightConstraint.constant = keyboardHeight;
}
if (containerWidth > 0 && self.kb_widthConstraint) {
self.kb_widthConstraint.constant = containerWidth;
}
if (self.contentWidthConstraint) {
[self.contentWidthConstraint setOffset:portraitWidth];
}
if (self.contentHeightConstraint) {
[self.contentHeightConstraint setOffset:keyboardHeight];
}
if (self.keyBoardMainHeightConstraint) {
[self.keyBoardMainHeightConstraint setOffset:keyboardBaseHeight];
}
if (self.chatPanelHeightConstraint) {
[self.chatPanelHeightConstraint setOffset:chatPanelHeight];
}
[self.view layoutIfNeeded];
}
@end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,656 @@
//
// KeyboardViewController+Panels.m
// CustomKeyboard
//
// Created by Codex on 2026/02/22.
//
#import "KeyboardViewController+Private.h"
#import "KBAuthManager.h"
#import "KBBackspaceUndoManager.h"
#import "KBChatMessage.h"
#import "KBChatPanelView.h"
#import "KBFunctionView.h"
#import "KBFullAccessManager.h"
#import "KBHostAppLauncher.h"
#import "KBInputBufferManager.h"
#import "KBKey.h"
#import "KBKeyBoardMainView.h"
#import "KBKeyboardSubscriptionView.h"
#import "KBSettingView.h"
#import "Masonry.h"
#import <SDWebImage/SDWebImage.h>
#import <AVFoundation/AVAudioPlayer.h>
@implementation KeyboardViewController (Panels)
#pragma mark - Panel Mode
- (void)kb_setPanelMode:(KBKeyboardPanelMode)mode animated:(BOOL)animated {
if (mode == self.kb_panelMode) {
return;
}
KBKeyboardPanelMode fromMode = self.kb_panelMode;
// AI 访
if (mode == KBKeyboardPanelModeFunction &&
![[KBFullAccessManager shared] ensureFullAccessOrGuideInView:self.view]) {
return;
}
// mode Function
BOOL islogin = YES;
if (mode == KBKeyboardPanelModeFunction) {
[[KBAuthManager shared] reloadFromKeychain];
islogin = KBAuthManager.shared.isLoggedIn;
}
#if DEBUG
if (mode == KBKeyboardPanelModeFunction) {
NSString *token = [KBAuthManager shared].current.accessToken ?: @"";
NSLog(@"[AuthTrace][Ext] tapAI mode=%ld isLoggedIn=%d tokenLen=%lu",
(long)mode, islogin, (unsigned long)token.length);
}
#endif
if (mode == KBKeyboardPanelModeFunction && !islogin) {
[KBHUD showInfo:KBLocalized(@"请先登录后使用AI功能")];
NSString *schemeStr =
[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
if (!ok) {
[KBHUD showInfo:KBLocalized(@"请回到桌面手动打开App登录")];
}
return;
}
self.kb_panelMode = mode;
//
[self kb_ensureKeyBoardMainViewIfNeeded];
// 1) /
[self kb_setSubscriptionPanelVisible:NO animated:animated];
[self kb_setSettingViewVisible:NO animated:animated];
[self kb_setChatPanelVisible:NO animated:animated];
[self kb_setFunctionPanelVisible:NO];
// 2)
switch (mode) {
case KBKeyboardPanelModeFunction:
[self kb_setFunctionPanelVisible:YES];
break;
case KBKeyboardPanelModeChat:
[self kb_setChatPanelVisible:YES animated:animated];
break;
case KBKeyboardPanelModeSettings:
[self kb_setSettingViewVisible:YES animated:animated];
break;
case KBKeyboardPanelModeSubscription:
[self kb_setSubscriptionPanelVisible:YES animated:animated];
break;
case KBKeyboardPanelModeMain:
default:
break;
}
// 3) /
if (mode == KBKeyboardPanelModeFunction) {
[[KBMaiPointReporter sharedReporter]
reportPageExposureWithEventName:@"enter_keyboard_function_panel"
pageId:@"keyboard_function_panel"
extra:nil
completion:nil];
} else if (mode == KBKeyboardPanelModeMain &&
fromMode == KBKeyboardPanelModeFunction) {
[[KBMaiPointReporter sharedReporter]
reportPageExposureWithEventName:@"enter_keyboard_main_panel"
pageId:@"keyboard_main_panel"
extra:nil
completion:nil];
} else if (mode == KBKeyboardPanelModeSettings) {
[[KBMaiPointReporter sharedReporter]
reportPageExposureWithEventName:@"enter_keyboard_settings"
pageId:@"keyboard_settings"
extra:nil
completion:nil];
} else if (mode == KBKeyboardPanelModeSubscription) {
[[KBMaiPointReporter sharedReporter]
reportPageExposureWithEventName:@"enter_keyboard_subscription_panel"
pageId:@"keyboard_subscription_panel"
extra:nil
completion:nil];
}
// 4)
if (mode == KBKeyboardPanelModeSubscription) {
[self.contentView bringSubviewToFront:self.subscriptionView];
} else if (mode == KBKeyboardPanelModeSettings) {
[self.contentView bringSubviewToFront:self.settingView];
} else if (mode == KBKeyboardPanelModeChat) {
[self.contentView bringSubviewToFront:self.chatPanelView];
} else if (mode == KBKeyboardPanelModeFunction) {
[self.contentView bringSubviewToFront:self.functionView];
} else {
[self.contentView bringSubviewToFront:self.keyBoardMainView];
}
}
/// /
- (void)showFunctionPanel:(BOOL)show {
if (show) {
[self kb_setPanelMode:KBKeyboardPanelModeFunction animated:NO];
return;
}
if (self.kb_panelMode == KBKeyboardPanelModeFunction) {
[self kb_setPanelMode:KBKeyboardPanelModeMain animated:NO];
}
}
/// / keyBoardMainView /
- (void)showSettingView:(BOOL)show {
if (show) {
[self kb_setPanelMode:KBKeyboardPanelModeSettings animated:YES];
return;
}
if (self.kb_panelMode == KBKeyboardPanelModeSettings) {
[self kb_setPanelMode:KBKeyboardPanelModeMain animated:YES];
}
}
/// /
- (void)showChatPanel:(BOOL)show {
if (show) {
[self kb_setPanelMode:KBKeyboardPanelModeChat animated:YES];
return;
}
if (self.kb_panelMode == KBKeyboardPanelModeChat) {
[self kb_setPanelMode:KBKeyboardPanelModeMain animated:YES];
}
}
- (void)kb_setFunctionPanelVisible:(BOOL)visible {
if (visible) {
[self kb_ensureFunctionViewIfNeeded];
}
if (_functionView) {
_functionView.hidden = !visible;
} else if (visible) {
// ensure
self.functionView.hidden = NO;
}
self.keyBoardMainView.hidden = visible;
}
- (void)kb_setChatPanelVisible:(BOOL)visible animated:(BOOL)animated {
if (visible == self.chatPanelVisible) {
return;
}
self.chatPanelVisible = visible;
if (visible) {
// 宿
[[KBInputBufferManager shared]
refreshFromProxyIfPossible:self.textDocumentProxy];
self.chatPanelBaselineText = [KBInputBufferManager shared].liveText ?: @"";
[self kb_ensureChatPanelViewIfNeeded];
self.chatPanelView.hidden = NO;
self.chatPanelView.alpha = 0.0;
if (animated) {
[UIView animateWithDuration:0.2
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.chatPanelView.alpha = 1.0;
}
completion:nil];
} else {
self.chatPanelView.alpha = 1.0;
}
} else {
// show/hide
if (!_chatPanelView) {
[self kb_updateKeyboardLayoutIfNeeded];
return;
}
if (animated) {
[UIView animateWithDuration:0.18
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
self.chatPanelView.alpha = 0.0;
}
completion:^(BOOL finished) {
self.chatPanelView.hidden = YES;
}];
} else {
self.chatPanelView.alpha = 0.0;
self.chatPanelView.hidden = YES;
}
}
[self kb_updateKeyboardLayoutIfNeeded];
}
- (void)kb_setSettingViewVisible:(BOOL)visible animated:(BOOL)animated {
if (visible) {
KBSettingView *settingView = self.settingView;
if (!settingView.superview) {
settingView.hidden = YES;
[self.contentView addSubview:settingView];
[settingView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.contentView);
}];
[settingView.backButton addTarget:self
action:@selector(onTapSettingsBack)
forControlEvents:UIControlEventTouchUpInside];
}
[self.contentView bringSubviewToFront:settingView];
// keyBoardMainView self.view
[self.contentView layoutIfNeeded];
CGFloat w = CGRectGetWidth(self.keyBoardMainView.bounds);
if (w <= 0) {
w = CGRectGetWidth(self.contentView.bounds);
}
if (w <= 0) {
w = [self kb_portraitWidth];
}
settingView.transform = CGAffineTransformMakeTranslation(w, 0);
settingView.hidden = NO;
if (animated) {
[UIView animateWithDuration:0.25
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
settingView.transform = CGAffineTransformIdentity;
}
completion:nil];
} else {
settingView.transform = CGAffineTransformIdentity;
}
} else {
KBSettingView *settingView = _settingView;
if (!settingView) {
return;
}
if (!settingView.superview || settingView.hidden) {
return;
}
CGFloat w = CGRectGetWidth(self.keyBoardMainView.bounds);
if (w <= 0) {
w = CGRectGetWidth(self.contentView.bounds);
}
if (w <= 0) {
w = [self kb_portraitWidth];
}
if (animated) {
[UIView animateWithDuration:0.22
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
settingView.transform = CGAffineTransformMakeTranslation(w, 0);
}
completion:^(BOOL finished) {
settingView.hidden = YES;
}];
} else {
settingView.transform = CGAffineTransformMakeTranslation(w, 0);
settingView.hidden = YES;
}
}
}
- (void)kb_setSubscriptionPanelVisible:(BOOL)visible animated:(BOOL)animated {
if (visible) {
KBKeyboardSubscriptionView *panel = self.subscriptionView;
if (!panel.superview) {
panel.hidden = YES;
[self.contentView addSubview:panel];
[panel mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.contentView);
}];
}
[self.contentView bringSubviewToFront:panel];
panel.hidden = NO;
panel.alpha = 0.0;
CGFloat height = CGRectGetHeight(self.contentView.bounds);
if (height <= 0) {
height = 260;
}
panel.transform = CGAffineTransformMakeTranslation(0, height);
[panel refreshProductsIfNeeded];
if (animated) {
[UIView animateWithDuration:0.25
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
panel.alpha = 1.0;
panel.transform = CGAffineTransformIdentity;
}
completion:nil];
} else {
panel.alpha = 1.0;
panel.transform = CGAffineTransformIdentity;
}
return;
}
KBKeyboardSubscriptionView *panel = _subscriptionView;
if (!panel) {
return;
}
if (!panel.superview || panel.hidden) {
return;
}
CGFloat height = CGRectGetHeight(panel.bounds);
if (height <= 0) {
height = CGRectGetHeight(self.contentView.bounds);
}
if (animated) {
[UIView animateWithDuration:0.22
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
panel.alpha = 0.0;
panel.transform = CGAffineTransformMakeTranslation(0, height);
}
completion:^(BOOL finished) {
panel.hidden = YES;
panel.alpha = 1.0;
panel.transform = CGAffineTransformIdentity;
}];
} else {
panel.hidden = YES;
panel.alpha = 1.0;
panel.transform = CGAffineTransformIdentity;
}
}
// /
- (void)kb_ensureFunctionViewIfNeeded {
if (_functionView && _functionView.superview) {
return;
}
KBFunctionView *v = self.functionView;
if (!v.superview) {
v.hidden = YES;
[self.contentView addSubview:v];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.contentView);
}];
}
}
// /
- (void)kb_ensureChatPanelViewIfNeeded {
if (_chatPanelView && _chatPanelView.superview) {
return;
}
CGFloat portraitWidth = [self kb_portraitWidth];
CGFloat chatPanelHeight = [self kb_chatPanelHeightForWidth:portraitWidth];
KBChatPanelView *v = self.chatPanelView;
if (!v.superview) {
[self.contentView addSubview:v];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.contentView);
make.bottom.equalTo(self.keyBoardMainView.mas_top);
self.chatPanelHeightConstraint =
make.height.mas_equalTo(chatPanelHeight);
}];
v.hidden = YES;
}
}
//
- (void)kb_ensureKeyBoardMainViewIfNeeded {
if (_keyBoardMainView && _keyBoardMainView.superview) {
return;
}
CGFloat portraitWidth = [self kb_portraitWidth];
CGFloat keyboardBaseHeight =
[self kb_keyboardBaseHeightForWidth:portraitWidth];
KBKeyBoardMainView *v = self.keyBoardMainView;
if (!v.superview) {
[self.contentView addSubview:v];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.contentView);
make.bottom.equalTo(self.contentView);
self.keyBoardMainHeightConstraint =
make.height.mas_equalTo(keyboardBaseHeight);
}];
}
[self.contentView bringSubviewToFront:v];
}
// //
- (void)kb_releaseMemoryWhenKeyboardHidden {
[KBHUD setContainerView:nil];
self.bgImageView.image = nil;
self.kb_cachedGradientImage = nil;
[self.kb_defaultGradientLayer removeFromSuperlayer];
self.kb_defaultGradientLayer = nil;
[[SDImageCache sharedImageCache] clearMemory];
// /
if (self.chatAudioPlayer) {
[self.chatAudioPlayer stop];
self.chatAudioPlayer = nil;
}
if (_chatMessages.count > 0) {
NSString *tmpRoot = NSTemporaryDirectory();
for (KBChatMessage *msg in _chatMessages.copy) {
if (tmpRoot.length > 0 && msg.audioFilePath.length > 0 &&
[msg.audioFilePath hasPrefix:tmpRoot]) {
[[NSFileManager defaultManager] removeItemAtPath:msg.audioFilePath
error:nil];
}
}
[_chatMessages removeAllObjects];
}
if (_keyBoardMainView) {
[_keyBoardMainView removeFromSuperview];
_keyBoardMainView = nil;
}
self.keyBoardMainHeightConstraint = nil;
if (_functionView) {
[_functionView removeFromSuperview];
_functionView = nil;
}
if (_chatPanelView) {
[_chatPanelView removeFromSuperview];
_chatPanelView = nil;
}
self.chatPanelVisible = NO;
self.kb_panelMode = KBKeyboardPanelModeMain;
if (_subscriptionView) {
[_subscriptionView removeFromSuperview];
_subscriptionView = nil;
}
if (_settingView) {
[_settingView removeFromSuperview];
_settingView = nil;
}
}
// MARK: - KBKeyBoardMainViewDelegate
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView
didTapKey:(KBKey *)key {
switch (key.type) {
case KBKeyTypeCharacter: {
[[KBBackspaceUndoManager shared] registerNonClearAction];
NSString *text = key.output ?: key.title ?: @"";
[self.textDocumentProxy insertText:text];
[self kb_updateCurrentWordWithInsertedText:text];
[[KBInputBufferManager shared] appendText:text];
} break;
case KBKeyTypeBackspace:
[[KBInputBufferManager shared]
refreshFromProxyIfPossible:self.textDocumentProxy];
[[KBInputBufferManager shared]
prepareSnapshotForDeleteWithContextBefore:
self.textDocumentProxy.documentContextBeforeInput
after:
self.textDocumentProxy
.documentContextAfterInput];
[[KBBackspaceUndoManager shared]
captureAndDeleteBackwardFromProxy:self.textDocumentProxy
count:1];
[self kb_scheduleContextRefreshResetSuppression:NO];
[[KBInputBufferManager shared] applyHoldDeleteCount:1];
break;
case KBKeyTypeSpace:
[[KBBackspaceUndoManager shared] registerNonClearAction];
[self.textDocumentProxy insertText:@" "];
[self kb_clearCurrentWord];
[[KBInputBufferManager shared] appendText:@" "];
break;
case KBKeyTypeReturn:
if (self.chatPanelVisible) {
[self kb_handleChatSendAction];
break;
}
[[KBBackspaceUndoManager shared] registerNonClearAction];
[self.textDocumentProxy insertText:@"\n"];
[self kb_clearCurrentWord];
[[KBInputBufferManager shared] appendText:@"\n"];
break;
case KBKeyTypeGlobe:
[self advanceToNextInputMode];
break;
case KBKeyTypeCustom:
[[KBBackspaceUndoManager shared] registerNonClearAction];
//
[self kb_setPanelMode:KBKeyboardPanelModeFunction animated:NO];
[self kb_clearCurrentWord];
break;
case KBKeyTypeModeChange:
case KBKeyTypeShift:
// KBKeyBoardMainView/KBKeyboardView
break;
}
}
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView
didTapToolActionAtIndex:(NSInteger)index {
NSDictionary *extra = @{@"index" : @(index)};
[[KBMaiPointReporter sharedReporter]
reportClickWithEventName:@"click_keyboard_toolbar_action"
pageId:@"keyboard_main_panel"
elementId:@"toolbar_action"
extra:extra
completion:nil];
if (index == 0) {
[self kb_setPanelMode:KBKeyboardPanelModeFunction animated:YES];
[self kb_clearCurrentWord];
return;
}
if (index == 1) {
[self kb_setPanelMode:KBKeyboardPanelModeChat animated:YES];
return;
}
[self kb_setPanelMode:KBKeyboardPanelModeMain animated:YES];
}
- (void)keyBoardMainViewDidTapSettings:(KBKeyBoardMainView *)keyBoardMainView {
[[KBMaiPointReporter sharedReporter]
reportClickWithEventName:@"click_keyboard_settings_btn"
pageId:@"keyboard_main_panel"
elementId:@"settings_btn"
extra:nil
completion:nil];
[self kb_setPanelMode:KBKeyboardPanelModeSettings animated:YES];
}
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView
didSelectEmoji:(NSString *)emoji {
if (emoji.length == 0) {
return;
}
[[KBBackspaceUndoManager shared] registerNonClearAction];
[self.textDocumentProxy insertText:emoji];
[self kb_clearCurrentWord];
[[KBInputBufferManager shared] appendText:emoji];
}
- (void)keyBoardMainViewDidTapUndo:(KBKeyBoardMainView *)keyBoardMainView {
[[KBMaiPointReporter sharedReporter]
reportClickWithEventName:@"click_keyboard_undo_btn"
pageId:@"keyboard_main_panel"
elementId:@"undo_btn"
extra:nil
completion:nil];
[[KBBackspaceUndoManager shared] performUndoFromResponder:self.view];
[self kb_scheduleContextRefreshResetSuppression:YES];
}
- (void)keyBoardMainViewDidTapEmojiSearch:
(KBKeyBoardMainView *)keyBoardMainView {
// [[KBMaiPointReporter sharedReporter]
// reportClickWithEventName:@"click_keyboard_emoji_search_btn"
// pageId:@"keyboard_main_panel"
// elementId:@"emoji_search_btn"
// extra:nil
// completion:nil];
[KBHUD showInfo:KBLocalized(@"Search coming soon")];
}
// MARK: - KBFunctionViewDelegate
- (void)functionView:(KBFunctionView *)functionView
didTapToolActionAtIndex:(NSInteger)index {
// index == 0
if (index == 0) {
[self kb_setPanelMode:KBKeyboardPanelModeMain animated:NO];
}
}
- (void)functionView:(KBFunctionView *_Nullable)functionView
didRightTapToolActionAtIndex:(NSInteger)index {
[[KBMaiPointReporter sharedReporter]
reportClickWithEventName:@"click_keyboard_function_right_action"
pageId:@"keyboard_function_panel"
elementId:@"right_action"
extra:@{@"action" : @"login_or_recharge"}
completion:nil];
if (!KBAuthManager.shared.isLoggedIn) {
NSString *schemeStr =
[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
return;
}
NSString *schemeStr =
[NSString stringWithFormat:@"%@://recharge?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
if (!ok) {
//
// XXX App /
[KBHUD showInfo:@"请回到桌面手动打开App进行充值"];
}
}
- (void)functionViewDidRequestSubscription:(KBFunctionView *)functionView {
[self showSubscriptionPanel];
}
#pragma mark - Actions
- (void)onTapSettingsBack {
[[KBMaiPointReporter sharedReporter]
reportClickWithEventName:@"click_keyboard_settings_back_btn"
pageId:@"keyboard_settings"
elementId:@"back_btn"
extra:nil
completion:nil];
[self kb_setPanelMode:KBKeyboardPanelModeMain animated:YES];
}
@end

View File

@@ -0,0 +1,154 @@
//
// KeyboardViewController+Private.h
// CustomKeyboard
//
// Created by Codex on 2026/02/22.
//
#import "KeyboardViewController.h"
#import "Masonry.h"
@class AVAudioPlayer;
@class CAGradientLayer;
@class KBChatMessage;
@class KBChatPanelView;
@class KBFunctionView;
@class KBKeyBoardMainView;
@class KBKeyboardSubscriptionView;
@class KBSettingView;
@class KBSuggestionEngine;
@protocol KBChatLimitPopViewDelegate;
@protocol KBChatPanelViewDelegate;
@protocol KBFunctionViewDelegate;
@protocol KBKeyBoardMainViewDelegate;
@protocol KBKeyboardSubscriptionViewDelegate;
typedef NS_ENUM(NSInteger, KBKeyboardPanelMode) {
KBKeyboardPanelModeMain = 0,
KBKeyboardPanelModeFunction,
KBKeyboardPanelModeChat,
KBKeyboardPanelModeSettings,
KBKeyboardPanelModeSubscription,
};
@interface KeyboardViewController () <KBKeyBoardMainViewDelegate,
KBFunctionViewDelegate,
KBKeyboardSubscriptionViewDelegate,
KBChatPanelViewDelegate,
KBChatLimitPopViewDelegate>
{
UIButton *_nextKeyboardButton;
UIView *_contentView;
KBKeyBoardMainView *_keyBoardMainView;
KBFunctionView *_functionView;
KBSettingView *_settingView;
UIImageView *_bgImageView;
KBChatPanelView *_chatPanelView;
KBKeyboardSubscriptionView *_subscriptionView;
KBSuggestionEngine *_suggestionEngine;
NSString *_currentWord;
UIControl *_chatLimitMaskView;
MASConstraint *_contentWidthConstraint;
MASConstraint *_contentHeightConstraint;
MASConstraint *_keyBoardMainHeightConstraint;
MASConstraint *_chatPanelHeightConstraint;
NSLayoutConstraint *_kb_heightConstraint;
NSLayoutConstraint *_kb_widthConstraint;
CGFloat _kb_lastPortraitWidth;
CGFloat _kb_lastKeyboardHeight;
UIImage *_kb_cachedGradientImage;
CGSize _kb_cachedGradientSize;
CAGradientLayer *_kb_defaultGradientLayer;
NSString *_kb_lastAppliedThemeKey;
NSMutableArray<KBChatMessage *> *_chatMessages;
AVAudioPlayer *_chatAudioPlayer;
BOOL _suppressSuggestions;
BOOL _chatPanelVisible;
NSString *_chatPanelBaselineText;
id _kb_fullAccessObserverToken;
id _kb_skinObserverToken;
KBKeyboardPanelMode _kb_panelMode;
}
@property(nonatomic, strong)
UIButton *nextKeyboardButton; // 系统“下一个键盘”按钮(可选)
@property(nonatomic, strong) UIView *contentView;
@property(nonatomic, strong) KBKeyBoardMainView
*keyBoardMainView; // 功能面板视图点击工具栏第0个时显示
@property(nonatomic, strong)
KBFunctionView *functionView; // 功能面板视图点击工具栏第0个时显示
@property(nonatomic, strong) KBSettingView *settingView; // 设置页
@property(nonatomic, strong) UIImageView *bgImageView; // 背景图(在底层)
@property(nonatomic, strong) KBChatPanelView *chatPanelView;
@property(nonatomic, strong) KBKeyboardSubscriptionView *subscriptionView;
@property(nonatomic, strong) KBSuggestionEngine *suggestionEngine;
@property(nonatomic, copy) NSString *currentWord;
@property(nonatomic, assign) BOOL suppressSuggestions;
@property(nonatomic, strong) UIControl *chatLimitMaskView;
@property(nonatomic, strong) MASConstraint *contentWidthConstraint;
@property(nonatomic, strong) MASConstraint *contentHeightConstraint;
@property(nonatomic, strong) MASConstraint *keyBoardMainHeightConstraint;
@property(nonatomic, strong) MASConstraint *chatPanelHeightConstraint;
@property(nonatomic, strong) NSLayoutConstraint *kb_heightConstraint;
@property(nonatomic, strong) NSLayoutConstraint *kb_widthConstraint;
@property(nonatomic, assign) CGFloat kb_lastPortraitWidth;
@property(nonatomic, assign) CGFloat kb_lastKeyboardHeight;
@property(nonatomic, strong) UIImage *kb_cachedGradientImage;
@property(nonatomic, assign) CGSize kb_cachedGradientSize;
@property(nonatomic, strong, nullable) CAGradientLayer *kb_defaultGradientLayer;
@property(nonatomic, copy, nullable) NSString *kb_lastAppliedThemeKey;
@property(nonatomic, strong) NSMutableArray<KBChatMessage *> *chatMessages;
@property(nonatomic, strong) AVAudioPlayer *chatAudioPlayer;
@property(nonatomic, assign) BOOL chatPanelVisible;
@property(nonatomic, copy) NSString *chatPanelBaselineText; // 打开聊天面板时宿主输入框已有的文本
@property(nonatomic, strong, nullable) id kb_fullAccessObserverToken;
@property(nonatomic, strong, nullable) id kb_skinObserverToken;
@property(nonatomic, assign) KBKeyboardPanelMode kb_panelMode;
@end
@interface KeyboardViewController (KBPrivate)
// UI
- (void)setupUI;
- (nullable KBFunctionView *)kb_functionViewIfCreated;
// Panels
- (void)showFunctionPanel:(BOOL)show;
- (void)showSettingView:(BOOL)show;
- (void)showChatPanel:(BOOL)show;
- (void)showSubscriptionPanel;
- (void)hideSubscriptionPanel;
- (void)kb_setPanelMode:(KBKeyboardPanelMode)mode animated:(BOOL)animated;
- (void)kb_ensureFunctionViewIfNeeded;
- (void)kb_ensureChatPanelViewIfNeeded;
- (void)kb_ensureKeyBoardMainViewIfNeeded;
- (void)kb_releaseMemoryWhenKeyboardHidden;
// Suggestions
- (void)kb_updateCurrentWordWithInsertedText:(NSString *)text;
- (void)kb_clearCurrentWord;
- (void)kb_scheduleContextRefreshResetSuppression:(BOOL)resetSuppression;
- (void)kb_refreshCurrentWordFromDocumentContextResetSuppression:
(BOOL)resetSuppression;
- (void)kb_updateSuggestionsForCurrentWord;
// Chat
- (void)kb_handleChatSendAction;
// Theme
- (void)kb_applyTheme;
- (void)kb_applyDefaultSkinIfNeeded;
- (void)kb_consumePendingShopSkin;
- (void)kb_registerDarwinSkinInstallObserver;
- (void)kb_unregisterDarwinSkinInstallObserver;
// Layout
- (CGFloat)kb_portraitWidth;
- (CGFloat)kb_keyboardHeightForWidth:(CGFloat)width;
- (CGFloat)kb_keyboardBaseHeightForWidth:(CGFloat)width;
- (CGFloat)kb_chatPanelHeightForWidth:(CGFloat)width;
- (void)kb_updateKeyboardLayoutIfNeeded;
@end

View File

@@ -0,0 +1,117 @@
//
// KeyboardViewController+Subscription.m
// CustomKeyboard
//
// Created by Codex on 2026/02/22.
//
#import "KeyboardViewController+Private.h"
#import "KBAuthManager.h"
#import "KBFullAccessManager.h"
#import "KBHostAppLauncher.h"
#import "KBKeyboardSubscriptionProduct.h"
#import "KBKeyboardSubscriptionView.h"
@implementation KeyboardViewController (Subscription)
- (void)showSubscriptionPanel {
// 1) 访
if (![[KBFullAccessManager shared] hasFullAccess]) {
// 访
// [KBHUD showInfo:KBLocalized(@"处理中…")];
[[KBFullAccessManager shared] ensureFullAccessOrGuideInView:self.view];
return;
}
//
// 2) -> App App
if (!KBAuthManager.shared.isLoggedIn) {
NSString *schemeStr =
[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
return;
}
[self kb_setPanelMode:KBKeyboardPanelModeSubscription animated:YES];
}
- (void)hideSubscriptionPanel {
if (self.kb_panelMode != KBKeyboardPanelModeSubscription) {
return;
}
[self kb_setPanelMode:KBKeyboardPanelModeMain animated:YES];
}
#pragma mark - KBKeyboardSubscriptionViewDelegate
- (void)subscriptionViewDidTapClose:(KBKeyboardSubscriptionView *)view {
[[KBMaiPointReporter sharedReporter]
reportClickWithEventName:@"click_keyboard_subscription_close_btn"
pageId:@"keyboard_subscription_panel"
elementId:@"close_btn"
extra:nil
completion:nil];
[self hideSubscriptionPanel];
}
- (void)subscriptionView:(KBKeyboardSubscriptionView *)view
didTapPurchaseForProduct:(KBKeyboardSubscriptionProduct *)product {
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
if ([product.productId isKindOfClass:NSString.class] &&
product.productId.length > 0) {
extra[@"product_id"] = product.productId;
}
[[KBMaiPointReporter sharedReporter]
reportClickWithEventName:@"click_keyboard_subscription_product_btn"
pageId:@"keyboard_subscription_panel"
elementId:@"product_btn"
extra:extra.copy
completion:nil];
[self hideSubscriptionPanel];
[self kb_openRechargeForProduct:product];
}
#pragma mark - Actions
- (void)kb_openRechargeForProduct:(KBKeyboardSubscriptionProduct *)product {
if (![product isKindOfClass:KBKeyboardSubscriptionProduct.class] ||
product.productId.length == 0) {
[KBHUD showInfo:KBLocalized(@"Product unavailable")];
return;
}
NSString *encodedId = [self.class kb_urlEncodedString:product.productId];
NSString *title = [product displayTitle];
NSString *encodedTitle = [self.class kb_urlEncodedString:title];
NSMutableArray<NSString *> *params =
[NSMutableArray arrayWithObjects:@"autoPay=1", @"prefill=1", nil];
if (encodedId.length) {
[params addObject:[NSString stringWithFormat:@"productId=%@", encodedId]];
}
if (encodedTitle.length) {
[params
addObject:[NSString stringWithFormat:@"productTitle=%@", encodedTitle]];
}
NSString *query = [params componentsJoinedByString:@"&"];
NSString *urlString = [NSString
stringWithFormat:@"%@://recharge?src=keyboard&%@", KB_APP_SCHEME, query];
NSURL *scheme = [NSURL URLWithString:urlString];
BOOL success = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
if (!success) {
[KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")];
}
}
+ (NSString *)kb_urlEncodedString:(NSString *)value {
if (value.length == 0) {
return @"";
}
NSString *reserved = @"!*'();:@&=+$,/?%#[]";
NSMutableCharacterSet *allowed =
[[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowed removeCharactersInString:reserved];
return [value stringByAddingPercentEncodingWithAllowedCharacters:allowed]
?: @"";
}
@end

View File

@@ -0,0 +1,178 @@
//
// KeyboardViewController+Suggestions.m
// CustomKeyboard
//
// Created by Codex on 2026/02/22.
//
#import "KeyboardViewController+Private.h"
#import "KBBackspaceUndoManager.h"
#import "KBInputBufferManager.h"
#import "KBKeyBoardMainView.h"
#import "KBSuggestionEngine.h"
@implementation KeyboardViewController (Suggestions)
// MARK: - Suggestions
- (void)kb_updateCurrentWordWithInsertedText:(NSString *)text {
if (text.length == 0) {
return;
}
if ([self kb_isAlphabeticString:text]) {
NSString *current = self.currentWord ?: @"";
self.currentWord = [current stringByAppendingString:text];
self.suppressSuggestions = NO;
[self kb_updateSuggestionsForCurrentWord];
} else {
[self kb_clearCurrentWord];
}
}
- (void)kb_clearCurrentWord {
self.currentWord = @"";
[self.keyBoardMainView kb_setSuggestions:@[]];
self.suppressSuggestions = NO;
}
- (void)kb_scheduleContextRefreshResetSuppression:(BOOL)resetSuppression {
dispatch_async(dispatch_get_main_queue(), ^{
[self kb_refreshCurrentWordFromDocumentContextResetSuppression:
resetSuppression];
});
}
- (void)kb_refreshCurrentWordFromDocumentContextResetSuppression:
(BOOL)resetSuppression {
NSString *context = self.textDocumentProxy.documentContextBeforeInput ?: @"";
NSString *word = [self kb_extractTrailingWordFromContext:context];
self.currentWord = word ?: @"";
if (resetSuppression) {
self.suppressSuggestions = NO;
}
[self kb_updateSuggestionsForCurrentWord];
}
- (NSString *)kb_extractTrailingWordFromContext:(NSString *)context {
if (context.length == 0) {
return @"";
}
static NSCharacterSet *letters = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
letters = [NSCharacterSet
characterSetWithCharactersInString:
@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"];
});
NSInteger idx = (NSInteger)context.length - 1;
while (idx >= 0) {
unichar ch = [context characterAtIndex:(NSUInteger)idx];
if (![letters characterIsMember:ch]) {
break;
}
idx -= 1;
}
NSUInteger start = (NSUInteger)(idx + 1);
if (start >= context.length) {
return @"";
}
return [context substringFromIndex:start];
}
- (BOOL)kb_isAlphabeticString:(NSString *)text {
if (text.length == 0) {
return NO;
}
static NSCharacterSet *letters = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
letters = [NSCharacterSet
characterSetWithCharactersInString:
@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"];
});
for (NSUInteger i = 0; i < text.length; i++) {
if (![letters characterIsMember:[text characterAtIndex:i]]) {
return NO;
}
}
return YES;
}
- (void)kb_updateSuggestionsForCurrentWord {
NSString *prefix = self.currentWord ?: @"";
if (prefix.length == 0) {
[self.keyBoardMainView kb_setSuggestions:@[]];
return;
}
if (self.suppressSuggestions) {
[self.keyBoardMainView kb_setSuggestions:@[]];
return;
}
NSArray<NSString *> *items =
[self.suggestionEngine suggestionsForPrefix:prefix limit:5];
NSArray<NSString *> *cased = [self kb_applyCaseToSuggestions:items
prefix:prefix];
[self.keyBoardMainView kb_setSuggestions:cased];
}
- (NSArray<NSString *> *)kb_applyCaseToSuggestions:(NSArray<NSString *> *)items
prefix:(NSString *)prefix {
if (items.count == 0 || prefix.length == 0) {
return items;
}
BOOL allUpper = [prefix isEqualToString:prefix.uppercaseString];
BOOL firstUpper = [[prefix substringToIndex:1]
isEqualToString:[[prefix substringToIndex:1] uppercaseString]];
if (!allUpper && !firstUpper) {
return items;
}
NSMutableArray<NSString *> *result =
[NSMutableArray arrayWithCapacity:items.count];
for (NSString *word in items) {
if (allUpper) {
[result addObject:word.uppercaseString];
} else {
NSString *first = [[word substringToIndex:1] uppercaseString];
NSString *rest = (word.length > 1) ? [word substringFromIndex:1] : @"";
[result addObject:[first stringByAppendingString:rest]];
}
}
return result.copy;
}
// MARK: - KBKeyBoardMainViewDelegate (Suggestion)
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView
didSelectSuggestion:(NSString *)suggestion {
if (suggestion.length == 0) {
return;
}
NSDictionary *extra = @{@"suggestion_len" : @(suggestion.length)};
// [[KBMaiPointReporter sharedReporter]
// reportClickWithEventName:@"click_keyboard_suggestion_item"
// pageId:@"keyboard_main_panel"
// elementId:@"suggestion_item"
// extra:extra
// completion:nil];
[[KBBackspaceUndoManager shared] registerNonClearAction];
NSString *current = self.currentWord ?: @"";
if (current.length > 0) {
for (NSUInteger i = 0; i < current.length; i++) {
[self.textDocumentProxy deleteBackward];
}
}
[self.textDocumentProxy insertText:suggestion];
self.currentWord = suggestion;
[self.suggestionEngine recordSelection:suggestion];
self.suppressSuggestions = YES;
[self.keyBoardMainView kb_setSuggestions:@[]];
[[KBInputBufferManager shared] replaceTailWithText:suggestion
deleteCount:current.length];
}
@end

View File

@@ -0,0 +1,376 @@
//
// KeyboardViewController+Theme.m
// CustomKeyboard
//
// Created by Codex on 2026/02/22.
//
#import "KeyboardViewController+Private.h"
#import "KBFunctionView.h"
#import "KBKeyBoardMainView.h"
#import "KBSkinInstallBridge.h"
#import "KBSkinManager.h"
#import "UIImage+KBColor.h"
#import <QuartzCore/QuartzCore.h>
static NSString *const kKBDefaultSkinIdLight = @"normal_them";
static NSString *const kKBDefaultSkinZipNameLight = @"normal_them";
static NSString *const kKBDefaultSkinIdDark = @"normal_hei_them";
static NSString *const kKBDefaultSkinZipNameDark = @"normal_hei_them";
// 使 static kb_consumePendingShopSkin
@interface KeyboardViewController (KBSkinShopBridge)
- (void)kb_consumePendingShopSkin;
@end
static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
void *observer, CFStringRef name,
const void *object,
CFDictionaryRef userInfo) {
KeyboardViewController *strongSelf =
(__bridge KeyboardViewController *)observer;
if (!strongSelf) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
if ([strongSelf respondsToSelector:@selector(kb_consumePendingShopSkin)]) {
[strongSelf kb_consumePendingShopSkin];
}
});
}
@implementation KeyboardViewController (Theme)
- (void)kb_registerDarwinSkinInstallObserver {
CFNotificationCenterAddObserver(
CFNotificationCenterGetDarwinNotifyCenter(),
(__bridge const void *)(self), KBSkinInstallNotificationCallback,
(__bridge CFStringRef)KBDarwinSkinInstallRequestNotification, NULL,
CFNotificationSuspensionBehaviorDeliverImmediately);
}
- (void)kb_unregisterDarwinSkinInstallObserver {
CFNotificationCenterRemoveObserver(
CFNotificationCenterGetDarwinNotifyCenter(),
(__bridge const void *)(self),
(__bridge CFStringRef)KBDarwinSkinInstallRequestNotification, NULL);
}
- (void)kb_applyTheme {
@autoreleasepool {
KBSkinTheme *t = [KBSkinManager shared].current;
UIImage *img = nil;
BOOL isDefaultTheme = [self kb_isDefaultKeyboardTheme:t];
BOOL isDarkMode = [self kb_isDarkModeActive];
NSString *skinId = t.skinId ?: @"";
NSString *themeKey =
[NSString stringWithFormat:@"%@|default=%d|dark=%d", skinId,
isDefaultTheme, isDarkMode];
BOOL themeChanged =
(self.kb_lastAppliedThemeKey.length == 0 ||
![self.kb_lastAppliedThemeKey isEqualToString:themeKey]);
if (themeChanged) {
self.kb_lastAppliedThemeKey = themeKey;
}
CGSize size = self.bgImageView.bounds.size;
if (isDefaultTheme) {
if (isDarkMode) {
// 使使
//
img = nil;
self.bgImageView.image = nil;
[self.kb_defaultGradientLayer removeFromSuperlayer];
self.kb_defaultGradientLayer = nil;
// 使
if (@available(iOS 13.0, *)) {
// iOS 使 (RGB: 44, 44, 46 in sRGB, #2C2C2E)
// 使
UIColor *kbBgColor =
[UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
UITraitCollection *_Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle ==
UIUserInterfaceStyleDark) {
//
return [UIColor colorWithRed:43.0 / 255.0
green:43.0 / 255.0
blue:43.0 / 255.0
alpha:1.0];
} else {
return [UIColor colorWithRed:209.0 / 255.0
green:211.0 / 255.0
blue:219.0 / 255.0
alpha:1.0];
}
}];
self.contentView.backgroundColor = kbBgColor;
self.bgImageView.backgroundColor = kbBgColor;
} else {
UIColor *darkColor = [UIColor colorWithRed:43.0 / 255.0
green:43.0 / 255.0
blue:43.0 / 255.0
alpha:1.0];
self.contentView.backgroundColor = darkColor;
self.bgImageView.backgroundColor = darkColor;
}
} else {
// 使
if (size.width <= 0 || size.height <= 0) {
[self.view layoutIfNeeded];
size = self.bgImageView.bounds.size;
}
if (size.width <= 0 || size.height <= 0) {
size = self.view.bounds.size;
}
if (size.width <= 0 || size.height <= 0) {
size = [UIScreen mainScreen].bounds.size;
}
UIColor *topColor = [UIColor colorWithHex:0xDEDFE4];
UIColor *bottomColor = [UIColor colorWithHex:0xD1D3DB];
UIColor *resolvedTopColor = topColor;
UIColor *resolvedBottomColor = bottomColor;
if (@available(iOS 13.0, *)) {
resolvedTopColor =
[topColor resolvedColorWithTraitCollection:self.traitCollection];
resolvedBottomColor =
[bottomColor resolvedColorWithTraitCollection:self.traitCollection];
}
CAGradientLayer *layer = self.kb_defaultGradientLayer;
if (!layer) {
layer = [CAGradientLayer layer];
layer.startPoint = CGPointMake(0.5, 0.0);
layer.endPoint = CGPointMake(0.5, 1.0);
[self.bgImageView.layer insertSublayer:layer atIndex:0];
self.kb_defaultGradientLayer = layer;
}
layer.colors =
@[ (id)resolvedTopColor.CGColor, (id)resolvedBottomColor.CGColor ];
layer.frame = (CGRect){CGPointZero, size};
img = nil;
self.bgImageView.image = nil;
self.contentView.backgroundColor = [UIColor clearColor];
self.bgImageView.backgroundColor = [UIColor clearColor];
}
NSLog(@"===");
} else {
// 使
self.contentView.backgroundColor = [UIColor clearColor];
self.bgImageView.backgroundColor = [UIColor clearColor];
[self.kb_defaultGradientLayer removeFromSuperlayer];
self.kb_defaultGradientLayer = nil;
img = [[KBSkinManager shared] currentBackgroundImage];
}
NSLog(@"⌨️[Keyboard] apply theme id=%@ hasBg=%d", t.skinId, (img != nil));
[self kb_logSkinDiagnosticsWithTheme:t backgroundImage:img];
self.bgImageView.image = img;
//
if (themeChanged &&
[self.keyBoardMainView respondsToSelector:@selector(kb_applyTheme)]) {
// method declared in KBKeyBoardMainView.h
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.keyBoardMainView performSelector:@selector(kb_applyTheme)];
#pragma clang diagnostic pop
}
// 访 self.functionView
KBFunctionView *functionView = [self kb_functionViewIfCreated];
if (themeChanged && functionView &&
[functionView respondsToSelector:@selector(kb_applyTheme)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[functionView performSelector:@selector(kb_applyTheme)];
#pragma clang diagnostic pop
}
}
}
- (BOOL)kb_isDefaultKeyboardTheme:(KBSkinTheme *)theme {
NSString *skinId = theme.skinId ?: @"";
if (skinId.length == 0 || [skinId isEqualToString:@"default"]) {
return YES;
}
if ([skinId isEqualToString:kKBDefaultSkinIdLight]) {
return YES;
}
return [skinId isEqualToString:kKBDefaultSkinIdDark];
}
- (BOOL)kb_isDarkModeActive {
if (@available(iOS 13.0, *)) {
return self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark;
}
return NO;
}
- (NSString *)kb_defaultSkinIdForCurrentStyle {
return [self kb_isDarkModeActive] ? kKBDefaultSkinIdDark
: kKBDefaultSkinIdLight;
}
- (NSString *)kb_defaultSkinZipNameForCurrentStyle {
return [self kb_isDarkModeActive] ? kKBDefaultSkinZipNameDark
: kKBDefaultSkinZipNameLight;
}
- (UIImage *)kb_defaultGradientImageWithSize:(CGSize)size
topColor:(UIColor *)topColor
bottomColor:(UIColor *)bottomColor {
if (size.width <= 0 || size.height <= 0) {
return nil;
}
//
if (self.kb_cachedGradientImage &&
CGSizeEqualToSize(self.kb_cachedGradientSize, size)) {
return self.kb_cachedGradientImage;
}
UIColor *resolvedTopColor = topColor;
UIColor *resolvedBottomColor = bottomColor;
if (@available(iOS 13.0, *)) {
resolvedTopColor =
[topColor resolvedColorWithTraitCollection:self.traitCollection];
resolvedBottomColor =
[bottomColor resolvedColorWithTraitCollection:self.traitCollection];
}
CAGradientLayer *layer = [CAGradientLayer layer];
layer.frame = CGRectMake(0, 0, size.width, size.height);
layer.startPoint = CGPointMake(0.5, 0.0);
layer.endPoint = CGPointMake(0.5, 1.0);
layer.colors =
@[ (id)resolvedTopColor.CGColor, (id)resolvedBottomColor.CGColor ];
UIGraphicsBeginImageContextWithOptions(size, YES, 0);
[layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.kb_cachedGradientImage = image;
self.kb_cachedGradientSize = size;
return image;
}
- (void)kb_logSkinDiagnosticsWithTheme:(KBSkinTheme *)theme
backgroundImage:(UIImage *)image {
#if DEBUG
NSString *skinId = theme.skinId ?: @"";
NSString *name = theme.name ?: @"";
NSMutableArray<NSString *> *roots = [NSMutableArray array];
NSURL *containerURL = [[NSFileManager defaultManager]
containerURLForSecurityApplicationGroupIdentifier:AppGroup];
if (containerURL.path.length > 0) {
[roots addObject:containerURL.path];
}
NSString *cacheRoot = NSSearchPathForDirectoriesInDomains(
NSCachesDirectory, NSUserDomainMask, YES)
.firstObject;
if (cacheRoot.length > 0) {
[roots addObject:cacheRoot];
}
NSFileManager *fm = [NSFileManager defaultManager];
NSMutableArray<NSString *> *lines = [NSMutableArray array];
for (NSString *root in roots) {
NSString *iconsDir = [[root stringByAppendingPathComponent:@"Skins"]
stringByAppendingPathComponent:skinId];
iconsDir = [iconsDir stringByAppendingPathComponent:@"icons"];
BOOL isDir = NO;
BOOL exists = [fm fileExistsAtPath:iconsDir isDirectory:&isDir] && isDir;
NSArray *contents =
exists ? [fm contentsOfDirectoryAtPath:iconsDir error:nil] : nil;
NSUInteger count = contents.count;
BOOL hasQ =
exists &&
[fm fileExistsAtPath:[iconsDir
stringByAppendingPathComponent:@"key_q.png"]];
BOOL hasQUp =
exists && [fm fileExistsAtPath:[iconsDir stringByAppendingPathComponent:
@"key_q_up.png"]];
BOOL hasDel =
exists && [fm fileExistsAtPath:[iconsDir stringByAppendingPathComponent:
@"key_del.png"]];
BOOL hasShift =
exists &&
[fm fileExistsAtPath:[iconsDir
stringByAppendingPathComponent:@"key_up.png"]];
BOOL hasShiftUpper =
exists && [fm fileExistsAtPath:[iconsDir stringByAppendingPathComponent:
@"key_up_upper.png"]];
NSString *line = [NSString
stringWithFormat:@"root=%@ icons=%@ exist=%d count=%tu key_q=%d "
@"key_q_up=%d key_del=%d key_up=%d key_up_upper=%d",
root, iconsDir, exists, count, hasQ, hasQUp, hasDel,
hasShift, hasShiftUpper];
[lines addObject:line];
}
NSLog(@"[Keyboard] theme id=%@ name=%@ hasBg=%d\n%@", skinId, name,
(image != nil), [lines componentsJoinedByString:@"\n"]);
#endif
}
- (void)kb_consumePendingShopSkin {
KBWeakSelf [KBSkinInstallBridge
consumePendingRequestFromBundle:NSBundle.mainBundle
completion:^(BOOL success,
NSError *_Nullable error) {
if (!success) {
if (error) {
NSLog(@"[Keyboard] skin request failed: %@",
error);
[KBHUD
showInfo:
KBLocalized(
@"皮肤资源准备失败,请稍后再试")];
}
return;
}
[weakSelf kb_applyTheme];
[KBHUD showInfo:KBLocalized(
@"皮肤已更新,立即体验吧")];
}];
}
- (void)kb_applyDefaultSkinIfNeeded {
NSDictionary *pending = [KBSkinInstallBridge pendingRequestPayload];
if (pending.count > 0) {
return;
}
NSString *currentId = [KBSkinManager shared].current.skinId ?: @"";
BOOL isDefault =
(currentId.length == 0 || [currentId isEqualToString:@"default"]);
BOOL isLightDefault = [currentId isEqualToString:kKBDefaultSkinIdLight];
BOOL isDarkDefault = [currentId isEqualToString:kKBDefaultSkinIdDark];
if (!isDefault && !isLightDefault && !isDarkDefault) {
//
return;
}
NSString *targetId = [self kb_defaultSkinIdForCurrentStyle];
NSString *targetZip = [self kb_defaultSkinZipNameForCurrentStyle];
if (currentId.length > 0 && [currentId isEqualToString:targetId]) {
return;
}
NSError *applyError = nil;
if ([KBSkinInstallBridge applyInstalledSkinWithId:targetId error:&applyError]) {
return;
}
[KBSkinInstallBridge publishBundleSkinRequestWithId:targetId
name:targetId
zipName:targetZip
iconShortNames:nil];
[KBSkinInstallBridge
consumePendingRequestFromBundle:NSBundle.mainBundle
completion:^(__unused BOOL success,
__unused NSError *_Nullable error) {
//
}];
}
@end

View File

@@ -0,0 +1,151 @@
//
// KeyboardViewController+UI.m
// CustomKeyboard
//
// Created by Codex on 2026/02/22.
//
#import "KeyboardViewController+Private.h"
#import "KBChatMessage.h"
#import "KBChatPanelView.h"
#import "KBFunctionView.h"
#import "KBKeyBoardMainView.h"
#import "KBKeyboardSubscriptionView.h"
#import "KBSettingView.h"
#import "Masonry.h"
@implementation KeyboardViewController (UI)
- (void)setupUI {
self.view.translatesAutoresizingMaskIntoConstraints = NO;
//
CGFloat portraitWidth = [self kb_portraitWidth];
CGFloat keyboardHeight = [self kb_keyboardHeightForWidth:portraitWidth];
CGFloat keyboardBaseHeight = [self kb_keyboardBaseHeightForWidth:portraitWidth];
CGFloat screenWidth = CGRectGetWidth([UIScreen mainScreen].bounds);
// FIX: iOS 26
// iOS 26 self.view view
// view
// UI
// 0
// viewWillAppear:
// iOS 18
NSLayoutConstraint *h = [self.view.heightAnchor constraintEqualToConstant:0];
NSLayoutConstraint *w =
[self.view.widthAnchor constraintEqualToConstant:screenWidth];
self.kb_heightConstraint = h;
self.kb_widthConstraint = w;
h.priority = UILayoutPriorityRequired;
w.priority = UILayoutPriorityRequired;
[NSLayoutConstraint activateConstraints:@[ h, w ]];
// UIInputView
if ([self.view isKindOfClass:[UIInputView class]]) {
UIInputView *iv = (UIInputView *)self.view;
if ([iv respondsToSelector:@selector(setAllowsSelfSizing:)]) {
iv.allowsSelfSizing = NO;
}
}
//
[self.view addSubview:self.contentView];
[self.contentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view);
make.bottom.equalTo(self.view);
self.contentWidthConstraint = make.width.mas_equalTo(portraitWidth);
self.contentHeightConstraint = make.height.mas_equalTo(keyboardHeight);
}];
//
[self.contentView addSubview:self.bgImageView];
[self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.contentView);
}];
[self.contentView addSubview:self.keyBoardMainView];
[self.keyBoardMainView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.contentView);
make.bottom.equalTo(self.contentView);
self.keyBoardMainHeightConstraint =
make.height.mas_equalTo(keyboardBaseHeight);
}];
//
self.contentView.hidden = YES;
}
#pragma mark - Lazy
- (nullable KBFunctionView *)kb_functionViewIfCreated {
return _functionView;
}
- (UIView *)contentView {
if (!_contentView) {
_contentView = [[UIView alloc] init];
_contentView.backgroundColor = [UIColor clearColor];
}
return _contentView;
}
- (UIImageView *)bgImageView {
if (!_bgImageView) {
_bgImageView = [[UIImageView alloc] init];
_bgImageView.contentMode = UIViewContentModeScaleAspectFill;
_bgImageView.clipsToBounds = YES;
}
return _bgImageView;
}
- (KBKeyBoardMainView *)keyBoardMainView {
if (!_keyBoardMainView) {
_keyBoardMainView = [[KBKeyBoardMainView alloc] init];
_keyBoardMainView.delegate = self;
}
return _keyBoardMainView;
}
- (KBFunctionView *)functionView {
if (!_functionView) {
_functionView = [[KBFunctionView alloc] init];
_functionView.delegate = self; // Bar
}
return _functionView;
}
- (KBSettingView *)settingView {
if (!_settingView) {
_settingView = [[KBSettingView alloc] init];
}
return _settingView;
}
- (KBChatPanelView *)chatPanelView {
if (!_chatPanelView) {
NSLog(@"[Keyboard] ⚠️ chatPanelView 被创建!");
_chatPanelView = [[KBChatPanelView alloc] init];
_chatPanelView.delegate = self;
}
return _chatPanelView;
}
- (NSMutableArray<KBChatMessage *> *)chatMessages {
if (!_chatMessages) {
_chatMessages = [NSMutableArray array];
}
return _chatMessages;
}
- (KBKeyboardSubscriptionView *)subscriptionView {
if (!_subscriptionView) {
_subscriptionView = [[KBKeyboardSubscriptionView alloc] init];
_subscriptionView.delegate = self;
_subscriptionView.hidden = YES;
_subscriptionView.alpha = 0.0;
}
return _subscriptionView;
}
@end

View File

@@ -41,38 +41,10 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
}
- (void)getSignWithParare:(NSDictionary *)bodyParams{
NSString *appId = @"loveKeyboard";
NSString *secret = @"kZJM39HYvhxwbJkG1fmquQRVkQiLAh2H"; //
NSString *timestamp = [KBSignUtils currentTimestamp];
NSString *nonce = [KBSignUtils generateNonceWithLength:16];
// 1.
NSMutableDictionary<NSString *, NSString *> *signParams = [NSMutableDictionary dictionary];
signParams[@"appId"] = appId;
signParams[@"timestamp"] = timestamp;
signParams[@"nonce"] = nonce;
// body
[bodyParams enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([obj isKindOfClass:[NSString class]]) {
signParams[key] = obj;
} else {
signParams[key] = [obj description];
}
}];
NSString *sign = [KBSignUtils signWithParams:signParams secret:secret];
//
NSDictionary<NSString *, NSString *> *signHeaders = [KBSignUtils signHeadersWithBodyParams:bodyParams];
NSMutableDictionary<NSString *, NSString *> *headers =
[self.defaultHeaders mutableCopy] ?: [NSMutableDictionary dictionary];
if (sign.length > 0) {
headers[@"X-Sign"] = sign;
}
headers[@"X-App-Id"] = appId;
headers[@"X-Timestamp"] = timestamp;
headers[@"X-Nonce"] = nonce;
// copy
[headers addEntriesFromDictionary:signHeaders ?: @{}];
self.defaultHeaders = headers;
}

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array>
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeOtherUserContent</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<true/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
</array>
</dict>
</array>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>

Binary file not shown.

Binary file not shown.

View File

@@ -7,15 +7,25 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class KBChatDataModel;
NS_ASSUME_NONNULL_BEGIN
/// 聊天响应模型
@interface KBChatResponse : NSObject
@property (nonatomic, copy, nullable) NSString *text;
@property (nonatomic, copy, nullable) NSString *audioId;
@property (nonatomic, copy, nullable) NSString *errorMessage;
@property (nonatomic, strong, nullable) KBChatDataModel *data;
//@property (nonatomic, copy, nullable) NSString *audioId;
@property (nonatomic, copy, nullable) NSString *message;
@property (nonatomic, assign) BOOL success;
@property (nonatomic, assign) NSInteger code;
@end
@interface KBChatDataModel : NSObject
@property (nonatomic, copy, nullable) NSString *aiResponse;
@property (nonatomic, copy, nullable) NSString *audioId;
@property (nonatomic, copy, nullable) NSString *llmDuration;
@end
/// 音频响应模型

View File

@@ -7,10 +7,14 @@
#import "KBNetworkManager.h"
#import "KBConfig.h"
#import <AVFoundation/AVFoundation.h>
#import <MJExtension/MJExtension.h>
@implementation KBChatResponse
@end
@implementation KBChatDataModel
@end
@implementation KBAudioResponse
@end
@@ -46,7 +50,7 @@
if (completion) {
KBChatResponse *response = [[KBChatResponse alloc] init];
response.success = NO;
response.errorMessage = @"内容为空";
response.message = @"内容为空";
completion(response);
}
return;
@@ -66,24 +70,23 @@
headers:nil
completion:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
KBChatResponse *chatResponse = [[KBChatResponse alloc] init];
if (error) {
KBChatResponse *chatResponse = [KBChatResponse mj_objectWithKeyValues:json];
if (chatResponse.code != 0) {
chatResponse.success = NO;
chatResponse.errorMessage = error.localizedDescription ?: @"请求失败";
// chatResponse.errorMessage = error.localizedDescription ?: @"请求失败";
if (completion) completion(chatResponse);
return;
}
//
chatResponse.text = [self p_parseTextFromJSON:json];
// audioId
chatResponse.audioId = [self p_parseAudioIdFromJSON:json];
// //
// chatResponse.text = [self p_parseTextFromJSON:json];
// // audioId
// chatResponse.audioId = [self p_parseAudioIdFromJSON:json];
chatResponse.success = (chatResponse.text.length > 0);
if (!chatResponse.success) {
chatResponse.errorMessage = @"未获取到回复内容";
}
// chatResponse.success = (chatResponse.text.length > 0);
// if (!chatResponse.success) {
// chatResponse.errorMessage = @"未获取到回复内容";
// }
if (completion) completion(chatResponse);
});

View File

@@ -318,6 +318,7 @@ static const NSUInteger kKBChatMessageLimit = 10;
- (UILabel *)titleLabel {
if (!_titleLabel) {
_titleLabel = [[UILabel alloc] init];
_titleLabel.hidden = true;
_titleLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightMedium];
_titleLabel.textColor =
[UIColor kb_dynamicColorWithLightColor:[UIColor colorWithHex:0x1B1F1A]

View File

@@ -67,6 +67,8 @@ static CGFloat const kKBItemSpace = 4;
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return kKBItemSpace; }
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
// cellloadingcell
if (self.loadingIndexes.count > 0) { return; }
KBTagItemModel *model = (indexPath.item < self.items.count) ? self.items[indexPath.item] : [KBTagItemModel new];
NSInteger personaId = 0;
if ([model isKindOfClass:KBTagItemModel.class]) {

View File

@@ -19,6 +19,7 @@
#import "KBHostAppLauncher.h"
#import "KBInputBufferManager.h"
#import "KBResponderUtils.h" // UIInputViewController
#import "KBSignUtils.h"
#import "KBSkinManager.h"
#import "KBStreamOverlayView.h" //
#import "KBStreamTextView.h" //
@@ -435,11 +436,32 @@
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:60];
request.HTTPMethod = @"POST";
[request setValue:@"text/event-stream" forHTTPHeaderField:@"Accept"];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
// 401Missing sign headers
NSDictionary<NSString *, NSString *> *signHeaders =
[KBSignUtils signHeadersWithBodyParams:payload];
[signHeaders enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj,
BOOL *stop) {
if (key.length == 0 || obj.length == 0) {
return;
}
[request setValue:obj forHTTPHeaderField:key];
}];
NSString *token = KBAuthManager.shared.current.accessToken ?: @"";
if (token.length > 0) {
[request setValue:token forHTTPHeaderField:@"auth-token"];
}
// App Bearer
NSDictionary<NSString *, NSString *> *authHeader =
[[KBAuthManager shared] authorizationHeader];
[authHeader enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj,
BOOL *stop) {
if (key.length == 0 || obj.length == 0) {
return;
}
[request setValue:obj forHTTPHeaderField:key];
}];
request.HTTPBody = bodyData;
self.streamHasOutput = NO;
@@ -463,7 +485,7 @@
__strong typeof(weakSelf) self = weakSelf;
if (!self)
return;
[self kb_handleEventSourceError:event.error];
// [self kb_handleEventSourceError:event.error];
}
forEvent:WJXEventNameError
queue:NSOperationQueue.mainQueue];

View File

@@ -95,6 +95,10 @@
[self.keyboardView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.topBar.mas_bottom).offset(barSpacing);
}];
// 4
[self.keyboardView reloadKeys];
//
[self kb_applyTheme];
// /
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(kb_undoStateChanged)

View File

@@ -49,7 +49,6 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
self.layoutConfig = [KBKeyboardLayoutConfig sharedConfig];
self.backspaceHandler = [[KBBackspaceLongPressHandler alloc] initWithContainerView:self];
[self buildBase];
[self reloadKeys];
}
return self;
}
@@ -126,6 +125,39 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
[self buildRow:self.row2 withRowConfig:rows[1]];
[self buildRow:self.row3 withRowConfig:rows[2]];
[self buildRow:self.row4 withRowConfig:rows[3]];
NSUInteger totalButtons = [self kb_totalKeyButtonCount];
if (totalButtons == 0) {
NSLog(@"[KBKeyboardView] config layout produced no keys, fallback to legacy.");
for (UIView *row in @[self.row1, self.row2, self.row3, self.row4]) {
[row.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
}
[self kb_buildLegacyLayout];
}
}
- (void)didMoveToWindow {
[super didMoveToWindow];
if (!self.window) { return; }
if ([self kb_totalKeyButtonCount] > 0) { return; }
//
[self reloadKeys];
//
UIView *container = self.superview;
if ([container respondsToSelector:@selector(kb_applyTheme)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[container performSelector:@selector(kb_applyTheme)];
#pragma clang diagnostic pop
}
}
- (NSUInteger)kb_totalKeyButtonCount {
NSUInteger total = 0;
for (UIView *row in @[self.row1, self.row2, self.row3, self.row4]) {
total += [self kb_collectKeyButtonsInView:row].count;
}
return total;
}
#pragma mark - Hit Test
@@ -594,7 +626,7 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
[btn setTitle:key.title forState:UIControlStateNormal];
//
[btn applyThemeForCurrentKey];
[btn addTarget:self action:@selector(onKeyTapped:) forControlEvents:UIControlEventTouchUpInside];
[btn addTarget:self action:@selector(onKeyTapped:) forControlEvents:UIControlEventTouchDown];
[row addSubview:btn];
if (key.type == KBKeyTypeBackspace) {
@@ -920,7 +952,7 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
}
[btn applyThemeForCurrentKey];
[btn addTarget:self action:@selector(onKeyTapped:) forControlEvents:UIControlEventTouchUpInside];
[btn addTarget:self action:@selector(onKeyTapped:) forControlEvents:UIControlEventTouchDown];
if (key.type == KBKeyTypeBackspace) {
[self.backspaceHandler bindDeleteButton:btn showClearLabel:YES];

View File

@@ -26,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, weak, nullable) id<KBToolBarDelegate> delegate;
/// 左侧按钮的标题(数量由数组决定)。默认值:@[@"AI", @"语音"]。
/// 左侧按钮的标题(数量由数组决定)。默认值:@[@"AI"]。
@property (nonatomic, copy) NSArray<NSString *> *leftButtonTitles;
/// 暴露按钮以便外部定制(只读;首次访问时懒加载创建)

View File

@@ -9,29 +9,34 @@
#import "KBResponderUtils.h" // UIInputViewController
#import "KBBackspaceUndoManager.h"
#import "KBSkinManager.h"
#import <ImageIO/ImageIO.h>
@interface KBToolBar ()
@property (nonatomic, strong) UIView *leftContainer;
@property (nonatomic, strong) NSArray<UIButton *> *leftButtonsInternal;
//@property (nonatomic, strong) UIButton *settingsButtonInternal;
@property (nonatomic, strong) UIButton *globeButtonInternal; //
@property (nonatomic, strong) UIImageView *avatarImageView; // AppGroup persona_cover.jpg
@property (nonatomic, strong) UIButton *undoButtonInternal; //
@property (nonatomic, assign) BOOL kbNeedsInputModeSwitchKey;
@property (nonatomic, assign) BOOL kbUndoVisible;
@property (nonatomic, assign) BOOL kbAvatarVisible;
@property (nonatomic, copy, nullable) NSString *kb_cachedPersonaCoverPath;
@property (nonatomic, strong, nullable) UIImage *kb_cachedPersonaCoverImage;
@end
@implementation KBToolBar
static NSString * const kKBAIKeyIdentifier = @"ai";
static NSString * const kKBUndoKeyIdentifier = @"key_revoke";
static const CGFloat kKBAIButtonWidth = 40;
static const CGFloat kKBAIButtonHeight = 40;
static const NSInteger kKBVoiceButtonIndex = 1;
static NSString * const kKBAIKeyIdentifier = @"ai";
static NSString * const kKBUndoKeyIdentifier = @"key_revoke";
static const CGFloat kKBAIButtonWidth = 40;
static const CGFloat kKBAIButtonHeight = 40;
static const CGFloat kKBAvatarSize = 40;
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
_leftButtonTitles = @[@"AI", KBLocalized(@"语音")]; //
_leftButtonTitles = @[@"AI"]; //
[self setupUI];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(kb_undoStateChanged)
@@ -69,7 +74,6 @@ static const NSInteger kKBVoiceButtonIndex = 1;
}
}];
[self kb_updateAIButtonAppearance];
[self kb_updateVoiceButtonAppearance];
}
#pragma mark -
@@ -79,6 +83,7 @@ static const NSInteger kKBVoiceButtonIndex = 1;
// [self addSubview:self.settingsButtonInternal];
[self addSubview:self.globeButtonInternal];
[self addSubview:self.undoButtonInternal];
[self addSubview:self.avatarImageView];
//
// [self.settingsButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) {
@@ -94,13 +99,7 @@ static const NSInteger kKBVoiceButtonIndex = 1;
make.width.height.mas_equalTo(32);
}];
//
[self.undoButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.mas_right).offset(-12);
make.centerY.equalTo(self.mas_centerY);
make.height.mas_equalTo(32);
make.width.mas_equalTo(84);
}];
[self kb_updateRightControlsConstraints];
[self kb_updateLeftContainerConstraints];
@@ -173,8 +172,8 @@ static const NSInteger kKBVoiceButtonIndex = 1;
- (void)kb_applyTheme {
[self kb_updateAIButtonAppearance];
[self kb_updateVoiceButtonAppearance];
[self kb_updateUndoButtonAppearance];
[self kb_updateAvatarAppearance];
}
- (void)kb_updateAIButtonAppearance {
@@ -211,16 +210,6 @@ static const NSInteger kKBVoiceButtonIndex = 1;
}
}
- (void)kb_updateVoiceButtonAppearance {
UIButton *voiceButton = [self kb_voiceButton];
if (!voiceButton) { return; }
voiceButton.backgroundColor = [UIColor colorWithHex:0xE53935];
[voiceButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
voiceButton.layer.cornerRadius = 16;
voiceButton.layer.masksToBounds = YES;
}
- (void)kb_updateUndoButtonAppearance {
if (!self.undoButtonInternal) { return; }
@@ -241,6 +230,72 @@ static const NSInteger kKBVoiceButtonIndex = 1;
}
}
#pragma mark - Avatar
- (void)kb_updateAvatarAppearance {
UIImage *img = [self kb_personaCoverImageFromAppGroup];
BOOL shouldShow = (img != nil);
self.avatarImageView.image = img;
if (self.kbAvatarVisible == shouldShow) {
self.avatarImageView.hidden = !shouldShow;
return;
}
self.kbAvatarVisible = shouldShow;
self.avatarImageView.hidden = !shouldShow;
[self kb_updateRightControlsConstraints];
[self kb_updateLeftContainerConstraints];
[self setNeedsLayout];
[self layoutIfNeeded];
}
- (nullable UIImage *)kb_personaCoverImageFromAppGroup {
NSURL *containerURL = [[NSFileManager defaultManager]
containerURLForSecurityApplicationGroupIdentifier:AppGroup];
if (!containerURL) {
return nil;
}
NSString *imagePath =
[[containerURL path] stringByAppendingPathComponent:@"persona_cover.jpg"];
if (imagePath.length == 0 ||
![[NSFileManager defaultManager] fileExistsAtPath:imagePath]) {
self.kb_cachedPersonaCoverPath = nil;
self.kb_cachedPersonaCoverImage = nil;
return nil;
}
if (self.kb_cachedPersonaCoverImage &&
[self.kb_cachedPersonaCoverPath isEqualToString:imagePath]) {
return self.kb_cachedPersonaCoverImage;
}
// 40pt full decode JPG
NSUInteger maxPixel = 256;
NSURL *url = [NSURL fileURLWithPath:imagePath];
CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)url, NULL);
if (!source) {
return nil;
}
NSDictionary *opts = @{
(__bridge id)kCGImageSourceCreateThumbnailFromImageAlways : @YES,
(__bridge id)kCGImageSourceCreateThumbnailWithTransform : @YES,
(__bridge id)kCGImageSourceThumbnailMaxPixelSize : @(maxPixel),
};
CGImageRef cg = CGImageSourceCreateThumbnailAtIndex(source, 0, (__bridge CFDictionaryRef)opts);
CFRelease(source);
if (!cg) {
return nil;
}
UIImage *img = [UIImage imageWithCGImage:cg
scale:[UIScreen mainScreen].scale
orientation:UIImageOrientationUp];
CGImageRelease(cg);
self.kb_cachedPersonaCoverPath = imagePath;
self.kb_cachedPersonaCoverImage = img;
return img;
}
#pragma mark - Actions
- (void)onLeftAction:(UIButton *)sender {
@@ -261,6 +316,16 @@ static const NSInteger kKBVoiceButtonIndex = 1;
}
}
- (void)onAvatarTap {
if (!self.kbAvatarVisible || self.avatarImageView.hidden) {
return;
}
// index=1 index
if ([self.delegate respondsToSelector:@selector(toolBar:didTapActionAtIndex:)]) {
[self.delegate toolBar:self didTapActionAtIndex:1];
}
}
#pragma mark - Lazy
- (UIView *)leftContainer {
@@ -271,6 +336,23 @@ static const NSInteger kKBVoiceButtonIndex = 1;
return _leftContainer;
}
- (UIImageView *)avatarImageView {
if (!_avatarImageView) {
_avatarImageView = [[UIImageView alloc] init];
_avatarImageView.hidden = YES;
_avatarImageView.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9];
_avatarImageView.contentMode = UIViewContentModeScaleAspectFill;
_avatarImageView.layer.cornerRadius = kKBAvatarSize * 0.5;
_avatarImageView.layer.masksToBounds = YES;
_avatarImageView.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:@selector(onAvatarTap)];
[_avatarImageView addGestureRecognizer:tap];
}
return _avatarImageView;
}
//- (UIButton *)settingsButtonInternal {
// if (!_settingsButtonInternal) {
// _settingsButtonInternal = [UIButton buttonWithType:UIButtonTypeSystem];
@@ -319,11 +401,6 @@ static const NSInteger kKBVoiceButtonIndex = 1;
return self.leftButtonsInternal[0];
}
- (UIButton *)kb_voiceButton {
if (self.leftButtonsInternal.count <= kKBVoiceButtonIndex) { return nil; }
return self.leftButtonsInternal[kKBVoiceButtonIndex];
}
#pragma mark - Globe (Input Mode Switch)
// 宿
@@ -362,6 +439,8 @@ static const NSInteger kKBVoiceButtonIndex = 1;
}
if (self.kbUndoVisible) {
make.right.equalTo(self.undoButtonInternal.mas_left).offset(-8);
} else if (self.kbAvatarVisible) {
make.right.equalTo(self.avatarImageView.mas_left).offset(-8);
} else {
make.right.equalTo(self).offset(-12);
}
@@ -371,6 +450,24 @@ static const NSInteger kKBVoiceButtonIndex = 1;
}];
}
- (void)kb_updateRightControlsConstraints {
[self.avatarImageView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self).offset(-12);
make.centerY.equalTo(self).offset(0);
make.width.height.mas_equalTo(kKBAvatarSize);
}];
[self.undoButtonInternal mas_remakeConstraints:^(MASConstraintMaker *make) {
if (self.kbAvatarVisible) {
make.right.equalTo(self.avatarImageView.mas_left).offset(-8);
} else {
make.right.equalTo(self).offset(-12);
}
make.centerY.equalTo(self.mas_centerY);
make.height.mas_equalTo(32);
make.width.mas_equalTo(84);
}];
}
- (void)kb_undoStateChanged {
[self kb_updateUndoVisibilityAnimated:YES];
}
@@ -405,6 +502,7 @@ static const NSInteger kKBVoiceButtonIndex = 1;
- (void)didMoveToWindow {
[super didMoveToWindow];
[self kb_refreshGlobeVisibility];
[self kb_updateAvatarAppearance];
}
@end

View File

@@ -0,0 +1,42 @@
Feb 5 20:30:09 macbookpro com.apple.dt.xcodebuild[56551] <Error>: Unable to deliver request ({
"developer_dir" = "/Applications/Xcode.app/Contents/Developer";
request = "set_developer_dir";
}) because we are not connected to CoreSimulatorService.
Feb 5 20:30:22 macbookpro com.apple.dt.xcodebuild[56567] <Error>: Unable to deliver request ({
"developer_dir" = "/Applications/Xcode.app/Contents/Developer";
request = "set_developer_dir";
}) because we are not connected to CoreSimulatorService.
Feb 5 20:31:06 macbookpro com.apple.dt.xcodebuild[56684] <Error>: Unable to deliver request ({
"developer_dir" = "/Applications/Xcode.app/Contents/Developer";
request = "set_developer_dir";
}) because we are not connected to CoreSimulatorService.
Feb 5 20:31:06 macbookpro com.apple.dt.xcodebuild[56684] <Warning>: Unable to discover any Simulator runtimes. Developer Directory is /Applications/Xcode.app/Contents/Developer.
Feb 5 20:31:06 macbookpro com.apple.dt.xcodebuild[56684] <Error>: Could not kickstart simdiskimaged; SimDiskImageManager services will not be available: Error Domain=NSPOSIXErrorDomain Code=53 "Software caused connection abort" UserInfo={NSLocalizedDescription=Error returned in reply from CoreSimulatorService: Connection invalid}
Feb 5 20:31:06 macbookpro com.apple.dt.xcodebuild[56684] <Error>: simdiskimaged returned error (invalid), marking disconnected.
Feb 5 20:31:06 macbookpro com.apple.dt.xcodebuild[56684] <Error>: Could not get list of trusted mount directories: Error Domain=com.apple.CoreSimulator.SimError Code=410 "The service used to manage runtime disk images (simdiskimaged) crashed or is not responding" UserInfo={NSLocalizedDescription=The service used to manage runtime disk images (simdiskimaged) crashed or is not responding}
Feb 5 20:31:06 macbookpro com.apple.dt.xcodebuild[56684] <Error>: simdiskimaged returned error (invalid), marking disconnected.
Feb 5 20:31:06 macbookpro com.apple.dt.xcodebuild[56684] <Error>: Unable to deliver request ({
request = "notification_subscription";
"set_path" = "/Users/mac/Library/Developer/CoreSimulator/Devices";
}) because we are not connected to CoreSimulatorService.
Feb 5 20:31:06 macbookpro com.apple.dt.xcodebuild[56684] <Error>: Unable to deliver request ({
request = "notification_subscription";
"set_path" = "/Users/mac/Library/Developer/CoreSimulator/Devices";
}) because we are not connected to CoreSimulatorService.
Feb 5 20:31:55 macbookpro com.apple.dt.xcodebuild[56806] <Error>: Unable to deliver request ({
"developer_dir" = "/Applications/Xcode.app/Contents/Developer";
request = "set_developer_dir";
}) because we are not connected to CoreSimulatorService.
Feb 5 20:31:55 macbookpro com.apple.dt.xcodebuild[56806] <Warning>: Unable to discover any Simulator runtimes. Developer Directory is /Applications/Xcode.app/Contents/Developer.
Feb 5 20:31:55 macbookpro com.apple.dt.xcodebuild[56806] <Error>: Could not kickstart simdiskimaged; SimDiskImageManager services will not be available: Error Domain=NSPOSIXErrorDomain Code=53 "Software caused connection abort" UserInfo={NSLocalizedDescription=Error returned in reply from CoreSimulatorService: Connection invalid}
Feb 5 20:31:55 macbookpro com.apple.dt.xcodebuild[56806] <Error>: simdiskimaged returned error (invalid), marking disconnected.
Feb 5 20:31:55 macbookpro com.apple.dt.xcodebuild[56806] <Error>: Could not get list of trusted mount directories: Error Domain=com.apple.CoreSimulator.SimError Code=410 "The service used to manage runtime disk images (simdiskimaged) crashed or is not responding" UserInfo={NSLocalizedDescription=The service used to manage runtime disk images (simdiskimaged) crashed or is not responding}
Feb 5 20:31:55 macbookpro com.apple.dt.xcodebuild[56806] <Error>: simdiskimaged returned error (invalid), marking disconnected.
Feb 5 20:31:55 macbookpro com.apple.dt.xcodebuild[56806] <Error>: Unable to deliver request ({
request = "notification_subscription";
"set_path" = "/Users/mac/Library/Developer/CoreSimulator/Devices";
}) because we are not connected to CoreSimulatorService.
Feb 5 20:31:55 macbookpro com.apple.dt.xcodebuild[56806] <Error>: Unable to deliver request ({
request = "notification_subscription";
"set_path" = "/Users/mac/Library/Developer/CoreSimulator/Devices";
}) because we are not connected to CoreSimulatorService.

View File

@@ -28,11 +28,14 @@
#define API_LOGOUT @"/user/logout" // 退出登录
#define API_USER_CANCEL_ACCOUNT @"/user/cancelAccount" // 注销账户
#define API_CANCEL_ACCOUNT_WARNING @"/keyboardWarningMessage/byLocale" // 按locale查询注销提示信息
#define API_UPDATA_INFO @"/user/updateInfo" // 更新用户
#define KB_API_USER_DETAIL @"/user/detail" // 用户详情
#define API_USER_INVITE_CODE @"/user/inviteCode" // 查询邀请码
#define API_USER_CUSTOMER_MAIL @"/user/customerMail" // 获取客服邮箱
#define API_CHARACTER_LIST @"/character/list" // 排行榜角色列表(综合)
#define API_NOT_LOGIN_CHARACTER_LIST @"/character/listWithNotLogin" //未登录用户人设列表

View File

@@ -30,6 +30,12 @@
/// 用户头像 URL主 App 写入,键盘扩展读取)
#define AppGroup_UserAvatarURL @"AppGroup_UserAvatarURL"
/// 键盘扩展聊天更新的 companionId键盘写入主 App 读取后刷新对应聊天记录)
#define AppGroup_ChatUpdatedCompanionId @"AppGroup_ChatUpdatedCompanionId"
/// Darwin 跨进程通知:键盘扩展发送聊天消息后通知主 App 刷新
#define kKBDarwinChatUpdated @"com.loveKey.nyx.chat.updated"
/// 皮肤图标加载模式:
/// 0 = 使用本地 Assets 图片名key_icons 的 value 写成图片名,例如 "kb_q_melon"
/// 1 = 使用远程 Zip 皮肤包skinJSON 中提供 zip_urlkey_icons 的 value 写成 Zip 内图标文件名,例如 "key_a"

View File

@@ -296,7 +296,7 @@ static NSDictionary<NSString *, NSDictionary *> *KBMaiPoint_PageExposureMap(void
@"HomeHotVC": @{@"event_name": @"enter_home_hot", @"page_id": @"home_hot"},
@"HomeRankVC": @{@"event_name": @"enter_home_rank", @"page_id": @"home_rank"},
@"HomeRankContentVC": @{@"event_name": @"enter_home_rank_content", @"page_id": @"home_rank_content"},
@"HomeSheetVC": @{@"event_name": @"enter_home_sheet", @"page_id": @"home_sheet"},
// @"HomeSheetVC": @{@"event_name": @"enter_home_sheet", @"page_id": @"home_sheet"},
@"KBCommunityVC": @{@"event_name": @"enter_community", @"page_id": @"community"},
@"KBSearchVC": @{@"event_name": @"enter_search", @"page_id": @"search"},
@"KBSearchResultVC": @{@"event_name": @"enter_search_result", @"page_id": @"search_result"},

View File

@@ -20,13 +20,20 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSString *)signWithParams:(NSDictionary<NSString *, NSString *> *)params
secret:(NSString *)secret;
/// 获取签名原始拼接字符串HMAC 前的明文)
+ (NSString *)signSourceStringWithParams:(NSDictionary<NSString *, NSString *> *)params
secret:(NSString *)secret;
/// 秒级时间戳(字符串)
+ (NSString *)currentTimestamp;
/// 简单 nonce 生成(默认 16 位)
+ (NSString *)generateNonceWithLength:(NSUInteger)length;
/// 生成本项目后端约定的签名请求头X-Sign/X-App-Id/X-Timestamp/X-Nonce
/// bodyParams参与签名的业务参数如 JSON body 字段)。内部会做类型容错与空值过滤。
+ (NSDictionary<NSString *, NSString *> *)signHeadersWithBodyParams:(nullable NSDictionary *)bodyParams;
@end
NS_ASSUME_NONNULL_END

View File

@@ -10,12 +10,44 @@
@implementation KBSignUtils
static NSString *const KBSignAppId = @"loveKeyboard";
static NSString *const KBSignSecret = @"kZJM39HYvhxwbJkG1fmquQRVkQiLAh2H";
static NSString *KBSignStringFromObject(id obj) {
if (!obj || obj == (id)kCFNull) {
return nil;
}
if ([obj isKindOfClass:[NSString class]]) {
return (NSString *)obj;
}
if ([obj isKindOfClass:[NSNumber class]]) {
return [(NSNumber *)obj stringValue];
}
if ([obj isKindOfClass:[NSArray class]] || [obj isKindOfClass:[NSDictionary class]]) {
NSJSONWritingOptions options = 0;
if (@available(iOS 11.0, *)) {
options = NSJSONWritingSortedKeys;
}
NSData *data = [NSJSONSerialization dataWithJSONObject:obj options:options error:nil];
if (data.length > 0) {
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
}
return [obj description];
}
+ (NSString *)urlEncode:(NSString *)value {
if (!value) return @"";
// Swift .urlQueryAllowed
NSCharacterSet *allowed = [NSCharacterSet URLQueryAllowedCharacterSet];
// application/x-www-form-urlencoded
NSMutableCharacterSet *allowed = [NSMutableCharacterSet alphanumericCharacterSet];
[allowed addCharactersInString:@"-._*"];
NSString *encoded = [value stringByAddingPercentEncodingWithAllowedCharacters:allowed];
return encoded ?: value;
if (!encoded) {
return value;
}
// +URLEncoder
encoded = [encoded stringByReplacingOccurrencesOfString:@"%20" withString:@"+"];
return encoded;
}
+ (NSString *)hmacSHA256:(NSString *)data secret:(NSString *)secret {
@@ -41,6 +73,12 @@
+ (NSString *)signWithParams:(NSDictionary<NSString *, NSString *> *)params
secret:(NSString *)secret {
NSString *dataString = [self signSourceStringWithParams:params secret:secret];
return [self hmacSHA256:dataString secret:secret];
}
+ (NSString *)signSourceStringWithParams:(NSDictionary<NSString *, NSString *> *)params
secret:(NSString *)secret {
// 1. & sign
NSMutableDictionary<NSString *, NSString *> *filtered = [NSMutableDictionary dictionary];
@@ -62,15 +100,11 @@
[components addObject:part];
}
NSString *encodedSecret = [self urlEncode:secret];
NSString *encodedSecret = [self urlEncode:secret ?: @""];
NSString *secretPart = [NSString stringWithFormat:@"secret=%@", encodedSecret];
[components addObject:secretPart];
NSString *dataString = [components componentsJoinedByString:@"&"];
// 4. HMAC-SHA256
NSString *sign = [self hmacSHA256:dataString secret:secret];
return sign;
return [components componentsJoinedByString:@"&"];
}
+ (NSString *)currentTimestamp {
@@ -88,5 +122,50 @@
return [uuid substringToIndex:length];
}
@end
+ (NSDictionary<NSString *, NSString *> *)signHeadersWithBodyParams:(NSDictionary *)bodyParams {
NSString *timestamp = [self currentTimestamp];
NSString *nonce = [self generateNonceWithLength:16];
NSMutableDictionary<NSString *, NSString *> *signParams = [NSMutableDictionary dictionary];
if (KBSignAppId.length > 0) {
signParams[@"appId"] = KBSignAppId;
}
if (timestamp.length > 0) {
signParams[@"timestamp"] = timestamp;
}
if (nonce.length > 0) {
signParams[@"nonce"] = nonce;
}
if ([bodyParams isKindOfClass:[NSDictionary class]] && bodyParams.count > 0) {
[bodyParams enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if (![key isKindOfClass:[NSString class]]) {
return;
}
NSString *value = KBSignStringFromObject(obj);
if (value.length == 0) {
return;
}
signParams[(NSString *)key] = value;
}];
}
NSString *sign = [self signWithParams:signParams secret:KBSignSecret ?: @""];
NSMutableDictionary<NSString *, NSString *> *headers = [NSMutableDictionary dictionary];
if (sign.length > 0) {
headers[@"X-Sign"] = sign;
}
if (KBSignAppId.length > 0) {
headers[@"X-App-Id"] = KBSignAppId;
}
if (timestamp.length > 0) {
headers[@"X-Timestamp"] = timestamp;
}
if (nonce.length > 0) {
headers[@"X-Nonce"] = nonce;
}
return [headers copy];
}
@end

View File

@@ -443,7 +443,7 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
#if __has_include("KBNetworkManager.h")
// http/https
NSLog(@"🌐[SkinBridge] will GET zip: %@", zipURL);
[KBHUD showWithStatus:@"正在下载..."];
[KBHUD showWithStatus:KBLocalized(@"Downloading...")];
[[KBNetworkManager shared] GETData:zipURL parameters:nil headers:nil completion:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"🌐[SkinBridge] GET finished id=%@ error=%@", skinId, error);
if (error || data.length == 0) {

View File

@@ -54,6 +54,9 @@ extern NSString * const KBDarwinSkinChanged; // cross-process
/// 当前背景图片(若存在)
- (nullable UIImage *)currentBackgroundImage;
/// 清理运行时图片缓存(内存缓存)。键盘扩展接近内存上限时可主动调用。
- (void)clearRuntimeImageCaches;
/// 当前主题下,指定按键标识的文字是否应被隐藏(例如图标里已包含字母)
- (BOOL)shouldHideKeyTextForIdentifier:(nullable NSString *)identifier;

View File

@@ -4,6 +4,7 @@
#import "KBSkinManager.h"
#import "KBConfig.h"
#import <ImageIO/ImageIO.h>
NSString * const KBSkinDidChangeNotification = @"KBSkinDidChangeNotification";
NSString * const KBDarwinSkinChanged = @"com.loveKey.nyx.skin.changed";
@@ -59,10 +60,45 @@ static NSString * const kKBSkinThemeStoreKey = @"KBSkinThemeCurrent";
@interface KBSkinManager ()
@property (atomic, strong, readwrite) KBSkinTheme *current;
@property (nonatomic, strong) NSCache<NSString *, UIImage *> *kb_fileImageCache;
@property (nonatomic, copy, nullable) NSString *kb_cachedBgSkinId;
@property (nonatomic, assign) BOOL kb_cachedBgResolved;
@property (nonatomic, strong, nullable) UIImage *kb_cachedBgImage;
@end
@implementation KBSkinManager
/// maxPixel
+ (nullable UIImage *)kb_imageAtPath:(NSString *)path maxPixel:(NSUInteger)maxPixel {
if (path.length == 0) return nil;
NSURL *url = [NSURL fileURLWithPath:path];
CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)url, NULL);
if (!source) return nil;
NSDictionary *opts = @{
(__bridge id)kCGImageSourceCreateThumbnailFromImageAlways : @YES,
(__bridge id)kCGImageSourceCreateThumbnailWithTransform : @YES,
(__bridge id)kCGImageSourceThumbnailMaxPixelSize : @(MAX(1, (NSInteger)maxPixel)),
};
CGImageRef cg = CGImageSourceCreateThumbnailAtIndex(source, 0, (__bridge CFDictionaryRef)opts);
CFRelease(source);
if (!cg) return nil;
UIImage *img = [UIImage imageWithCGImage:cg scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp];
CGImageRelease(cg);
return img;
}
static inline NSUInteger KBApproxImageCostBytes(UIImage *img) {
if (!img) return 0;
CGFloat scale = img.scale > 0 ? img.scale : [UIScreen mainScreen].scale;
CGSize s = img.size;
double px = (double)s.width * scale * (double)s.height * scale;
if (px <= 0) return 0;
// RGBA 4 bytes/pixel
double cost = px * 4.0;
if (cost > (double)NSUIntegerMax) return NSUIntegerMax;
return (NSUInteger)cost;
}
/// App Group Caches
+ (NSArray<NSString *> *)kb_candidateBaseRoots {
NSMutableArray<NSString *> *roots = [NSMutableArray array];
@@ -104,6 +140,14 @@ static NSString * const kKBSkinThemeStoreKey = @"KBSkinThemeCurrent";
- (instancetype)init {
if (self = [super init]) {
_kb_fileImageCache = [NSCache new];
// App
// iPad
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
_kb_fileImageCache.totalCostLimit = 24 * 1024 * 1024;
} else {
_kb_fileImageCache.totalCostLimit = 12 * 1024 * 1024;
}
KBSkinTheme *t = [self p_loadFromStore];
// App Group / 退
if (!t || ![self.class kb_hasAssetsForSkinId:t.skinId]) {
@@ -170,6 +214,7 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
- (BOOL)applyTheme:(KBSkinTheme *)theme {
if (!theme) return NO;
NSLog(@"🎨[SkinManager] apply theme id=%@ name=%@", theme.skinId, theme.name);
[self clearRuntimeImageCaches];
// App Group 使
[self p_saveToStore:theme];
// 广
@@ -187,6 +232,15 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
[self applyTheme:[self.class defaultTheme]];
}
- (void)clearRuntimeImageCaches {
@synchronized (self) {
[self.kb_fileImageCache removeAllObjects];
self.kb_cachedBgSkinId = nil;
self.kb_cachedBgResolved = NO;
self.kb_cachedBgImage = nil;
}
}
- (BOOL)applyImageSkinWithData:(NSData *)imageData skinId:(NSString *)skinId name:(NSString *)name {
// 使 App Group
// Skins/<skinId>/background.png Keychain
@@ -216,20 +270,52 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
NSString *skinId = self.current.skinId;
if (skinId.length == 0) return nil;
// skinId
@synchronized (self) {
if (self.kb_cachedBgResolved && [self.kb_cachedBgSkinId isEqualToString:skinId]) {
return self.kb_cachedBgImage;
}
}
NSArray<NSString *> *roots = [self.class kb_candidateBaseRoots];
NSFileManager *fm = [NSFileManager defaultManager];
NSString *relative = [NSString stringWithFormat:@"Skins/%@/background.png", skinId];
//
NSUInteger maxPixel = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) ? 2048 : 1024;
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;
NSString *cacheKey = [NSString stringWithFormat:@"bg|%@", bgPath];
UIImage *cached = [self.kb_fileImageCache objectForKey:cacheKey];
if (cached) {
@synchronized (self) {
self.kb_cachedBgSkinId = skinId;
self.kb_cachedBgResolved = YES;
self.kb_cachedBgImage = cached;
}
return cached;
}
UIImage *img = [self.class kb_imageAtPath:bgPath maxPixel:maxPixel];
if (img) {
NSUInteger cost = KBApproxImageCostBytes(img);
[self.kb_fileImageCache setObject:img forKey:cacheKey cost:cost];
@synchronized (self) {
self.kb_cachedBgSkinId = skinId;
self.kb_cachedBgResolved = YES;
self.kb_cachedBgImage = img;
}
return img;
}
}
@synchronized (self) {
self.kb_cachedBgSkinId = skinId;
self.kb_cachedBgResolved = YES;
self.kb_cachedBgImage = nil;
}
return nil;
}
@@ -314,7 +400,13 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
if (![fm fileExistsAtPath:fullPath isDirectory:&isDir] || isDir) {
continue;
}
UIImage *img = [UIImage imageWithContentsOfFile:fullPath];
NSString *cacheKey = [NSString stringWithFormat:@"icon|%@", fullPath];
UIImage *img = [self.kb_fileImageCache objectForKey:cacheKey];
if (img) return img;
img = [UIImage imageWithContentsOfFile:fullPath];
if (img) {
[self.kb_fileImageCache setObject:img forKey:cacheKey cost:KBApproxImageCostBytes(img)];
}
if (img) return img;
}
#if DEBUG
@@ -351,7 +443,13 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
NSString *fullPath = [[base stringByAppendingPathComponent:relative] stringByStandardizingPath];
BOOL isDir = NO;
if ([fm fileExistsAtPath:fullPath isDirectory:&isDir] && !isDir) {
UIImage *img = [UIImage imageWithContentsOfFile:fullPath];
NSString *cacheKey = [NSString stringWithFormat:@"icon|%@", fullPath];
UIImage *img = [self.kb_fileImageCache objectForKey:cacheKey];
if (img) return img;
img = [UIImage imageWithContentsOfFile:fullPath];
if (img) {
[self.kb_fileImageCache setObject:img forKey:cacheKey cost:KBApproxImageCostBytes(img)];
}
if (img) return img;
}
}
@@ -363,7 +461,13 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
NSString *fullPath = [[base stringByAppendingPathComponent:relative] stringByStandardizingPath];
BOOL isDir = NO;
if ([fm fileExistsAtPath:fullPath isDirectory:&isDir] && !isDir) {
UIImage *img = [UIImage imageWithContentsOfFile:fullPath];
NSString *cacheKey = [NSString stringWithFormat:@"icon|%@", fullPath];
UIImage *img = [self.kb_fileImageCache objectForKey:cacheKey];
if (img) return img;
img = [UIImage imageWithContentsOfFile:fullPath];
if (img) {
[self.kb_fileImageCache setObject:img forKey:cacheKey cost:KBApproxImageCostBytes(img)];
}
if (img) return img;
}
}
@@ -449,6 +553,7 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
if (!t || ![self.class kb_hasAssetsForSkinId:t.skinId]) {
t = [self.class defaultTheme];
}
[self clearRuntimeImageCaches];
self.current = t;
if (broadcast) {
[[NSNotificationCenter defaultCenter] postNotificationName:KBSkinDidChangeNotification object:nil];

View File

@@ -99,10 +99,22 @@ static NSString * const kKBSessionInstallFlagKey = @"KBSession.installInitialize
// Keychain KBAuthManager
NSString *token = user.token;
if (token.length > 0) {
[[KBAuthManager shared] saveAccessToken:token
BOOL saveOK = [[KBAuthManager shared] saveAccessToken:token
refreshToken:nil
expiryDate:nil
userIdentifier:nil];
#if DEBUG
NSLog(@"[AuthTrace][App] saveAccessToken tokenLen=%lu saveOK=%d",
(unsigned long)token.length, saveOK);
#endif
// reload 便
[[KBAuthManager shared] reloadFromKeychain];
#if DEBUG
NSString *savedToken = [KBAuthManager shared].current.accessToken ?: @"";
NSLog(@"[AuthTrace][App] reloadAfterSave isLoggedIn=%d tokenLen=%lu",
[KBAuthManager shared].isLoggedIn,
(unsigned long)savedToken.length);
#endif
}
// App/使

View File

@@ -82,6 +82,7 @@
"Network error" = "Network error";
"Saved" = "Saved";
"Copy Success" = "Copy Success";
"Email Copy Success" = "Email Copy Success";
// Network
"Network unavailable" = "Network unavailable";
@@ -140,6 +141,10 @@
"Nickname" = "Nickname";
"Gender" = "Gender";
"User ID" = "User ID";
"Modify Gender" = "Modify Gender";
"Male" = "Male";
"Female" = "Female";
"The Third Gender" = "The Third Gender";
" Paste Ta's Words" = " Paste Ta's Words";
@@ -167,9 +172,18 @@
"Delete" = "Delete";
"Points\nMall" = "Points\nMall";
"Log Out" = "Log Out";
"Cancel Account" = "Cancel Account";
"After cancellation, your account will be deactivated and local login data will be cleared. Continue?" = "After cancellation, your account will be deactivated and local login data will be cleared. Continue?";
"Please enter your password" = "Please enter your password";
"Cancel Account Notice" = "Cancel Account Notice";
"Confirm Cancel Account" = "Confirm Cancel Account";
"Ranking List" = "Ranking List";
"Persona circle" = "Persona circle";
"Clear" = "Clear";
"Copy" = "Copy";
"Report" = "Report报";
"Thumbs Up" = "Thumbs Up";
"Chatting" = "Chatting";
// Payment & IAP
"Payment successful" = "Payment successful";
@@ -188,10 +202,6 @@
"测试" = "Test";
"这里是设置内容占位" = "Settings content placeholder";
"设置" = "Settings";
"使用引导" = "Usage Guide";
"螃蟹啊斯柯达积分卡" = "Crab points card";
"❎不是自己的键盘" = "❎ Not our keyboard";
"是自己的键盘" = "Our keyboard";
// English-only keys with Chinese equivalents
"Change The Nickname" = "Change Nickname";
@@ -202,3 +212,4 @@
"Purchase pending approval." = "Purchase pending approval.";
"Unable to obtain transaction payload." = "Unable to obtain transaction payload.";
"Resume Purchase" = "Resume Purchase";
"Downloading..." = "Downloading...";

View File

@@ -83,6 +83,7 @@
"Network error" = "网络错误";
"Saved" = "已保存";
"Copy Success" = "复制成功";
"Email Copy Success" = "Email Copy Success";
// 网络相关(英文 key
"Network unavailable" = "网络不可用";
@@ -103,7 +104,6 @@
"Please check this app's wireless-data permission or network connection in Settings." = "请在“设置”中检查本应用的无线数据权限或网络连接。";
// 权限与引导(英文 key
"Usage Guide" = "使用引导";
"Turn on Allow Full Access to experience all features" = "开启【允许完全访问】,体验完整功能";
"Allow Full Access" = "允许完全访问";
"Follow: Settings → General → Keyboard → Keyboards → %@ → Allow Full Access" = "请按路径:设置→通用→键盘→键盘→%@→允许完全访问";
@@ -142,6 +142,10 @@
"Nickname" = "用户名";
"Gender" = "性别";
"User ID" = "用户ID";
"Modify Gender" = "修改性别";
"Male" = "男";
"Female" = "女";
"The Third Gender" = "第三性别";
" Paste Ta's Words" = " 粘贴TA的话";
@@ -168,9 +172,18 @@
"Delete" = "删除";
"Points\nMall" = "积分\n商城";
"Log Out" = "退出";
"Cancel Account" = "注销账户";
"After cancellation, your account will be deactivated and local login data will be cleared. Continue?" = "注销后账号将被停用,并清除本地登录数据,是否继续?";
"Please enter your password" = "请输入密码";
"Cancel Account Notice" = "注销账户须知";
"Confirm Cancel Account" = "确认注销";
"Ranking List" = "排行榜";
"Persona circle" = "圈子";
"Clear" = "立刻清空";
"Copy" = "复制";
"Report" = "举报";
"Thumbs Up" = "赞过";
"Chatting" = "聊过";
// 皮肤示例名称
@@ -191,10 +204,6 @@
"Test" = "测试";
"暂无数据" = "暂无数据"; // 已有英文 key "No data"
"Settings" = "设置";
"Usage Guide" = "使用引导";
"螃蟹啊斯柯达积分卡" = "螃蟹啊斯柯达积分卡";
"❎不是自己的键盘" = "❎不是自己的键盘";
"是自己的键盘" = "是自己的键盘";
"Change The Nickname" = "修改名称";
@@ -205,3 +214,4 @@
"Purchase pending approval." = "购买等待确认";
"Unable to obtain transaction payload." = "无法获取交易凭据";
"Resume Purchase" = "恢复购买";
"Downloading..." = "正在下载...";

View File

@@ -6,7 +6,7 @@
<integer>11</integer>
<key>logs</key>
<dict>
<key>800731DD-5595-43EC-B207-003BAB7870CE</key>
<key>8E255990-30E2-4820-98AE-0559FF9CB504</key>
<dict>
<key>className</key>
<string>IDECommandLineBuildLog</string>
@@ -15,7 +15,7 @@
<key>domainType</key>
<string>Xcode.IDEActivityLogDomainType.BuildLog</string>
<key>fileName</key>
<string>800731DD-5595-43EC-B207-003BAB7870CE.xcactivitylog</string>
<string>8E255990-30E2-4820-98AE-0559FF9CB504.xcactivitylog</string>
<key>hasPrimaryLog</key>
<true/>
<key>primaryObservable</key>
@@ -34,13 +34,49 @@
<key>signature</key>
<string>Resolve Packages</string>
<key>timeStartedRecording</key>
<real>788359220.39837599</real>
<real>791987515.57282996</real>
<key>timeStoppedRecording</key>
<real>788359220.51885402</real>
<real>791987515.86043</real>
<key>title</key>
<string>Resolve Packages</string>
<key>uniqueIdentifier</key>
<string>800731DD-5595-43EC-B207-003BAB7870CE</string>
<string>8E255990-30E2-4820-98AE-0559FF9CB504</string>
</dict>
<key>C9B2536F-0143-4FD7-90C7-C8F37E9F59D6</key>
<dict>
<key>className</key>
<string>IDECommandLineBuildLog</string>
<key>documentTypeString</key>
<string>&lt;nil&gt;</string>
<key>domainType</key>
<string>Xcode.IDEActivityLogDomainType.BuildLog</string>
<key>fileName</key>
<string>C9B2536F-0143-4FD7-90C7-C8F37E9F59D6.xcactivitylog</string>
<key>hasPrimaryLog</key>
<true/>
<key>primaryObservable</key>
<dict>
<key>highLevelStatus</key>
<string>E</string>
<key>totalNumberOfAnalyzerIssues</key>
<integer>0</integer>
<key>totalNumberOfErrors</key>
<integer>1</integer>
<key>totalNumberOfTestFailures</key>
<integer>0</integer>
<key>totalNumberOfWarnings</key>
<integer>3</integer>
</dict>
<key>signature</key>
<string>Resolve Packages</string>
<key>timeStartedRecording</key>
<real>791987467.25816</real>
<key>timeStoppedRecording</key>
<real>791987467.36928797</real>
<key>title</key>
<string>Resolve Packages</string>
<key>uniqueIdentifier</key>
<string>C9B2536F-0143-4FD7-90C7-C8F37E9F59D6</string>
</dict>
</dict>
</dict>

Submodule _spm/checkouts/swift-collections added at 7b847a3b70

View File

@@ -8,8 +8,14 @@
/* Begin PBXBuildFile section */
04050ECB2F10FB8F008051EB /* UIImage+KBColor.m in Sources */ = {isa = PBXBuildFile; fileRef = 047C655D2EBCD5B20035E841 /* UIImage+KBColor.m */; };
040B620F2F4BF2560099DEAC /* KeyboardViewController+Theme.m in Sources */ = {isa = PBXBuildFile; fileRef = 040B620C2F4BF2560099DEAC /* KeyboardViewController+Theme.m */; };
040B62102F4BF2560099DEAC /* KeyboardViewController+Chat.m in Sources */ = {isa = PBXBuildFile; fileRef = 040B62052F4BF2560099DEAC /* KeyboardViewController+Chat.m */; };
040B62112F4BF2560099DEAC /* KeyboardViewController+Panels.m in Sources */ = {isa = PBXBuildFile; fileRef = 040B62082F4BF2560099DEAC /* KeyboardViewController+Panels.m */; };
040B62122F4BF2560099DEAC /* KeyboardViewController+Layout.m in Sources */ = {isa = PBXBuildFile; fileRef = 040B62062F4BF2560099DEAC /* KeyboardViewController+Layout.m */; };
040B62132F4BF2560099DEAC /* KeyboardViewController+UI.m in Sources */ = {isa = PBXBuildFile; fileRef = 040B620D2F4BF2560099DEAC /* KeyboardViewController+UI.m */; };
040B62142F4BF2560099DEAC /* KeyboardViewController+Subscription.m in Sources */ = {isa = PBXBuildFile; fileRef = 040B620A2F4BF2560099DEAC /* KeyboardViewController+Subscription.m */; };
040B62162F4BF2560099DEAC /* KeyboardViewController+Suggestions.m in Sources */ = {isa = PBXBuildFile; fileRef = 040B620B2F4BF2560099DEAC /* KeyboardViewController+Suggestions.m */; };
041007D22ECE012000D203BB /* KBSkinIconMap.strings in Resources */ = {isa = PBXBuildFile; fileRef = 041007D12ECE012000D203BB /* KBSkinIconMap.strings */; };
041007D42ECE012500D203BB /* 002.zip in Resources */ = {isa = PBXBuildFile; fileRef = 041007D32ECE012500D203BB /* 002.zip */; };
04122F5D2EC5E5A900EF7AB3 /* KBLoginVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 04122F5B2EC5E5A900EF7AB3 /* KBLoginVM.m */; };
04122F622EC5F41D00EF7AB3 /* KBUser.m in Sources */ = {isa = PBXBuildFile; fileRef = 04122F612EC5F41D00EF7AB3 /* KBUser.m */; };
04122F7E2EC5FC5500EF7AB3 /* KBJfPayCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04122F7C2EC5FC5500EF7AB3 /* KBJfPayCell.m */; };
@@ -28,7 +34,6 @@
04286A032ECB0A1600CE730C /* KBSexSelVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 04286A022ECB0A1600CE730C /* KBSexSelVC.m */; };
04286A062ECC81B200CE730C /* KBSkinService.m in Sources */ = {isa = PBXBuildFile; fileRef = 04286A052ECC81B200CE730C /* KBSkinService.m */; };
04286A0B2ECD88B400CE730C /* KeyboardAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 04286A0A2ECD88B400CE730C /* KeyboardAssets.xcassets */; };
04286A0F2ECDA71B00CE730C /* 001.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04286A0E2ECDA71B00CE730C /* 001.zip */; };
04286A132ECDEBF900CE730C /* KBSkinIconMap.strings in Resources */ = {isa = PBXBuildFile; fileRef = 041007D12ECE012000D203BB /* KBSkinIconMap.strings */; };
043FBCD22EAF97630036AFE1 /* KBPermissionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 04C6EAE12EAF940F0089C901 /* KBPermissionViewController.m */; };
0450AA742EF013D000B6AF06 /* KBEmojiCollectionCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0450AA732EF013D000B6AF06 /* KBEmojiCollectionCell.m */; };
@@ -53,21 +58,12 @@
0459D1B42EBA284C00F2D189 /* KBSkinCenterVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */; };
0459D1B72EBA287900F2D189 /* KBSkinManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B62EBA287900F2D189 /* KBSkinManager.m */; };
0459D1B82EBA287900F2D189 /* KBSkinManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B62EBA287900F2D189 /* KBSkinManager.m */; };
0460866B2F18D75500757C95 /* ai_test.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 0460866A2F18D75500757C95 /* ai_test.m4a */; };
046086732F191B6900757C95 /* KBAiMainVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086722F191B6900757C95 /* KBAiMainVC.m */; };
045ED5212F52AF9200131114 /* KBCancelAccountVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 045ED5202F52AF9200131114 /* KBCancelAccountVC.m */; };
046086752F191CC700757C95 /* AI技术分析.txt in Resources */ = {isa = PBXBuildFile; fileRef = 046086742F191CC700757C95 /* AI技术分析.txt */; };
0460869A2F19238500757C95 /* KBAiWaveformView.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086992F19238500757C95 /* KBAiWaveformView.m */; };
0460869C2F19238500757C95 /* KBAiRecordButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086972F19238500757C95 /* KBAiRecordButton.m */; };
046086B12F19239B00757C95 /* SubtitleSync.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086AC2F19239B00757C95 /* SubtitleSync.m */; };
046086B22F19239B00757C95 /* TTSServiceClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086B02F19239B00757C95 /* TTSServiceClient.m */; };
046086B32F19239B00757C95 /* AudioSessionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086A22F19239B00757C95 /* AudioSessionManager.m */; };
046086B42F19239B00757C95 /* LLMStreamClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086A82F19239B00757C95 /* LLMStreamClient.m */; };
046086B52F19239B00757C95 /* Segmenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086AA2F19239B00757C95 /* Segmenter.m */; };
046086B62F19239B00757C95 /* TTSPlaybackPipeline.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086AE2F19239B00757C95 /* TTSPlaybackPipeline.m */; };
046086B72F19239B00757C95 /* ConversationOrchestrator.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086A62F19239B00757C95 /* ConversationOrchestrator.m */; };
046086B82F19239B00757C95 /* ASRStreamClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 0460869E2F19239B00757C95 /* ASRStreamClient.m */; };
046086B92F19239B00757C95 /* AudioCaptureManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086A02F19239B00757C95 /* AudioCaptureManager.m */; };
046086BA2F19239B00757C95 /* AudioStreamPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086A42F19239B00757C95 /* AudioStreamPlayer.m */; };
046086BD2F1A039F00757C95 /* KBAICommentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086BC2F1A039F00757C95 /* KBAICommentView.m */; };
046086CB2F1A092500757C95 /* comments_mock.json in Resources */ = {isa = PBXBuildFile; fileRef = 046086C62F1A092500757C95 /* comments_mock.json */; };
046086CC2F1A092500757C95 /* KBAIReplyModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086CA2F1A092500757C95 /* KBAIReplyModel.m */; };
@@ -77,9 +73,7 @@
046086D82F1A093400757C95 /* KBAIReplyCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086D52F1A093400757C95 /* KBAIReplyCell.m */; };
046086D92F1A093400757C95 /* KBAICommentHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 046086D12F1A093400757C95 /* KBAICommentHeaderView.m */; };
046131142ECF454500A6FADF /* KBKeyPreviewView.m in Sources */ = {isa = PBXBuildFile; fileRef = 046131132ECF454500A6FADF /* KBKeyPreviewView.m */; };
0477BDF02EBB76E30055D639 /* HomeSheetVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BDEF2EBB76E30055D639 /* HomeSheetVC.m */; };
0477BDF32EBB7B850055D639 /* KBDirectionIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BDF22EBB7B850055D639 /* KBDirectionIndicatorView.m */; };
0477BDF72EBC63A80055D639 /* KBTestVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BDF62EBC63A80055D639 /* KBTestVC.m */; };
0477BDFA2EBC66340055D639 /* HomeHeadView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BDF92EBC66340055D639 /* HomeHeadView.m */; };
0477BDFD2EBC6A170055D639 /* HomeHotVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BDFC2EBC6A170055D639 /* HomeHotVC.m */; };
0477BE002EBC6A330055D639 /* HomeRankVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BDFF2EBC6A330055D639 /* HomeRankVC.m */; };
@@ -91,7 +85,6 @@
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 */; };
04791FF72ED5B985004E8522 /* Christmas.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04791FF62ED5B985004E8522 /* Christmas.zip */; };
04791FFC2ED71D17004E8522 /* UIColor+Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95E42EB220B5007BD342 /* UIColor+Extension.m */; };
04791FFF2ED830FA004E8522 /* KBKeyboardMaskView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04791FFE2ED830FA004E8522 /* KBKeyboardMaskView.m */; };
047920072ED86ABC004E8522 /* kb_guide_keyboard.gif in Resources */ = {isa = PBXBuildFile; fileRef = 047920062ED86ABC004E8522 /* kb_guide_keyboard.gif */; };
@@ -150,7 +143,6 @@
048FFD302F29F3C3005D62AE /* KBAIMessageZanVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD2F2F29F3C3005D62AE /* KBAIMessageZanVC.m */; };
048FFD332F29F3D2005D62AE /* KBAIMessageChatingVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD322F29F3D2005D62AE /* KBAIMessageChatingVC.m */; };
048FFD342F29F400005D62AE /* KBAIMessageListVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD362F29F400005D62AE /* KBAIMessageListVC.m */; };
048FFD362F29F88E005D62AE /* AIMessageVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD352F29F88E005D62AE /* AIMessageVM.m */; };
048FFD372F29F410005D62AE /* KBAIMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD392F29F410005D62AE /* KBAIMessageCell.m */; };
048FFD392F2A24C5005D62AE /* KBAIChatMessageCacheManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD382F2A24C5005D62AE /* KBAIChatMessageCacheManager.m */; };
048FFD3C2F29F500005D62AE /* KBLikedCompanionModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD3B2F29F500005D62AE /* KBLikedCompanionModel.m */; };
@@ -209,6 +201,9 @@
04A9FE202EB893F10020DB6D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 04A9FE1E2EB893F10020DB6D /* Localizable.strings */; };
04A9FE212EB893F10020DB6D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 04A9FE1E2EB893F10020DB6D /* Localizable.strings */; };
04B5A1A22EEFA12300AAAAAA /* KBPayProductModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 04B5A1A12EEFA12300AAAAAA /* KBPayProductModel.m */; };
04BBF89D2F3ACD8800B1FBB2 /* KBKeyboardStressTestVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 04BBF89A2F3ACD8800B1FBB2 /* KBKeyboardStressTestVC.m */; };
04BBF89E2F3ACD8800B1FBB2 /* KBTestVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 04BBF89C2F3ACD8800B1FBB2 /* KBTestVC.m */; };
04BBF9002F3C97CB00B1FBB2 /* DeepgramWebSocketClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 04BBF8FF2F3C97CB00B1FBB2 /* DeepgramWebSocketClient.m */; };
04C6EABA2EAF86530089C901 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 04C6EAAE2EAF86530089C901 /* Assets.xcassets */; };
04C6EABC2EAF86530089C901 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 04C6EAB12EAF86530089C901 /* LaunchScreen.storyboard */; };
04C6EABD2EAF86530089C901 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 04C6EAB42EAF86530089C901 /* Main.storyboard */; };
@@ -222,10 +217,7 @@
04D1F6B32EDFF10A00B12345 /* KBSkinInstallBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 04D1F6B12EDFF10A00B12345 /* KBSkinInstallBridge.m */; };
04E0383E2F1A7C30002CA5A0 /* KBCustomTabBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E0383D2F1A7C30002CA5A0 /* KBCustomTabBar.m */; };
04E038D82F20BFFB002CA5A0 /* websocket-api.md in Resources */ = {isa = PBXBuildFile; fileRef = 04E038D72F20BFFB002CA5A0 /* websocket-api.md */; };
04E038DD2F20C420002CA5A0 /* VoiceChatStreamingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E038DA2F20C420002CA5A0 /* VoiceChatStreamingManager.m */; };
04E038DE2F20C420002CA5A0 /* VoiceChatWebSocketClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E038DC2F20C420002CA5A0 /* VoiceChatWebSocketClient.m */; };
04E038E32F20E500002CA5A0 /* deepgramAPI.md in Resources */ = {isa = PBXBuildFile; fileRef = 04E038E22F20E500002CA5A0 /* deepgramAPI.md */; };
04E038E82F20E877002CA5A0 /* DeepgramWebSocketClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E038E72F20E877002CA5A0 /* DeepgramWebSocketClient.m */; };
04E038E92F20E877002CA5A0 /* DeepgramStreamingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E038E52F20E877002CA5A0 /* DeepgramStreamingManager.m */; };
04E038EF2F21F0EC002CA5A0 /* AiVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E038EE2F21F0EC002CA5A0 /* AiVM.m */; };
04E0394B2F236E75002CA5A0 /* KBChatUserMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E0394A2F236E75002CA5A0 /* KBChatUserMessageCell.m */; };
@@ -238,6 +230,16 @@
04E0B2022F300002002CA5A0 /* KBVoiceRecordManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E0B2012F300002002CA5A0 /* KBVoiceRecordManager.m */; };
04E161832F10E6470022C23B /* normal_hei_them.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04E161812F10E6470022C23B /* normal_hei_them.zip */; };
04E161842F10E6470022C23B /* normal_them.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04E161822F10E6470022C23B /* normal_them.zip */; };
04E2277D2F516EBD001A8F14 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 04E2277C2F516EBD001A8F14 /* PrivacyInfo.xcprivacy */; };
04E2277F2F516ED3001A8F14 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 04E2277E2F516ED3001A8F14 /* PrivacyInfo.xcprivacy */; };
04F4C0AA2F32274000E8F08C /* KBPayMainVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F4C0A92F32274000E8F08C /* KBPayMainVC.m */; };
04F4C0AD2F32288600E8F08C /* KBPaySvipVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F4C0AC2F32288600E8F08C /* KBPaySvipVC.m */; };
04F4C0B02F322EF200E8F08C /* PagingViewTableHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F4C0AF2F322EF200E8F08C /* PagingViewTableHeaderView.m */; };
04F4C0B52F33053800E8F08C /* KBSvipBenefitCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F4C0B22F33053800E8F08C /* KBSvipBenefitCell.m */; };
04F4C0B62F33053800E8F08C /* KBSvipSubscribeCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F4C0B42F33053800E8F08C /* KBSvipSubscribeCell.m */; };
04F4C0BB2F3306CF00E8F08C /* KBSvipFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F4C0BA2F3306CF00E8F08C /* KBSvipFlowLayout.m */; };
04F4C0BC2F3306CF00E8F08C /* KBSvipBenefitBgView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F4C0B82F3306CF00E8F08C /* KBSvipBenefitBgView.m */; };
04F4C0BF2F33369900E8F08C /* KBAIChatDeleteConfirmView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F4C0BE2F33369900E8F08C /* KBAIChatDeleteConfirmView.m */; };
04FC95672EB0546C007BD342 /* KBKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95652EB0546C007BD342 /* KBKey.m */; };
04FC956A2EB05497007BD342 /* KBKeyButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95692EB05497007BD342 /* KBKeyButton.m */; };
04FC956D2EB054B7007BD342 /* KBKeyboardView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC956C2EB054B7007BD342 /* KBKeyboardView.m */; };
@@ -271,6 +273,8 @@
04FEDC422F00040000999999 /* KBKeyboardSubscriptionFeatureMarqueeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FEDC412F00040000999999 /* KBKeyboardSubscriptionFeatureMarqueeView.m */; };
05A1B2D12F5B1A2B3C4D5E60 /* KBSearchVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A1B2C52F5B1A2B3C4D5E60 /* KBSearchVM.m */; };
05A1B2D22F5B1A2B3C4D5E60 /* KBSearchThemeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A1B2C72F5B1A2B3C4D5E60 /* KBSearchThemeModel.m */; };
0F2A10032F3C0001002CA5A0 /* KBChatMessageActionPopView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F2A10022F3C0001002CA5A0 /* KBChatMessageActionPopView.m */; };
0F2A10132F3C0002002CA5A0 /* KBAIPersonaSidebarView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F2A10122F3C0002002CA5A0 /* KBAIPersonaSidebarView.m */; };
471CAD3574798685B72ADD55 /* KBMyTheme.m in Sources */ = {isa = PBXBuildFile; fileRef = 180D662EC4DB3A7FFF83FF18 /* KBMyTheme.m */; };
49B63DBAEE9076C591E13D68 /* KBShopThemeTagModel.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A844CD2D8584596DBE6316 /* KBShopThemeTagModel.m */; };
550CB2630FA4A7B4B9782EFA /* KBMyTheme.m in Sources */ = {isa = PBXBuildFile; fileRef = 180D662EC4DB3A7FFF83FF18 /* KBMyTheme.m */; };
@@ -292,6 +296,7 @@
A1B2C9262FC9000100000001 /* KBChatMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C9212FC9000100000001 /* KBChatMessage.m */; };
A1B2C9272FC9000100000001 /* KBChatMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C9232FC9000100000001 /* KBChatMessageCell.m */; };
A1B2C9282FC9000100000001 /* KBChatPanelView.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C9252FC9000100000001 /* KBChatPanelView.m */; };
A1B2C9302FCA000100000001 /* KBChatLimitPopView.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD232F28A836005D62AE /* KBChatLimitPopView.m */; };
A1B2D7022EB8C00100000001 /* KBLangTestVC.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2D7012EB8C00100000001 /* KBLangTestVC.m */; };
A1B2E1012EBC7AAA00000001 /* KBTopThreeView.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2E0022EBC7AAA00000001 /* KBTopThreeView.m */; };
A1B2E1022EBC7AAA00000001 /* HomeHotCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2E0042EBC7AAA00000001 /* HomeHotCell.m */; };
@@ -331,8 +336,16 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
040B62052F4BF2560099DEAC /* KeyboardViewController+Chat.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "KeyboardViewController+Chat.m"; sourceTree = "<group>"; };
040B62062F4BF2560099DEAC /* KeyboardViewController+Layout.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "KeyboardViewController+Layout.m"; sourceTree = "<group>"; };
040B62072F4BF2560099DEAC /* KeyboardViewController+Legacy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "KeyboardViewController+Legacy.m"; sourceTree = "<group>"; };
040B62082F4BF2560099DEAC /* KeyboardViewController+Panels.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "KeyboardViewController+Panels.m"; sourceTree = "<group>"; };
040B62092F4BF2560099DEAC /* KeyboardViewController+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "KeyboardViewController+Private.h"; sourceTree = "<group>"; };
040B620A2F4BF2560099DEAC /* KeyboardViewController+Subscription.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "KeyboardViewController+Subscription.m"; sourceTree = "<group>"; };
040B620B2F4BF2560099DEAC /* KeyboardViewController+Suggestions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "KeyboardViewController+Suggestions.m"; sourceTree = "<group>"; };
040B620C2F4BF2560099DEAC /* KeyboardViewController+Theme.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "KeyboardViewController+Theme.m"; sourceTree = "<group>"; };
040B620D2F4BF2560099DEAC /* KeyboardViewController+UI.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "KeyboardViewController+UI.m"; sourceTree = "<group>"; };
041007D12ECE012000D203BB /* KBSkinIconMap.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = KBSkinIconMap.strings; sourceTree = "<group>"; };
041007D32ECE012500D203BB /* 002.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = 002.zip; sourceTree = "<group>"; };
04122F592EC5D40000EF7AB3 /* KBAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAPI.h; sourceTree = "<group>"; };
04122F5A2EC5E5A900EF7AB3 /* KBLoginVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBLoginVM.h; sourceTree = "<group>"; };
04122F5B2EC5E5A900EF7AB3 /* KBLoginVM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBLoginVM.m; sourceTree = "<group>"; };
@@ -367,7 +380,6 @@
04286A042ECC81B200CE730C /* KBSkinService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinService.h; sourceTree = "<group>"; };
04286A052ECC81B200CE730C /* KBSkinService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinService.m; sourceTree = "<group>"; };
04286A0A2ECD88B400CE730C /* KeyboardAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = KeyboardAssets.xcassets; sourceTree = "<group>"; };
04286A0E2ECDA71B00CE730C /* 001.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = 001.zip; sourceTree = "<group>"; };
0450AA722EF013D000B6AF06 /* KBEmojiCollectionCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBEmojiCollectionCell.h; sourceTree = "<group>"; };
0450AA732EF013D000B6AF06 /* KBEmojiCollectionCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBEmojiCollectionCell.m; sourceTree = "<group>"; };
0450AAE02EF03D5100B6AF06 /* keyBoard-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "keyBoard-Bridging-Header.h"; sourceTree = "<group>"; };
@@ -393,34 +405,17 @@
0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinCenterVC.m; sourceTree = "<group>"; };
0459D1B52EBA287900F2D189 /* KBSkinManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinManager.h; sourceTree = "<group>"; };
0459D1B62EBA287900F2D189 /* KBSkinManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinManager.m; sourceTree = "<group>"; };
0460866A2F18D75500757C95 /* ai_test.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = ai_test.m4a; sourceTree = "<group>"; };
046086712F191B6900757C95 /* KBAiMainVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAiMainVC.h; sourceTree = "<group>"; };
046086722F191B6900757C95 /* KBAiMainVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAiMainVC.m; sourceTree = "<group>"; };
045ED51F2F52AF9200131114 /* KBCancelAccountVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBCancelAccountVC.h; sourceTree = "<group>"; };
045ED5202F52AF9200131114 /* KBCancelAccountVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBCancelAccountVC.m; sourceTree = "<group>"; };
046086742F191CC700757C95 /* AI技术分析.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "AI技术分析.txt"; sourceTree = "<group>"; };
046086962F19238500757C95 /* KBAiRecordButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAiRecordButton.h; sourceTree = "<group>"; };
046086972F19238500757C95 /* KBAiRecordButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAiRecordButton.m; sourceTree = "<group>"; };
046086982F19238500757C95 /* KBAiWaveformView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAiWaveformView.h; sourceTree = "<group>"; };
046086992F19238500757C95 /* KBAiWaveformView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAiWaveformView.m; sourceTree = "<group>"; };
0460869D2F19239B00757C95 /* ASRStreamClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASRStreamClient.h; sourceTree = "<group>"; };
0460869E2F19239B00757C95 /* ASRStreamClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASRStreamClient.m; sourceTree = "<group>"; };
0460869F2F19239B00757C95 /* AudioCaptureManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AudioCaptureManager.h; sourceTree = "<group>"; };
046086A02F19239B00757C95 /* AudioCaptureManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AudioCaptureManager.m; sourceTree = "<group>"; };
046086A12F19239B00757C95 /* AudioSessionManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AudioSessionManager.h; sourceTree = "<group>"; };
046086A22F19239B00757C95 /* AudioSessionManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AudioSessionManager.m; sourceTree = "<group>"; };
046086A32F19239B00757C95 /* AudioStreamPlayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AudioStreamPlayer.h; sourceTree = "<group>"; };
046086A42F19239B00757C95 /* AudioStreamPlayer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AudioStreamPlayer.m; sourceTree = "<group>"; };
046086A52F19239B00757C95 /* ConversationOrchestrator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConversationOrchestrator.h; sourceTree = "<group>"; };
046086A62F19239B00757C95 /* ConversationOrchestrator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ConversationOrchestrator.m; sourceTree = "<group>"; };
046086A72F19239B00757C95 /* LLMStreamClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LLMStreamClient.h; sourceTree = "<group>"; };
046086A82F19239B00757C95 /* LLMStreamClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LLMStreamClient.m; sourceTree = "<group>"; };
046086A92F19239B00757C95 /* Segmenter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Segmenter.h; sourceTree = "<group>"; };
046086AA2F19239B00757C95 /* Segmenter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Segmenter.m; sourceTree = "<group>"; };
046086AB2F19239B00757C95 /* SubtitleSync.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SubtitleSync.h; sourceTree = "<group>"; };
046086AC2F19239B00757C95 /* SubtitleSync.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SubtitleSync.m; sourceTree = "<group>"; };
046086AD2F19239B00757C95 /* TTSPlaybackPipeline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TTSPlaybackPipeline.h; sourceTree = "<group>"; };
046086AE2F19239B00757C95 /* TTSPlaybackPipeline.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TTSPlaybackPipeline.m; sourceTree = "<group>"; };
046086AF2F19239B00757C95 /* TTSServiceClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TTSServiceClient.h; sourceTree = "<group>"; };
046086B02F19239B00757C95 /* TTSServiceClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TTSServiceClient.m; sourceTree = "<group>"; };
046086BB2F1A039F00757C95 /* KBAICommentView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAICommentView.h; sourceTree = "<group>"; };
046086BC2F1A039F00757C95 /* KBAICommentView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAICommentView.m; sourceTree = "<group>"; };
046086C62F1A092500757C95 /* comments_mock.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = comments_mock.json; sourceTree = "<group>"; };
@@ -438,12 +433,8 @@
046086D52F1A093400757C95 /* KBAIReplyCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIReplyCell.m; sourceTree = "<group>"; };
046131122ECF454500A6FADF /* KBKeyPreviewView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKeyPreviewView.h; sourceTree = "<group>"; };
046131132ECF454500A6FADF /* KBKeyPreviewView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyPreviewView.m; sourceTree = "<group>"; };
0477BDEE2EBB76E30055D639 /* HomeSheetVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HomeSheetVC.h; sourceTree = "<group>"; };
0477BDEF2EBB76E30055D639 /* HomeSheetVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HomeSheetVC.m; sourceTree = "<group>"; };
0477BDF12EBB7B850055D639 /* KBDirectionIndicatorView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBDirectionIndicatorView.h; sourceTree = "<group>"; };
0477BDF22EBB7B850055D639 /* KBDirectionIndicatorView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBDirectionIndicatorView.m; sourceTree = "<group>"; };
0477BDF52EBC63A80055D639 /* KBTestVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBTestVC.h; sourceTree = "<group>"; };
0477BDF62EBC63A80055D639 /* KBTestVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBTestVC.m; sourceTree = "<group>"; };
0477BDF82EBC66340055D639 /* HomeHeadView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HomeHeadView.h; sourceTree = "<group>"; };
0477BDF92EBC66340055D639 /* HomeHeadView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HomeHeadView.m; sourceTree = "<group>"; };
0477BDFB2EBC6A170055D639 /* HomeHotVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HomeHotVC.h; sourceTree = "<group>"; };
@@ -462,7 +453,6 @@
04791F942ED48028004E8522 /* KBFeedBackVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFeedBackVC.m; sourceTree = "<group>"; };
04791F962ED49CE7004E8522 /* KBFont.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFont.h; sourceTree = "<group>"; };
04791F972ED49CE7004E8522 /* KBFont.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFont.m; sourceTree = "<group>"; };
04791FF62ED5B985004E8522 /* Christmas.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = Christmas.zip; sourceTree = "<group>"; };
04791FFD2ED830FA004E8522 /* KBKeyboardMaskView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKeyboardMaskView.h; sourceTree = "<group>"; };
04791FFE2ED830FA004E8522 /* KBKeyboardMaskView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardMaskView.m; sourceTree = "<group>"; };
047920062ED86ABC004E8522 /* kb_guide_keyboard.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = kb_guide_keyboard.gif; sourceTree = "<group>"; };
@@ -572,9 +562,7 @@
048FFD2F2F29F3C3005D62AE /* KBAIMessageZanVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIMessageZanVC.m; sourceTree = "<group>"; };
048FFD312F29F3D2005D62AE /* KBAIMessageChatingVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIMessageChatingVC.h; sourceTree = "<group>"; };
048FFD322F29F3D2005D62AE /* KBAIMessageChatingVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIMessageChatingVC.m; sourceTree = "<group>"; };
048FFD342F29F88E005D62AE /* AIMessageVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AIMessageVM.h; sourceTree = "<group>"; };
048FFD352F29F400005D62AE /* KBAIMessageListVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIMessageListVC.h; sourceTree = "<group>"; };
048FFD352F29F88E005D62AE /* AIMessageVM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AIMessageVM.m; sourceTree = "<group>"; };
048FFD362F29F400005D62AE /* KBAIMessageListVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIMessageListVC.m; sourceTree = "<group>"; };
048FFD372F2A24C5005D62AE /* KBAIChatMessageCacheManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIChatMessageCacheManager.h; sourceTree = "<group>"; };
048FFD382F29F410005D62AE /* KBAIMessageCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIMessageCell.h; sourceTree = "<group>"; };
@@ -677,6 +665,12 @@
04A9FE1D2EB893F10020DB6D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
04B5A1A02EEFA12300AAAAAA /* KBPayProductModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBPayProductModel.h; sourceTree = "<group>"; };
04B5A1A12EEFA12300AAAAAA /* KBPayProductModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBPayProductModel.m; sourceTree = "<group>"; };
04BBF8992F3ACD8800B1FBB2 /* KBKeyboardStressTestVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKeyboardStressTestVC.h; sourceTree = "<group>"; };
04BBF89A2F3ACD8800B1FBB2 /* KBKeyboardStressTestVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardStressTestVC.m; sourceTree = "<group>"; };
04BBF89B2F3ACD8800B1FBB2 /* KBTestVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBTestVC.h; sourceTree = "<group>"; };
04BBF89C2F3ACD8800B1FBB2 /* KBTestVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBTestVC.m; sourceTree = "<group>"; };
04BBF8FE2F3C97CB00B1FBB2 /* DeepgramWebSocketClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeepgramWebSocketClient.h; sourceTree = "<group>"; };
04BBF8FF2F3C97CB00B1FBB2 /* DeepgramWebSocketClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DeepgramWebSocketClient.m; sourceTree = "<group>"; };
04C6EAAC2EAF86530089C901 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
04C6EAAD2EAF86530089C901 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
04C6EAAE2EAF86530089C901 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -700,15 +694,9 @@
04E0383C2F1A7C30002CA5A0 /* KBCustomTabBar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBCustomTabBar.h; sourceTree = "<group>"; };
04E0383D2F1A7C30002CA5A0 /* KBCustomTabBar.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBCustomTabBar.m; sourceTree = "<group>"; };
04E038D72F20BFFB002CA5A0 /* websocket-api.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "websocket-api.md"; sourceTree = "<group>"; };
04E038D92F20C420002CA5A0 /* VoiceChatStreamingManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VoiceChatStreamingManager.h; sourceTree = "<group>"; };
04E038DA2F20C420002CA5A0 /* VoiceChatStreamingManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VoiceChatStreamingManager.m; sourceTree = "<group>"; };
04E038DB2F20C420002CA5A0 /* VoiceChatWebSocketClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VoiceChatWebSocketClient.h; sourceTree = "<group>"; };
04E038DC2F20C420002CA5A0 /* VoiceChatWebSocketClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VoiceChatWebSocketClient.m; sourceTree = "<group>"; };
04E038E22F20E500002CA5A0 /* deepgramAPI.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = deepgramAPI.md; sourceTree = "<group>"; };
04E038E42F20E877002CA5A0 /* DeepgramStreamingManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeepgramStreamingManager.h; sourceTree = "<group>"; };
04E038E52F20E877002CA5A0 /* DeepgramStreamingManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DeepgramStreamingManager.m; sourceTree = "<group>"; };
04E038E62F20E877002CA5A0 /* DeepgramWebSocketClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeepgramWebSocketClient.h; sourceTree = "<group>"; };
04E038E72F20E877002CA5A0 /* DeepgramWebSocketClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DeepgramWebSocketClient.m; sourceTree = "<group>"; };
04E038ED2F21F0EC002CA5A0 /* AiVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AiVM.h; sourceTree = "<group>"; };
04E038EE2F21F0EC002CA5A0 /* AiVM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AiVM.m; sourceTree = "<group>"; };
04E039422F236E75002CA5A0 /* KBChatAssistantMessageCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBChatAssistantMessageCell.h; sourceTree = "<group>"; };
@@ -728,6 +716,24 @@
04E0B2012F300002002CA5A0 /* KBVoiceRecordManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBVoiceRecordManager.m; sourceTree = "<group>"; };
04E161812F10E6470022C23B /* normal_hei_them.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = normal_hei_them.zip; sourceTree = "<group>"; };
04E161822F10E6470022C23B /* normal_them.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = normal_them.zip; sourceTree = "<group>"; };
04E2277C2F516EBD001A8F14 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
04E2277E2F516ED3001A8F14 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
04F4C0A82F32274000E8F08C /* KBPayMainVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBPayMainVC.h; sourceTree = "<group>"; };
04F4C0A92F32274000E8F08C /* KBPayMainVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBPayMainVC.m; sourceTree = "<group>"; };
04F4C0AB2F32288600E8F08C /* KBPaySvipVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBPaySvipVC.h; sourceTree = "<group>"; };
04F4C0AC2F32288600E8F08C /* KBPaySvipVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBPaySvipVC.m; sourceTree = "<group>"; };
04F4C0AE2F322EF200E8F08C /* PagingViewTableHeaderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PagingViewTableHeaderView.h; sourceTree = "<group>"; };
04F4C0AF2F322EF200E8F08C /* PagingViewTableHeaderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PagingViewTableHeaderView.m; sourceTree = "<group>"; };
04F4C0B12F33053800E8F08C /* KBSvipBenefitCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSvipBenefitCell.h; sourceTree = "<group>"; };
04F4C0B22F33053800E8F08C /* KBSvipBenefitCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSvipBenefitCell.m; sourceTree = "<group>"; };
04F4C0B32F33053800E8F08C /* KBSvipSubscribeCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSvipSubscribeCell.h; sourceTree = "<group>"; };
04F4C0B42F33053800E8F08C /* KBSvipSubscribeCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSvipSubscribeCell.m; sourceTree = "<group>"; };
04F4C0B72F3306CF00E8F08C /* KBSvipBenefitBgView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSvipBenefitBgView.h; sourceTree = "<group>"; };
04F4C0B82F3306CF00E8F08C /* KBSvipBenefitBgView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSvipBenefitBgView.m; sourceTree = "<group>"; };
04F4C0B92F3306CF00E8F08C /* KBSvipFlowLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSvipFlowLayout.h; sourceTree = "<group>"; };
04F4C0BA2F3306CF00E8F08C /* KBSvipFlowLayout.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSvipFlowLayout.m; sourceTree = "<group>"; };
04F4C0BD2F33369900E8F08C /* KBAIChatDeleteConfirmView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIChatDeleteConfirmView.h; sourceTree = "<group>"; };
04F4C0BE2F33369900E8F08C /* KBAIChatDeleteConfirmView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIChatDeleteConfirmView.m; sourceTree = "<group>"; };
04FC953A2EAFAE56007BD342 /* KeyBoardPrefixHeader.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyBoardPrefixHeader.pch; sourceTree = "<group>"; };
04FC95642EB0546C007BD342 /* KBKey.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKey.h; sourceTree = "<group>"; };
04FC95652EB0546C007BD342 /* KBKey.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKey.m; sourceTree = "<group>"; };
@@ -797,6 +803,10 @@
05A1B2C52F5B1A2B3C4D5E60 /* KBSearchVM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSearchVM.m; sourceTree = "<group>"; };
05A1B2C62F5B1A2B3C4D5E60 /* KBSearchThemeModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSearchThemeModel.h; sourceTree = "<group>"; };
05A1B2C72F5B1A2B3C4D5E60 /* KBSearchThemeModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSearchThemeModel.m; sourceTree = "<group>"; };
0F2A10012F3C0001002CA5A0 /* KBChatMessageActionPopView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBChatMessageActionPopView.h; sourceTree = "<group>"; };
0F2A10022F3C0001002CA5A0 /* KBChatMessageActionPopView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBChatMessageActionPopView.m; sourceTree = "<group>"; };
0F2A10112F3C0002002CA5A0 /* KBAIPersonaSidebarView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIPersonaSidebarView.h; sourceTree = "<group>"; };
0F2A10122F3C0002002CA5A0 /* KBAIPersonaSidebarView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIPersonaSidebarView.m; sourceTree = "<group>"; };
180D662EC4DB3A7FFF83FF18 /* KBMyTheme.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyTheme.m; sourceTree = "<group>"; };
2C1092FB2B452F95B15D4263 /* Pods_CustomKeyboard.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CustomKeyboard.framework; sourceTree = BUILT_PRODUCTS_DIR; };
35E2B1C590E060D912A4E7F4 /* KBShopThemeTagModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBShopThemeTagModel.h; sourceTree = "<group>"; };
@@ -879,18 +889,31 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
040B620E2F4BF2560099DEAC /* KeyboardViewControllerHelp */ = {
isa = PBXGroup;
children = (
040B62052F4BF2560099DEAC /* KeyboardViewController+Chat.m */,
040B62062F4BF2560099DEAC /* KeyboardViewController+Layout.m */,
040B62072F4BF2560099DEAC /* KeyboardViewController+Legacy.m */,
040B62082F4BF2560099DEAC /* KeyboardViewController+Panels.m */,
040B62092F4BF2560099DEAC /* KeyboardViewController+Private.h */,
040B620A2F4BF2560099DEAC /* KeyboardViewController+Subscription.m */,
040B620B2F4BF2560099DEAC /* KeyboardViewController+Suggestions.m */,
040B620C2F4BF2560099DEAC /* KeyboardViewController+Theme.m */,
040B620D2F4BF2560099DEAC /* KeyboardViewController+UI.m */,
);
path = KeyboardViewControllerHelp;
sourceTree = "<group>";
};
041007D02ECE010100D203BB /* Resource */ = {
isa = PBXGroup;
children = (
0460866A2F18D75500757C95 /* ai_test.m4a */,
04E161812F10E6470022C23B /* normal_hei_them.zip */,
04E161822F10E6470022C23B /* normal_them.zip */,
A1B2C3EC2F20000000000001 /* kb_words.txt */,
A1B2C3F02F20000000000002 /* kb_keyboard_layout_config.json */,
0498BDF42EEC50EE006CC1D5 /* emoji_categories.json */,
041007D12ECE012000D203BB /* KBSkinIconMap.strings */,
041007D32ECE012500D203BB /* 002.zip */,
04791FF62ED5B985004E8522 /* Christmas.zip */,
);
path = Resource;
sourceTree = "<group>";
@@ -951,6 +974,16 @@
04122FAF2EC73C0100EF7AB3 /* KBVipReviewItemCell.m */,
04122FB12EC73C0100EF7AB3 /* KBVipReviewListCell.h */,
04122FB22EC73C0100EF7AB3 /* KBVipReviewListCell.m */,
04F4C0AE2F322EF200E8F08C /* PagingViewTableHeaderView.h */,
04F4C0AF2F322EF200E8F08C /* PagingViewTableHeaderView.m */,
04F4C0B12F33053800E8F08C /* KBSvipBenefitCell.h */,
04F4C0B22F33053800E8F08C /* KBSvipBenefitCell.m */,
04F4C0B32F33053800E8F08C /* KBSvipSubscribeCell.h */,
04F4C0B42F33053800E8F08C /* KBSvipSubscribeCell.m */,
04F4C0B72F3306CF00E8F08C /* KBSvipBenefitBgView.h */,
04F4C0B82F3306CF00E8F08C /* KBSvipBenefitBgView.m */,
04F4C0B92F3306CF00E8F08C /* KBSvipFlowLayout.h */,
04F4C0BA2F3306CF00E8F08C /* KBSvipFlowLayout.m */,
);
path = V;
sourceTree = "<group>";
@@ -962,6 +995,10 @@
04122F802EC5FC6F00EF7AB3 /* KBJfPay.m */,
04122F8F2EC73AF700EF7AB3 /* KBVipPay.h */,
04122F902EC73AF700EF7AB3 /* KBVipPay.m */,
04F4C0A82F32274000E8F08C /* KBPayMainVC.h */,
04F4C0A92F32274000E8F08C /* KBPayMainVC.m */,
04F4C0AB2F32288600E8F08C /* KBPaySvipVC.h */,
04F4C0AC2F32288600E8F08C /* KBPaySvipVC.m */,
);
path = VC;
sourceTree = "<group>";
@@ -1114,8 +1151,6 @@
0460866E2F191A5100757C95 /* VC */ = {
isa = PBXGroup;
children = (
046086712F191B6900757C95 /* KBAiMainVC.h */,
046086722F191B6900757C95 /* KBAiMainVC.m */,
048FFD092F273BFC005D62AE /* KBAIHomeVC.h */,
048FFD0A2F273BFC005D62AE /* KBAIHomeVC.m */,
048FFD2B2F29F356005D62AE /* KBAIMessageVC.h */,
@@ -1137,42 +1172,20 @@
0460866F2F191A5100757C95 /* VM */ = {
isa = PBXGroup;
children = (
0460869D2F19239B00757C95 /* ASRStreamClient.h */,
0460869E2F19239B00757C95 /* ASRStreamClient.m */,
0460869F2F19239B00757C95 /* AudioCaptureManager.h */,
046086A02F19239B00757C95 /* AudioCaptureManager.m */,
046086A12F19239B00757C95 /* AudioSessionManager.h */,
046086A22F19239B00757C95 /* AudioSessionManager.m */,
046086A32F19239B00757C95 /* AudioStreamPlayer.h */,
046086A42F19239B00757C95 /* AudioStreamPlayer.m */,
046086A52F19239B00757C95 /* ConversationOrchestrator.h */,
046086A62F19239B00757C95 /* ConversationOrchestrator.m */,
046086A72F19239B00757C95 /* LLMStreamClient.h */,
046086A82F19239B00757C95 /* LLMStreamClient.m */,
046086A92F19239B00757C95 /* Segmenter.h */,
046086AA2F19239B00757C95 /* Segmenter.m */,
046086AB2F19239B00757C95 /* SubtitleSync.h */,
046086AC2F19239B00757C95 /* SubtitleSync.m */,
046086AD2F19239B00757C95 /* TTSPlaybackPipeline.h */,
046086AE2F19239B00757C95 /* TTSPlaybackPipeline.m */,
046086AF2F19239B00757C95 /* TTSServiceClient.h */,
046086B02F19239B00757C95 /* TTSServiceClient.m */,
04E038D92F20C420002CA5A0 /* VoiceChatStreamingManager.h */,
04E038DA2F20C420002CA5A0 /* VoiceChatStreamingManager.m */,
04E038DB2F20C420002CA5A0 /* VoiceChatWebSocketClient.h */,
04E038DC2F20C420002CA5A0 /* VoiceChatWebSocketClient.m */,
04E038E42F20E877002CA5A0 /* DeepgramStreamingManager.h */,
04E038E52F20E877002CA5A0 /* DeepgramStreamingManager.m */,
04BBF8FE2F3C97CB00B1FBB2 /* DeepgramWebSocketClient.h */,
04BBF8FF2F3C97CB00B1FBB2 /* DeepgramWebSocketClient.m */,
04E0B1002F300001002CA5A0 /* KBVoiceToTextManager.h */,
04E0B1012F300001002CA5A0 /* KBVoiceToTextManager.m */,
04E0B2002F300002002CA5A0 /* KBVoiceRecordManager.h */,
04E0B2012F300002002CA5A0 /* KBVoiceRecordManager.m */,
04E038E62F20E877002CA5A0 /* DeepgramWebSocketClient.h */,
04E038E72F20E877002CA5A0 /* DeepgramWebSocketClient.m */,
04E038ED2F21F0EC002CA5A0 /* AiVM.h */,
04E038EE2F21F0EC002CA5A0 /* AiVM.m */,
048FFD342F29F88E005D62AE /* AIMessageVM.h */,
048FFD352F29F88E005D62AE /* AIMessageVM.m */,
);
path = VM;
sourceTree = "<group>";
@@ -1207,12 +1220,14 @@
0477BE012EBC6D420055D639 /* FunctionTest */ = {
isa = PBXGroup;
children = (
0477BDF52EBC63A80055D639 /* KBTestVC.h */,
0477BDF62EBC63A80055D639 /* KBTestVC.m */,
A1B2D7002EB8C00100000001 /* KBLangTestVC.h */,
A1B2D7012EB8C00100000001 /* KBLangTestVC.m */,
0459D1B22EBA284C00F2D189 /* KBSkinCenterVC.h */,
0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */,
04BBF8992F3ACD8800B1FBB2 /* KBKeyboardStressTestVC.h */,
04BBF89A2F3ACD8800B1FBB2 /* KBKeyboardStressTestVC.m */,
04BBF89B2F3ACD8800B1FBB2 /* KBTestVC.h */,
04BBF89C2F3ACD8800B1FBB2 /* KBTestVC.m */,
);
path = FunctionTest;
sourceTree = "<group>";
@@ -1244,7 +1259,6 @@
0479200A2ED87CEE004E8522 /* permiss_video.mp4 */,
047920102ED98E7D004E8522 /* permiss_video_2.mp4 */,
047920062ED86ABC004E8522 /* kb_guide_keyboard.gif */,
04286A0E2ECDA71B00CE730C /* 001.zip */,
);
path = Resource;
sourceTree = "<group>";
@@ -1431,6 +1445,8 @@
04E039452F236E75002CA5A0 /* KBChatTableView.m */,
04E039492F236E75002CA5A0 /* KBChatUserMessageCell.h */,
04E0394A2F236E75002CA5A0 /* KBChatUserMessageCell.m */,
0F2A10012F3C0001002CA5A0 /* KBChatMessageActionPopView.h */,
0F2A10022F3C0001002CA5A0 /* KBChatMessageActionPopView.m */,
04E039422F236E75002CA5A0 /* KBChatAssistantMessageCell.h */,
04E039432F236E75002CA5A0 /* KBChatAssistantMessageCell.m */,
04E039472F236E75002CA5A0 /* KBChatTimeCell.h */,
@@ -1444,6 +1460,10 @@
children = (
048FFD222F28A836005D62AE /* KBChatLimitPopView.h */,
048FFD232F28A836005D62AE /* KBChatLimitPopView.m */,
0F2A10112F3C0002002CA5A0 /* KBAIPersonaSidebarView.h */,
0F2A10122F3C0002002CA5A0 /* KBAIPersonaSidebarView.m */,
04F4C0BD2F33369900E8F08C /* KBAIChatDeleteConfirmView.h */,
04F4C0BE2F33369900E8F08C /* KBAIChatDeleteConfirmView.m */,
);
path = PopView;
sourceTree = "<group>";
@@ -1543,6 +1563,7 @@
04C6EAB92EAF86530089C901 /* keyBoard */ = {
isa = PBXGroup;
children = (
04E2277C2F516EBD001A8F14 /* PrivacyInfo.xcprivacy */,
04FC95F52EB33B52007BD342 /* keyBoard.entitlements */,
04FC95BF2EB1E3B1007BD342 /* Class */,
04C6EAE32EAF942E0089C901 /* VC */,
@@ -1563,6 +1584,7 @@
04C6EAD72EAF870B0089C901 /* CustomKeyboard */ = {
isa = PBXGroup;
children = (
04E2277E2F516ED3001A8F14 /* PrivacyInfo.xcprivacy */,
0419C9632F2C7630002E86D3 /* VM */,
041007D02ECE010100D203BB /* Resource */,
0477BD942EBAFF4E0055D639 /* Utils */,
@@ -1573,6 +1595,7 @@
04C6EAD42EAF870B0089C901 /* Info.plist */,
04C6EAD52EAF870B0089C901 /* KeyboardViewController.h */,
04C6EAD62EAF870B0089C901 /* KeyboardViewController.m */,
040B620E2F4BF2560099DEAC /* KeyboardViewControllerHelp */,
04C6EADE2EAF8D680089C901 /* PrefixHeader.pch */,
04286A0A2ECD88B400CE730C /* KeyboardAssets.xcassets */,
);
@@ -1706,8 +1729,6 @@
children = (
0477BE022EBC83130055D639 /* HomeMainVC.h */,
0477BE032EBC83130055D639 /* HomeMainVC.m */,
0477BDEE2EBB76E30055D639 /* HomeSheetVC.h */,
0477BDEF2EBB76E30055D639 /* HomeSheetVC.m */,
0477BDFB2EBC6A170055D639 /* HomeHotVC.h */,
0477BDFC2EBC6A170055D639 /* HomeHotVC.m */,
0477BDFE2EBC6A330055D639 /* HomeRankVC.h */,
@@ -1824,6 +1845,8 @@
A1F0C1A52F1234567890ABCD /* KBConsumptionRecordVC.m */,
049FB2212EC311F900FAB05D /* KBPersonInfoVC.h */,
049FB2222EC311F900FAB05D /* KBPersonInfoVC.m */,
045ED51F2F52AF9200131114 /* KBCancelAccountVC.h */,
045ED5202F52AF9200131114 /* KBCancelAccountVC.m */,
04791F902ED48010004E8522 /* KBNoticeVC.h */,
04791F912ED48010004E8522 /* KBNoticeVC.m */,
04791F932ED48028004E8522 /* KBFeedBackVC.h */,
@@ -2230,13 +2253,11 @@
04E161832F10E6470022C23B /* normal_hei_them.zip in Resources */,
04E161842F10E6470022C23B /* normal_them.zip in Resources */,
04A9FE202EB893F10020DB6D /* Localizable.strings in Resources */,
0460866B2F18D75500757C95 /* ai_test.m4a in Resources */,
041007D42ECE012500D203BB /* 002.zip in Resources */,
041007D22ECE012000D203BB /* KBSkinIconMap.strings in Resources */,
04E2277F2F516ED3001A8F14 /* PrivacyInfo.xcprivacy in Resources */,
A1B2C3ED2F20000000000001 /* kb_words.txt in Resources */,
A1B2C3F12F20000000000002 /* kb_keyboard_layout_config.json in Resources */,
0498BDF52EEC50EE006CC1D5 /* emoji_categories.json in Resources */,
04791FF72ED5B985004E8522 /* Christmas.zip in Resources */,
04286A0B2ECD88B400CE730C /* KeyboardAssets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -2245,9 +2266,9 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
04286A0F2ECDA71B00CE730C /* 001.zip in Resources */,
04E038D82F20BFFB002CA5A0 /* websocket-api.md in Resources */,
0479200B2ED87CEE004E8522 /* permiss_video.mp4 in Resources */,
04E2277D2F516EBD001A8F14 /* PrivacyInfo.xcprivacy in Resources */,
04C6EABA2EAF86530089C901 /* Assets.xcassets in Resources */,
04A9FE212EB893F10020DB6D /* Localizable.strings in Resources */,
047920072ED86ABC004E8522 /* kb_guide_keyboard.gif in Resources */,
@@ -2340,9 +2361,17 @@
0498BD862EE1BEC9006CC1D5 /* KBSignUtils.m in Sources */,
04791FFC2ED71D17004E8522 /* UIColor+Extension.m in Sources */,
0450AC4A2EF2C3ED00B6AF06 /* KBKeyboardSubscriptionOptionCell.m in Sources */,
040B620F2F4BF2560099DEAC /* KeyboardViewController+Theme.m in Sources */,
040B62102F4BF2560099DEAC /* KeyboardViewController+Chat.m in Sources */,
040B62112F4BF2560099DEAC /* KeyboardViewController+Panels.m in Sources */,
040B62122F4BF2560099DEAC /* KeyboardViewController+Layout.m in Sources */,
040B62132F4BF2560099DEAC /* KeyboardViewController+UI.m in Sources */,
040B62142F4BF2560099DEAC /* KeyboardViewController+Subscription.m in Sources */,
040B62162F4BF2560099DEAC /* KeyboardViewController+Suggestions.m in Sources */,
04A9FE0F2EB481100020DB6D /* KBHUD.m in Sources */,
048FFD562F2B9C3D005D62AE /* KBChatAssistantCell.m in Sources */,
048FFD572F2B9C3D005D62AE /* KBChatUserCell.m in Sources */,
A1B2C9302FCA000100000001 /* KBChatLimitPopView.m in Sources */,
04C6EADD2EAF8CEB0089C901 /* KBToolBar.m in Sources */,
A1B2C9262FC9000100000001 /* KBChatMessage.m in Sources */,
A1B2C9272FC9000100000001 /* KBChatMessageCell.m in Sources */,
@@ -2408,11 +2437,14 @@
04122F622EC5F41D00EF7AB3 /* KBUser.m in Sources */,
04122F8B2EC6F7C800EF7AB3 /* IAPVerifyTransactionObj.m in Sources */,
04B5A1A22EEFA12300AAAAAA /* KBPayProductModel.m in Sources */,
04F4C0AD2F32288600E8F08C /* KBPaySvipVC.m in Sources */,
046086D62F1A093400757C95 /* KBAICommentInputView.m in Sources */,
046086D72F1A093400757C95 /* KBAICommentFooterView.m in Sources */,
046086D82F1A093400757C95 /* KBAIReplyCell.m in Sources */,
046086D92F1A093400757C95 /* KBAICommentHeaderView.m in Sources */,
04E0394B2F236E75002CA5A0 /* KBChatUserMessageCell.m in Sources */,
0F2A10032F3C0001002CA5A0 /* KBChatMessageActionPopView.m in Sources */,
0F2A10132F3C0002002CA5A0 /* KBAIPersonaSidebarView.m in Sources */,
04E0394C2F236E75002CA5A0 /* KBChatTimeCell.m in Sources */,
04E0394D2F236E75002CA5A0 /* KBChatAssistantMessageCell.m in Sources */,
04E0394E2F236E75002CA5A0 /* KBChatTableView.m in Sources */,
@@ -2421,7 +2453,6 @@
04122FAD2EC73C0100EF7AB3 /* KBVipSubscribeCell.m in Sources */,
049FB31D2EC21BCD00FAB05D /* KBMyKeyboardCell.m in Sources */,
048909F62EC0AAAA00FABA60 /* KBCategoryTitleCell.m in Sources */,
046086732F191B6900757C95 /* KBAiMainVC.m in Sources */,
048909F72EC0AAAA00FABA60 /* KBCategoryTitleView.m in Sources */,
048FFD332F29F3D2005D62AE /* KBAIMessageChatingVC.m in Sources */,
048FFD342F29F400005D62AE /* KBAIMessageListVC.m in Sources */,
@@ -2440,6 +2471,7 @@
046086CC2F1A092500757C95 /* KBAIReplyModel.m in Sources */,
046086CD2F1A092500757C95 /* KBAICommentModel.m in Sources */,
04791F8F2ED469C0004E8522 /* KBHostAppLauncher.m in Sources */,
04F4C0BF2F33369900E8F08C /* KBAIChatDeleteConfirmView.m in Sources */,
047C65582EBCC06D0035E841 /* HomeRankCardCell.m in Sources */,
04D1F6B32EDFF10A00B12345 /* KBSkinInstallBridge.m in Sources */,
04122F912EC73AF700EF7AB3 /* KBVipPay.m in Sources */,
@@ -2475,6 +2507,7 @@
048908E92EBF843000FABA60 /* KBSkinDetailHeaderCell.m in Sources */,
04A9FE1B2EB892460020DB6D /* KBLocalizationManager.m in Sources */,
048908BC2EBE1FCB00FABA60 /* BaseViewController.m in Sources */,
04F4C0B02F322EF200E8F08C /* PagingViewTableHeaderView.m in Sources */,
0498BD902EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */,
A1F0C1C32FABCDEF12345678 /* KBInviteCodeModel.m in Sources */,
A1F0C1D32FACAD0012345678 /* KBMaiPointReporter.m in Sources */,
@@ -2487,7 +2520,6 @@
0498BD712EE02A41006CC1D5 /* KBForgetPwdNewPwdVC.m in Sources */,
048908EF2EBF861800FABA60 /* KBSkinSectionTitleCell.m in Sources */,
0450AAE22EF03D5100B6AF06 /* KBPerson.swift in Sources */,
04E038E82F20E877002CA5A0 /* DeepgramWebSocketClient.m in Sources */,
04E038E92F20E877002CA5A0 /* DeepgramStreamingManager.m in Sources */,
04E0B1022F300001002CA5A0 /* KBVoiceToTextManager.m in Sources */,
04E0B2022F300002002CA5A0 /* KBVoiceRecordManager.m in Sources */,
@@ -2499,18 +2531,18 @@
048908D22EBF611D00FABA60 /* KBHistoryMoreCell.m in Sources */,
04FC95D82EB1EA16007BD342 /* BaseCell.m in Sources */,
0498BD852EE1B255006CC1D5 /* KBSignUtils.m in Sources */,
0477BDF72EBC63A80055D639 /* KBTestVC.m in Sources */,
04122F7E2EC5FC5500EF7AB3 /* KBJfPayCell.m in Sources */,
048FFD502F2B52E7005D62AE /* AIReportVC.m in Sources */,
049FB2402EC4B6EF00FAB05D /* KBULBridgeNotification.m in Sources */,
04BBF9002F3C97CB00B1FBB2 /* DeepgramWebSocketClient.m in Sources */,
04FC95C92EB1E4C9007BD342 /* BaseNavigationController.m in Sources */,
048908DD2EBF67EB00FABA60 /* KBSearchResultVC.m in Sources */,
05A1B2D12F5B1A2B3C4D5E60 /* KBSearchVM.m in Sources */,
05A1B2D22F5B1A2B3C4D5E60 /* KBSearchThemeModel.m in Sources */,
047C65102EBCA8DD0035E841 /* HomeRankContentVC.m in Sources */,
047C655C2EBCD0F80035E841 /* UIView+KBShadow.m in Sources */,
04E038DD2F20C420002CA5A0 /* VoiceChatStreamingManager.m in Sources */,
04E038DE2F20C420002CA5A0 /* VoiceChatWebSocketClient.m in Sources */,
04F4C0B52F33053800E8F08C /* KBSvipBenefitCell.m in Sources */,
04F4C0B62F33053800E8F08C /* KBSvipSubscribeCell.m in Sources */,
049FB2262EC3136D00FAB05D /* KBPersonInfoItemCell.m in Sources */,
048908C32EBE32B800FABA60 /* KBSearchVC.m in Sources */,
049FB20B2EC1C13800FAB05D /* KBSkinBottomActionView.m in Sources */,
@@ -2518,14 +2550,13 @@
04FC95DD2EB202A3007BD342 /* KBGuideVC.m in Sources */,
04FC95E52EB220B5007BD342 /* UIColor+Extension.m in Sources */,
048908E02EBF73DC00FABA60 /* MySkinVC.m in Sources */,
048FFD362F29F88E005D62AE /* AIMessageVM.m in Sources */,
04F4C0AA2F32274000E8F08C /* KBPayMainVC.m in Sources */,
048908F22EC047FD00FABA60 /* KBShopHeadView.m in Sources */,
0498BD742EE02E3D006CC1D5 /* KBRegistVerEmailVC.m in Sources */,
049FB2292EC31BB000FAB05D /* KBChangeNicknamePopView.m in Sources */,
048908EC2EBF849300FABA60 /* KBSkinTagsContainerCell.m in Sources */,
049FB2172EC20A6600FAB05D /* BMLongPressDragCellCollectionView.m in Sources */,
04122F8E2EC6F83F00EF7AB3 /* PayVM.m in Sources */,
0477BDF02EBB76E30055D639 /* HomeSheetVC.m in Sources */,
048908E62EBF841B00FABA60 /* KBSkinDetailTagCell.m in Sources */,
04FC97002EB30A00007BD342 /* KBGuideTopCell.m in Sources */,
04791F982ED49CE7004E8522 /* KBFont.m in Sources */,
@@ -2541,16 +2572,8 @@
048FFD112F27432D005D62AE /* KBPersonaPageModel.m in Sources */,
0498BD6B2EE025FC006CC1D5 /* KBForgetPwdVC.m in Sources */,
048FFD182F2763A5005D62AE /* KBVoiceInputBar.m in Sources */,
046086B12F19239B00757C95 /* SubtitleSync.m in Sources */,
046086B22F19239B00757C95 /* TTSServiceClient.m in Sources */,
046086B32F19239B00757C95 /* AudioSessionManager.m in Sources */,
046086B42F19239B00757C95 /* LLMStreamClient.m in Sources */,
046086B52F19239B00757C95 /* Segmenter.m in Sources */,
046086B62F19239B00757C95 /* TTSPlaybackPipeline.m in Sources */,
046086B72F19239B00757C95 /* ConversationOrchestrator.m in Sources */,
046086B82F19239B00757C95 /* ASRStreamClient.m in Sources */,
046086B92F19239B00757C95 /* AudioCaptureManager.m in Sources */,
046086BA2F19239B00757C95 /* AudioStreamPlayer.m in Sources */,
048908FE2EC0CC2400FABA60 /* UIScrollView+KBEmptyView.m in Sources */,
0498BD7E2EE04F9C006CC1D5 /* KBTag.m in Sources */,
04791F922ED48010004E8522 /* KBNoticeVC.m in Sources */,
@@ -2579,6 +2602,8 @@
047C65502EBCBA9E0035E841 /* KBShopVC.m in Sources */,
0477BE042EBC83130055D639 /* HomeMainVC.m in Sources */,
0477BDFD2EBC6A170055D639 /* HomeHotVC.m in Sources */,
04F4C0BB2F3306CF00E8F08C /* KBSvipFlowLayout.m in Sources */,
04F4C0BC2F3306CF00E8F08C /* KBSvipBenefitBgView.m in Sources */,
04E039522F2387D2002CA5A0 /* KBAiChatMessage.m in Sources */,
0460869A2F19238500757C95 /* KBAiWaveformView.m in Sources */,
0460869C2F19238500757C95 /* KBAiRecordButton.m in Sources */,
@@ -2590,12 +2615,15 @@
0498BDE42EEA885D006CC1D5 /* KBShopThemeModel.m in Sources */,
048908CD2EBE373500FABA60 /* KBSearchSectionHeader.m in Sources */,
049FB2202EC30D2700FAB05D /* HomeRankDetailPopView.m in Sources */,
045ED5212F52AF9200131114 /* KBCancelAccountVC.m in Sources */,
048908CE2EBE373500FABA60 /* KBSkinCardCell.m in Sources */,
048908CF2EBE373500FABA60 /* KBTagCell.m in Sources */,
0477BEA22EBCF0000055D639 /* KBTopImageButton.m in Sources */,
A1B2E1012EBC7AAA00000001 /* KBTopThreeView.m in Sources */,
A1B2E1022EBC7AAA00000001 /* HomeHotCell.m in Sources */,
048FFD272F28C6CF005D62AE /* KBImagePositionButton.m in Sources */,
04BBF89D2F3ACD8800B1FBB2 /* KBKeyboardStressTestVC.m in Sources */,
04BBF89E2F3ACD8800B1FBB2 /* KBTestVC.m in Sources */,
0459D1B72EBA287900F2D189 /* KBSkinManager.m in Sources */,
04286A002ECAEF2B00CE730C /* KBMoneyBtn.m in Sources */,
048908F52EC0496400FABA60 /* KBShopItemVC.m in Sources */,

View File

@@ -57,8 +57,12 @@
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<RemoteRunnable
runnableDebuggingMode = "0"
BundleIdentifier = "com.loveKey.nyx"
RemotePath = "/var/containers/Bundle/Application/E51DCFA2-A182-4B31-8A45-BCCF663ADCAA/keyBoard.app">
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "727EC7522EAF848B00B36487"
@@ -66,7 +70,14 @@
BlueprintName = "keyBoard"
ReferencedContainer = "container:keyBoard.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</MacroExpansion>
<EnvironmentVariables>
<EnvironmentVariable
key = "OS_ACTIVITY_MODE"
value = "disable"
isEnabled = "NO">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildLocationStyle</key>
<string>UseAppPreferences</string>
<key>CustomBuildLocationType</key>
<string>RelativeToDerivedData</string>
<key>DerivedDataLocationStyle</key>
<string>Default</string>
<key>ShowSharedSchemesAutomaticallyEnabled</key>
<true/>
</dict>
</plist>

View File

@@ -21,7 +21,7 @@
#import "KBLoginPopView.h"
#import "KBSexSelVC.h"
#import "KBKeyboardPermissionManager.h"
#import "KBVipPay.h"
#import "KBPayMainVC.h"
#import "KBUserSessionManager.h"
#import "KBLoginVC.h"
#import "KBConfig.h"
@@ -192,6 +192,9 @@ static NSTimeInterval const kKBSubscriptionPrefillTTL = 10 * 60.0;
return YES;
} else if ([host isEqualToString:@"recharge"]) { // kbkeyboard://recharge
NSDictionary<NSString *, NSString *> *params = [self kb_queryParametersFromURL:url];
NSString *vipType = [params[@"vipType"] lowercaseString];
BOOL preferSvip = ([vipType isKindOfClass:NSString.class] && vipType.length > 0 &&
([vipType isEqualToString:@"svip"] || [vipType isEqualToString:@"1"]));
NSString *productId = params[@"productId"];
BOOL autoPay = NO;
NSString *autoFlag = params[@"autoPay"];
@@ -203,6 +206,9 @@ static NSTimeInterval const kKBSubscriptionPrefillTTL = 10 * 60.0;
autoPay = YES;
}
KBPayMainVC *vc = [[KBPayMainVC alloc] init];
vc.initialSelectedIndex = preferSvip ? 1 : 0;
if (!preferSvip) {
BOOL wantsPrefill = NO;
NSString *prefillFlag = params[@"prefill"];
if ([prefillFlag respondsToSelector:@selector(boolValue)] && prefillFlag.boolValue) {
@@ -220,7 +226,6 @@ static NSTimeInterval const kKBSubscriptionPrefillTTL = 10 * 60.0;
}
}
KBVipPay *vc = [[KBVipPay alloc] init];
if ([prefillPayload isKindOfClass:NSDictionary.class]) {
NSArray *productsJSON = prefillPayload[@"products"];
NSNumber *selectedIndexNumber = prefillPayload[@"selectedIndex"];
@@ -232,6 +237,7 @@ static NSTimeInterval const kKBSubscriptionPrefillTTL = 10 * 60.0;
} else {
[vc configureWithProductId:productId autoPurchase:autoPay];
}
}
[KB_CURRENT_NAV pushViewController:vc animated:true];
return YES;
}

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ai_limit_close@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ai_limit_close@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ai_limit_goto@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ai_limit_goto@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ai_limit_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ai_limit_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ai_more_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ai_more_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ai_role_sel@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ai_role_sel@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ai_search_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ai_search_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "chat_del_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "chat_del_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "right_arrow_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "right_arrow_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "my_svip_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "my_svip_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "微信图片_20260226192149_128_935 (1).png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "SVIP_Normal_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "SVIP_Normal_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "SVIP_Selected_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "SVIP_Selected_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "VIP_Normal_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "VIP_Normal_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "VIP_Selected_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "VIP_Selected_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "pay_ai_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "pay_ai_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Some files were not shown because too many files have changed in this diff Show More