Compare commits
16 Commits
1096f24c57
...
dev_修改键盘UI
| Author | SHA1 | Date | |
|---|---|---|---|
| 85dcd72a5d | |||
| 21fcbe3665 | |||
| 1b6724f043 | |||
| ef332ecaa1 | |||
| 3d6d673c0b | |||
| 674f09d5b6 | |||
| 11d8f78b1b | |||
| bbacef4ff7 | |||
| 8e692647d3 | |||
| 6f80f969a4 | |||
| bdf2a9af80 | |||
| e858d35722 | |||
| f2d5210313 | |||
| 1b0af3e2d6 | |||
| 0965cd3c7e | |||
| c3909d63da |
@@ -5,12 +5,12 @@
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "切图 270@2x.png",
|
||||
"filename" : "切图 271@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "切图 270@3x.png",
|
||||
"filename" : "切图 271@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 11 KiB |
BIN
CustomKeyboard/KeyboardAssets.xcassets/ai_key_icon.imageset/切图 271@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
CustomKeyboard/KeyboardAssets.xcassets/ai_key_icon.imageset/切图 271@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
@@ -4,15 +4,79 @@
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "light"
|
||||
}
|
||||
],
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "kb_del_icon@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "light"
|
||||
}
|
||||
],
|
||||
"filename" : "kb_del_icon@2x 1.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "切图 256@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "kb_del_icon@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "light"
|
||||
}
|
||||
],
|
||||
"filename" : "kb_del_icon@3x 1.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "切图 256@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
BIN
CustomKeyboard/KeyboardAssets.xcassets/kb_del_icon.imageset/kb_del_icon@2x 1.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
CustomKeyboard/KeyboardAssets.xcassets/kb_del_icon.imageset/kb_del_icon@3x 1.png
vendored
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
CustomKeyboard/KeyboardAssets.xcassets/kb_del_icon.imageset/切图 256@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1008 B |
BIN
CustomKeyboard/KeyboardAssets.xcassets/kb_del_icon.imageset/切图 256@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
96
CustomKeyboard/Model/KBKeyboardLayoutConfig.h
Normal file
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// KBKeyboardLayoutConfig.h
|
||||
// CustomKeyboard
|
||||
//
|
||||
// 键盘布局配置模型(由 JSON 驱动)
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface KBKeyboardLayoutMetrics : NSObject
|
||||
@property (nonatomic, strong, nullable) NSNumber *rowSpacing;
|
||||
@property (nonatomic, strong, nullable) NSNumber *topInset;
|
||||
@property (nonatomic, strong, nullable) NSNumber *bottomInset;
|
||||
@property (nonatomic, strong, nullable) NSNumber *keyHeight;
|
||||
@property (nonatomic, strong, nullable) NSNumber *edgeInset;
|
||||
@property (nonatomic, strong, nullable) NSNumber *gap;
|
||||
@property (nonatomic, strong, nullable) NSNumber *letterWidth;
|
||||
@property (nonatomic, strong, nullable) NSNumber *controlWidth;
|
||||
@property (nonatomic, strong, nullable) NSNumber *sendWidth;
|
||||
@property (nonatomic, strong, nullable) NSNumber *symbolsWideWidth;
|
||||
@property (nonatomic, strong, nullable) NSNumber *symbolsSideWidth;
|
||||
@end
|
||||
|
||||
@interface KBKeyboardLayoutFonts : NSObject
|
||||
@property (nonatomic, strong, nullable) NSNumber *letter;
|
||||
@property (nonatomic, strong, nullable) NSNumber *digit;
|
||||
@property (nonatomic, strong, nullable) NSNumber *symbol;
|
||||
@property (nonatomic, strong, nullable) NSNumber *mode;
|
||||
@property (nonatomic, strong, nullable) NSNumber *space;
|
||||
@property (nonatomic, strong, nullable) NSNumber *send;
|
||||
@end
|
||||
|
||||
@interface KBKeyboardKeyDef : NSObject
|
||||
@property (nonatomic, copy, nullable) NSString *type;
|
||||
@property (nonatomic, copy, nullable) NSString *title;
|
||||
@property (nonatomic, copy, nullable) NSString *selectedTitle;
|
||||
@property (nonatomic, copy, nullable) NSString *symbolName;
|
||||
@property (nonatomic, copy, nullable) NSString *selectedSymbolName;
|
||||
@property (nonatomic, copy, nullable) NSString *font;
|
||||
@property (nonatomic, copy, nullable) NSString *width;
|
||||
@property (nonatomic, strong, nullable) NSNumber *widthValue;
|
||||
@property (nonatomic, copy, nullable) NSString *backgroundColor;
|
||||
@end
|
||||
|
||||
@interface KBKeyboardRowItem : NSObject
|
||||
@property (nonatomic, copy, nullable) NSString *itemId;
|
||||
@property (nonatomic, copy, nullable) NSString *width;
|
||||
@property (nonatomic, strong, nullable) NSNumber *widthValue;
|
||||
+ (NSArray<KBKeyboardRowItem *> *)itemsFromRawArray:(NSArray *)raw;
|
||||
@end
|
||||
|
||||
@interface KBKeyboardRowSegments : NSObject
|
||||
@property (nonatomic, strong, nullable) NSArray *left;
|
||||
@property (nonatomic, strong, nullable) NSArray *center;
|
||||
@property (nonatomic, strong, nullable) NSArray *right;
|
||||
- (NSArray<KBKeyboardRowItem *> *)leftItems;
|
||||
- (NSArray<KBKeyboardRowItem *> *)centerItems;
|
||||
- (NSArray<KBKeyboardRowItem *> *)rightItems;
|
||||
@end
|
||||
|
||||
@interface KBKeyboardRowConfig : NSObject
|
||||
@property (nonatomic, strong, nullable) NSNumber *height;
|
||||
@property (nonatomic, strong, nullable) NSNumber *insetLeft;
|
||||
@property (nonatomic, strong, nullable) NSNumber *insetRight;
|
||||
@property (nonatomic, strong, nullable) NSNumber *gap;
|
||||
@property (nonatomic, copy, nullable) NSString *align;
|
||||
@property (nonatomic, strong, nullable) NSArray *items;
|
||||
@property (nonatomic, strong, nullable) KBKeyboardRowSegments *segments;
|
||||
- (NSArray<KBKeyboardRowItem *> *)resolvedItems;
|
||||
@end
|
||||
|
||||
@interface KBKeyboardLayout : NSObject
|
||||
@property (nonatomic, strong, nullable) NSArray<KBKeyboardRowConfig *> *rows;
|
||||
@end
|
||||
|
||||
@interface KBKeyboardLayoutConfig : NSObject
|
||||
@property (nonatomic, assign) CGFloat designWidth;
|
||||
@property (nonatomic, strong, nullable) KBKeyboardLayoutMetrics *metrics;
|
||||
@property (nonatomic, strong, nullable) KBKeyboardLayoutFonts *fonts;
|
||||
@property (nonatomic, copy, nullable) NSString *defaultKeyBackground;
|
||||
@property (nonatomic, strong, nullable) NSDictionary<NSString *, KBKeyboardKeyDef *> *keyDefs;
|
||||
@property (nonatomic, strong, nullable) NSDictionary<NSString *, KBKeyboardLayout *> *layouts;
|
||||
|
||||
+ (nullable instancetype)sharedConfig;
|
||||
+ (nullable instancetype)configFromJSONData:(NSData *)data;
|
||||
- (CGFloat)scaledValue:(CGFloat)designValue;
|
||||
- (CGFloat)keyboardAreaDesignHeight;
|
||||
- (CGFloat)keyboardAreaScaledHeight;
|
||||
- (nullable KBKeyboardLayout *)layoutForName:(NSString *)name;
|
||||
- (nullable KBKeyboardKeyDef *)keyDefForIdentifier:(NSString *)identifier;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
187
CustomKeyboard/Model/KBKeyboardLayoutConfig.m
Normal file
@@ -0,0 +1,187 @@
|
||||
//
|
||||
// KBKeyboardLayoutConfig.m
|
||||
// CustomKeyboard
|
||||
//
|
||||
|
||||
#import "KBKeyboardLayoutConfig.h"
|
||||
#import <MJExtension/MJExtension.h>
|
||||
#import "KBConfig.h"
|
||||
|
||||
static NSString * const kKBKeyboardLayoutConfigFileName = @"kb_keyboard_layout_config";
|
||||
|
||||
@implementation KBKeyboardLayoutMetrics
|
||||
@end
|
||||
|
||||
@implementation KBKeyboardLayoutFonts
|
||||
@end
|
||||
|
||||
@implementation KBKeyboardKeyDef
|
||||
@end
|
||||
|
||||
@implementation KBKeyboardRowItem
|
||||
|
||||
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
|
||||
return @{ @"itemId": @"id" };
|
||||
}
|
||||
|
||||
+ (NSArray<KBKeyboardRowItem *> *)itemsFromRawArray:(NSArray *)raw {
|
||||
if (![raw isKindOfClass:[NSArray class]] || raw.count == 0) {
|
||||
return @[];
|
||||
}
|
||||
NSMutableArray<KBKeyboardRowItem *> *items = [NSMutableArray arrayWithCapacity:raw.count];
|
||||
for (id obj in raw) {
|
||||
if ([obj isKindOfClass:[NSString class]]) {
|
||||
KBKeyboardRowItem *item = [KBKeyboardRowItem new];
|
||||
item.itemId = (NSString *)obj;
|
||||
[items addObject:item];
|
||||
continue;
|
||||
}
|
||||
if ([obj isKindOfClass:[NSDictionary class]]) {
|
||||
KBKeyboardRowItem *item = [KBKeyboardRowItem mj_objectWithKeyValues:obj];
|
||||
if (item.itemId.length == 0) {
|
||||
NSString *fallback = ((NSDictionary *)obj)[@"id"];
|
||||
if ([fallback isKindOfClass:[NSString class]]) {
|
||||
item.itemId = fallback;
|
||||
}
|
||||
}
|
||||
if (item.itemId.length > 0) {
|
||||
[items addObject:item];
|
||||
}
|
||||
}
|
||||
}
|
||||
return items.copy;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBKeyboardRowSegments
|
||||
|
||||
- (NSArray<KBKeyboardRowItem *> *)leftItems {
|
||||
return [KBKeyboardRowItem itemsFromRawArray:self.left ?: @[]];
|
||||
}
|
||||
|
||||
- (NSArray<KBKeyboardRowItem *> *)centerItems {
|
||||
return [KBKeyboardRowItem itemsFromRawArray:self.center ?: @[]];
|
||||
}
|
||||
|
||||
- (NSArray<KBKeyboardRowItem *> *)rightItems {
|
||||
return [KBKeyboardRowItem itemsFromRawArray:self.right ?: @[]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBKeyboardRowConfig
|
||||
|
||||
- (NSArray<KBKeyboardRowItem *> *)resolvedItems {
|
||||
return [KBKeyboardRowItem itemsFromRawArray:self.items ?: @[]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBKeyboardLayout
|
||||
|
||||
+ (NSDictionary *)mj_objectClassInArray {
|
||||
return @{ @"rows": [KBKeyboardRowConfig class] };
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBKeyboardLayoutConfig
|
||||
|
||||
+ (instancetype)sharedConfig {
|
||||
static KBKeyboardLayoutConfig *config = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *path = [[NSBundle mainBundle] pathForResource:kKBKeyboardLayoutConfigFileName ofType:@"json"];
|
||||
NSData *data = path.length ? [NSData dataWithContentsOfFile:path] : nil;
|
||||
config = data ? [KBKeyboardLayoutConfig configFromJSONData:data] : nil;
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
+ (instancetype)configFromJSONData:(NSData *)data {
|
||||
if (data.length == 0) { return nil; }
|
||||
NSError *error = nil;
|
||||
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
|
||||
if (error || ![json isKindOfClass:[NSDictionary class]]) {
|
||||
return nil;
|
||||
}
|
||||
NSDictionary *dict = (NSDictionary *)json;
|
||||
KBKeyboardLayoutConfig *config = [KBKeyboardLayoutConfig mj_objectWithKeyValues:dict];
|
||||
|
||||
NSDictionary *keyDefsRaw = dict[@"keyDefs"];
|
||||
if ([keyDefsRaw isKindOfClass:[NSDictionary class]]) {
|
||||
NSMutableDictionary<NSString *, KBKeyboardKeyDef *> *defs = [NSMutableDictionary dictionaryWithCapacity:keyDefsRaw.count];
|
||||
[keyDefsRaw enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||
if (![key isKindOfClass:[NSString class]] || ![obj isKindOfClass:[NSDictionary class]]) {
|
||||
return;
|
||||
}
|
||||
KBKeyboardKeyDef *def = [KBKeyboardKeyDef mj_objectWithKeyValues:obj];
|
||||
if (def) {
|
||||
defs[key] = def;
|
||||
}
|
||||
}];
|
||||
config.keyDefs = defs.copy;
|
||||
}
|
||||
|
||||
NSDictionary *layoutsRaw = dict[@"layouts"];
|
||||
if ([layoutsRaw isKindOfClass:[NSDictionary class]]) {
|
||||
NSMutableDictionary<NSString *, KBKeyboardLayout *> *layouts = [NSMutableDictionary dictionaryWithCapacity:layoutsRaw.count];
|
||||
[layoutsRaw enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||
if (![key isKindOfClass:[NSString class]] || ![obj isKindOfClass:[NSDictionary class]]) {
|
||||
return;
|
||||
}
|
||||
KBKeyboardLayout *layout = [KBKeyboardLayout mj_objectWithKeyValues:obj];
|
||||
if (layout) {
|
||||
layouts[key] = layout;
|
||||
}
|
||||
}];
|
||||
config.layouts = layouts.copy;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
- (CGFloat)scaledValue:(CGFloat)designValue {
|
||||
CGFloat baseWidth = (self.designWidth > 0.0) ? self.designWidth : KB_DESIGN_WIDTH;
|
||||
CGFloat scale = KBScreenWidth() / baseWidth;
|
||||
return designValue * scale;
|
||||
}
|
||||
|
||||
- (CGFloat)keyboardAreaDesignHeight {
|
||||
KBKeyboardLayout *layout = [self layoutForName:@"letters"] ?: self.layouts.allValues.firstObject;
|
||||
NSUInteger rowCount = layout.rows.count;
|
||||
if (rowCount == 0) { return 0.0; }
|
||||
|
||||
CGFloat rowSpacing = self.metrics.rowSpacing.doubleValue;
|
||||
CGFloat topInset = self.metrics.topInset.doubleValue;
|
||||
CGFloat bottomInset = self.metrics.bottomInset.doubleValue;
|
||||
|
||||
CGFloat total = topInset + bottomInset + rowSpacing * (rowCount - 1);
|
||||
for (KBKeyboardRowConfig *row in layout.rows) {
|
||||
CGFloat height = row.height.doubleValue;
|
||||
if (height <= 0.0) {
|
||||
height = self.metrics.keyHeight.doubleValue;
|
||||
}
|
||||
if (height <= 0.0) { height = 40.0; }
|
||||
total += height;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
- (CGFloat)keyboardAreaScaledHeight {
|
||||
CGFloat designHeight = [self keyboardAreaDesignHeight];
|
||||
return designHeight > 0.0 ? [self scaledValue:designHeight] : 0.0;
|
||||
}
|
||||
|
||||
- (KBKeyboardLayout *)layoutForName:(NSString *)name {
|
||||
if (name.length == 0) { return nil; }
|
||||
return self.layouts[name];
|
||||
}
|
||||
|
||||
- (KBKeyboardKeyDef *)keyDefForIdentifier:(NSString *)identifier {
|
||||
if (identifier.length == 0) { return nil; }
|
||||
return self.keyDefs[identifier];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -8,7 +8,7 @@
|
||||
// - 兼容后端“/t”作为分段标记:可自动替换为制表符“\t”
|
||||
// - 首段去首个“\t”:若首次正文以一个制表符起始(允许前导空白),可只移除“一个”\t
|
||||
//
|
||||
|
||||
// 暂未使用
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//
|
||||
// Created by Mac on 2025/11/12.
|
||||
//
|
||||
|
||||
// 暂未使用
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
/* 字母 g(小写) */
|
||||
"letter_g_lower" = "key_g";
|
||||
/* 字母 G(大写) */
|
||||
"letter_g_upper" = "key_f_up";
|
||||
"letter_g_upper" = "key_g_up";
|
||||
|
||||
/* 字母 h(小写) */
|
||||
"letter_h_lower" = "key_h";
|
||||
@@ -246,4 +246,3 @@
|
||||
"emoji_panel" = "key_emoji";
|
||||
/* 发送/换行键 */
|
||||
"return" = "key_send";
|
||||
|
||||
|
||||
414
CustomKeyboard/Resource/kb_keyboard_layout_config.json
Normal file
@@ -0,0 +1,414 @@
|
||||
{
|
||||
"__comment": "键盘布局配置:所有尺寸为设计稿值(会按 designWidth 等比缩放)",
|
||||
"designWidth": 375,
|
||||
"__comment_designWidth": "设计稿宽度(如 375),用于计算缩放比例",
|
||||
"defaultKeyBackground": "#FFFFFF",
|
||||
"__comment_defaultKeyBackground": "无皮肤时按键默认背景色",
|
||||
"metrics": {
|
||||
"__comment": "全局尺寸参数(单位:pt,按 designWidth 缩放)",
|
||||
"rowSpacing": 8,
|
||||
"__comment_rowSpacing": "行间距(垂直)",
|
||||
"topInset": 8,
|
||||
"__comment_topInset": "键盘顶部内边距",
|
||||
"bottomInset": 6,
|
||||
"__comment_bottomInset": "键盘底部内边距",
|
||||
"keyHeight": 41,
|
||||
"__comment_keyHeight": "默认按键高度",
|
||||
"edgeInset": 4,
|
||||
"__comment_edgeInset": "行左右内边距(默认)",
|
||||
"gap": 5,
|
||||
"__comment_gap": "按键之间水平间距",
|
||||
"letterWidth": 32,
|
||||
"__comment_letterWidth": "字母键默认宽度",
|
||||
"controlWidth": 41,
|
||||
"__comment_controlWidth": "控制键宽度(如 shift/backspace/123)",
|
||||
"sendWidth": 88,
|
||||
"__comment_sendWidth": "send 键宽度",
|
||||
"symbolsWideWidth": 47,
|
||||
"__comment_symbolsWideWidth": "符号第3行中间大键宽度",
|
||||
"symbolsSideWidth": 41,
|
||||
"__comment_symbolsSideWidth": "符号第3行左右控制键宽度"
|
||||
},
|
||||
"fonts": {
|
||||
"__comment": "字体大小(pt)",
|
||||
"letter": 20,
|
||||
"__comment_letter": "字母键字体大小",
|
||||
"digit": 20,
|
||||
"__comment_digit": "数字键字体大小",
|
||||
"symbol": 18,
|
||||
"__comment_symbol": "符号键字体大小",
|
||||
"mode": 14,
|
||||
"__comment_mode": "模式切换键字体大小(ABC/#+=/123)",
|
||||
"space": 18,
|
||||
"__comment_space": "空格键字体大小",
|
||||
"send": 18,
|
||||
"__comment_send": "发送键字体大小"
|
||||
},
|
||||
"keyDefs": {
|
||||
"__comment": "特殊功能键配置(id 对应布局中的 item)",
|
||||
"shift": {
|
||||
"__comment": "大小写切换键",
|
||||
"type": "shift",
|
||||
"__comment_type": "类型:shift/backspace/mode/symbolsToggle/space/return/custom",
|
||||
"title": "⇧",
|
||||
"__comment_title": "按钮文本(无皮肤时显示)",
|
||||
"symbolName": "shift",
|
||||
"__comment_symbolName": "无皮肤时使用 SF Symbol 名称",
|
||||
"selectedSymbolName": "shift.fill",
|
||||
"__comment_selectedSymbolName": "选中态 SF Symbol 名称",
|
||||
"font": "symbol",
|
||||
"__comment_font": "使用 fonts 中哪一类字号",
|
||||
"width": "controlWidth",
|
||||
"__comment_width": "宽度:引用 metrics 中字段或具体数值",
|
||||
"backgroundColor": "#B7BBC4",
|
||||
"__comment_backgroundColor": "按键背景色"
|
||||
},
|
||||
"backspace": {
|
||||
"__comment": "删除键",
|
||||
"type": "backspace",
|
||||
"__comment_type": "类型:shift/backspace/mode/symbolsToggle/space/return/custom",
|
||||
"title": "⌫",
|
||||
"__comment_title": "按钮文本(无皮肤时显示)",
|
||||
"font": "symbol",
|
||||
"__comment_font": "使用 fonts 中哪一类字号",
|
||||
"width": "controlWidth",
|
||||
"__comment_width": "宽度:引用 metrics 中字段或具体数值",
|
||||
"backgroundColor": "#B7BBC4",
|
||||
"__comment_backgroundColor": "按键背景色"
|
||||
},
|
||||
"mode_123": {
|
||||
"__comment": "字母面板左下角 123",
|
||||
"type": "mode",
|
||||
"__comment_type": "类型:shift/backspace/mode/symbolsToggle/space/return/custom",
|
||||
"title": "123",
|
||||
"__comment_title": "按钮文本(无皮肤时显示)",
|
||||
"font": "mode",
|
||||
"__comment_font": "使用 fonts 中哪一类字号",
|
||||
"width": "controlWidth",
|
||||
"__comment_width": "宽度:引用 metrics 中字段或具体数值",
|
||||
"backgroundColor": "#B7BBC4",
|
||||
"__comment_backgroundColor": "按键背景色"
|
||||
},
|
||||
"mode_abc": {
|
||||
"__comment": "数字面板左下角 ABC",
|
||||
"type": "mode",
|
||||
"__comment_type": "类型:shift/backspace/mode/symbolsToggle/space/return/custom",
|
||||
"title": "ABC",
|
||||
"__comment_title": "按钮文本(无皮肤时显示)",
|
||||
"font": "mode",
|
||||
"__comment_font": "使用 fonts 中哪一类字号",
|
||||
"width": "controlWidth",
|
||||
"__comment_width": "宽度:引用 metrics 中字段或具体数值",
|
||||
"backgroundColor": "#B7BBC4",
|
||||
"__comment_backgroundColor": "按键背景色"
|
||||
},
|
||||
"symbols_toggle_more": {
|
||||
"__comment": "数字面板内 123 -> #+=",
|
||||
"type": "symbolsToggle",
|
||||
"__comment_type": "类型:shift/backspace/mode/symbolsToggle/space/return/custom",
|
||||
"title": "#+=",
|
||||
"__comment_title": "按钮文本(无皮肤时显示)",
|
||||
"font": "mode",
|
||||
"__comment_font": "使用 fonts 中哪一类字号",
|
||||
"width": "symbolsSideWidth",
|
||||
"__comment_width": "宽度:引用 metrics 中字段或具体数值",
|
||||
"backgroundColor": "#B7BBC4",
|
||||
"__comment_backgroundColor": "按键背景色"
|
||||
},
|
||||
"symbols_toggle_123": {
|
||||
"__comment": "数字面板内 #+= -> 123",
|
||||
"type": "symbolsToggle",
|
||||
"__comment_type": "类型:shift/backspace/mode/symbolsToggle/space/return/custom",
|
||||
"title": "123",
|
||||
"__comment_title": "按钮文本(无皮肤时显示)",
|
||||
"font": "mode",
|
||||
"__comment_font": "使用 fonts 中哪一类字号",
|
||||
"width": "symbolsSideWidth",
|
||||
"__comment_width": "宽度:引用 metrics 中字段或具体数值",
|
||||
"backgroundColor": "#B7BBC4",
|
||||
"__comment_backgroundColor": "按键背景色"
|
||||
},
|
||||
"emoji": {
|
||||
"__comment": "emoji 功能键",
|
||||
"type": "custom",
|
||||
"__comment_type": "类型:shift/backspace/mode/symbolsToggle/space/return/custom",
|
||||
"title": "😁",
|
||||
"__comment_title": "按钮文本(无皮肤时显示)",
|
||||
"font": "symbol",
|
||||
"__comment_font": "使用 fonts 中哪一类字号",
|
||||
"width": "controlWidth",
|
||||
"__comment_width": "宽度:引用 metrics 中字段或具体数值",
|
||||
"backgroundColor": "#B7BBC4",
|
||||
"__comment_backgroundColor": "按键背景色"
|
||||
},
|
||||
"space": {
|
||||
"__comment": "空格键",
|
||||
"type": "space",
|
||||
"__comment_type": "类型:shift/backspace/mode/symbolsToggle/space/return/custom",
|
||||
"title": "space",
|
||||
"__comment_title": "按钮文本(无皮肤时显示)",
|
||||
"font": "space",
|
||||
"__comment_font": "使用 fonts 中哪一类字号",
|
||||
"width": "flex",
|
||||
"__comment_width": "flex 表示自动占满剩余空间"
|
||||
},
|
||||
"send": {
|
||||
"__comment": "发送键",
|
||||
"type": "return",
|
||||
"__comment_type": "类型:shift/backspace/mode/symbolsToggle/space/return/custom",
|
||||
"title": "send",
|
||||
"__comment_title": "按钮文本(无皮肤时显示)",
|
||||
"font": "send",
|
||||
"__comment_font": "使用 fonts 中哪一类字号",
|
||||
"width": "sendWidth",
|
||||
"__comment_width": "宽度:引用 metrics 中字段或具体数值",
|
||||
"backgroundColor": "#B7BBC4",
|
||||
"__comment_backgroundColor": "按键背景色"
|
||||
}
|
||||
},
|
||||
"layouts": {
|
||||
"__comment": "布局集合:letters/numbers/symbolsMore",
|
||||
"letters": {
|
||||
"__comment": "字母布局(小写/大写共用)",
|
||||
"rows": [
|
||||
{
|
||||
"__comment": "字母第一行 qwertyuiop",
|
||||
"align": "left",
|
||||
"__comment_align": "对齐方式:left/center",
|
||||
"insetLeft": 4,
|
||||
"__comment_insetLeft": "本行左边距(覆盖 metrics.edgeInset)",
|
||||
"insetRight": 4,
|
||||
"__comment_insetRight": "本行右边距(覆盖 metrics.edgeInset)",
|
||||
"gap": 5,
|
||||
"__comment_gap": "本行按键间距(覆盖 metrics.gap)",
|
||||
"items": [
|
||||
"letter:q", "letter:w", "letter:e", "letter:r", "letter:t",
|
||||
"letter:y", "letter:u", "letter:i", "letter:o", "letter:p"
|
||||
],
|
||||
"__comment_items": "本行按键列表;letter:x/digit:x/sym:x 或 keyDefs 中的 id"
|
||||
},
|
||||
{
|
||||
"__comment": "字母第二行 asdfghjkl",
|
||||
"align": "center",
|
||||
"__comment_align": "对齐方式:left/center",
|
||||
"insetLeft": 0,
|
||||
"__comment_insetLeft": "本行左边距(覆盖 metrics.edgeInset)",
|
||||
"insetRight": 0,
|
||||
"__comment_insetRight": "本行右边距(覆盖 metrics.edgeInset)",
|
||||
"gap": 5,
|
||||
"__comment_gap": "本行按键间距(覆盖 metrics.gap)",
|
||||
"items": [
|
||||
"letter:a", "letter:s", "letter:d", "letter:f", "letter:g",
|
||||
"letter:h", "letter:j", "letter:k", "letter:l"
|
||||
],
|
||||
"__comment_items": "本行按键列表;letter:x/digit:x/sym:x 或 keyDefs 中的 id"
|
||||
},
|
||||
{
|
||||
"__comment": "字母第三行:左 shift,中间字母,右 backspace",
|
||||
"align": "left",
|
||||
"__comment_align": "对齐方式:left/center",
|
||||
"insetLeft": 4,
|
||||
"__comment_insetLeft": "本行左边距(覆盖 metrics.edgeInset)",
|
||||
"insetRight": 4,
|
||||
"__comment_insetRight": "本行右边距(覆盖 metrics.edgeInset)",
|
||||
"gap": 5,
|
||||
"__comment_gap": "本行按键间距(覆盖 metrics.gap)",
|
||||
"segments": {
|
||||
"__comment": "分段布局:left/center/right",
|
||||
"left": [
|
||||
{ "id": "shift", "width": "controlWidth", "__comment_id": "引用 keyDefs 的 id", "__comment_width": "宽度引用 metrics.controlWidth" }
|
||||
],
|
||||
"__comment_left": "左侧固定按钮",
|
||||
"center": [
|
||||
"letter:z", "letter:x", "letter:c", "letter:v", "letter:b", "letter:n", "letter:m"
|
||||
],
|
||||
"__comment_center": "中间字母键集合,整体居中",
|
||||
"right": [
|
||||
{ "id": "backspace", "width": "controlWidth", "__comment_id": "引用 keyDefs 的 id", "__comment_width": "宽度引用 metrics.controlWidth" }
|
||||
],
|
||||
"__comment_right": "右侧固定按钮"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__comment": "字母第四行:123/emoji/space/send",
|
||||
"align": "left",
|
||||
"__comment_align": "对齐方式:left/center",
|
||||
"insetLeft": 4,
|
||||
"__comment_insetLeft": "本行左边距(覆盖 metrics.edgeInset)",
|
||||
"insetRight": 4,
|
||||
"__comment_insetRight": "本行右边距(覆盖 metrics.edgeInset)",
|
||||
"gap": 5,
|
||||
"__comment_gap": "本行按键间距(覆盖 metrics.gap)",
|
||||
"items": [
|
||||
"mode_123", "emoji", "space", "send"
|
||||
],
|
||||
"__comment_items": "本行按键列表;letter:x/digit:x/sym:x 或 keyDefs 中的 id"
|
||||
}
|
||||
]
|
||||
},
|
||||
"numbers": {
|
||||
"__comment": "数字面板布局(123 页)",
|
||||
"rows": [
|
||||
{
|
||||
"__comment": "数字第一行 1234567890",
|
||||
"align": "left",
|
||||
"__comment_align": "对齐方式:left/center",
|
||||
"insetLeft": 4,
|
||||
"__comment_insetLeft": "本行左边距(覆盖 metrics.edgeInset)",
|
||||
"insetRight": 4,
|
||||
"__comment_insetRight": "本行右边距(覆盖 metrics.edgeInset)",
|
||||
"gap": 5,
|
||||
"__comment_gap": "本行按键间距(覆盖 metrics.gap)",
|
||||
"items": [
|
||||
"digit:1", "digit:2", "digit:3", "digit:4", "digit:5",
|
||||
"digit:6", "digit:7", "digit:8", "digit:9", "digit:0"
|
||||
],
|
||||
"__comment_items": "本行按键列表;letter:x/digit:x/sym:x 或 keyDefs 中的 id"
|
||||
},
|
||||
{
|
||||
"__comment": "数字第二行 - / : ; ( ) ¥ & @ “",
|
||||
"align": "left",
|
||||
"__comment_align": "对齐方式:left/center",
|
||||
"insetLeft": 4,
|
||||
"__comment_insetLeft": "本行左边距(覆盖 metrics.edgeInset)",
|
||||
"insetRight": 4,
|
||||
"__comment_insetRight": "本行右边距(覆盖 metrics.edgeInset)",
|
||||
"gap": 5,
|
||||
"__comment_gap": "本行按键间距(覆盖 metrics.gap)",
|
||||
"items": [
|
||||
"sym:-", "sym:/", "sym::", "sym:;", "sym:(",
|
||||
"sym:)", "sym:¥", "sym:&", "sym:@", "sym:“"
|
||||
],
|
||||
"__comment_items": "本行按键列表;letter:x/digit:x/sym:x 或 keyDefs 中的 id"
|
||||
},
|
||||
{
|
||||
"__comment": "数字第三行:#+= / 中间符号 / 删除",
|
||||
"align": "center",
|
||||
"__comment_align": "对齐方式:left/center",
|
||||
"insetLeft": 4,
|
||||
"__comment_insetLeft": "本行左边距(覆盖 metrics.edgeInset)",
|
||||
"insetRight": 4,
|
||||
"__comment_insetRight": "本行右边距(覆盖 metrics.edgeInset)",
|
||||
"gap": 5,
|
||||
"__comment_gap": "本行按键间距(覆盖 metrics.gap)",
|
||||
"segments": {
|
||||
"__comment": "分段布局:left/center/right",
|
||||
"left": [
|
||||
{ "id": "symbols_toggle_more", "width": "symbolsSideWidth", "__comment_id": "引用 keyDefs 的 id", "__comment_width": "宽度引用 metrics.symbolsSideWidth" }
|
||||
],
|
||||
"__comment_left": "左侧切换按钮",
|
||||
"center": [
|
||||
{ "id": "sym:.", "width": "symbolsWideWidth", "__comment_id": "符号键 id", "__comment_width": "宽度引用 metrics.symbolsWideWidth" },
|
||||
{ "id": "sym:,", "width": "symbolsWideWidth", "__comment_id": "符号键 id", "__comment_width": "宽度引用 metrics.symbolsWideWidth" },
|
||||
{ "id": "sym:?", "width": "symbolsWideWidth", "__comment_id": "符号键 id", "__comment_width": "宽度引用 metrics.symbolsWideWidth" },
|
||||
{ "id": "sym:!", "width": "symbolsWideWidth", "__comment_id": "符号键 id", "__comment_width": "宽度引用 metrics.symbolsWideWidth" },
|
||||
{ "id": "sym:‘", "width": "symbolsWideWidth", "__comment_id": "符号键 id", "__comment_width": "宽度引用 metrics.symbolsWideWidth" }
|
||||
],
|
||||
"__comment_center": "中间符号键集合,整体居中",
|
||||
"right": [
|
||||
{ "id": "backspace", "width": "symbolsSideWidth", "__comment_id": "引用 keyDefs 的 id", "__comment_width": "宽度引用 metrics.symbolsSideWidth" }
|
||||
],
|
||||
"__comment_right": "右侧删除键"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__comment": "数字第四行:ABC/emoji/space/send",
|
||||
"align": "left",
|
||||
"__comment_align": "对齐方式:left/center",
|
||||
"insetLeft": 4,
|
||||
"__comment_insetLeft": "本行左边距(覆盖 metrics.edgeInset)",
|
||||
"insetRight": 4,
|
||||
"__comment_insetRight": "本行右边距(覆盖 metrics.edgeInset)",
|
||||
"gap": 5,
|
||||
"__comment_gap": "本行按键间距(覆盖 metrics.gap)",
|
||||
"items": [
|
||||
"mode_abc", "emoji", "space", "send"
|
||||
],
|
||||
"__comment_items": "本行按键列表;letter:x/digit:x/sym:x 或 keyDefs 中的 id"
|
||||
}
|
||||
]
|
||||
},
|
||||
"symbolsMore": {
|
||||
"__comment": "符号面板布局(#+= 页)",
|
||||
"rows": [
|
||||
{
|
||||
"__comment": "符号第一行 [ ] { } # % ^ * + =",
|
||||
"align": "left",
|
||||
"__comment_align": "对齐方式:left/center",
|
||||
"insetLeft": 4,
|
||||
"__comment_insetLeft": "本行左边距(覆盖 metrics.edgeInset)",
|
||||
"insetRight": 4,
|
||||
"__comment_insetRight": "本行右边距(覆盖 metrics.edgeInset)",
|
||||
"gap": 5,
|
||||
"__comment_gap": "本行按键间距(覆盖 metrics.gap)",
|
||||
"items": [
|
||||
"sym:[", "sym:]", "sym:{", "sym:}", "sym:#",
|
||||
"sym:%", "sym:^", "sym:*", "sym:+", "sym:="
|
||||
],
|
||||
"__comment_items": "本行按键列表;letter:x/digit:x/sym:x 或 keyDefs 中的 id"
|
||||
},
|
||||
{
|
||||
"__comment": "符号第二行 _ \\ | ~ < > € ¥ $ ·",
|
||||
"align": "left",
|
||||
"__comment_align": "对齐方式:left/center",
|
||||
"insetLeft": 4,
|
||||
"__comment_insetLeft": "本行左边距(覆盖 metrics.edgeInset)",
|
||||
"insetRight": 4,
|
||||
"__comment_insetRight": "本行右边距(覆盖 metrics.edgeInset)",
|
||||
"gap": 5,
|
||||
"__comment_gap": "本行按键间距(覆盖 metrics.gap)",
|
||||
"items": [
|
||||
"sym:_", "sym:\\", "sym:|", "sym:~", "sym:<",
|
||||
"sym:>", "sym:€", "sym:¥", "sym:$", "sym:·"
|
||||
],
|
||||
"__comment_items": "本行按键列表;letter:x/digit:x/sym:x 或 keyDefs 中的 id"
|
||||
},
|
||||
{
|
||||
"__comment": "符号第三行:123 / 中间符号 / 删除",
|
||||
"align": "center",
|
||||
"__comment_align": "对齐方式:left/center",
|
||||
"insetLeft": 4,
|
||||
"__comment_insetLeft": "本行左边距(覆盖 metrics.edgeInset)",
|
||||
"insetRight": 4,
|
||||
"__comment_insetRight": "本行右边距(覆盖 metrics.edgeInset)",
|
||||
"gap": 5,
|
||||
"__comment_gap": "本行按键间距(覆盖 metrics.gap)",
|
||||
"segments": {
|
||||
"__comment": "分段布局:left/center/right",
|
||||
"left": [
|
||||
{ "id": "symbols_toggle_123", "width": "symbolsSideWidth", "__comment_id": "引用 keyDefs 的 id", "__comment_width": "宽度引用 metrics.symbolsSideWidth" }
|
||||
],
|
||||
"__comment_left": "左侧切换按钮",
|
||||
"center": [
|
||||
{ "id": "sym:.", "width": "symbolsWideWidth", "__comment_id": "符号键 id", "__comment_width": "宽度引用 metrics.symbolsWideWidth" },
|
||||
{ "id": "sym:,", "width": "symbolsWideWidth", "__comment_id": "符号键 id", "__comment_width": "宽度引用 metrics.symbolsWideWidth" },
|
||||
{ "id": "sym:?", "width": "symbolsWideWidth", "__comment_id": "符号键 id", "__comment_width": "宽度引用 metrics.symbolsWideWidth" },
|
||||
{ "id": "sym:!", "width": "symbolsWideWidth", "__comment_id": "符号键 id", "__comment_width": "宽度引用 metrics.symbolsWideWidth" },
|
||||
{ "id": "sym:‘", "width": "symbolsWideWidth", "__comment_id": "符号键 id", "__comment_width": "宽度引用 metrics.symbolsWideWidth" }
|
||||
],
|
||||
"__comment_center": "中间符号键集合,整体居中",
|
||||
"right": [
|
||||
{ "id": "backspace", "width": "symbolsSideWidth", "__comment_id": "引用 keyDefs 的 id", "__comment_width": "宽度引用 metrics.symbolsSideWidth" }
|
||||
],
|
||||
"__comment_right": "右侧删除键"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__comment": "符号第四行:ABC/emoji/space/send",
|
||||
"align": "left",
|
||||
"__comment_align": "对齐方式:left/center",
|
||||
"insetLeft": 4,
|
||||
"__comment_insetLeft": "本行左边距(覆盖 metrics.edgeInset)",
|
||||
"insetRight": 4,
|
||||
"__comment_insetRight": "本行右边距(覆盖 metrics.edgeInset)",
|
||||
"gap": 5,
|
||||
"__comment_gap": "本行按键间距(覆盖 metrics.gap)",
|
||||
"items": [
|
||||
"mode_abc", "emoji", "space", "send"
|
||||
],
|
||||
"__comment_items": "本行按键列表;letter:x/digit:x/sym:x 或 keyDefs 中的 id"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
CustomKeyboard/Resource/normal_hei_them.zip
Normal file
BIN
CustomKeyboard/Resource/normal_them.zip
Normal file
@@ -4,6 +4,7 @@
|
||||
|
||||
#import "KBFunctionTagListView.h"
|
||||
#import "KBFunctionTagCell.h"
|
||||
#import "KBMaiPointReporter.h"
|
||||
|
||||
static NSString * const kKBFunctionTagCellId2 = @"KBFunctionTagCellId2";
|
||||
static CGFloat const kKBItemSpace = 4;
|
||||
@@ -66,8 +67,23 @@ static CGFloat const kKBItemSpace = 4;
|
||||
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return kKBItemSpace; }
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
KBTagItemModel *model = (indexPath.item < self.items.count) ? self.items[indexPath.item] : [KBTagItemModel new];
|
||||
NSInteger personaId = 0;
|
||||
if ([model isKindOfClass:KBTagItemModel.class]) {
|
||||
personaId = model.characterId > 0 ? model.characterId : model.tagId;
|
||||
}
|
||||
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
|
||||
extra[@"index"] = @(indexPath.item);
|
||||
extra[@"id"] = @(personaId);
|
||||
if ([model.characterName isKindOfClass:NSString.class] && model.characterName.length > 0) {
|
||||
extra[@"name"] = model.characterName;
|
||||
}
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_tag_item"
|
||||
pageId:@"keyboard_function_panel"
|
||||
elementId:@"renshe_item"
|
||||
extra:extra.copy
|
||||
completion:nil];
|
||||
if ([self.delegate respondsToSelector:@selector(tagListView:didSelectIndex:title:)]) {
|
||||
KBTagItemModel *model = (indexPath.item < self.items.count) ? self.items[indexPath.item] : [KBTagItemModel new];
|
||||
[self.delegate tagListView:self didSelectIndex:indexPath.item title:model.characterName];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,118 +6,125 @@
|
||||
//
|
||||
|
||||
#import "KBFunctionTagCell.h"
|
||||
#import "KBFunctionView.h"
|
||||
#import "Masonry.h"
|
||||
|
||||
@interface KBFunctionTagCell ()
|
||||
@property (nonatomic, strong) UILabel *emojiLabel;
|
||||
@property (nonatomic, strong) UILabel *titleLabelInternal;
|
||||
@property (nonatomic, strong) UIActivityIndicatorView *loadingView;
|
||||
@property(nonatomic, strong) UILabel *emojiLabel;
|
||||
@property(nonatomic, strong) UILabel *titleLabelInternal;
|
||||
@property(nonatomic, strong) UIActivityIndicatorView *loadingView;
|
||||
@end
|
||||
|
||||
@implementation KBFunctionTagCell
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.contentView.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9];
|
||||
self.contentView.layer.cornerRadius = 12;
|
||||
self.contentView.layer.masksToBounds = YES;
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.contentView.backgroundColor = [KBFunctionView kb_cellBackgroundColor];
|
||||
self.contentView.layer.cornerRadius = 12;
|
||||
self.contentView.layer.masksToBounds = YES;
|
||||
|
||||
// 小菊花:默认隐藏,放在整体内容右侧偏内的位置
|
||||
[self.contentView addSubview:self.loadingView];
|
||||
[self.loadingView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.center.equalTo(self.contentView);
|
||||
make.width.height.mas_equalTo(16);
|
||||
}];
|
||||
// 小菊花:默认隐藏,放在整体内容右侧偏内的位置
|
||||
[self.contentView addSubview:self.loadingView];
|
||||
[self.loadingView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.center.equalTo(self.contentView);
|
||||
make.width.height.mas_equalTo(16);
|
||||
}];
|
||||
|
||||
// 中心容器:将 icon + title 组合整体水平居中
|
||||
UIView *centerContainer = [[UIView alloc] init];
|
||||
centerContainer.backgroundColor = [UIColor clearColor];
|
||||
[self.contentView addSubview:centerContainer];
|
||||
[centerContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerX.equalTo(self.contentView.mas_centerX);
|
||||
make.centerY.equalTo(self.contentView.mas_centerY);
|
||||
make.left.greaterThanOrEqualTo(self.contentView.mas_left).offset(6);
|
||||
make.right.lessThanOrEqualTo(self.contentView).offset(-6);
|
||||
}];
|
||||
// 中心容器:将 icon + title 组合整体水平居中
|
||||
UIView *centerContainer = [[UIView alloc] init];
|
||||
centerContainer.backgroundColor = [UIColor clearColor];
|
||||
[self.contentView addSubview:centerContainer];
|
||||
[centerContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerX.equalTo(self.contentView.mas_centerX);
|
||||
make.centerY.equalTo(self.contentView.mas_centerY);
|
||||
make.left.greaterThanOrEqualTo(self.contentView.mas_left).offset(6);
|
||||
make.right.lessThanOrEqualTo(self.contentView).offset(-6);
|
||||
}];
|
||||
|
||||
[centerContainer addSubview:self.emojiLabel];
|
||||
[centerContainer addSubview:self.titleLabelInternal];
|
||||
[centerContainer addSubview:self.emojiLabel];
|
||||
[centerContainer addSubview:self.titleLabelInternal];
|
||||
|
||||
[self.emojiLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(centerContainer.mas_left);
|
||||
make.centerY.equalTo(centerContainer.mas_centerY);
|
||||
// 留出一点余量,避免 emoji 字形在右侧被裁剪
|
||||
make.width.height.mas_equalTo(24);
|
||||
}];
|
||||
[self.titleLabelInternal mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.emojiLabel.mas_right).offset(3);
|
||||
make.top.equalTo(centerContainer.mas_top);
|
||||
make.bottom.equalTo(centerContainer.mas_bottom);
|
||||
make.right.equalTo(centerContainer.mas_right);
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
[self.emojiLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(centerContainer.mas_left);
|
||||
make.centerY.equalTo(centerContainer.mas_centerY);
|
||||
// 留出一点余量,避免 emoji 字形在右侧被裁剪
|
||||
make.width.height.mas_equalTo(24);
|
||||
}];
|
||||
[self.titleLabelInternal mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.emojiLabel.mas_right).offset(3);
|
||||
make.top.equalTo(centerContainer.mas_top);
|
||||
make.bottom.equalTo(centerContainer.mas_bottom);
|
||||
make.right.equalTo(centerContainer.mas_right);
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setItemModel:(KBTagItemModel *)itemModel{
|
||||
_itemModel = itemModel;
|
||||
self.emojiLabel.text = itemModel.emoji;
|
||||
self.titleLabelInternal.text = itemModel.characterName;
|
||||
- (void)setItemModel:(KBTagItemModel *)itemModel {
|
||||
_itemModel = itemModel;
|
||||
self.emojiLabel.text = itemModel.emoji;
|
||||
self.titleLabelInternal.text = itemModel.characterName;
|
||||
}
|
||||
|
||||
#pragma mark - Lazy
|
||||
|
||||
- (UILabel *)emojiLabel {
|
||||
if (!_emojiLabel) {
|
||||
_emojiLabel = [[UILabel alloc] init];
|
||||
_emojiLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_emojiLabel.font = [KBFont medium:20];
|
||||
_emojiLabel.adjustsFontSizeToFitWidth = YES;
|
||||
|
||||
}
|
||||
return _emojiLabel;
|
||||
if (!_emojiLabel) {
|
||||
_emojiLabel = [[UILabel alloc] init];
|
||||
_emojiLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_emojiLabel.font = [KBFont medium:20];
|
||||
_emojiLabel.adjustsFontSizeToFitWidth = YES;
|
||||
}
|
||||
return _emojiLabel;
|
||||
}
|
||||
|
||||
- (UILabel *)titleLabelInternal {
|
||||
if (!_titleLabelInternal) {
|
||||
_titleLabelInternal = [[UILabel alloc] init];
|
||||
_titleLabelInternal.font = [KBFont medium:10];
|
||||
_titleLabelInternal.textColor = [UIColor colorWithHex:0x1B1F1A];
|
||||
// 最多两行,文本过长时末尾截断
|
||||
_titleLabelInternal.numberOfLines = 2;
|
||||
_titleLabelInternal.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
}
|
||||
return _titleLabelInternal;
|
||||
if (!_titleLabelInternal) {
|
||||
_titleLabelInternal = [[UILabel alloc] init];
|
||||
_titleLabelInternal.font = [KBFont medium:10];
|
||||
_titleLabelInternal.textColor = [KBFunctionView kb_cellTextColor];
|
||||
// 最多两行,文本过长时末尾截断
|
||||
_titleLabelInternal.numberOfLines = 2;
|
||||
_titleLabelInternal.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
}
|
||||
return _titleLabelInternal;
|
||||
}
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
|
||||
static UIActivityIndicatorViewStyle KBSpinnerStyle(void) { return UIActivityIndicatorViewStyleMedium; }
|
||||
static UIActivityIndicatorViewStyle KBSpinnerStyle(void) {
|
||||
return UIActivityIndicatorViewStyleMedium;
|
||||
}
|
||||
#else
|
||||
static UIActivityIndicatorViewStyle KBSpinnerStyle(void) { return UIActivityIndicatorViewStyleGray; }
|
||||
static UIActivityIndicatorViewStyle KBSpinnerStyle(void) {
|
||||
return UIActivityIndicatorViewStyleGray;
|
||||
}
|
||||
#endif
|
||||
|
||||
- (UIActivityIndicatorView *)loadingView {
|
||||
if (!_loadingView) {
|
||||
_loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:KBSpinnerStyle()];
|
||||
_loadingView.hidesWhenStopped = YES;
|
||||
_loadingView.color = [UIColor grayColor];
|
||||
_loadingView.hidden = YES;
|
||||
}
|
||||
return _loadingView;
|
||||
if (!_loadingView) {
|
||||
_loadingView = [[UIActivityIndicatorView alloc]
|
||||
initWithActivityIndicatorStyle:KBSpinnerStyle()];
|
||||
_loadingView.hidesWhenStopped = YES;
|
||||
_loadingView.color = [UIColor grayColor];
|
||||
_loadingView.hidden = YES;
|
||||
}
|
||||
return _loadingView;
|
||||
}
|
||||
|
||||
#pragma mark - Expose
|
||||
|
||||
- (UILabel *)titleLabel { return self.titleLabelInternal; }
|
||||
- (UILabel *)titleLabel {
|
||||
return self.titleLabelInternal;
|
||||
}
|
||||
|
||||
- (void)setLoading:(BOOL)loading {
|
||||
if (loading) {
|
||||
self.loadingView.hidden = NO;
|
||||
[self.loadingView startAnimating];
|
||||
} else {
|
||||
[self.loadingView stopAnimating];
|
||||
self.loadingView.hidden = YES;
|
||||
}
|
||||
if (loading) {
|
||||
self.loadingView.hidden = NO;
|
||||
[self.loadingView startAnimating];
|
||||
} else {
|
||||
[self.loadingView stopAnimating];
|
||||
self.loadingView.hidden = YES;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,13 +6,16 @@
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
@class KBFunctionBarView, KBFunctionPasteView,KBFunctionView;
|
||||
@class KBFunctionBarView, KBFunctionPasteView, KBFunctionView;
|
||||
|
||||
@protocol KBFunctionViewDelegate <NSObject>
|
||||
@optional
|
||||
- (void)functionView:(KBFunctionView *_Nullable)functionView didTapToolActionAtIndex:(NSInteger)index;
|
||||
- (void)functionView:(KBFunctionView *_Nullable)functionView didRightTapToolActionAtIndex:(NSInteger)index;
|
||||
- (void)functionViewDidRequestSubscription:(KBFunctionView *_Nullable)functionView;
|
||||
- (void)functionView:(KBFunctionView *_Nullable)functionView
|
||||
didTapToolActionAtIndex:(NSInteger)index;
|
||||
- (void)functionView:(KBFunctionView *_Nullable)functionView
|
||||
didRightTapToolActionAtIndex:(NSInteger)index;
|
||||
- (void)functionViewDidRequestSubscription:
|
||||
(KBFunctionView *_Nullable)functionView;
|
||||
|
||||
@end
|
||||
|
||||
@@ -21,24 +24,33 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// 整个功能面板视图:顶部Bar + 粘贴区 + 标签列表 + 右侧操作按钮
|
||||
@interface KBFunctionView : UIView
|
||||
|
||||
@property (nonatomic, weak) id<KBFunctionViewDelegate> delegate;
|
||||
@property(nonatomic, weak) id<KBFunctionViewDelegate> delegate;
|
||||
|
||||
|
||||
@property (nonatomic, strong, readonly) UICollectionView *collectionView; // 话术分类/标签列表
|
||||
@property (nonatomic, strong, readonly) NSArray<NSString *> *items; // 简单数据源(演示用)
|
||||
@property(nonatomic, strong, readonly)
|
||||
UICollectionView *collectionView; // 话术分类/标签列表
|
||||
@property(nonatomic, strong, readonly)
|
||||
NSArray<NSString *> *items; // 简单数据源(演示用)
|
||||
|
||||
// 子视图暴露,便于外部接入事件
|
||||
@property (nonatomic, strong, readonly) KBFunctionBarView *barView;
|
||||
@property (nonatomic, strong, readonly) KBFunctionPasteView *pasteView;
|
||||
@property(nonatomic, strong, readonly) KBFunctionBarView *barView;
|
||||
@property(nonatomic, strong, readonly) KBFunctionPasteView *pasteView;
|
||||
|
||||
@property (nonatomic, strong, readonly) UIButton *pasteButton; // 右侧-粘贴
|
||||
@property (nonatomic, strong, readonly) UIButton *deleteButton; // 右侧-删除
|
||||
@property (nonatomic, strong, readonly) UIButton *clearButton; // 右侧-清空
|
||||
@property (nonatomic, strong, readonly) UIButton *sendButton; // 右侧-发送
|
||||
@property(nonatomic, strong, readonly) UIButton *pasteButton; // 右侧-粘贴
|
||||
@property(nonatomic, strong, readonly) UIButton *deleteButton; // 右侧-删除
|
||||
@property(nonatomic, strong, readonly) UIButton *clearButton; // 右侧-清空
|
||||
@property(nonatomic, strong, readonly) UIButton *sendButton; // 右侧-发送
|
||||
|
||||
/// 应用当前皮肤(更新背景/强调色)
|
||||
- (void)kb_applyTheme;
|
||||
|
||||
#pragma mark - Theme Colors (用于 Cell 获取暗黑模式颜色)
|
||||
|
||||
/// Cell 背景色:暗黑 #707070,浅色 白色90%透明度
|
||||
+ (UIColor *)kb_cellBackgroundColor;
|
||||
|
||||
/// Cell 文字颜色:暗黑 #FFFFFF,浅色 #1B1F1A
|
||||
+ (UIColor *)kb_cellTextColor;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#import "Masonry.h"
|
||||
#import "KBSkinManager.h"
|
||||
#import "KBBackspaceUndoManager.h"
|
||||
#import "KBKeyboardLayoutConfig.h"
|
||||
|
||||
@interface KBKeyBoardMainView ()<KBToolBarDelegate, KBKeyboardViewDelegate, KBEmojiPanelViewDelegate, KBSuggestionBarViewDelegate>
|
||||
@property (nonatomic, strong) KBToolBar *topBar;
|
||||
@@ -45,6 +46,13 @@
|
||||
|
||||
// 键盘区域(高度按照设计值做等比缩放,避免不同机型上按键被压缩/拉伸)
|
||||
CGFloat keyboardAreaHeight = KBFit(200.0f);
|
||||
KBKeyboardLayoutConfig *layoutConfig = [KBKeyboardLayoutConfig sharedConfig];
|
||||
if (layoutConfig) {
|
||||
CGFloat configHeight = [layoutConfig keyboardAreaScaledHeight];
|
||||
if (configHeight > 0.0) {
|
||||
keyboardAreaHeight = configHeight;
|
||||
}
|
||||
}
|
||||
CGFloat bottomInset = KBFit(4.0f);
|
||||
// CGFloat topBarHeight = KBFit(40.0f);
|
||||
CGFloat barSpacing = KBFit(6.0f);
|
||||
@@ -247,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];
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
@property (nonatomic, strong) KBKey *key;
|
||||
@property (nonatomic, strong) UIImageView *iconView;
|
||||
@property (nonatomic, strong, nullable) UIColor *customBackgroundColor;
|
||||
|
||||
/// 配置基础样式(背景、圆角等)。创建按钮时调用。
|
||||
- (void)applyDefaultStyle;
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
#import "KBKeyButton.h"
|
||||
#import "KBKey.h"
|
||||
#import "KBSkinManager.h"
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
@interface KBKeyButton ()
|
||||
// 内部缓存:便于从按钮查找到所属的 KBKeyboardView
|
||||
@property (nonatomic, weak, readonly) UIView *kb_keyboardContainer;
|
||||
@property (nonatomic, strong) UIImageView *normalImageView; /// 没有皮肤的时候展示
|
||||
@property (nonatomic, strong) UIColor *baseBackgroundColor; /// 无按下状态下,由皮肤/主题决定的底色(由 normalImageView 展示)
|
||||
@property (nonatomic, strong) CAGradientLayer *bottomShadowLayer;
|
||||
|
||||
@end
|
||||
|
||||
@@ -24,8 +26,8 @@
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.normalImageView.topAnchor constraintEqualToAnchor:self.topAnchor],
|
||||
[self.normalImageView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
|
||||
[self.normalImageView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:2],
|
||||
[self.normalImageView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-2],
|
||||
[self.normalImageView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
|
||||
[self.normalImageView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
|
||||
]];
|
||||
[self applyDefaultStyle];
|
||||
}
|
||||
@@ -48,6 +50,7 @@
|
||||
|
||||
// 初始状态下根据主题设置底色(给没有皮肤图的按键使用)
|
||||
[self refreshStateAppearance];
|
||||
[self kb_setupBottomShadowIfNeeded];
|
||||
|
||||
// 懒创建图标视图,用于后续皮肤按键小图标展示
|
||||
if (!self.iconView) {
|
||||
@@ -61,8 +64,8 @@
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[iv.topAnchor constraintEqualToAnchor:self.topAnchor],
|
||||
[iv.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
|
||||
[iv.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:2],
|
||||
[iv.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-2],
|
||||
[iv.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
|
||||
[iv.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
|
||||
]];
|
||||
self.iconView = iv;
|
||||
|
||||
@@ -72,6 +75,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
if (!self.bottomShadowLayer) { return; }
|
||||
CGRect bounds = self.normalImageView.bounds;
|
||||
CGFloat shadowHeight = 2;
|
||||
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),
|
||||
shadowHeight);
|
||||
}
|
||||
|
||||
- (void)setKey:(KBKey *)key {
|
||||
_key = key;
|
||||
}
|
||||
@@ -121,14 +142,25 @@
|
||||
[self refreshStateAppearance];
|
||||
}
|
||||
|
||||
- (void)setCustomBackgroundColor:(UIColor *)customBackgroundColor {
|
||||
_customBackgroundColor = customBackgroundColor;
|
||||
[self refreshStateAppearance];
|
||||
}
|
||||
|
||||
- (void)refreshStateAppearance {
|
||||
// 选中态用于 Shift/CapsLock 等特殊按键的高亮显示
|
||||
KBSkinTheme *t = [KBSkinManager shared].current;
|
||||
UIColor *base = nil;
|
||||
if (self.isSelected) {
|
||||
base = t.keyHighlightBackground ?: t.keyBackground;
|
||||
if (self.customBackgroundColor) {
|
||||
base = t.keyHighlightBackground ?: self.customBackgroundColor;
|
||||
}
|
||||
} else {
|
||||
base = t.keyBackground;
|
||||
base = self.customBackgroundColor ?: t.keyBackground;
|
||||
}
|
||||
if (self.customBackgroundColor && self.key.type == KBKeyTypeShift) {
|
||||
base = self.customBackgroundColor;
|
||||
}
|
||||
if (!base) {
|
||||
base = [UIColor whiteColor];
|
||||
@@ -138,6 +170,13 @@
|
||||
// 按键背景统一由 normalImageView 控制,按钮本身透明
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
|
||||
if (self.key.type == KBKeyTypeShift) {
|
||||
UIColor *textColor = self.isSelected ? [UIColor blackColor] : (t.keyTextColor ?: [UIColor blackColor]);
|
||||
[self setTitleColor:textColor forState:UIControlStateNormal];
|
||||
[self setTitleColor:textColor forState:UIControlStateHighlighted];
|
||||
[self setTitleColor:textColor forState:UIControlStateSelected];
|
||||
}
|
||||
|
||||
// 有皮肤图时仅展示 icon,不再显示普通背景色
|
||||
if (self.iconView.image != nil || self.normalImageView.hidden) {
|
||||
return;
|
||||
@@ -169,6 +208,7 @@
|
||||
|
||||
BOOL hasIcon = (iconImg != nil);
|
||||
self.normalImageView.hidden = hasIcon;
|
||||
self.bottomShadowLayer.hidden = hasIcon;
|
||||
if (hasIcon) {
|
||||
// 有图标:仅显示图片,完全隐藏文字
|
||||
[self setTitle:@"" forState:UIControlStateNormal];
|
||||
@@ -184,6 +224,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)kb_setupBottomShadowIfNeeded {
|
||||
if (self.bottomShadowLayer) { return; }
|
||||
CAGradientLayer *layer = [CAGradientLayer layer];
|
||||
layer.startPoint = CGPointMake(0.5, 0.0);
|
||||
layer.endPoint = CGPointMake(0.5, 1.0);
|
||||
layer.colors = @[
|
||||
(id)[UIColor colorWithWhite:0 alpha:0.5].CGColor,
|
||||
(id)[UIColor colorWithWhite:0 alpha:0.7].CGColor
|
||||
];
|
||||
[self.normalImageView.layer addSublayer:layer];
|
||||
// self.bottomShadowLayer = layer;
|
||||
}
|
||||
|
||||
- (UIImageView *)normalImageView{
|
||||
if (!_normalImageView) {
|
||||
_normalImageView = [[UIImageView alloc] init];
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#import "KBSkinManager.h"
|
||||
#import "KBKeyPreviewView.h"
|
||||
#import "KBBackspaceLongPressHandler.h"
|
||||
#import "KBKeyboardLayoutConfig.h"
|
||||
|
||||
// UI 常量统一管理,方便后续调试样式(以 375 宽设计稿为基准,通过 KBFit 做等比缩放)
|
||||
#define kKBRowVerticalSpacing KBFit(8.0f)
|
||||
@@ -33,17 +34,19 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
||||
@property (nonatomic, strong) NSArray<NSArray<KBKey *> *> *keysForRows;
|
||||
@property (nonatomic, strong) KBBackspaceLongPressHandler *backspaceHandler;
|
||||
@property (nonatomic, strong) KBKeyPreviewView *previewView;
|
||||
@property (nonatomic, strong) KBKeyboardLayoutConfig *layoutConfig;
|
||||
@end
|
||||
|
||||
@implementation KBKeyboardView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.backgroundColor = [KBSkinManager shared].current.keyboardBackground;
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
_layoutStyle = KBKeyboardLayoutStyleLetters;
|
||||
// 默认小写:与需求一致,初始不开启 Shift
|
||||
_shiftOn = NO;
|
||||
_symbolsMoreOn = NO; // 数字面板默认第一页(123)
|
||||
self.layoutConfig = [KBKeyboardLayoutConfig sharedConfig];
|
||||
self.backspaceHandler = [[KBBackspaceLongPressHandler alloc] initWithContainerView:self];
|
||||
[self buildBase];
|
||||
[self reloadKeys];
|
||||
@@ -67,26 +70,39 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
||||
[self addSubview:self.row3];
|
||||
[self addSubview:self.row4];
|
||||
|
||||
KBKeyboardLayoutConfig *config = [self kb_layoutConfig];
|
||||
KBKeyboardLayout *layout = [self kb_layoutForName:@"letters"];
|
||||
NSArray<KBKeyboardRowConfig *> *rows = layout.rows ?: @[];
|
||||
|
||||
CGFloat rowSpacing = [self kb_metricValue:config.metrics.rowSpacing fallback:nil defaultValue:8.0];
|
||||
CGFloat topInset = [self kb_metricValue:config.metrics.topInset fallback:nil defaultValue:8.0];
|
||||
CGFloat bottomInset = [self kb_metricValue:config.metrics.bottomInset fallback:nil defaultValue:6.0];
|
||||
|
||||
CGFloat row1Height = [self kb_rowHeightForRow:(rows.count > 0 ? rows[0] : nil)];
|
||||
CGFloat row2Height = [self kb_rowHeightForRow:(rows.count > 1 ? rows[1] : nil)];
|
||||
CGFloat row3Height = [self kb_rowHeightForRow:(rows.count > 2 ? rows[2] : nil)];
|
||||
CGFloat row4Height = [self kb_rowHeightForRow:(rows.count > 3 ? rows[3] : nil)];
|
||||
|
||||
[self.row1 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.mas_top).offset(kKBRowVerticalSpacing);
|
||||
make.top.equalTo(self.mas_top).offset(topInset);
|
||||
make.left.right.equalTo(self);
|
||||
make.height.mas_equalTo(kKBRowHeight);
|
||||
make.height.mas_equalTo(row1Height);
|
||||
}];
|
||||
[self.row2 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.row1.mas_bottom).offset(kKBRowVerticalSpacing);
|
||||
make.top.equalTo(self.row1.mas_bottom).offset(rowSpacing);
|
||||
make.left.right.equalTo(self);
|
||||
make.height.equalTo(self.row1);
|
||||
make.height.mas_equalTo(row2Height);
|
||||
}];
|
||||
[self.row3 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.row2.mas_bottom).offset(kKBRowVerticalSpacing);
|
||||
make.top.equalTo(self.row2.mas_bottom).offset(rowSpacing);
|
||||
make.left.right.equalTo(self);
|
||||
make.height.equalTo(self.row1);
|
||||
make.height.mas_equalTo(row3Height);
|
||||
}];
|
||||
[self.row4 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.row3.mas_bottom).offset(kKBRowVerticalSpacing);
|
||||
make.top.equalTo(self.row3.mas_bottom).offset(rowSpacing);
|
||||
make.left.right.equalTo(self);
|
||||
make.height.equalTo(self.row1);
|
||||
make.bottom.equalTo(self.mas_bottom).offset(-6);
|
||||
make.height.mas_equalTo(row4Height);
|
||||
make.bottom.equalTo(self.mas_bottom).offset(-bottomInset);
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -99,18 +115,92 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
||||
[row.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
}
|
||||
|
||||
self.keysForRows = [self buildKeysForCurrentLayout];
|
||||
if (self.keysForRows.count < 4) return;
|
||||
KBKeyboardLayout *layout = [self kb_currentLayout];
|
||||
NSArray<KBKeyboardRowConfig *> *rows = layout.rows ?: @[];
|
||||
if (rows.count < 4) {
|
||||
[self kb_buildLegacyLayout];
|
||||
return;
|
||||
}
|
||||
|
||||
[self buildRow:self.row1 withKeys:self.keysForRows[0]];
|
||||
[self buildRow:self.row1 withRowConfig:rows[0]];
|
||||
[self buildRow:self.row2 withRowConfig:rows[1]];
|
||||
[self buildRow:self.row3 withRowConfig:rows[2]];
|
||||
[self buildRow:self.row4 withRowConfig:rows[3]];
|
||||
}
|
||||
|
||||
// 第二行:字母布局时通过左右等宽占位让整行居中
|
||||
CGFloat row2Spacer = (self.layoutStyle == KBKeyboardLayoutStyleLetters)
|
||||
? kKBLettersRow2EdgeSpacerMultiplier : 0.0;
|
||||
[self buildRow:self.row2 withKeys:self.keysForRows[1] edgeSpacerMultiplier:row2Spacer];
|
||||
#pragma mark - Hit Test
|
||||
|
||||
[self buildRow:self.row3 withKeys:self.keysForRows[2]];
|
||||
[self buildRow:self.row4 withKeys:self.keysForRows[3]];
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
UIView *hit = [super hitTest:point withEvent:event];
|
||||
if ([hit isKindOfClass:[KBKeyButton class]]) {
|
||||
return hit;
|
||||
}
|
||||
if ([self kb_isHitInsideKeyRows:hit]) {
|
||||
KBKeyButton *btn = [self kb_nearestKeyButtonForPoint:point];
|
||||
if (btn) { return btn; }
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
- (BOOL)kb_isHitInsideKeyRows:(UIView *)hitView {
|
||||
if (!hitView) { return NO; }
|
||||
if (hitView == self) { return YES; }
|
||||
if ([hitView isDescendantOfView:self.row1]) { return YES; }
|
||||
if ([hitView isDescendantOfView:self.row2]) { return YES; }
|
||||
if ([hitView isDescendantOfView:self.row3]) { return YES; }
|
||||
if ([hitView isDescendantOfView:self.row4]) { return YES; }
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (KBKeyButton *)kb_nearestKeyButtonForPoint:(CGPoint)point {
|
||||
KBKeyButton *best = nil;
|
||||
CGFloat bestDistance = CGFLOAT_MAX;
|
||||
NSArray<UIView *> *rows = @[self.row1, self.row2, self.row3, self.row4];
|
||||
|
||||
UIView *targetRow = nil;
|
||||
for (UIView *row in rows) {
|
||||
CGRect rowFrame = [self convertRect:row.bounds fromView:row];
|
||||
if (CGRectContainsPoint(rowFrame, point)) {
|
||||
targetRow = row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NSArray<UIView *> *candidateRows = targetRow ? @[targetRow] : rows;
|
||||
for (UIView *row in candidateRows) {
|
||||
NSArray<KBKeyButton *> *buttons = [self kb_collectKeyButtonsInView:row];
|
||||
for (KBKeyButton *btn in buttons) {
|
||||
CGRect frame = [self convertRect:btn.frame fromView:btn.superview];
|
||||
CGFloat dx = point.x - CGRectGetMidX(frame);
|
||||
CGFloat dy = point.y - CGRectGetMidY(frame);
|
||||
CGFloat dist = (dx * dx) + (dy * dy);
|
||||
if (dist < bestDistance) {
|
||||
bestDistance = dist;
|
||||
best = btn;
|
||||
}
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
- (NSArray<KBKeyButton *> *)kb_collectKeyButtonsInView:(UIView *)view {
|
||||
if (!view) { return @[]; }
|
||||
NSMutableArray<KBKeyButton *> *buttons = [NSMutableArray array];
|
||||
[self kb_collectKeyButtonsInView:view into:buttons];
|
||||
return buttons.copy;
|
||||
}
|
||||
|
||||
- (void)kb_collectKeyButtonsInView:(UIView *)view
|
||||
into:(NSMutableArray<KBKeyButton *> *)buttons {
|
||||
for (UIView *sub in view.subviews) {
|
||||
if ([sub isKindOfClass:[KBKeyButton class]]) {
|
||||
[buttons addObject:(KBKeyButton *)sub];
|
||||
continue;
|
||||
}
|
||||
if (sub.subviews.count > 0) {
|
||||
[self kb_collectKeyButtonsInView:sub into:buttons];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Key Model Construction
|
||||
@@ -315,6 +405,152 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
||||
|
||||
#pragma mark - Row Building
|
||||
|
||||
- (void)buildRow:(UIView *)row withRowConfig:(KBKeyboardRowConfig *)rowConfig {
|
||||
if (!row || !rowConfig) { return; }
|
||||
CGFloat gap = [self kb_gapForRow:rowConfig];
|
||||
CGFloat insetLeft = [self kb_insetLeftForRow:rowConfig];
|
||||
CGFloat insetRight = [self kb_insetRightForRow:rowConfig];
|
||||
|
||||
if (rowConfig.segments) {
|
||||
KBKeyboardRowSegments *segments = rowConfig.segments;
|
||||
NSArray<KBKeyboardRowItem *> *leftItems = [segments leftItems];
|
||||
NSArray<KBKeyboardRowItem *> *centerItems = [segments centerItems];
|
||||
NSArray<KBKeyboardRowItem *> *rightItems = [segments rightItems];
|
||||
UIView *leftContainer = [UIView new];
|
||||
UIView *centerContainer = [UIView new];
|
||||
UIView *rightContainer = [UIView new];
|
||||
[row addSubview:leftContainer];
|
||||
[row addSubview:centerContainer];
|
||||
[row addSubview:rightContainer];
|
||||
|
||||
[leftContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(row.mas_left).offset(insetLeft);
|
||||
make.top.bottom.equalTo(row);
|
||||
}];
|
||||
[rightContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(row.mas_right).offset(-insetRight);
|
||||
make.top.bottom.equalTo(row);
|
||||
}];
|
||||
[centerContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerX.equalTo(row);
|
||||
make.top.bottom.equalTo(row);
|
||||
make.left.greaterThanOrEqualTo(leftContainer.mas_right).offset(gap);
|
||||
make.right.lessThanOrEqualTo(rightContainer.mas_left).offset(-gap);
|
||||
}];
|
||||
|
||||
if (leftItems.count == 0) {
|
||||
[leftContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.mas_equalTo(0);
|
||||
}];
|
||||
}
|
||||
if (centerItems.count == 0) {
|
||||
[centerContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.mas_equalTo(0);
|
||||
}];
|
||||
}
|
||||
if (rightItems.count == 0) {
|
||||
[rightContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.mas_equalTo(0);
|
||||
}];
|
||||
}
|
||||
|
||||
[self kb_buildButtonsInContainer:leftContainer
|
||||
items:leftItems
|
||||
gap:gap
|
||||
insetLeft:0
|
||||
insetRight:0
|
||||
alignCenter:NO];
|
||||
[self kb_buildButtonsInContainer:centerContainer
|
||||
items:centerItems
|
||||
gap:gap
|
||||
insetLeft:0
|
||||
insetRight:0
|
||||
alignCenter:NO];
|
||||
[self kb_buildButtonsInContainer:rightContainer
|
||||
items:rightItems
|
||||
gap:gap
|
||||
insetLeft:0
|
||||
insetRight:0
|
||||
alignCenter:NO];
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL alignCenter = [rowConfig.align.lowercaseString isEqualToString:@"center"];
|
||||
[self kb_buildButtonsInContainer:row
|
||||
items:[rowConfig resolvedItems]
|
||||
gap:gap
|
||||
insetLeft:insetLeft
|
||||
insetRight:insetRight
|
||||
alignCenter:alignCenter];
|
||||
}
|
||||
|
||||
- (void)kb_buildButtonsInContainer:(UIView *)container
|
||||
items:(NSArray<KBKeyboardRowItem *> *)items
|
||||
gap:(CGFloat)gap
|
||||
insetLeft:(CGFloat)insetLeft
|
||||
insetRight:(CGFloat)insetRight
|
||||
alignCenter:(BOOL)alignCenter {
|
||||
if (items.count == 0) { return; }
|
||||
|
||||
UIView *leftSpacer = nil;
|
||||
UIView *rightSpacer = nil;
|
||||
if (alignCenter) {
|
||||
leftSpacer = [UIView new];
|
||||
rightSpacer = [UIView new];
|
||||
[container addSubview:leftSpacer];
|
||||
[container addSubview:rightSpacer];
|
||||
[leftSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(container.mas_left).offset(insetLeft);
|
||||
make.top.bottom.equalTo(container);
|
||||
}];
|
||||
[rightSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(container.mas_right).offset(-insetRight);
|
||||
make.top.bottom.equalTo(container);
|
||||
}];
|
||||
[leftSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.equalTo(rightSpacer);
|
||||
}];
|
||||
}
|
||||
|
||||
KBKeyButton *previous = nil;
|
||||
for (KBKeyboardRowItem *item in items) {
|
||||
KBKeyButton *btn = [self kb_buttonForItem:item];
|
||||
if (!btn) { continue; }
|
||||
[container addSubview:btn];
|
||||
|
||||
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.bottom.equalTo(container);
|
||||
if (previous) {
|
||||
make.left.equalTo(previous.mas_right).offset(gap);
|
||||
} else {
|
||||
if (leftSpacer) {
|
||||
make.left.equalTo(leftSpacer.mas_right).offset(gap);
|
||||
} else {
|
||||
make.left.equalTo(container.mas_left).offset(insetLeft);
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
CGFloat width = [self kb_widthForItem:item key:btn.key];
|
||||
if (width > 0.0) {
|
||||
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.mas_equalTo(width);
|
||||
}];
|
||||
}
|
||||
|
||||
previous = btn;
|
||||
}
|
||||
|
||||
if (!previous) { return; }
|
||||
[previous mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
if (rightSpacer) {
|
||||
make.right.equalTo(rightSpacer.mas_left).offset(-gap);
|
||||
} else {
|
||||
make.right.equalTo(container.mas_right).offset(-insetRight);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)buildRow:(UIView *)row withKeys:(NSArray<KBKey *> *)keys {
|
||||
[self buildRow:row withKeys:keys edgeSpacerMultiplier:0.0];
|
||||
}
|
||||
@@ -581,6 +817,386 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
|
||||
// Space 不设置宽度;通过此前已建立的左右约束自动占满剩余宽度。
|
||||
}
|
||||
|
||||
#pragma mark - Config Helpers
|
||||
|
||||
- (KBKeyboardLayoutConfig *)kb_layoutConfig {
|
||||
if (!self.layoutConfig) {
|
||||
self.layoutConfig = [KBKeyboardLayoutConfig sharedConfig];
|
||||
}
|
||||
return self.layoutConfig;
|
||||
}
|
||||
|
||||
- (KBKeyboardLayout *)kb_layoutForName:(NSString *)name {
|
||||
return [[self kb_layoutConfig] layoutForName:name];
|
||||
}
|
||||
|
||||
- (KBKeyboardLayout *)kb_currentLayout {
|
||||
if (self.layoutStyle == KBKeyboardLayoutStyleNumbers) {
|
||||
return [self kb_layoutForName:(self.symbolsMoreOn ? @"symbolsMore" : @"numbers")];
|
||||
}
|
||||
return [self kb_layoutForName:@"letters"];
|
||||
}
|
||||
|
||||
- (void)kb_buildLegacyLayout {
|
||||
self.keysForRows = [self buildKeysForCurrentLayout];
|
||||
if (self.keysForRows.count < 4) { return; }
|
||||
|
||||
[self buildRow:self.row1 withKeys:self.keysForRows[0]];
|
||||
|
||||
CGFloat row2Spacer = (self.layoutStyle == KBKeyboardLayoutStyleLetters)
|
||||
? kKBLettersRow2EdgeSpacerMultiplier : 0.0;
|
||||
[self buildRow:self.row2 withKeys:self.keysForRows[1] edgeSpacerMultiplier:row2Spacer];
|
||||
|
||||
[self buildRow:self.row3 withKeys:self.keysForRows[2]];
|
||||
[self buildRow:self.row4 withKeys:self.keysForRows[3]];
|
||||
}
|
||||
|
||||
- (CGFloat)kb_scaledValue:(CGFloat)designValue {
|
||||
KBKeyboardLayoutConfig *config = [self kb_layoutConfig];
|
||||
if (config) {
|
||||
return [config scaledValue:designValue];
|
||||
}
|
||||
return KBFit(designValue);
|
||||
}
|
||||
|
||||
- (CGFloat)kb_numberValue:(NSNumber *)value defaultValue:(CGFloat)defaultValue {
|
||||
if ([value isKindOfClass:[NSNumber class]]) {
|
||||
return value.doubleValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
- (CGFloat)kb_metricValue:(NSNumber *)value fallback:(NSNumber *)fallback defaultValue:(CGFloat)defaultValue {
|
||||
CGFloat v = [self kb_numberValue:value defaultValue:-1.0];
|
||||
if (v < 0.0) {
|
||||
v = [self kb_numberValue:fallback defaultValue:defaultValue];
|
||||
}
|
||||
if (v < 0.0) {
|
||||
v = defaultValue;
|
||||
}
|
||||
return [self kb_scaledValue:v];
|
||||
}
|
||||
|
||||
- (CGFloat)kb_rowHeightForRow:(KBKeyboardRowConfig *)row {
|
||||
KBKeyboardLayoutConfig *config = [self kb_layoutConfig];
|
||||
NSNumber *height = row.height ?: config.metrics.keyHeight;
|
||||
CGFloat value = [self kb_numberValue:height defaultValue:40.0];
|
||||
return [self kb_scaledValue:value];
|
||||
}
|
||||
|
||||
- (CGFloat)kb_gapForRow:(KBKeyboardRowConfig *)row {
|
||||
KBKeyboardLayoutConfig *config = [self kb_layoutConfig];
|
||||
return [self kb_metricValue:row.gap fallback:config.metrics.gap defaultValue:5.0];
|
||||
}
|
||||
|
||||
- (CGFloat)kb_insetLeftForRow:(KBKeyboardRowConfig *)row {
|
||||
KBKeyboardLayoutConfig *config = [self kb_layoutConfig];
|
||||
return [self kb_metricValue:row.insetLeft fallback:config.metrics.edgeInset defaultValue:0.0];
|
||||
}
|
||||
|
||||
- (CGFloat)kb_insetRightForRow:(KBKeyboardRowConfig *)row {
|
||||
KBKeyboardLayoutConfig *config = [self kb_layoutConfig];
|
||||
return [self kb_metricValue:row.insetRight fallback:config.metrics.edgeInset defaultValue:0.0];
|
||||
}
|
||||
|
||||
- (KBKeyButton *)kb_buttonForItem:(KBKeyboardRowItem *)item {
|
||||
if (item.itemId.length == 0) { return nil; }
|
||||
KBKeyboardKeyDef *def = [[self kb_layoutConfig] keyDefForIdentifier:item.itemId];
|
||||
KBKey *key = [self kb_keyForItemId:item.itemId];
|
||||
if (!key) { return nil; }
|
||||
|
||||
KBKeyButton *btn = [[KBKeyButton alloc] init];
|
||||
btn.key = key;
|
||||
[btn setTitle:key.title forState:UIControlStateNormal];
|
||||
|
||||
UIColor *bgColor = [self kb_backgroundColorForItem:item keyDef:def];
|
||||
if (bgColor) {
|
||||
btn.customBackgroundColor = bgColor;
|
||||
}
|
||||
|
||||
CGFloat fontSize = [self kb_fontSizeForItem:item key:key];
|
||||
if (fontSize > 0.0) {
|
||||
btn.titleLabel.font = [UIFont systemFontOfSize:fontSize weight:UIFontWeightSemibold];
|
||||
}
|
||||
|
||||
[btn applyThemeForCurrentKey];
|
||||
[btn addTarget:self action:@selector(onKeyTapped:) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
if (key.type == KBKeyTypeBackspace) {
|
||||
[self.backspaceHandler bindDeleteButton:btn showClearLabel:YES];
|
||||
}
|
||||
if (key.type == KBKeyTypeShift) {
|
||||
btn.selected = self.shiftOn;
|
||||
}
|
||||
[self kb_applySymbolIfNeededForButton:btn keyDef:def fontSize:fontSize];
|
||||
return btn;
|
||||
}
|
||||
|
||||
- (void)kb_applySymbolIfNeededForButton:(KBKeyButton *)button
|
||||
keyDef:(KBKeyboardKeyDef *)def
|
||||
fontSize:(CGFloat)fontSize {
|
||||
if (!button || !def) { return; }
|
||||
if (button.iconView.image != nil) { return; }
|
||||
NSString *symbolName = button.isSelected ? def.selectedSymbolName : def.symbolName;
|
||||
if (symbolName.length == 0) { return; }
|
||||
|
||||
UIImage *image = [UIImage systemImageNamed:symbolName];
|
||||
if (!image) { return; }
|
||||
|
||||
UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:fontSize weight:UIFontWeightSemibold];
|
||||
image = [image imageWithConfiguration:config];
|
||||
|
||||
button.iconView.image = image;
|
||||
button.iconView.hidden = NO;
|
||||
button.iconView.contentMode = UIViewContentModeCenter;
|
||||
button.titleLabel.hidden = YES;
|
||||
|
||||
UIColor *textColor = [KBSkinManager shared].current.keyTextColor ?: [UIColor blackColor];
|
||||
button.iconView.tintColor = button.isSelected ? [UIColor blackColor] : textColor;
|
||||
}
|
||||
|
||||
- (UIColor *)kb_backgroundColorForItem:(KBKeyboardRowItem *)item keyDef:(KBKeyboardKeyDef *)def {
|
||||
NSString *hex = def.backgroundColor;
|
||||
if (hex.length == 0) {
|
||||
hex = [self kb_layoutConfig].defaultKeyBackground;
|
||||
}
|
||||
if (hex.length == 0) { return nil; }
|
||||
return [KBSkinManager colorFromHexString:hex defaultColor:nil];
|
||||
}
|
||||
|
||||
- (CGFloat)kb_metricWidthForKey:(NSString *)key {
|
||||
KBKeyboardLayoutMetrics *m = [self kb_layoutConfig].metrics;
|
||||
if ([key isEqualToString:@"letterWidth"]) { return m.letterWidth.doubleValue; }
|
||||
if ([key isEqualToString:@"controlWidth"]) { return m.controlWidth.doubleValue; }
|
||||
if ([key isEqualToString:@"sendWidth"]) { return m.sendWidth.doubleValue; }
|
||||
if ([key isEqualToString:@"symbolsWideWidth"]) { return m.symbolsWideWidth.doubleValue; }
|
||||
if ([key isEqualToString:@"symbolsSideWidth"]) { return m.symbolsSideWidth.doubleValue; }
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
- (CGFloat)kb_widthForItem:(KBKeyboardRowItem *)item key:(KBKey *)key {
|
||||
CGFloat width = 0.0;
|
||||
if (item.widthValue.doubleValue > 0.0) {
|
||||
width = item.widthValue.doubleValue;
|
||||
} else if (item.width.length > 0) {
|
||||
if ([item.width.lowercaseString isEqualToString:@"flex"]) {
|
||||
return 0.0;
|
||||
}
|
||||
width = [self kb_metricWidthForKey:item.width];
|
||||
if (width <= 0.0) {
|
||||
width = item.width.doubleValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (width <= 0.0) {
|
||||
KBKeyboardLayoutMetrics *m = [self kb_layoutConfig].metrics;
|
||||
if ([item.itemId hasPrefix:@"letter:"] ||
|
||||
[item.itemId hasPrefix:@"digit:"] ||
|
||||
[item.itemId hasPrefix:@"sym:"]) {
|
||||
width = m.letterWidth.doubleValue;
|
||||
} else if (key.type == KBKeyTypeReturn) {
|
||||
width = m.sendWidth.doubleValue;
|
||||
} else if (key.type == KBKeyTypeSpace) {
|
||||
return 0.0;
|
||||
} else {
|
||||
width = m.controlWidth.doubleValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (width <= 0.0) {
|
||||
if ([item.itemId hasPrefix:@"letter:"] ||
|
||||
[item.itemId hasPrefix:@"digit:"] ||
|
||||
[item.itemId hasPrefix:@"sym:"]) {
|
||||
width = 32.0;
|
||||
} else if (key.type == KBKeyTypeReturn) {
|
||||
width = 88.0;
|
||||
} else if (key.type == KBKeyTypeSpace) {
|
||||
return 0.0;
|
||||
} else {
|
||||
width = 41.0;
|
||||
}
|
||||
}
|
||||
|
||||
return width > 0.0 ? [self kb_scaledValue:width] : 0.0;
|
||||
}
|
||||
|
||||
- (CGFloat)kb_fontSizeForItem:(KBKeyboardRowItem *)item key:(KBKey *)key {
|
||||
NSString *fontKey = nil;
|
||||
if ([item.itemId hasPrefix:@"letter:"]) {
|
||||
fontKey = @"letter";
|
||||
} else if ([item.itemId hasPrefix:@"digit:"]) {
|
||||
fontKey = @"digit";
|
||||
} else if ([item.itemId hasPrefix:@"sym:"]) {
|
||||
fontKey = @"symbol";
|
||||
} else {
|
||||
KBKeyboardKeyDef *def = [[self kb_layoutConfig] keyDefForIdentifier:item.itemId];
|
||||
fontKey = def.font;
|
||||
}
|
||||
|
||||
if (fontKey.length == 0) {
|
||||
switch (key.type) {
|
||||
case KBKeyTypeModeChange:
|
||||
case KBKeyTypeSymbolsToggle:
|
||||
fontKey = @"mode";
|
||||
break;
|
||||
case KBKeyTypeSpace:
|
||||
fontKey = @"space";
|
||||
break;
|
||||
case KBKeyTypeReturn:
|
||||
fontKey = @"send";
|
||||
break;
|
||||
default:
|
||||
fontKey = @"symbol";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return [self kb_fontSizeForFontKey:fontKey];
|
||||
}
|
||||
|
||||
- (CGFloat)kb_fontSizeForFontKey:(NSString *)fontKey {
|
||||
KBKeyboardLayoutFonts *fonts = [self kb_layoutConfig].fonts;
|
||||
CGFloat size = 0.0;
|
||||
if ([fontKey isEqualToString:@"letter"]) { size = fonts.letter.doubleValue; }
|
||||
else if ([fontKey isEqualToString:@"digit"]) { size = fonts.digit.doubleValue; }
|
||||
else if ([fontKey isEqualToString:@"symbol"]) { size = fonts.symbol.doubleValue; }
|
||||
else if ([fontKey isEqualToString:@"mode"]) { size = fonts.mode.doubleValue; }
|
||||
else if ([fontKey isEqualToString:@"space"]) { size = fonts.space.doubleValue; }
|
||||
else if ([fontKey isEqualToString:@"send"]) { size = fonts.send.doubleValue; }
|
||||
if (size <= 0.0) { size = 18.0; }
|
||||
return [self kb_scaledValue:size];
|
||||
}
|
||||
|
||||
- (KBKey *)kb_keyForItemId:(NSString *)itemId {
|
||||
if (itemId.length == 0) { return nil; }
|
||||
KBKeyboardKeyDef *def = [[self kb_layoutConfig] keyDefForIdentifier:itemId];
|
||||
if (def) {
|
||||
return [self kb_keyFromDef:def identifier:itemId];
|
||||
}
|
||||
|
||||
NSRange range = [itemId rangeOfString:@":"];
|
||||
if (range.location != NSNotFound) {
|
||||
NSString *prefix = [itemId substringToIndex:range.location];
|
||||
NSString *value = [itemId substringFromIndex:range.location + 1];
|
||||
if ([prefix isEqualToString:@"letter"]) {
|
||||
if (value.length == 1) {
|
||||
return [self kb_letterKeyWithChar:value];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
if ([prefix isEqualToString:@"digit"]) {
|
||||
NSString *identifier = [NSString stringWithFormat:@"digit_%@", value];
|
||||
KBKey *k = [KBKey keyWithIdentifier:identifier title:value output:value type:KBKeyTypeCharacter];
|
||||
k.caseVariant = KBKeyCaseVariantNone;
|
||||
return k;
|
||||
}
|
||||
if ([prefix isEqualToString:@"sym"]) {
|
||||
NSString *identifier = [self kb_identifierForSymbol:value];
|
||||
KBKey *k = [KBKey keyWithIdentifier:identifier title:value output:value type:KBKeyTypeCharacter];
|
||||
k.caseVariant = KBKeyCaseVariantNone;
|
||||
return k;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (KBKey *)kb_keyFromDef:(KBKeyboardKeyDef *)def identifier:(NSString *)identifier {
|
||||
KBKeyType type = [self kb_keyTypeForDef:def];
|
||||
NSString *title = def.title ?: @"";
|
||||
if (type == KBKeyTypeShift && self.shiftOn && def.selectedTitle.length > 0) {
|
||||
title = def.selectedTitle;
|
||||
}
|
||||
NSString *output = @"";
|
||||
switch (type) {
|
||||
case KBKeyTypeSpace:
|
||||
output = @" ";
|
||||
break;
|
||||
case KBKeyTypeReturn:
|
||||
output = @"\n";
|
||||
break;
|
||||
default:
|
||||
output = @"";
|
||||
break;
|
||||
}
|
||||
|
||||
NSString *finalId = identifier;
|
||||
if ([identifier isEqualToString:@"emoji"]) {
|
||||
finalId = KBKeyIdentifierEmojiPanel;
|
||||
} else if ([identifier isEqualToString:@"send"]) {
|
||||
finalId = @"return";
|
||||
}
|
||||
|
||||
KBKey *k = [KBKey keyWithIdentifier:finalId title:title output:output type:type];
|
||||
if (type == KBKeyTypeShift) {
|
||||
k.caseVariant = self.shiftOn ? KBKeyCaseVariantUpper : KBKeyCaseVariantLower;
|
||||
} else {
|
||||
k.caseVariant = KBKeyCaseVariantNone;
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
- (KBKeyType)kb_keyTypeForDef:(KBKeyboardKeyDef *)def {
|
||||
NSString *type = def.type.lowercaseString;
|
||||
if ([type isEqualToString:@"shift"]) return KBKeyTypeShift;
|
||||
if ([type isEqualToString:@"backspace"]) return KBKeyTypeBackspace;
|
||||
if ([type isEqualToString:@"mode"]) return KBKeyTypeModeChange;
|
||||
if ([type isEqualToString:@"symbolstoggle"]) return KBKeyTypeSymbolsToggle;
|
||||
if ([type isEqualToString:@"space"]) return KBKeyTypeSpace;
|
||||
if ([type isEqualToString:@"return"]) return KBKeyTypeReturn;
|
||||
if ([type isEqualToString:@"globe"]) return KBKeyTypeGlobe;
|
||||
return KBKeyTypeCustom;
|
||||
}
|
||||
|
||||
- (NSString *)kb_identifierForSymbol:(NSString *)symbol {
|
||||
if (symbol.length == 0) { return nil; }
|
||||
static NSDictionary<NSString *, NSString *> *map = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
map = @{
|
||||
@"-": @"sym_minus",
|
||||
@"/": @"sym_slash",
|
||||
@":": @"sym_colon",
|
||||
@";": @"sym_semicolon",
|
||||
@"(": @"sym_paren_l",
|
||||
@")": @"sym_paren_r",
|
||||
@"¥": @"sym_money",
|
||||
@"¥": @"sym_money",
|
||||
@"&": @"sym_amp",
|
||||
@"@": @"sym_at",
|
||||
@"\"": @"sym_quote_double",
|
||||
@"“": @"sym_quote_double",
|
||||
@"”": @"sym_quote_double",
|
||||
@".": @"sym_dot",
|
||||
@",": @"sym_comma",
|
||||
@"?": @"sym_question",
|
||||
@"!": @"sym_exclam",
|
||||
@"'": @"sym_quote_single",
|
||||
@"‘": @"sym_quote_single",
|
||||
@"’": @"sym_quote_single",
|
||||
@"[": @"sym_bracket_l",
|
||||
@"]": @"sym_bracket_r",
|
||||
@"{": @"sym_brace_l",
|
||||
@"}": @"sym_brace_r",
|
||||
@"#": @"sym_hash",
|
||||
@"%": @"sym_percent",
|
||||
@"^": @"sym_caret",
|
||||
@"*": @"sym_asterisk",
|
||||
@"+": @"sym_plus",
|
||||
@"=": @"sym_equal",
|
||||
@"_": @"sym_underscore",
|
||||
@"\\": @"sym_backslash",
|
||||
@"|": @"sym_pipe",
|
||||
@"~": @"sym_tilde",
|
||||
@"<": @"sym_lt",
|
||||
@">": @"sym_gt",
|
||||
@"€": @"sym_euro",
|
||||
@"$": @"sym_dollar",
|
||||
@"·": @"sym_bullet"
|
||||
};
|
||||
});
|
||||
return map[symbol];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)onKeyTapped:(KBKeyButton *)sender {
|
||||
|
||||
148
KBMaiPointEventTable.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# KBMaiPoint 埋点事件表(统一口径:iOS / Android / 后端)
|
||||
|
||||
## 统一约定(全端一致)
|
||||
|
||||
### 1)事件类型(event_type)
|
||||
- 页面曝光:`page_exposure`
|
||||
- 点击事件:`click`
|
||||
|
||||
> iOS 侧可映射为:`KBMaiPointGenericReportTypePage / KBMaiPointGenericReportTypeClick`
|
||||
|
||||
### 2)事件名称(event_name)
|
||||
- 统一使用 `lower_snake_case`,不绑定任何端的类名/资源名
|
||||
- 页面曝光统一前缀:`enter_`
|
||||
- 点击事件统一前缀:`click_`
|
||||
|
||||
### 3)事件参数(value / params)
|
||||
- **所有事件都固定带**:`token`(`NSString`,有就传真实值;没有就传空字符串 `""`)
|
||||
- 建议额外固定带:`page_id`(页面/区域统一ID)
|
||||
- 点击类事件建议固定带:`element_id`(控件/入口统一ID)
|
||||
- 列表/集合类点击建议带:`index`(`NSInteger`)与业务 `id`(如 `theme_id` / `product_id`)
|
||||
|
||||
参数示例(最小):
|
||||
```json
|
||||
{ "token": "", "page_id": "shop", "element_id": "search_btn" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## A. 主工程(keyBoard)
|
||||
|
||||
### A1)页面曝光(触发:VC 的 `viewDidAppear`)
|
||||
|
||||
| 注释 | 事件类型 | 事件名称 | page_id | iOS 对应页面 | Android 对应页面 | 触发时机 | 事件参数(示例) |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 进入首页 | page_exposure | enter_home_main | home_main | HomeMainVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_main" }` |
|
||||
| 进入首页Tab容器 | page_exposure | enter_home | home | HomeVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home" }` |
|
||||
| 进入热门页 | page_exposure | enter_home_hot | home_hot | HomeHotVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_hot" }` |
|
||||
| 进入排行榜页 | page_exposure | enter_home_rank | home_rank | HomeRankVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_rank" }` |
|
||||
| 进入排行榜内容页 | page_exposure | enter_home_rank_content | home_rank_content | HomeRankContentVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_rank_content" }` |
|
||||
| 进入首页底部弹层 | page_exposure | enter_home_sheet | home_sheet | HomeSheetVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_sheet" }` |
|
||||
| 进入社区页 | page_exposure | enter_community | community | KBCommunityVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"community" }` |
|
||||
| 进入搜索页 | page_exposure | enter_search | search | KBSearchVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"search" }` |
|
||||
| 进入搜索结果页 | page_exposure | enter_search_result | search_result | KBSearchResultVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"search_result" }` |
|
||||
| 进入商店页 | page_exposure | enter_shop | shop | KBShopVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"shop" }` |
|
||||
| 进入商店分类列表页 | page_exposure | enter_shop_item_list | shop_item_list | KBShopItemVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"shop_item_list" }` |
|
||||
| 进入皮肤详情页 | page_exposure | enter_skin_detail | skin_detail | KBSkinDetailVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"skin_detail", "theme_id":"" }` |
|
||||
| 进入我的页 | page_exposure | enter_my | my | MyVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"my" }` |
|
||||
| 进入我的皮肤页 | page_exposure | enter_my_skin | my_skin | MySkinVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"my_skin" }` |
|
||||
| 进入我的键盘配置页 | page_exposure | enter_my_keyboard | my_keyboard | KBMyKeyBoardVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"my_keyboard" }` |
|
||||
| 进入个人信息页 | page_exposure | enter_person_info | person_info | KBPersonInfoVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"person_info" }` |
|
||||
| 进入反馈页 | page_exposure | enter_feedback | feedback | KBFeedBackVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"feedback" }` |
|
||||
| 进入公告页 | page_exposure | enter_notice | notice | KBNoticeVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"notice" }` |
|
||||
| 进入消费记录页 | page_exposure | enter_consumption_record | consumption_record | KBConsumptionRecordVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"consumption_record" }` |
|
||||
| 进入VIP购买页 | page_exposure | enter_vip_pay | vip_pay | KBVipPay | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"vip_pay" }` |
|
||||
| 进入积分充值页 | page_exposure | enter_points_recharge | points_recharge | KBJfPay | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"points_recharge" }` |
|
||||
| 进入登录页 | page_exposure | enter_login | login | KBLoginVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"login" }` |
|
||||
| 进入邮箱登录页 | page_exposure | enter_login_email | login_email | KBEmailLoginVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"login_email" }` |
|
||||
| 进入邮箱注册页 | page_exposure | enter_register_email | register_email | KBEmailRegistVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"register_email" }` |
|
||||
| 进入注册验证码页 | page_exposure | enter_register_verify_email | register_verify_email | KBRegistVerEmailVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"register_verify_email" }` |
|
||||
| 进入忘记密码页 | page_exposure | enter_forgot_password_email | forgot_password_email | KBForgetPwdVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"forgot_password_email" }` |
|
||||
| 进入忘记密码验证码页 | page_exposure | enter_forgot_password_verify | forgot_password_verify | KBForgetVerPwdVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"forgot_password_verify" }` |
|
||||
| 进入忘记密码新密码页 | page_exposure | enter_forgot_password_newpwd | forgot_password_newpwd | KBForgetPwdNewPwdVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"forgot_password_newpwd" }` |
|
||||
| 进入键盘权限引导页(App内) | page_exposure | enter_keyboard_permission_guide | keyboard_permission_guide | KBPermissionViewController | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"keyboard_permission_guide" }` |
|
||||
| 进入首次引导页 | page_exposure | enter_guide | guide | KBGuideVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"guide" }` |
|
||||
| 进入性别选择页 | page_exposure | enter_sex_select | sex_select | KBSexSelVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"sex_select" }` |
|
||||
| 进入WebView页 | page_exposure | enter_webview | webview | KBWebViewViewController | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"webview", "url":"" }` |
|
||||
|
||||
> 测试/工具页(建议仅 DEBUG 或按需接入):`KBTestVC / KBLangTestVC / KBSkinCenterVC / ViewController / LoginViewController / KBLoginSheetViewController`。
|
||||
|
||||
### A2)点击事件(按钮/列表/入口)
|
||||
|
||||
| 注释 | 事件类型 | 事件名称 | page_id | element_id | iOS 对应控件/方法 | Android 对应控件 | 触发时机 | 事件参数(示例) |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| 首页点击“购买会员” | click | click_home_buy_vip_btn | home_main | buy_vip_btn | HomeHeadView `onTapBuyAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"home_main", "element_id":"buy_vip_btn" }` |
|
||||
| 首页点击“权限悬浮按钮” | click | click_home_permission_float_btn | home_main | permission_float_btn | HomeMainVC `keyPermissButton.clickDragViewBlock` | Android 自定义 | 点击悬浮按钮 | `{ "token":"", "page_id":"home_main", "element_id":"permission_float_btn" }` |
|
||||
| 权限引导页点击“去设置” | click | click_permission_open_settings_btn | keyboard_permission_guide | open_settings_btn | KBPermissionViewController `openSettings` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_permission_guide", "element_id":"open_settings_btn" }` |
|
||||
| 权限引导页点击“关闭” | click | click_permission_close_btn | keyboard_permission_guide | close_btn | KBPermissionViewController `closeButtonAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_permission_guide", "element_id":"close_btn" }` |
|
||||
| 商店页点击“搜索” | click | click_shop_search_btn | shop | search_btn | KBShopVC `searchBtnAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"shop", "element_id":"search_btn" }` |
|
||||
| 商店页点击“我的皮肤” | click | click_shop_my_skin_btn | shop | my_skin_btn | KBShopVC `skinBtnAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"shop", "element_id":"my_skin_btn" }` |
|
||||
| 商店列表点击皮肤卡片 | click | click_shop_theme_card | shop_item_list | theme_card | KBShopItemVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"shop_item_list", "element_id":"theme_card", "theme_id":"", "index":0 }` |
|
||||
| 皮肤详情点击“下载/购买” | click | click_skin_download_btn | skin_detail | download_btn | KBSkinDetailVC `handleDownloadAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"skin_detail", "element_id":"download_btn", "theme_id":"", "purchased":0 }` |
|
||||
| 皮肤详情点击“推荐皮肤” | click | click_skin_recommend_card | skin_detail | recommend_card | KBSkinDetailVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"skin_detail", "element_id":"recommend_card", "from_theme_id":"", "to_theme_id":"", "index":0 }` |
|
||||
| 搜索栏点击搜索 | click | click_search_submit | search | search_submit | KBSearchBarView `onSearch` | Android 自定义 | 点击搜索 | `{ "token":"", "page_id":"search", "element_id":"search_submit", "keyword_len":0 }` |
|
||||
| 搜索页点击历史词条 | click | click_search_history_item | search | history_item | KBSearchVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"search", "element_id":"history_item", "index":0 }` |
|
||||
| 搜索页点击“展开更多历史” | click | click_search_history_more | search | history_more | KBSearchVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"search", "element_id":"history_more" }` |
|
||||
| 搜索页点击“清空历史” | click | click_search_clear_history | search | clear_history | KBSearchVC `clearHistory`(header trash) | Android 自定义 | 点击垃圾桶 | `{ "token":"", "page_id":"search", "element_id":"clear_history" }` |
|
||||
| 搜索页点击推荐皮肤 | click | click_search_recommend_theme | search | recommend_theme_card | KBSearchVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"search", "element_id":"recommend_theme_card", "theme_id":"", "index":0 }` |
|
||||
| 搜索结果页点击皮肤 | click | click_search_result_theme | search_result | result_theme_card | KBSearchResultVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"search_result", "element_id":"result_theme_card", "theme_id":"", "index":0 }` |
|
||||
| 我的页点击菜单项 | click | click_my_menu_item | my | menu_item | MyVC `didSelectRowAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"my", "element_id":"menu_item", "item_id":"", "item_title":"" }` |
|
||||
| 我的页点击“邀请”成功复制 | click | click_my_invite_copy | my | invite_copy | MyVC(邀请分支) | Android 自定义 | 复制时机 | `{ "token":"", "page_id":"my", "element_id":"invite_copy" }` |
|
||||
| 反馈页点击提交 | click | click_feedback_commit_btn | feedback | commit_btn | KBFeedBackVC `onTapCommit` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"feedback", "element_id":"commit_btn", "content_len":0 }` |
|
||||
| 个人信息点击更换头像 | click | click_person_avatar_edit | person_info | avatar_edit | KBPersonInfoVC `onTapAvatarEdit` | Android 自定义 | tapGesture | `{ "token":"", "page_id":"person_info", "element_id":"avatar_edit" }` |
|
||||
| 个人信息点击退出登录 | click | click_person_logout_btn | person_info | logout_btn | KBPersonInfoVC `onTapLogout` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"person_info", "element_id":"logout_btn" }` |
|
||||
| 我的键盘页点击保存 | click | click_my_keyboard_save_btn | my_keyboard | save_btn | KBMyKeyBoardVC `onSave` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"my_keyboard", "element_id":"save_btn" }` |
|
||||
| 我的皮肤页点击编辑/取消 | click | click_my_skin_toggle_edit | my_skin | toggle_edit | MySkinVC `onToggleEdit` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"my_skin", "element_id":"toggle_edit", "editing":0 }` |
|
||||
| 我的皮肤页点击删除 | click | click_my_skin_delete_btn | my_skin | delete_btn | MySkinVC `onDelete` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"my_skin", "element_id":"delete_btn", "selected_count":0 }` |
|
||||
| 我的皮肤页点击皮肤(进入详情) | click | click_my_skin_theme_card | my_skin | theme_card | MySkinVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"my_skin", "element_id":"theme_card", "theme_id":"", "index":0 }` |
|
||||
| 登录页点击 Apple 登录 | click | click_login_apple_btn | login | apple_btn | KBLoginVC `onTapAppleLogin` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login", "element_id":"apple_btn" }` |
|
||||
| 登录页点击邮箱登录 | click | click_login_email_btn | login | email_btn | KBLoginVC `onTapEmailLogin` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login", "element_id":"email_btn" }` |
|
||||
| 登录页点击注册 | click | click_login_signup_btn | login | signup_btn | KBLoginVC `onTapSignUp` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login", "element_id":"signup_btn" }` |
|
||||
| 登录页点击忘记密码 | click | click_login_forgot_btn | login | forgot_btn | KBLoginVC `onTapForgotPassword` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login", "element_id":"forgot_btn" }` |
|
||||
| 邮箱登录页点击提交 | click | click_login_email_submit_btn | login_email | submit_btn | KBEmailLoginVC `onTapSubmit` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login_email", "element_id":"submit_btn" }` |
|
||||
| 邮箱注册页点击提交 | click | click_register_email_submit_btn | register_email | submit_btn | KBEmailRegistVC `onTapSubmit` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"register_email", "element_id":"submit_btn" }` |
|
||||
| 注册验证码页点击确认 | click | click_register_verify_confirm_btn | register_verify_email | confirm_btn | KBRegistVerEmailVC `onTapConfirm` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"register_verify_email", "element_id":"confirm_btn" }` |
|
||||
| 忘记密码(邮箱)点击下一步 | click | click_forgot_email_next_btn | forgot_password_email | next_btn | KBForgetPwdVC `onTapNext` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"forgot_password_email", "element_id":"next_btn" }` |
|
||||
| 忘记密码(验证码)点击下一步 | click | click_forgot_verify_next_btn | forgot_password_verify | next_btn | KBForgetVerPwdVC `onTapNext` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"forgot_password_verify", "element_id":"next_btn" }` |
|
||||
| 忘记密码(新密码)点击下一步 | click | click_forgot_newpwd_next_btn | forgot_password_newpwd | next_btn | KBForgetPwdNewPwdVC `onTapNext` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"forgot_password_newpwd", "element_id":"next_btn" }` |
|
||||
| VIP页选择套餐 | click | click_vip_select_plan | vip_pay | plan_item | KBVipPay `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"vip_pay", "element_id":"plan_item", "product_id":"", "index":0 }` |
|
||||
| VIP页点击支付 | click | click_vip_pay_btn | vip_pay | pay_btn | KBVipPay `onTapPayButton` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"vip_pay", "element_id":"pay_btn", "product_id":"" }` |
|
||||
| VIP页点击恢复购买 | click | click_vip_restore_btn | vip_pay | restore_btn | KBVipPay `onTapRestoreButton` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"vip_pay", "element_id":"restore_btn" }` |
|
||||
| VIP页点击关闭 | click | click_vip_close_btn | vip_pay | close_btn | KBVipPay `onTapClose` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"vip_pay", "element_id":"close_btn" }` |
|
||||
| 积分充值页选择商品 | click | click_points_select_product | points_recharge | product_item | KBJfPay `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"points_recharge", "element_id":"product_item", "product_id":"", "index":0 }` |
|
||||
| 积分充值页点击充值 | click | click_points_pay_btn | points_recharge | pay_btn | KBJfPay `onTapPayButton` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"points_recharge", "element_id":"pay_btn", "product_id":"" }` |
|
||||
| 引导页点击复制示例1 | click | click_guide_copy_example_1 | guide | copy_example_1 | KBGuideTopCell `kb_onTapQ1` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"guide", "element_id":"copy_example_1" }` |
|
||||
| 引导页点击复制示例2 | click | click_guide_copy_example_2 | guide | copy_example_2 | KBGuideTopCell `kb_onTapQ2` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"guide", "element_id":"copy_example_2" }` |
|
||||
|
||||
---
|
||||
|
||||
## B. 键盘扩展(CustomKeyboard)
|
||||
|
||||
### B1)页面曝光(触发:显示/切换时机)
|
||||
|
||||
| 注释 | 事件类型 | 事件名称 | page_id | iOS 对应页面/视图 | Android 对应页面 | 触发时机 | 事件参数(示例) |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 键盘首次显示 | page_exposure | enter_keyboard | keyboard | KeyboardViewController | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"keyboard" }` |
|
||||
| 打开功能面板 | page_exposure | enter_keyboard_function_panel | keyboard_function_panel | KBFunctionView | Android 自定义 | showFunctionPanel:YES | `{ "token":"", "page_id":"keyboard_function_panel" }` |
|
||||
| 关闭功能面板(回到主键盘) | page_exposure | enter_keyboard_main_panel | keyboard_main_panel | KBKeyBoardMainView | Android 自定义 | showFunctionPanel:NO | `{ "token":"", "page_id":"keyboard_main_panel" }` |
|
||||
| 打开设置页 | page_exposure | enter_keyboard_settings | keyboard_settings | KBSettingView | Android 自定义 | showSettingView:YES | `{ "token":"", "page_id":"keyboard_settings" }` |
|
||||
| 打开订阅/充值面板 | page_exposure | enter_keyboard_subscription_panel | keyboard_subscription_panel | KBKeyboardSubscriptionView | Android 自定义 | showSubscriptionPanel | `{ "token":"", "page_id":"keyboard_subscription_panel" }` |
|
||||
|
||||
### B2)点击事件(键盘工具栏 / 功能面板 / 订阅面板)
|
||||
|
||||
| 注释 | 事件类型 | 事件名称 | page_id | element_id | iOS 对应控件/方法 | Android 对应控件 | 触发时机 | 事件参数(示例) |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| 点击键盘顶部工具栏(index=0 打开功能面板) | click | click_keyboard_toolbar_action | keyboard_main_panel | toolbar_action | KBKeyBoardMainViewDelegate `didTapToolActionAtIndex:` | Android 自定义 | 点击工具栏 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"toolbar_action", "index":0 }` |
|
||||
| 点击键盘设置按钮 | click | click_keyboard_settings_btn | keyboard_main_panel | settings_btn | `keyBoardMainViewDidTapSettings:` | Android 自定义 | 点击设置 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"settings_btn" }` |
|
||||
| 点击设置页返回 | click | click_keyboard_settings_back_btn | keyboard_settings | back_btn | KeyboardViewController `onTapSettingsBack` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_settings", "element_id":"back_btn" }` |
|
||||
| 点击撤销删除 | click | click_keyboard_undo_btn | keyboard_main_panel | undo_btn | `keyBoardMainViewDidTapUndo:` | Android 自定义 | 点击撤销 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"undo_btn" }` |
|
||||
| 点击表情面板搜索 | click | click_keyboard_emoji_search_btn | keyboard_main_panel | emoji_search_btn | `keyBoardMainViewDidTapEmojiSearch:` | Android 自定义 | 点击搜索 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"emoji_search_btn" }` |
|
||||
| 点击联想词条 | click | click_keyboard_suggestion_item | keyboard_main_panel | suggestion_item | `didSelectSuggestion:` | Android 自定义 | 点击候选 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"suggestion_item", "index":0 }` |
|
||||
| 功能面板点击“粘贴” | click | click_keyboard_function_paste_btn | keyboard_function_panel | paste_btn | KBFunctionView `onTapPaste` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"paste_btn" }` |
|
||||
| 功能面板点击“删除” | click | click_keyboard_function_delete_btn | keyboard_function_panel | delete_btn | KBFunctionView `onTapDelete` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"delete_btn" }` |
|
||||
| 功能面板点击“清空” | click | click_keyboard_function_clear_btn | keyboard_function_panel | clear_btn | KBFunctionView `onTapClear` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"clear_btn" }` |
|
||||
| 功能面板点击“发送” | click | click_keyboard_function_send_btn | keyboard_function_panel | send_btn | KBFunctionView `onTapSend` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"send_btn" }` |
|
||||
| 功能面板点击“人设/标签”条目 | click | click_keyboard_function_tag_item | keyboard_function_panel | renshe_item | KBFunctionTagListView `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"renshe_item", "index":0, "id":456, "name":"" }` |
|
||||
| 功能面板右侧点击“登录/充值”入口(未登录走登录) | click | click_keyboard_function_right_action | keyboard_function_panel | right_action | KeyboardViewController `didRightTapToolActionAtIndex:` | Android 自定义 | 点击右侧入口 | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"right_action", "action":"login_or_recharge" }` |
|
||||
| 订阅面板点击关闭 | click | click_keyboard_subscription_close_btn | keyboard_subscription_panel | close_btn | `subscriptionViewDidTapClose:` | Android 自定义 | 点击关闭 | `{ "token":"", "page_id":"keyboard_subscription_panel", "element_id":"close_btn" }` |
|
||||
| 订阅面板点击购买某商品 | click | click_keyboard_subscription_product_btn | keyboard_subscription_panel | product_btn | `didTapPurchaseForProduct:` | Android 自定义 | 点击购买 | `{ "token":"", "page_id":"keyboard_subscription_panel", "element_id":"product_btn", "product_id":"", "index":0 }` |
|
||||
BIN
KBMaiPointEventTable.xlsx
Normal file
@@ -96,6 +96,6 @@ SPEC CHECKSUMS:
|
||||
SDWebImage: f29024626962457f3470184232766516dee8dfea
|
||||
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
|
||||
|
||||
PODFILE CHECKSUM: acf7541bd40dd969fa4950d6c000005b2889c85b
|
||||
PODFILE CHECKSUM: 3b9d37a9d2c323afb33b6389f3c70184f53ea313
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
2
Pods/Manifest.lock
generated
@@ -96,6 +96,6 @@ SPEC CHECKSUMS:
|
||||
SDWebImage: f29024626962457f3470184232766516dee8dfea
|
||||
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
|
||||
|
||||
PODFILE CHECKSUM: acf7541bd40dd969fa4950d6c000005b2889c85b
|
||||
PODFILE CHECKSUM: 3b9d37a9d2c323afb33b6389f3c70184f53ea313
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
1738
Pods/Pods.xcodeproj/project.pbxproj
generated
@@ -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"
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -88,7 +88,8 @@
|
||||
|
||||
#if __OBJC__
|
||||
static inline CGFloat KBScreenWidth(void) {
|
||||
return [UIScreen mainScreen].bounds.size.width;
|
||||
CGSize size = [UIScreen mainScreen].bounds.size;
|
||||
return MIN(size.width, size.height);
|
||||
}
|
||||
|
||||
static inline CGFloat KBScaleFactor(void) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#ifndef KB_MAI_POINT_BASE_URL
|
||||
#define KB_MAI_POINT_BASE_URL @"http://192.168.2.188:35310/api"
|
||||
#define KB_MAI_POINT_BASE_URL @"http://192.168.2.21:35310/api"
|
||||
#endif
|
||||
|
||||
#ifndef KB_MAI_POINT_PATH_NEW_ACCOUNT
|
||||
@@ -20,6 +20,8 @@
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern NSString * const KBMaiPointErrorDomain;
|
||||
extern NSString * const KBMaiPointEventTypePageExposure;
|
||||
extern NSString * const KBMaiPointEventTypeClick;
|
||||
|
||||
typedef void (^KBMaiPointReportCompletion)(BOOL success, NSError * _Nullable error);
|
||||
|
||||
@@ -39,6 +41,28 @@ typedef NS_ENUM(NSInteger, KBMaiPointGenericReportType) {
|
||||
|
||||
+ (instancetype)sharedReporter;
|
||||
|
||||
/// 统一埋点:POST /genericData
|
||||
/// - eventType: 建议取值 `page_exposure` / `click`
|
||||
/// - eventName: 统一事件名(如 enter_xxx / click_xxx)
|
||||
/// - value: 事件参数字典(内部会自动注入 token;无 token 时为 @"")
|
||||
- (void)reportEventType:(NSString *)eventType
|
||||
eventName:(NSString *)eventName
|
||||
value:(nullable NSDictionary *)value
|
||||
completion:(KBMaiPointReportCompletion _Nullable)completion;
|
||||
|
||||
/// 页面曝光快捷方法:内部会补齐 page_id
|
||||
- (void)reportPageExposureWithEventName:(NSString *)eventName
|
||||
pageId:(NSString *)pageId
|
||||
extra:(nullable NSDictionary *)extra
|
||||
completion:(KBMaiPointReportCompletion _Nullable)completion;
|
||||
|
||||
/// 点击快捷方法:内部会补齐 page_id / element_id
|
||||
- (void)reportClickWithEventName:(NSString *)eventName
|
||||
pageId:(NSString *)pageId
|
||||
elementId:(NSString *)elementId
|
||||
extra:(nullable NSDictionary *)extra
|
||||
completion:(KBMaiPointReportCompletion _Nullable)completion;
|
||||
|
||||
/// POST /newAccount with type + account.
|
||||
- (void)reportNewAccountWithType:(NSString *)type
|
||||
account:(nullable NSString *)account
|
||||
|
||||
@@ -5,8 +5,15 @@
|
||||
|
||||
#import "KBMaiPointReporter.h"
|
||||
#import "KBLog.h"
|
||||
#import "KBAuthManager.h"
|
||||
#if __has_include(<UIKit/UIKit.h>)
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <objc/runtime.h>
|
||||
#endif
|
||||
|
||||
NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain";
|
||||
NSString * const KBMaiPointEventTypePageExposure = @"page_exposure";
|
||||
NSString * const KBMaiPointEventTypeClick = @"click";
|
||||
|
||||
#if DEBUG
|
||||
static void KBMaiPoint_DebugLogURL(NSURLRequest *request) {
|
||||
@@ -47,12 +54,91 @@ static void KBMaiPoint_DebugLogError(NSURLResponse *response, NSError *error) {
|
||||
return [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] ?: @"";
|
||||
}
|
||||
|
||||
- (NSString *)kb_currentTokenOrEmpty {
|
||||
NSString *t = [KBAuthManager shared].current.accessToken;
|
||||
return [self kb_trimmedStringOrEmpty:t];
|
||||
}
|
||||
|
||||
- (void)reportEventType:(NSString *)eventType
|
||||
eventName:(NSString *)eventName
|
||||
value:(NSDictionary * _Nullable)value
|
||||
completion:(KBMaiPointReportCompletion _Nullable)completion {
|
||||
NSString *trimmedType = [self kb_trimmedStringOrEmpty:eventType];
|
||||
NSString *trimmedName = [self kb_trimmedStringOrEmpty:eventName];
|
||||
if (trimmedType.length == 0 || trimmedName.length == 0) {
|
||||
NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain
|
||||
code:-1
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"Invalid parameter"}];
|
||||
if (completion) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
completion(NO, error);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary *val = [NSMutableDictionary dictionary];
|
||||
if ([value isKindOfClass:[NSDictionary class]] && value.count > 0) {
|
||||
[val addEntriesFromDictionary:value];
|
||||
}
|
||||
if (![val[@"token"] isKindOfClass:NSString.class]) {
|
||||
val[@"token"] = [self kb_currentTokenOrEmpty];
|
||||
} else {
|
||||
// 若外部传了 token,也做一次兜底(nil -> @"" / trim)
|
||||
val[@"token"] = [self kb_trimmedStringOrEmpty:val[@"token"]];
|
||||
}
|
||||
|
||||
NSDictionary *params = @{
|
||||
// 字段兼容:后端若用 eventId 统计,也能直接用 eventName
|
||||
@"eventType": trimmedType,
|
||||
@"eventName": trimmedName,
|
||||
@"eventId": trimmedName,
|
||||
@"value": val.copy
|
||||
};
|
||||
[self postPath:KB_MAI_POINT_PATH_GENERIC_DATA parameters:params completion:completion];
|
||||
}
|
||||
|
||||
- (void)reportPageExposureWithEventName:(NSString *)eventName
|
||||
pageId:(NSString *)pageId
|
||||
extra:(NSDictionary * _Nullable)extra
|
||||
completion:(KBMaiPointReportCompletion _Nullable)completion {
|
||||
NSString *pid = [self kb_trimmedStringOrEmpty:pageId];
|
||||
NSMutableDictionary *val = [NSMutableDictionary dictionary];
|
||||
if (pid.length > 0) {
|
||||
val[@"page_id"] = pid;
|
||||
}
|
||||
if ([extra isKindOfClass:[NSDictionary class]] && extra.count > 0) {
|
||||
[val addEntriesFromDictionary:extra];
|
||||
}
|
||||
[self reportEventType:KBMaiPointEventTypePageExposure eventName:eventName value:val completion:completion];
|
||||
}
|
||||
|
||||
- (void)reportClickWithEventName:(NSString *)eventName
|
||||
pageId:(NSString *)pageId
|
||||
elementId:(NSString *)elementId
|
||||
extra:(NSDictionary * _Nullable)extra
|
||||
completion:(KBMaiPointReportCompletion _Nullable)completion {
|
||||
NSString *pid = [self kb_trimmedStringOrEmpty:pageId];
|
||||
NSString *eid = [self kb_trimmedStringOrEmpty:elementId];
|
||||
NSMutableDictionary *val = [NSMutableDictionary dictionary];
|
||||
if (pid.length > 0) {
|
||||
val[@"page_id"] = pid;
|
||||
}
|
||||
if (eid.length > 0) {
|
||||
val[@"element_id"] = eid;
|
||||
}
|
||||
if ([extra isKindOfClass:[NSDictionary class]] && extra.count > 0) {
|
||||
[val addEntriesFromDictionary:extra];
|
||||
}
|
||||
[self reportEventType:KBMaiPointEventTypeClick eventName:eventName value:val completion:completion];
|
||||
}
|
||||
|
||||
- (void)reportNewAccountWithType:(NSString *)type
|
||||
account:(NSString * _Nullable)account
|
||||
completion:(KBMaiPointReportCompletion _Nullable)completion {
|
||||
NSString *trimmedType = [self kb_trimmedStringOrEmpty:type];
|
||||
NSString *trimmedAccount = [self kb_trimmedStringOrEmpty:account];
|
||||
if (trimmedType.length == 0 || trimmedAccount.length == 0) {
|
||||
if (trimmedType.length == 0) {
|
||||
NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain
|
||||
code:-1
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"Invalid parameter"}];
|
||||
@@ -66,7 +152,8 @@ static void KBMaiPoint_DebugLogError(NSURLResponse *response, NSError *error) {
|
||||
|
||||
NSDictionary *params = @{
|
||||
@"type": trimmedType,
|
||||
@"account": trimmedAccount
|
||||
@"account": trimmedAccount ?: @"",
|
||||
@"token": [self kb_currentTokenOrEmpty]
|
||||
};
|
||||
[self postPath:KB_MAI_POINT_PATH_NEW_ACCOUNT parameters:params completion:completion];
|
||||
}
|
||||
@@ -83,27 +170,20 @@ static void KBMaiPoint_DebugLogError(NSURLResponse *response, NSError *error) {
|
||||
- (void)reportGenericDataWithEventType:(KBMaiPointGenericReportType)eventType
|
||||
account:(nullable NSString *)account
|
||||
completion:(KBMaiPointReportCompletion _Nullable)completion{
|
||||
// if ([KBUserSessionManager shared].isLoggedIn == false) {
|
||||
// return;
|
||||
// }
|
||||
NSString *trimmedAccount = [self kb_trimmedStringOrEmpty:account];
|
||||
if (trimmedAccount.length == 0) {
|
||||
NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain
|
||||
code:-1
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"Invalid parameter"}];
|
||||
if (completion) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
completion(NO, error);
|
||||
});
|
||||
}
|
||||
return;
|
||||
// 兼容旧接口:没有 eventName 时给一个默认值,避免调用方崩溃
|
||||
NSString *typeStr = @"unknown";
|
||||
switch (eventType) {
|
||||
case KBMaiPointGenericReportTypeClick: typeStr = KBMaiPointEventTypeClick; break;
|
||||
case KBMaiPointGenericReportTypeExposure: typeStr = @"exposure"; break;
|
||||
case KBMaiPointGenericReportTypePage: typeStr = KBMaiPointEventTypePageExposure; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
NSDictionary *params = @{
|
||||
@"eventId": @"123",
|
||||
@"account": account
|
||||
};
|
||||
[self postPath:KB_MAI_POINT_PATH_GENERIC_DATA parameters:params completion:completion];
|
||||
NSMutableDictionary *val = [NSMutableDictionary dictionary];
|
||||
NSString *trimmedAccount = [self kb_trimmedStringOrEmpty:account];
|
||||
if (trimmedAccount.length > 0) {
|
||||
val[@"account"] = trimmedAccount;
|
||||
}
|
||||
[self reportEventType:typeStr eventName:@"generic_event" value:val completion:completion];
|
||||
}
|
||||
|
||||
- (void)postPath:(NSString *)path
|
||||
@@ -197,3 +277,123 @@ static void KBMaiPoint_DebugLogError(NSURLResponse *response, NSError *error) {
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#if __has_include(<UIKit/UIKit.h>)
|
||||
|
||||
// ============================
|
||||
// 自动页面曝光(viewDidAppear)
|
||||
// 说明:仅对“在表里登记过的 VC”生效;其它 VC 不处理。
|
||||
// ============================
|
||||
|
||||
static NSDictionary<NSString *, NSDictionary *> *KBMaiPoint_PageExposureMap(void) {
|
||||
static NSDictionary<NSString *, NSDictionary *> *m;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
m = @{
|
||||
// 主工程
|
||||
@"HomeMainVC": @{@"event_name": @"enter_home_main", @"page_id": @"home_main"},
|
||||
@"HomeVC": @{@"event_name": @"enter_home", @"page_id": @"home"},
|
||||
@"HomeHotVC": @{@"event_name": @"enter_home_hot", @"page_id": @"home_hot"},
|
||||
@"HomeRankVC": @{@"event_name": @"enter_home_rank", @"page_id": @"home_rank"},
|
||||
@"HomeRankContentVC": @{@"event_name": @"enter_home_rank_content", @"page_id": @"home_rank_content"},
|
||||
@"HomeSheetVC": @{@"event_name": @"enter_home_sheet", @"page_id": @"home_sheet"},
|
||||
@"KBCommunityVC": @{@"event_name": @"enter_community", @"page_id": @"community"},
|
||||
@"KBSearchVC": @{@"event_name": @"enter_search", @"page_id": @"search"},
|
||||
@"KBSearchResultVC": @{@"event_name": @"enter_search_result", @"page_id": @"search_result"},
|
||||
@"KBShopVC": @{@"event_name": @"enter_shop", @"page_id": @"shop"},
|
||||
@"KBShopItemVC": @{@"event_name": @"enter_shop_item_list", @"page_id": @"shop_item_list"},
|
||||
@"KBSkinDetailVC": @{@"event_name": @"enter_skin_detail", @"page_id": @"skin_detail"},
|
||||
@"MyVC": @{@"event_name": @"enter_my", @"page_id": @"my"},
|
||||
@"MySkinVC": @{@"event_name": @"enter_my_skin", @"page_id": @"my_skin"},
|
||||
@"KBMyKeyBoardVC": @{@"event_name": @"enter_my_keyboard", @"page_id": @"my_keyboard"},
|
||||
@"KBPersonInfoVC": @{@"event_name": @"enter_person_info", @"page_id": @"person_info"},
|
||||
@"KBFeedBackVC": @{@"event_name": @"enter_feedback", @"page_id": @"feedback"},
|
||||
@"KBNoticeVC": @{@"event_name": @"enter_notice", @"page_id": @"notice"},
|
||||
@"KBConsumptionRecordVC": @{@"event_name": @"enter_consumption_record", @"page_id": @"consumption_record"},
|
||||
@"KBVipPay": @{@"event_name": @"enter_vip_pay", @"page_id": @"vip_pay"},
|
||||
@"KBJfPay": @{@"event_name": @"enter_points_recharge", @"page_id": @"points_recharge"},
|
||||
@"KBLoginVC": @{@"event_name": @"enter_login", @"page_id": @"login"},
|
||||
@"KBEmailLoginVC": @{@"event_name": @"enter_login_email", @"page_id": @"login_email"},
|
||||
@"KBEmailRegistVC": @{@"event_name": @"enter_register_email", @"page_id": @"register_email"},
|
||||
@"KBRegistVerEmailVC": @{@"event_name": @"enter_register_verify_email", @"page_id": @"register_verify_email"},
|
||||
@"KBForgetPwdVC": @{@"event_name": @"enter_forgot_password_email", @"page_id": @"forgot_password_email"},
|
||||
@"KBForgetVerPwdVC": @{@"event_name": @"enter_forgot_password_verify", @"page_id": @"forgot_password_verify"},
|
||||
@"KBForgetPwdNewPwdVC": @{@"event_name": @"enter_forgot_password_newpwd", @"page_id": @"forgot_password_newpwd"},
|
||||
@"KBPermissionViewController": @{@"event_name": @"enter_keyboard_permission_guide", @"page_id": @"keyboard_permission_guide"},
|
||||
@"KBGuideVC": @{@"event_name": @"enter_guide", @"page_id": @"guide"},
|
||||
@"KBSexSelVC": @{@"event_name": @"enter_sex_select", @"page_id": @"sex_select"},
|
||||
@"KBWebViewViewController": @{@"event_name": @"enter_webview", @"page_id": @"webview"},
|
||||
|
||||
// 键盘扩展
|
||||
@"KeyboardViewController": @{@"event_name": @"enter_keyboard", @"page_id": @"keyboard"},
|
||||
};
|
||||
});
|
||||
return m;
|
||||
}
|
||||
|
||||
static inline void KBMaiPoint_SwizzleInstanceMethod(Class cls, SEL originalSel, SEL swizzledSel) {
|
||||
Method original = class_getInstanceMethod(cls, originalSel);
|
||||
Method swizzled = class_getInstanceMethod(cls, swizzledSel);
|
||||
if (!original || !swizzled) return;
|
||||
BOOL added = class_addMethod(cls,
|
||||
originalSel,
|
||||
method_getImplementation(swizzled),
|
||||
method_getTypeEncoding(swizzled));
|
||||
if (added) {
|
||||
class_replaceMethod(cls,
|
||||
swizzledSel,
|
||||
method_getImplementation(original),
|
||||
method_getTypeEncoding(original));
|
||||
} else {
|
||||
method_exchangeImplementations(original, swizzled);
|
||||
}
|
||||
}
|
||||
|
||||
@interface UIViewController (KBMaiPointAutoReport)
|
||||
@end
|
||||
|
||||
@implementation UIViewController (KBMaiPointAutoReport)
|
||||
|
||||
+ (void)load {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
KBMaiPoint_SwizzleInstanceMethod(self, @selector(viewDidAppear:), @selector(kb_maipoint_viewDidAppear:));
|
||||
});
|
||||
}
|
||||
|
||||
- (void)kb_maipoint_viewDidAppear:(BOOL)animated {
|
||||
[self kb_maipoint_viewDidAppear:animated];
|
||||
|
||||
NSString *clsName = NSStringFromClass(self.class);
|
||||
NSDictionary *cfg = KBMaiPoint_PageExposureMap()[clsName];
|
||||
if (![cfg isKindOfClass:NSDictionary.class]) { return; }
|
||||
|
||||
NSString *eventName = cfg[@"event_name"];
|
||||
NSString *pageId = cfg[@"page_id"];
|
||||
if (![eventName isKindOfClass:NSString.class] || ![pageId isKindOfClass:NSString.class]) { return; }
|
||||
|
||||
// 少数页面带额外参数(尽量不取敏感信息)
|
||||
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
|
||||
if ([clsName isEqualToString:@"KBSkinDetailVC"]) {
|
||||
id themeId = nil;
|
||||
@try { themeId = [self valueForKey:@"themeId"]; } @catch (__unused NSException *e) { themeId = nil; }
|
||||
if ([themeId isKindOfClass:NSString.class] && ((NSString *)themeId).length > 0) {
|
||||
extra[@"theme_id"] = themeId;
|
||||
}
|
||||
} else if ([clsName isEqualToString:@"KBWebViewViewController"]) {
|
||||
id url = nil;
|
||||
@try { url = [self valueForKey:@"url"]; } @catch (__unused NSException *e) { url = nil; }
|
||||
if ([url isKindOfClass:NSString.class] && ((NSString *)url).length > 0) {
|
||||
extra[@"url"] = url;
|
||||
}
|
||||
}
|
||||
|
||||
[[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:eventName
|
||||
pageId:pageId
|
||||
extra:(extra.count > 0 ? extra.copy : nil)
|
||||
completion:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
@@ -226,6 +226,12 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
|
||||
if ([forceValue respondsToSelector:@selector(boolValue)]) {
|
||||
forceDownload = [forceValue boolValue];
|
||||
}
|
||||
id serverIcons = skinJSON[@"key_icons"];
|
||||
NSUInteger serverIconCount = [serverIcons isKindOfClass:NSDictionary.class] ? ((NSDictionary *)serverIcons).count : 0;
|
||||
NSLog(@"[SkinBridge] request id=%@ zip=%@ force=%d key_icons_class=%@ count=%tu",
|
||||
skinId, zipURL, forceDownload,
|
||||
serverIcons ? NSStringFromClass([serverIcons class]) : @"nil",
|
||||
serverIconCount);
|
||||
|
||||
// key_icons 可选:
|
||||
// - 若后端提供 key_icons,则优先使用服务端映射;
|
||||
@@ -236,6 +242,9 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
|
||||
} else {
|
||||
iconShortNames = [self defaultIconShortNames];
|
||||
}
|
||||
NSLog(@"[SkinBridge] iconShortNames source=%@ count=%tu",
|
||||
[skinJSON[@"key_icons"] isKindOfClass:NSDictionary.class] ? @"server" : @"default",
|
||||
iconShortNames.count);
|
||||
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
NSURL *containerURL = [fm containerURLForSecurityApplicationGroupIdentifier:AppGroup];
|
||||
@@ -262,6 +271,7 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
|
||||
NSArray *contents = hasIconsDir ? [fm contentsOfDirectoryAtPath:iconsDir error:NULL] : nil;
|
||||
// 标记在本次请求发起前是否已经有缓存资源(用于“有缓存但本次下载失败”时仍允许切换皮肤)。
|
||||
BOOL hasCachedAssets = (contents.count > 0);
|
||||
NSLog(@"[SkinBridge] assets cache id=%@ cached=%d iconsDir=%@", skinId, hasCachedAssets, iconsDir);
|
||||
|
||||
NSString *bgPath = [skinRoot stringByAppendingPathComponent:@"background.png"];
|
||||
BOOL useTempRoot = forceDownload;
|
||||
@@ -482,6 +492,8 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
|
||||
// 若既没有预先存在的缓存资源,也没有在本次流程中成功解压出资源,
|
||||
// 说明当前皮肤 B 的资源完全不可用,此时不应覆盖现有皮肤主题。
|
||||
BOOL hasAssets = (didUnzip || (!forceDownload && hasCachedAssets));
|
||||
NSLog(@"[SkinBridge] apply check id=%@ hasAssets=%d didUnzip=%d cached=%d",
|
||||
skinId, hasAssets, didUnzip, hasCachedAssets);
|
||||
if (!hasAssets) {
|
||||
NSError *finalError = innerError ?: [NSError errorWithDomain:KBSkinBridgeErrorDomain
|
||||
code:KBSkinBridgeErrorZipMissing
|
||||
@@ -491,7 +503,9 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
|
||||
return;
|
||||
}
|
||||
|
||||
// 构造 key_icons -> App Group 相对路径 映射
|
||||
// 构造 key_icons -> App Group 相对路径 映射(仅保留实际存在的图标)
|
||||
NSString *iconsDirFinal = [skinRoot stringByAppendingPathComponent:@"icons"];
|
||||
__block NSUInteger missingCount = 0;
|
||||
NSMutableDictionary<NSString *, NSString *> *iconPathMap = [NSMutableDictionary dictionary];
|
||||
[iconShortNames enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, NSString *shortName, BOOL *stop) {
|
||||
if (![shortName isKindOfClass:NSString.class] || shortName.length == 0) return;
|
||||
@@ -500,9 +514,27 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
|
||||
if (fileName.pathExtension.length == 0) {
|
||||
fileName = [fileName stringByAppendingPathExtension:@"png"];
|
||||
}
|
||||
NSString *fullPath = [iconsDirFinal stringByAppendingPathComponent:fileName];
|
||||
if (![fm fileExistsAtPath:fullPath]) {
|
||||
missingCount += 1;
|
||||
if (missingCount <= 5) {
|
||||
NSLog(@"[SkinBridge] icon missing id=%@ short=%@", identifier, fileName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
NSString *relative = [NSString stringWithFormat:@"Skins/%@/icons/%@", skinId, fileName];
|
||||
iconPathMap[identifier] = relative;
|
||||
}];
|
||||
if (missingCount > 0) {
|
||||
NSLog(@"[SkinBridge] icon missing count=%tu total=%tu", missingCount, iconShortNames.count);
|
||||
}
|
||||
NSLog(@"[SkinBridge] iconPathMap count=%tu shift=%@ shift_upper=%@ backspace=%@ mode_123=%@ return=%@",
|
||||
iconPathMap.count,
|
||||
iconPathMap[@"shift"],
|
||||
iconPathMap[@"shift_upper"],
|
||||
iconPathMap[@"backspace"],
|
||||
iconPathMap[@"mode_123"],
|
||||
iconPathMap[@"return"]);
|
||||
|
||||
NSMutableDictionary *themeJSON = [skinJSON mutableCopy];
|
||||
themeJSON[@"id"] = skinId;
|
||||
@@ -515,6 +547,8 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
|
||||
// 背景图优先从 Zip 解压出的 background.png 读取
|
||||
NSData *bgData = [NSData dataWithContentsOfFile:bgPath];
|
||||
BOOL ok = themeOK;
|
||||
NSLog(@"[SkinBridge] theme apply id=%@ themeOK=%d bg=%d",
|
||||
skinId, themeOK, (bgData.length > 0));
|
||||
if (bgData.length > 0) {
|
||||
ok = [[KBSkinManager shared] applyImageSkinWithData:bgData skinId:skinId name:name];
|
||||
}
|
||||
@@ -748,6 +782,8 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
|
||||
shortNames = [self defaultIconShortNames];
|
||||
}
|
||||
|
||||
NSString *iconsDirFinal = iconsDir;
|
||||
__block NSUInteger missingCount = 0;
|
||||
NSMutableDictionary<NSString *, NSString *> *iconPathMap = [NSMutableDictionary dictionary];
|
||||
[shortNames enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, NSString *shortName, BOOL *stop) {
|
||||
if (identifier.length == 0 || ![shortName isKindOfClass:NSString.class] || shortName.length == 0) return;
|
||||
@@ -755,9 +791,20 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
|
||||
if (fileName.pathExtension.length == 0) {
|
||||
fileName = [fileName stringByAppendingPathExtension:@"png"];
|
||||
}
|
||||
NSString *fullPath = [iconsDirFinal stringByAppendingPathComponent:fileName];
|
||||
if (![fm fileExistsAtPath:fullPath]) {
|
||||
missingCount += 1;
|
||||
if (missingCount <= 5) {
|
||||
NSLog(@"[SkinBridge] icon missing(bundle) id=%@ short=%@", identifier, fileName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
NSString *relative = [NSString stringWithFormat:@"Skins/%@/icons/%@", skinId, fileName];
|
||||
iconPathMap[identifier] = relative;
|
||||
}];
|
||||
if (missingCount > 0) {
|
||||
NSLog(@"[SkinBridge] icon missing(bundle) count=%tu total=%tu", missingCount, shortNames.count);
|
||||
}
|
||||
|
||||
NSMutableDictionary *themeJSON = [NSMutableDictionary dictionary];
|
||||
themeJSON[@"id"] = skinId;
|
||||
|
||||
@@ -152,6 +152,18 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
|
||||
if ([icons isKindOfClass:NSDictionary.class]) {
|
||||
t.keyIconMap = icons;
|
||||
}
|
||||
NSUInteger iconCount = [t.keyIconMap isKindOfClass:NSDictionary.class] ? t.keyIconMap.count : 0;
|
||||
NSUInteger hiddenCount = t.hiddenKeyTextIdentifiers.count;
|
||||
NSLog(@"[SkinManager] applyThemeFromJSON id=%@ name=%@ iconMap=%tu hiddenKeys=%tu",
|
||||
t.skinId, t.name, iconCount, hiddenCount);
|
||||
if (iconCount > 0) {
|
||||
NSLog(@"[SkinManager] iconMap sample shift=%@ shift_upper=%@ backspace=%@ mode_123=%@ return=%@",
|
||||
t.keyIconMap[@"shift"],
|
||||
t.keyIconMap[@"shift_upper"],
|
||||
t.keyIconMap[@"backspace"],
|
||||
t.keyIconMap[@"mode_123"],
|
||||
t.keyIconMap[@"return"]);
|
||||
}
|
||||
return [self applyTheme:t];
|
||||
}
|
||||
|
||||
@@ -249,6 +261,19 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
|
||||
}
|
||||
|
||||
- (UIImage *)iconImageForKeyIdentifier:(NSString *)identifier caseVariant:(NSInteger)caseVariant {
|
||||
#if DEBUG
|
||||
static NSSet<NSString *> *kb_debugIconIds;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
kb_debugIconIds = [NSSet setWithObjects:
|
||||
@"shift", @"backspace", @"mode_123", @"mode_abc",
|
||||
@"symbols_toggle_more", @"symbols_toggle_123",
|
||||
@"return", @"space", @"emoji_panel", @"letter_q",
|
||||
nil];
|
||||
});
|
||||
BOOL shouldLog = [kb_debugIconIds containsObject:identifier];
|
||||
#endif
|
||||
|
||||
NSDictionary<NSString *, NSString *> *map = self.current.keyIconMap;
|
||||
NSString *value = nil;
|
||||
|
||||
@@ -292,10 +317,23 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
|
||||
UIImage *img = [UIImage imageWithContentsOfFile:fullPath];
|
||||
if (img) return img;
|
||||
}
|
||||
#if DEBUG
|
||||
if (shouldLog) {
|
||||
NSLog(@"[SkinManager] icon file missing id=%@ value=%@ skin=%@",
|
||||
identifier, value, self.current.skinId ?: @"");
|
||||
}
|
||||
#endif
|
||||
return nil;
|
||||
}
|
||||
// 否则按本地 Assets 名称加载(兼容旧实现)
|
||||
return [UIImage imageNamed:value];
|
||||
UIImage *img = [UIImage imageNamed:value];
|
||||
#if DEBUG
|
||||
if (!img && shouldLog) {
|
||||
NSLog(@"[SkinManager] icon asset missing id=%@ value=%@ skin=%@",
|
||||
identifier, value, self.current.skinId ?: @"");
|
||||
}
|
||||
#endif
|
||||
return img;
|
||||
}
|
||||
|
||||
// 兜底:若 keyIconMap 中没有该键,则按照约定的命名规则直接从 App Group 读取:
|
||||
@@ -329,6 +367,12 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
|
||||
if (img) return img;
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
if (shouldLog) {
|
||||
NSLog(@"[SkinManager] icon fallback missing id=%@ variant=%ld skin=%@",
|
||||
identifier, (long)caseVariant, self.current.skinId ?: @"");
|
||||
}
|
||||
#endif
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */; };
|
||||
@@ -27,7 +28,7 @@
|
||||
04286A062ECC81B200CE730C /* KBSkinService.m in Sources */ = {isa = PBXBuildFile; fileRef = 04286A052ECC81B200CE730C /* KBSkinService.m */; };
|
||||
04286A0B2ECD88B400CE730C /* KeyboardAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 04286A0A2ECD88B400CE730C /* KeyboardAssets.xcassets */; };
|
||||
04286A0F2ECDA71B00CE730C /* 001.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04286A0E2ECDA71B00CE730C /* 001.zip */; };
|
||||
04286A132ECDEBF900CE730C /* KBSkinIconMap.strings in Resources */ = {isa = PBXBuildFile; fileRef = 04286A122ECDEBF900CE730C /* KBSkinIconMap.strings */; };
|
||||
04286A132ECDEBF900CE730C /* KBSkinIconMap.strings in Resources */ = {isa = PBXBuildFile; fileRef = 041007D12ECE012000D203BB /* KBSkinIconMap.strings */; };
|
||||
043FBCD22EAF97630036AFE1 /* KBPermissionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 04C6EAE12EAF940F0089C901 /* KBPermissionViewController.m */; };
|
||||
0450AA742EF013D000B6AF06 /* KBEmojiCollectionCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0450AA732EF013D000B6AF06 /* KBEmojiCollectionCell.m */; };
|
||||
0450AAE22EF03D5100B6AF06 /* KBPerson.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450AAE12EF03D5100B6AF06 /* KBPerson.swift */; };
|
||||
@@ -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 /* fense.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04791FFA2ED5EAB8004E8522 /* fense.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 */; };
|
||||
@@ -129,10 +129,6 @@
|
||||
0498BD8C2EE69E15006CC1D5 /* KBTagItemModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD8A2EE69E15006CC1D5 /* KBTagItemModel.m */; };
|
||||
0498BD8F2EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */; };
|
||||
0498BD902EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */; };
|
||||
A1F0C1D22FACAD0012345678 /* KBMaiPointReporter.m in Sources */ = {isa = PBXBuildFile; fileRef = A1F0C1D12FACAD0012345678 /* KBMaiPointReporter.m */; };
|
||||
A1F0C1D32FACAD0012345678 /* KBMaiPointReporter.m in Sources */ = {isa = PBXBuildFile; fileRef = A1F0C1D12FACAD0012345678 /* KBMaiPointReporter.m */; };
|
||||
A1F0C1C22FABCDEF12345678 /* KBInviteCodeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = A1F0C1C12FABCDEF12345678 /* KBInviteCodeModel.m */; };
|
||||
A1F0C1C32FABCDEF12345678 /* KBInviteCodeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = A1F0C1C12FABCDEF12345678 /* KBInviteCodeModel.m */; };
|
||||
0498BDDA2EE7ECEA006CC1D5 /* WJXEventSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDD82EE7ECEA006CC1D5 /* WJXEventSource.m */; };
|
||||
0498BDDE2EE81508006CC1D5 /* KBShopVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDDD2EE81508006CC1D5 /* KBShopVM.m */; };
|
||||
0498BDE12EEA87C9006CC1D5 /* KBShopStyleModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDE02EEA87C8006CC1D5 /* KBShopStyleModel.m */; };
|
||||
@@ -149,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 */; };
|
||||
@@ -176,6 +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 */; };
|
||||
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 */; };
|
||||
@@ -205,6 +201,7 @@
|
||||
04FEDB032EFE000000123456 /* KBEmojiBottomBarView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FEDB022EFE000000123456 /* KBEmojiBottomBarView.m */; };
|
||||
04FEDC122F00010000999999 /* KBKeyboardSubscriptionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FEDC112F00010000999999 /* KBKeyboardSubscriptionView.m */; };
|
||||
04FEDC222F00020000999999 /* KBKeyboardSubscriptionProduct.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FEDC212F00020000999999 /* KBKeyboardSubscriptionProduct.m */; };
|
||||
04FEDC252F10000100000001 /* KBKeyboardLayoutConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FEDC242F10000100000001 /* KBKeyboardLayoutConfig.m */; };
|
||||
04FEDC322F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FEDC312F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.m */; };
|
||||
04FEDC422F00040000999999 /* KBKeyboardSubscriptionFeatureMarqueeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FEDC412F00040000999999 /* KBKeyboardSubscriptionFeatureMarqueeView.m */; };
|
||||
05A1B2D12F5B1A2B3C4D5E60 /* KBSearchVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A1B2C52F5B1A2B3C4D5E60 /* KBSearchVM.m */; };
|
||||
@@ -218,6 +215,7 @@
|
||||
A1B2C3EA2F20000000000001 /* KBSuggestionEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3E72F20000000000001 /* KBSuggestionEngine.m */; };
|
||||
A1B2C3EB2F20000000000001 /* KBSuggestionBarView.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3E92F20000000000001 /* KBSuggestionBarView.m */; };
|
||||
A1B2C3ED2F20000000000001 /* kb_words.txt in Resources */ = {isa = PBXBuildFile; fileRef = A1B2C3EC2F20000000000001 /* kb_words.txt */; };
|
||||
A1B2C3F12F20000000000002 /* kb_keyboard_layout_config.json in Resources */ = {isa = PBXBuildFile; fileRef = A1B2C3F02F20000000000002 /* kb_keyboard_layout_config.json */; };
|
||||
A1B2C3F42EB35A9900000001 /* KBFullAccessGuideView.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3F22EB35A9900000001 /* KBFullAccessGuideView.m */; };
|
||||
A1B2C4002EB4A0A100000003 /* KBAuthManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C4002EB4A0A100000002 /* KBAuthManager.m */; };
|
||||
A1B2C4002EB4A0A100000004 /* KBAuthManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C4002EB4A0A100000002 /* KBAuthManager.m */; };
|
||||
@@ -232,6 +230,10 @@
|
||||
A1F0C1B12F1234567890ABCD /* KBConsumptionRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = A1F0C1A12F1234567890ABCD /* KBConsumptionRecord.m */; };
|
||||
A1F0C1B22F1234567890ABCD /* KBConsumptionRecordCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A1F0C1A32F1234567890ABCD /* KBConsumptionRecordCell.m */; };
|
||||
A1F0C1B32F1234567890ABCD /* KBConsumptionRecordVC.m in Sources */ = {isa = PBXBuildFile; fileRef = A1F0C1A52F1234567890ABCD /* KBConsumptionRecordVC.m */; };
|
||||
A1F0C1C22FABCDEF12345678 /* KBInviteCodeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = A1F0C1C12FABCDEF12345678 /* KBInviteCodeModel.m */; };
|
||||
A1F0C1C32FABCDEF12345678 /* KBInviteCodeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = A1F0C1C12FABCDEF12345678 /* KBInviteCodeModel.m */; };
|
||||
A1F0C1D22FACAD0012345678 /* KBMaiPointReporter.m in Sources */ = {isa = PBXBuildFile; fileRef = A1F0C1D12FACAD0012345678 /* KBMaiPointReporter.m */; };
|
||||
A1F0C1D32FACAD0012345678 /* KBMaiPointReporter.m in Sources */ = {isa = PBXBuildFile; fileRef = A1F0C1D12FACAD0012345678 /* KBMaiPointReporter.m */; };
|
||||
EB72B60040437E3C0A4890FC /* KBShopThemeDetailModel.m in Sources */ = {isa = PBXBuildFile; fileRef = B9F60894E529C3EDAF6BAC3D /* KBShopThemeDetailModel.m */; };
|
||||
ECC9EE02174D86E8D792472F /* Pods_keyBoard.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 967065BB5230E43F293B3AF9 /* Pods_keyBoard.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
@@ -296,7 +298,6 @@
|
||||
04286A052ECC81B200CE730C /* KBSkinService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinService.m; sourceTree = "<group>"; };
|
||||
04286A0A2ECD88B400CE730C /* KeyboardAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = KeyboardAssets.xcassets; sourceTree = "<group>"; };
|
||||
04286A0E2ECDA71B00CE730C /* 001.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = 001.zip; sourceTree = "<group>"; };
|
||||
04286A122ECDEBF900CE730C /* KBSkinIconMap.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = KBSkinIconMap.strings; sourceTree = "<group>"; };
|
||||
0450AA722EF013D000B6AF06 /* KBEmojiCollectionCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBEmojiCollectionCell.h; sourceTree = "<group>"; };
|
||||
0450AA732EF013D000B6AF06 /* KBEmojiCollectionCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBEmojiCollectionCell.m; sourceTree = "<group>"; };
|
||||
0450AAE02EF03D5100B6AF06 /* keyBoard-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "keyBoard-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
@@ -349,7 +350,6 @@
|
||||
04791F962ED49CE7004E8522 /* KBFont.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFont.h; sourceTree = "<group>"; };
|
||||
04791F972ED49CE7004E8522 /* KBFont.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFont.m; sourceTree = "<group>"; };
|
||||
04791FF62ED5B985004E8522 /* Christmas.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = Christmas.zip; sourceTree = "<group>"; };
|
||||
04791FFA2ED5EAB8004E8522 /* fense.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = fense.zip; sourceTree = "<group>"; };
|
||||
04791FFD2ED830FA004E8522 /* KBKeyboardMaskView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKeyboardMaskView.h; sourceTree = "<group>"; };
|
||||
04791FFE2ED830FA004E8522 /* KBKeyboardMaskView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardMaskView.m; sourceTree = "<group>"; };
|
||||
047920062ED86ABC004E8522 /* kb_guide_keyboard.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = kb_guide_keyboard.gif; sourceTree = "<group>"; };
|
||||
@@ -460,10 +460,6 @@
|
||||
0498BD8A2EE69E15006CC1D5 /* KBTagItemModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBTagItemModel.m; sourceTree = "<group>"; };
|
||||
0498BD8D2EE6A3BD006CC1D5 /* KBMyMainModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBMyMainModel.h; sourceTree = "<group>"; };
|
||||
0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyMainModel.m; sourceTree = "<group>"; };
|
||||
A1F0C1C02FABCDEF12345678 /* KBInviteCodeModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBInviteCodeModel.h; sourceTree = "<group>"; };
|
||||
A1F0C1C12FABCDEF12345678 /* KBInviteCodeModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBInviteCodeModel.m; sourceTree = "<group>"; };
|
||||
A1F0C1D02FACAD0012345678 /* KBMaiPointReporter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBMaiPointReporter.h; sourceTree = "<group>"; };
|
||||
A1F0C1D12FACAD0012345678 /* KBMaiPointReporter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMaiPointReporter.m; sourceTree = "<group>"; };
|
||||
0498BDD72EE7ECEA006CC1D5 /* WJXEventSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WJXEventSource.h; sourceTree = "<group>"; };
|
||||
0498BDD82EE7ECEA006CC1D5 /* WJXEventSource.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WJXEventSource.m; sourceTree = "<group>"; };
|
||||
0498BDDC2EE81508006CC1D5 /* KBShopVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBShopVM.h; sourceTree = "<group>"; };
|
||||
@@ -497,10 +493,6 @@
|
||||
049FB22B2EC31F8800FAB05D /* KBGenderPickerPopView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBGenderPickerPopView.m; sourceTree = "<group>"; };
|
||||
049FB22D2EC34EB900FAB05D /* KBStreamTextView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBStreamTextView.h; sourceTree = "<group>"; };
|
||||
049FB22E2EC34EB900FAB05D /* KBStreamTextView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBStreamTextView.m; sourceTree = "<group>"; };
|
||||
049FB2302EC45A0000FAB05D /* KBStreamFetcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBStreamFetcher.h; sourceTree = "<group>"; };
|
||||
049FB2312EC45A0000FAB05D /* KBStreamFetcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBStreamFetcher.m; sourceTree = "<group>"; };
|
||||
049FB2332EC45C6A00FAB05D /* NetworkStreamHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NetworkStreamHandler.h; sourceTree = "<group>"; };
|
||||
049FB2342EC45C6A00FAB05D /* NetworkStreamHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NetworkStreamHandler.m; sourceTree = "<group>"; };
|
||||
049FB2362EC4766700FAB05D /* KBFunctionTagListView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFunctionTagListView.h; sourceTree = "<group>"; };
|
||||
049FB2372EC4766700FAB05D /* KBFunctionTagListView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFunctionTagListView.m; sourceTree = "<group>"; };
|
||||
049FB2382EC4766700FAB05D /* KBStreamOverlayView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBStreamOverlayView.h; sourceTree = "<group>"; };
|
||||
@@ -542,6 +534,8 @@
|
||||
04C6EAE12EAF940F0089C901 /* KBPermissionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBPermissionViewController.m; sourceTree = "<group>"; };
|
||||
04D1F6B02EDFF10A00B12345 /* KBSkinInstallBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinInstallBridge.h; sourceTree = "<group>"; };
|
||||
04D1F6B12EDFF10A00B12345 /* KBSkinInstallBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinInstallBridge.m; sourceTree = "<group>"; };
|
||||
04E161812F10E6470022C23B /* normal_hei_them.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = normal_hei_them.zip; sourceTree = "<group>"; };
|
||||
04E161822F10E6470022C23B /* normal_them.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = normal_them.zip; sourceTree = "<group>"; };
|
||||
04FC953A2EAFAE56007BD342 /* KeyBoardPrefixHeader.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyBoardPrefixHeader.pch; sourceTree = "<group>"; };
|
||||
04FC95642EB0546C007BD342 /* KBKey.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKey.h; sourceTree = "<group>"; };
|
||||
04FC95652EB0546C007BD342 /* KBKey.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKey.m; sourceTree = "<group>"; };
|
||||
@@ -603,6 +597,8 @@
|
||||
04FEDC112F00010000999999 /* KBKeyboardSubscriptionView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardSubscriptionView.m; sourceTree = "<group>"; };
|
||||
04FEDC202F00020000999999 /* KBKeyboardSubscriptionProduct.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKeyboardSubscriptionProduct.h; sourceTree = "<group>"; };
|
||||
04FEDC212F00020000999999 /* KBKeyboardSubscriptionProduct.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardSubscriptionProduct.m; sourceTree = "<group>"; };
|
||||
04FEDC232F10000100000001 /* KBKeyboardLayoutConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKeyboardLayoutConfig.h; sourceTree = "<group>"; };
|
||||
04FEDC242F10000100000001 /* KBKeyboardLayoutConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardLayoutConfig.m; sourceTree = "<group>"; };
|
||||
04FEDC302F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKeyboardSubscriptionFeatureItemView.h; sourceTree = "<group>"; };
|
||||
04FEDC312F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardSubscriptionFeatureItemView.m; sourceTree = "<group>"; };
|
||||
04FEDC402F00040000999999 /* KBKeyboardSubscriptionFeatureMarqueeView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKeyboardSubscriptionFeatureMarqueeView.h; sourceTree = "<group>"; };
|
||||
@@ -630,6 +626,7 @@
|
||||
A1B2C3E82F20000000000001 /* KBSuggestionBarView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSuggestionBarView.h; sourceTree = "<group>"; };
|
||||
A1B2C3E92F20000000000001 /* KBSuggestionBarView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSuggestionBarView.m; sourceTree = "<group>"; };
|
||||
A1B2C3EC2F20000000000001 /* kb_words.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = kb_words.txt; sourceTree = "<group>"; };
|
||||
A1B2C3F02F20000000000002 /* kb_keyboard_layout_config.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = kb_keyboard_layout_config.json; sourceTree = "<group>"; };
|
||||
A1B2C3F12EB35A9900000001 /* KBFullAccessGuideView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFullAccessGuideView.h; sourceTree = "<group>"; };
|
||||
A1B2C3F22EB35A9900000001 /* KBFullAccessGuideView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFullAccessGuideView.m; sourceTree = "<group>"; };
|
||||
A1B2C4002EB4A0A100000001 /* KBAuthManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAuthManager.h; sourceTree = "<group>"; };
|
||||
@@ -654,6 +651,10 @@
|
||||
A1F0C1A32F1234567890ABCD /* KBConsumptionRecordCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBConsumptionRecordCell.m; sourceTree = "<group>"; };
|
||||
A1F0C1A42F1234567890ABCD /* KBConsumptionRecordVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBConsumptionRecordVC.h; sourceTree = "<group>"; };
|
||||
A1F0C1A52F1234567890ABCD /* KBConsumptionRecordVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBConsumptionRecordVC.m; sourceTree = "<group>"; };
|
||||
A1F0C1C02FABCDEF12345678 /* KBInviteCodeModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBInviteCodeModel.h; sourceTree = "<group>"; };
|
||||
A1F0C1C12FABCDEF12345678 /* KBInviteCodeModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBInviteCodeModel.m; sourceTree = "<group>"; };
|
||||
A1F0C1D02FACAD0012345678 /* KBMaiPointReporter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBMaiPointReporter.h; sourceTree = "<group>"; };
|
||||
A1F0C1D12FACAD0012345678 /* KBMaiPointReporter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMaiPointReporter.m; sourceTree = "<group>"; };
|
||||
B12EC429812407B9F0E67565 /* Pods-CustomKeyboard.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CustomKeyboard.release.xcconfig"; path = "Target Support Files/Pods-CustomKeyboard/Pods-CustomKeyboard.release.xcconfig"; sourceTree = "<group>"; };
|
||||
B8CA018AB878499327504AAD /* Pods-CustomKeyboard.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CustomKeyboard.debug.xcconfig"; path = "Target Support Files/Pods-CustomKeyboard/Pods-CustomKeyboard.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
B9F60894E529C3EDAF6BAC3D /* KBShopThemeDetailModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBShopThemeDetailModel.m; sourceTree = "<group>"; };
|
||||
@@ -685,11 +686,13 @@
|
||||
041007D02ECE010100D203BB /* Resource */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
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 /* fense.zip */,
|
||||
04791FF62ED5B985004E8522 /* Christmas.zip */,
|
||||
);
|
||||
path = Resource;
|
||||
@@ -890,7 +893,6 @@
|
||||
0479200A2ED87CEE004E8522 /* permiss_video.mp4 */,
|
||||
047920102ED98E7D004E8522 /* permiss_video_2.mp4 */,
|
||||
047920062ED86ABC004E8522 /* kb_guide_keyboard.gif */,
|
||||
04286A122ECDEBF900CE730C /* KBSkinIconMap.strings */,
|
||||
04286A0E2ECDA71B00CE730C /* 001.zip */,
|
||||
);
|
||||
path = Resource;
|
||||
@@ -1248,6 +1250,8 @@
|
||||
04FC95652EB0546C007BD342 /* KBKey.m */,
|
||||
04FEDC202F00020000999999 /* KBKeyboardSubscriptionProduct.h */,
|
||||
04FEDC212F00020000999999 /* KBKeyboardSubscriptionProduct.m */,
|
||||
04FEDC232F10000100000001 /* KBKeyboardLayoutConfig.h */,
|
||||
04FEDC242F10000100000001 /* KBKeyboardLayoutConfig.m */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
@@ -1717,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 = "<group>";
|
||||
@@ -1815,11 +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 */,
|
||||
041007D22ECE012000D203BB /* KBSkinIconMap.strings in Resources */,
|
||||
A1B2C3ED2F20000000000001 /* kb_words.txt in Resources */,
|
||||
04791FFB2ED5EAB8004E8522 /* fense.zip in Resources */,
|
||||
A1B2C3F12F20000000000002 /* kb_keyboard_layout_config.json in Resources */,
|
||||
0498BDF52EEC50EE006CC1D5 /* emoji_categories.json in Resources */,
|
||||
04791FF72ED5B985004E8522 /* Christmas.zip in Resources */,
|
||||
04286A0B2ECD88B400CE730C /* KeyboardAssets.xcassets in Resources */,
|
||||
@@ -1919,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 */,
|
||||
@@ -1938,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 */,
|
||||
@@ -1962,6 +1963,7 @@
|
||||
A1F0C1C22FABCDEF12345678 /* KBInviteCodeModel.m in Sources */,
|
||||
A1F0C1D22FACAD0012345678 /* KBMaiPointReporter.m in Sources */,
|
||||
04FEDC222F00020000999999 /* KBKeyboardSubscriptionProduct.m in Sources */,
|
||||
04FEDC252F10000100000001 /* KBKeyboardLayoutConfig.m in Sources */,
|
||||
0450AA742EF013D000B6AF06 /* KBEmojiCollectionCell.m in Sources */,
|
||||
550CB2630FA4A7B4B9782EFA /* KBMyTheme.m in Sources */,
|
||||
0498BDDA2EE7ECEA006CC1D5 /* WJXEventSource.m in Sources */,
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<EnvironmentVariable
|
||||
key = "OS_ACTIVITY_MODE"
|
||||
value = "disable"
|
||||
isEnabled = "YES">
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</LaunchAction>
|
||||
|
||||
BIN
keyBoard/Assets.xcassets/Test/jinling_icon.imageset/ChatGPT Image 2026年1月12日 14_07_17.png
vendored
Normal file
|
After Width: | Height: | Size: 144 KiB |
@@ -1,16 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ChatGPT Image 2026年1月12日 14_07_17.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_a@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_a@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_123@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_123@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 29 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_ai@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_ai@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 49 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_b@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_b@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 26 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_c@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_c@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 24 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_d@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_d@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 22 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_del@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_del@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 45 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_e@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_e@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 26 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_f@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_f@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 27 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_g@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_g@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 32 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_h@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_h@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 21 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_i@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_i@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 33 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_j@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_j@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 34 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_k@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_k@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 25 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_l@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_l@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 29 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_m@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_m@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 26 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_n@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_n@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 32 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_o@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_o@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 34 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_p@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_p@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 33 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_q@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "key_q@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||