diff --git a/CustomKeyboard/KeyboardViewController.m b/CustomKeyboard/KeyboardViewController.m index e55c7c2..0dbb4f3 100644 --- a/CustomKeyboard/KeyboardViewController.m +++ b/CustomKeyboard/KeyboardViewController.m @@ -8,728 +8,859 @@ #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" -#import "KBSkinInstallBridge.h" -#import "KBHostAppLauncher.h" -#import "KBKeyboardSubscriptionView.h" -#import "KBKeyboardSubscriptionProduct.h" #import "KBBackspaceUndoManager.h" +#import "KBFullAccessManager.h" +#import "KBFunctionView.h" +#import "KBHostAppLauncher.h" #import "KBInputBufferManager.h" +#import "KBKey.h" +#import "KBKeyboardSubscriptionProduct.h" +#import "KBKeyboardSubscriptionView.h" +#import "KBSettingView.h" +#import "KBSkinInstallBridge.h" +#import "KBSkinManager.h" #import "KBSuggestionEngine.h" -//#import "KBLog.h" +#import "Masonry.h" +#import "UIImage+KBColor.h" -// 提前声明一个类别,使编译器在 static 回调中识别 kb_consumePendingShopSkin 方法。 +// #import "KBLog.h" + +// 提前声明一个类别,使编译器在 static 回调中识别 kb_consumePendingShopSkin +// 方法。 @interface KeyboardViewController (KBSkinShopBridge) - (void)kb_consumePendingShopSkin; @end // 以 375 宽设计稿为基准的键盘总高度 static const CGFloat kKBKeyboardBaseHeight = 250.0f; -static NSString * const kKBDefaultSkinIdLight = @"normal_them"; -static NSString * const kKBDefaultSkinZipNameLight = @"normal_them"; -static NSString * const kKBDefaultSkinIdDark = @"normal_hei_them"; -static NSString * const kKBDefaultSkinZipNameDark = @"normal_hei_them"; +static NSString *const kKBDefaultSkinIdLight = @"normal_them"; +static NSString *const kKBDefaultSkinZipNameLight = @"normal_them"; +static NSString *const kKBDefaultSkinIdDark = @"normal_hei_them"; +static NSString *const kKBDefaultSkinZipNameDark = @"normal_hei_them"; static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, - void *observer, - CFStringRef name, + 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]; - } - }); + KeyboardViewController *strongSelf = + (__bridge KeyboardViewController *)observer; + if (!strongSelf) { + return; + } + dispatch_async(dispatch_get_main_queue(), ^{ + if ([strongSelf respondsToSelector:@selector(kb_consumePendingShopSkin)]) { + [strongSelf kb_consumePendingShopSkin]; + } + }); } -@interface KeyboardViewController () -@property (nonatomic, strong) UIButton *nextKeyboardButton; // 系统“下一个键盘”按钮(可选) -@property (nonatomic, strong) UIView *contentView; -@property (nonatomic, strong) KBKeyBoardMainView *keyBoardMainView; // 功能面板视图(点击工具栏第0个时显示) -@property (nonatomic, strong) KBFunctionView *functionView; // 功能面板视图(点击工具栏第0个时显示) -@property (nonatomic, strong) KBSettingView *settingView; // 设置页 -@property (nonatomic, strong) UIImageView *bgImageView; // 背景图(在底层) -@property (nonatomic, strong) KBKeyboardSubscriptionView *subscriptionView; -@property (nonatomic, strong) KBSuggestionEngine *suggestionEngine; -@property (nonatomic, copy) NSString *currentWord; -@property (nonatomic, assign) BOOL suppressSuggestions; -@property (nonatomic, strong) MASConstraint *contentWidthConstraint; -@property (nonatomic, strong) MASConstraint *contentHeightConstraint; -@property (nonatomic, strong) NSLayoutConstraint *kb_heightConstraint; -@property (nonatomic, strong) NSLayoutConstraint *kb_widthConstraint; -@property (nonatomic, assign) CGFloat kb_lastPortraitWidth; -@property (nonatomic, assign) CGFloat kb_lastKeyboardHeight; +@interface KeyboardViewController () +@property(nonatomic, strong) + UIButton *nextKeyboardButton; // 系统“下一个键盘”按钮(可选) +@property(nonatomic, strong) UIView *contentView; +@property(nonatomic, strong) KBKeyBoardMainView + *keyBoardMainView; // 功能面板视图(点击工具栏第0个时显示) +@property(nonatomic, strong) + KBFunctionView *functionView; // 功能面板视图(点击工具栏第0个时显示) +@property(nonatomic, strong) KBSettingView *settingView; // 设置页 +@property(nonatomic, strong) UIImageView *bgImageView; // 背景图(在底层) +@property(nonatomic, strong) KBKeyboardSubscriptionView *subscriptionView; +@property(nonatomic, strong) KBSuggestionEngine *suggestionEngine; +@property(nonatomic, copy) NSString *currentWord; +@property(nonatomic, assign) BOOL suppressSuggestions; +@property(nonatomic, strong) MASConstraint *contentWidthConstraint; +@property(nonatomic, strong) MASConstraint *contentHeightConstraint; +@property(nonatomic, strong) NSLayoutConstraint *kb_heightConstraint; +@property(nonatomic, strong) NSLayoutConstraint *kb_widthConstraint; +@property(nonatomic, assign) CGFloat kb_lastPortraitWidth; +@property(nonatomic, assign) CGFloat kb_lastKeyboardHeight; @end @implementation KeyboardViewController { - BOOL _kb_didTriggerLoginDeepLinkOnce; + BOOL _kb_didTriggerLoginDeepLinkOnce; } - (void)viewDidLoad { - [super viewDidLoad]; - // 撤销删除是“上一段删除操作”的临时状态;键盘被系统回收/重建或跨页面回来时应当清空,避免误显示。 - [[KBBackspaceUndoManager shared] registerNonClearAction]; - [self setupUI]; - self.suggestionEngine = [KBSuggestionEngine shared]; - self.currentWord = @""; - // 指定 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 - }]; + [super viewDidLoad]; + // 撤销删除是“上一段删除操作”的临时状态;键盘被系统回收/重建或跨页面回来时应当清空,避免误显示。 + [[KBBackspaceUndoManager shared] registerNonClearAction]; + [self setupUI]; + self.suggestionEngine = [KBSuggestionEngine shared]; + self.currentWord = @""; + // 指定 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]; - CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), - (__bridge const void *)(self), - KBSkinInstallNotificationCallback, - (__bridge CFStringRef)KBDarwinSkinInstallRequestNotification, - NULL, - CFNotificationSuspensionBehaviorDeliverImmediately); - [self kb_consumePendingShopSkin]; - [self kb_applyDefaultSkinIfNeeded]; - + // 皮肤变化时,立即应用 + __unused id token2 = [[NSNotificationCenter defaultCenter] + addObserverForName:KBSkinDidChangeNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(__unused NSNotification *_Nonnull note) { + [self kb_applyTheme]; + }]; + [self kb_applyTheme]; + CFNotificationCenterAddObserver( + CFNotificationCenterGetDarwinNotifyCenter(), + (__bridge const void *)(self), KBSkinInstallNotificationCallback, + (__bridge CFStringRef)KBDarwinSkinInstallRequestNotification, NULL, + CFNotificationSuspensionBehaviorDeliverImmediately); + [self kb_consumePendingShopSkin]; + [self kb_applyDefaultSkinIfNeeded]; } -- (void)viewWillAppear:(BOOL)animated{ - [super viewWillAppear:animated]; - // 进入/重新进入输入界面时,清理上一次会话残留的撤销状态与缓存,避免显示“撤销删除”但实际上已不可撤销。 - [[KBBackspaceUndoManager shared] registerNonClearAction]; - [[KBInputBufferManager shared] resetWithText:@""]; - [[KBLocalizationManager shared] reloadFromSharedStorageIfNeeded]; - // 注意:微信/QQ 等宿主的 documentContext 可能是“截断窗口”,这里只更新 liveText,不要把它当作全文 manualSnapshot。 - [[KBInputBufferManager shared] updateFromExternalContextBefore:self.textDocumentProxy.documentContextBeforeInput - after:self.textDocumentProxy.documentContextAfterInput]; +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + // 进入/重新进入输入界面时,清理上一次会话残留的撤销状态与缓存,避免显示“撤销删除”但实际上已不可撤销。 + [[KBBackspaceUndoManager shared] registerNonClearAction]; + [[KBInputBufferManager shared] resetWithText:@""]; + [[KBLocalizationManager shared] reloadFromSharedStorageIfNeeded]; + // 注意:微信/QQ 等宿主的 documentContext 可能是“截断窗口”,这里只更新 + // liveText,不要把它当作全文 manualSnapshot。 + [[KBInputBufferManager shared] + updateFromExternalContextBefore:self.textDocumentProxy + .documentContextBeforeInput + after:self.textDocumentProxy + .documentContextAfterInput]; } - (void)viewWillDisappear:(BOOL)animated { - [super viewWillDisappear:animated]; - [[KBBackspaceUndoManager shared] registerNonClearAction]; + [super viewWillDisappear:animated]; + [[KBBackspaceUndoManager shared] registerNonClearAction]; } - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { - [super traitCollectionDidChange:previousTraitCollection]; - if (@available(iOS 13.0, *)) { - if (previousTraitCollection.userInterfaceStyle != self.traitCollection.userInterfaceStyle) { - [self kb_applyDefaultSkinIfNeeded]; - } + [super traitCollectionDidChange:previousTraitCollection]; + if (@available(iOS 13.0, *)) { + if (previousTraitCollection.userInterfaceStyle != + self.traitCollection.userInterfaceStyle) { + [self kb_applyDefaultSkinIfNeeded]; } + } } - (void)textDidChange:(id)textInput { - [super textDidChange:textInput]; - [[KBInputBufferManager shared] updateFromExternalContextBefore:self.textDocumentProxy.documentContextBeforeInput - after:self.textDocumentProxy.documentContextAfterInput]; + [super textDidChange:textInput]; + [[KBInputBufferManager shared] + updateFromExternalContextBefore:self.textDocumentProxy + .documentContextBeforeInput + after:self.textDocumentProxy + .documentContextAfterInput]; } - - (void)setupUI { - self.view.translatesAutoresizingMaskIntoConstraints = NO; - - // 按“短边”宽度等比缩放,横屏保持竖屏布局比例 - CGFloat portraitWidth = [self kb_portraitWidth]; - CGFloat keyboardHeight = [self kb_keyboardHeightForWidth:portraitWidth]; - CGFloat screenWidth = CGRectGetWidth([UIScreen mainScreen].bounds); - CGFloat outerVerticalInset = KBFit(4.0f); + self.view.translatesAutoresizingMaskIntoConstraints = NO; - NSLayoutConstraint *h = [self.view.heightAnchor constraintEqualToConstant:keyboardHeight]; - NSLayoutConstraint *w = [self.view.widthAnchor constraintEqualToConstant:screenWidth]; - self.kb_heightConstraint = h; - self.kb_widthConstraint = w; + // 按“短边”宽度等比缩放,横屏保持竖屏布局比例 + CGFloat portraitWidth = [self kb_portraitWidth]; + CGFloat keyboardHeight = [self kb_keyboardHeightForWidth:portraitWidth]; + CGFloat screenWidth = CGRectGetWidth([UIScreen mainScreen].bounds); + CGFloat outerVerticalInset = KBFit(4.0f); - 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; - } + NSLayoutConstraint *h = + [self.view.heightAnchor constraintEqualToConstant:keyboardHeight]; + NSLayoutConstraint *w = + [self.view.widthAnchor constraintEqualToConstant:screenWidth]; + self.kb_heightConstraint = h; + self.kb_widthConstraint = w; + + h.priority = UILayoutPriorityRequired; + w.priority = UILayoutPriorityRequired; + [NSLayoutConstraint activateConstraints:@[ h, w ]]; + // 关闭 UIInputView 自适应(某些系统版本会尝试放大为全屏高度导致冲突) + if ([self.view isKindOfClass:[UIInputView class]]) { + UIInputView *iv = (UIInputView *)self.view; + if ([iv respondsToSelector:@selector(setAllowsSelfSizing:)]) { + iv.allowsSelfSizing = NO; } - // 内容容器:横屏时保持竖屏宽度,居中显示 - [self.view addSubview:self.contentView]; - [self.contentView mas_makeConstraints:^(MASConstraintMaker *make) { - make.centerX.equalTo(self.view); - make.bottom.equalTo(self.view); - self.contentWidthConstraint = make.width.mas_equalTo(portraitWidth); - self.contentHeightConstraint = make.height.mas_equalTo(keyboardHeight); - }]; + } + // 内容容器:横屏时保持竖屏宽度,居中显示 + [self.view addSubview:self.contentView]; + [self.contentView mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerX.equalTo(self.view); + make.bottom.equalTo(self.view); + self.contentWidthConstraint = make.width.mas_equalTo(portraitWidth); + self.contentHeightConstraint = make.height.mas_equalTo(keyboardHeight); + }]; - // 背景图铺底(仅在内容容器内) - [self.contentView addSubview:self.bgImageView]; - [self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) { - make.edges.equalTo(self.contentView); - }]; - // 预置功能面板(默认隐藏),与键盘区域共享相同布局 - self.functionView.hidden = YES; - [self.contentView addSubview:self.functionView]; - [self.functionView mas_makeConstraints:^(MASConstraintMaker *make) { - make.edges.equalTo(self.contentView); - }]; - - [self.contentView addSubview:self.keyBoardMainView]; - [self.keyBoardMainView mas_makeConstraints:^(MASConstraintMaker *make) { - make.edges.equalTo(self.contentView); - }]; + // 背景图铺底(仅在内容容器内) + [self.contentView addSubview:self.bgImageView]; + [self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self.contentView); + }]; + // 预置功能面板(默认隐藏),与键盘区域共享相同布局 + self.functionView.hidden = YES; + [self.contentView addSubview:self.functionView]; + [self.functionView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self.contentView); + }]; + + [self.contentView addSubview:self.keyBoardMainView]; + [self.keyBoardMainView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self.contentView); + }]; } - - #pragma mark - Private // MARK: - Suggestions - (void)kb_updateCurrentWordWithInsertedText:(NSString *)text { - if (text.length == 0) { return; } - if ([self kb_isAlphabeticString:text]) { - NSString *current = self.currentWord ?: @""; - self.currentWord = [current stringByAppendingString:text]; - self.suppressSuggestions = NO; - [self kb_updateSuggestionsForCurrentWord]; - } else { - [self kb_clearCurrentWord]; - } + if (text.length == 0) { + return; + } + if ([self kb_isAlphabeticString:text]) { + NSString *current = self.currentWord ?: @""; + self.currentWord = [current stringByAppendingString:text]; + self.suppressSuggestions = NO; + [self kb_updateSuggestionsForCurrentWord]; + } else { + [self kb_clearCurrentWord]; + } } - (void)kb_clearCurrentWord { - self.currentWord = @""; - [self.keyBoardMainView kb_setSuggestions:@[]]; - self.suppressSuggestions = NO; + self.currentWord = @""; + [self.keyBoardMainView kb_setSuggestions:@[]]; + self.suppressSuggestions = NO; } - (void)kb_scheduleContextRefreshResetSuppression:(BOOL)resetSuppression { - dispatch_async(dispatch_get_main_queue(), ^{ - [self kb_refreshCurrentWordFromDocumentContextResetSuppression:resetSuppression]; - }); + dispatch_async(dispatch_get_main_queue(), ^{ + [self kb_refreshCurrentWordFromDocumentContextResetSuppression: + resetSuppression]; + }); } -- (void)kb_refreshCurrentWordFromDocumentContextResetSuppression:(BOOL)resetSuppression { - NSString *context = self.textDocumentProxy.documentContextBeforeInput ?: @""; - NSString *word = [self kb_extractTrailingWordFromContext:context]; - self.currentWord = word ?: @""; - if (resetSuppression) { - self.suppressSuggestions = NO; - } - [self kb_updateSuggestionsForCurrentWord]; +- (void)kb_refreshCurrentWordFromDocumentContextResetSuppression: + (BOOL)resetSuppression { + NSString *context = self.textDocumentProxy.documentContextBeforeInput ?: @""; + NSString *word = [self kb_extractTrailingWordFromContext:context]; + self.currentWord = word ?: @""; + if (resetSuppression) { + self.suppressSuggestions = NO; + } + [self kb_updateSuggestionsForCurrentWord]; } - (NSString *)kb_extractTrailingWordFromContext:(NSString *)context { - if (context.length == 0) { return @""; } - static NSCharacterSet *letters = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - letters = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"]; - }); + if (context.length == 0) { + return @""; + } + static NSCharacterSet *letters = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + letters = [NSCharacterSet + characterSetWithCharactersInString: + @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"]; + }); - NSInteger idx = (NSInteger)context.length - 1; - while (idx >= 0) { - unichar ch = [context characterAtIndex:(NSUInteger)idx]; - if (![letters characterIsMember:ch]) { - break; - } - idx -= 1; + NSInteger idx = (NSInteger)context.length - 1; + while (idx >= 0) { + unichar ch = [context characterAtIndex:(NSUInteger)idx]; + if (![letters characterIsMember:ch]) { + break; } - NSUInteger start = (NSUInteger)(idx + 1); - if (start >= context.length) { return @""; } - return [context substringFromIndex:start]; + idx -= 1; + } + NSUInteger start = (NSUInteger)(idx + 1); + if (start >= context.length) { + return @""; + } + return [context substringFromIndex:start]; } - (BOOL)kb_isAlphabeticString:(NSString *)text { - if (text.length == 0) { return NO; } - static NSCharacterSet *letters = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - letters = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"]; - }); - for (NSUInteger i = 0; i < text.length; i++) { - if (![letters characterIsMember:[text characterAtIndex:i]]) { - return NO; - } + if (text.length == 0) { + return NO; + } + static NSCharacterSet *letters = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + letters = [NSCharacterSet + characterSetWithCharactersInString: + @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"]; + }); + for (NSUInteger i = 0; i < text.length; i++) { + if (![letters characterIsMember:[text characterAtIndex:i]]) { + return NO; } - return YES; + } + return YES; } - (void)kb_updateSuggestionsForCurrentWord { - NSString *prefix = self.currentWord ?: @""; - if (prefix.length == 0) { - [self.keyBoardMainView kb_setSuggestions:@[]]; - return; - } - if (self.suppressSuggestions) { - [self.keyBoardMainView kb_setSuggestions:@[]]; - return; - } - NSArray *items = [self.suggestionEngine suggestionsForPrefix:prefix limit:5]; - NSArray *cased = [self kb_applyCaseToSuggestions:items prefix:prefix]; - [self.keyBoardMainView kb_setSuggestions:cased]; + NSString *prefix = self.currentWord ?: @""; + if (prefix.length == 0) { + [self.keyBoardMainView kb_setSuggestions:@[]]; + return; + } + if (self.suppressSuggestions) { + [self.keyBoardMainView kb_setSuggestions:@[]]; + return; + } + NSArray *items = + [self.suggestionEngine suggestionsForPrefix:prefix limit:5]; + NSArray *cased = [self kb_applyCaseToSuggestions:items + prefix:prefix]; + [self.keyBoardMainView kb_setSuggestions:cased]; } -- (NSArray *)kb_applyCaseToSuggestions:(NSArray *)items prefix:(NSString *)prefix { - if (items.count == 0 || prefix.length == 0) { return items; } - BOOL allUpper = [prefix isEqualToString:prefix.uppercaseString]; - BOOL firstUpper = [[prefix substringToIndex:1] isEqualToString:[[prefix substringToIndex:1] uppercaseString]]; +- (NSArray *)kb_applyCaseToSuggestions:(NSArray *)items + prefix:(NSString *)prefix { + if (items.count == 0 || prefix.length == 0) { + return items; + } + BOOL allUpper = [prefix isEqualToString:prefix.uppercaseString]; + BOOL firstUpper = [[prefix substringToIndex:1] + isEqualToString:[[prefix substringToIndex:1] uppercaseString]]; - if (!allUpper && !firstUpper) { return items; } + if (!allUpper && !firstUpper) { + return items; + } - NSMutableArray *result = [NSMutableArray arrayWithCapacity:items.count]; - for (NSString *word in items) { - if (allUpper) { - [result addObject:word.uppercaseString]; - } else { - NSString *first = [[word substringToIndex:1] uppercaseString]; - NSString *rest = (word.length > 1) ? [word substringFromIndex:1] : @""; - [result addObject:[first stringByAppendingString:rest]]; - } + NSMutableArray *result = + [NSMutableArray arrayWithCapacity:items.count]; + for (NSString *word in items) { + if (allUpper) { + [result addObject:word.uppercaseString]; + } else { + NSString *first = [[word substringToIndex:1] uppercaseString]; + NSString *rest = (word.length > 1) ? [word substringFromIndex:1] : @""; + [result addObject:[first stringByAppendingString:rest]]; } - return result.copy; + } + return result.copy; } /// 切换显示功能面板/键盘主视图 - (void)showFunctionPanel:(BOOL)show { - // 简单显隐切换,复用相同的布局区域 - self.functionView.hidden = !show; - self.keyBoardMainView.hidden = show; + // 简单显隐切换,复用相同的布局区域 + self.functionView.hidden = !show; + self.keyBoardMainView.hidden = show; - if (show) { - [[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:@"enter_keyboard_function_panel" - pageId:@"keyboard_function_panel" - extra:nil - completion:nil]; - [self hideSubscriptionPanel]; - } else { - [[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:@"enter_keyboard_main_panel" - pageId:@"keyboard_main_panel" - extra:nil - completion:nil]; - } - - // 可选:把当前显示的视图置顶,避免层级遮挡 - if (show) { - [self.contentView bringSubviewToFront:self.functionView]; - } else { - [self.contentView bringSubviewToFront:self.keyBoardMainView]; - } + if (show) { + [[KBMaiPointReporter sharedReporter] + reportPageExposureWithEventName:@"enter_keyboard_function_panel" + pageId:@"keyboard_function_panel" + extra:nil + completion:nil]; + [self hideSubscriptionPanel]; + } else { + [[KBMaiPointReporter sharedReporter] + reportPageExposureWithEventName:@"enter_keyboard_main_panel" + pageId:@"keyboard_main_panel" + extra:nil + completion:nil]; + } + + // 可选:把当前显示的视图置顶,避免层级遮挡 + if (show) { + [self.contentView bringSubviewToFront:self.functionView]; + } else { + [self.contentView bringSubviewToFront:self.keyBoardMainView]; + } } /// 显示/隐藏设置页(高度与 keyBoardMainView 一致),右侧滑入/滑出 - (void)showSettingView:(BOOL)show { - if (show) { - [[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:@"enter_keyboard_settings" - pageId:@"keyboard_settings" - extra:nil - completion:nil]; - // if (!self.settingView) { - self.settingView = [[KBSettingView alloc] init]; - self.settingView.hidden = YES; - [self.contentView addSubview:self.settingView]; - [self.settingView mas_makeConstraints:^(MASConstraintMaker *make) { - // 与键盘主视图完全等同的区域,保证高度、宽度一致 - make.edges.equalTo(self.contentView); - }]; - [self.settingView.backButton addTarget:self action:@selector(onTapSettingsBack) forControlEvents:UIControlEventTouchUpInside]; - // } - [self.contentView bringSubviewToFront:self.settingView]; - // 以 keyBoardMainView 的实际宽度为准,避免首次添加时 self.view 宽度尚未计算 - [self.contentView layoutIfNeeded]; - CGFloat w = CGRectGetWidth(self.keyBoardMainView.bounds); - if (w <= 0) { w = CGRectGetWidth(self.contentView.bounds); } - if (w <= 0) { w = [self kb_portraitWidth]; } - 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.contentView.bounds); } - if (w <= 0) { w = [self kb_portraitWidth]; } - [UIView animateWithDuration:0.22 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{ - self.settingView.transform = CGAffineTransformMakeTranslation(w, 0); - } completion:^(BOOL finished) { - self.settingView.hidden = YES; - }]; + if (show) { + [[KBMaiPointReporter sharedReporter] + reportPageExposureWithEventName:@"enter_keyboard_settings" + pageId:@"keyboard_settings" + extra:nil + completion:nil]; + // if (!self.settingView) { + self.settingView = [[KBSettingView alloc] init]; + self.settingView.hidden = YES; + [self.contentView addSubview:self.settingView]; + [self.settingView mas_makeConstraints:^(MASConstraintMaker *make) { + // 与键盘主视图完全等同的区域,保证高度、宽度一致 + make.edges.equalTo(self.contentView); + }]; + [self.settingView.backButton addTarget:self + action:@selector(onTapSettingsBack) + forControlEvents:UIControlEventTouchUpInside]; + // } + [self.contentView bringSubviewToFront:self.settingView]; + // 以 keyBoardMainView 的实际宽度为准,避免首次添加时 self.view 宽度尚未计算 + [self.contentView layoutIfNeeded]; + CGFloat w = CGRectGetWidth(self.keyBoardMainView.bounds); + if (w <= 0) { + w = CGRectGetWidth(self.contentView.bounds); } + if (w <= 0) { + w = [self kb_portraitWidth]; + } + 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.contentView.bounds); + } + if (w <= 0) { + w = [self kb_portraitWidth]; + } + [UIView animateWithDuration:0.22 + delay:0 + options:UIViewAnimationOptionCurveEaseIn + animations:^{ + self.settingView.transform = CGAffineTransformMakeTranslation(w, 0); + } + completion:^(BOOL finished) { + self.settingView.hidden = YES; + }]; + } } - (void)showSubscriptionPanel { - // 1) 先判断权限:未开启“完全访问”则走引导逻辑 - if (![[KBFullAccessManager shared] hasFullAccess]) { - // 未开启完全访问:保持原有引导路径 -// [KBHUD showInfo:KBLocalized(@"处理中…")]; - [[KBFullAccessManager shared] ensureFullAccessOrGuideInView:self.view]; - return; - } - // 点击充值要先判断是否登录 - // 2) 权限没问题,再判断是否登录:未登录 -> 直接拉起主 App,由主 App 负责完成登录 - if (!KBAuthManager.shared.isLoggedIn) { - NSString *schemeStr = [NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]; - NSURL *scheme = [NSURL URLWithString:schemeStr]; - // 从当前视图作为起点,通过响应链找到 UIApplication 再调起主 App - BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view]; - return; - } - [[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:@"enter_keyboard_subscription_panel" - pageId:@"keyboard_subscription_panel" - extra:nil - completion:nil]; - [self showFunctionPanel:NO]; - KBKeyboardSubscriptionView *panel = self.subscriptionView; - if (!panel.superview) { - panel.hidden = YES; - [self.contentView addSubview:panel]; - [panel mas_makeConstraints:^(MASConstraintMaker *make) { - make.edges.equalTo(self.contentView); - }]; - } - [self.contentView bringSubviewToFront:panel]; - panel.hidden = NO; - panel.alpha = 0.0; - CGFloat height = CGRectGetHeight(self.contentView.bounds); - if (height <= 0) { height = 260; } - panel.transform = CGAffineTransformMakeTranslation(0, height); - [panel refreshProductsIfNeeded]; - [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ - panel.alpha = 1.0; - panel.transform = CGAffineTransformIdentity; - } completion:nil]; + // 1) 先判断权限:未开启“完全访问”则走引导逻辑 + if (![[KBFullAccessManager shared] hasFullAccess]) { + // 未开启完全访问:保持原有引导路径 + // [KBHUD showInfo:KBLocalized(@"处理中…")]; + [[KBFullAccessManager shared] ensureFullAccessOrGuideInView:self.view]; + return; + } + // 点击充值要先判断是否登录 + // 2) 权限没问题,再判断是否登录:未登录 -> 直接拉起主 App,由主 App + // 负责完成登录 + if (!KBAuthManager.shared.isLoggedIn) { + NSString *schemeStr = + [NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]; + NSURL *scheme = [NSURL URLWithString:schemeStr]; + // 从当前视图作为起点,通过响应链找到 UIApplication 再调起主 App + BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view]; + return; + } + [[KBMaiPointReporter sharedReporter] + reportPageExposureWithEventName:@"enter_keyboard_subscription_panel" + pageId:@"keyboard_subscription_panel" + extra:nil + completion:nil]; + [self showFunctionPanel:NO]; + KBKeyboardSubscriptionView *panel = self.subscriptionView; + if (!panel.superview) { + panel.hidden = YES; + [self.contentView addSubview:panel]; + [panel mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self.contentView); + }]; + } + [self.contentView bringSubviewToFront:panel]; + panel.hidden = NO; + panel.alpha = 0.0; + CGFloat height = CGRectGetHeight(self.contentView.bounds); + if (height <= 0) { + height = 260; + } + panel.transform = CGAffineTransformMakeTranslation(0, height); + [panel refreshProductsIfNeeded]; + [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.contentView.bounds); } - KBKeyboardSubscriptionView *panel = self.subscriptionView; - [UIView animateWithDuration:0.22 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{ + if (!self.subscriptionView || self.subscriptionView.hidden) { + return; + } + CGFloat height = CGRectGetHeight(self.subscriptionView.bounds); + if (height <= 0) { + height = CGRectGetHeight(self.contentView.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) { + } + completion:^(BOOL finished) { panel.hidden = YES; panel.alpha = 1.0; panel.transform = CGAffineTransformIdentity; - }]; + }]; } - - // MARK: - KBKeyBoardMainViewDelegate -- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didTapKey:(KBKey *)key { - switch (key.type) { - case KBKeyTypeCharacter: { - [[KBBackspaceUndoManager shared] registerNonClearAction]; - NSString *text = key.output ?: key.title ?: @""; - [self.textDocumentProxy insertText:text]; - [self kb_updateCurrentWordWithInsertedText:text]; - [[KBInputBufferManager shared] appendText:text]; - } break; - case KBKeyTypeBackspace: - [[KBInputBufferManager shared] refreshFromProxyIfPossible:self.textDocumentProxy]; - [[KBInputBufferManager shared] prepareSnapshotForDeleteWithContextBefore:self.textDocumentProxy.documentContextBeforeInput - after:self.textDocumentProxy.documentContextAfterInput]; - [[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:self.textDocumentProxy count:1]; - [self kb_scheduleContextRefreshResetSuppression:NO]; - [[KBInputBufferManager shared] applyHoldDeleteCount:1]; - break; - case KBKeyTypeSpace: - [[KBBackspaceUndoManager shared] registerNonClearAction]; - [self.textDocumentProxy insertText:@" "]; - [self kb_clearCurrentWord]; - [[KBInputBufferManager shared] appendText:@" "]; - break; - case KBKeyTypeReturn: - [[KBBackspaceUndoManager shared] registerNonClearAction]; - [self.textDocumentProxy insertText:@"\n"]; - [self kb_clearCurrentWord]; - [[KBInputBufferManager shared] appendText:@"\n"]; - break; - case KBKeyTypeGlobe: - [self advanceToNextInputMode]; break; - case KBKeyTypeCustom: - [[KBBackspaceUndoManager shared] registerNonClearAction]; - // 点击自定义键切换到功能面板 - [self showFunctionPanel:YES]; - [self kb_clearCurrentWord]; - break; - case KBKeyTypeModeChange: - case KBKeyTypeShift: - // 这些已在 KBKeyBoardMainView/KBKeyboardView 内部处理 - break; - } +- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView + didTapKey:(KBKey *)key { + switch (key.type) { + case KBKeyTypeCharacter: { + [[KBBackspaceUndoManager shared] registerNonClearAction]; + NSString *text = key.output ?: key.title ?: @""; + [self.textDocumentProxy insertText:text]; + [self kb_updateCurrentWordWithInsertedText:text]; + [[KBInputBufferManager shared] appendText:text]; + } break; + case KBKeyTypeBackspace: + [[KBInputBufferManager shared] + refreshFromProxyIfPossible:self.textDocumentProxy]; + [[KBInputBufferManager shared] + prepareSnapshotForDeleteWithContextBefore: + self.textDocumentProxy.documentContextBeforeInput + after: + self.textDocumentProxy + .documentContextAfterInput]; + [[KBBackspaceUndoManager shared] + captureAndDeleteBackwardFromProxy:self.textDocumentProxy + count:1]; + [self kb_scheduleContextRefreshResetSuppression:NO]; + [[KBInputBufferManager shared] applyHoldDeleteCount:1]; + break; + case KBKeyTypeSpace: + [[KBBackspaceUndoManager shared] registerNonClearAction]; + [self.textDocumentProxy insertText:@" "]; + [self kb_clearCurrentWord]; + [[KBInputBufferManager shared] appendText:@" "]; + break; + case KBKeyTypeReturn: + [[KBBackspaceUndoManager shared] registerNonClearAction]; + [self.textDocumentProxy insertText:@"\n"]; + [self kb_clearCurrentWord]; + [[KBInputBufferManager shared] appendText:@"\n"]; + break; + case KBKeyTypeGlobe: + [self advanceToNextInputMode]; + break; + case KBKeyTypeCustom: + [[KBBackspaceUndoManager shared] registerNonClearAction]; + // 点击自定义键切换到功能面板 + [self showFunctionPanel:YES]; + [self kb_clearCurrentWord]; + break; + case KBKeyTypeModeChange: + case KBKeyTypeShift: + // 这些已在 KBKeyBoardMainView/KBKeyboardView 内部处理 + break; + } } -- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didTapToolActionAtIndex:(NSInteger)index { - NSDictionary *extra = @{@"index": @(index)}; - [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_toolbar_action" - pageId:@"keyboard_main_panel" - elementId:@"toolbar_action" - extra:extra - completion:nil]; - if (index == 0) { - [self showFunctionPanel:YES]; - [self kb_clearCurrentWord]; - return; - } - [self showFunctionPanel:NO]; +- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView + didTapToolActionAtIndex:(NSInteger)index { + NSDictionary *extra = @{@"index" : @(index)}; + [[KBMaiPointReporter sharedReporter] + reportClickWithEventName:@"click_keyboard_toolbar_action" + pageId:@"keyboard_main_panel" + elementId:@"toolbar_action" + extra:extra + completion:nil]; + if (index == 0) { + [self showFunctionPanel:YES]; + [self kb_clearCurrentWord]; + return; + } + [self showFunctionPanel:NO]; } - (void)keyBoardMainViewDidTapSettings:(KBKeyBoardMainView *)keyBoardMainView { - [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_settings_btn" - pageId:@"keyboard_main_panel" - elementId:@"settings_btn" - extra:nil - completion:nil]; - [self showSettingView:YES]; + [[KBMaiPointReporter sharedReporter] + reportClickWithEventName:@"click_keyboard_settings_btn" + pageId:@"keyboard_main_panel" + elementId:@"settings_btn" + extra:nil + completion:nil]; + [self showSettingView:YES]; } -- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didSelectEmoji:(NSString *)emoji { - if (emoji.length == 0) { return; } - [[KBBackspaceUndoManager shared] registerNonClearAction]; - [self.textDocumentProxy insertText:emoji]; - [self kb_clearCurrentWord]; - [[KBInputBufferManager shared] appendText:emoji]; +- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView + didSelectEmoji:(NSString *)emoji { + if (emoji.length == 0) { + return; + } + [[KBBackspaceUndoManager shared] registerNonClearAction]; + [self.textDocumentProxy insertText:emoji]; + [self kb_clearCurrentWord]; + [[KBInputBufferManager shared] appendText:emoji]; } - (void)keyBoardMainViewDidTapUndo:(KBKeyBoardMainView *)keyBoardMainView { - [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_undo_btn" - pageId:@"keyboard_main_panel" - elementId:@"undo_btn" - extra:nil - completion:nil]; - [[KBBackspaceUndoManager shared] performUndoFromResponder:self.view]; - [self kb_scheduleContextRefreshResetSuppression:YES]; + [[KBMaiPointReporter sharedReporter] + reportClickWithEventName:@"click_keyboard_undo_btn" + pageId:@"keyboard_main_panel" + elementId:@"undo_btn" + extra:nil + completion:nil]; + [[KBBackspaceUndoManager shared] performUndoFromResponder:self.view]; + [self kb_scheduleContextRefreshResetSuppression:YES]; } -- (void)keyBoardMainViewDidTapEmojiSearch:(KBKeyBoardMainView *)keyBoardMainView { - [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_emoji_search_btn" - pageId:@"keyboard_main_panel" - elementId:@"emoji_search_btn" - extra:nil - completion:nil]; - [KBHUD showInfo:KBLocalized(@"Search coming soon")]; +- (void)keyBoardMainViewDidTapEmojiSearch: + (KBKeyBoardMainView *)keyBoardMainView { + [[KBMaiPointReporter sharedReporter] + reportClickWithEventName:@"click_keyboard_emoji_search_btn" + pageId:@"keyboard_main_panel" + elementId:@"emoji_search_btn" + extra:nil + completion:nil]; + [KBHUD showInfo:KBLocalized(@"Search coming soon")]; } -- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didSelectSuggestion:(NSString *)suggestion { - if (suggestion.length == 0) { return; } - NSDictionary *extra = @{@"suggestion_len": @(suggestion.length)}; - [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_suggestion_item" - pageId:@"keyboard_main_panel" - elementId:@"suggestion_item" - extra:extra - completion:nil]; - [[KBBackspaceUndoManager shared] registerNonClearAction]; - NSString *current = self.currentWord ?: @""; - if (current.length > 0) { - for (NSUInteger i = 0; i < current.length; i++) { - [self.textDocumentProxy deleteBackward]; - } +- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView + didSelectSuggestion:(NSString *)suggestion { + if (suggestion.length == 0) { + return; + } + NSDictionary *extra = @{@"suggestion_len" : @(suggestion.length)}; + [[KBMaiPointReporter sharedReporter] + reportClickWithEventName:@"click_keyboard_suggestion_item" + pageId:@"keyboard_main_panel" + elementId:@"suggestion_item" + extra:extra + completion:nil]; + [[KBBackspaceUndoManager shared] registerNonClearAction]; + NSString *current = self.currentWord ?: @""; + if (current.length > 0) { + for (NSUInteger i = 0; i < current.length; i++) { + [self.textDocumentProxy deleteBackward]; } - [self.textDocumentProxy insertText:suggestion]; - self.currentWord = suggestion; - [self.suggestionEngine recordSelection:suggestion]; - self.suppressSuggestions = YES; - [self.keyBoardMainView kb_setSuggestions:@[]]; - [[KBInputBufferManager shared] replaceTailWithText:suggestion deleteCount:current.length]; + } + [self.textDocumentProxy insertText:suggestion]; + self.currentWord = suggestion; + [self.suggestionEngine recordSelection:suggestion]; + self.suppressSuggestions = YES; + [self.keyBoardMainView kb_setSuggestions:@[]]; + [[KBInputBufferManager shared] replaceTailWithText:suggestion + deleteCount:current.length]; } // MARK: - KBFunctionViewDelegate -- (void)functionView:(KBFunctionView *)functionView didTapToolActionAtIndex:(NSInteger)index { - // 需求:当 index == 0 时,切回键盘主视图 - if (index == 0) { - [self showFunctionPanel:NO]; - } +- (void)functionView:(KBFunctionView *)functionView + didTapToolActionAtIndex:(NSInteger)index { + // 需求:当 index == 0 时,切回键盘主视图 + if (index == 0) { + [self showFunctionPanel:NO]; + } } -- (void)functionView:(KBFunctionView *_Nullable)functionView didRightTapToolActionAtIndex:(NSInteger)index{ - [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_right_action" - pageId:@"keyboard_function_panel" - elementId:@"right_action" - extra:@{@"action": @"login_or_recharge"} - completion:nil]; - if (!KBAuthManager.shared.isLoggedIn) { - NSString *schemeStr = [NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]; - NSURL *scheme = [NSURL URLWithString:schemeStr]; - // 从当前视图作为起点,通过响应链找到 UIApplication 再调起主 App - BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view]; - return; - } - NSString *schemeStr = [NSString stringWithFormat:@"%@://recharge?src=keyboard", KB_APP_SCHEME]; +- (void)functionView:(KBFunctionView *_Nullable)functionView + didRightTapToolActionAtIndex:(NSInteger)index { + [[KBMaiPointReporter sharedReporter] + reportClickWithEventName:@"click_keyboard_function_right_action" + pageId:@"keyboard_function_panel" + elementId:@"right_action" + extra:@{@"action" : @"login_or_recharge"} + completion:nil]; + if (!KBAuthManager.shared.isLoggedIn) { + NSString *schemeStr = + [NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]; NSURL *scheme = [NSURL URLWithString:schemeStr]; -// -// if (!ul && !scheme) { return; } -// // 从当前视图作为起点,通过响应链找到 UIApplication 再调起主 App BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view]; + return; + } + NSString *schemeStr = + [NSString stringWithFormat:@"%@://recharge?src=keyboard", KB_APP_SCHEME]; + NSURL *scheme = [NSURL URLWithString:schemeStr]; + // + // if (!ul && !scheme) { return; } + // + // 从当前视图作为起点,通过响应链找到 UIApplication 再调起主 App + BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view]; - if (!ok) { - // 失败兜底:给个文案提示 - // 比如:请回到桌面手动打开 XXX App 进行设置/充值 - [KBHUD showInfo:@"请回到桌面手动打开App进行充值"]; - } + if (!ok) { + // 失败兜底:给个文案提示 + // 比如:请回到桌面手动打开 XXX App 进行设置/充值 + [KBHUD showInfo:@"请回到桌面手动打开App进行充值"]; + } } - (void)functionViewDidRequestSubscription:(KBFunctionView *)functionView { - [self showSubscriptionPanel]; + [self showSubscriptionPanel]; } #pragma mark - KBKeyboardSubscriptionViewDelegate - (void)subscriptionViewDidTapClose:(KBKeyboardSubscriptionView *)view { - [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_subscription_close_btn" - pageId:@"keyboard_subscription_panel" - elementId:@"close_btn" - extra:nil - completion:nil]; - [self hideSubscriptionPanel]; + [[KBMaiPointReporter sharedReporter] + reportClickWithEventName:@"click_keyboard_subscription_close_btn" + pageId:@"keyboard_subscription_panel" + elementId:@"close_btn" + extra:nil + completion:nil]; + [self hideSubscriptionPanel]; } -- (void)subscriptionView:(KBKeyboardSubscriptionView *)view didTapPurchaseForProduct:(KBKeyboardSubscriptionProduct *)product { - NSMutableDictionary *extra = [NSMutableDictionary dictionary]; - if ([product.productId isKindOfClass:NSString.class] && product.productId.length > 0) { - extra[@"product_id"] = product.productId; - } - [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_subscription_product_btn" - pageId:@"keyboard_subscription_panel" - elementId:@"product_btn" - extra:extra.copy - completion:nil]; - [self hideSubscriptionPanel]; - [self kb_openRechargeForProduct:product]; +- (void)subscriptionView:(KBKeyboardSubscriptionView *)view + didTapPurchaseForProduct:(KBKeyboardSubscriptionProduct *)product { + NSMutableDictionary *extra = [NSMutableDictionary dictionary]; + if ([product.productId isKindOfClass:NSString.class] && + product.productId.length > 0) { + extra[@"product_id"] = product.productId; + } + [[KBMaiPointReporter sharedReporter] + reportClickWithEventName:@"click_keyboard_subscription_product_btn" + pageId:@"keyboard_subscription_panel" + elementId:@"product_btn" + extra:extra.copy + completion:nil]; + [self hideSubscriptionPanel]; + [self kb_openRechargeForProduct:product]; } #pragma mark - lazy -- (KBKeyBoardMainView *)keyBoardMainView{ - if (!_keyBoardMainView) { - _keyBoardMainView = [[KBKeyBoardMainView alloc] init]; - _keyBoardMainView.delegate = self; - } - return _keyBoardMainView; +- (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; +- (KBFunctionView *)functionView { + if (!_functionView) { + _functionView = [[KBFunctionView alloc] init]; + _functionView.delegate = self; // 监听功能面板顶部Bar点击 + } + return _functionView; } - (KBSettingView *)settingView { - if (!_settingView) { - _settingView = [[KBSettingView alloc] init]; - } - return _settingView; + if (!_settingView) { + _settingView = [[KBSettingView alloc] init]; + } + return _settingView; } - (KBKeyboardSubscriptionView *)subscriptionView { - if (!_subscriptionView) { - _subscriptionView = [[KBKeyboardSubscriptionView alloc] init]; - _subscriptionView.delegate = self; - _subscriptionView.hidden = YES; - _subscriptionView.alpha = 0.0; - } - return _subscriptionView; + if (!_subscriptionView) { + _subscriptionView = [[KBKeyboardSubscriptionView alloc] init]; + _subscriptionView.delegate = self; + _subscriptionView.hidden = YES; + _subscriptionView.alpha = 0.0; + } + return _subscriptionView; } - #pragma mark - Actions - (void)kb_openRechargeForProduct:(KBKeyboardSubscriptionProduct *)product { - if (![product isKindOfClass:KBKeyboardSubscriptionProduct.class] || product.productId.length == 0) { - [KBHUD showInfo:KBLocalized(@"Product unavailable")]; - return; - } - NSString *encodedId = [self.class kb_urlEncodedString:product.productId]; - NSString *title = [product displayTitle]; - NSString *encodedTitle = [self.class kb_urlEncodedString:title]; - NSMutableArray *params = [NSMutableArray arrayWithObjects:@"autoPay=1", @"prefill=1", nil]; - if (encodedId.length) { - [params addObject:[NSString stringWithFormat:@"productId=%@", encodedId]]; - } - if (encodedTitle.length) { - [params addObject:[NSString stringWithFormat:@"productTitle=%@", encodedTitle]]; - } - NSString *query = [params componentsJoinedByString:@"&"]; - NSString *urlString = [NSString stringWithFormat:@"%@://recharge?src=keyboard&%@", KB_APP_SCHEME, query]; - NSURL *scheme = [NSURL URLWithString:urlString]; - BOOL success = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view]; - if (!success) { - [KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")]; - } + 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 *params = + [NSMutableArray arrayWithObjects:@"autoPay=1", @"prefill=1", nil]; + if (encodedId.length) { + [params addObject:[NSString stringWithFormat:@"productId=%@", encodedId]]; + } + if (encodedTitle.length) { + [params + addObject:[NSString stringWithFormat:@"productTitle=%@", encodedTitle]]; + } + NSString *query = [params componentsJoinedByString:@"&"]; + NSString *urlString = [NSString + stringWithFormat:@"%@://recharge?src=keyboard&%@", KB_APP_SCHEME, query]; + NSURL *scheme = [NSURL URLWithString:urlString]; + BOOL success = [KBHostAppLauncher openHostAppURL:scheme + fromResponder:self.view]; + if (!success) { + [KBHUD showInfo:KBLocalized(@"Please open the App to finish purchase")]; + } } + (NSString *)kb_urlEncodedString:(NSString *)value { - if (value.length == 0) { return @""; } - NSString *reserved = @"!*'();:@&=+$,/?%#[]"; - NSMutableCharacterSet *allowed = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; - [allowed removeCharactersInString:reserved]; - return [value stringByAddingPercentEncodingWithAllowedCharacters:allowed] ?: @""; + if (value.length == 0) { + return @""; + } + NSString *reserved = @"!*'();:@&=+$,/?%#[]"; + NSMutableCharacterSet *allowed = + [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; + [allowed removeCharactersInString:reserved]; + return [value stringByAddingPercentEncodingWithAllowedCharacters:allowed] + ?: @""; } - (void)onTapSettingsBack { - [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_settings_back_btn" - pageId:@"keyboard_settings" - elementId:@"back_btn" - extra:nil - completion:nil]; - [self showSettingView:NO]; + [[KBMaiPointReporter sharedReporter] + reportClickWithEventName:@"click_keyboard_settings_back_btn" + pageId:@"keyboard_settings" + elementId:@"back_btn" + extra:nil + completion:nil]; + [self showSettingView:NO]; } - (void)dealloc { - CFNotificationCenterRemoveObserver(CFNotificationCenterGetDarwinNotifyCenter(), - (__bridge const void *)(self), - (__bridge CFStringRef)KBDarwinSkinInstallRequestNotification, - NULL); + CFNotificationCenterRemoveObserver( + CFNotificationCenterGetDarwinNotifyCenter(), + (__bridge const void *)(self), + (__bridge CFStringRef)KBDarwinSkinInstallRequestNotification, NULL); } - -// 当键盘第一次显示时,尝试唤起主 App 以提示登录(由主 App 决定是否真的弹登录)。 +// 当键盘第一次显示时,尝试唤起主 App 以提示登录(由主 App +// 决定是否真的弹登录)。 - (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; -// if (!_kb_didTriggerLoginDeepLinkOnce) { -// _kb_didTriggerLoginDeepLinkOnce = YES; -// // 仅在未登录时尝试拉起主App登录 -// if (!KBAuthManager.shared.isLoggedIn) { -// [self kb_tryOpenContainerForLoginIfNeeded]; -// } -// } + [super viewDidAppear:animated]; + // if (!_kb_didTriggerLoginDeepLinkOnce) { + // _kb_didTriggerLoginDeepLinkOnce = YES; + // // 仅在未登录时尝试拉起主App登录 + // if (!KBAuthManager.shared.isLoggedIn) { + // [self kb_tryOpenContainerForLoginIfNeeded]; + // } + // } } - (void)viewDidLayoutSubviews { - [super viewDidLayoutSubviews]; - [self kb_updateKeyboardLayoutIfNeeded]; + [super viewDidLayoutSubviews]; + [self kb_updateKeyboardLayoutIfNeeded]; } -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { - [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; - __weak typeof(self) weakSelf = self; - [coordinator animateAlongsideTransition:^(id _Nonnull context) { +- (void)viewWillTransitionToSize:(CGSize)size + withTransitionCoordinator: + (id)coordinator { + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + __weak typeof(self) weakSelf = self; + [coordinator + animateAlongsideTransition:^( + id _Nonnull context) { [weakSelf kb_updateKeyboardLayoutIfNeeded]; - } completion:^(__unused id _Nonnull context) { + } + completion:^( + __unused id< + UIViewControllerTransitionCoordinatorContext> _Nonnull context) { [weakSelf kb_updateKeyboardLayoutIfNeeded]; - }]; + }]; } //- (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) { +// 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; // }]; @@ -738,242 +869,342 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, #pragma mark - Theme - (void)kb_applyTheme { - KBSkinTheme *t = [KBSkinManager shared].current; - UIImage *img = [[KBSkinManager shared] currentBackgroundImage]; - if ([self kb_isDefaultKeyboardTheme:t]) { - CGSize size = self.bgImageView.bounds.size; - if (size.width <= 0 || size.height <= 0) { - [self.view layoutIfNeeded]; - size = self.bgImageView.bounds.size; - } - if (size.width <= 0 || size.height <= 0) { - size = self.view.bounds.size; - } - if (size.width <= 0 || size.height <= 0) { - size = [UIScreen mainScreen].bounds.size; - } - img = [self kb_defaultGradientImageWithSize:size]; + KBSkinTheme *t = [KBSkinManager shared].current; + UIImage *img = [[KBSkinManager shared] currentBackgroundImage]; + BOOL isDefaultTheme = [self kb_isDefaultKeyboardTheme:t]; + BOOL isDarkMode = [self kb_isDarkModeActive]; + CGSize size = self.bgImageView.bounds.size; + if (isDefaultTheme) { + if (isDarkMode) { + // 暗黑模式:直接使用背景色,不使用图片渲染 + // 这样可以避免图片渲染时的色彩空间转换导致颜色不一致 + img = nil; + self.bgImageView.image = nil; + // 使用与系统键盘底部完全相同的颜色 + if (@available(iOS 13.0, *)) { + // iOS 系统键盘使用的实际颜色 (RGB: 44, 44, 46 in sRGB, 或 #2C2C2E) + // 但为了完美匹配,我们使用动态颜色并直接设置为背景 + UIColor *kbBgColor = + [UIColor colorWithDynamicProvider:^UIColor *_Nonnull( + UITraitCollection *_Nonnull traitCollection) { + if (traitCollection.userInterfaceStyle == + UIUserInterfaceStyleDark) { + // 暗黑模式下系统键盘实际背景色 + return [UIColor colorWithRed:43.0 / 255.0 + green:43.0 / 255.0 + blue:43.0 / 255.0 + alpha:1.0]; + } else { + return [UIColor colorWithRed:209.0 / 255.0 + green:211.0 / 255.0 + blue:219.0 / 255.0 + alpha:1.0]; + } + }]; + self.contentView.backgroundColor = kbBgColor; + self.bgImageView.backgroundColor = kbBgColor; + } else { + UIColor *darkColor = [UIColor colorWithRed:43.0 / 255.0 + green:43.0 / 255.0 + blue:43.0 / 255.0 + alpha:1.0]; + self.contentView.backgroundColor = darkColor; + self.bgImageView.backgroundColor = darkColor; + } + } else { + // 浅色模式:使用渐变图片 + if (size.width <= 0 || size.height <= 0) { + [self.view layoutIfNeeded]; + size = self.bgImageView.bounds.size; + } + if (size.width <= 0 || size.height <= 0) { + size = self.view.bounds.size; + } + if (size.width <= 0 || size.height <= 0) { + size = [UIScreen mainScreen].bounds.size; + } + UIColor *topColor = [UIColor colorWithHex:0xDEDFE4]; + UIColor *bottomColor = [UIColor colorWithHex:0xD1D3DB]; + img = [self kb_defaultGradientImageWithSize:size + topColor:topColor + bottomColor:bottomColor]; + self.contentView.backgroundColor = [UIColor clearColor]; + self.bgImageView.backgroundColor = [UIColor clearColor]; } - NSLog(@"⌨️[Keyboard] apply theme id=%@ hasBg=%d", t.skinId, (img != nil)); - [self kb_logSkinDiagnosticsWithTheme:t backgroundImage:img]; - self.bgImageView.image = img; - BOOL hasImg = (img != nil); - self.view.backgroundColor = hasImg ? [UIColor clearColor] : t.keyboardBackground; - self.contentView.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)]) { + NSLog(@"==="); + } else { + // 自定义皮肤:清除背景色,使用皮肤图片 + self.contentView.backgroundColor = [UIColor clearColor]; + self.bgImageView.backgroundColor = [UIColor clearColor]; + } + NSLog(@"⌨️[Keyboard] apply theme id=%@ hasBg=%d", t.skinId, (img != nil)); + [self kb_logSkinDiagnosticsWithTheme:t backgroundImage:img]; + self.bgImageView.image = img; + BOOL hasImg = (img != nil); + // 触发键区按主题重绘 + if ([self.keyBoardMainView respondsToSelector:@selector(kb_applyTheme)]) { +// method declared in KBKeyBoardMainView.h #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" - [self.functionView performSelector:@selector(kb_applyTheme)]; + [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 + } } - (BOOL)kb_isDefaultKeyboardTheme:(KBSkinTheme *)theme { - NSString *skinId = theme.skinId ?: @""; - if (skinId.length == 0 || [skinId isEqualToString:@"default"]) { - return YES; - } - if ([skinId isEqualToString:kKBDefaultSkinIdLight]) { - return YES; - } - return [skinId isEqualToString:kKBDefaultSkinIdDark]; + NSString *skinId = theme.skinId ?: @""; + if (skinId.length == 0 || [skinId isEqualToString:@"default"]) { + return YES; + } + if ([skinId isEqualToString:kKBDefaultSkinIdLight]) { + return YES; + } + return [skinId isEqualToString:kKBDefaultSkinIdDark]; } - (BOOL)kb_isDarkModeActive { - if (@available(iOS 13.0, *)) { - return self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark; - } - return NO; + if (@available(iOS 13.0, *)) { + return self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark; + } + return NO; } - (NSString *)kb_defaultSkinIdForCurrentStyle { - return [self kb_isDarkModeActive] ? kKBDefaultSkinIdDark : kKBDefaultSkinIdLight; + return [self kb_isDarkModeActive] ? kKBDefaultSkinIdDark + : kKBDefaultSkinIdLight; } - (NSString *)kb_defaultSkinZipNameForCurrentStyle { - return [self kb_isDarkModeActive] ? kKBDefaultSkinZipNameDark : kKBDefaultSkinZipNameLight; + return [self kb_isDarkModeActive] ? kKBDefaultSkinZipNameDark + : kKBDefaultSkinZipNameLight; } -- (UIImage *)kb_defaultGradientImageWithSize:(CGSize)size { - if (size.width <= 0 || size.height <= 0) { return nil; } - CAGradientLayer *layer = [CAGradientLayer layer]; - layer.frame = CGRectMake(0, 0, size.width, size.height); - layer.startPoint = CGPointMake(0.5, 0.0); - layer.endPoint = CGPointMake(0.5, 1.0); - layer.colors = @[ - (id)[UIColor colorWithHex:0xDEDFE4].CGColor, - (id)[UIColor colorWithHex:0xD1D3DB].CGColor - ]; +- (UIImage *)kb_defaultGradientImageWithSize:(CGSize)size + topColor:(UIColor *)topColor + bottomColor:(UIColor *)bottomColor { + if (size.width <= 0 || size.height <= 0) { + return nil; + } - UIGraphicsBeginImageContextWithOptions(size, YES, 0); - [layer renderInContext:UIGraphicsGetCurrentContext()]; - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return image; + // 将动态颜色解析为当前 trait collection 下的具体颜色值 + // 否则在 UIGraphicsBeginImageContextWithOptions 中渲染时会使用默认的浅色模式 + UIColor *resolvedTopColor = topColor; + UIColor *resolvedBottomColor = bottomColor; + if (@available(iOS 13.0, *)) { + resolvedTopColor = + [topColor resolvedColorWithTraitCollection:self.traitCollection]; + resolvedBottomColor = + [bottomColor resolvedColorWithTraitCollection:self.traitCollection]; + } + + CAGradientLayer *layer = [CAGradientLayer layer]; + layer.frame = CGRectMake(0, 0, size.width, size.height); + layer.startPoint = CGPointMake(0.5, 0.0); + layer.endPoint = CGPointMake(0.5, 1.0); + layer.colors = + @[ (id)resolvedTopColor.CGColor, (id)resolvedBottomColor.CGColor ]; + + UIGraphicsBeginImageContextWithOptions(size, YES, 0); + [layer renderInContext:UIGraphicsGetCurrentContext()]; + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; } - (void)kb_logSkinDiagnosticsWithTheme:(KBSkinTheme *)theme backgroundImage:(UIImage *)image { #if DEBUG - NSString *skinId = theme.skinId ?: @""; - NSString *name = theme.name ?: @""; - NSMutableArray *roots = [NSMutableArray array]; - NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:AppGroup]; - if (containerURL.path.length > 0) { - [roots addObject:containerURL.path]; - } - NSString *cacheRoot = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject; - if (cacheRoot.length > 0) { - [roots addObject:cacheRoot]; - } + NSString *skinId = theme.skinId ?: @""; + NSString *name = theme.name ?: @""; + NSMutableArray *roots = [NSMutableArray array]; + NSURL *containerURL = [[NSFileManager defaultManager] + containerURLForSecurityApplicationGroupIdentifier:AppGroup]; + if (containerURL.path.length > 0) { + [roots addObject:containerURL.path]; + } + NSString *cacheRoot = NSSearchPathForDirectoriesInDomains( + NSCachesDirectory, NSUserDomainMask, YES) + .firstObject; + if (cacheRoot.length > 0) { + [roots addObject:cacheRoot]; + } - NSFileManager *fm = [NSFileManager defaultManager]; - NSMutableArray *lines = [NSMutableArray array]; - for (NSString *root in roots) { - NSString *iconsDir = [[root stringByAppendingPathComponent:@"Skins"] - stringByAppendingPathComponent:skinId]; - iconsDir = [iconsDir stringByAppendingPathComponent:@"icons"]; - BOOL isDir = NO; - BOOL exists = [fm fileExistsAtPath:iconsDir isDirectory:&isDir] && isDir; - NSArray *contents = exists ? [fm contentsOfDirectoryAtPath:iconsDir error:nil] : nil; - NSUInteger count = contents.count; - BOOL hasQ = exists && [fm fileExistsAtPath:[iconsDir stringByAppendingPathComponent:@"key_q.png"]]; - BOOL hasQUp = exists && [fm fileExistsAtPath:[iconsDir stringByAppendingPathComponent:@"key_q_up.png"]]; - BOOL hasDel = exists && [fm fileExistsAtPath:[iconsDir stringByAppendingPathComponent:@"key_del.png"]]; - BOOL hasShift = exists && [fm fileExistsAtPath:[iconsDir stringByAppendingPathComponent:@"key_up.png"]]; - BOOL hasShiftUpper = exists && [fm fileExistsAtPath:[iconsDir stringByAppendingPathComponent:@"key_up_upper.png"]]; - NSString *line = [NSString stringWithFormat:@"root=%@ icons=%@ exist=%d count=%tu key_q=%d key_q_up=%d key_del=%d key_up=%d key_up_upper=%d", - root, iconsDir, exists, count, hasQ, hasQUp, hasDel, hasShift, hasShiftUpper]; - [lines addObject:line]; - } + NSFileManager *fm = [NSFileManager defaultManager]; + NSMutableArray *lines = [NSMutableArray array]; + for (NSString *root in roots) { + NSString *iconsDir = [[root stringByAppendingPathComponent:@"Skins"] + stringByAppendingPathComponent:skinId]; + iconsDir = [iconsDir stringByAppendingPathComponent:@"icons"]; + BOOL isDir = NO; + BOOL exists = [fm fileExistsAtPath:iconsDir isDirectory:&isDir] && isDir; + NSArray *contents = + exists ? [fm contentsOfDirectoryAtPath:iconsDir error:nil] : nil; + NSUInteger count = contents.count; + BOOL hasQ = + exists && + [fm fileExistsAtPath:[iconsDir + stringByAppendingPathComponent:@"key_q.png"]]; + BOOL hasQUp = + exists && [fm fileExistsAtPath:[iconsDir stringByAppendingPathComponent: + @"key_q_up.png"]]; + BOOL hasDel = + exists && [fm fileExistsAtPath:[iconsDir stringByAppendingPathComponent: + @"key_del.png"]]; + BOOL hasShift = + exists && + [fm fileExistsAtPath:[iconsDir + stringByAppendingPathComponent:@"key_up.png"]]; + BOOL hasShiftUpper = + exists && [fm fileExistsAtPath:[iconsDir stringByAppendingPathComponent: + @"key_up_upper.png"]]; + NSString *line = [NSString + stringWithFormat:@"root=%@ icons=%@ exist=%d count=%tu key_q=%d " + @"key_q_up=%d key_del=%d key_up=%d key_up_upper=%d", + root, iconsDir, exists, count, hasQ, hasQUp, hasDel, + hasShift, hasShiftUpper]; + [lines addObject:line]; + } - NSLog(@"[Keyboard] theme id=%@ name=%@ hasBg=%d\n%@", - skinId, name, (image != nil), [lines componentsJoinedByString:@"\n"]); + NSLog(@"[Keyboard] theme id=%@ name=%@ hasBg=%d\n%@", skinId, name, + (image != nil), [lines componentsJoinedByString:@"\n"]); #endif } - (void)kb_consumePendingShopSkin { - KBWeakSelf - [KBSkinInstallBridge consumePendingRequestFromBundle:NSBundle.mainBundle - completion:^(BOOL success, NSError * _Nullable error) { - if (!success) { - if (error) { - NSLog(@"[Keyboard] skin request failed: %@", error); - [KBHUD showInfo:KBLocalized(@"皮肤资源准备失败,请稍后再试")]; - } - return; - } - [weakSelf kb_applyTheme]; - [KBHUD showInfo:KBLocalized(@"皮肤已更新,立即体验吧")]; - }]; + 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( + @"皮肤已更新,立即体验吧")]; + }]; } #pragma mark - Default Skin - (void)kb_applyDefaultSkinIfNeeded { - NSDictionary *pending = [KBSkinInstallBridge pendingRequestPayload]; - if (pending.count > 0) { - return; - } + NSDictionary *pending = [KBSkinInstallBridge pendingRequestPayload]; + if (pending.count > 0) { + return; + } - NSString *currentId = [KBSkinManager shared].current.skinId ?: @""; - BOOL isDefault = (currentId.length == 0 || [currentId isEqualToString:@"default"]); - BOOL isLightDefault = [currentId isEqualToString:kKBDefaultSkinIdLight]; - BOOL isDarkDefault = [currentId isEqualToString:kKBDefaultSkinIdDark]; - if (!isDefault && !isLightDefault && !isDarkDefault) { - // 用户已应用自定义皮肤:不随深色模式切换默认皮肤 - return; - } - NSString *targetId = [self kb_defaultSkinIdForCurrentStyle]; - NSString *targetZip = [self kb_defaultSkinZipNameForCurrentStyle]; - if (currentId.length > 0 && [currentId isEqualToString:targetId]) { return; } + NSString *currentId = [KBSkinManager shared].current.skinId ?: @""; + BOOL isDefault = + (currentId.length == 0 || [currentId isEqualToString:@"default"]); + BOOL isLightDefault = [currentId isEqualToString:kKBDefaultSkinIdLight]; + BOOL isDarkDefault = [currentId isEqualToString:kKBDefaultSkinIdDark]; + if (!isDefault && !isLightDefault && !isDarkDefault) { + // 用户已应用自定义皮肤:不随深色模式切换默认皮肤 + return; + } + NSString *targetId = [self kb_defaultSkinIdForCurrentStyle]; + NSString *targetZip = [self kb_defaultSkinZipNameForCurrentStyle]; + if (currentId.length > 0 && [currentId isEqualToString:targetId]) { + return; + } - NSError *applyError = nil; - if ([KBSkinInstallBridge applyInstalledSkinWithId:targetId error:&applyError]) { - return; - } + NSError *applyError = nil; + if ([KBSkinInstallBridge applyInstalledSkinWithId:targetId + error:&applyError]) { + return; + } - [KBSkinInstallBridge publishBundleSkinRequestWithId:targetId - name:targetId - zipName:targetZip - iconShortNames:nil]; - [KBSkinInstallBridge consumePendingRequestFromBundle:NSBundle.mainBundle - completion:^(__unused BOOL success, __unused NSError * _Nullable error) { - // 已通过通知触发主题刷新,这里无需额外处理 - }]; + [KBSkinInstallBridge publishBundleSkinRequestWithId:targetId + name:targetId + zipName:targetZip + iconShortNames:nil]; + [KBSkinInstallBridge + consumePendingRequestFromBundle:NSBundle.mainBundle + completion:^(__unused BOOL success, + __unused NSError *_Nullable error){ + // 已通过通知触发主题刷新,这里无需额外处理 + }]; } #pragma mark - Layout Helpers - (CGFloat)kb_portraitWidth { - CGSize s = [UIScreen mainScreen].bounds.size; - return MIN(s.width, s.height); + CGSize s = [UIScreen mainScreen].bounds.size; + return MIN(s.width, s.height); } - (CGFloat)kb_keyboardHeightForWidth:(CGFloat)width { - if (width <= 0) { width = KB_DESIGN_WIDTH; } - return kKBKeyboardBaseHeight * (width / KB_DESIGN_WIDTH); + if (width <= 0) { + width = KB_DESIGN_WIDTH; + } + return kKBKeyboardBaseHeight * (width / KB_DESIGN_WIDTH); } - (void)kb_updateKeyboardLayoutIfNeeded { - CGFloat portraitWidth = [self kb_portraitWidth]; - CGFloat keyboardHeight = [self kb_keyboardHeightForWidth:portraitWidth]; - CGFloat containerWidth = CGRectGetWidth(self.view.superview.bounds); - if (containerWidth <= 0) { - containerWidth = CGRectGetWidth(self.view.window.bounds); - } - if (containerWidth <= 0) { - containerWidth = CGRectGetWidth([UIScreen mainScreen].bounds); - } + CGFloat portraitWidth = [self kb_portraitWidth]; + CGFloat keyboardHeight = [self kb_keyboardHeightForWidth:portraitWidth]; + CGFloat containerWidth = CGRectGetWidth(self.view.superview.bounds); + if (containerWidth <= 0) { + containerWidth = CGRectGetWidth(self.view.window.bounds); + } + if (containerWidth <= 0) { + containerWidth = CGRectGetWidth([UIScreen mainScreen].bounds); + } - BOOL widthChanged = (fabs(self.kb_lastPortraitWidth - portraitWidth) >= 0.5); - BOOL heightChanged = (fabs(self.kb_lastKeyboardHeight - keyboardHeight) >= 0.5); - if (!widthChanged && !heightChanged && containerWidth > 0 && self.kb_widthConstraint.constant == containerWidth) { - return; - } - self.kb_lastPortraitWidth = portraitWidth; - self.kb_lastKeyboardHeight = keyboardHeight; + BOOL widthChanged = (fabs(self.kb_lastPortraitWidth - portraitWidth) >= 0.5); + BOOL heightChanged = + (fabs(self.kb_lastKeyboardHeight - keyboardHeight) >= 0.5); + if (!widthChanged && !heightChanged && containerWidth > 0 && + self.kb_widthConstraint.constant == containerWidth) { + return; + } + self.kb_lastPortraitWidth = portraitWidth; + self.kb_lastKeyboardHeight = keyboardHeight; - if (self.kb_heightConstraint) { - self.kb_heightConstraint.constant = keyboardHeight; - } - if (containerWidth > 0 && self.kb_widthConstraint) { - self.kb_widthConstraint.constant = containerWidth; - } - if (self.contentWidthConstraint) { - [self.contentWidthConstraint setOffset:portraitWidth]; - } - if (self.contentHeightConstraint) { - [self.contentHeightConstraint setOffset:keyboardHeight]; - } - [self.view layoutIfNeeded]; + if (self.kb_heightConstraint) { + self.kb_heightConstraint.constant = keyboardHeight; + } + if (containerWidth > 0 && self.kb_widthConstraint) { + self.kb_widthConstraint.constant = containerWidth; + } + if (self.contentWidthConstraint) { + [self.contentWidthConstraint setOffset:portraitWidth]; + } + if (self.contentHeightConstraint) { + [self.contentHeightConstraint setOffset:keyboardHeight]; + } + [self.view layoutIfNeeded]; } #pragma mark - Lazy - (UIView *)contentView { - if (!_contentView) { - _contentView = [[UIView alloc] init]; - _contentView.backgroundColor = [UIColor clearColor]; - } - return _contentView; + if (!_contentView) { + _contentView = [[UIView alloc] init]; + _contentView.backgroundColor = [UIColor clearColor]; + } + return _contentView; } - (UIImageView *)bgImageView { - if (!_bgImageView) { - _bgImageView = [[UIImageView alloc] init]; - _bgImageView.contentMode = UIViewContentModeScaleAspectFill; - _bgImageView.clipsToBounds = YES; - } - return _bgImageView; + if (!_bgImageView) { + _bgImageView = [[UIImageView alloc] init]; + _bgImageView.contentMode = UIViewContentModeScaleAspectFill; + _bgImageView.clipsToBounds = YES; + } + return _bgImageView; } @end diff --git a/CustomKeyboard/Network/KBStreamFetcher.h b/CustomKeyboard/Network/KBStreamFetcher.h index b89392c..c37ec8f 100644 --- a/CustomKeyboard/Network/KBStreamFetcher.h +++ b/CustomKeyboard/Network/KBStreamFetcher.h @@ -8,7 +8,7 @@ // - 兼容后端“/t”作为分段标记:可自动替换为制表符“\t” // - 首段去首个“\t”:若首次正文以一个制表符起始(允许前导空白),可只移除“一个”\t // - +// 暂未使用 #import NS_ASSUME_NONNULL_BEGIN diff --git a/CustomKeyboard/Network/NetworkStreamHandler.h b/CustomKeyboard/Network/NetworkStreamHandler.h index 4c1c74b..5a412ae 100644 --- a/CustomKeyboard/Network/NetworkStreamHandler.h +++ b/CustomKeyboard/Network/NetworkStreamHandler.h @@ -4,7 +4,7 @@ // // Created by Mac on 2025/11/12. // - +// 暂未使用 #import NS_ASSUME_NONNULL_BEGIN diff --git a/CustomKeyboard/Resource/normal_hei_them.zip b/CustomKeyboard/Resource/normal_hei_them.zip index 6613bf0..a33d171 100644 Binary files a/CustomKeyboard/Resource/normal_hei_them.zip and b/CustomKeyboard/Resource/normal_hei_them.zip differ diff --git a/CustomKeyboard/Resource/normal_them.zip b/CustomKeyboard/Resource/normal_them.zip index 9c2b941..639eea9 100644 Binary files a/CustomKeyboard/Resource/normal_them.zip and b/CustomKeyboard/Resource/normal_them.zip differ diff --git a/CustomKeyboard/View/KBKeyBoardMainView.m b/CustomKeyboard/View/KBKeyBoardMainView.m index d742eab..b81be25 100644 --- a/CustomKeyboard/View/KBKeyBoardMainView.m +++ b/CustomKeyboard/View/KBKeyBoardMainView.m @@ -255,11 +255,8 @@ - (void)kb_applyTheme { KBSkinManager *mgr = [KBSkinManager shared]; - BOOL hasImg = ([mgr currentBackgroundImage] != nil); -// UIColor *bg = mgr.current.keyboardBackground; - UIColor *bg = [UIColor colorWithHex:0xD1D3DB]; - self.backgroundColor = hasImg ? [UIColor clearColor] : bg; - self.keyboardView.backgroundColor = hasImg ? [UIColor clearColor] : bg; + self.backgroundColor = [UIColor clearColor]; + self.keyboardView.backgroundColor = [UIColor clearColor]; if ([self.topBar respondsToSelector:@selector(kb_applyTheme)]) { [self.topBar kb_applyTheme]; } diff --git a/CustomKeyboard/View/KBKeyButton.m b/CustomKeyboard/View/KBKeyButton.m index 5c09fa2..27e5cd9 100644 --- a/CustomKeyboard/View/KBKeyButton.m +++ b/CustomKeyboard/View/KBKeyButton.m @@ -83,6 +83,10 @@ if (CGRectGetHeight(bounds) <= 0 || CGRectGetWidth(bounds) <= 0) { return; } + // 有皮肤图时,强制隐藏文字,避免图标与文本叠加 + if (self.iconView.image != nil) { + self.titleLabel.hidden = YES; + } self.bottomShadowLayer.frame = CGRectMake(0, CGRectGetHeight(bounds) - shadowHeight, CGRectGetWidth(bounds), diff --git a/CustomKeyboard/View/KBKeyboardView.m b/CustomKeyboard/View/KBKeyboardView.m index 7919ef8..5936411 100644 --- a/CustomKeyboard/View/KBKeyboardView.m +++ b/CustomKeyboard/View/KBKeyboardView.m @@ -41,7 +41,7 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5; - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { - self.backgroundColor = [KBSkinManager shared].current.keyboardBackground; + self.backgroundColor = [UIColor clearColor]; _layoutStyle = KBKeyboardLayoutStyleLetters; // 默认小写:与需求一致,初始不开启 Shift _shiftOn = NO; diff --git a/Shared/KBAPI.h b/Shared/KBAPI.h index 6cf6769..9e90d34 100644 --- a/Shared/KBAPI.h +++ b/Shared/KBAPI.h @@ -68,7 +68,7 @@ #define API_SUBSCRIPTION_PRODUCT_LIST @"/products/subscription/list" // 查询订阅商品列表 /// AI -#define API_AI_TALK @"/chat/talk" // 排行榜标签列表 +#define API_AI_TALK @"/chat/talk" diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 1c4cba3..123237f 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 04050ECB2F10FB8F008051EB /* UIImage+KBColor.m in Sources */ = {isa = PBXBuildFile; fileRef = 047C655D2EBCD5B20035E841 /* UIImage+KBColor.m */; }; 041007D22ECE012000D203BB /* KBSkinIconMap.strings in Resources */ = {isa = PBXBuildFile; fileRef = 041007D12ECE012000D203BB /* KBSkinIconMap.strings */; }; 041007D42ECE012500D203BB /* 002.zip in Resources */ = {isa = PBXBuildFile; fileRef = 041007D32ECE012500D203BB /* 002.zip */; }; 04122F5D2EC5E5A900EF7AB3 /* KBLoginVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 04122F5B2EC5E5A900EF7AB3 /* KBLoginVM.m */; }; @@ -67,7 +68,6 @@ 04791F982ED49CE7004E8522 /* KBFont.m in Sources */ = {isa = PBXBuildFile; fileRef = 04791F972ED49CE7004E8522 /* KBFont.m */; }; 04791F992ED49CE7004E8522 /* KBFont.m in Sources */ = {isa = PBXBuildFile; fileRef = 04791F972ED49CE7004E8522 /* KBFont.m */; }; 04791FF72ED5B985004E8522 /* Christmas.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04791FF62ED5B985004E8522 /* Christmas.zip */; }; - 04791FFB2ED5EAB8004E8522 /* normal_hei_them.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04791FFA2ED5EAB8004E8522 /* normal_hei_them.zip */; }; 04791FFC2ED71D17004E8522 /* UIColor+Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95E42EB220B5007BD342 /* UIColor+Extension.m */; }; 04791FFF2ED830FA004E8522 /* KBKeyboardMaskView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04791FFE2ED830FA004E8522 /* KBKeyboardMaskView.m */; }; 047920072ED86ABC004E8522 /* kb_guide_keyboard.gif in Resources */ = {isa = PBXBuildFile; fileRef = 047920062ED86ABC004E8522 /* kb_guide_keyboard.gif */; }; @@ -145,8 +145,6 @@ 049FB2292EC31BB000FAB05D /* KBChangeNicknamePopView.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2282EC31BB000FAB05D /* KBChangeNicknamePopView.m */; }; 049FB22C2EC31F8800FAB05D /* KBGenderPickerPopView.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB22B2EC31F8800FAB05D /* KBGenderPickerPopView.m */; }; 049FB22F2EC34EB900FAB05D /* KBStreamTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB22E2EC34EB900FAB05D /* KBStreamTextView.m */; }; - 049FB2322EC45A0000FAB05D /* KBStreamFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2312EC45A0000FAB05D /* KBStreamFetcher.m */; }; - 049FB2352EC45C6A00FAB05D /* NetworkStreamHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2342EC45C6A00FAB05D /* NetworkStreamHandler.m */; }; 049FB23B2EC4766700FAB05D /* KBFunctionTagListView.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2372EC4766700FAB05D /* KBFunctionTagListView.m */; }; 049FB23C2EC4766700FAB05D /* KBStreamOverlayView.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2392EC4766700FAB05D /* KBStreamOverlayView.m */; }; 049FB23F2EC4B6EF00FAB05D /* KBULBridgeNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB23E2EC4B6EF00FAB05D /* KBULBridgeNotification.m */; }; @@ -172,7 +170,8 @@ 04C6EADD2EAF8CEB0089C901 /* KBToolBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 04C6EADC2EAF8CEB0089C901 /* KBToolBar.m */; }; 04D1F6B22EDFF10A00B12345 /* KBSkinInstallBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 04D1F6B12EDFF10A00B12345 /* KBSkinInstallBridge.m */; }; 04D1F6B32EDFF10A00B12345 /* KBSkinInstallBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 04D1F6B12EDFF10A00B12345 /* KBSkinInstallBridge.m */; }; - 04E1617C2F0FCA050022C23B /* normal_them.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04E1617B2F0FCA050022C23B /* normal_them.zip */; }; + 04E161832F10E6470022C23B /* normal_hei_them.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04E161812F10E6470022C23B /* normal_hei_them.zip */; }; + 04E161842F10E6470022C23B /* normal_them.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04E161822F10E6470022C23B /* normal_them.zip */; }; 04FC95672EB0546C007BD342 /* KBKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95652EB0546C007BD342 /* KBKey.m */; }; 04FC956A2EB05497007BD342 /* KBKeyButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95692EB05497007BD342 /* KBKeyButton.m */; }; 04FC956D2EB054B7007BD342 /* KBKeyboardView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC956C2EB054B7007BD342 /* KBKeyboardView.m */; }; @@ -351,7 +350,6 @@ 04791F962ED49CE7004E8522 /* KBFont.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFont.h; sourceTree = ""; }; 04791F972ED49CE7004E8522 /* KBFont.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFont.m; sourceTree = ""; }; 04791FF62ED5B985004E8522 /* Christmas.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = Christmas.zip; sourceTree = ""; }; - 04791FFA2ED5EAB8004E8522 /* normal_hei_them.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = normal_hei_them.zip; sourceTree = ""; }; 04791FFD2ED830FA004E8522 /* KBKeyboardMaskView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKeyboardMaskView.h; sourceTree = ""; }; 04791FFE2ED830FA004E8522 /* KBKeyboardMaskView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardMaskView.m; sourceTree = ""; }; 047920062ED86ABC004E8522 /* kb_guide_keyboard.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = kb_guide_keyboard.gif; sourceTree = ""; }; @@ -495,10 +493,6 @@ 049FB22B2EC31F8800FAB05D /* KBGenderPickerPopView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBGenderPickerPopView.m; sourceTree = ""; }; 049FB22D2EC34EB900FAB05D /* KBStreamTextView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBStreamTextView.h; sourceTree = ""; }; 049FB22E2EC34EB900FAB05D /* KBStreamTextView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBStreamTextView.m; sourceTree = ""; }; - 049FB2302EC45A0000FAB05D /* KBStreamFetcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBStreamFetcher.h; sourceTree = ""; }; - 049FB2312EC45A0000FAB05D /* KBStreamFetcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBStreamFetcher.m; sourceTree = ""; }; - 049FB2332EC45C6A00FAB05D /* NetworkStreamHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NetworkStreamHandler.h; sourceTree = ""; }; - 049FB2342EC45C6A00FAB05D /* NetworkStreamHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NetworkStreamHandler.m; sourceTree = ""; }; 049FB2362EC4766700FAB05D /* KBFunctionTagListView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFunctionTagListView.h; sourceTree = ""; }; 049FB2372EC4766700FAB05D /* KBFunctionTagListView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFunctionTagListView.m; sourceTree = ""; }; 049FB2382EC4766700FAB05D /* KBStreamOverlayView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBStreamOverlayView.h; sourceTree = ""; }; @@ -540,7 +534,8 @@ 04C6EAE12EAF940F0089C901 /* KBPermissionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBPermissionViewController.m; sourceTree = ""; }; 04D1F6B02EDFF10A00B12345 /* KBSkinInstallBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinInstallBridge.h; sourceTree = ""; }; 04D1F6B12EDFF10A00B12345 /* KBSkinInstallBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinInstallBridge.m; sourceTree = ""; }; - 04E1617B2F0FCA050022C23B /* normal_them.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = normal_them.zip; sourceTree = ""; }; + 04E161812F10E6470022C23B /* normal_hei_them.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = normal_hei_them.zip; sourceTree = ""; }; + 04E161822F10E6470022C23B /* normal_them.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = normal_them.zip; sourceTree = ""; }; 04FC953A2EAFAE56007BD342 /* KeyBoardPrefixHeader.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyBoardPrefixHeader.pch; sourceTree = ""; }; 04FC95642EB0546C007BD342 /* KBKey.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKey.h; sourceTree = ""; }; 04FC95652EB0546C007BD342 /* KBKey.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKey.m; sourceTree = ""; }; @@ -691,13 +686,13 @@ 041007D02ECE010100D203BB /* Resource */ = { isa = PBXGroup; children = ( - 04E1617B2F0FCA050022C23B /* normal_them.zip */, + 04E161812F10E6470022C23B /* normal_hei_them.zip */, + 04E161822F10E6470022C23B /* normal_them.zip */, A1B2C3EC2F20000000000001 /* kb_words.txt */, A1B2C3F02F20000000000002 /* kb_keyboard_layout_config.json */, 0498BDF42EEC50EE006CC1D5 /* emoji_categories.json */, 041007D12ECE012000D203BB /* KBSkinIconMap.strings */, 041007D32ECE012500D203BB /* 002.zip */, - 04791FFA2ED5EAB8004E8522 /* normal_hei_them.zip */, 04791FF62ED5B985004E8522 /* Christmas.zip */, ); path = Resource; @@ -1726,10 +1721,6 @@ 0498BDD92EE7ECEA006CC1D5 /* WJXEventSource */, A1B2C3E02EB0C0A100000001 /* KBNetworkManager.h */, A1B2C3E12EB0C0A100000001 /* KBNetworkManager.m */, - 049FB2302EC45A0000FAB05D /* KBStreamFetcher.h */, - 049FB2312EC45A0000FAB05D /* KBStreamFetcher.m */, - 049FB2332EC45C6A00FAB05D /* NetworkStreamHandler.h */, - 049FB2342EC45C6A00FAB05D /* NetworkStreamHandler.m */, ); path = Network; sourceTree = ""; @@ -1824,13 +1815,13 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 04E161832F10E6470022C23B /* normal_hei_them.zip in Resources */, + 04E161842F10E6470022C23B /* normal_them.zip in Resources */, 04A9FE202EB893F10020DB6D /* Localizable.strings in Resources */, 041007D42ECE012500D203BB /* 002.zip in Resources */, - 04E1617C2F0FCA050022C23B /* normal_them.zip in Resources */, 041007D22ECE012000D203BB /* KBSkinIconMap.strings in Resources */, A1B2C3ED2F20000000000001 /* kb_words.txt in Resources */, A1B2C3F12F20000000000002 /* kb_keyboard_layout_config.json in Resources */, - 04791FFB2ED5EAB8004E8522 /* normal_hei_them.zip in Resources */, 0498BDF52EEC50EE006CC1D5 /* emoji_categories.json in Resources */, 04791FF72ED5B985004E8522 /* Christmas.zip in Resources */, 04286A0B2ECD88B400CE730C /* KeyboardAssets.xcassets in Resources */, @@ -1930,13 +1921,13 @@ files = ( 0498BD862EE1BEC9006CC1D5 /* KBSignUtils.m in Sources */, 04791FFC2ED71D17004E8522 /* UIColor+Extension.m in Sources */, - 049FB2322EC45A0000FAB05D /* KBStreamFetcher.m in Sources */, 0450AC4A2EF2C3ED00B6AF06 /* KBKeyboardSubscriptionOptionCell.m in Sources */, 04A9FE0F2EB481100020DB6D /* KBHUD.m in Sources */, 04C6EADD2EAF8CEB0089C901 /* KBToolBar.m in Sources */, A1B2C3EB2F20000000000001 /* KBSuggestionBarView.m in Sources */, 04FC95792EB09BC8007BD342 /* KBKeyBoardMainView.m in Sources */, 04FEDAB32EEDB05000123456 /* KBEmojiPanelView.m in Sources */, + 04050ECB2F10FB8F008051EB /* UIImage+KBColor.m in Sources */, 04FEDB032EFE000000123456 /* KBEmojiBottomBarView.m in Sources */, 0498BD8C2EE69E15006CC1D5 /* KBTagItemModel.m in Sources */, 046131142ECF454500A6FADF /* KBKeyPreviewView.m in Sources */, @@ -1949,7 +1940,6 @@ 04A9FE1A2EB892460020DB6D /* KBLocalizationManager.m in Sources */, A1B2C3EA2F20000000000001 /* KBSuggestionEngine.m in Sources */, A1B2C3E22EB0C0A100000001 /* KBNetworkManager.m in Sources */, - 049FB2352EC45C6A00FAB05D /* NetworkStreamHandler.m in Sources */, 04FC956A2EB05497007BD342 /* KBKeyButton.m in Sources */, 04FEDAA12EEDB00100123456 /* KBEmojiDataProvider.m in Sources */, 04FC95B22EB0B2CC007BD342 /* KBSettingView.m in Sources */,