添加联想

This commit is contained in:
2025-12-22 12:54:28 +08:00
parent 9cdd024ce2
commit 8a344b293d
9 changed files with 235009 additions and 10 deletions

View File

@@ -20,14 +20,15 @@
#import "KBKeyboardSubscriptionView.h"
#import "KBKeyboardSubscriptionProduct.h"
#import "KBBackspaceUndoManager.h"
#import "KBSuggestionEngine.h"
// 使 static kb_consumePendingShopSkin
@interface KeyboardViewController (KBSkinShopBridge)
- (void)kb_consumePendingShopSkin;
@end
// 375 稿
static const CGFloat kKBKeyboardDesignHeight = 250.0f;
// 375 稿
static const CGFloat kKBKeyboardBaseHeight = 250.0f;
static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
void *observer,
@@ -50,6 +51,9 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
@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;
@end
@implementation KeyboardViewController
@@ -61,6 +65,8 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUI];
self.suggestionEngine = [KBSuggestionEngine shared];
self.currentWord = @"";
// HUD App KeyWindow
[KBHUD setContainerView:self.view];
// 访便
@@ -94,7 +100,7 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
self.view.translatesAutoresizingMaskIntoConstraints = NO;
// /
CGFloat keyboardHeight = KBFit(kKBKeyboardDesignHeight);
CGFloat keyboardHeight = KBFit(kKBKeyboardBaseHeight);
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
CGFloat outerVerticalInset = KBFit(4.0f);
@@ -137,6 +143,113 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
#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];
}
}
- (void)kb_clearCurrentWord {
self.currentWord = @"";
[self.keyBoardMainView kb_setSuggestions:@[]];
self.suppressSuggestions = NO;
}
- (void)kb_scheduleContextRefreshResetSuppression:(BOOL)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];
}
- (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"];
});
NSInteger idx = (NSInteger)context.length - 1;
while (idx >= 0) {
unichar ch = [context characterAtIndex:(NSUInteger)idx];
if (![letters characterIsMember:ch]) {
break;
}
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;
}
}
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<NSString *> *items = [self.suggestionEngine suggestionsForPrefix:prefix limit:5];
NSArray<NSString *> *cased = [self kb_applyCaseToSuggestions:items prefix:prefix];
[self.keyBoardMainView kb_setSuggestions:cased];
}
- (NSArray<NSString *> *)kb_applyCaseToSuggestions:(NSArray<NSString *> *)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; }
NSMutableArray<NSString *> *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;
}
/// /
- (void)showFunctionPanel:(BOOL)show {
//
@@ -254,19 +367,29 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
[[KBBackspaceUndoManager shared] registerNonClearAction];
}
switch (key.type) {
case KBKeyTypeCharacter:
[self.textDocumentProxy insertText:key.output ?: key.title ?: @""]; break;
case KBKeyTypeCharacter: {
NSString *text = key.output ?: key.title ?: @"";
[self.textDocumentProxy insertText:text];
[self kb_updateCurrentWordWithInsertedText:text];
} break;
case KBKeyTypeBackspace:
[self.textDocumentProxy deleteBackward]; break;
[self.textDocumentProxy deleteBackward];
[self kb_scheduleContextRefreshResetSuppression:NO];
break;
case KBKeyTypeSpace:
[self.textDocumentProxy insertText:@" "]; break;
[self.textDocumentProxy insertText:@" "];
[self kb_clearCurrentWord];
break;
case KBKeyTypeReturn:
[self.textDocumentProxy insertText:@"\n"]; break;
[self.textDocumentProxy insertText:@"\n"];
[self kb_clearCurrentWord];
break;
case KBKeyTypeGlobe:
[self advanceToNextInputMode]; break;
case KBKeyTypeCustom:
//
[self showFunctionPanel:YES];
[self kb_clearCurrentWord];
break;
case KBKeyTypeModeChange:
case KBKeyTypeShift:
@@ -278,6 +401,7 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didTapToolActionAtIndex:(NSInteger)index {
if (index == 0) {
[self showFunctionPanel:YES];
[self kb_clearCurrentWord];
return;
}
[self showFunctionPanel:NO];
@@ -291,16 +415,33 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
if (emoji.length == 0) { return; }
[[KBBackspaceUndoManager shared] registerNonClearAction];
[self.textDocumentProxy insertText:emoji];
[self kb_clearCurrentWord];
}
- (void)keyBoardMainViewDidTapUndo:(KBKeyBoardMainView *)keyBoardMainView {
[[KBBackspaceUndoManager shared] performUndoFromResponder:self.view];
[self kb_scheduleContextRefreshResetSuppression:YES];
}
- (void)keyBoardMainViewDidTapEmojiSearch:(KBKeyBoardMainView *)keyBoardMainView {
[KBHUD showInfo:KBLocalized(@"Search coming soon")];
}
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didSelectSuggestion:(NSString *)suggestion {
if (suggestion.length == 0) { return; }
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:@[]];
}
// MARK: - KBFunctionViewDelegate
- (void)functionView:(KBFunctionView *)functionView didTapToolActionAtIndex:(NSInteger)index {
// index == 0