处理上架的问题

1:处理了openurl 拉起问题
2:去掉了http
3 隐私等等
This commit is contained in:
2026-03-05 14:30:07 +08:00
parent 8cc484edcb
commit d8a84dc478
34 changed files with 506 additions and 511 deletions

9
.gitignore vendored
View File

@@ -1 +1,10 @@
# Xcode / build artifacts
_DerivedData/
DerivedData/
*.xcresult/
xcuserdata/
# SwiftPM artifacts
_spm/
_SourcePackages/
.swiftpm/

View File

@@ -11,7 +11,7 @@
#import "KBChatMessage.h"
#import "KBChatPanelView.h"
#import "KBFullAccessManager.h"
#import "KBHostAppLauncher.h"
#import "../Utils/KBExtensionAppLauncher.h"
#import "KBInputBufferManager.h"
#import "KBNetworkManager.h"
#import "KBVM.h"
@@ -118,7 +118,9 @@ static const NSUInteger kKBChatMessageLimit = 6;
if (text.length == 0) {
return;
}
NSLog(@"[KB] 发送消息: %@", text);
#if DEBUG
NSLog(@"[KB] 发送消息 len=%lu", (unsigned long)text.length);
#endif
KBChatMessage *outgoing = [KBChatMessage userMessageWithText:text];
outgoing.avatarURL = [self kb_sharedUserAvatarURL];
@@ -702,11 +704,21 @@ static const NSUInteger kKBChatMessageLimit = 6;
[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")];
}
NSString *ulString = [NSString stringWithFormat:@"%@?src=keyboard&vipType=svip", KB_UL_RECHARGE];
NSURL *ul = [NSURL URLWithString:ulString];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")];
});
}];
}
@end

View File

@@ -12,7 +12,7 @@
#import "KBBackspaceUndoManager.h"
#import "KBFullAccessManager.h"
#import "KBFunctionView.h"
#import "KBHostAppLauncher.h"
#import "../Utils/KBExtensionAppLauncher.h"
#import "KBInputBufferManager.h"
#import "KBKey.h"
#import "KBKeyboardSubscriptionProduct.h"
@@ -766,11 +766,15 @@ static NSString *KBFormatMB(uint64_t bytes) {
// 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];
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard", KB_UL_LOGIN]];
NSURL *scheme =
[NSURL URLWithString:[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:nil];
return;
}
[[KBMaiPointReporter sharedReporter]
@@ -999,11 +1003,15 @@ static NSString *KBFormatMB(uint64_t bytes) {
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];
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard", KB_UL_LOGIN]];
NSURL *scheme =
[NSURL URLWithString:[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:nil];
return;
}
NSString *schemeStr =
@@ -1013,13 +1021,20 @@ static NSString *KBFormatMB(uint64_t bytes) {
// if (!ul && !scheme) { return; }
//
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
if (!ok) {
//
// XXX App /
[KBHUD showInfo:@"请回到桌面手动打开App进行充值"];
}
__weak typeof(self) weakSelf = self;
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard", KB_UL_RECHARGE]];
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:@"当前应用不允许键盘直接唤起App请回到桌面手动打开App进行充值"];
});
}];
}
- (void)functionViewDidRequestSubscription:(KBFunctionView *)functionView {
@@ -1672,11 +1687,20 @@ static NSString *KBFormatMB(uint64_t bytes) {
[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")];
}
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard&vipType=svip", KB_UL_RECHARGE]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")];
});
}];
}
#pragma mark - lazy
@@ -1753,11 +1777,21 @@ static NSString *KBFormatMB(uint64_t bytes) {
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 *ulString = [NSString stringWithFormat:@"%@?src=keyboard&%@", KB_UL_RECHARGE, query];
NSURL *ul = [NSURL URLWithString:ulString];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")];
});
}];
}
+ (NSString *)kb_urlEncodedString:(NSString *)value {

View File

@@ -13,7 +13,7 @@
#import "KBChatPanelView.h"
#import "KBFunctionView.h"
#import "KBFullAccessManager.h"
#import "KBHostAppLauncher.h"
#import "../Utils/KBExtensionAppLauncher.h"
#import "KBInputBufferManager.h"
#import "KBKey.h"
#import "KBKeyBoardMainView.h"
@@ -55,13 +55,22 @@
#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登录")];
}
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard", KB_UL_LOGIN]];
NSURL *scheme =
[NSURL URLWithString:[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:KBLocalized(@"请回到桌面手动打开App登录")];
});
}];
return;
}
@@ -617,24 +626,33 @@
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];
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard", KB_UL_LOGIN]];
NSURL *scheme =
[NSURL URLWithString:[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:nil];
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进行充值"];
}
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard", KB_UL_RECHARGE]];
NSURL *scheme =
[NSURL URLWithString:[NSString stringWithFormat:@"%@://recharge?src=keyboard", KB_APP_SCHEME]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:@"当前应用不允许键盘直接唤起App请回到桌面手动打开App进行充值"];
});
}];
}
- (void)functionViewDidRequestSubscription:(KBFunctionView *)functionView {

View File

@@ -9,7 +9,7 @@
#import "KBAuthManager.h"
#import "KBFullAccessManager.h"
#import "KBHostAppLauncher.h"
#import "../Utils/KBExtensionAppLauncher.h"
#import "KBKeyboardSubscriptionProduct.h"
#import "KBKeyboardSubscriptionView.h"
@@ -26,11 +26,15 @@
//
// 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];
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard", KB_UL_LOGIN]];
NSURL *scheme =
[NSURL URLWithString:[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:nil];
return;
}
[self kb_setPanelMode:KBKeyboardPanelModeSubscription animated:YES];
@@ -96,10 +100,21 @@
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 *ulString = [NSString stringWithFormat:@"%@?src=keyboard&%@", KB_UL_RECHARGE, query];
NSURL *ul = [NSURL URLWithString:ulString];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:self
source:(self.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")];
});
}];
}
+ (NSString *)kb_urlEncodedString:(NSString *)value {

View File

@@ -2,12 +2,11 @@
// KBFullAccessManager.m
//
// 访
// 1) UIInputViewController hasFullAccess API
// 1) 使 UIInputViewController.hasFullAccess API
// 2) Unknown Denied
//
#import "KBFullAccessManager.h"
#import <objc/message.h>
#if __has_include("KBNetworkManager.h")
#import "KBNetworkManager.h"
#endif
@@ -62,7 +61,10 @@ NSNotificationName const KBFullAccessChangedNotification = @"KBFullAccessChanged
Class guideCls = NSClassFromString(@"KBFullAccessGuideView");
if (guideCls && [guideCls respondsToSelector:NSSelectorFromString(@"showInView:")]) {
SEL sel = NSSelectorFromString(@"showInView:");
((void (*)(id, SEL, UIView *))objc_msgSend)(guideCls, sel, parent);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[guideCls performSelector:sel withObject:parent];
#pragma clang diagnostic pop
}
#endif
return NO;
@@ -74,13 +76,9 @@ NSNotificationName const KBFullAccessChangedNotification = @"KBFullAccessChanged
- (KBFullAccessState)p_detectFullAccessState {
UIInputViewController *ivc = self.ivc;
if (!ivc) return KBFullAccessStateUnknown;
SEL sel = NSSelectorFromString(@"hasFullAccess");
if ([ivc respondsToSelector:sel]) {
BOOL granted = ((BOOL (*)(id, SEL))objc_msgSend)(ivc, sel);
return granted ? KBFullAccessStateGranted : KBFullAccessStateDenied;
if ([ivc respondsToSelector:@selector(hasFullAccess)]) {
return ivc.hasFullAccess ? KBFullAccessStateGranted : KBFullAccessStateDenied;
}
// Unknown
return KBFullAccessStateUnknown;
}

View File

@@ -225,7 +225,10 @@ static NSString * const kKBStreamSplitToken = @"<SPLIT>";
}
if (payload.length > 0) {
if (self.loggingEnabled) {
NSLog(@"[KBStream] SSE raw payload: %@", payload);
#if DEBUG
NSLog(@"[KBStream] SSE raw payload len=%lu",
(unsigned long)(payload ?: @"").length);
#endif
}
NSString *llmText = nil;
if ([self processLLMChunkPayload:payload output:&llmText]) {
@@ -278,7 +281,10 @@ static NSString * const kKBStreamSplitToken = @"<SPLIT>";
}
if (payload.length > 0) {
if (self.loggingEnabled) {
NSLog(@"[KBStream] SSE raw payload: %@", payload);
#if DEBUG
NSLog(@"[KBStream] SSE raw payload len=%lu",
(unsigned long)(payload ?: @"").length);
#endif
}
NSString *delta = nil;
if ((NSInteger)payload.length >= self.deliveredCharCount) {

View File

@@ -6,6 +6,7 @@
//
#import "NetworkStreamHandler.h"
#import <Security/Security.h>
@interface NetworkStreamHandler ()
@@ -243,8 +244,26 @@ didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
// SSL
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
SecTrustRef trust = challenge.protectionSpace.serverTrust;
if (!trust) {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
return;
}
BOOL ok = NO;
if (@available(iOS 13.0, *)) {
ok = SecTrustEvaluateWithError(trust, nil);
} else {
SecTrustResultType result = kSecTrustResultInvalid;
OSStatus status = SecTrustEvaluate(trust, &result);
ok = (status == errSecSuccess) &&
(result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
}
if (ok) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:trust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
} else {
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}

View File

@@ -31,10 +31,18 @@
#define KB_UL_LOGIN KB_UL_BASE @"/login"
#define KB_UL_SETTINGS KB_UL_BASE @"/settings"
// 在扩展内,启用 URL Bridge仅在显式的用户点击动作中使用
// 这样即便宿主 App如备忘录拒绝 extensionContext openURL,仍可通过响应链兜底拉起容器 App。
// 说明:
// - `extensionContext openURL:` 是 Apple 官方推荐方式,但部分宿主 App尤其是“B 类应用”)
// 可能会拦截该调用,导致无法直接唤起容器 App
// - 行业内不少键盘会加“响应链 openURL”兜底来提升唤起成功率但该方式存在上架审核风险。
// 如你要走更稳妥的上架策略:把该宏改为 0仅保留 extensionContext 方案)。
#ifndef KB_URL_BRIDGE_ENABLE
// 上架建议Release 关闭“响应链 openURL”兜底避免审核风险。
#if DEBUG
#define KB_URL_BRIDGE_ENABLE 1
#else
#define KB_URL_BRIDGE_ENABLE 1
#endif
#endif

View File

@@ -2,7 +2,7 @@
// KBExtensionAppLauncher.h
// CustomKeyboard
//
// 封装:在键盘扩展中拉起主 AppScheme / Universal Link + 响应链兜底)。
// 封装:在键盘扩展中拉起主 AppScheme / Universal Link
//
#import <UIKit/UIKit.h>
@@ -12,23 +12,25 @@ NS_ASSUME_NONNULL_BEGIN
@interface KBExtensionAppLauncher : NSObject
/// 通用入口:优先尝试 primaryURL失败后尝试 fallbackURL
/// 两者都失败时再通过响应链openURL:)做兜底
/// 均通过 `extensionContext openURL` 发起跳转(避免使用扩展禁用 API/响应链绕行)
/// 若开启 `KB_URL_BRIDGE_ENABLE=1`,会在两次 `extensionContext openURL` 均失败时,
/// 再尝试一次“响应链 openURL”兜底上架有风险请谨慎开启
/// - Parameters:
/// - primaryURL: 第一优先尝试的 URL可为 Scheme 或 UL
/// - fallbackURL: 失败时的备用 URL可为 nil
/// - ivc: 当前的 UIInputViewController用于 extensionContext openURL
/// - source: 兜底时用作起点的 responder通常传 self 或 self.view
/// - source: 作为响应链兜底的起点(可为 nil
/// - completion: 最终是否“看起来已成功发起”打开动作(不保证一定跳转到 App
+ (void)openPrimaryURL:(NSURL * _Nullable)primaryURL
fallbackURL:(NSURL * _Nullable)fallbackURL
usingInputController:(UIInputViewController *)ivc
source:(UIResponder *)source
source:(UIResponder * _Nullable)source
completion:(void (^ _Nullable)(BOOL success))completion;
/// 简化版:只针对单一 Scheme 做尝试 + 响应链兜底
/// 简化版:只针对单一 Scheme 做尝试。
+ (void)openScheme:(NSURL *)scheme
usingInputController:(UIInputViewController *)ivc
source:(UIResponder *)source
source:(UIResponder * _Nullable)source
completion:(void (^ _Nullable)(BOOL success))completion;
@end

View File

@@ -4,15 +4,86 @@
//
#import "KBExtensionAppLauncher.h"
#if KB_URL_BRIDGE_ENABLE
#import <objc/message.h>
#endif
@implementation KBExtensionAppLauncher
#if KB_URL_BRIDGE_ENABLE
+ (BOOL)kb_openURLViaResponderChain:(NSURL *)url
source:(nullable UIResponder *)source
completion:(void (^ _Nullable)(BOOL success))completion {
if (!url) {
if (completion) { completion(NO); }
return NO;
}
UIResponder *responder = source;
// openURL:options:completionHandler:
// /宿 App
// options options nil
SEL openURLOptionsSel = NSSelectorFromString(@"openURL:options:completionHandler:");
while (responder) {
if ([responder respondsToSelector:openURLOptionsSel]) {
void (*msgSend)(id, SEL, NSURL *, id, void (^)(BOOL)) = (void *)objc_msgSend;
msgSend(responder, openURLOptionsSel, url, nil, ^(BOOL ok) {
if (completion) { completion(ok); }
});
return YES;
}
responder = responder.nextResponder;
}
// openURL:completionHandler:
responder = source;
SEL openURLCompletionSel = NSSelectorFromString(@"openURL:completionHandler:");
while (responder) {
if ([responder respondsToSelector:openURLCompletionSel]) {
void (*msgSend)(id, SEL, NSURL *, void (^)(BOOL)) = (void *)objc_msgSend;
msgSend(responder, openURLCompletionSel, url, ^(BOOL ok) {
if (completion) { completion(ok); }
});
return YES;
}
responder = responder.nextResponder;
}
// openURL:
responder = source;
SEL openURLSel = NSSelectorFromString(@"openURL:");
while (responder) {
if ([responder respondsToSelector:openURLSel]) {
BOOL (*msgSend)(id, SEL, NSURL *) = (void *)objc_msgSend;
BOOL ok = msgSend(responder, openURLSel, url);
if (completion) { completion(ok); }
return YES;
}
responder = responder.nextResponder;
}
if (completion) { completion(NO); }
return NO;
}
#endif
+ (void)openPrimaryURL:(NSURL * _Nullable)primaryURL
fallbackURL:(NSURL * _Nullable)fallbackURL
usingInputController:(UIInputViewController *)ivc
source:(UIResponder *)source
source:(UIResponder * _Nullable)source
completion:(void (^ _Nullable)(BOOL success))completion {
if (![NSThread isMainThread]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self openPrimaryURL:primaryURL
fallbackURL:fallbackURL
usingInputController:ivc
source:source
completion:completion];
});
return;
}
if (!ivc || (!primaryURL && !fallbackURL)) {
if (completion) { completion(NO); }
return;
@@ -48,19 +119,38 @@
finish(YES);
return;
}
BOOL bridged = [self p_bridgeFirst:first second:second from:source];
finish(bridged);
#if KB_URL_BRIDGE_ENABLE
// 宿 App extensionContext openURL
//
UIResponder *start = (source ?: (UIResponder *)ivc.view ?: (UIResponder *)ivc);
[self kb_openURLViaResponderChain:second
source:start
completion:^(BOOL ok3) {
finish(ok3);
}];
#else
finish(NO);
#endif
}];
} else {
BOOL bridged = [self p_bridgeFirst:first second:nil from:source];
finish(bridged);
#if KB_URL_BRIDGE_ENABLE
UIResponder *start = (source ?: (UIResponder *)ivc.view ?: (UIResponder *)ivc);
[self kb_openURLViaResponderChain:first
source:start
completion:^(BOOL ok3) {
finish(ok3);
}];
#else
finish(NO);
#endif
}
}];
}
+ (void)openScheme:(NSURL *)scheme
usingInputController:(UIInputViewController *)ivc
source:(UIResponder *)source
source:(UIResponder * _Nullable)source
completion:(void (^ _Nullable)(BOOL success))completion {
[self openPrimaryURL:scheme
fallbackURL:nil
@@ -69,53 +159,4 @@
completion:completion];
}
#pragma mark - Private
// openURL: KBURLOpenBridge
+ (BOOL)p_openURLViaResponder:(NSURL *)url from:(UIResponder *)start {
#if KB_URL_BRIDGE_ENABLE
if (!url || !start) return NO;
SEL sel = NSSelectorFromString(@"openURL:");
UIResponder *responder = start;
while (responder) {
@try {
if ([responder respondsToSelector:sel]) {
BOOL handled = NO;
BOOL (*funcBool)(id, SEL, NSURL *) = (BOOL (*)(id, SEL, NSURL *))objc_msgSend;
if (funcBool) {
handled = funcBool(responder, sel, url);
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[responder performSelector:sel withObject:url];
handled = YES;
#pragma clang diagnostic pop
}
return handled;
}
} @catch (__unused NSException *e) {
// ignore and continue
}
responder = responder.nextResponder;
}
return NO;
#else
(void)url; (void)start;
return NO;
#endif
}
+ (BOOL)p_bridgeFirst:(NSURL * _Nullable)first
second:(NSURL * _Nullable)second
from:(UIResponder *)source {
BOOL bridged = NO;
if (first) {
bridged = [self p_openURLViaResponder:first from:source];
}
if (!bridged && second) {
bridged = [self p_openURLViaResponder:second from:source];
}
return bridged;
}
@end

View File

@@ -7,7 +7,7 @@
#import "Masonry.h"
#import "KBResponderUtils.h" // UIInputViewController
#import "KBHUD.h"
#import "KBHostAppLauncher.h"
#import "../Utils/KBExtensionAppLauncher.h"
@interface KBFullAccessGuideView ()
@property (nonatomic, strong) UIControl *backdrop;
@@ -159,18 +159,34 @@
// KBResponderUtils.h
// App访宿 UIApplication + Scheme
- (void)onTapGoEnable {
UIInputViewController *ivc = KBFindInputViewController(self);
// responder
UIResponder *start = ivc.view ?: (UIResponder *)self;
// SchemeAppDelegate kbkeyboardAppExtension://settings
NSURL *scheme = [NSURL URLWithString:[NSString stringWithFormat:@"%@@//settings?src=kb_extension", KB_APP_SCHEME]];
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:start];
if (ok) {
[self dismiss];
} else {
NSString *showInfo = [NSString stringWithFormat:KBLocalized(@"Follow: Settings → General → Keyboard → Keyboards → %@ → Allow Full Access"),AppName];
UIInputViewController *ivc = self.ivc ?: KBFindInputViewController(self);
if (!ivc) {
NSString *showInfo = [NSString stringWithFormat:KBLocalized(@"Follow: Settings → General → Keyboard → Keyboards → %@ → Allow Full Access"), AppName];
[KBHUD showInfo:showInfo];
return;
}
// Universal Link 退 Scheme
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=kb_extension", KB_UL_SETTINGS]];
NSURL *scheme = [NSURL URLWithString:[NSString stringWithFormat:@"%@://settings?src=kb_extension", KB_APP_SCHEME]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:ivc
source:(ivc.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) self = weakSelf;
if (!self) {
return;
}
if (success) {
[self dismiss];
} else {
NSString *showInfo = [NSString stringWithFormat:KBLocalized(@"Follow: Settings → General → Keyboard → Keyboards → %@ → Allow Full Access"), AppName];
[KBHUD showInfo:showInfo];
}
});
}];
}
@end

View File

@@ -16,7 +16,7 @@
#import "KBFunctionPasteView.h"
#import "KBFunctionTagCell.h"
#import "KBFunctionTagListView.h"
#import "KBHostAppLauncher.h"
#import "KBExtensionAppLauncher.h"
#import "KBInputBufferManager.h"
#import "KBResponderUtils.h" // UIInputViewController
#import "KBSignUtils.h"
@@ -142,76 +142,26 @@
/// #323232 #D0D3DA
+ (UIColor *)kb_backgroundColor {
if (@available(iOS 13.0, *)) {
return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
UITraitCollection *_Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor colorWithHex:0x2B2B2B];
} else {
return [UIColor colorWithHex:0xD0D3DA];
}
}];
}
return [UIColor colorWithHex:0xD0D3DA];
}
/// Cell #707070 90%
+ (UIColor *)kb_cellBackgroundColor {
if (@available(iOS 13.0, *)) {
return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
UITraitCollection *_Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor colorWithHex:0x707070];
} else {
return [UIColor colorWithWhite:1 alpha:0.9];
}
}];
}
return [UIColor colorWithWhite:1 alpha:0.9];
}
/// Cell #FFFFFF #1B1F1A
+ (UIColor *)kb_cellTextColor {
if (@available(iOS 13.0, *)) {
return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
UITraitCollection *_Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor whiteColor];
} else {
return [UIColor colorWithHex:0x1B1F1A];
}
}];
}
return [UIColor colorWithHex:0x1B1F1A];
}
/// Clear
+ (UIColor *)kb_clearButtonTextColor {
if (@available(iOS 13.0, *)) {
return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
UITraitCollection *_Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor whiteColor];
} else {
return [UIColor blackColor];
}
}];
}
return [UIColor blackColor];
}
/// #707070 #B9BDC8
+ (UIColor *)kb_deleteButtonBackgroundColor {
if (@available(iOS 13.0, *)) {
return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
UITraitCollection *_Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor colorWithHex:0x707070];
} else {
return [UIColor colorWithHex:0xB9BDC8];
}
}];
}
return [UIColor colorWithHex:0xB9BDC8];
}
@@ -505,7 +455,10 @@
if (event.data.length == 0) {
return;
}
NSLog(@"[KBStream] SSE raw payload: %@", event.data);
#if DEBUG
NSLog(@"[KBStream] SSE raw payload len=%lu",
(unsigned long)(event.data ?: @"").length);
#endif
NSData *jsonData = [event.data dataUsingEncoding:NSUTF8StringEncoding];
if (!jsonData) {
return;
@@ -778,12 +731,27 @@
if (!KBAuthManager.shared.isLoggedIn) {
UIInputViewController *ivc = KBFindInputViewController(self);
if (!ivc) {
[KBHUD showInfo:KBLocalized(@"请回到桌面手动打开App登录")];
return;
}
NSString *schemeStr =
[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:ivc.view];
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=keyboard", KB_UL_LOGIN]];
NSURL *scheme =
[NSURL URLWithString:[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]];
__weak typeof(self) weakSelf = self;
[KBExtensionAppLauncher openPrimaryURL:ul
fallbackURL:scheme
usingInputController:ivc
source:(ivc.view ?: (UIResponder *)weakSelf)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD showInfo:KBLocalized(@"请回到桌面手动打开App登录")];
});
}];
return;
// if (!ivc) return;
// NSString *encodedTitle = [title
@@ -881,37 +849,34 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer,
if (!ul)
return;
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
// extensionContext UL
[ivc.extensionContext
openURL:ul
completionHandler:^(BOOL ok) {
if (ok) {
return;
}
// UL 宿 UIApplication + Scheme
NSURL *scheme = [NSURL
URLWithString:
[NSString
stringWithFormat:
@"%@@//login?src=functionView&index=%ld&title=%@",
KB_APP_SCHEME, (long)indexPath.item,
encodedTitle]];
UIResponder *start = ivc.view ?: (UIResponder *)self;
BOOL ok2 = [KBHostAppLauncher openHostAppURL:scheme
fromResponder:start];
if (!ok2) {
// 访宿 Manager
//
dispatch_async(dispatch_get_main_queue(), ^{
[[KBFullAccessManager shared]
ensureFullAccessOrGuideInView:self];
});
}
}];
});
NSURL *scheme = [NSURL
URLWithString:
[NSString stringWithFormat:
@"%@://login?src=functionView&index=%ld&title=%@",
KB_APP_SCHEME, (long)indexPath.item, encodedTitle]];
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) self = weakSelf;
if (!self) {
return;
}
[KBExtensionAppLauncher
openPrimaryURL:ul
fallbackURL:scheme
usingInputController:ivc
source:(ivc.view ?: (UIResponder *)self)
completion:^(BOOL success) {
if (success) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[[KBFullAccessManager shared]
ensureFullAccessOrGuideInView:self];
});
}];
});
}
#pragma mark - Button Actions

View File

@@ -51,8 +51,6 @@
// 基础baseUrl
#ifndef KB_BASE_URL
//#define KB_BASE_URL @"https://m1.apifoxmock.com/m1/5438099-5113192-default/"
//#define KB_BASE_URL @"http://192.168.2.22:7529/api"
#define KB_BASE_URL @"https://devcallback.loveamorkey.com/api"
#endif

View File

@@ -6,11 +6,17 @@
// 从扩展拉起主app
#import <Foundation/Foundation.h>
#import <TargetConditionals.h>
#if __has_include(<UIKit/UIKit.h>)
#import <UIKit/UIKit.h>
#else
@class UIResponder;
#endif
NS_ASSUME_NONNULL_BEGIN
@interface KBHostAppLauncher : NSObject
/// 从某个 responder 出发,尝试通过 UIApplication 打开宿主 app 的 URL
/// 在主 App 内打开 URL。注意在 App Extension 环境下会直接返回 NO扩展内请使用 `extensionContext openURL:`)。
+ (BOOL)openHostAppURL:(NSURL *)url fromResponder:(UIResponder *)startResponder;
@end

View File

@@ -7,46 +7,34 @@
// KBHostAppLauncher.m
#import "KBHostAppLauncher.h"
#import <objc/message.h>
#import <TargetConditionals.h>
#if !TARGET_OS_EXTENSION
#import <UIKit/UIKit.h>
#endif
@implementation KBHostAppLauncher
+ (BOOL)openHostAppURL:(NSURL *)url fromResponder:(UIResponder *)startResponder {
if (!url || !startResponder) return NO;
UIResponder *responder = startResponder;
while (responder) {
if ([responder isKindOfClass:[UIApplication class]]) {
UIApplication *app = (UIApplication *)responder;
#if TARGET_OS_EXTENSION
// App Extension 使 UIApplication
return NO;
#else
// App UIApplication responder
UIApplication *app = UIApplication.sharedApplication;
if (!app) { return NO; }
if (@available(iOS 18.0, *)) {
// iOS 18+ open:options:completionHandler:
SEL sel = @selector(openURL:options:completionHandler:);
if ([app respondsToSelector:sel]) {
// [app openURL:url options:@{} completionHandler:nil];
void (*func)(id, SEL, NSURL *, NSDictionary *, void(^)(BOOL)) = (void *)objc_msgSend;
if (func) {
func(app, sel, url, @{}, nil);
return YES;
}
}
return NO;
} else {
// iOS 17- openURL:
if (@available(iOS 10.0, *)) {
[app openURL:url options:@{} completionHandler:nil];
return YES;
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if ([app respondsToSelector:@selector(openURL:)]) {
return [app openURL:url];
}
return [app openURL:url];
#pragma clang diagnostic pop
return NO;
}
}
responder = responder.nextResponder;
}
return NO;
#endif
}
@end

View File

@@ -6,7 +6,13 @@
#import <Foundation/Foundation.h>
#ifndef KB_MAI_POINT_BASE_URL
#if DEBUG
#define KB_MAI_POINT_BASE_URL @"http://192.168.2.21:35310/api"
#else
/// Release 默认关闭埋点上报(避免内网地址/HTTP 出现在上架包里)。
/// 线上如需开启,请在 Build SettingsPreprocessor Macros中覆盖该宏为 HTTPS 地址。
#define KB_MAI_POINT_BASE_URL @""
#endif
#endif
#ifndef KB_MAI_POINT_PATH_NEW_ACCOUNT

View File

@@ -201,8 +201,19 @@ static void KBMaiPoint_DebugLogError(NSURLResponse *response, NSError *error) {
return;
}
NSString *base = [self kb_trimmedStringOrEmpty:KB_MAI_POINT_BASE_URL];
if (base.length == 0) {
// Release /HTTP
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(YES, nil);
});
}
return;
}
NSString *safePath = [path hasPrefix:@"/"] ? path : [@"/" stringByAppendingString:path];
NSString *urlString = [NSString stringWithFormat:@"%@%@", KB_MAI_POINT_BASE_URL, safePath];
NSString *urlString = [NSString stringWithFormat:@"%@%@", base, safePath];
NSURL *url = [NSURL URLWithString:urlString];
if (!url) {
NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain
@@ -216,6 +227,18 @@ static void KBMaiPoint_DebugLogError(NSURLResponse *response, NSError *error) {
return;
}
#if !DEBUG
// HTTPS HTTP
if (![url.scheme.lowercaseString isEqualToString:@"https"]) {
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(YES, nil);
});
}
return;
}
#endif
NSError *jsonError = nil;
NSData *body = [NSJSONSerialization dataWithJSONObject:parameters options:0 error:&jsonError];
if (jsonError) {

View File

@@ -1,10 +0,0 @@
<?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>logFormatVersion</key>
<integer>11</integer>
<key>logs</key>
<dict/>
</dict>
</plist>

View File

@@ -1,10 +0,0 @@
<?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>logFormatVersion</key>
<integer>11</integer>
<key>logs</key>
<dict/>
</dict>
</plist>

View File

@@ -1,10 +0,0 @@
<?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>logFormatVersion</key>
<integer>11</integer>
<key>logs</key>
<dict/>
</dict>
</plist>

View File

@@ -1,83 +0,0 @@
<?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>logFormatVersion</key>
<integer>11</integer>
<key>logs</key>
<dict>
<key>8E255990-30E2-4820-98AE-0559FF9CB504</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>8E255990-30E2-4820-98AE-0559FF9CB504.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>791987515.57282996</real>
<key>timeStoppedRecording</key>
<real>791987515.86043</real>
<key>title</key>
<string>Resolve Packages</string>
<key>uniqueIdentifier</key>
<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>
</plist>

View File

@@ -1,10 +0,0 @@
<?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>logFormatVersion</key>
<integer>11</integer>
<key>logs</key>
<dict/>
</dict>
</plist>

View File

@@ -53,7 +53,6 @@
043FBCD22EAF97630036AFE1 /* KBPermissionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 04C6EAE12EAF940F0089C901 /* KBPermissionViewController.m */; };
0450AA742EF013D000B6AF06 /* KBEmojiCollectionCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0450AA732EF013D000B6AF06 /* KBEmojiCollectionCell.m */; };
0450AAE22EF03D5100B6AF06 /* KBPerson.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450AAE12EF03D5100B6AF06 /* KBPerson.swift */; };
0450AB682EF0443E00B6AF06 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = 0450AB672EF0443E00B6AF06 /* OrderedCollections */; };
0450AC0A2EF11E4400B6AF06 /* StoreKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450AC082EF11E4400B6AF06 /* StoreKitManager.swift */; };
0450AC0B2EF11E4400B6AF06 /* StoreKitStateConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450ABF82EF11E4400B6AF06 /* StoreKitStateConverter.swift */; };
0450AC0C2EF11E4400B6AF06 /* SubscriptionLocale.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450ABFF2EF11E4400B6AF06 /* SubscriptionLocale.swift */; };
@@ -99,7 +98,6 @@
0477BE002EBC6A330055D639 /* HomeRankVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BDFF2EBC6A330055D639 /* HomeRankVC.m */; };
0477BE042EBC83130055D639 /* HomeMainVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BE032EBC83130055D639 /* HomeMainVC.m */; };
0477BEA22EBCF0000055D639 /* KBTopImageButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BEA12EBCF0000055D639 /* KBTopImageButton.m */; };
04791F8E2ED469C0004E8522 /* KBHostAppLauncher.m in Sources */ = {isa = PBXBuildFile; fileRef = 04791F8D2ED469C0004E8522 /* KBHostAppLauncher.m */; };
04791F8F2ED469C0004E8522 /* KBHostAppLauncher.m in Sources */ = {isa = PBXBuildFile; fileRef = 04791F8D2ED469C0004E8522 /* KBHostAppLauncher.m */; };
04791F922ED48010004E8522 /* KBNoticeVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 04791F912ED48010004E8522 /* KBNoticeVC.m */; };
04791F952ED48028004E8522 /* KBFeedBackVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 04791F942ED48028004E8522 /* KBFeedBackVC.m */; };
@@ -312,6 +310,7 @@
A1B2C9032FBD000100000001 /* KBBackspaceLongPressHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C9022FBD000100000001 /* KBBackspaceLongPressHandler.m */; };
A1B2C9052FBD000200000001 /* KBBackspaceUndoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C9042FBD000200000001 /* KBBackspaceUndoManager.m */; };
A1B2C9092FBD000200000005 /* KBInputBufferManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C9082FBD000200000004 /* KBInputBufferManager.m */; };
A1B2C90C2FBD000200000008 /* KBExtensionAppLauncher.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C90B2FBD000200000007 /* KBExtensionAppLauncher.m */; };
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 */; };
@@ -900,6 +899,8 @@
A1B2C9042FBD000200000001 /* KBBackspaceUndoManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBBackspaceUndoManager.m; sourceTree = "<group>"; };
A1B2C9072FBD000200000003 /* KBInputBufferManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBInputBufferManager.h; sourceTree = "<group>"; };
A1B2C9082FBD000200000004 /* KBInputBufferManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBInputBufferManager.m; sourceTree = "<group>"; };
A1B2C90A2FBD000200000006 /* KBExtensionAppLauncher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBExtensionAppLauncher.h; sourceTree = "<group>"; };
A1B2C90B2FBD000200000007 /* KBExtensionAppLauncher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBExtensionAppLauncher.m; sourceTree = "<group>"; };
A1B2C9202FC9000100000001 /* KBChatMessage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBChatMessage.h; sourceTree = "<group>"; };
A1B2C9212FC9000100000001 /* KBChatMessage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBChatMessage.m; sourceTree = "<group>"; };
A1B2C9222FC9000100000001 /* KBChatMessageCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBChatMessageCell.h; sourceTree = "<group>"; };
@@ -961,7 +962,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
0450AB682EF0443E00B6AF06 /* OrderedCollections in Frameworks */,
ECC9EE02174D86E8D792472F /* Pods_keyBoard.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1335,6 +1335,8 @@
A1B2C9042FBD000200000001 /* KBBackspaceUndoManager.m */,
A1B2C9072FBD000200000003 /* KBInputBufferManager.h */,
A1B2C9082FBD000200000004 /* KBInputBufferManager.m */,
A1B2C90A2FBD000200000006 /* KBExtensionAppLauncher.h */,
A1B2C90B2FBD000200000007 /* KBExtensionAppLauncher.m */,
);
path = Utils;
sourceTree = "<group>";
@@ -2370,7 +2372,6 @@
mainGroup = 727EC74A2EAF848B00B36487;
minimizedProjectReferenceProxies = 1;
packageReferences = (
0450AB662EF043C000B6AF06 /* XCRemoteSwiftPackageReference "swift-collections" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = 727EC7542EAF848B00B36487 /* Products */;
@@ -2558,7 +2559,6 @@
04FEDC122F00010000999999 /* KBKeyboardSubscriptionView.m in Sources */,
04FEDC322F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.m in Sources */,
04FEDC422F00040000999999 /* KBKeyboardSubscriptionFeatureMarqueeView.m in Sources */,
04791F8E2ED469C0004E8522 /* KBHostAppLauncher.m in Sources */,
049FB23B2EC4766700FAB05D /* KBFunctionTagListView.m in Sources */,
049FB23C2EC4766700FAB05D /* KBStreamOverlayView.m in Sources */,
049FB22F2EC34EB900FAB05D /* KBStreamTextView.m in Sources */,
@@ -2566,6 +2566,7 @@
A1B2C9032FBD000100000001 /* KBBackspaceLongPressHandler.m in Sources */,
A1B2C9052FBD000200000001 /* KBBackspaceUndoManager.m in Sources */,
A1B2C9092FBD000200000005 /* KBInputBufferManager.m in Sources */,
A1B2C90C2FBD000200000008 /* KBExtensionAppLauncher.m in Sources */,
049FB23F2EC4B6EF00FAB05D /* KBULBridgeNotification.m in Sources */,
04791F992ED49CE7004E8522 /* KBFont.m in Sources */,
04FC956D2EB054B7007BD342 /* KBKeyboardView.m in Sources */,
@@ -2886,7 +2887,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.loveKey.nyx.CustomKeyboard;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -2920,7 +2921,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.loveKey.nyx.CustomKeyboard;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -3180,24 +3181,6 @@
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
0450AB662EF043C000B6AF06 /* XCRemoteSwiftPackageReference "swift-collections" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-collections.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.3.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
0450AB672EF0443E00B6AF06 /* OrderedCollections */ = {
isa = XCSwiftPackageProductDependency;
package = 0450AB662EF043C000B6AF06 /* XCRemoteSwiftPackageReference "swift-collections" */;
productName = OrderedCollections;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 727EC74B2EAF848B00B36487 /* Project object */;
}

View File

@@ -60,7 +60,7 @@
<RemoteRunnable
runnableDebuggingMode = "0"
BundleIdentifier = "com.loveKey.nyx"
RemotePath = "/var/containers/Bundle/Application/E51DCFA2-A182-4B31-8A45-BCCF663ADCAA/keyBoard.app">
RemotePath = "/var/containers/Bundle/Application/2983EA21-E95E-41B4-BE99-C7ACBD71D2AF/keyBoard.app">
</RemoteRunnable>
<MacroExpansion>
<BuildableReference

View File

@@ -1,15 +0,0 @@
{
"originHash" : "51f90653b2c9f9f7064c0d52159b40bf7d222e5f314be23e62fe28520fec03db",
"pins" : [
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e",
"version" : "1.3.0"
}
}
],
"version" : 3
}

View File

@@ -212,6 +212,23 @@ static NSTimeInterval const kKBSubscriptionPrefillTTL = 10 * 60.0;
[self kb_openAppSettings];
return YES;
}
if ([path hasPrefix:@"/ul/recharge"]) {
// UL App
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(),
(__bridge CFStringRef)KBDarwinULHandled,
NULL, NULL, true);
// Scheme
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
NSURLComponents *schemeComponents = [[NSURLComponents alloc] init];
schemeComponents.scheme = @"kbkeyboardAppExtension";
schemeComponents.host = @"recharge";
schemeComponents.queryItems = components.queryItems;
NSURL *schemeURL = schemeComponents.URL;
if (schemeURL) {
[self application:application openURL:schemeURL options:@{}];
}
return YES;
}
if ([path hasPrefix:@"/ul/login"]) {
// query entry=recharge
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
@@ -227,9 +244,15 @@ static NSTimeInterval const kKBSubscriptionPrefillTTL = 10 * 60.0;
(__bridge CFStringRef)KBDarwinULHandled,
NULL, NULL, true);
if ([entry isEqualToString:@"recharge"]) {
// App
// [KBHUD showInfo:@"去充值"];
// Scheme kbkeyboardAppExtension://recharge?...
NSURLComponents *schemeComponents = [[NSURLComponents alloc] init];
schemeComponents.scheme = @"kbkeyboardAppExtension";
schemeComponents.host = @"recharge";
schemeComponents.queryItems = components.queryItems;
NSURL *schemeURL = schemeComponents.URL;
if (schemeURL) {
[self application:application openURL:schemeURL options:@{}];
}
} else {
//
[self kb_presentLoginSheetIfNeeded];

View File

@@ -6,6 +6,7 @@
#import "KBHUD.h"
#import <MBProgressHUD/MBProgressHUD.h>
#import <UIKit/UIKit.h>
#import <TargetConditionals.h>
#ifndef KBSCREEN
#define KBSCREEN [UIScreen mainScreen].bounds.size
@@ -29,7 +30,7 @@ static __weak UIView *sContainerView = nil; // 缺省承载视图(
/// HUD 宿
+ (UIView *)kb_resolvedHostView {
UIView *hostView = sContainerView;
#ifndef KB_APP_EXTENSION
#if !TARGET_OS_EXTENSION
// App 退 KeyWindow
if (!hostView) {
UIWindow *win = nil;

View File

@@ -10,7 +10,9 @@
#import "KBSkinDetailVC.h"
#import "KBMyHeaderView.h" //
#import "KBMyListCell.h"
#if DEBUG
#import "KBTestVC.h"
#endif
#import "KBNoticeVC.h"
#import "KBFeedBackVC.h"
#import "KBMyVM.h"
@@ -50,7 +52,9 @@
@{ @"title": KBLocalized(@"E-mail"), @"icon": @"my_email_icon", @"color": @(0xFF8A65),@"id":@"4" },
@{ @"title": KBLocalized(@"Agreement"), @"icon": @"my_agreement_icon", @"color": @(0x4CD964),@"id":@"5" },
@{ @"title": KBLocalized(@"Privacy Policy"), @"icon": @"my_privacy_icon", @"color": @(0x5AC8FA),@"id":@"6" },
#if DEBUG
@{ @"title": KBLocalized(@"Test"), @"icon": @"", @"color": @(0x5AC8FA),@"id":@"7" },
#endif
]
];
@@ -223,9 +227,11 @@
}else if ([itemID isEqualToString:@"8"]){
KBConsumptionRecordVC *vc = [[KBConsumptionRecordVC alloc] init];
[self.navigationController pushViewController:vc animated:true];
#if DEBUG
}else if ([itemID isEqualToString:@"7"]){
KBTestVC *vc = [[KBTestVC alloc] init];
[self.navigationController pushViewController:vc animated:true];
#endif
}
}

View File

@@ -10,20 +10,18 @@
// 基础baseUrl
#ifndef KB_BASE_URL
#define KB_BASE_URL @"https://m1.apifoxmock.com/m1/5438099-5113192-default/"
#define KB_BASE_URL @"https://devcallback.loveamorkey.com/api"
#endif
// Universal Links 通用链接
#ifndef KB_UL_BASE
#define KB_UL_BASE @"https://your.domain/ul"
#define KB_UL_BASE @"https://app.tknb.net/ul"
#endif
#define KB_UL_LOGIN KB_UL_BASE @"/login"
#define KB_UL_SETTINGS KB_UL_BASE @"/settings"
// 充值入口的通用链接:当前复用 /login 路径,通过 query 区分(避免额外配置 AASA 路径)
#define KB_UL_RECHARGE KB_UL_LOGIN
#endif /* KBConfig_h */
// 充值入口的通用链接
#define KB_UL_RECHARGE KB_UL_BASE @"/recharge"
// --- 认证/共享钥匙串 配置 ---
// 若已在 Capabilities 中启用 Keychain Sharing并添加访问组
@@ -34,30 +32,9 @@
#define KB_KEYCHAIN_ACCESS_GROUP @"TN6HHV45BB.com.loveKey.nyx.shared"
#endif
// --- 设备特性是否为带刘海机型iPhone X 及以后异形屏)---
// 说明:在 iPhone 12 等机型底部会有 34px 安全区,这里通过安全区来判断是否为“刘海屏”。
// 注意:使用到 UIKit这里自行引入避免依赖 PCH 的包含顺序。
#import <UIKit/UIKit.h>
static inline BOOL KBDeviceHasNotchRuntime(void) {
if (@available(iOS 11.0, *)) {
UIWindow *window = nil;
if (@available(iOS 13.0, *)) {
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
if (scene.activationState == UISceneActivationStateForegroundActive && [scene isKindOfClass:[UIWindowScene class]]) {
UIWindowScene *ws = (UIWindowScene *)scene;
for (UIWindow *w in ws.windows) { if (w.isKeyWindow) { window = w; break; } }
if (window) { break; }
}
}
if (!window) { window = UIApplication.sharedApplication.windows.firstObject; }
} else {
window = UIApplication.sharedApplication.keyWindow;
}
return window.safeAreaInsets.bottom > 0.0;
}
return NO;
}
// 说明:
// - 该头文件同时被主 App 与键盘扩展引用;
// - 设备/窗口相关的 UIKit 辅助方法(如 UIApplication.sharedApplication在扩展中不可用
// 请放到主 App 的前缀头或具体业务代码中,避免引入扩展不允许的 API。
#ifndef KB_DEVICE_HAS_NOTCH
#define KB_DEVICE_HAS_NOTCH (KBDeviceHasNotchRuntime())
#endif
#endif /* KBConfig_h */

View File

@@ -18,8 +18,10 @@
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<false/>
</dict>
<key>NSMicrophoneUsageDescription</key>
<string>需要使用麦克风进行语音输入</string>
<key>UIDesignRequiresCompatibility</key>
<true/>
</dict>

View File

@@ -10,20 +10,18 @@
// 基础baseUrl
#ifndef KB_BASE_URL
#define KB_BASE_URL @"https://m1.apifoxmock.com/m1/5438099-5113192-default/"
#define KB_BASE_URL @"https://devcallback.loveamorkey.com/api"
#endif
// Universal Links 通用链接
#ifndef KB_UL_BASE
#define KB_UL_BASE @"https://your.domain/ul"
#define KB_UL_BASE @"https://app.tknb.net/ul"
#endif
#define KB_UL_LOGIN KB_UL_BASE @"/login"
#define KB_UL_SETTINGS KB_UL_BASE @"/settings"
// 充值入口的通用链接:当前复用 /login 路径,通过 query 区分(避免额外配置 AASA 路径)
#define KB_UL_RECHARGE KB_UL_LOGIN
#endif /* KBConfig_h */
// 充值入口的通用链接
#define KB_UL_RECHARGE KB_UL_BASE @"/recharge"
// --- 认证/共享钥匙串 配置 ---
// 若已在 Capabilities 中启用 Keychain Sharing并添加访问组
@@ -34,30 +32,9 @@
#define KB_KEYCHAIN_ACCESS_GROUP @"TN6HHV45BB.com.loveKey.nyx.shared"
#endif
// --- 设备特性是否为带刘海机型iPhone X 及以后异形屏)---
// 说明:在 iPhone 12 等机型底部会有 34px 安全区,这里通过安全区来判断是否为“刘海屏”。
// 注意:使用到 UIKit这里自行引入避免依赖 PCH 的包含顺序。
#import <UIKit/UIKit.h>
static inline BOOL KBDeviceHasNotchRuntime(void) {
if (@available(iOS 11.0, *)) {
UIWindow *window = nil;
if (@available(iOS 13.0, *)) {
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
if (scene.activationState == UISceneActivationStateForegroundActive && [scene isKindOfClass:[UIWindowScene class]]) {
UIWindowScene *ws = (UIWindowScene *)scene;
for (UIWindow *w in ws.windows) { if (w.isKeyWindow) { window = w; break; } }
if (window) { break; }
}
}
if (!window) { window = UIApplication.sharedApplication.windows.firstObject; }
} else {
window = UIApplication.sharedApplication.keyWindow;
}
return window.safeAreaInsets.bottom > 0.0;
}
return NO;
}
// 说明:
// - 该头文件同时被主 App 与键盘扩展引用;
// - 设备/窗口相关的 UIKit 辅助方法(如 UIApplication.sharedApplication在扩展中不可用
// 请放到主 App 的前缀头或具体业务代码中,避免引入扩展不允许的 API。
#ifndef KB_DEVICE_HAS_NOTCH
#define KB_DEVICE_HAS_NOTCH (KBDeviceHasNotchRuntime())
#endif
#endif /* KBConfig_h */