1
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
#import "KBLocalizationManager.h"
|
||||
#import "KBSkinManager.h"
|
||||
#import "KBSuggestionEngine.h"
|
||||
#import "KBKeyboardLayoutResolver.h"
|
||||
#import <SDWebImage/SDWebImage.h>
|
||||
|
||||
#if DEBUG
|
||||
@@ -48,6 +49,7 @@ static NSString *KBFormatMB(uint64_t bytes) {
|
||||
|
||||
{
|
||||
BOOL _kb_didTriggerLoginDeepLinkOnce;
|
||||
NSString *_kb_lastLoadedProfileId; // 记录上次加载的 profileId
|
||||
#if DEBUG
|
||||
BOOL _kb_debugDidCountAlive;
|
||||
#endif
|
||||
@@ -97,6 +99,9 @@ static NSString *KBFormatMB(uint64_t bytes) {
|
||||
[self kb_registerDarwinSkinInstallObserver];
|
||||
[self kb_consumePendingShopSkin];
|
||||
[self kb_applyDefaultSkinIfNeeded];
|
||||
|
||||
// 监听 App Group 配置变化,动态切换键盘布局
|
||||
[self kb_checkAndApplyLayoutIfNeeded];
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
@@ -198,6 +203,9 @@ static NSString *KBFormatMB(uint64_t bytes) {
|
||||
if (self.kb_defaultGradientLayer) {
|
||||
self.kb_defaultGradientLayer.frame = self.bgImageView.bounds;
|
||||
}
|
||||
|
||||
// 每次布局时检查是否需要切换键盘布局
|
||||
[self kb_checkAndApplyLayoutIfNeeded];
|
||||
}
|
||||
|
||||
- (void)viewWillTransitionToSize:(CGSize)size
|
||||
@@ -238,4 +246,39 @@ static NSString *KBFormatMB(uint64_t bytes) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark - Layout Switching
|
||||
|
||||
- (void)kb_checkAndApplyLayoutIfNeeded {
|
||||
NSString *currentProfileId = [[KBKeyboardLayoutResolver sharedResolver] currentProfileId];
|
||||
if (currentProfileId.length == 0) {
|
||||
currentProfileId = @"en_US_qwerty"; // 默认回退
|
||||
}
|
||||
|
||||
// 如果 profileId 没有变化,不需要重新加载
|
||||
if ([currentProfileId isEqualToString:_kb_lastLoadedProfileId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[KeyboardViewController] Detected profileId change: %@ -> %@", _kb_lastLoadedProfileId, currentProfileId);
|
||||
_kb_lastLoadedProfileId = currentProfileId;
|
||||
|
||||
// 通知 KBKeyBoardMainView 切换布局
|
||||
if (self.keyBoardMainView && [self.keyBoardMainView respondsToSelector:@selector(reloadLayoutWithProfileId:)]) {
|
||||
[self.keyBoardMainView performSelector:@selector(reloadLayoutWithProfileId:) withObject:currentProfileId];
|
||||
}
|
||||
|
||||
// 更新联想引擎类型
|
||||
NSString *suggestionEngine = [[KBKeyboardLayoutResolver sharedResolver] suggestionEngineForProfileId:currentProfileId];
|
||||
if (suggestionEngine.length > 0) {
|
||||
[self kb_updateSuggestionEngineType:suggestionEngine];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)kb_updateSuggestionEngineType:(NSString *)engineType {
|
||||
// 根据 engineType 切换不同的联想引擎
|
||||
// 例如:latin, pinyin_traditional, pinyin_simplified, bopomofo
|
||||
NSLog(@"[KeyboardViewController] Switching suggestion engine to: %@", engineType);
|
||||
[[KBSuggestionEngine shared] setEngineTypeFromString:engineType];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -351,7 +351,6 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
return;
|
||||
}
|
||||
NSString *targetId = [self kb_defaultSkinIdForCurrentStyle];
|
||||
NSString *targetZip = [self kb_defaultSkinZipNameForCurrentStyle];
|
||||
if (currentId.length > 0 && [currentId isEqualToString:targetId]) {
|
||||
return;
|
||||
}
|
||||
@@ -360,17 +359,12 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
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) {
|
||||
// 已通过通知触发主题刷新,这里无需额外处理
|
||||
}];
|
||||
// 默认皮肤 zip 仅由主 App 持有并解压。扩展侧不再尝试从自身 bundle 解压。
|
||||
// 若主 App 尚未安装对应默认皮肤,这里仅保留当前主题,避免“找不到 zip”报错。
|
||||
if (applyError) {
|
||||
NSLog(@"[Keyboard] default skin %@ not installed in AppGroup yet: %@",
|
||||
targetId, applyError);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
37
CustomKeyboard/Manager/KBKeyboardLayoutResolver.h
Normal file
37
CustomKeyboard/Manager/KBKeyboardLayoutResolver.h
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// KBKeyboardLayoutResolver.h
|
||||
// CustomKeyboard
|
||||
//
|
||||
// 扩展侧布局解析器:根据 profileId 解析对应的布局配置
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface KBKeyboardLayoutResolver : NSObject
|
||||
|
||||
+ (instancetype)sharedResolver;
|
||||
|
||||
/// 根据 profileId 获取对应的布局 JSON ID
|
||||
/// @param profileId 输入配置 ID(如 "es_ES_azerty")
|
||||
/// @return 布局 JSON ID(如 "letters_azerty"),如果未找到返回 "letters"
|
||||
- (NSString *)layoutJsonIdForProfileId:(NSString *)profileId;
|
||||
|
||||
/// 根据 profileId 获取对应的联想引擎类型
|
||||
/// @param profileId 输入配置 ID
|
||||
/// @return 联想引擎类型(如 "latin", "pinyin_traditional", "bopomofo")
|
||||
- (NSString *)suggestionEngineForProfileId:(NSString *)profileId;
|
||||
|
||||
/// 从 App Group 读取当前选中的 profileId
|
||||
- (nullable NSString *)currentProfileId;
|
||||
|
||||
/// 从 App Group 读取当前选中的语言代码
|
||||
- (nullable NSString *)currentLanguageCode;
|
||||
|
||||
/// 从 App Group 读取当前选中的布局变体
|
||||
- (nullable NSString *)currentLayoutVariant;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
69
CustomKeyboard/Manager/KBKeyboardLayoutResolver.m
Normal file
69
CustomKeyboard/Manager/KBKeyboardLayoutResolver.m
Normal file
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// KBKeyboardLayoutResolver.m
|
||||
// CustomKeyboard
|
||||
//
|
||||
|
||||
#import "KBKeyboardLayoutResolver.h"
|
||||
#import "KBInputProfileManager.h"
|
||||
#import "KBConfig.h"
|
||||
|
||||
@implementation KBKeyboardLayoutResolver
|
||||
|
||||
+ (instancetype)sharedResolver {
|
||||
static KBKeyboardLayoutResolver *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [[self alloc] init];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (NSString *)layoutJsonIdForProfileId:(NSString *)profileId {
|
||||
if (profileId.length == 0) {
|
||||
return @"letters";
|
||||
}
|
||||
|
||||
NSString *layoutJsonId = [[KBInputProfileManager sharedManager] layoutJsonIdForProfileId:profileId];
|
||||
if (layoutJsonId.length > 0) {
|
||||
return layoutJsonId;
|
||||
}
|
||||
|
||||
// 回退到默认布局
|
||||
NSLog(@"[KBKeyboardLayoutResolver] No layoutJsonId found for profileId: %@, using default 'letters'", profileId);
|
||||
return @"letters";
|
||||
}
|
||||
|
||||
- (NSString *)suggestionEngineForProfileId:(NSString *)profileId {
|
||||
if (profileId.length == 0) {
|
||||
return @"latin";
|
||||
}
|
||||
|
||||
NSString *engine = [[KBInputProfileManager sharedManager] suggestionEngineForProfileId:profileId];
|
||||
if (engine.length > 0) {
|
||||
return engine;
|
||||
}
|
||||
|
||||
// 回退到默认引擎
|
||||
NSLog(@"[KBKeyboardLayoutResolver] No suggestionEngine found for profileId: %@, using default 'latin'", profileId);
|
||||
return @"latin";
|
||||
}
|
||||
|
||||
- (nullable NSString *)currentProfileId {
|
||||
NSUserDefaults *appGroup = [[NSUserDefaults alloc] initWithSuiteName:AppGroup];
|
||||
NSString *profileId = [appGroup stringForKey:AppGroup_SelectedKeyboardProfileId];
|
||||
return profileId;
|
||||
}
|
||||
|
||||
- (nullable NSString *)currentLanguageCode {
|
||||
NSUserDefaults *appGroup = [[NSUserDefaults alloc] initWithSuiteName:AppGroup];
|
||||
NSString *languageCode = [appGroup stringForKey:AppGroup_SelectedKeyboardLanguageCode];
|
||||
return languageCode;
|
||||
}
|
||||
|
||||
- (nullable NSString *)currentLayoutVariant {
|
||||
NSUserDefaults *appGroup = [[NSUserDefaults alloc] initWithSuiteName:AppGroup];
|
||||
NSString *layoutVariant = [appGroup stringForKey:AppGroup_SelectedKeyboardLayoutVariant];
|
||||
return layoutVariant;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -7,9 +7,18 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, KBSuggestionEngineType) {
|
||||
KBSuggestionEngineTypeLatin = 0, // 拉丁字母(英语、西班牙语、葡萄牙语、印尼语)
|
||||
KBSuggestionEngineTypePinyinSimplified, // 简体拼音
|
||||
KBSuggestionEngineTypePinyinTraditional, // 繁体拼音
|
||||
KBSuggestionEngineTypeBopomofo // 注音(繁体)
|
||||
};
|
||||
|
||||
/// Simple local suggestion engine (prefix match + lightweight ranking).
|
||||
@interface KBSuggestionEngine : NSObject
|
||||
|
||||
@property (nonatomic, assign) KBSuggestionEngineType engineType;
|
||||
|
||||
+ (instancetype)shared;
|
||||
|
||||
/// Returns suggestions for prefix (lowercase expected), limited by count.
|
||||
@@ -18,6 +27,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// Record a selection to slightly boost ranking next time.
|
||||
- (void)recordSelection:(NSString *)word;
|
||||
|
||||
/// 设置联想引擎类型(根据 profileId 的 suggestionEngine 字段)
|
||||
- (void)setEngineTypeFromString:(NSString *)engineTypeString;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
@property (nonatomic, copy) NSArray<NSString *> *words;
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *selectionCounts;
|
||||
@property (nonatomic, strong) NSSet<NSString *> *priorityWords;
|
||||
@property (nonatomic, copy) NSArray<NSString *> *traditionalChineseWords;
|
||||
@property (nonatomic, copy) NSArray<NSString *> *simplifiedChineseWords;
|
||||
@end
|
||||
|
||||
@implementation KBSuggestionEngine
|
||||
@@ -25,49 +27,32 @@
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
_engineType = KBSuggestionEngineTypeLatin;
|
||||
_selectionCounts = [NSMutableDictionary dictionary];
|
||||
NSArray<NSString *> *defaults = [self.class kb_defaultWords];
|
||||
_priorityWords = [NSSet setWithArray:defaults];
|
||||
_words = [self kb_loadWords];
|
||||
_traditionalChineseWords = [self kb_loadTraditionalChineseWords];
|
||||
_simplifiedChineseWords = [self kb_loadSimplifiedChineseWords];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)suggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit {
|
||||
if (prefix.length == 0 || limit == 0) { return @[]; }
|
||||
NSString *lower = prefix.lowercaseString;
|
||||
NSMutableArray<NSString *> *matches = [NSMutableArray array];
|
||||
|
||||
for (NSString *word in self.words) {
|
||||
if ([word hasPrefix:lower]) {
|
||||
[matches addObject:word];
|
||||
if (matches.count >= limit * 3) {
|
||||
// Avoid scanning too many matches for long lists.
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 根据引擎类型选择不同的联想逻辑
|
||||
switch (self.engineType) {
|
||||
case KBSuggestionEngineTypePinyinTraditional:
|
||||
return [self kb_traditionalPinyinSuggestionsForPrefix:prefix limit:limit];
|
||||
case KBSuggestionEngineTypePinyinSimplified:
|
||||
return [self kb_simplifiedPinyinSuggestionsForPrefix:prefix limit:limit];
|
||||
case KBSuggestionEngineTypeBopomofo:
|
||||
return [self kb_bopomofoSuggestionsForPrefix:prefix limit:limit];
|
||||
case KBSuggestionEngineTypeLatin:
|
||||
default:
|
||||
return [self kb_latinSuggestionsForPrefix:prefix limit:limit];
|
||||
}
|
||||
|
||||
if (matches.count == 0) { return @[]; }
|
||||
|
||||
[matches sortUsingComparator:^NSComparisonResult(NSString *a, NSString *b) {
|
||||
NSInteger ca = self.selectionCounts[a].integerValue;
|
||||
NSInteger cb = self.selectionCounts[b].integerValue;
|
||||
if (ca != cb) {
|
||||
return (cb > ca) ? NSOrderedAscending : NSOrderedDescending;
|
||||
}
|
||||
BOOL pa = [self.priorityWords containsObject:a];
|
||||
BOOL pb = [self.priorityWords containsObject:b];
|
||||
if (pa != pb) {
|
||||
return pa ? NSOrderedAscending : NSOrderedDescending;
|
||||
}
|
||||
return [a compare:b];
|
||||
}];
|
||||
|
||||
if (matches.count > limit) {
|
||||
return [matches subarrayWithRange:NSMakeRange(0, limit)];
|
||||
}
|
||||
return matches.copy;
|
||||
}
|
||||
|
||||
- (void)recordSelection:(NSString *)word {
|
||||
@@ -164,4 +149,148 @@
|
||||
];
|
||||
}
|
||||
|
||||
#pragma mark - Engine Type Management
|
||||
|
||||
- (void)setEngineTypeFromString:(NSString *)engineTypeString {
|
||||
if ([engineTypeString isEqualToString:@"latin"]) {
|
||||
self.engineType = KBSuggestionEngineTypeLatin;
|
||||
} else if ([engineTypeString isEqualToString:@"pinyin_traditional"]) {
|
||||
self.engineType = KBSuggestionEngineTypePinyinTraditional;
|
||||
} else if ([engineTypeString isEqualToString:@"pinyin_simplified"]) {
|
||||
self.engineType = KBSuggestionEngineTypePinyinSimplified;
|
||||
} else if ([engineTypeString isEqualToString:@"bopomofo"]) {
|
||||
self.engineType = KBSuggestionEngineTypeBopomofo;
|
||||
} else {
|
||||
self.engineType = KBSuggestionEngineTypeLatin;
|
||||
}
|
||||
NSLog(@"[KBSuggestionEngine] Engine type set to: %@", engineTypeString);
|
||||
}
|
||||
|
||||
#pragma mark - Latin Suggestions
|
||||
|
||||
- (NSArray<NSString *> *)kb_latinSuggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit {
|
||||
NSString *lower = prefix.lowercaseString;
|
||||
NSMutableArray<NSString *> *matches = [NSMutableArray array];
|
||||
|
||||
for (NSString *word in self.words) {
|
||||
if ([word hasPrefix:lower]) {
|
||||
[matches addObject:word];
|
||||
if (matches.count >= limit * 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matches.count == 0) { return @[]; }
|
||||
|
||||
[matches sortUsingComparator:^NSComparisonResult(NSString *a, NSString *b) {
|
||||
NSInteger ca = self.selectionCounts[a].integerValue;
|
||||
NSInteger cb = self.selectionCounts[b].integerValue;
|
||||
if (ca != cb) {
|
||||
return (cb > ca) ? NSOrderedAscending : NSOrderedDescending;
|
||||
}
|
||||
BOOL pa = [self.priorityWords containsObject:a];
|
||||
BOOL pb = [self.priorityWords containsObject:b];
|
||||
if (pa != pb) {
|
||||
return pa ? NSOrderedAscending : NSOrderedDescending;
|
||||
}
|
||||
return [a compare:b];
|
||||
}];
|
||||
|
||||
if (matches.count > limit) {
|
||||
return [matches subarrayWithRange:NSMakeRange(0, limit)];
|
||||
}
|
||||
return matches.copy;
|
||||
}
|
||||
|
||||
#pragma mark - Traditional Chinese Pinyin Suggestions
|
||||
|
||||
- (NSArray<NSString *> *)kb_traditionalPinyinSuggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit {
|
||||
// 繁体拼音联想:输入拼音,返回繁体中文候选词
|
||||
NSString *lower = prefix.lowercaseString;
|
||||
NSMutableArray<NSString *> *matches = [NSMutableArray array];
|
||||
|
||||
// 这里应该使用拼音到繁体字的映射表
|
||||
// 目前先返回一些常用繁体词作为示例
|
||||
for (NSString *word in self.traditionalChineseWords) {
|
||||
// TODO: 实现拼音匹配逻辑
|
||||
// 这里需要一个拼音库来将输入的拼音转换为繁体字
|
||||
[matches addObject:word];
|
||||
if (matches.count >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return matches.copy;
|
||||
}
|
||||
|
||||
#pragma mark - Simplified Chinese Pinyin Suggestions
|
||||
|
||||
- (NSArray<NSString *> *)kb_simplifiedPinyinSuggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit {
|
||||
// 简体拼音联想:输入拼音,返回简体中文候选词
|
||||
NSString *lower = prefix.lowercaseString;
|
||||
NSMutableArray<NSString *> *matches = [NSMutableArray array];
|
||||
|
||||
// 这里应该使用拼音到简体字的映射表
|
||||
for (NSString *word in self.simplifiedChineseWords) {
|
||||
// TODO: 实现拼音匹配逻辑
|
||||
[matches addObject:word];
|
||||
if (matches.count >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return matches.copy;
|
||||
}
|
||||
|
||||
#pragma mark - Bopomofo (Zhuyin) Suggestions
|
||||
|
||||
- (NSArray<NSString *> *)kb_bopomofoSuggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit {
|
||||
// 注音联想:输入注音符号,返回繁体中文候选词
|
||||
NSMutableArray<NSString *> *matches = [NSMutableArray array];
|
||||
|
||||
// 这里应该使用注音到繁体字的映射表
|
||||
// 注音符号:ㄅㄆㄇㄈㄉㄊㄋㄌㄍㄎㄏㄐㄑㄒㄓㄔㄕㄖㄗㄘㄙ
|
||||
// 韵母:ㄚㄛㄜㄝㄞㄟㄠㄡㄢㄣㄤㄥㄦㄧㄨㄩ
|
||||
// 声调:ˊˇˋ˙
|
||||
for (NSString *word in self.traditionalChineseWords) {
|
||||
// TODO: 实现注音匹配逻辑
|
||||
[matches addObject:word];
|
||||
if (matches.count >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return matches.copy;
|
||||
}
|
||||
|
||||
#pragma mark - Chinese Word Loading
|
||||
|
||||
- (NSArray<NSString *> *)kb_loadTraditionalChineseWords {
|
||||
// 加载繁体中文常用词
|
||||
// 这里先返回一些示例词,实际应该从文件或数据库加载
|
||||
return @[
|
||||
@"你好", @"謝謝", @"對不起", @"再見", @"早安",
|
||||
@"晚安", @"請問", @"不好意思", @"沒關係", @"加油",
|
||||
@"台灣", @"台北", @"高雄", @"台中", @"台南",
|
||||
@"朋友", @"家人", @"工作", @"學習", @"生活",
|
||||
@"時間", @"地點", @"方法", @"問題", @"答案",
|
||||
@"喜歡", @"愛", @"想念", @"開心", @"快樂",
|
||||
@"美麗", @"漂亮", @"帥氣", @"可愛", @"溫柔"
|
||||
];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)kb_loadSimplifiedChineseWords {
|
||||
// 加载简体中文常用词
|
||||
return @[
|
||||
@"你好", @"谢谢", @"对不起", @"再见", @"早安",
|
||||
@"晚安", @"请问", @"不好意思", @"没关系", @"加油",
|
||||
@"中国", @"北京", @"上海", @"广州", @"深圳",
|
||||
@"朋友", @"家人", @"工作", @"学习", @"生活",
|
||||
@"时间", @"地点", @"方法", @"问题", @"答案",
|
||||
@"喜欢", @"爱", @"想念", @"开心", @"快乐",
|
||||
@"美丽", @"漂亮", @"帅气", @"可爱", @"温柔"
|
||||
];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -409,6 +409,226 @@
|
||||
"__comment_items": "本行按键列表;letter:x/digit:x/sym:x 或 keyDefs 中的 id"
|
||||
}
|
||||
]
|
||||
},
|
||||
"letters_azerty": {
|
||||
"__comment": "AZERTY 布局(法语/西班牙语)",
|
||||
"rows": [
|
||||
{
|
||||
"__comment": "第一行 azertyuiop",
|
||||
"align": "left",
|
||||
"insetLeft": 4,
|
||||
"insetRight": 4,
|
||||
"gap": 5,
|
||||
"items": [
|
||||
"letter:a", "letter:z", "letter:e", "letter:r", "letter:t",
|
||||
"letter:y", "letter:u", "letter:i", "letter:o", "letter:p"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__comment": "第二行 qsdfghjklm",
|
||||
"align": "center",
|
||||
"insetLeft": 0,
|
||||
"insetRight": 0,
|
||||
"gap": 5,
|
||||
"items": [
|
||||
"letter:q", "letter:s", "letter:d", "letter:f", "letter:g",
|
||||
"letter:h", "letter:j", "letter:k", "letter:l", "letter:m"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__comment": "第三行:shift + wxcvbn + backspace",
|
||||
"align": "left",
|
||||
"insetLeft": 4,
|
||||
"insetRight": 4,
|
||||
"gap": 5,
|
||||
"segments": {
|
||||
"left": [
|
||||
{ "id": "shift", "width": "controlWidth" }
|
||||
],
|
||||
"center": [
|
||||
"letter:w", "letter:x", "letter:c", "letter:v", "letter:b", "letter:n"
|
||||
],
|
||||
"right": [
|
||||
{ "id": "backspace", "width": "controlWidth" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"__comment": "第四行:123/emoji/space/send",
|
||||
"align": "left",
|
||||
"insetLeft": 4,
|
||||
"insetRight": 4,
|
||||
"gap": 5,
|
||||
"items": [
|
||||
"mode_123", "emoji", "space", "send"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"letters_qwertz": {
|
||||
"__comment": "QWERTZ 布局(德语/西班牙语)",
|
||||
"rows": [
|
||||
{
|
||||
"__comment": "第一行 qwertzuiop",
|
||||
"align": "left",
|
||||
"insetLeft": 4,
|
||||
"insetRight": 4,
|
||||
"gap": 5,
|
||||
"items": [
|
||||
"letter:q", "letter:w", "letter:e", "letter:r", "letter:t",
|
||||
"letter:z", "letter:u", "letter:i", "letter:o", "letter:p"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__comment": "第二行 asdfghjkl",
|
||||
"align": "center",
|
||||
"insetLeft": 0,
|
||||
"insetRight": 0,
|
||||
"gap": 5,
|
||||
"items": [
|
||||
"letter:a", "letter:s", "letter:d", "letter:f", "letter:g",
|
||||
"letter:h", "letter:j", "letter:k", "letter:l"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__comment": "第三行:shift + yxcvbnm + backspace",
|
||||
"align": "left",
|
||||
"insetLeft": 4,
|
||||
"insetRight": 4,
|
||||
"gap": 5,
|
||||
"segments": {
|
||||
"left": [
|
||||
{ "id": "shift", "width": "controlWidth" }
|
||||
],
|
||||
"center": [
|
||||
"letter:y", "letter:x", "letter:c", "letter:v", "letter:b", "letter:n", "letter:m"
|
||||
],
|
||||
"right": [
|
||||
{ "id": "backspace", "width": "controlWidth" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"__comment": "第四行:123/emoji/space/send",
|
||||
"align": "left",
|
||||
"insetLeft": 4,
|
||||
"insetRight": 4,
|
||||
"gap": 5,
|
||||
"items": [
|
||||
"mode_123", "emoji", "space", "send"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"letters_bopomofo_full": {
|
||||
"__comment": "繁体注音全键盘布局",
|
||||
"rows": [
|
||||
{
|
||||
"__comment": "第一行注音符号",
|
||||
"align": "left",
|
||||
"insetLeft": 4,
|
||||
"insetRight": 4,
|
||||
"gap": 5,
|
||||
"items": [
|
||||
"letter:ㄅ", "letter:ㄉ", "letter:ˇ", "letter:ˋ", "letter:ㄓ",
|
||||
"letter:ˊ", "letter:˙", "letter:ㄚ", "letter:ㄞ", "letter:ㄢ"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__comment": "第二行注音符号",
|
||||
"align": "center",
|
||||
"insetLeft": 0,
|
||||
"insetRight": 0,
|
||||
"gap": 5,
|
||||
"items": [
|
||||
"letter:ㄆ", "letter:ㄊ", "letter:ㄍ", "letter:ㄐ", "letter:ㄔ",
|
||||
"letter:ㄗ", "letter:ㄧ", "letter:ㄛ", "letter:ㄟ", "letter:ㄣ"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__comment": "第三行:shift + 注音符号 + backspace",
|
||||
"align": "left",
|
||||
"insetLeft": 4,
|
||||
"insetRight": 4,
|
||||
"gap": 5,
|
||||
"segments": {
|
||||
"left": [
|
||||
{ "id": "shift", "width": "controlWidth" }
|
||||
],
|
||||
"center": [
|
||||
"letter:ㄇ", "letter:ㄋ", "letter:ㄎ", "letter:ㄑ", "letter:ㄕ", "letter:ㄘ", "letter:ㄨ"
|
||||
],
|
||||
"right": [
|
||||
{ "id": "backspace", "width": "controlWidth" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"__comment": "第四行:123/emoji/space/send",
|
||||
"align": "left",
|
||||
"insetLeft": 4,
|
||||
"insetRight": 4,
|
||||
"gap": 5,
|
||||
"items": [
|
||||
"mode_123", "emoji", "space", "send"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"letters_bopomofo_standard": {
|
||||
"__comment": "繁体注音标准布局",
|
||||
"rows": [
|
||||
{
|
||||
"__comment": "第一行注音符号",
|
||||
"align": "left",
|
||||
"insetLeft": 4,
|
||||
"insetRight": 4,
|
||||
"gap": 5,
|
||||
"items": [
|
||||
"letter:ㄅ", "letter:ㄆ", "letter:ㄇ", "letter:ㄈ", "letter:ㄉ",
|
||||
"letter:ㄊ", "letter:ㄋ", "letter:ㄌ", "letter:ㄍ", "letter:ㄎ"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__comment": "第二行注音符号",
|
||||
"align": "center",
|
||||
"insetLeft": 0,
|
||||
"insetRight": 0,
|
||||
"gap": 5,
|
||||
"items": [
|
||||
"letter:ㄏ", "letter:ㄐ", "letter:ㄑ", "letter:ㄒ", "letter:ㄓ",
|
||||
"letter:ㄔ", "letter:ㄕ", "letter:ㄖ", "letter:ㄗ"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__comment": "第三行:shift + 注音符号 + backspace",
|
||||
"align": "left",
|
||||
"insetLeft": 4,
|
||||
"insetRight": 4,
|
||||
"gap": 5,
|
||||
"segments": {
|
||||
"left": [
|
||||
{ "id": "shift", "width": "controlWidth" }
|
||||
],
|
||||
"center": [
|
||||
"letter:ㄘ", "letter:ㄙ", "letter:ㄧ", "letter:ㄨ", "letter:ㄩ", "letter:ㄚ", "letter:ㄛ"
|
||||
],
|
||||
"right": [
|
||||
{ "id": "backspace", "width": "controlWidth" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"__comment": "第四行:123/emoji/space/send",
|
||||
"align": "left",
|
||||
"insetLeft": 4,
|
||||
"insetRight": 4,
|
||||
"gap": 5,
|
||||
"items": [
|
||||
"mode_123", "emoji", "space", "send"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -45,6 +45,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// 更新联想候选
|
||||
- (void)kb_setSuggestions:(NSArray<NSString *> *)suggestions;
|
||||
|
||||
/// 根据 profileId 重新加载键盘布局
|
||||
- (void)reloadLayoutWithProfileId:(NSString *)profileId;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -305,4 +305,13 @@
|
||||
self.suggestionBar.alpha = shouldShow ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
- (void)reloadLayoutWithProfileId:(NSString *)profileId {
|
||||
if (profileId.length == 0) {
|
||||
NSLog(@"[KBKeyBoardMainView] reloadLayoutWithProfileId: empty profileId");
|
||||
return;
|
||||
}
|
||||
NSLog(@"[KBKeyBoardMainView] Reloading layout with profileId: %@", profileId);
|
||||
[self.keyboardView reloadLayoutWithProfileId:profileId];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -27,7 +27,9 @@ typedef NS_ENUM(NSInteger, KBKeyboardLayoutStyle) {
|
||||
@property (nonatomic, assign, getter=isShiftOn) BOOL shiftOn; // 大小写状态
|
||||
// 在数字布局中,是否显示“更多符号”(#+=)页
|
||||
@property (nonatomic, assign) BOOL symbolsMoreOn;
|
||||
@property (nonatomic, copy) NSString *currentLayoutJsonId; // 当前使用的布局 JSON ID
|
||||
|
||||
- (void)reloadKeys; // 当布局样式/大小写变化时调用
|
||||
- (void)reloadLayoutWithProfileId:(NSString *)profileId; // 根据 profileId 重新加载布局
|
||||
|
||||
@end
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#import "KBKeyPreviewView.h"
|
||||
#import "KBBackspaceLongPressHandler.h"
|
||||
#import "KBKeyboardLayoutConfig.h"
|
||||
#import "KBKeyboardLayoutResolver.h"
|
||||
|
||||
// UI 常量统一管理,方便后续调试样式(以 375 宽设计稿为基准,通过 KBFit 做等比缩放)
|
||||
#define kKBRowVerticalSpacing KBFit(8.0f)
|
||||
@@ -46,6 +47,17 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
||||
// 默认小写:与需求一致,初始不开启 Shift
|
||||
_shiftOn = NO;
|
||||
_symbolsMoreOn = NO; // 数字面板默认第一页(123)
|
||||
|
||||
// 从 App Group 读取当前 profileId 并设置布局
|
||||
NSString *profileId = [[KBKeyboardLayoutResolver sharedResolver] currentProfileId];
|
||||
if (profileId.length > 0) {
|
||||
_currentLayoutJsonId = [[KBKeyboardLayoutResolver sharedResolver] layoutJsonIdForProfileId:profileId];
|
||||
NSLog(@"[KBKeyboardView] Loaded profileId: %@, layoutJsonId: %@", profileId, _currentLayoutJsonId);
|
||||
} else {
|
||||
_currentLayoutJsonId = @"letters";
|
||||
NSLog(@"[KBKeyboardView] No profileId found, using default 'letters'");
|
||||
}
|
||||
|
||||
self.layoutConfig = [KBKeyboardLayoutConfig sharedConfig];
|
||||
self.backspaceHandler = [[KBBackspaceLongPressHandler alloc] initWithContainerView:self];
|
||||
[self buildBase];
|
||||
@@ -866,7 +878,28 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
|
||||
if (self.layoutStyle == KBKeyboardLayoutStyleNumbers) {
|
||||
return [self kb_layoutForName:(self.symbolsMoreOn ? @"symbolsMore" : @"numbers")];
|
||||
}
|
||||
return [self kb_layoutForName:@"letters"];
|
||||
// 使用当前设置的 layoutJsonId,如果为空则回退到 "letters"
|
||||
NSString *layoutName = self.currentLayoutJsonId.length > 0 ? self.currentLayoutJsonId : @"letters";
|
||||
return [self kb_layoutForName:layoutName];
|
||||
}
|
||||
|
||||
- (void)reloadLayoutWithProfileId:(NSString *)profileId {
|
||||
if (profileId.length == 0) {
|
||||
NSLog(@"[KBKeyboardView] reloadLayoutWithProfileId: empty profileId, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *newLayoutJsonId = [[KBKeyboardLayoutResolver sharedResolver] layoutJsonIdForProfileId:profileId];
|
||||
if ([newLayoutJsonId isEqualToString:self.currentLayoutJsonId]) {
|
||||
NSLog(@"[KBKeyboardView] Layout already loaded: %@", newLayoutJsonId);
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[KBKeyboardView] Switching layout from %@ to %@", self.currentLayoutJsonId, newLayoutJsonId);
|
||||
self.currentLayoutJsonId = newLayoutJsonId;
|
||||
|
||||
// 重新加载键盘布局
|
||||
[self reloadKeys];
|
||||
}
|
||||
|
||||
- (void)kb_buildLegacyLayout {
|
||||
|
||||
Reference in New Issue
Block a user