Files
keyboard/CustomKeyboard/KeyboardViewController.m

473 lines
18 KiB
Mathematica
Raw Normal View History

2025-10-27 19:42:27 +08:00
//
// KeyboardViewController.m
// CustomKeyboard
//
// Created by Mac on 2025/10/27.
//
#import "KeyboardViewController.h"
2025-10-28 15:10:38 +08:00
#import "KBKeyBoardMainView.h"
2025-10-28 10:18:10 +08:00
#import "KBKey.h"
2025-10-28 15:10:38 +08:00
#import "KBFunctionView.h"
2025-10-28 18:02:10 +08:00
#import "KBSettingView.h"
2025-10-28 15:10:38 +08:00
#import "Masonry.h"
2025-10-31 16:06:54 +08:00
#import "KBAuthManager.h"
2025-11-03 13:25:41 +08:00
#import "KBFullAccessManager.h"
2025-11-04 21:01:46 +08:00
#import "KBSkinManager.h"
2025-11-20 14:27:57 +08:00
#import "KBSkinInstallBridge.h"
#import "KBHostAppLauncher.h"
2025-12-17 16:22:41 +08:00
#import "KBKeyboardSubscriptionView.h"
#import "KBKeyboardSubscriptionProduct.h"
2025-11-20 14:27:57 +08:00
// 使 static kb_consumePendingShopSkin
@interface KeyboardViewController (KBSkinShopBridge)
- (void)kb_consumePendingShopSkin;
@end
2025-10-27 19:42:27 +08:00
2025-11-21 21:50:40 +08:00
// 375 稿
static const CGFloat kKBKeyboardDesignHeight = 250.0f;
2025-10-27 19:42:27 +08:00
2025-11-20 14:27:57 +08:00
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];
}
});
}
2025-12-17 16:22:41 +08:00
@interface KeyboardViewController () <KBKeyBoardMainViewDelegate, KBFunctionViewDelegate, KBKeyboardSubscriptionViewDelegate>
2025-10-28 10:18:10 +08:00
@property (nonatomic, strong) UIButton *nextKeyboardButton; //
2025-10-28 15:10:38 +08:00
@property (nonatomic, strong) KBKeyBoardMainView *keyBoardMainView; // 0
@property (nonatomic, strong) KBFunctionView *functionView; // 0
2025-10-28 18:02:10 +08:00
@property (nonatomic, strong) KBSettingView *settingView; //
2025-11-04 21:01:46 +08:00
@property (nonatomic, strong) UIImageView *bgImageView; //
2025-12-17 16:22:41 +08:00
@property (nonatomic, strong) KBKeyboardSubscriptionView *subscriptionView;
2025-10-27 19:42:27 +08:00
@end
@implementation KeyboardViewController
2025-10-30 20:23:34 +08:00
{
BOOL _kb_didTriggerLoginDeepLinkOnce;
}
2025-10-27 19:42:27 +08:00
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUI];
2025-10-31 15:08:30 +08:00
// HUD App KeyWindow
[KBHUD setContainerView:self.view];
2025-11-03 13:25:41 +08:00
// 访便
[[KBFullAccessManager shared] bindInputController:self];
__unused id token = [[NSNotificationCenter defaultCenter] addObserverForName:KBFullAccessChangedNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(__unused NSNotification * _Nonnull note) {
// 访 UI
}];
2025-11-04 21:01:46 +08:00
//
__unused id token2 = [[NSNotificationCenter defaultCenter] addObserverForName:KBSkinDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(__unused NSNotification * _Nonnull note) {
[self kb_applyTheme];
}];
[self kb_applyTheme];
2025-11-20 14:27:57 +08:00
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
(__bridge const void *)(self),
KBSkinInstallNotificationCallback,
(__bridge CFStringRef)KBDarwinSkinInstallRequestNotification,
NULL,
CFNotificationSuspensionBehaviorDeliverImmediately);
[self kb_consumePendingShopSkin];
2025-11-18 14:41:35 +08:00
2025-10-27 19:42:27 +08:00
}
2025-11-27 15:34:33 +08:00
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[[KBLocalizationManager shared] reloadFromSharedStorageIfNeeded];
}
2025-10-27 19:42:27 +08:00
- (void)setupUI {
self.view.translatesAutoresizingMaskIntoConstraints = NO;
2025-11-21 21:50:40 +08:00
// /
CGFloat keyboardHeight = KBFit(kKBKeyboardDesignHeight);
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
2025-11-21 21:50:40 +08:00
CGFloat outerVerticalInset = KBFit(4.0f);
2025-11-21 20:59:39 +08:00
NSLayoutConstraint *h = [self.view.heightAnchor constraintEqualToConstant:keyboardHeight];
NSLayoutConstraint *w = [self.view.widthAnchor constraintEqualToConstant:screenWidth];
2025-11-21 20:59:39 +08:00
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;
}
}
2025-11-04 21:01:46 +08:00
//
[self.view addSubview:self.bgImageView];
[self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
2025-10-28 15:10:38 +08:00
//
self.functionView.hidden = YES;
[self.view addSubview:self.functionView];
[self.functionView mas_makeConstraints:^(MASConstraintMaker *make) {
2025-10-28 10:18:10 +08:00
make.left.right.equalTo(self.view);
2025-11-26 19:46:23 +08:00
make.top.equalTo(self.view).offset(0);
make.bottom.equalTo(self.view).offset(0);
2025-10-28 10:18:10 +08:00
}];
2025-10-28 15:10:38 +08:00
[self.view addSubview:self.keyBoardMainView];
[self.keyBoardMainView mas_makeConstraints:^(MASConstraintMaker *make) {
2025-10-28 10:18:10 +08:00
make.left.right.equalTo(self.view);
2025-11-21 21:50:40 +08:00
make.top.equalTo(self.view).offset(outerVerticalInset);
make.bottom.equalTo(self.view.mas_bottom).offset(-outerVerticalInset);
2025-10-28 10:18:10 +08:00
}];
2025-10-27 19:42:27 +08:00
}
2025-10-28 10:18:10 +08:00
2025-10-28 15:10:38 +08:00
#pragma mark - Private
2025-10-28 14:30:03 +08:00
2025-10-28 15:10:38 +08:00
/// /
- (void)showFunctionPanel:(BOOL)show {
//
self.functionView.hidden = !show;
self.keyBoardMainView.hidden = show;
2025-12-17 16:22:41 +08:00
if (show) {
[self hideSubscriptionPanel];
}
2025-10-28 15:18:12 +08:00
2025-10-28 15:10:38 +08:00
//
if (show) {
[self.view bringSubviewToFront:self.functionView];
} else {
[self.view bringSubviewToFront:self.keyBoardMainView];
}
2025-10-28 10:18:10 +08:00
}
2025-10-28 18:02:10 +08:00
/// / keyBoardMainView /
- (void)showSettingView:(BOOL)show {
if (show) {
2025-10-30 20:23:34 +08:00
// if (!self.settingView) {
self.settingView = [[KBSettingView alloc] init];
self.settingView.hidden = YES;
[self.view addSubview:self.settingView];
[self.settingView mas_makeConstraints:^(MASConstraintMaker *make) {
//
make.edges.equalTo(self.keyBoardMainView);
}];
[self.settingView.backButton addTarget:self action:@selector(onTapSettingsBack) forControlEvents:UIControlEventTouchUpInside];
// }
2025-10-28 18:02:10 +08:00
[self.view bringSubviewToFront:self.settingView];
// keyBoardMainView self.view
[self.view layoutIfNeeded];
CGFloat w = CGRectGetWidth(self.keyBoardMainView.bounds);
if (w <= 0) { w = CGRectGetWidth(self.view.bounds); }
if (w <= 0) { w = [UIScreen mainScreen].bounds.size.width; }
self.settingView.transform = CGAffineTransformMakeTranslation(w, 0);
self.settingView.hidden = NO;
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.settingView.transform = CGAffineTransformIdentity;
} completion:nil];
} else {
if (!self.settingView || self.settingView.hidden) return;
CGFloat w = CGRectGetWidth(self.keyBoardMainView.bounds);
if (w <= 0) { w = CGRectGetWidth(self.view.bounds); }
if (w <= 0) { w = [UIScreen mainScreen].bounds.size.width; }
[UIView animateWithDuration:0.22 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
self.settingView.transform = CGAffineTransformMakeTranslation(w, 0);
} completion:^(BOOL finished) {
self.settingView.hidden = YES;
}];
}
}
2025-12-17 16:22:41 +08:00
- (void)showSubscriptionPanel {
[self showFunctionPanel:NO];
KBKeyboardSubscriptionView *panel = self.subscriptionView;
if (!panel.superview) {
panel.hidden = YES;
[self.view addSubview:panel];
[panel mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.keyBoardMainView);
}];
}
[self.view bringSubviewToFront:panel];
panel.hidden = NO;
panel.alpha = 0.0;
CGFloat height = CGRectGetHeight(self.view.bounds);
if (height <= 0) { height = 260; }
panel.transform = CGAffineTransformMakeTranslation(0, height);
[panel refreshProductsIfNeeded];
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
panel.alpha = 1.0;
panel.transform = CGAffineTransformIdentity;
} completion:nil];
}
- (void)hideSubscriptionPanel {
if (!self.subscriptionView || self.subscriptionView.hidden) { return; }
CGFloat height = CGRectGetHeight(self.subscriptionView.bounds);
if (height <= 0) { height = CGRectGetHeight(self.view.bounds); }
KBKeyboardSubscriptionView *panel = self.subscriptionView;
[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;
}];
}
2025-10-28 10:18:10 +08:00
2025-10-28 15:10:38 +08:00
// MARK: - KBKeyBoardMainViewDelegate
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didTapKey:(KBKey *)key {
2025-10-28 10:18:10 +08:00
switch (key.type) {
case KBKeyTypeCharacter:
[self.textDocumentProxy insertText:key.output ?: key.title ?: @""]; break;
case KBKeyTypeBackspace:
[self.textDocumentProxy deleteBackward]; break;
case KBKeyTypeSpace:
[self.textDocumentProxy insertText:@" "]; break;
case KBKeyTypeReturn:
[self.textDocumentProxy insertText:@"\n"]; break;
case KBKeyTypeGlobe:
[self advanceToNextInputMode]; break;
case KBKeyTypeCustom:
2025-10-29 12:59:22 +08:00
// AI
[self showFunctionPanel:YES];
break;
2025-10-28 15:10:38 +08:00
case KBKeyTypeModeChange:
2025-10-28 10:18:10 +08:00
case KBKeyTypeShift:
2025-10-28 15:10:38 +08:00
// KBKeyBoardMainView/KBKeyboardView
2025-10-28 10:18:10 +08:00
break;
}
}
2025-10-28 15:10:38 +08:00
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didTapToolActionAtIndex:(NSInteger)index {
2025-11-21 18:26:02 +08:00
if (index != 0) {
2025-10-28 15:10:38 +08:00
[self showFunctionPanel:NO];
2025-11-21 18:26:02 +08:00
return;
2025-10-28 15:10:38 +08:00
}
2025-12-17 16:22:41 +08:00
[self showSubscriptionPanel];
2025-10-28 15:10:38 +08:00
}
2025-10-28 18:02:10 +08:00
- (void)keyBoardMainViewDidTapSettings:(KBKeyBoardMainView *)keyBoardMainView {
[self showSettingView:YES];
}
2025-12-15 13:24:43 +08:00
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didSelectEmoji:(NSString *)emoji {
if (emoji.length == 0) { return; }
[self.textDocumentProxy insertText:emoji];
}
- (void)keyBoardMainViewDidTapEmojiSearch:(KBKeyBoardMainView *)keyBoardMainView {
[KBHUD showInfo:KBLocalized(@"Search coming soon")];
}
2025-10-28 15:18:12 +08:00
// MARK: - KBFunctionViewDelegate
- (void)functionView:(KBFunctionView *)functionView didTapToolActionAtIndex:(NSInteger)index {
// index == 0
if (index == 0) {
[self showFunctionPanel:NO];
}
}
2025-11-26 21:16:56 +08:00
- (void)functionView:(KBFunctionView *_Nullable)functionView didRightTapToolActionAtIndex:(NSInteger)index{
NSString *schemeStr = [NSString stringWithFormat:@"%@://recharge?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
//
// if (!ul && !scheme) { return; }
//
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
if (!ok) {
//
// XXX App /
[KBHUD showInfo:@"请回到桌面手动打开App进行充值"];
}
}
2025-10-28 15:18:12 +08:00
2025-12-17 17:01:49 +08:00
- (void)functionViewDidRequestSubscription:(KBFunctionView *)functionView {
[self showSubscriptionPanel];
}
2025-12-17 16:22:41 +08:00
#pragma mark - KBKeyboardSubscriptionViewDelegate
- (void)subscriptionViewDidTapClose:(KBKeyboardSubscriptionView *)view {
[self hideSubscriptionPanel];
}
- (void)subscriptionView:(KBKeyboardSubscriptionView *)view didTapPurchaseForProduct:(KBKeyboardSubscriptionProduct *)product {
[self hideSubscriptionPanel];
[self kb_openRechargeForProduct:product];
}
2025-10-28 15:10:38 +08:00
#pragma mark - lazy
- (KBKeyBoardMainView *)keyBoardMainView{
if (!_keyBoardMainView) {
_keyBoardMainView = [[KBKeyBoardMainView alloc] init];
2025-10-28 15:18:12 +08:00
_keyBoardMainView.delegate = self;
2025-10-28 15:10:38 +08:00
}
return _keyBoardMainView;
}
- (KBFunctionView *)functionView{
if (!_functionView) {
_functionView = [[KBFunctionView alloc] init];
2025-10-28 15:18:12 +08:00
_functionView.delegate = self; // Bar
2025-10-28 15:10:38 +08:00
}
return _functionView;
}
2025-10-28 18:02:10 +08:00
- (KBSettingView *)settingView {
if (!_settingView) {
_settingView = [[KBSettingView alloc] init];
}
return _settingView;
}
2025-10-28 15:10:38 +08:00
2025-12-17 16:22:41 +08:00
- (KBKeyboardSubscriptionView *)subscriptionView {
if (!_subscriptionView) {
_subscriptionView = [[KBKeyboardSubscriptionView alloc] init];
_subscriptionView.delegate = self;
_subscriptionView.hidden = YES;
_subscriptionView.alpha = 0.0;
}
return _subscriptionView;
}
2025-10-28 15:10:38 +08:00
2025-10-28 18:02:10 +08:00
#pragma mark - Actions
2025-12-17 16:22:41 +08:00
- (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 arrayWithObject:@"autoPay=1"];
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] ?: @"";
}
2025-10-28 18:02:10 +08:00
- (void)onTapSettingsBack {
[self showSettingView:NO];
}
2025-10-28 15:18:12 +08:00
2025-11-20 14:27:57 +08:00
- (void)dealloc {
CFNotificationCenterRemoveObserver(CFNotificationCenterGetDarwinNotifyCenter(),
(__bridge const void *)(self),
(__bridge CFStringRef)KBDarwinSkinInstallRequestNotification,
NULL);
}
2025-10-30 20:23:34 +08:00
// App App
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
2025-11-13 14:11:44 +08:00
// if (!_kb_didTriggerLoginDeepLinkOnce) {
// _kb_didTriggerLoginDeepLinkOnce = YES;
// // App
// if (!KBAuthManager.shared.isLoggedIn) {
// [self kb_tryOpenContainerForLoginIfNeeded];
// }
// }
2025-10-30 20:23:34 +08:00
}
2025-11-13 14:11:44 +08:00
//- (void)kb_tryOpenContainerForLoginIfNeeded {
// // 使 App Scheme
// NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@@//login?src=keyboard", KB_APP_SCHEME]];
// if (!url) return;
// KBWeakSelf
// [self.extensionContext openURL:url completionHandler:^(__unused BOOL success) {
// // 使
// __unused typeof(weakSelf) selfStrong = weakSelf;
// }];
//}
2025-11-04 21:01:46 +08:00
#pragma mark - Theme
- (void)kb_applyTheme {
KBSkinTheme *t = [KBSkinManager shared].current;
UIImage *img = [[KBSkinManager shared] currentBackgroundImage];
self.bgImageView.image = img;
BOOL hasImg = (img != nil);
self.view.backgroundColor = hasImg ? [UIColor clearColor] : t.keyboardBackground;
self.keyBoardMainView.backgroundColor = hasImg ? [UIColor clearColor] : t.keyboardBackground;
//
if ([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
}
if ([self.functionView respondsToSelector:@selector(kb_applyTheme)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.functionView performSelector:@selector(kb_applyTheme)];
#pragma clang diagnostic pop
}
}
2025-11-20 14:27:57 +08:00
- (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(@"皮肤已更新,立即体验吧")];
}];
}
2025-11-04 21:01:46 +08:00
#pragma mark - Lazy
- (UIImageView *)bgImageView {
if (!_bgImageView) {
_bgImageView = [[UIImageView alloc] init];
_bgImageView.contentMode = UIViewContentModeScaleAspectFill;
_bgImageView.clipsToBounds = YES;
}
return _bgImageView;
}
2025-10-28 15:18:12 +08:00
@end