处理上架的问题
1:处理了openurl 拉起问题 2:去掉了http 3 隐私等等
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// KBExtensionAppLauncher.h
|
||||
// CustomKeyboard
|
||||
//
|
||||
// 封装:在键盘扩展中拉起主 App(Scheme / Universal Link + 响应链兜底)。
|
||||
// 封装:在键盘扩展中拉起主 App(Scheme / 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
// 自定义 Scheme(AppDelegate 中处理 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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user