// // KeyboardViewController.m // CustomKeyboard // // Created by Mac on 2025/10/27. // #import "KeyboardViewController.h" #import "KBKeyBoardMainView.h" #import "KBKey.h" #import "KBFunctionView.h" #import "KBSettingView.h" #import "Masonry.h" #import "KBAuthManager.h" #import "KBFullAccessManager.h" #import "KBSkinManager.h" static CGFloat KEYBOARDHEIGHT = 256 + 20; @interface KeyboardViewController () @property (nonatomic, strong) UIButton *nextKeyboardButton; // 系统“下一个键盘”按钮(可选) @property (nonatomic, strong) KBKeyBoardMainView *keyBoardMainView; // 功能面板视图(点击工具栏第0个时显示) @property (nonatomic, strong) KBFunctionView *functionView; // 功能面板视图(点击工具栏第0个时显示) @property (nonatomic, strong) KBSettingView *settingView; // 设置页 @property (nonatomic, strong) UIImageView *bgImageView; // 背景图(在底层) @end @implementation KeyboardViewController { BOOL _kb_didTriggerLoginDeepLinkOnce; } - (void)viewDidLoad { [super viewDidLoad]; [self setupUI]; // 指定 HUD 的承载视图(扩展里无法取到 App 的 KeyWindow) [KBHUD setContainerView:self.view]; // 绑定完全访问管理器,便于统一感知和联动网络开关 [[KBFullAccessManager shared] bindInputController:self]; __unused id token = [[NSNotificationCenter defaultCenter] addObserverForName:KBFullAccessChangedNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(__unused NSNotification * _Nonnull note) { // 如需,可在此刷新与完全访问相关的 UI }]; // 皮肤变化时,立即应用 __unused id token2 = [[NSNotificationCenter defaultCenter] addObserverForName:KBSkinDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(__unused NSNotification * _Nonnull note) { [self kb_applyTheme]; }]; [self kb_applyTheme]; // NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:AppGroup]; // NSString *value = [sharedDefaults objectForKey:@"TestSharedString"]; // NSLog(@"[Keyboard] 读取到的数据: %@", value); } - (void)setupUI { // 固定键盘整体高度;为减少与系统自调整高度产生的告警,将优先级设置为 High NSLayoutConstraint *h = [self.view.heightAnchor constraintEqualToConstant:KEYBOARDHEIGHT]; h.priority = UILayoutPriorityDefaultHigh; // 750 h.active = YES; // 关闭 UIInputView 自适应(某些系统版本会尝试放大为全屏高度导致冲突) if ([self.view isKindOfClass:[UIInputView class]]) { UIInputView *iv = (UIInputView *)self.view; if ([iv respondsToSelector:@selector(setAllowsSelfSizing:)]) { iv.allowsSelfSizing = NO; } } // 背景图铺底 [self.view addSubview:self.bgImageView]; [self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self.view); }]; // 预置功能面板(默认隐藏),与键盘区域共享相同布局 self.functionView.hidden = YES; [self.view addSubview:self.functionView]; [self.functionView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.equalTo(self.view); make.top.equalTo(self.view).offset(4); make.bottom.equalTo(self.view.mas_bottom).offset(-4); }]; [self.view addSubview:self.keyBoardMainView]; [self.keyBoardMainView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.equalTo(self.view); make.top.equalTo(self.view).offset(4); make.bottom.equalTo(self.view.mas_bottom).offset(-4); }]; } #pragma mark - Private /// 切换显示功能面板/键盘主视图 - (void)showFunctionPanel:(BOOL)show { // 简单显隐切换,复用相同的布局区域 self.functionView.hidden = !show; self.keyBoardMainView.hidden = show; // 可选:把当前显示的视图置顶,避免层级遮挡 if (show) { [self.view bringSubviewToFront:self.functionView]; } else { [self.view bringSubviewToFront:self.keyBoardMainView]; } } /// 显示/隐藏设置页(高度与 keyBoardMainView 一致),右侧滑入/滑出 - (void)showSettingView:(BOOL)show { if (show) { // 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]; // } [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; }]; } } // MARK: - KBKeyBoardMainViewDelegate - (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didTapKey:(KBKey *)key { 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: // 点击自定义键(如“AI”)切换到功能面板 [self showFunctionPanel:YES]; break; case KBKeyTypeModeChange: case KBKeyTypeShift: // 这些已在 KBKeyBoardMainView/KBKeyboardView 内部处理 break; } } - (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didTapToolActionAtIndex:(NSInteger)index { if (index == 0) { [self showFunctionPanel:YES]; } else { [self showFunctionPanel:NO]; } } - (void)keyBoardMainViewDidTapSettings:(KBKeyBoardMainView *)keyBoardMainView { [self showSettingView:YES]; } // MARK: - KBFunctionViewDelegate - (void)functionView:(KBFunctionView *)functionView didTapToolActionAtIndex:(NSInteger)index { // 需求:当 index == 0 时,切回键盘主视图 if (index == 0) { [self showFunctionPanel:NO]; } } #pragma mark - lazy - (KBKeyBoardMainView *)keyBoardMainView{ if (!_keyBoardMainView) { _keyBoardMainView = [[KBKeyBoardMainView alloc] init]; _keyBoardMainView.delegate = self; } return _keyBoardMainView; } - (KBFunctionView *)functionView{ if (!_functionView) { _functionView = [[KBFunctionView alloc] init]; _functionView.delegate = self; // 监听功能面板顶部Bar点击 } return _functionView; } - (KBSettingView *)settingView { if (!_settingView) { _settingView = [[KBSettingView alloc] init]; } return _settingView; } #pragma mark - Actions - (void)onTapSettingsBack { [self showSettingView:NO]; } // 当键盘第一次显示时,尝试唤起主 App 以提示登录(由主 App 决定是否真的弹登录)。 - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; // if (!_kb_didTriggerLoginDeepLinkOnce) { // _kb_didTriggerLoginDeepLinkOnce = YES; // // 仅在未登录时尝试拉起主App登录 // if (!KBAuthManager.shared.isLoggedIn) { // [self kb_tryOpenContainerForLoginIfNeeded]; // } // } } //- (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; // }]; //} #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 } } #pragma mark - Lazy - (UIImageView *)bgImageView { if (!_bgImageView) { _bgImageView = [[UIImageView alloc] init]; _bgImageView.contentMode = UIViewContentModeScaleAspectFill; _bgImageView.clipsToBounds = YES; } return _bgImageView; } @end