36 Commits

Author SHA1 Message Date
6ec98468de Merge branch 'dev_处理立刻清空和撤销删除' into dev_st
# Conflicts:
#	CustomKeyboard/Utils/KBBackspaceLongPressHandler.m
解决冲突
2025-12-26 15:08:14 +08:00
2d5919016f 测试上报 接口报错 2025-12-26 15:02:48 +08:00
c0fa51bb2e 修改用户名UI 2025-12-26 14:48:24 +08:00
6713f36387 修改国际化 2025-12-26 14:38:29 +08:00
f24750458a 修改立刻清空按钮 2025-12-26 14:19:39 +08:00
510a2f4d66 添加邀请链接 2025-12-26 14:07:21 +08:00
ae37730da6 修改 我已经退出界面,然后从新进入界面弹起键盘,为什么撤销删除按钮显示? 2025-12-26 13:55:07 +08:00
203f104ece 修改键盘长按立即清空和撤销删除 2025-12-26 11:47:44 +08:00
8e934dd83a 1 2025-12-25 17:58:33 +08:00
1676916a5c 1 2025-12-25 17:32:34 +08:00
1af5a0e849 添加部分埋点 2025-12-25 17:20:24 +08:00
5b6e0a8fbf 修改key 2025-12-25 13:07:28 +08:00
9968883bab 修改邀请 2025-12-25 13:01:14 +08:00
af5f637d31 1 2025-12-24 20:17:37 +08:00
0a725e845e 处理详情tag的背景色 2025-12-23 20:56:00 +08:00
6a539dc3c5 处理键盘长按删除 撤销出现的bug 2025-12-23 18:05:01 +08:00
73d6ec933a 修改金币超出问题 2025-12-23 15:33:14 +08:00
000d603241 优化下载皮肤❤️ 2025-12-23 15:26:32 +08:00
fbf9fe9f2a 2 2025-12-23 14:37:11 +08:00
8e4d7e1ee8 处理按钮 2025-12-23 14:15:27 +08:00
262eb57b36 修改接口 2025-12-23 14:07:53 +08:00
2e1c261775 修改UI 2025-12-23 14:02:44 +08:00
6ad2079351 修改emoji 更换api 2025-12-23 13:27:25 +08:00
a477592f5d 新增按钮 2025-12-22 21:16:38 +08:00
6f336e8368 国际化 2025-12-22 20:53:24 +08:00
17e038beb1 修改接口 2025-12-22 19:22:12 +08:00
4e6fd90668 1 2025-12-22 19:19:28 +08:00
5cfc76e6c5 修改我的皮肤逻辑 2025-12-22 16:42:56 +08:00
9e33c93763 修改弹窗 2025-12-22 15:49:28 +08:00
1c9ae7bc06 1 2025-12-22 15:37:22 +08:00
472e9ad341 修改联想背景色 2025-12-22 14:02:43 +08:00
19c69f4f6f Merge branch 'dev_联想' into dev_st 2025-12-22 13:47:23 +08:00
8788cbb105 处理UI 2025-12-22 13:46:45 +08:00
ea77e9a5f8 处理苹果bug 默认键盘颜色改为 2025-12-22 13:29:00 +08:00
eaaf0e1ed6 修改UI 2025-12-22 13:08:59 +08:00
8a344b293d 添加联想 2025-12-22 12:54:28 +08:00
108 changed files with 237558 additions and 494 deletions

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "key_revoke@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "key_revoke@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -20,14 +20,16 @@
#import "KBKeyboardSubscriptionView.h" #import "KBKeyboardSubscriptionView.h"
#import "KBKeyboardSubscriptionProduct.h" #import "KBKeyboardSubscriptionProduct.h"
#import "KBBackspaceUndoManager.h" #import "KBBackspaceUndoManager.h"
#import "KBInputBufferManager.h"
#import "KBSuggestionEngine.h"
// 使 static kb_consumePendingShopSkin // 使 static kb_consumePendingShopSkin
@interface KeyboardViewController (KBSkinShopBridge) @interface KeyboardViewController (KBSkinShopBridge)
- (void)kb_consumePendingShopSkin; - (void)kb_consumePendingShopSkin;
@end @end
// 375 稿 // 375 稿
static const CGFloat kKBKeyboardDesignHeight = 250.0f; static const CGFloat kKBKeyboardBaseHeight = 250.0f;
static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
void *observer, void *observer,
@@ -50,6 +52,9 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
@property (nonatomic, strong) KBSettingView *settingView; // @property (nonatomic, strong) KBSettingView *settingView; //
@property (nonatomic, strong) UIImageView *bgImageView; // @property (nonatomic, strong) UIImageView *bgImageView; //
@property (nonatomic, strong) KBKeyboardSubscriptionView *subscriptionView; @property (nonatomic, strong) KBKeyboardSubscriptionView *subscriptionView;
@property (nonatomic, strong) KBSuggestionEngine *suggestionEngine;
@property (nonatomic, copy) NSString *currentWord;
@property (nonatomic, assign) BOOL suppressSuggestions;
@end @end
@implementation KeyboardViewController @implementation KeyboardViewController
@@ -60,7 +65,11 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
- (void)viewDidLoad { - (void)viewDidLoad {
[super viewDidLoad]; [super viewDidLoad];
// /
[[KBBackspaceUndoManager shared] registerNonClearAction];
[self setupUI]; [self setupUI];
self.suggestionEngine = [KBSuggestionEngine shared];
self.currentWord = @"";
// HUD App KeyWindow // HUD App KeyWindow
[KBHUD setContainerView:self.view]; [KBHUD setContainerView:self.view];
// 访便 // 访便
@@ -86,7 +95,24 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
- (void)viewWillAppear:(BOOL)animated{ - (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated]; [super viewWillAppear:animated];
// /
[[KBBackspaceUndoManager shared] registerNonClearAction];
[[KBInputBufferManager shared] resetWithText:@""];
[[KBLocalizationManager shared] reloadFromSharedStorageIfNeeded]; [[KBLocalizationManager shared] reloadFromSharedStorageIfNeeded];
// /QQ 宿 documentContext liveText manualSnapshot
[[KBInputBufferManager shared] updateFromExternalContextBefore:self.textDocumentProxy.documentContextBeforeInput
after:self.textDocumentProxy.documentContextAfterInput];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[KBBackspaceUndoManager shared] registerNonClearAction];
}
- (void)textDidChange:(id<UITextInput>)textInput {
[super textDidChange:textInput];
[[KBInputBufferManager shared] updateFromExternalContextBefore:self.textDocumentProxy.documentContextBeforeInput
after:self.textDocumentProxy.documentContextAfterInput];
} }
@@ -94,7 +120,7 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
self.view.translatesAutoresizingMaskIntoConstraints = NO; self.view.translatesAutoresizingMaskIntoConstraints = NO;
// / // /
CGFloat keyboardHeight = KBFit(kKBKeyboardDesignHeight); CGFloat keyboardHeight = KBFit(kKBKeyboardBaseHeight);
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
CGFloat outerVerticalInset = KBFit(4.0f); CGFloat outerVerticalInset = KBFit(4.0f);
@@ -137,6 +163,113 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
#pragma mark - Private #pragma mark - Private
// MARK: - Suggestions
- (void)kb_updateCurrentWordWithInsertedText:(NSString *)text {
if (text.length == 0) { return; }
if ([self kb_isAlphabeticString:text]) {
NSString *current = self.currentWord ?: @"";
self.currentWord = [current stringByAppendingString:text];
self.suppressSuggestions = NO;
[self kb_updateSuggestionsForCurrentWord];
} else {
[self kb_clearCurrentWord];
}
}
- (void)kb_clearCurrentWord {
self.currentWord = @"";
[self.keyBoardMainView kb_setSuggestions:@[]];
self.suppressSuggestions = NO;
}
- (void)kb_scheduleContextRefreshResetSuppression:(BOOL)resetSuppression {
dispatch_async(dispatch_get_main_queue(), ^{
[self kb_refreshCurrentWordFromDocumentContextResetSuppression:resetSuppression];
});
}
- (void)kb_refreshCurrentWordFromDocumentContextResetSuppression:(BOOL)resetSuppression {
NSString *context = self.textDocumentProxy.documentContextBeforeInput ?: @"";
NSString *word = [self kb_extractTrailingWordFromContext:context];
self.currentWord = word ?: @"";
if (resetSuppression) {
self.suppressSuggestions = NO;
}
[self kb_updateSuggestionsForCurrentWord];
}
- (NSString *)kb_extractTrailingWordFromContext:(NSString *)context {
if (context.length == 0) { return @""; }
static NSCharacterSet *letters = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
letters = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"];
});
NSInteger idx = (NSInteger)context.length - 1;
while (idx >= 0) {
unichar ch = [context characterAtIndex:(NSUInteger)idx];
if (![letters characterIsMember:ch]) {
break;
}
idx -= 1;
}
NSUInteger start = (NSUInteger)(idx + 1);
if (start >= context.length) { return @""; }
return [context substringFromIndex:start];
}
- (BOOL)kb_isAlphabeticString:(NSString *)text {
if (text.length == 0) { return NO; }
static NSCharacterSet *letters = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
letters = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"];
});
for (NSUInteger i = 0; i < text.length; i++) {
if (![letters characterIsMember:[text characterAtIndex:i]]) {
return NO;
}
}
return YES;
}
- (void)kb_updateSuggestionsForCurrentWord {
NSString *prefix = self.currentWord ?: @"";
if (prefix.length == 0) {
[self.keyBoardMainView kb_setSuggestions:@[]];
return;
}
if (self.suppressSuggestions) {
[self.keyBoardMainView kb_setSuggestions:@[]];
return;
}
NSArray<NSString *> *items = [self.suggestionEngine suggestionsForPrefix:prefix limit:5];
NSArray<NSString *> *cased = [self kb_applyCaseToSuggestions:items prefix:prefix];
[self.keyBoardMainView kb_setSuggestions:cased];
}
- (NSArray<NSString *> *)kb_applyCaseToSuggestions:(NSArray<NSString *> *)items prefix:(NSString *)prefix {
if (items.count == 0 || prefix.length == 0) { return items; }
BOOL allUpper = [prefix isEqualToString:prefix.uppercaseString];
BOOL firstUpper = [[prefix substringToIndex:1] isEqualToString:[[prefix substringToIndex:1] uppercaseString]];
if (!allUpper && !firstUpper) { return items; }
NSMutableArray<NSString *> *result = [NSMutableArray arrayWithCapacity:items.count];
for (NSString *word in items) {
if (allUpper) {
[result addObject:word.uppercaseString];
} else {
NSString *first = [[word substringToIndex:1] uppercaseString];
NSString *rest = (word.length > 1) ? [word substringFromIndex:1] : @"";
[result addObject:[first stringByAppendingString:rest]];
}
}
return result.copy;
}
/// / /// /
- (void)showFunctionPanel:(BOOL)show { - (void)showFunctionPanel:(BOOL)show {
// //
@@ -250,23 +383,41 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
// MARK: - KBKeyBoardMainViewDelegate // MARK: - KBKeyBoardMainViewDelegate
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didTapKey:(KBKey *)key { - (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didTapKey:(KBKey *)key {
if (key.type != KBKeyTypeShift && key.type != KBKeyTypeModeChange) {
[[KBBackspaceUndoManager shared] registerNonClearAction];
}
switch (key.type) { switch (key.type) {
case KBKeyTypeCharacter: case KBKeyTypeCharacter: {
[self.textDocumentProxy insertText:key.output ?: key.title ?: @""]; break; [[KBBackspaceUndoManager shared] registerNonClearAction];
NSString *text = key.output ?: key.title ?: @"";
[self.textDocumentProxy insertText:text];
[self kb_updateCurrentWordWithInsertedText:text];
[[KBInputBufferManager shared] appendText:text];
} break;
case KBKeyTypeBackspace: case KBKeyTypeBackspace:
[self.textDocumentProxy deleteBackward]; break; [[KBInputBufferManager shared] refreshFromProxyIfPossible:self.textDocumentProxy];
[[KBInputBufferManager shared] prepareSnapshotForDeleteWithContextBefore:self.textDocumentProxy.documentContextBeforeInput
after:self.textDocumentProxy.documentContextAfterInput];
[[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:self.textDocumentProxy count:1];
[self kb_scheduleContextRefreshResetSuppression:NO];
[[KBInputBufferManager shared] applyHoldDeleteCount:1];
break;
case KBKeyTypeSpace: case KBKeyTypeSpace:
[self.textDocumentProxy insertText:@" "]; break; [[KBBackspaceUndoManager shared] registerNonClearAction];
[self.textDocumentProxy insertText:@" "];
[self kb_clearCurrentWord];
[[KBInputBufferManager shared] appendText:@" "];
break;
case KBKeyTypeReturn: case KBKeyTypeReturn:
[self.textDocumentProxy insertText:@"\n"]; break; [[KBBackspaceUndoManager shared] registerNonClearAction];
[self.textDocumentProxy insertText:@"\n"];
[self kb_clearCurrentWord];
[[KBInputBufferManager shared] appendText:@"\n"];
break;
case KBKeyTypeGlobe: case KBKeyTypeGlobe:
[self advanceToNextInputMode]; break; [self advanceToNextInputMode]; break;
case KBKeyTypeCustom: case KBKeyTypeCustom:
[[KBBackspaceUndoManager shared] registerNonClearAction];
// //
[self showFunctionPanel:YES]; [self showFunctionPanel:YES];
[self kb_clearCurrentWord];
break; break;
case KBKeyTypeModeChange: case KBKeyTypeModeChange:
case KBKeyTypeShift: case KBKeyTypeShift:
@@ -278,6 +429,7 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didTapToolActionAtIndex:(NSInteger)index { - (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didTapToolActionAtIndex:(NSInteger)index {
if (index == 0) { if (index == 0) {
[self showFunctionPanel:YES]; [self showFunctionPanel:YES];
[self kb_clearCurrentWord];
return; return;
} }
[self showFunctionPanel:NO]; [self showFunctionPanel:NO];
@@ -291,16 +443,36 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
if (emoji.length == 0) { return; } if (emoji.length == 0) { return; }
[[KBBackspaceUndoManager shared] registerNonClearAction]; [[KBBackspaceUndoManager shared] registerNonClearAction];
[self.textDocumentProxy insertText:emoji]; [self.textDocumentProxy insertText:emoji];
[self kb_clearCurrentWord];
[[KBInputBufferManager shared] appendText:emoji];
} }
- (void)keyBoardMainViewDidTapUndo:(KBKeyBoardMainView *)keyBoardMainView { - (void)keyBoardMainViewDidTapUndo:(KBKeyBoardMainView *)keyBoardMainView {
[[KBBackspaceUndoManager shared] performUndoFromResponder:self.view]; [[KBBackspaceUndoManager shared] performUndoFromResponder:self.view];
[self kb_scheduleContextRefreshResetSuppression:YES];
} }
- (void)keyBoardMainViewDidTapEmojiSearch:(KBKeyBoardMainView *)keyBoardMainView { - (void)keyBoardMainViewDidTapEmojiSearch:(KBKeyBoardMainView *)keyBoardMainView {
[KBHUD showInfo:KBLocalized(@"Search coming soon")]; [KBHUD showInfo:KBLocalized(@"Search coming soon")];
} }
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didSelectSuggestion:(NSString *)suggestion {
if (suggestion.length == 0) { return; }
[[KBBackspaceUndoManager shared] registerNonClearAction];
NSString *current = self.currentWord ?: @"";
if (current.length > 0) {
for (NSUInteger i = 0; i < current.length; i++) {
[self.textDocumentProxy deleteBackward];
}
}
[self.textDocumentProxy insertText:suggestion];
self.currentWord = suggestion;
[self.suggestionEngine recordSelection:suggestion];
self.suppressSuggestions = YES;
[self.keyBoardMainView kb_setSuggestions:@[]];
[[KBInputBufferManager shared] replaceTailWithText:suggestion deleteCount:current.length];
}
// MARK: - KBFunctionViewDelegate // MARK: - KBFunctionViewDelegate
- (void)functionView:(KBFunctionView *)functionView didTapToolActionAtIndex:(NSInteger)index { - (void)functionView:(KBFunctionView *)functionView didTapToolActionAtIndex:(NSInteger)index {
// index == 0 // index == 0
@@ -448,6 +620,7 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
- (void)kb_applyTheme { - (void)kb_applyTheme {
KBSkinTheme *t = [KBSkinManager shared].current; KBSkinTheme *t = [KBSkinManager shared].current;
UIImage *img = [[KBSkinManager shared] currentBackgroundImage]; UIImage *img = [[KBSkinManager shared] currentBackgroundImage];
NSLog(@"⌨️[Keyboard] apply theme id=%@ hasBg=%d", t.skinId, (img != nil));
self.bgImageView.image = img; self.bgImageView.image = img;
BOOL hasImg = (img != nil); BOOL hasImg = (img != nil);
self.view.backgroundColor = hasImg ? [UIColor clearColor] : t.keyboardBackground; self.view.backgroundColor = hasImg ? [UIColor clearColor] : t.keyboardBackground;

View File

@@ -0,0 +1,23 @@
//
// KBSuggestionEngine.h
// CustomKeyboard
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// Simple local suggestion engine (prefix match + lightweight ranking).
@interface KBSuggestionEngine : NSObject
+ (instancetype)shared;
/// Returns suggestions for prefix (lowercase expected), limited by count.
- (NSArray<NSString *> *)suggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit;
/// Record a selection to slightly boost ranking next time.
- (void)recordSelection:(NSString *)word;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,167 @@
//
// KBSuggestionEngine.m
// CustomKeyboard
//
#import "KBSuggestionEngine.h"
#import "KBConfig.h"
@interface KBSuggestionEngine ()
@property (nonatomic, copy) NSArray<NSString *> *words;
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *selectionCounts;
@property (nonatomic, strong) NSSet<NSString *> *priorityWords;
@end
@implementation KBSuggestionEngine
+ (instancetype)shared {
static KBSuggestionEngine *engine;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
engine = [[KBSuggestionEngine alloc] init];
});
return engine;
}
- (instancetype)init {
if (self = [super init]) {
_selectionCounts = [NSMutableDictionary dictionary];
NSArray<NSString *> *defaults = [self.class kb_defaultWords];
_priorityWords = [NSSet setWithArray:defaults];
_words = [self kb_loadWords];
}
return self;
}
- (NSArray<NSString *> *)suggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit {
if (prefix.length == 0 || limit == 0) { return @[]; }
NSString *lower = prefix.lowercaseString;
NSMutableArray<NSString *> *matches = [NSMutableArray array];
for (NSString *word in self.words) {
if ([word hasPrefix:lower]) {
[matches addObject:word];
if (matches.count >= limit * 3) {
// Avoid scanning too many matches for long lists.
break;
}
}
}
if (matches.count == 0) { return @[]; }
[matches sortUsingComparator:^NSComparisonResult(NSString *a, NSString *b) {
NSInteger ca = self.selectionCounts[a].integerValue;
NSInteger cb = self.selectionCounts[b].integerValue;
if (ca != cb) {
return (cb > ca) ? NSOrderedAscending : NSOrderedDescending;
}
BOOL pa = [self.priorityWords containsObject:a];
BOOL pb = [self.priorityWords containsObject:b];
if (pa != pb) {
return pa ? NSOrderedAscending : NSOrderedDescending;
}
return [a compare:b];
}];
if (matches.count > limit) {
return [matches subarrayWithRange:NSMakeRange(0, limit)];
}
return matches.copy;
}
- (void)recordSelection:(NSString *)word {
if (word.length == 0) { return; }
NSString *key = word.lowercaseString;
NSInteger count = self.selectionCounts[key].integerValue + 1;
self.selectionCounts[key] = @(count);
}
#pragma mark - Defaults
- (NSArray<NSString *> *)kb_loadWords {
NSMutableOrderedSet<NSString *> *set = [[NSMutableOrderedSet alloc] init];
[set addObjectsFromArray:[self.class kb_defaultWords]];
NSArray<NSString *> *paths = [self kb_wordListPaths];
for (NSString *path in paths) {
if (path.length == 0) { continue; }
NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
if (content.length == 0) { continue; }
NSArray<NSString *> *lines = [content componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
for (NSString *line in lines) {
NSString *word = [self kb_sanitizedWordFromLine:line];
if (word.length == 0) { continue; }
[set addObject:word];
}
}
NSArray<NSString *> *result = set.array ?: @[];
return result;
}
- (NSArray<NSString *> *)kb_wordListPaths {
NSMutableArray<NSString *> *paths = [NSMutableArray array];
// 1) App Group override (allows server-downloaded large list).
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:AppGroup];
if (containerURL.path.length > 0) {
NSString *groupPath = [[containerURL path] stringByAppendingPathComponent:@"kb_words.txt"];
[paths addObject:groupPath];
}
// 2) Bundle fallback.
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"kb_words" ofType:@"txt"];
if (bundlePath.length > 0) {
[paths addObject:bundlePath];
}
return paths;
}
- (NSString *)kb_sanitizedWordFromLine:(NSString *)line {
NSString *trimmed = [[line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] lowercaseString];
if (trimmed.length == 0) { return @""; }
static NSCharacterSet *letters = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
letters = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyz"];
});
for (NSUInteger i = 0; i < trimmed.length; i++) {
if (![letters characterIsMember:[trimmed characterAtIndex:i]]) {
return @"";
}
}
return trimmed;
}
+ (NSArray<NSString *> *)kb_defaultWords {
return @[
@"a", @"an", @"and", @"are", @"as", @"at",
@"app", @"ap", @"apple", @"apply", @"april", @"application",
@"about", @"above", @"after", @"again", @"against", @"all",
@"am", @"among", @"amount", @"any", @"around",
@"be", @"because", @"been", @"before", @"being", @"below",
@"best", @"between", @"both", @"but", @"by",
@"can", @"could", @"come", @"common", @"case",
@"do", @"does", @"down", @"day",
@"each", @"early", @"end", @"even", @"every",
@"for", @"from", @"first", @"found", @"free",
@"get", @"good", @"great", @"go",
@"have", @"has", @"had", @"help", @"how",
@"in", @"is", @"it", @"if", @"into",
@"just", @"keep", @"kind", @"know",
@"like", @"look", @"long", @"last",
@"make", @"more", @"most", @"my",
@"new", @"no", @"not", @"now",
@"of", @"on", @"one", @"or", @"other", @"our", @"out",
@"people", @"place", @"please",
@"quick", @"quite",
@"right", @"read", @"real",
@"see", @"say", @"some", @"such", @"so",
@"the", @"to", @"this", @"that", @"them", @"then", @"there", @"they", @"these", @"time",
@"use", @"up", @"under",
@"very",
@"we", @"with", @"what", @"when", @"where", @"who", @"why", @"will", @"would",
@"you", @"your"
];
}
@end

View File

@@ -21,6 +21,8 @@
#import "Masonry.h" #import "Masonry.h"
#import "KBHUD.h" // 复用 App 内的 HUD 封装 #import "KBHUD.h" // 复用 App 内的 HUD 封装
#import "KBLocalizationManager.h" // 复用多语言封装(可在扩展内使用) #import "KBLocalizationManager.h" // 复用多语言封装(可在扩展内使用)
#import "KBMaiPointReporter.h"
// 通用链接Universal Links统一配置 // 通用链接Universal Links统一配置
// 配置好 AASA 与 Associated Domains 后,只需修改这里即可切换域名/path。 // 配置好 AASA 与 Associated Domains 后,只需修改这里即可切换域名/path。

View File

@@ -242,7 +242,8 @@
/* 自定义 AI 功能键 */ /* 自定义 AI 功能键 */
"ai" = "key_ai"; "ai" = "key_ai";
/* Emoji功能键 */ /* Emoji功能键 */
"emoji" = "key_emoji"; //"emoji" = "key_emoji";
"emoji_panel" = "key_emoji";
/* 发送/换行键 */ /* 发送/换行键 */
"return" = "key_send"; "return" = "key_send";

File diff suppressed because it is too large Load Diff

View File

@@ -11,10 +11,10 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithContainerView:(UIView *)containerView; - (instancetype)initWithContainerView:(UIView *)containerView;
/// 配置删除按钮(包含长按删除;可选是否显示“立刻清空”提示) /// 配置删除按钮(包含长按删除;可选是否显示“上滑清空”提示)
- (void)bindDeleteButton:(nullable UIView *)button showClearLabel:(BOOL)showClearLabel; - (void)bindDeleteButton:(nullable UIView *)button showClearLabel:(BOOL)showClearLabel;
/// 触发“立刻清空”逻辑(可用于功能面板的清空按钮) /// 触发“上滑清空”逻辑(可用于功能面板的清空按钮)
- (void)performClearAction; - (void)performClearAction;
@end @end

View File

@@ -7,24 +7,25 @@
#import "KBResponderUtils.h" #import "KBResponderUtils.h"
#import "KBSkinManager.h" #import "KBSkinManager.h"
#import "KBBackspaceUndoManager.h" #import "KBBackspaceUndoManager.h"
#import "KBInputBufferManager.h"
static const NSTimeInterval kKBBackspaceLongPressMinDuration = 0.35; static const NSTimeInterval kKBBackspaceLongPressMinDuration = 0.35;
static const NSTimeInterval kKBBackspaceRepeatInterval = 0.06; static const NSTimeInterval kKBBackspaceRepeatInterval = 0.06;
static const NSTimeInterval kKBBackspaceChunkStartDelay = 1.0; static const NSTimeInterval kKBBackspaceChunkStartDelay = 0.6;
static const NSTimeInterval kKBBackspaceChunkRepeatInterval = 0.1; static const NSTimeInterval kKBBackspaceChunkRepeatInterval = 0.1;
static const NSTimeInterval kKBBackspaceChunkFastDelay = 1.4; static const NSTimeInterval kKBBackspaceChunkFastDelay = 1.2;
static const NSInteger kKBBackspaceChunkSize = 6; static const NSInteger kKBBackspaceChunkSize = 8;
static const NSInteger kKBBackspaceChunkSizeFast = 12; static const NSInteger kKBBackspaceChunkSizeFast = 16;
static const CGFloat kKBBackspaceClearLabelCornerRadius = 8.0; static const CGFloat kKBBackspaceClearLabelCornerRadius = 8.0;
static const CGFloat kKBBackspaceClearLabelHeight = 26.0; static const CGFloat kKBBackspaceClearLabelHeight = 34;
static const CGFloat kKBBackspaceClearLabelPaddingX = 10.0; static const CGFloat kKBBackspaceClearLabelPaddingX = 10.0;
static const CGFloat kKBBackspaceClearLabelTopGap = 6.0; static const CGFloat kKBBackspaceClearLabelTopGap = 6.0;
static const CGFloat kKBBackspaceClearLabelHorizontalInset = 6.0; static const CGFloat kKBBackspaceClearLabelHorizontalInset = 6.0;
static const NSInteger kKBBackspaceClearBatchSize = 24; static const NSTimeInterval kKBBackspaceClearBatchInterval = 0.02;
static const NSTimeInterval kKBBackspaceClearBatchInterval = 0.005;
static const NSInteger kKBBackspaceClearMaxDeletes = 10000; static const NSInteger kKBBackspaceClearMaxDeletes = 10000;
static const NSInteger kKBBackspaceClearEmptyContextMaxRounds = 40; static const NSInteger kKBBackspaceClearEmptyContextMaxRounds = 40;
static const NSInteger kKBBackspaceClearMaxStep = 80; static const NSInteger kKBBackspaceClearMaxStep = 80;
static const NSInteger kKBBackspaceClearDeletesPerTick = 10;
typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) { typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
KBBackspaceChunkClassUnknown = 0, KBBackspaceChunkClassUnknown = 0,
@@ -34,6 +35,12 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
KBBackspaceChunkClassOther KBBackspaceChunkClassOther
}; };
typedef NS_ENUM(NSInteger, KBClearPhase) {
KBClearPhaseSkipWhitespace = 0,
KBClearPhaseSkipTrailingBoundary,
KBClearPhaseDeleteUntilBoundary
};
@interface KBBackspaceLongPressHandler () @interface KBBackspaceLongPressHandler ()
@property (nonatomic, weak) UIView *containerView; @property (nonatomic, weak) UIView *containerView;
@property (nonatomic, weak) UIView *backspaceButton; @property (nonatomic, weak) UIView *backspaceButton;
@@ -48,6 +55,9 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
@property (nonatomic, assign) CGPoint backspaceLastTouchPointInSelf; @property (nonatomic, assign) CGPoint backspaceLastTouchPointInSelf;
@property (nonatomic, assign) NSUInteger backspaceClearToken; @property (nonatomic, assign) NSUInteger backspaceClearToken;
@property (nonatomic, strong) UILabel *backspaceClearLabel; @property (nonatomic, strong) UILabel *backspaceClearLabel;
@property (nonatomic, copy) NSString *pendingClearBefore;
@property (nonatomic, copy) NSString *pendingClearAfter;
@property (nonatomic, assign) KBClearPhase backspaceClearPhase;
@end @end
@implementation KBBackspaceLongPressHandler @implementation KBBackspaceLongPressHandler
@@ -55,6 +65,7 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
- (instancetype)initWithContainerView:(UIView *)containerView { - (instancetype)initWithContainerView:(UIView *)containerView {
if (self = [super init]) { if (self = [super init]) {
_containerView = containerView; _containerView = containerView;
_backspaceClearPhase = KBClearPhaseSkipWhitespace;
} }
return self; return self;
} }
@@ -73,6 +84,8 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
self.backspaceHasLastTouchPoint = NO; self.backspaceHasLastTouchPoint = NO;
self.backspaceHoldToken += 1; self.backspaceHoldToken += 1;
[self kb_hideBackspaceClearLabel]; [self kb_hideBackspaceClearLabel];
self.pendingClearBefore = nil;
self.pendingClearAfter = nil;
if (!button) { return; } if (!button) { return; }
@@ -99,7 +112,18 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
} }
switch (gr.state) { switch (gr.state) {
case UIGestureRecognizerStateBegan: { case UIGestureRecognizerStateBegan: {
[[KBBackspaceUndoManager shared] registerNonClearAction]; UIResponder *start = (UIResponder *)([self kb_hostView] ?: self.backspaceButton);
UIInputViewController *ivc = KBFindInputViewController(start);
if (ivc) {
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
[[KBInputBufferManager shared] refreshFromProxyIfPossible:proxy];
[[KBInputBufferManager shared] prepareSnapshotForDeleteWithContextBefore:proxy.documentContextBeforeInput
after:proxy.documentContextAfterInput];
}
if (self.showClearLabelEnabled) {
[self kb_capturePendingClearSnapshotIfNeeded];
[[KBInputBufferManager shared] beginPendingClearSnapshot];
}
self.backspaceHoldToken += 1; self.backspaceHoldToken += 1;
NSUInteger token = self.backspaceHoldToken; NSUInteger token = self.backspaceHoldToken;
self.backspaceHoldActive = YES; self.backspaceHoldActive = YES;
@@ -134,6 +158,7 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
if (!ivc) { self.backspaceHoldActive = NO; return; } if (!ivc) { self.backspaceHoldActive = NO; return; }
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy; id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
NSString *before = proxy.documentContextBeforeInput ?: @""; NSString *before = proxy.documentContextBeforeInput ?: @"";
if (before.length == 0) { before = [KBInputBufferManager shared].liveText ?: @""; }
NSTimeInterval elapsed = [NSDate date].timeIntervalSinceReferenceDate - self.backspaceHoldStartTime; NSTimeInterval elapsed = [NSDate date].timeIntervalSinceReferenceDate - self.backspaceHoldStartTime;
NSInteger deleteCount = 1; NSInteger deleteCount = 1;
if (before.length > 0) { if (before.length > 0) {
@@ -145,9 +170,8 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
[self kb_showBackspaceClearLabelIfNeeded]; [self kb_showBackspaceClearLabelIfNeeded];
} }
} }
for (NSInteger i = 0; i < deleteCount; i++) { [[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:proxy count:(NSUInteger)deleteCount];
[proxy deleteBackward]; [[KBInputBufferManager shared] applyHoldDeleteCount:(NSUInteger)deleteCount];
}
NSTimeInterval interval = [self kb_backspaceRepeatIntervalForElapsed:elapsed]; NSTimeInterval interval = [self kb_backspaceRepeatIntervalForElapsed:elapsed];
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
@@ -186,34 +210,77 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
asciiWordSet = [NSCharacterSet characterSetWithCharactersInString: asciiWordSet = [NSCharacterSet characterSetWithCharactersInString:
@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"]; @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"];
punctuationSet = [NSCharacterSet punctuationCharacterSet]; NSMutableCharacterSet *punct = [[NSCharacterSet punctuationCharacterSet] mutableCopy];
// / chunk 1
[punct addCharactersInString:@",。!?;:、()【】《》“”‘’·…—"];
punctuationSet = [punct copy];
}); });
__block NSInteger deleteCount = 0; __block NSInteger deleteCount = 0;
__block KBBackspaceChunkClass chunkClass = KBBackspaceChunkClassUnknown; typedef NS_ENUM(NSInteger, KBBackspaceChunkPhase) {
KBBackspaceChunkPhaseWhitespace = 0,
KBBackspaceChunkPhasePunctuation,
KBBackspaceChunkPhaseCore
};
__block KBBackspaceChunkPhase phase = KBBackspaceChunkPhaseWhitespace;
__block KBBackspaceChunkClass coreClass = KBBackspaceChunkClassUnknown;
[context enumerateSubstringsInRange:NSMakeRange(0, context.length) [context enumerateSubstringsInRange:NSMakeRange(0, context.length)
options:NSStringEnumerationByComposedCharacterSequences | NSStringEnumerationReverse options:NSStringEnumerationByComposedCharacterSequences | NSStringEnumerationReverse
usingBlock:^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, BOOL *stop) { usingBlock:^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, BOOL *stop) {
if (substring.length == 0) { return; } if (substring.length == 0) { return; }
KBBackspaceChunkClass currentClass = KBBackspaceChunkClassOther; if (deleteCount >= maxCount) {
if ([substring rangeOfCharacterFromSet:whitespaceSet].location != NSNotFound) {
currentClass = KBBackspaceChunkClassWhitespace;
} else if ([substring rangeOfCharacterFromSet:asciiWordSet].location != NSNotFound) {
currentClass = KBBackspaceChunkClassASCIIWord;
} else if ([substring rangeOfCharacterFromSet:punctuationSet].location != NSNotFound) {
currentClass = KBBackspaceChunkClassPunctuation;
}
if (chunkClass == KBBackspaceChunkClassUnknown) {
chunkClass = currentClass;
} else if (chunkClass != currentClass) {
*stop = YES; *stop = YES;
return; return;
} }
KBBackspaceChunkClass currentClass = KBBackspaceChunkClassOther;
if ([substring rangeOfCharacterFromSet:whitespaceSet].location != NSNotFound) {
currentClass = KBBackspaceChunkClassWhitespace;
} else if ([substring rangeOfCharacterFromSet:punctuationSet].location != NSNotFound) {
currentClass = KBBackspaceChunkClassPunctuation;
} else if ([substring rangeOfCharacterFromSet:asciiWordSet].location != NSNotFound) {
currentClass = KBBackspaceChunkClassASCIIWord;
}
BOOL consumed = NO;
while (!consumed) {
if (phase == KBBackspaceChunkPhaseWhitespace) {
if (currentClass == KBBackspaceChunkClassWhitespace) {
deleteCount += 1; deleteCount += 1;
consumed = YES;
} else {
phase = KBBackspaceChunkPhasePunctuation;
}
continue;
}
if (phase == KBBackspaceChunkPhasePunctuation) {
if (currentClass == KBBackspaceChunkClassPunctuation) {
deleteCount += 1;
consumed = YES;
} else {
phase = KBBackspaceChunkPhaseCore;
}
continue;
}
// phase == CoreASCII /
if (coreClass == KBBackspaceChunkClassUnknown) {
coreClass = currentClass;
}
if (currentClass != coreClass) {
*stop = YES;
consumed = YES;
continue;
}
deleteCount += 1;
consumed = YES;
}
if (deleteCount >= maxCount) { if (deleteCount >= maxCount) {
*stop = YES; *stop = YES;
return;
} }
}]; }];
@@ -222,13 +289,16 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
- (NSInteger)kb_clearDeleteCountForContext:(NSString *)context - (NSInteger)kb_clearDeleteCountForContext:(NSString *)context
hitBoundary:(BOOL *)hitBoundary { hitBoundary:(BOOL *)hitBoundary {
if (context.length == 0) { return kKBBackspaceClearBatchSize; } if (context.length == 0) {
if (hitBoundary) { *hitBoundary = NO; }
return 1;
}
static NSCharacterSet *sentenceBoundarySet = nil; static NSCharacterSet *sentenceBoundarySet = nil;
static NSCharacterSet *whitespaceSet = nil; static NSCharacterSet *whitespaceSet = nil;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
sentenceBoundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?;。!?;…\n"]; sentenceBoundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?。!?"];
whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
}); });
@@ -303,6 +373,12 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
shouldClear = [self kb_isPointInsideBackspaceClearLabel:point]; shouldClear = [self kb_isPointInsideBackspaceClearLabel:point];
} }
} }
#if DEBUG
NSLog(@"[kb_handleBackspaceLongPressEnded] shouldClear=%@ highlighted=%@ labelHidden=%@",
shouldClear ? @"YES" : @"NO",
self.backspaceClearHighlighted ? @"YES" : @"NO",
self.backspaceClearLabel.hidden ? @"YES" : @"NO");
#endif
self.backspaceHoldActive = NO; self.backspaceHoldActive = NO;
self.backspaceChunkModeActive = NO; self.backspaceChunkModeActive = NO;
self.backspaceHoldToken += 1; self.backspaceHoldToken += 1;
@@ -310,6 +386,11 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
[self kb_hideBackspaceClearLabel]; [self kb_hideBackspaceClearLabel];
if (shouldClear) { if (shouldClear) {
[self kb_clearAllInput]; [self kb_clearAllInput];
} else {
self.pendingClearBefore = nil;
self.pendingClearAfter = nil;
[[KBInputBufferManager shared] clearPendingClearSnapshot];
[[KBInputBufferManager shared] commitLiveToManual];
} }
} }
@@ -401,9 +482,9 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
- (UILabel *)backspaceClearLabel { - (UILabel *)backspaceClearLabel {
if (!_backspaceClearLabel) { if (!_backspaceClearLabel) {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero]; UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
label.text = @"立刻清空"; label.text = KBLocalized(@"Clear");
label.textAlignment = NSTextAlignmentCenter; label.textAlignment = NSTextAlignmentCenter;
label.font = [UIFont systemFontOfSize:12 weight:UIFontWeightSemibold]; label.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
label.textColor = [KBSkinManager shared].current.keyTextColor ?: UIColor.blackColor; label.textColor = [KBSkinManager shared].current.keyTextColor ?: UIColor.blackColor;
label.backgroundColor = [self kb_backspaceClearLabelNormalColor]; label.backgroundColor = [self kb_backspaceClearLabelNormalColor];
label.layer.cornerRadius = kKBBackspaceClearLabelCornerRadius; label.layer.cornerRadius = kKBBackspaceClearLabelCornerRadius;
@@ -421,10 +502,14 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
UIResponder *start = (UIResponder *)([self kb_hostView] ?: self.backspaceButton); UIResponder *start = (UIResponder *)([self kb_hostView] ?: self.backspaceButton);
UIInputViewController *ivc = KBFindInputViewController(start); UIInputViewController *ivc = KBFindInputViewController(start);
if (ivc) { if (ivc) {
NSString *before = ivc.textDocumentProxy.documentContextBeforeInput ?: @""; id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
[[KBBackspaceUndoManager shared] recordClearWithContext:before]; [[KBInputBufferManager shared] refreshFromProxyIfPossible:proxy];
} }
self.pendingClearBefore = nil;
self.pendingClearAfter = nil;
[[KBInputBufferManager shared] clearPendingClearSnapshot];
self.backspaceClearToken += 1; self.backspaceClearToken += 1;
self.backspaceClearPhase = KBClearPhaseSkipWhitespace;
NSUInteger token = self.backspaceClearToken; NSUInteger token = self.backspaceClearToken;
[self kb_clearAllInputStepForToken:token guard:0 emptyRounds:0]; [self kb_clearAllInputStepForToken:token guard:0 emptyRounds:0];
} }
@@ -437,40 +522,101 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
UIInputViewController *ivc = KBFindInputViewController(start); UIInputViewController *ivc = KBFindInputViewController(start);
if (!ivc) { return; } if (!ivc) { return; }
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy; id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
NSString *before = proxy.documentContextBeforeInput ?: @"";
NSInteger count = before.length;
NSInteger batch = 0;
NSInteger nextEmptyRounds = emptyRounds; NSInteger nextEmptyRounds = emptyRounds;
BOOL hitBoundary = NO; static NSCharacterSet *stopBoundarySet = nil;
if (count > 0) { static NSCharacterSet *trailingBoundarySet = nil;
batch = [self kb_clearDeleteCountForContext:before hitBoundary:&hitBoundary]; static NSCharacterSet *trailingWhitespaceSet = nil;
nextEmptyRounds = 0; static dispatch_once_t onceToken;
} else { dispatch_once(&onceToken, ^{
batch = kKBBackspaceClearBatchSize; // stopBoundary:
nextEmptyRounds = emptyRounds + 1; // - . ! ?
} // - /
if (batch <= 0) { batch = 1; } // - \n \r
stopBoundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?。!?…\n\r\u2028\u2029"];
if (guard >= kKBBackspaceClearMaxDeletes || // trailingBoundary:
nextEmptyRounds > kKBBackspaceClearEmptyContextMaxRounds) { // //
trailingBoundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?。!?"];
// trailingWhitespace: /Tab stopBoundarySet
trailingWhitespaceSet = [NSCharacterSet whitespaceCharacterSet];
});
KBClearPhase phase = self.backspaceClearPhase;
NSInteger deletedThisTick = 0;
BOOL shouldStop = NO;
NSString *lastBefore = nil;
for (NSInteger i = 0; i < kKBBackspaceClearDeletesPerTick; i++) {
NSString *before = proxy.documentContextBeforeInput ?: @"";
if (before.length == 0) {
nextEmptyRounds += 1;
// 宿/QQ context使
// before
shouldStop = YES;
break;
}
nextEmptyRounds = 0;
if (lastBefore && [before isEqualToString:lastBefore] && deletedThisTick > 0) {
// 宿 context tick /
break;
}
lastBefore = before;
//
__block NSString *lastChar = @"";
[before enumerateSubstringsInRange:NSMakeRange(0, before.length)
options:NSStringEnumerationByComposedCharacterSequences | NSStringEnumerationReverse
usingBlock:^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, BOOL *stop) {
lastChar = substring ?: @"";
*stop = YES;
}];
if (lastChar.length == 0) { break; }
BOOL isWhitespace = ([lastChar rangeOfCharacterFromSet:trailingWhitespaceSet].location != NSNotFound);
BOOL isStopBoundary = ([lastChar rangeOfCharacterFromSet:stopBoundarySet].location != NSNotFound);
BOOL isTrailingBoundary = ([lastChar rangeOfCharacterFromSet:trailingBoundarySet].location != NSNotFound);
if (phase == KBClearPhaseSkipWhitespace) {
if (isWhitespace) {
[[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:proxy count:1];
[[KBInputBufferManager shared] applyClearDeleteCount:1];
deletedThisTick += 1;
continue;
}
phase = KBClearPhaseSkipTrailingBoundary;
}
if (phase == KBClearPhaseSkipTrailingBoundary) {
if (isTrailingBoundary) {
[[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:proxy count:1];
[[KBInputBufferManager shared] applyClearDeleteCount:1];
deletedThisTick += 1;
continue;
}
phase = KBClearPhaseDeleteUntilBoundary;
}
// phase == DeleteUntilBoundary
if (isStopBoundary) {
shouldStop = YES; //
break;
}
[[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:proxy count:1];
[[KBInputBufferManager shared] applyClearDeleteCount:1];
deletedThisTick += 1;
if (guard + deletedThisTick >= kKBBackspaceClearMaxDeletes) { break; }
if (deletedThisTick >= kKBBackspaceClearMaxStep) { break; }
}
self.backspaceClearPhase = phase;
NSInteger nextGuard = guard + deletedThisTick;
if (nextGuard >= kKBBackspaceClearMaxDeletes ||
nextEmptyRounds > kKBBackspaceClearEmptyContextMaxRounds ||
shouldStop) {
return; return;
} }
for (NSInteger i = 0; i < batch; i++) {
[proxy deleteBackward];
}
NSInteger nextGuard = guard + batch;
BOOL shouldContinue = NO;
if (count > 0 && !hitBoundary) {
if (count > batch) {
shouldContinue = YES;
} else if ([proxy hasText]) {
shouldContinue = YES;
}
}
if (!shouldContinue) { return; }
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(kKBBackspaceClearBatchInterval * NSEC_PER_SEC)), (int64_t)(kKBBackspaceClearBatchInterval * NSEC_PER_SEC)),
@@ -489,4 +635,28 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
return self.backspaceButton.superview; return self.backspaceButton.superview;
} }
- (void)kb_captureDeletionSnapshotIfNeeded {
if ([KBBackspaceUndoManager shared].hasUndo) { return; }
UIResponder *start = (UIResponder *)([self kb_hostView] ?: self.backspaceButton);
UIInputViewController *ivc = KBFindInputViewController(start);
if (!ivc) { return; }
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
[[KBBackspaceUndoManager shared] recordDeletionSnapshotBefore:proxy.documentContextBeforeInput
after:proxy.documentContextAfterInput];
}
- (void)kb_capturePendingClearSnapshotIfNeeded {
if (self.pendingClearBefore.length > 0 || self.pendingClearAfter.length > 0) { return; }
UIResponder *start = (UIResponder *)([self kb_hostView] ?: self.backspaceButton);
UIInputViewController *ivc = KBFindInputViewController(start);
if (!ivc) { return; }
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
self.pendingClearBefore = proxy.documentContextBeforeInput ?: @"";
self.pendingClearAfter = proxy.documentContextAfterInput ?: @"";
#if DEBUG
NSLog(@"[kb_capturePendingClearSnapshotIfNeeded/before] len=%lu text=%@", (unsigned long)self.pendingClearBefore.length, self.pendingClearBefore);
NSLog(@"[kb_capturePendingClearSnapshotIfNeeded/after] len=%lu text=%@", (unsigned long)self.pendingClearAfter.length, self.pendingClearAfter);
#endif
}
@end @end

View File

@@ -15,13 +15,19 @@ extern NSNotificationName const KBBackspaceUndoStateDidChangeNotification;
+ (instancetype)shared; + (instancetype)shared;
/// 记录一次“立刻清空”删除的内容(基于 documentContextBeforeInput /// 记录一次删除前的快照(不改变撤销按钮显示)。
- (void)recordClearWithContext:(NSString *)context; - (void)recordDeletionSnapshotBefore:(NSString *)before after:(NSString *)after;
/// 记录一次“立刻清空”删除的内容(基于 documentContextBeforeInput/AfterInput
- (void)recordClearWithContextBefore:(NSString *)before after:(NSString *)after;
/// 记录本次将被 deleteBackward 的内容,并执行 deleteBackward支持多次累计撤销时一次性插回
- (void)captureAndDeleteBackwardFromProxy:(id<UITextDocumentProxy>)proxy count:(NSUInteger)count;
/// 在指定 responder 处执行撤销(向光标处插回删除的内容) /// 在指定 responder 处执行撤销(向光标处插回删除的内容)
- (void)performUndoFromResponder:(UIResponder *)responder; - (void)performUndoFromResponder:(UIResponder *)responder;
/// 非清空行为触发时,清理撤销状态 /// 非删除行为触发时,清理撤销状态
- (void)registerNonClearAction; - (void)registerNonClearAction;
@end @end

View File

@@ -5,13 +5,38 @@
#import "KBBackspaceUndoManager.h" #import "KBBackspaceUndoManager.h"
#import "KBResponderUtils.h" #import "KBResponderUtils.h"
#import "KBInputBufferManager.h"
NSNotificationName const KBBackspaceUndoStateDidChangeNotification = @"KBBackspaceUndoStateDidChangeNotification"; NSNotificationName const KBBackspaceUndoStateDidChangeNotification = @"KBBackspaceUndoStateDidChangeNotification";
#if DEBUG
static NSString *KBLogString(NSString *tag, NSString *text) {
NSString *safeTag = tag ?: @"";
NSString *safeText = text ?: @"";
if (safeText.length <= 2000) {
return [NSString stringWithFormat:@"[%@] len=%lu text=%@", safeTag, (unsigned long)safeText.length, safeText];
}
NSString *head = [safeText substringToIndex:800];
NSString *tail = [safeText substringFromIndex:safeText.length - 800];
return [NSString stringWithFormat:@"[%@] len=%lu head=%@ ... tail=%@", safeTag, (unsigned long)safeText.length, head, tail];
}
#define KB_UNDO_LOG(tag, text) NSLog(@"%@", KBLogString((tag), (text)))
#else
#define KB_UNDO_LOG(tag, text) do {} while(0)
#endif
typedef NS_ENUM(NSInteger, KBUndoSnapshotSource) {
KBUndoSnapshotSourceNone = 0,
KBUndoSnapshotSourceDeletionSnapshot,
KBUndoSnapshotSourceClear
};
@interface KBBackspaceUndoManager () @interface KBBackspaceUndoManager ()
@property (nonatomic, strong) NSMutableArray<NSString *> *segments; // deletion order (last -> first) @property (nonatomic, copy) NSString *undoText;
@property (nonatomic, assign) BOOL lastActionWasClear; @property (nonatomic, assign) NSInteger undoAfterLength;
@property (nonatomic, assign) BOOL hasUndo; @property (nonatomic, assign) BOOL hasUndo;
@property (nonatomic, assign) KBUndoSnapshotSource snapshotSource;
@property (nonatomic, strong) NSMutableArray<NSString *> *undoDeletedPieces;
@end @end
@implementation KBBackspaceUndoManager @implementation KBBackspaceUndoManager
@@ -27,42 +52,191 @@ NSNotificationName const KBBackspaceUndoStateDidChangeNotification = @"KBBackspa
- (instancetype)init { - (instancetype)init {
if (self = [super init]) { if (self = [super init]) {
_segments = [NSMutableArray array]; _undoText = @"";
_undoAfterLength = 0;
_snapshotSource = KBUndoSnapshotSourceNone;
_undoDeletedPieces = [NSMutableArray array];
} }
return self; return self;
} }
- (void)recordClearWithContext:(NSString *)context { - (void)captureAndDeleteBackwardFromProxy:(id<UITextDocumentProxy>)proxy count:(NSUInteger)count {
if (context.length == 0) { return; } if (!proxy || count == 0) { return; }
NSString *segment = [self kb_segmentForClearFromContext:context];
if (segment.length == 0) { return; }
if (!self.lastActionWasClear) { NSString *selected = proxy.selectedText ?: @"";
[self.segments removeAllObjects]; NSString *ctxBefore = proxy.documentContextBeforeInput ?: @"";
NSString *ctxAfter = proxy.documentContextAfterInput ?: @"";
NSUInteger ctxLen = ctxBefore.length + ctxAfter.length;
BOOL isSelectAllLike = (selected.length > 0 &&
(ctxLen == 0 || selected.length >= MAX((NSUInteger)40, ctxLen * 2)));
if (isSelectAllLike) {
// /QQ/
if (self.hasUndo) {
[self registerNonClearAction];
} }
[self.segments addObject:segment]; #if DEBUG
self.lastActionWasClear = YES; KB_UNDO_LOG(@"captureAndDelete/selectAllDisableUndo", selected);
#endif
[proxy deleteBackward];
[[KBInputBufferManager shared] resetWithText:@""];
return;
}
if (!self.hasUndo) {
[self.undoDeletedPieces removeAllObjects];
self.undoText = @"";
self.undoAfterLength = 0;
self.snapshotSource = KBUndoSnapshotSourceDeletionSnapshot;
[self kb_updateHasUndo:YES];
}
BOOL didAppend = NO;
NSString *lastObservedBefore = nil;
for (NSUInteger i = 0; i < count; i++) {
NSString *before = proxy.documentContextBeforeInput ?: @"";
if (before.length > 0) {
// 宿 runloop context
if (lastObservedBefore && [before isEqualToString:lastObservedBefore]) {
// still delete, but don't record
} else {
NSString *piece = [self kb_lastComposedCharacterFromString:before];
if (piece.length > 0) {
[self.undoDeletedPieces addObject:piece];
didAppend = YES;
}
lastObservedBefore = before;
}
}
[proxy deleteBackward];
}
#if DEBUG
if (didAppend) {
NSUInteger piecesCount = self.undoDeletedPieces.count;
if (piecesCount <= 20) {
KB_UNDO_LOG(@"captureAndDelete/undoInsertTextNow", [self kb_buildUndoInsertTextFromPieces]);
} else if (piecesCount % 50 == 0) {
NSString *lastPiece = self.undoDeletedPieces.lastObject ?: @"";
NSLog(@"[captureAndDelete/undoPieces] pieces=%lu lastPiece=%@",
(unsigned long)piecesCount,
lastPiece);
}
}
#endif
}
- (void)recordDeletionSnapshotBefore:(NSString *)before after:(NSString *)after {
if (self.hasUndo) { return; }
NSString *pending = [KBInputBufferManager shared].pendingClearSnapshot;
NSString *manual = [KBInputBufferManager shared].manualSnapshot;
NSString *fallbackText = (pending.length > 0) ? pending : ((manual.length > 0) ? manual : [KBInputBufferManager shared].liveText);
if (fallbackText.length > 0) {
self.undoText = fallbackText;
self.undoAfterLength = 0;
self.snapshotSource = KBUndoSnapshotSourceDeletionSnapshot;
KB_UNDO_LOG(@"recordDeletionSnapshot/fallback", self.undoText);
[self kb_updateHasUndo:YES];
return;
}
NSString *safeBefore = before ?: @"";
NSString *safeAfter = after ?: @"";
NSString *full = [safeBefore stringByAppendingString:safeAfter];
if (full.length == 0) { return; }
self.undoText = full;
self.undoAfterLength = (NSInteger)safeAfter.length;
self.snapshotSource = KBUndoSnapshotSourceDeletionSnapshot;
KB_UNDO_LOG(@"recordDeletionSnapshot/context", self.undoText);
[self kb_updateHasUndo:YES];
}
- (void)recordClearWithContextBefore:(NSString *)before after:(NSString *)after {
NSString *pending = [KBInputBufferManager shared].pendingClearSnapshot;
NSString *manual = [KBInputBufferManager shared].manualSnapshot;
NSString *fallbackText = (pending.length > 0) ? pending : ((manual.length > 0) ? manual : [KBInputBufferManager shared].liveText);
NSString *safeBefore = before ?: @"";
NSString *safeAfter = after ?: @"";
NSString *contextText = [[safeBefore stringByAppendingString:safeAfter] copy];
NSString *candidate = (fallbackText.length > 0) ? fallbackText : contextText;
NSInteger candidateAfterLen = (fallbackText.length > 0) ? 0 : (NSInteger)safeAfter.length;
if (candidate.length == 0) { return; }
KB_UNDO_LOG(@"recordClear/candidate", candidate);
if (self.undoText.length > 0) {
if (self.snapshotSource == KBUndoSnapshotSourceClear) {
KB_UNDO_LOG(@"recordClear/ignored(alreadyClear)", self.undoText);
[self kb_updateHasUndo:YES];
return;
}
if (self.snapshotSource == KBUndoSnapshotSourceDeletionSnapshot) {
if (candidate.length > self.undoText.length) {
self.undoText = candidate;
self.undoAfterLength = candidateAfterLen;
KB_UNDO_LOG(@"recordClear/upgradedFromDeletion", self.undoText);
} else {
KB_UNDO_LOG(@"recordClear/keepDeletionSnapshot", self.undoText);
}
self.snapshotSource = KBUndoSnapshotSourceClear;
[self kb_updateHasUndo:YES];
return;
}
}
self.undoText = candidate;
self.undoAfterLength = candidateAfterLen;
self.snapshotSource = KBUndoSnapshotSourceClear;
KB_UNDO_LOG(@"recordClear/set", self.undoText);
[self kb_updateHasUndo:YES]; [self kb_updateHasUndo:YES];
} }
- (void)performUndoFromResponder:(UIResponder *)responder { - (void)performUndoFromResponder:(UIResponder *)responder {
if (self.segments.count == 0) { return; } if (!self.hasUndo) { return; }
UIInputViewController *ivc = KBFindInputViewController(responder); UIInputViewController *ivc = KBFindInputViewController(responder);
if (!ivc) { return; } if (!ivc) { return; }
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy; id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
NSString *text = [self kb_buildUndoText]; NSString *curBefore = proxy.documentContextBeforeInput ?: @"";
if (text.length == 0) { return; } NSString *curAfter = proxy.documentContextAfterInput ?: @"";
[proxy insertText:text]; KB_UNDO_LOG(@"performUndo/currentBefore", curBefore);
KB_UNDO_LOG(@"performUndo/currentAfter", curAfter);
[self.segments removeAllObjects]; NSString *insertText = [self kb_buildUndoInsertTextFromPieces];
self.lastActionWasClear = NO; if (insertText.length > 0) {
KB_UNDO_LOG(@"performUndo/insertDeletedText", insertText);
[proxy insertText:insertText];
[[KBInputBufferManager shared] appendText:insertText];
} else if (self.undoText.length > 0) {
KB_UNDO_LOG(@"performUndo/fallbackUndoText", self.undoText);
[self kb_clearAllTextForProxy:proxy];
[proxy insertText:self.undoText];
if (self.undoAfterLength > 0 &&
[proxy respondsToSelector:@selector(adjustTextPositionByCharacterOffset:)]) {
[proxy adjustTextPositionByCharacterOffset:-self.undoAfterLength];
}
[[KBInputBufferManager shared] resetWithText:self.undoText];
} else {
return;
}
self.undoText = @"";
self.undoAfterLength = 0;
self.snapshotSource = KBUndoSnapshotSourceNone;
[self.undoDeletedPieces removeAllObjects];
[self kb_updateHasUndo:NO]; [self kb_updateHasUndo:NO];
} }
- (void)registerNonClearAction { - (void)registerNonClearAction {
self.lastActionWasClear = NO; if (!self.hasUndo) { return; }
if (self.segments.count == 0) { return; } if (self.undoText.length > 0) {
[self.segments removeAllObjects]; KB_UNDO_LOG(@"registerNonClearAction/clearUndoText", self.undoText);
}
if (self.undoDeletedPieces.count > 0) {
KB_UNDO_LOG(@"registerNonClearAction/clearDeletedPieces", [self kb_buildUndoInsertTextFromPieces]);
}
self.undoText = @"";
self.undoAfterLength = 0;
self.snapshotSource = KBUndoSnapshotSourceNone;
[self.undoDeletedPieces removeAllObjects];
[self kb_updateHasUndo:NO]; [self kb_updateHasUndo:NO];
} }
@@ -74,97 +248,57 @@ NSNotificationName const KBBackspaceUndoStateDidChangeNotification = @"KBBackspa
[[NSNotificationCenter defaultCenter] postNotificationName:KBBackspaceUndoStateDidChangeNotification object:self]; [[NSNotificationCenter defaultCenter] postNotificationName:KBBackspaceUndoStateDidChangeNotification object:self];
} }
- (NSString *)kb_segmentForClearFromContext:(NSString *)context { - (NSString *)kb_lastComposedCharacterFromString:(NSString *)text {
NSInteger length = context.length; if (text.length == 0) { return @""; }
if (length == 0) { return @""; } __block NSString *last = @"";
[text enumerateSubstringsInRange:NSMakeRange(0, text.length)
static NSCharacterSet *sentenceBoundarySet = nil; options:NSStringEnumerationByComposedCharacterSequences | NSStringEnumerationReverse
static NSCharacterSet *whitespaceSet = nil; usingBlock:^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, BOOL *stop) {
static dispatch_once_t onceToken; last = substring ?: @"";
dispatch_once(&onceToken, ^{ *stop = YES;
sentenceBoundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?;。!?;…\n"]; }];
whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; return last ?: @"";
});
NSInteger end = length;
while (end > 0) {
unichar ch = [context characterAtIndex:end - 1];
if ([whitespaceSet characterIsMember:ch]) {
end -= 1;
} else {
break;
}
}
NSInteger searchEnd = end;
while (searchEnd > 0) {
unichar ch = [context characterAtIndex:searchEnd - 1];
if ([sentenceBoundarySet characterIsMember:ch]) {
searchEnd -= 1;
} else {
break;
}
} }
NSInteger boundaryIndex = NSNotFound; - (NSString *)kb_buildUndoInsertTextFromPieces {
for (NSInteger i = searchEnd - 1; i >= 0; i--) { if (self.undoDeletedPieces.count == 0) { return @""; }
unichar ch = [context characterAtIndex:i];
if ([sentenceBoundarySet characterIsMember:ch]) {
boundaryIndex = i;
break;
}
}
NSInteger start = (boundaryIndex == NSNotFound) ? 0 : (boundaryIndex + 1);
if (start >= length) { return @""; }
return [context substringFromIndex:start];
}
- (NSString *)kb_buildUndoText {
if (self.segments.count == 0) { return @""; }
NSArray<NSString *> *ordered = [[self.segments reverseObjectEnumerator] allObjects];
NSMutableString *result = [NSMutableString string]; NSMutableString *result = [NSMutableString string];
for (NSInteger i = 0; i < ordered.count; i++) { for (NSInteger i = (NSInteger)self.undoDeletedPieces.count - 1; i >= 0; i--) {
NSString *segment = ordered[i] ?: @""; NSString *piece = self.undoDeletedPieces[(NSUInteger)i] ?: @"";
if (segment.length == 0) { continue; } if (piece.length == 0) { continue; }
if (i < ordered.count - 1) { [result appendString:piece];
segment = [self kb_replaceTrailingBoundaryWithComma:segment];
}
[result appendString:segment];
} }
return result; return result;
}
- (NSString *)kb_replaceTrailingBoundaryWithComma:(NSString *)segment {
if (segment.length == 0) { return segment; }
static NSCharacterSet *boundarySet = nil;
static NSCharacterSet *englishBoundarySet = nil;
static NSCharacterSet *whitespaceSet = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
boundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?;。!?;…\n"];
englishBoundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?;"];
whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
});
NSInteger idx = segment.length - 1;
while (idx >= 0) {
unichar ch = [segment characterAtIndex:idx];
if ([whitespaceSet characterIsMember:ch]) {
idx -= 1;
continue;
}
if (![boundarySet characterIsMember:ch]) {
return segment;
}
NSString *comma = [englishBoundarySet characterIsMember:ch] ? @"," : @"";
NSMutableString *mutable = [segment mutableCopy];
NSRange r = NSMakeRange(idx, 1);
[mutable replaceCharactersInRange:r withString:comma];
return mutable;
} }
return segment; static const NSInteger kKBUndoClearMaxRounds = 200;
- (void)kb_clearAllTextForProxy:(id<UITextDocumentProxy>)proxy {
if (!proxy) { return; }
if ([proxy respondsToSelector:@selector(adjustTextPositionByCharacterOffset:)]) {
NSInteger guard = 0;
NSString *contextAfter = proxy.documentContextAfterInput ?: @"";
while (contextAfter.length > 0 && guard < kKBUndoClearMaxRounds) {
NSInteger offset = (NSInteger)contextAfter.length;
[proxy adjustTextPositionByCharacterOffset:offset];
for (NSUInteger i = 0; i < contextAfter.length; i++) {
[proxy deleteBackward];
}
guard += 1;
contextAfter = proxy.documentContextAfterInput ?: @"";
}
}
NSInteger guard = 0;
NSString *contextBefore = proxy.documentContextBeforeInput ?: @"";
while (contextBefore.length > 0 && guard < kKBUndoClearMaxRounds) {
for (NSUInteger i = 0; i < contextBefore.length; i++) {
[proxy deleteBackward];
}
guard += 1;
contextBefore = proxy.documentContextBeforeInput ?: @"";
}
} }
@end @end

View File

@@ -0,0 +1,34 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol UITextDocumentProxy;
@interface KBInputBufferManager : NSObject
+ (instancetype)shared;
@property (nonatomic, copy, readonly) NSString *liveText;
@property (nonatomic, copy, readonly) NSString *manualSnapshot;
@property (nonatomic, copy, readonly) NSString *pendingClearSnapshot;
- (void)seedIfEmptyWithContextBefore:(nullable NSString *)before after:(nullable NSString *)after;
- (void)updateFromExternalContextBefore:(nullable NSString *)before after:(nullable NSString *)after;
- (void)refreshFromProxyIfPossible:(nullable id<UITextDocumentProxy>)proxy;
- (void)prepareSnapshotForDeleteWithContextBefore:(nullable NSString *)before
after:(nullable NSString *)after;
- (void)beginPendingClearSnapshot;
- (void)clearPendingClearSnapshot;
- (void)resetWithText:(NSString *)text;
- (void)appendText:(NSString *)text;
- (void)deleteBackwardByCount:(NSUInteger)count;
- (void)replaceTailWithText:(NSString *)text deleteCount:(NSUInteger)count;
- (void)applyHoldDeleteCount:(NSUInteger)count;
- (void)applyClearDeleteCount:(NSUInteger)count;
- (void)clearAllLiveText;
- (void)commitLiveToManual;
- (void)restoreManualSnapshot;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,279 @@
#import "KBInputBufferManager.h"
#import <UIKit/UIKit.h>
#if DEBUG
static NSString *KBLogString2(NSString *tag, NSString *text) {
NSString *safeTag = tag ?: @"";
NSString *safeText = text ?: @"";
if (safeText.length <= 2000) {
return [NSString stringWithFormat:@"[%@] len=%lu text=%@", safeTag, (unsigned long)safeText.length, safeText];
}
NSString *head = [safeText substringToIndex:800];
NSString *tail = [safeText substringFromIndex:safeText.length - 800];
return [NSString stringWithFormat:@"[%@] len=%lu head=%@ ... tail=%@", safeTag, (unsigned long)safeText.length, head, tail];
}
#define KB_BUF_LOG(tag, text) NSLog(@"❤️=%@", KBLogString2((tag), (text)))
#else
#define KB_BUF_LOG(tag, text) do {} while(0)
#endif
@interface KBInputBufferManager ()
@property (nonatomic, copy, readwrite) NSString *liveText;
@property (nonatomic, copy, readwrite) NSString *manualSnapshot;
@property (nonatomic, copy, readwrite) NSString *pendingClearSnapshot;
@property (nonatomic, assign) BOOL manualSnapshotDirty;
@end
@implementation KBInputBufferManager
+ (instancetype)shared {
static KBInputBufferManager *mgr = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mgr = [[KBInputBufferManager alloc] init];
});
return mgr;
}
- (instancetype)init {
if (self = [super init]) {
_liveText = @"";
_manualSnapshot = @"";
_pendingClearSnapshot = @"";
_manualSnapshotDirty = NO;
}
return self;
}
- (void)seedIfEmptyWithContextBefore:(NSString *)before after:(NSString *)after {
if (self.liveText.length > 0 || self.manualSnapshot.length > 0) { return; }
NSString *safeBefore = before ?: @"";
NSString *safeAfter = after ?: @"";
NSString *full = [safeBefore stringByAppendingString:safeAfter];
if (full.length == 0) { return; }
self.liveText = full;
self.manualSnapshot = full;
self.manualSnapshotDirty = NO;
KB_BUF_LOG(@"seedIfEmpty", full);
}
- (void)updateFromExternalContextBefore:(NSString *)before after:(NSString *)after {
NSString *safeBefore = before ?: @"";
NSString *safeAfter = after ?: @"";
NSString *context = [safeBefore stringByAppendingString:safeAfter];
if (context.length == 0) { return; }
// /QQ 宿
// liveText/manualSnapshot /
self.liveText = context;
self.manualSnapshotDirty = YES;
#if DEBUG
static NSUInteger sExternalLogCounter = 0;
sExternalLogCounter += 1;
if (sExternalLogCounter % 12 == 0) {
KB_BUF_LOG(@"updateFromExternalContext/liveOnly", context);
}
#endif
}
- (void)refreshFromProxyIfPossible:(id<UITextDocumentProxy>)proxy {
NSString *harvested = [self kb_harvestFullTextFromProxy:proxy];
if (harvested.length == 0) {
KB_BUF_LOG(@"refreshFromProxy/failedOrUnsupported", @"");
return;
}
BOOL manualEmpty = (self.manualSnapshot.length == 0);
BOOL longerThanManual = (harvested.length > self.manualSnapshot.length);
if (!(manualEmpty || longerThanManual)) {
KB_BUF_LOG(@"refreshFromProxy/ignoredShorter", harvested);
return;
}
self.liveText = harvested;
self.manualSnapshot = harvested;
self.manualSnapshotDirty = NO;
KB_BUF_LOG(@"refreshFromProxy/accepted", harvested);
}
- (void)prepareSnapshotForDeleteWithContextBefore:(NSString *)before
after:(NSString *)after {
NSString *safeBefore = before ?: @"";
NSString *safeAfter = after ?: @"";
NSString *context = [safeBefore stringByAppendingString:safeAfter];
BOOL manualValid = (self.manualSnapshot.length > 0 &&
(context.length == 0 ||
(self.manualSnapshot.length >= context.length &&
[self.manualSnapshot rangeOfString:context].location != NSNotFound)));
if (manualValid) { return; }
if (self.liveText.length > 0) {
self.manualSnapshot = self.liveText;
self.manualSnapshotDirty = NO;
KB_BUF_LOG(@"prepareSnapshotForDelete/fromLiveText", self.manualSnapshot);
return;
}
if (context.length > 0) {
self.manualSnapshot = context;
self.manualSnapshotDirty = NO;
KB_BUF_LOG(@"prepareSnapshotForDelete/fromContext", self.manualSnapshot);
}
}
- (void)beginPendingClearSnapshot {
if (self.pendingClearSnapshot.length > 0) { return; }
if (self.manualSnapshot.length > 0) {
self.pendingClearSnapshot = self.manualSnapshot;
KB_BUF_LOG(@"beginPendingClearSnapshot/fromManual", self.pendingClearSnapshot);
return;
}
if (self.liveText.length > 0) {
self.pendingClearSnapshot = self.liveText;
KB_BUF_LOG(@"beginPendingClearSnapshot/fromLive", self.pendingClearSnapshot);
}
}
- (void)clearPendingClearSnapshot {
self.pendingClearSnapshot = @"";
}
- (void)resetWithText:(NSString *)text {
NSString *safe = text ?: @"";
self.liveText = safe;
self.manualSnapshot = safe;
self.pendingClearSnapshot = @"";
self.manualSnapshotDirty = NO;
KB_BUF_LOG(@"resetWithText", safe);
}
- (void)appendText:(NSString *)text {
if (text.length == 0) { return; }
[self kb_syncManualSnapshotIfNeeded];
self.liveText = [self.liveText stringByAppendingString:text];
self.manualSnapshot = [self.manualSnapshot stringByAppendingString:text];
}
- (void)deleteBackwardByCount:(NSUInteger)count {
if (count == 0) { return; }
self.liveText = [self kb_stringByDeletingComposedCharacters:count from:self.liveText];
self.manualSnapshot = [self kb_stringByDeletingComposedCharacters:count from:self.manualSnapshot];
}
- (void)replaceTailWithText:(NSString *)text deleteCount:(NSUInteger)count {
[self kb_syncManualSnapshotIfNeeded];
[self deleteBackwardByCount:count];
[self appendText:text];
}
- (void)applyHoldDeleteCount:(NSUInteger)count {
if (count == 0) { return; }
self.liveText = [self kb_stringByDeletingComposedCharacters:count from:self.liveText];
self.manualSnapshotDirty = YES;
}
- (void)applyClearDeleteCount:(NSUInteger)count {
if (count == 0) { return; }
self.liveText = [self kb_stringByDeletingComposedCharacters:count from:self.liveText];
self.manualSnapshotDirty = YES;
}
- (void)clearAllLiveText {
self.liveText = @"";
self.pendingClearSnapshot = @"";
self.manualSnapshotDirty = YES;
}
- (void)commitLiveToManual {
self.manualSnapshot = self.liveText ?: @"";
self.manualSnapshotDirty = NO;
KB_BUF_LOG(@"commitLiveToManual", self.manualSnapshot);
}
- (void)restoreManualSnapshot {
self.liveText = self.manualSnapshot ?: @"";
}
#pragma mark - Helpers
- (void)kb_syncManualSnapshotIfNeeded {
if (!self.manualSnapshotDirty) { return; }
self.manualSnapshot = self.liveText ?: @"";
self.manualSnapshotDirty = NO;
}
- (NSString *)kb_stringByDeletingComposedCharacters:(NSUInteger)count
from:(NSString *)text {
if (count == 0) { return text ?: @""; }
NSString *source = text ?: @"";
if (source.length == 0) { return @""; }
__block NSUInteger removed = 0;
__block NSUInteger endIndex = source.length;
[source enumerateSubstringsInRange:NSMakeRange(0, source.length)
options:NSStringEnumerationByComposedCharacterSequences | NSStringEnumerationReverse
usingBlock:^(__unused NSString *substring, NSRange substringRange, __unused NSRange enclosingRange, BOOL *stop) {
removed += 1;
endIndex = substringRange.location;
if (removed >= count) {
*stop = YES;
}
}];
if (removed < count) { return @""; }
return [source substringToIndex:endIndex];
}
- (NSString *)kb_harvestFullTextFromProxy:(id<UITextDocumentProxy>)proxy {
if (!proxy) { return @""; }
if (![proxy respondsToSelector:@selector(adjustTextPositionByCharacterOffset:)]) { return @""; }
static const NSInteger kKBHarvestMaxRounds = 160;
static const NSInteger kKBHarvestMaxChars = 50000;
NSInteger movedToEnd = 0;
NSInteger movedLeft = 0;
NSMutableArray<NSString *> *chunks = [NSMutableArray array];
NSInteger totalChars = 0;
@try {
NSInteger guard = 0;
NSString *after = proxy.documentContextAfterInput ?: @"";
while (after.length > 0 && guard < kKBHarvestMaxRounds) {
NSInteger step = (NSInteger)after.length;
[(id)proxy adjustTextPositionByCharacterOffset:step];
movedToEnd += step;
guard += 1;
after = proxy.documentContextAfterInput ?: @"";
}
guard = 0;
NSString *before = proxy.documentContextBeforeInput ?: @"";
while (before.length > 0 && guard < kKBHarvestMaxRounds && totalChars < kKBHarvestMaxChars) {
[chunks addObject:before];
totalChars += (NSInteger)before.length;
NSInteger step = (NSInteger)before.length;
[(id)proxy adjustTextPositionByCharacterOffset:-step];
movedLeft += step;
guard += 1;
before = proxy.documentContextBeforeInput ?: @"";
}
} @finally {
if (movedLeft != 0) {
[(id)proxy adjustTextPositionByCharacterOffset:movedLeft];
}
if (movedToEnd != 0) {
[(id)proxy adjustTextPositionByCharacterOffset:-movedToEnd];
}
}
if (chunks.count == 0) { return @""; }
NSMutableString *result = [NSMutableString stringWithCapacity:(NSUInteger)totalChars];
for (NSInteger i = (NSInteger)chunks.count - 1; i >= 0; i--) {
NSString *part = chunks[(NSUInteger)i] ?: @"";
if (part.length == 0) { continue; }
[result appendString:part];
}
return result;
}
@end

View File

@@ -18,6 +18,7 @@
@end @end
@implementation KBFunctionBarView @implementation KBFunctionBarView
static const CGFloat kKBBackButtonWidth = 40;
- (instancetype)initWithFrame:(CGRect)frame{ - (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) { if (self = [super initWithFrame:frame]) {
@@ -83,14 +84,14 @@
UIButton *appButton = [UIButton buttonWithType:UIButtonTypeCustom]; UIButton *appButton = [UIButton buttonWithType:UIButtonTypeCustom];
appButton.tag = 100; // index = 0 appButton.tag = 100; // index = 0
UIImage *appImage = [UIImage imageNamed:@"App_icon"]; UIImage *appImage = [UIImage imageNamed:@"App_icon"];
[appButton setImage:appImage forState:UIControlStateNormal]; [appButton setBackgroundImage:appImage forState:UIControlStateNormal];
appButton.imageView.contentMode = UIViewContentModeScaleAspectFit; appButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
appButton.adjustsImageWhenHighlighted = YES; appButton.adjustsImageWhenHighlighted = YES;
[appButton addTarget:self action:@selector(onLeftTap:) forControlEvents:UIControlEventTouchUpInside]; [appButton addTarget:self action:@selector(onLeftTap:) forControlEvents:UIControlEventTouchUpInside];
[self.leftContainer addSubview:appButton]; [self.leftContainer addSubview:appButton];
[appButton mas_makeConstraints:^(MASConstraintMaker *make) { [appButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.leftContainer); make.center.equalTo(self.leftContainer);
make.width.height.mas_equalTo(34); // make.width.height.mas_equalTo(kKBBackButtonWidth); //
}]; }];
self.leftButtonsInternal = @[appButton]; self.leftButtonsInternal = @[appButton];

View File

@@ -27,6 +27,7 @@
#import "KBBizCode.h" #import "KBBizCode.h"
#import "KBBackspaceLongPressHandler.h" #import "KBBackspaceLongPressHandler.h"
#import "KBBackspaceUndoManager.h" #import "KBBackspaceUndoManager.h"
#import "KBInputBufferManager.h"
@interface KBFunctionView () <KBFunctionBarViewDelegate, KBStreamOverlayViewDelegate, KBFunctionTagListViewDelegate> @interface KBFunctionView () <KBFunctionBarViewDelegate, KBStreamOverlayViewDelegate, KBFunctionTagListViewDelegate>
// UI // UI
@@ -721,6 +722,8 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
// - / changeCount // - / changeCount
- (void)startPasteboardMonitor { - (void)startPasteboardMonitor {
//
return;
// 访宿/ // 访宿/
if (![[KBFullAccessManager shared] hasFullAccess]) return; if (![[KBFullAccessManager shared] hasFullAccess]) return;
if (self.pasteboardTimer) return; if (self.pasteboardTimer) return;
@@ -770,24 +773,30 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
- (void)kb_fullAccessChanged { - (void)kb_fullAccessChanged {
dispatch_async(dispatch_get_main_queue(), ^{ [self kb_refreshPasteboardMonitor]; }); dispatch_async(dispatch_get_main_queue(), ^{ [self kb_refreshPasteboardMonitor]; });
} }
- (void)onTapDelete {
- (void)onTapDelete {
NSLog(@"点击:删除"); NSLog(@"点击:删除");
[[KBBackspaceUndoManager shared] registerNonClearAction];
UIInputViewController *ivc = KBFindInputViewController(self); UIInputViewController *ivc = KBFindInputViewController(self);
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy; id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
[proxy deleteBackward]; [[KBInputBufferManager shared] refreshFromProxyIfPossible:proxy];
[[KBInputBufferManager shared] prepareSnapshotForDeleteWithContextBefore:proxy.documentContextBeforeInput
after:proxy.documentContextAfterInput];
[[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:proxy count:1];
[[KBInputBufferManager shared] applyHoldDeleteCount:1];
} }
- (void)onTapClear { - (void)onTapClear {
NSLog(@"点击:清空"); NSLog(@"点击:清空");
[self.backspaceHandler performClearAction]; [self.backspaceHandler performClearAction];
} }
- (void)onTapSend {
- (void)onTapSend {
NSLog(@"点击:发送"); NSLog(@"点击:发送");
[[KBBackspaceUndoManager shared] registerNonClearAction]; [[KBBackspaceUndoManager shared] registerNonClearAction];
// App // App
UIInputViewController *ivc = KBFindInputViewController(self); UIInputViewController *ivc = KBFindInputViewController(self);
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy; id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
[proxy insertText:@"\n"]; [proxy insertText:@"\n"];
[[KBInputBufferManager shared] appendText:@"\n"];
} }
#pragma mark - Lazy #pragma mark - Lazy

View File

@@ -31,6 +31,9 @@ NS_ASSUME_NONNULL_BEGIN
/// emoji 面板点击搜索 /// emoji 面板点击搜索
- (void)keyBoardMainViewDidTapEmojiSearch:(KBKeyBoardMainView *)keyBoardMainView; - (void)keyBoardMainViewDidTapEmojiSearch:(KBKeyBoardMainView *)keyBoardMainView;
/// 选择了联想词
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didSelectSuggestion:(NSString *)suggestion;
@end @end
@interface KBKeyBoardMainView : UIView @interface KBKeyBoardMainView : UIView
@@ -39,6 +42,9 @@ NS_ASSUME_NONNULL_BEGIN
/// 应用当前皮肤(会触发键区重载以应用按键颜色) /// 应用当前皮肤(会触发键区重载以应用按键颜色)
- (void)kb_applyTheme; - (void)kb_applyTheme;
/// 更新联想候选
- (void)kb_setSuggestions:(NSArray<NSString *> *)suggestions;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@@ -11,29 +11,42 @@
#import "KBFunctionView.h" #import "KBFunctionView.h"
#import "KBKey.h" #import "KBKey.h"
#import "KBEmojiPanelView.h" #import "KBEmojiPanelView.h"
#import "KBSuggestionBarView.h"
#import "Masonry.h" #import "Masonry.h"
#import "KBSkinManager.h" #import "KBSkinManager.h"
#import "KBBackspaceUndoManager.h"
@interface KBKeyBoardMainView ()<KBToolBarDelegate, KBKeyboardViewDelegate, KBEmojiPanelViewDelegate> @interface KBKeyBoardMainView ()<KBToolBarDelegate, KBKeyboardViewDelegate, KBEmojiPanelViewDelegate, KBSuggestionBarViewDelegate>
@property (nonatomic, strong) KBToolBar *topBar; @property (nonatomic, strong) KBToolBar *topBar;
@property (nonatomic, strong) KBSuggestionBarView *suggestionBar;
@property (nonatomic, strong) KBKeyboardView *keyboardView; @property (nonatomic, strong) KBKeyboardView *keyboardView;
@property (nonatomic, strong) KBEmojiPanelView *emojiView; @property (nonatomic, strong) KBEmojiPanelView *emojiView;
@property (nonatomic, assign) BOOL emojiPanelVisible; @property (nonatomic, assign) BOOL emojiPanelVisible;
@property (nonatomic, assign) BOOL suggestionBarHasItems;
// / // /
@end @end
@implementation KBKeyBoardMainView @implementation KBKeyBoardMainView
- (instancetype)initWithFrame:(CGRect)frame { - (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) { if (self = [super initWithFrame:frame]) {
self.backgroundColor = [KBSkinManager shared].current.keyboardBackground; // self.backgroundColor = [KBSkinManager shared].current.keyboardBackground;
self.backgroundColor = [UIColor colorWithHex:0xD1D3DB];
// //
self.topBar = [[KBToolBar alloc] init]; self.topBar = [[KBToolBar alloc] init];
self.topBar.delegate = self; self.topBar.delegate = self;
[self addSubview:self.topBar]; [self addSubview:self.topBar];
//
self.suggestionBar = [[KBSuggestionBarView alloc] init];
self.suggestionBar.delegate = self;
self.suggestionBar.hidden = YES;
[self addSubview:self.suggestionBar];
// / // /
CGFloat keyboardAreaHeight = KBFit(200.0f); CGFloat keyboardAreaHeight = KBFit(200.0f);
CGFloat bottomInset = KBFit(4.0f); CGFloat bottomInset = KBFit(4.0f);
// CGFloat topBarHeight = KBFit(40.0f);
CGFloat barSpacing = KBFit(6.0f); CGFloat barSpacing = KBFit(6.0f);
self.keyboardView = [[KBKeyboardView alloc] init]; self.keyboardView = [[KBKeyboardView alloc] init];
@@ -54,16 +67,39 @@
make.edges.equalTo(self); make.edges.equalTo(self);
}]; }];
// [self.topBar mas_makeConstraints:^(MASConstraintMaker *make) {
// make.left.right.equalTo(self);
// make.top.equalTo(self.mas_top).offset(0);
// make.height.mas_equalTo(topBarHeight);
// }];
[self.topBar mas_makeConstraints:^(MASConstraintMaker *make) { [self.topBar mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self); make.left.right.equalTo(self);
make.top.equalTo(self.mas_top).offset(0); make.top.equalTo(self.mas_top).offset(0);
make.bottom.equalTo(self.keyboardView.mas_top).offset(0); make.bottom.equalTo(self.keyboardView.mas_top).offset(0);
}]; }];
[self.suggestionBar mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self);
make.top.equalTo(self.topBar);
make.bottom.equalTo(self.topBar);
}];
[self.keyboardView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.topBar.mas_bottom).offset(barSpacing);
}];
// / // /
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(kb_undoStateChanged)
name:KBBackspaceUndoStateDidChangeNotification
object:nil];
} }
return self; return self;
} }
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)setEmojiPanelVisible:(BOOL)visible animated:(BOOL)animated { - (void)setEmojiPanelVisible:(BOOL)visible animated:(BOOL)animated {
if (self.emojiPanelVisible == visible) return; if (self.emojiPanelVisible == visible) return;
self.emojiPanelVisible = visible; self.emojiPanelVisible = visible;
@@ -74,17 +110,24 @@
} else { } else {
self.keyboardView.hidden = NO; self.keyboardView.hidden = NO;
self.topBar.hidden = NO; self.topBar.hidden = NO;
self.suggestionBar.hidden = !self.suggestionBarHasItems;
} }
void (^changes)(void) = ^{ void (^changes)(void) = ^{
self.emojiView.alpha = visible ? 1.0 : 0.0; self.emojiView.alpha = visible ? 1.0 : 0.0;
self.keyboardView.alpha = visible ? 0.0 : 1.0; self.keyboardView.alpha = visible ? 0.0 : 1.0;
self.topBar.alpha = visible ? 0.0 : 1.0; self.topBar.alpha = visible ? 0.0 : 1.0;
self.suggestionBar.alpha = visible ? 0.0 : ([self kb_shouldShowSuggestions] ? 1.0 : 0.0);
}; };
void (^completion)(BOOL) = ^(BOOL finished) { void (^completion)(BOOL) = ^(BOOL finished) {
self.emojiView.hidden = !visible; self.emojiView.hidden = !visible;
self.keyboardView.hidden = visible; self.keyboardView.hidden = visible;
self.topBar.hidden = visible; self.topBar.hidden = visible;
if (visible) {
self.suggestionBar.hidden = YES;
} else {
self.suggestionBar.hidden = ![self kb_shouldShowSuggestions];
}
}; };
if (animated) { if (animated) {
@@ -205,16 +248,52 @@
- (void)kb_applyTheme { - (void)kb_applyTheme {
KBSkinManager *mgr = [KBSkinManager shared]; KBSkinManager *mgr = [KBSkinManager shared];
BOOL hasImg = ([mgr currentBackgroundImage] != nil); BOOL hasImg = ([mgr currentBackgroundImage] != nil);
UIColor *bg = mgr.current.keyboardBackground; // UIColor *bg = mgr.current.keyboardBackground;
UIColor *bg = [UIColor colorWithHex:0xD1D3DB];
self.backgroundColor = hasImg ? [UIColor clearColor] : bg; self.backgroundColor = hasImg ? [UIColor clearColor] : bg;
self.keyboardView.backgroundColor = hasImg ? [UIColor clearColor] : bg; self.keyboardView.backgroundColor = hasImg ? [UIColor clearColor] : bg;
if ([self.topBar respondsToSelector:@selector(kb_applyTheme)]) { if ([self.topBar respondsToSelector:@selector(kb_applyTheme)]) {
[self.topBar kb_applyTheme]; [self.topBar kb_applyTheme];
} }
[self.suggestionBar applyTheme:mgr.current];
[self.keyboardView reloadKeys]; [self.keyboardView reloadKeys];
if (self.emojiView) { if (self.emojiView) {
[self.emojiView applyTheme:mgr.current]; [self.emojiView applyTheme:mgr.current];
} }
} }
#pragma mark - Suggestions
- (void)kb_setSuggestions:(NSArray<NSString *> *)suggestions {
self.suggestionBarHasItems = (suggestions.count > 0);
[self.suggestionBar updateSuggestions:suggestions];
[self kb_applySuggestionVisibility];
}
#pragma mark - KBSuggestionBarViewDelegate
- (void)suggestionBarView:(KBSuggestionBarView *)view didSelectSuggestion:(NSString *)suggestion {
if ([self.delegate respondsToSelector:@selector(keyBoardMainView:didSelectSuggestion:)]) {
[self.delegate keyBoardMainView:self didSelectSuggestion:suggestion];
}
}
- (void)kb_undoStateChanged {
[self kb_applySuggestionVisibility];
}
- (BOOL)kb_shouldShowSuggestions {
if (self.emojiPanelVisible) { return NO; }
if (![KBBackspaceUndoManager shared].hasUndo && self.suggestionBarHasItems) {
return YES;
}
return NO;
}
- (void)kb_applySuggestionVisibility {
BOOL shouldShow = [self kb_shouldShowSuggestions];
self.suggestionBar.hidden = !shouldShow;
self.suggestionBar.alpha = shouldShow ? 1.0 : 0.0;
}
@end @end

View File

@@ -9,6 +9,7 @@
#import "KBStreamTextView.h" #import "KBStreamTextView.h"
#import "KBResponderUtils.h" // UIInputViewController宿 #import "KBResponderUtils.h" // UIInputViewController宿
#import "KBInputBufferManager.h"
@interface KBStreamTextView () @interface KBStreamTextView ()
@@ -371,6 +372,7 @@ static inline NSString *KBTrimRight(NSString *s) {
if (rawText.length > 0) { if (rawText.length > 0) {
[proxy insertText:rawText]; [proxy insertText:rawText];
} }
[[KBInputBufferManager shared] resetWithText:rawText ?: @""];
} }
} }
} }

View File

@@ -0,0 +1,26 @@
//
// KBSuggestionBarView.h
// CustomKeyboard
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class KBSuggestionBarView;
@class KBSkinTheme;
@protocol KBSuggestionBarViewDelegate <NSObject>
- (void)suggestionBarView:(KBSuggestionBarView *)view didSelectSuggestion:(NSString *)suggestion;
@end
@interface KBSuggestionBarView : UIView
@property (nonatomic, weak) id<KBSuggestionBarViewDelegate> delegate;
- (void)updateSuggestions:(NSArray<NSString *> *)suggestions;
- (void)applyTheme:(KBSkinTheme *)theme;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,114 @@
//
// KBSuggestionBarView.m
// CustomKeyboard
//
#import "KBSuggestionBarView.h"
#import "Masonry.h"
#import "KBSkinManager.h"
@interface KBSuggestionBarView ()
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIStackView *stackView;
@property (nonatomic, copy) NSArray<NSString *> *items;
@property (nonatomic, strong) UIColor *pillColor;
@property (nonatomic, strong) UIColor *textColor;
@end
@implementation KBSuggestionBarView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
[self setupUI];
}
return self;
}
- (void)setupUI {
[self addSubview:self.scrollView];
[self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self);
}];
[self.scrollView addSubview:self.stackView];
[self.stackView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.scrollView).insets(UIEdgeInsetsMake(0, 8, 0, 8));
make.height.equalTo(self.scrollView);
}];
[self applyTheme:[KBSkinManager shared].current];
}
- (void)updateSuggestions:(NSArray<NSString *> *)suggestions {
self.items = suggestions ?: @[];
for (UIView *view in self.stackView.arrangedSubviews) {
[self.stackView removeArrangedSubview:view];
[view removeFromSuperview];
}
for (NSString *item in self.items) {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.layer.cornerRadius = 12.0;
btn.layer.masksToBounds = YES;
btn.backgroundColor = self.pillColor ?: [UIColor colorWithWhite:1 alpha:0.9];
btn.titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
[btn setTitle:item forState:UIControlStateNormal];
[btn setTitleColor:self.textColor ?: [UIColor blackColor] forState:UIControlStateNormal];
btn.contentEdgeInsets = UIEdgeInsetsMake(4, 10, 4, 10);
[btn addTarget:self action:@selector(onTapSuggestion:) forControlEvents:UIControlEventTouchUpInside];
[self.stackView addArrangedSubview:btn];
}
self.hidden = (self.items.count == 0);
}
- (void)applyTheme:(KBSkinTheme *)theme {
UIColor *bg = theme.keyBackground ?: [UIColor whiteColor];
UIColor *text = theme.keyTextColor ?: [UIColor blackColor];
UIColor *barBg = [UIColor colorWithHex:0xD1D3DB];
self.backgroundColor = barBg;
self.pillColor = bg;
self.textColor = text;
for (UIView *view in self.stackView.arrangedSubviews) {
if (![view isKindOfClass:[UIButton class]]) { continue; }
UIButton *btn = (UIButton *)view;
btn.backgroundColor = self.pillColor;
[btn setTitleColor:self.textColor forState:UIControlStateNormal];
}
}
#pragma mark - Actions
- (void)onTapSuggestion:(UIButton *)sender {
NSString *title = sender.currentTitle ?: @"";
if (title.length == 0) { return; }
if ([self.delegate respondsToSelector:@selector(suggestionBarView:didSelectSuggestion:)]) {
[self.delegate suggestionBarView:self didSelectSuggestion:title];
}
}
#pragma mark - Lazy
- (UIScrollView *)scrollView {
if (!_scrollView) {
_scrollView = [[UIScrollView alloc] init];
_scrollView.showsHorizontalScrollIndicator = NO;
_scrollView.alwaysBounceHorizontal = YES;
}
return _scrollView;
}
- (UIStackView *)stackView {
if (!_stackView) {
_stackView = [[UIStackView alloc] init];
_stackView.axis = UILayoutConstraintAxisHorizontal;
_stackView.alignment = UIStackViewAlignmentCenter;
_stackView.spacing = 8.0;
}
return _stackView;
}
@end

View File

@@ -23,6 +23,7 @@
@implementation KBToolBar @implementation KBToolBar
static NSString * const kKBAIKeyIdentifier = @"ai"; static NSString * const kKBAIKeyIdentifier = @"ai";
static NSString * const kKBUndoKeyIdentifier = @"key_revoke";
static const CGFloat kKBAIButtonWidth = 40; static const CGFloat kKBAIButtonWidth = 40;
static const CGFloat kKBAIButtonHeight = 40; static const CGFloat kKBAIButtonHeight = 40;
@@ -96,6 +97,7 @@ static const CGFloat kKBAIButtonHeight = 40;
make.right.equalTo(self.mas_right).offset(-12); make.right.equalTo(self.mas_right).offset(-12);
make.centerY.equalTo(self.mas_centerY); make.centerY.equalTo(self.mas_centerY);
make.height.mas_equalTo(32); make.height.mas_equalTo(32);
make.width.mas_equalTo(84);
}]; }];
[self kb_updateLeftContainerConstraints]; [self kb_updateLeftContainerConstraints];
@@ -169,6 +171,7 @@ static const CGFloat kKBAIButtonHeight = 40;
- (void)kb_applyTheme { - (void)kb_applyTheme {
[self kb_updateAIButtonAppearance]; [self kb_updateAIButtonAppearance];
[self kb_updateUndoButtonAppearance];
} }
- (void)kb_updateAIButtonAppearance { - (void)kb_updateAIButtonAppearance {
@@ -205,6 +208,26 @@ static const CGFloat kKBAIButtonHeight = 40;
} }
} }
- (void)kb_updateUndoButtonAppearance {
if (!self.undoButtonInternal) { return; }
KBSkinManager *skinManager = [KBSkinManager shared];
UIImage *icon = [skinManager iconImageForKeyIdentifier:kKBUndoKeyIdentifier caseVariant:0];
if (!icon) {
icon = [UIImage imageNamed:@"key_revoke"];
}
if (icon) {
[self.undoButtonInternal setImage:icon forState:UIControlStateNormal];
[self.undoButtonInternal setImage:icon forState:UIControlStateHighlighted];
[self.undoButtonInternal setImage:icon forState:UIControlStateSelected];
} else {
[self.undoButtonInternal setImage:nil forState:UIControlStateNormal];
[self.undoButtonInternal setImage:nil forState:UIControlStateHighlighted];
[self.undoButtonInternal setImage:nil forState:UIControlStateSelected];
}
}
#pragma mark - Actions #pragma mark - Actions
- (void)onLeftAction:(UIButton *)sender { - (void)onLeftAction:(UIButton *)sender {
@@ -262,14 +285,15 @@ static const CGFloat kKBAIButtonHeight = 40;
- (UIButton *)undoButtonInternal { - (UIButton *)undoButtonInternal {
if (!_undoButtonInternal) { if (!_undoButtonInternal) {
_undoButtonInternal = [UIButton buttonWithType:UIButtonTypeSystem]; _undoButtonInternal = [UIButton buttonWithType:UIButtonTypeCustom];
_undoButtonInternal.layer.cornerRadius = 16; // _undoButtonInternal.layer.cornerRadius = 16;
_undoButtonInternal.layer.masksToBounds = YES; // _undoButtonInternal.layer.masksToBounds = YES;
_undoButtonInternal.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9]; // _undoButtonInternal.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9];
_undoButtonInternal.titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium]; // _undoButtonInternal.titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
[_undoButtonInternal setTitle:@"撤销删除" forState:UIControlStateNormal]; // [_undoButtonInternal setTitle:@"撤销删除" forState:UIControlStateNormal];
[_undoButtonInternal setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; // [_undoButtonInternal setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
_undoButtonInternal.contentEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 10); // _undoButtonInternal.contentEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 10);
[_undoButtonInternal setImage:[UIImage imageNamed:@"key_revoke"] forState:UIControlStateNormal];
_undoButtonInternal.hidden = YES; _undoButtonInternal.hidden = YES;
_undoButtonInternal.alpha = 0.0; _undoButtonInternal.alpha = 0.0;
[_undoButtonInternal addTarget:self action:@selector(onUndo) forControlEvents:UIControlEventTouchUpInside]; [_undoButtonInternal addTarget:self action:@selector(onUndo) forControlEvents:UIControlEventTouchUpInside];

View File

@@ -32,6 +32,7 @@
#define API_UPDATA_INFO @"/user/updateInfo" // 更新用户 #define API_UPDATA_INFO @"/user/updateInfo" // 更新用户
#define KB_API_USER_DETAIL @"/user/detail" // 用户详情 #define KB_API_USER_DETAIL @"/user/detail" // 用户详情
#define API_USER_INVITE_CODE @"/user/inviteCode" // 查询邀请码
#define API_CHARACTER_LIST @"/character/list" // 排行榜角色列表(综合) #define API_CHARACTER_LIST @"/character/list" // 排行榜角色列表(综合)
#define API_NOT_LOGIN_CHARACTER_LIST @"/character/listWithNotLogin" //未登录用户人设列表 #define API_NOT_LOGIN_CHARACTER_LIST @"/character/listWithNotLogin" //未登录用户人设列表
@@ -56,6 +57,10 @@
#define API_THEME_DOWNLOAD @"/themes/download" // 主题下载信息 #define API_THEME_DOWNLOAD @"/themes/download" // 主题下载信息
#define API_THEME_RECOMMENDED @"/themes/recommended" // 推荐主题列表 #define API_THEME_RECOMMENDED @"/themes/recommended" // 推荐主题列表
#define API_THEME_SEARCH @"/themes/search" // 搜索主题themeName #define API_THEME_SEARCH @"/themes/search" // 搜索主题themeName
#define API_USER_THEMES_BATCH_DELETE @"/user-themes/batch-delete" // 批量删除用户主题
#define API_THEME_PURCHASE_LIST @"/themes/purchase/list" // 查询主题购买记录
#define API_THEME_RESTORE @"/themes/restore" // 恢复已删除的主题
#define API_WALLET_TRANSACTIONS @"/wallet/transactions" // 分页查询钱包交易记录
/// pay /// pay
#define API_VALIDATE_RECEIPT @"/apple/validate-receipt" // 排行榜标签列表 #define API_VALIDATE_RECEIPT @"/apple/validate-receipt" // 排行榜标签列表

View File

@@ -38,7 +38,8 @@
// 基础baseUrl // 基础baseUrl
#ifndef KB_BASE_URL #ifndef KB_BASE_URL
//#define KB_BASE_URL @"https://m1.apifoxmock.com/m1/5438099-5113192-default/" //#define KB_BASE_URL @"https://m1.apifoxmock.com/m1/5438099-5113192-default/"
#define KB_BASE_URL @"http://192.168.2.21:7529/api" //#define KB_BASE_URL @"http://192.168.2.21:7529/api"
#define KB_BASE_URL @"https://devcallback.loveamorkey.com/api"
#endif #endif
#import "KBFont.h" #import "KBFont.h"

View File

@@ -0,0 +1,39 @@
//
// KBMaiPointReporter.h
// keyBoard
//
#import <Foundation/Foundation.h>
#ifndef KB_MAI_POINT_BASE_URL
#define KB_MAI_POINT_BASE_URL @"http://192.168.1.188:35310/api"
#endif
#ifndef KB_MAI_POINT_PATH_NEW_ACCOUNT
#define KB_MAI_POINT_PATH_NEW_ACCOUNT @"/newAccount"
#endif
NS_ASSUME_NONNULL_BEGIN
extern NSString * const KBMaiPointErrorDomain;
typedef void (^KBMaiPointReportCompletion)(BOOL success, NSError * _Nullable error);
/// Lightweight reporter for Mai point tracking. Safe for app + extension.
@interface KBMaiPointReporter : NSObject
+ (instancetype)sharedReporter;
/// POST /newAccount with type + account.
- (void)reportNewAccountWithType:(NSString *)type
account:(NSString *)account
completion:(KBMaiPointReportCompletion _Nullable)completion;
/// Generic POST for future endpoints.
- (void)postPath:(NSString *)path
parameters:(NSDictionary *)parameters
completion:(KBMaiPointReportCompletion _Nullable)completion;
@end
NS_ASSUME_NONNULL_END

127
Shared/KBMaiPointReporter.m Normal file
View File

@@ -0,0 +1,127 @@
//
// KBMaiPointReporter.m
// keyBoard
//
#import "KBMaiPointReporter.h"
NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain";
@implementation KBMaiPointReporter
+ (instancetype)sharedReporter {
static KBMaiPointReporter *reporter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
reporter = [[KBMaiPointReporter alloc] init];
});
return reporter;
}
- (void)reportNewAccountWithType:(NSString *)type
account:(NSString *)account
completion:(KBMaiPointReportCompletion)completion {
NSString *trimmedType = [type stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *trimmedAccount = [account stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (trimmedType.length == 0 || 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;
}
NSDictionary *params = @{
@"type": trimmedType,
@"account": trimmedAccount
};
[self postPath:KB_MAI_POINT_PATH_NEW_ACCOUNT parameters:params completion:completion];
}
- (void)postPath:(NSString *)path
parameters:(NSDictionary *)parameters
completion:(KBMaiPointReportCompletion)completion {
if (path.length == 0 || ![parameters isKindOfClass:[NSDictionary class]]) {
NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"Invalid parameter"}];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(NO, error);
});
}
return;
}
NSString *safePath = [path hasPrefix:@"/"] ? path : [@"/" stringByAppendingString:path];
NSString *urlString = [NSString stringWithFormat:@"%@%@", KB_MAI_POINT_BASE_URL, safePath];
NSURL *url = [NSURL URLWithString:urlString];
if (!url) {
NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain
code:-2
userInfo:@{NSLocalizedDescriptionKey: @"Invalid URL"}];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(NO, error);
});
}
return;
}
NSError *jsonError = nil;
NSData *body = [NSJSONSerialization dataWithJSONObject:parameters options:0 error:&jsonError];
if (jsonError) {
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(NO, jsonError);
});
}
return;
}
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
request.timeoutInterval = 10.0;
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
request.HTTPBody = body;
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
BOOL success = NO;
NSError *finalError = error;
if (!finalError) {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
success = (statusCode >= 200 && statusCode < 300);
if (!success) {
finalError = [NSError errorWithDomain:KBMaiPointErrorDomain
code:statusCode
userInfo:@{NSLocalizedDescriptionKey: @"Invalid response"}];
}
} else {
finalError = [NSError errorWithDomain:KBMaiPointErrorDomain
code:-3
userInfo:@{NSLocalizedDescriptionKey: @"Invalid response"}];
}
}
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(success, finalError);
});
}
}];
[task resume];
}
@end

View File

@@ -24,6 +24,7 @@ static NSString * const kKBSkinPendingKindKey = @"kind";
static NSString * const kKBSkinPendingTimestampKey = @"timestamp"; static NSString * const kKBSkinPendingTimestampKey = @"timestamp";
static NSString * const kKBSkinPendingIconShortKey = @"iconShortNames"; static NSString * const kKBSkinPendingIconShortKey = @"iconShortNames";
static NSString * const kKBSkinMetadataFileName = @"metadata.plist"; static NSString * const kKBSkinMetadataFileName = @"metadata.plist";
static NSString * const kKBSkinForceDownloadKey = @"force_download";
static NSString * const kKBSkinMetadataNameKey = @"name"; static NSString * const kKBSkinMetadataNameKey = @"name";
static NSString * const kKBSkinMetadataPreviewKey = @"preview"; static NSString * const kKBSkinMetadataPreviewKey = @"preview";
static NSString * const kKBSkinMetadataZipKey = @"zip_url"; static NSString * const kKBSkinMetadataZipKey = @"zip_url";
@@ -220,6 +221,11 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
NSString *skinId = skinJSON[@"id"] ?: @"remote"; NSString *skinId = skinJSON[@"id"] ?: @"remote";
NSString *name = skinJSON[@"name"] ?: skinId; NSString *name = skinJSON[@"name"] ?: skinId;
NSString *zipURL = skinJSON[@"zip_url"] ?: @""; NSString *zipURL = skinJSON[@"zip_url"] ?: @"";
BOOL forceDownload = NO;
id forceValue = skinJSON[kKBSkinForceDownloadKey];
if ([forceValue respondsToSelector:@selector(boolValue)]) {
forceDownload = [forceValue boolValue];
}
// key_icons // key_icons
// - key_icons使 // - key_icons使
@@ -258,6 +264,21 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
BOOL hasCachedAssets = (contents.count > 0); BOOL hasCachedAssets = (contents.count > 0);
NSString *bgPath = [skinRoot stringByAppendingPathComponent:@"background.png"]; NSString *bgPath = [skinRoot stringByAppendingPathComponent:@"background.png"];
BOOL useTempRoot = forceDownload;
NSString *tempToken = nil;
NSString *workingRoot = skinRoot;
NSString *workingIconsDir = iconsDir;
NSString *workingBgPath = bgPath;
if (useTempRoot) {
tempToken = [NSString stringWithFormat:@"%lld", (long long)([[NSDate date] timeIntervalSince1970] * 1000)];
NSString *tmpName = [NSString stringWithFormat:@"%@__tmp_%@", skinId, tempToken];
workingRoot = [skinsRoot stringByAppendingPathComponent:tmpName];
workingIconsDir = [workingRoot stringByAppendingPathComponent:@"icons"];
workingBgPath = [workingRoot stringByAppendingPathComponent:@"background.png"];
[fm removeItemAtPath:workingRoot error:nil];
}
NSLog(@"⬇️[SkinBridge] request id=%@ force=%d cached=%d zip=%@",
skinId, forceDownload, hasCachedAssets, zipURL);
dispatch_group_t group = dispatch_group_create(); dispatch_group_t group = dispatch_group_create();
__block BOOL zipOK = YES; __block BOOL zipOK = YES;
@@ -265,8 +286,8 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
__block NSError *innerError = nil; __block NSError *innerError = nil;
#if __has_include(<SSZipArchive/SSZipArchive.h>) #if __has_include(<SSZipArchive/SSZipArchive.h>)
// zip_url Zip // zip_url Zip
if (!hasCachedAssets && zipURL.length > 0) { if ((forceDownload || !hasCachedAssets) && zipURL.length > 0) {
dispatch_group_enter(group); dispatch_group_enter(group);
void (^handleZipData)(NSData *) = ^(NSData *data) { void (^handleZipData)(NSData *) = ^(NSData *data) {
@@ -277,15 +298,17 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
code:KBSkinBridgeErrorZipMissing code:KBSkinBridgeErrorZipMissing
userInfo:@{NSLocalizedDescriptionKey: @"Zip data is empty"}]; userInfo:@{NSLocalizedDescriptionKey: @"Zip data is empty"}];
} }
NSLog(@"❌[SkinBridge] zip data empty id=%@", skinId);
dispatch_group_leave(group); dispatch_group_leave(group);
return; return;
} }
NSLog(@"📦[SkinBridge] unzip start id=%@ temp=%d", skinId, useTempRoot);
// Zip // Zip
[fm createDirectoryAtPath:skinRoot [fm createDirectoryAtPath:workingRoot
withIntermediateDirectories:YES withIntermediateDirectories:YES
attributes:nil attributes:nil
error:NULL]; error:NULL];
NSString *zipPath = [skinRoot stringByAppendingPathComponent:@"skin.zip"]; NSString *zipPath = [workingRoot stringByAppendingPathComponent:@"skin.zip"];
if (![data writeToFile:zipPath atomically:YES]) { if (![data writeToFile:zipPath atomically:YES]) {
zipOK = NO; zipOK = NO;
if (!innerError) { if (!innerError) {
@@ -293,13 +316,14 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
code:KBSkinBridgeErrorUnzipFailed code:KBSkinBridgeErrorUnzipFailed
userInfo:@{NSLocalizedDescriptionKey: @"Failed to write zip file"}]; userInfo:@{NSLocalizedDescriptionKey: @"Failed to write zip file"}];
} }
NSLog(@"❌[SkinBridge] zip write failed id=%@", skinId);
dispatch_group_leave(group); dispatch_group_leave(group);
return; return;
} }
NSError *unzipError = nil; NSError *unzipError = nil;
BOOL ok = [SSZipArchive unzipFileAtPath:zipPath BOOL ok = [SSZipArchive unzipFileAtPath:zipPath
toDestination:skinRoot toDestination:workingRoot
overwrite:YES overwrite:YES
password:nil password:nil
error:&unzipError]; error:&unzipError];
@@ -311,24 +335,22 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
code:KBSkinBridgeErrorUnzipFailed code:KBSkinBridgeErrorUnzipFailed
userInfo:nil]; userInfo:nil];
} }
NSLog(@"❌[SkinBridge] unzip failed id=%@ error=%@", skinId, unzipError);
dispatch_group_leave(group); dispatch_group_leave(group);
return; return;
} }
// 使 icons
didUnzip = YES;
// //
// Skins/<skinId>/icons Skins/<skinId>/<>/icons // Skins/<skinId>/icons Skins/<skinId>/<>/icons
// icons background.png // icons background.png
BOOL isDir2 = NO; BOOL isDir2 = NO;
NSArray *iconsContent = [fm contentsOfDirectoryAtPath:iconsDir error:NULL]; NSArray *iconsContent = [fm contentsOfDirectoryAtPath:workingIconsDir error:NULL];
BOOL iconsValid = ([fm fileExistsAtPath:iconsDir isDirectory:&isDir2] && isDir2 && iconsContent.count > 0); BOOL iconsValid = ([fm fileExistsAtPath:workingIconsDir isDirectory:&isDir2] && isDir2 && iconsContent.count > 0);
if (!iconsValid) { if (!iconsValid) {
NSArray<NSString *> *subItems = [fm contentsOfDirectoryAtPath:skinRoot error:NULL]; NSArray<NSString *> *subItems = [fm contentsOfDirectoryAtPath:workingRoot error:NULL];
for (NSString *subName in subItems) { for (NSString *subName in subItems) {
if ([subName isEqualToString:@"icons"] || [subName isEqualToString:@"__MACOSX"]) continue; if ([subName isEqualToString:@"icons"] || [subName isEqualToString:@"__MACOSX"]) continue;
NSString *nestedRoot = [skinRoot stringByAppendingPathComponent:subName]; NSString *nestedRoot = [workingRoot stringByAppendingPathComponent:subName];
BOOL isDirNested = NO; BOOL isDirNested = NO;
if (![fm fileExistsAtPath:nestedRoot isDirectory:&isDirNested] || !isDirNested) continue; if (![fm fileExistsAtPath:nestedRoot isDirectory:&isDirNested] || !isDirNested) continue;
@@ -338,14 +360,14 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
NSArray *nestedFiles = [fm contentsOfDirectoryAtPath:nestedIcons error:NULL]; NSArray *nestedFiles = [fm contentsOfDirectoryAtPath:nestedIcons error:NULL];
if (nestedFiles.count > 0) { if (nestedFiles.count > 0) {
// icons // icons
[fm createDirectoryAtPath:iconsDir [fm createDirectoryAtPath:workingIconsDir
withIntermediateDirectories:YES withIntermediateDirectories:YES
attributes:nil attributes:nil
error:NULL]; error:NULL];
// icons // icons
for (NSString *fn in nestedFiles) { for (NSString *fn in nestedFiles) {
NSString *from = [nestedIcons stringByAppendingPathComponent:fn]; NSString *from = [nestedIcons stringByAppendingPathComponent:fn];
NSString *to = [iconsDir stringByAppendingPathComponent:fn]; NSString *to = [workingIconsDir stringByAppendingPathComponent:fn];
[fm removeItemAtPath:to error:nil]; [fm removeItemAtPath:to error:nil];
[fm moveItemAtPath:from toPath:to error:nil]; [fm moveItemAtPath:from toPath:to error:nil];
} }
@@ -355,20 +377,65 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
// background.png skinRoot // background.png skinRoot
NSString *nestedBg = [nestedRoot stringByAppendingPathComponent:@"background.png"]; NSString *nestedBg = [nestedRoot stringByAppendingPathComponent:@"background.png"];
if ([fm fileExistsAtPath:nestedBg]) { if ([fm fileExistsAtPath:nestedBg]) {
[fm removeItemAtPath:bgPath error:nil]; [fm removeItemAtPath:workingBgPath error:nil];
[fm moveItemAtPath:nestedBg toPath:bgPath error:nil]; [fm moveItemAtPath:nestedBg toPath:workingBgPath error:nil];
} }
} }
} }
if (useTempRoot) {
NSString *backupName = [NSString stringWithFormat:@"%@__bak_%@", skinId, (tempToken ?: @"0")];
NSString *backupRoot = [skinsRoot stringByAppendingPathComponent:backupName];
[fm removeItemAtPath:backupRoot error:nil];
NSError *swapError = nil;
BOOL movedOld = NO;
if ([fm fileExistsAtPath:skinRoot]) {
movedOld = [fm moveItemAtPath:skinRoot toPath:backupRoot error:&swapError];
if (!movedOld && swapError) {
zipOK = NO;
if (!innerError) {
innerError = [NSError errorWithDomain:KBSkinBridgeErrorDomain
code:KBSkinBridgeErrorUnzipFailed
userInfo:@{NSLocalizedDescriptionKey: @"Failed to backup old skin"}];
}
NSLog(@"❌[SkinBridge] backup failed id=%@ error=%@", skinId, swapError);
dispatch_group_leave(group);
return;
}
}
BOOL movedNew = [fm moveItemAtPath:workingRoot toPath:skinRoot error:&swapError];
if (!movedNew || swapError) {
zipOK = NO;
if (!innerError) {
innerError = [NSError errorWithDomain:KBSkinBridgeErrorDomain
code:KBSkinBridgeErrorUnzipFailed
userInfo:@{NSLocalizedDescriptionKey: @"Failed to replace skin assets"}];
}
if (movedOld) {
[fm moveItemAtPath:backupRoot toPath:skinRoot error:nil];
}
NSLog(@"❌[SkinBridge] replace failed id=%@ error=%@", skinId, swapError);
dispatch_group_leave(group);
return;
}
if (movedOld) {
[fm removeItemAtPath:backupRoot error:nil];
}
NSLog(@"🧹[SkinBridge] replaced old skin id=%@", skinId);
}
// 使 icons
didUnzip = YES;
NSLog(@"✅[SkinBridge] unzip done id=%@", skinId);
dispatch_group_leave(group); dispatch_group_leave(group);
}; };
#if __has_include("KBNetworkManager.h") #if __has_include("KBNetworkManager.h")
// http/https // http/https
NSLog(@"[SkinBridge] will GET zip: %@", zipURL); NSLog(@"🌐[SkinBridge] will GET zip: %@", zipURL);
[KBHUD show]; [KBHUD showWithStatus:@"正在下载..."];
[[KBNetworkManager shared] GETData:zipURL parameters:nil headers:nil completion:^(NSData *data, NSURLResponse *response, NSError *error) { [[KBNetworkManager shared] GETData:zipURL parameters:nil headers:nil completion:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"[SkinBridge] GET finished, error = %@", error); NSLog(@"🌐[SkinBridge] GET finished id=%@ error=%@", skinId, error);
if (error || data.length == 0) { if (error || data.length == 0) {
zipOK = NO; zipOK = NO;
if (!innerError) { if (!innerError) {
@@ -399,6 +466,9 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
} }
}); });
#endif #endif
} else {
NSLog(@"[SkinBridge] skip download id=%@ force=%d cached=%d zip=%@",
skinId, forceDownload, hasCachedAssets, zipURL);
} }
#else #else
zipOK = NO; zipOK = NO;
@@ -411,11 +481,12 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
dispatch_group_notify(group, dispatch_get_main_queue(), ^{ dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// //
// B // B
BOOL hasAssets = (hasCachedAssets || didUnzip); BOOL hasAssets = (didUnzip || (!forceDownload && hasCachedAssets));
if (!hasAssets) { if (!hasAssets) {
NSError *finalError = innerError ?: [NSError errorWithDomain:KBSkinBridgeErrorDomain NSError *finalError = innerError ?: [NSError errorWithDomain:KBSkinBridgeErrorDomain
code:KBSkinBridgeErrorZipMissing code:KBSkinBridgeErrorZipMissing
userInfo:@{NSLocalizedDescriptionKey: @"Zip resource not available"}]; userInfo:@{NSLocalizedDescriptionKey: @"Zip resource not available"}];
NSLog(@"❌[SkinBridge] apply aborted id=%@ error=%@", skinId, finalError);
if (completion) completion(NO, finalError); if (completion) completion(NO, finalError);
return; return;
} }
@@ -459,6 +530,10 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
userInfo:nil]; userInfo:nil];
} }
if (completion) completion(ok, finalError); if (completion) completion(ok, finalError);
NSLog(@"%@ [SkinBridge] apply %@ id=%@",
(ok ? @"✅" : @"❌"),
(ok ? @"ok" : @"failed"),
skinId);
if (ok) { if (ok) {
NSString *preview = [skinJSON[@"preview"] isKindOfClass:NSString.class] ? skinJSON[@"preview"] : nil; NSString *preview = [skinJSON[@"preview"] isKindOfClass:NSString.class] ? skinJSON[@"preview"] : nil;
[self recordInstalledSkinWithId:skinId [self recordInstalledSkinWithId:skinId
@@ -766,4 +841,3 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
} }
@end @end

View File

@@ -157,6 +157,7 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
- (BOOL)applyTheme:(KBSkinTheme *)theme { - (BOOL)applyTheme:(KBSkinTheme *)theme {
if (!theme) return NO; if (!theme) return NO;
NSLog(@"🎨[SkinManager] apply theme id=%@ name=%@", theme.skinId, theme.name);
// App Group 使 // App Group 使
[self p_saveToStore:theme]; [self p_saveToStore:theme];
// 广 // 广

View File

@@ -19,6 +19,12 @@
"current_lang" = "Current: %@"; "current_lang" = "Current: %@";
"common_back" = "Back"; "common_back" = "Back";
// search
"Recommended Skin" = "Recommended Skin";
"Historical Search" = "Historical Search";
"Search Themes" = "Search Themes";
"Search" = "Search";
// Login & account // Login & account
"Log In" = "Log In"; "Log In" = "Log In";
"Signed in successfully" = "Signed in successfully"; "Signed in successfully" = "Signed in successfully";
@@ -31,7 +37,8 @@
"Invalid login credential" = "Invalid login credential"; "Invalid login credential" = "Invalid login credential";
"No token returned" = "No token returned"; "No token returned" = "No token returned";
"Failed to save login state" = "Failed to save login state"; "Failed to save login state" = "Failed to save login state";
"请切换到主App完成登录" = "Please switch to the main app to finish signing in"; "Sign-in canceled" = "Sign-in canceled";
"Please switch to the key of love app to finish signing in" = "Please switch to the key of love app to finish signing in";
"Continue Via Email" = "Continue Via Email"; "Continue Via Email" = "Continue Via Email";
"Login With Email Password" = "Login With Email Password"; "Login With Email Password" = "Login With Email Password";
"Enter Email Address" = "Enter Email Address"; "Enter Email Address" = "Enter Email Address";
@@ -119,7 +126,7 @@
"Personal" = "Personal"; "Personal" = "Personal";
"My Keyboard" = "My Keyboard"; "My Keyboard" = "My Keyboard";
"Notice" = "Notice"; "Notice" = "Notice";
"Share App" = "Share App"; "invite" = "invite";
"Feedback" = "Feedback"; "Feedback" = "Feedback";
"E-mail" = "E-mail"; "E-mail" = "E-mail";
"Agreement" = "Agreement"; "Agreement" = "Agreement";
@@ -162,31 +169,7 @@
"Log Out" = "Log Out"; "Log Out" = "Log Out";
"Ranking List" = "Ranking List"; "Ranking List" = "Ranking List";
"Persona circle" = "Persona circle"; "Persona circle" = "Persona circle";
"Clear" = "Clear";
// Skin sample names
"极光" = "Aurora";
"雪山" = "Snow Mountain";
"湖面" = "Lake";
// Sample tags & copy
"高情商" = "High EQ";
"暖味拉扯" = "Ambiguous flirting";
"风趣幽默" = "Witty & humorous";
"撩女生" = "Flirt with girls";
"社交惬匿" = "Relaxed socializing";
"情场高手" = "Dating expert";
"一枚暖男" = "Warm-hearted guy";
"聊天搭子" = "Chat buddy";
"表达爱意" = "Express love";
"更多话术" = "More prompts";
"Tap to paste their message" = "Tap to paste their message";
"Tap any conversation to paste, then try any reply style~" = "Tap any conversation to paste, then try any reply style~";
"What are you doing?" = "What are you doing?";
"I'm going to take a shower." = "I'm going to take a shower.";
"Welcome to use the [key of love] keyboard" = "Welcome to use the [key of love] keyboard";
"👋 Welcome to Key of Love Keyboard" = "👋 Welcome to Key of Love Keyboard";
// Payment & IAP // Payment & IAP
"Payment successful" = "Payment successful"; "Payment successful" = "Payment successful";
@@ -194,60 +177,12 @@
"Purchase: %@ Coins %@" = "Purchase: %@ Coins %@"; "Purchase: %@ Coins %@" = "Purchase: %@ Coins %@";
"Pay clicked" = "Pay clicked"; "Pay clicked" = "Pay clicked";
"Points Recharge" = "Points Recharge"; "Points Recharge" = "Points Recharge";
"Recharge" = "Recharge";
"Consumption Record" = "Consumption Record";
"My Points" = "My Points"; "My Points" = "My Points";
"Consumption Details" = "Consumption Details";
"No data" = "No data"; "No data" = "No data";
// Example categories/items
"能力" = "Ability";
"能力2" = "Ability 2";
"爱好" = "Hobby";
"爱好2" = "Hobby 2";
"队友" = "Teammates";
"队友2" = "Teammates 2";
"高级能力" = "Advanced abilities";
"高级爱好" = "Advanced hobbies";
"高级队友" = "Advanced teammates";
// Example fruits etc.
"果冻橙" = "Jelly orange";
"芒果" = "Mango";
"有机水果卷心菜" = "Organic cabbage";
"水果萝卜" = "Fruit radish";
"熟冻帝王蟹" = "Cooked king crab";
"赣南脐橙" = "Gannan navel orange";
"苹果" = "Apple";
"胡萝卜" = "Carrot";
"葡萄" = "Grape";
"西瓜" = "Watermelon";
"小龙虾" = "Crawfish";
"吃烤肉" = "Eat barbecue";
"吃鸡腿肉" = "Eat chicken drumsticks";
"吃牛肉" = "Eat beef";
"各种肉" = "All kinds of meat";
// One Piece sample roles
"【剑士】罗罗诺亚·索隆" = "[Swordsman] Roronoa Zoro";
"【航海士】娜美" = "[Navigator] Nami";
"【狙击手】乌索普" = "[Sniper] Usopp";
"【厨师】香吉士" = "[Cook] Sanji";
"【船医】托尼托尼·乔巴" = "[Doctor] Tony Tony Chopper";
"【船匠】 弗兰奇" = "[Shipwright] Franky";
"【音乐家】布鲁克" = "[Musician] Brook";
"【考古学家】妮可·罗宾" = "[Archaeologist] Nico Robin";
// Rubber-series sample moves
"橡胶火箭" = "Gum-Gum Rocket";
"橡胶火箭炮" = "Gum-Gum Bazooka";
"橡胶机关枪" = "Gum-Gum Gatling";
"橡胶子弹" = "Gum-Gum Bullet";
"橡胶攻城炮" = "Gum-Gum Cannon";
"橡胶象枪" = "Gum-Gum Elephant Gun";
"橡胶象枪乱打" = "Gum-Gum Elephant Gatling";
"橡胶灰熊铳" = "Gum-Gum Grizzly Magnum";
"橡胶雷神象枪" = "Gum-Gum Thor Elephant Gun";
"橡胶猿王枪" = "Gum-Gum King Kong Gun";
"橡胶犀·榴弹炮" = "Gum-Gum Rhino Grenade";
"橡胶大蛇炮" = "Gum-Gum Great Serpent Cannon";
// Misc // Misc
"测试" = "Test"; "测试" = "Test";

View File

@@ -19,6 +19,13 @@
"current_lang" = "当前:%@"; "current_lang" = "当前:%@";
"common_back" = "返回"; "common_back" = "返回";
// search
"Recommended Skin" = "推荐皮肤";
"Historical Search" = "历史搜索";
"Search Themes" = "搜索主题";
"Search" = "搜索";
// 登录与账号(以英文 key 为准) // 登录与账号(以英文 key 为准)
"Log In" = "登录"; "Log In" = "登录";
"Signed in successfully" = "登录成功"; "Signed in successfully" = "登录成功";
@@ -31,7 +38,8 @@
"Invalid login credential" = "无效的登录凭证"; "Invalid login credential" = "无效的登录凭证";
"No token returned" = "未返回 token"; "No token returned" = "未返回 token";
"Failed to save login state" = "保存登录态失败"; "Failed to save login state" = "保存登录态失败";
"请切换到主App完成登录" = "请切换到主App完成登录"; "Sign-in canceled" = "登录已取消";
"Please switch to the key of love app to finish signing in" = "请切换到Key of Love App完成登录";
"Continue Via Email" = "通过邮箱登录"; "Continue Via Email" = "通过邮箱登录";
"Login With Email Password" = "使用邮箱密码登录"; "Login With Email Password" = "使用邮箱密码登录";
"Enter Email Address" = "请输入邮箱地址"; "Enter Email Address" = "请输入邮箱地址";
@@ -120,7 +128,7 @@
"Personal" = "个人"; "Personal" = "个人";
"My Keyboard" = "我的键盘"; "My Keyboard" = "我的键盘";
"Notice" = "通知"; "Notice" = "通知";
"Share App" = "分享app"; "invite" = "邀请";
"Feedback" = "反馈"; "Feedback" = "反馈";
"E-mail" = "联系我们"; "E-mail" = "联系我们";
"Agreement" = "协议"; "Agreement" = "协议";
@@ -162,29 +170,9 @@
"Log Out" = "退出"; "Log Out" = "退出";
"Ranking List" = "排行榜"; "Ranking List" = "排行榜";
"Persona circle" = "圈子"; "Persona circle" = "圈子";
"Clear" = "立刻清空";
// 皮肤示例名称 // 皮肤示例名称
"极光" = "极光";
"雪山" = "雪山";
"湖面" = "湖面";
// 示例标签与文案
"高情商" = "高情商";
"暖味拉扯" = "暖味拉扯";
"风趣幽默" = "风趣幽默";
"撩女生" = "撩女生";
"社交惬匿" = "社交惬匿";
"情场高手" = "情场高手";
"一枚暖男" = "一枚暖男";
"聊天搭子" = "聊天搭子";
"表达爱意" = "表达爱意";
"更多话术" = "更多话术";
"点击粘贴TA的话" = "点击粘贴TA的话";
"点击任一对话去粘贴,选择任意回复方式去试用吧~" = "点击任一对话去粘贴,选择任意回复方式去试用吧~";
"在干嘛?" = "在干嘛?";
"我去洗澡了" = "我去洗澡了";
"🎉 如您遇到其他问题,可点击在线客服帮您解决~" = "🎉 如您遇到其他问题,可点击在线客服帮您解决~";
"👋 欢迎使用『Lovekey 键盘』" = "👋 欢迎使用『Lovekey 键盘』";
// 支付与内购(英文 key // 支付与内购(英文 key
"Payment successful" = "支付成功"; "Payment successful" = "支付成功";
@@ -192,60 +180,12 @@
"Purchase: %@ Coins %@" = "购买:%@ Coins %@"; "Purchase: %@ Coins %@" = "购买:%@ Coins %@";
"Pay clicked" = "点击支付"; "Pay clicked" = "点击支付";
"Points Recharge" = "积分充值"; "Points Recharge" = "积分充值";
"Recharge" = "充值";
"Consumption Record" = "消费记录";
"My Points" = "我的积分"; "My Points" = "我的积分";
"Consumption Details" = "消费明细";
"No data" = "暂无数据"; "No data" = "暂无数据";
// 示例商品/分类
"能力" = "能力";
"能力2" = "能力2";
"爱好" = "爱好";
"爱好2" = "爱好2";
"队友" = "队友";
"队友2" = "队友2";
"高级能力" = "高级能力";
"高级爱好" = "高级爱好";
"高级队友" = "高级队友";
// 示例水果等
"果冻橙" = "果冻橙";
"芒果" = "芒果";
"有机水果卷心菜" = "有机水果卷心菜";
"水果萝卜" = "水果萝卜";
"熟冻帝王蟹" = "熟冻帝王蟹";
"赣南脐橙" = "赣南脐橙";
"苹果" = "苹果";
"胡萝卜" = "胡萝卜";
"葡萄" = "葡萄";
"西瓜" = "西瓜";
"小龙虾" = "小龙虾";
"吃烤肉" = "吃烤肉";
"吃鸡腿肉" = "吃鸡腿肉";
"吃牛肉" = "吃牛肉";
"各种肉" = "各种肉";
// One Piece 示例角色
"【剑士】罗罗诺亚·索隆" = "【剑士】罗罗诺亚·索隆";
"【航海士】娜美" = "【航海士】娜美";
"【狙击手】乌索普" = "【狙击手】乌索普";
"【厨师】香吉士" = "【厨师】香吉士";
"【船医】托尼托尼·乔巴" = "【船医】托尼托尼·乔巴";
"【船匠】 弗兰奇" = "【船匠】 弗兰奇";
"【音乐家】布鲁克" = "【音乐家】布鲁克";
"【考古学家】妮可·罗宾" = "【考古学家】妮可·罗宾";
// 橡胶系列示例文案
"橡胶火箭" = "橡胶火箭";
"橡胶火箭炮" = "橡胶火箭炮";
"橡胶机关枪" = "橡胶机关枪";
"橡胶子弹" = "橡胶子弹";
"橡胶攻城炮" = "橡胶攻城炮";
"橡胶象枪" = "橡胶象枪";
"橡胶象枪乱打" = "橡胶象枪乱打";
"橡胶灰熊铳" = "橡胶灰熊铳";
"橡胶雷神象枪" = "橡胶雷神象枪";
"橡胶猿王枪" = "橡胶猿王枪";
"橡胶犀·榴弹炮" = "橡胶犀·榴弹炮";
"橡胶大蛇炮" = "橡胶大蛇炮";
// 其它 // 其它
"Test" = "测试"; "Test" = "测试";

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>logFormatVersion</key>
<integer>11</integer>
<key>logs</key>
<dict/>
</dict>
</plist>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>logFormatVersion</key>
<integer>11</integer>
<key>logs</key>
<dict/>
</dict>
</plist>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>logFormatVersion</key>
<integer>11</integer>
<key>logs</key>
<dict/>
</dict>
</plist>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>logFormatVersion</key>
<integer>11</integer>
<key>logs</key>
<dict>
<key>800731DD-5595-43EC-B207-003BAB7870CE</key>
<dict>
<key>className</key>
<string>IDECommandLineBuildLog</string>
<key>documentTypeString</key>
<string>&lt;nil&gt;</string>
<key>domainType</key>
<string>Xcode.IDEActivityLogDomainType.BuildLog</string>
<key>fileName</key>
<string>800731DD-5595-43EC-B207-003BAB7870CE.xcactivitylog</string>
<key>hasPrimaryLog</key>
<true/>
<key>primaryObservable</key>
<dict>
<key>highLevelStatus</key>
<string>E</string>
<key>totalNumberOfAnalyzerIssues</key>
<integer>0</integer>
<key>totalNumberOfErrors</key>
<integer>1</integer>
<key>totalNumberOfTestFailures</key>
<integer>0</integer>
<key>totalNumberOfWarnings</key>
<integer>3</integer>
</dict>
<key>signature</key>
<string>Resolve Packages</string>
<key>timeStartedRecording</key>
<real>788359220.39837599</real>
<key>timeStoppedRecording</key>
<real>788359220.51885402</real>
<key>title</key>
<string>Resolve Packages</string>
<key>uniqueIdentifier</key>
<string>800731DD-5595-43EC-B207-003BAB7870CE</string>
</dict>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>logFormatVersion</key>
<integer>11</integer>
<key>logs</key>
<dict/>
</dict>
</plist>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>dateCreated</key>
<date>2025-12-25T12:40:20Z</date>
<key>externalLocations</key>
<array/>
<key>rootId</key>
<dict>
<key>hash</key>
<string>0~z4eUyi7LNyiJgMc9YvhirRPEbAqQY1U8Utz3Zonm5K5gXqlevHrHNamc2oelL32RyN2c9x-M59B2wBAeP3TOAg==</string>
</dict>
<key>storage</key>
<dict>
<key>backend</key>
<string>fileBacked2</string>
<key>compression</key>
<string>standard</string>
</dict>
<key>version</key>
<dict>
<key>major</key>
<integer>3</integer>
<key>minor</key>
<integer>53</integer>
</dict>
</dict>
</plist>

View File

@@ -97,8 +97,6 @@
048908D22EBF611D00FABA60 /* KBHistoryMoreCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908D12EBF611D00FABA60 /* KBHistoryMoreCell.m */; }; 048908D22EBF611D00FABA60 /* KBHistoryMoreCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908D12EBF611D00FABA60 /* KBHistoryMoreCell.m */; };
048908DA2EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908D82EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m */; }; 048908DA2EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908D82EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m */; };
048908DD2EBF67EB00FABA60 /* KBSearchResultVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908DC2EBF67EB00FABA60 /* KBSearchResultVC.m */; }; 048908DD2EBF67EB00FABA60 /* KBSearchResultVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908DC2EBF67EB00FABA60 /* KBSearchResultVC.m */; };
05A1B2D12F5B1A2B3C4D5E60 /* KBSearchVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A1B2C52F5B1A2B3C4D5E60 /* KBSearchVM.m */; };
05A1B2D22F5B1A2B3C4D5E60 /* KBSearchThemeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A1B2C72F5B1A2B3C4D5E60 /* KBSearchThemeModel.m */; };
048908E02EBF73DC00FABA60 /* MySkinVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908DF2EBF73DC00FABA60 /* MySkinVC.m */; }; 048908E02EBF73DC00FABA60 /* MySkinVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908DF2EBF73DC00FABA60 /* MySkinVC.m */; };
048908E32EBF760000FABA60 /* MySkinCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908E22EBF760000FABA60 /* MySkinCell.m */; }; 048908E32EBF760000FABA60 /* MySkinCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908E22EBF760000FABA60 /* MySkinCell.m */; };
048908E32EBF821700FABA60 /* KBSkinDetailVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908E22EBF821700FABA60 /* KBSkinDetailVC.m */; }; 048908E32EBF821700FABA60 /* KBSkinDetailVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048908E22EBF821700FABA60 /* KBSkinDetailVC.m */; };
@@ -131,6 +129,10 @@
0498BD8C2EE69E15006CC1D5 /* KBTagItemModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD8A2EE69E15006CC1D5 /* KBTagItemModel.m */; }; 0498BD8C2EE69E15006CC1D5 /* KBTagItemModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD8A2EE69E15006CC1D5 /* KBTagItemModel.m */; };
0498BD8F2EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */; }; 0498BD8F2EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */; };
0498BD902EE6A3BD006CC1D5 /* 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 */; }; 0498BDDA2EE7ECEA006CC1D5 /* WJXEventSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDD82EE7ECEA006CC1D5 /* WJXEventSource.m */; };
0498BDDE2EE81508006CC1D5 /* KBShopVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDDD2EE81508006CC1D5 /* KBShopVM.m */; }; 0498BDDE2EE81508006CC1D5 /* KBShopVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDDD2EE81508006CC1D5 /* KBShopVM.m */; };
0498BDE12EEA87C9006CC1D5 /* KBShopStyleModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDE02EEA87C8006CC1D5 /* KBShopStyleModel.m */; }; 0498BDE12EEA87C9006CC1D5 /* KBShopStyleModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BDE02EEA87C8006CC1D5 /* KBShopStyleModel.m */; };
@@ -178,8 +180,6 @@
04FC956A2EB05497007BD342 /* KBKeyButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95692EB05497007BD342 /* KBKeyButton.m */; }; 04FC956A2EB05497007BD342 /* KBKeyButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95692EB05497007BD342 /* KBKeyButton.m */; };
04FC956D2EB054B7007BD342 /* KBKeyboardView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC956C2EB054B7007BD342 /* KBKeyboardView.m */; }; 04FC956D2EB054B7007BD342 /* KBKeyboardView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC956C2EB054B7007BD342 /* KBKeyboardView.m */; };
04FC95702EB09516007BD342 /* KBFunctionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC956F2EB09516007BD342 /* KBFunctionView.m */; }; 04FC95702EB09516007BD342 /* KBFunctionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC956F2EB09516007BD342 /* KBFunctionView.m */; };
A1B2C9032FBD000100000001 /* KBBackspaceLongPressHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C9022FBD000100000001 /* KBBackspaceLongPressHandler.m */; };
A1B2C9052FBD000200000001 /* KBBackspaceUndoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C9042FBD000200000001 /* KBBackspaceUndoManager.m */; };
04FC95732EB09570007BD342 /* KBFunctionBarView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95722EB09570007BD342 /* KBFunctionBarView.m */; }; 04FC95732EB09570007BD342 /* KBFunctionBarView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95722EB09570007BD342 /* KBFunctionBarView.m */; };
04FC95762EB095DE007BD342 /* KBFunctionPasteView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95752EB095DE007BD342 /* KBFunctionPasteView.m */; }; 04FC95762EB095DE007BD342 /* KBFunctionPasteView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95752EB095DE007BD342 /* KBFunctionPasteView.m */; };
04FC95792EB09BC8007BD342 /* KBKeyBoardMainView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95782EB09BC8007BD342 /* KBKeyBoardMainView.m */; }; 04FC95792EB09BC8007BD342 /* KBKeyBoardMainView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95782EB09BC8007BD342 /* KBKeyBoardMainView.m */; };
@@ -207,20 +207,31 @@
04FEDC222F00020000999999 /* KBKeyboardSubscriptionProduct.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FEDC212F00020000999999 /* KBKeyboardSubscriptionProduct.m */; }; 04FEDC222F00020000999999 /* KBKeyboardSubscriptionProduct.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FEDC212F00020000999999 /* KBKeyboardSubscriptionProduct.m */; };
04FEDC322F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FEDC312F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.m */; }; 04FEDC322F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FEDC312F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.m */; };
04FEDC422F00040000999999 /* KBKeyboardSubscriptionFeatureMarqueeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FEDC412F00040000999999 /* KBKeyboardSubscriptionFeatureMarqueeView.m */; }; 04FEDC422F00040000999999 /* KBKeyboardSubscriptionFeatureMarqueeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FEDC412F00040000999999 /* KBKeyboardSubscriptionFeatureMarqueeView.m */; };
05A1B2D12F5B1A2B3C4D5E60 /* KBSearchVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A1B2C52F5B1A2B3C4D5E60 /* KBSearchVM.m */; };
05A1B2D22F5B1A2B3C4D5E60 /* KBSearchThemeModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A1B2C72F5B1A2B3C4D5E60 /* KBSearchThemeModel.m */; };
471CAD3574798685B72ADD55 /* KBMyTheme.m in Sources */ = {isa = PBXBuildFile; fileRef = 180D662EC4DB3A7FFF83FF18 /* KBMyTheme.m */; }; 471CAD3574798685B72ADD55 /* KBMyTheme.m in Sources */ = {isa = PBXBuildFile; fileRef = 180D662EC4DB3A7FFF83FF18 /* KBMyTheme.m */; };
49B63DBAEE9076C591E13D68 /* KBShopThemeTagModel.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A844CD2D8584596DBE6316 /* KBShopThemeTagModel.m */; }; 49B63DBAEE9076C591E13D68 /* KBShopThemeTagModel.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A844CD2D8584596DBE6316 /* KBShopThemeTagModel.m */; };
550CB2630FA4A7B4B9782EFA /* KBMyTheme.m in Sources */ = {isa = PBXBuildFile; fileRef = 180D662EC4DB3A7FFF83FF18 /* KBMyTheme.m */; }; 550CB2630FA4A7B4B9782EFA /* KBMyTheme.m in Sources */ = {isa = PBXBuildFile; fileRef = 180D662EC4DB3A7FFF83FF18 /* KBMyTheme.m */; };
7A36414DFDA5BEC9B7D2E318 /* Pods_CustomKeyboard.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2C1092FB2B452F95B15D4263 /* Pods_CustomKeyboard.framework */; }; 7A36414DFDA5BEC9B7D2E318 /* Pods_CustomKeyboard.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2C1092FB2B452F95B15D4263 /* Pods_CustomKeyboard.framework */; };
A1B2C3D42EB0A0A100000001 /* KBFunctionTagCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D32EB0A0A100000001 /* KBFunctionTagCell.m */; }; A1B2C3D42EB0A0A100000001 /* KBFunctionTagCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D32EB0A0A100000001 /* KBFunctionTagCell.m */; };
A1B2C3E22EB0C0A100000001 /* KBNetworkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3E12EB0C0A100000001 /* KBNetworkManager.m */; }; A1B2C3E22EB0C0A100000001 /* KBNetworkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3E12EB0C0A100000001 /* KBNetworkManager.m */; };
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 */; };
A1B2C3F42EB35A9900000001 /* KBFullAccessGuideView.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3F22EB35A9900000001 /* KBFullAccessGuideView.m */; }; A1B2C3F42EB35A9900000001 /* KBFullAccessGuideView.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3F22EB35A9900000001 /* KBFullAccessGuideView.m */; };
A1B2C4002EB4A0A100000003 /* KBAuthManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C4002EB4A0A100000002 /* KBAuthManager.m */; }; A1B2C4002EB4A0A100000003 /* KBAuthManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C4002EB4A0A100000002 /* KBAuthManager.m */; };
A1B2C4002EB4A0A100000004 /* KBAuthManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C4002EB4A0A100000002 /* KBAuthManager.m */; }; A1B2C4002EB4A0A100000004 /* KBAuthManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C4002EB4A0A100000002 /* KBAuthManager.m */; };
A1B2C4202EB4B7A100000001 /* KBKeyboardPermissionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C4222EB4B7A100000001 /* KBKeyboardPermissionManager.m */; }; A1B2C4202EB4B7A100000001 /* KBKeyboardPermissionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C4222EB4B7A100000001 /* KBKeyboardPermissionManager.m */; };
A1B2C4212EB4B7A100000001 /* KBKeyboardPermissionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C4222EB4B7A100000001 /* KBKeyboardPermissionManager.m */; }; A1B2C4212EB4B7A100000001 /* KBKeyboardPermissionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C4222EB4B7A100000001 /* KBKeyboardPermissionManager.m */; };
A1B2C9032FBD000100000001 /* KBBackspaceLongPressHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C9022FBD000100000001 /* KBBackspaceLongPressHandler.m */; };
A1B2C9052FBD000200000001 /* KBBackspaceUndoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C9042FBD000200000001 /* KBBackspaceUndoManager.m */; };
A1B2C9092FBD000200000005 /* KBInputBufferManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C9082FBD000200000004 /* KBInputBufferManager.m */; };
A1B2D7022EB8C00100000001 /* KBLangTestVC.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2D7012EB8C00100000001 /* KBLangTestVC.m */; }; A1B2D7022EB8C00100000001 /* KBLangTestVC.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2D7012EB8C00100000001 /* KBLangTestVC.m */; };
A1B2E1012EBC7AAA00000001 /* KBTopThreeView.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2E0022EBC7AAA00000001 /* KBTopThreeView.m */; }; A1B2E1012EBC7AAA00000001 /* KBTopThreeView.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2E0022EBC7AAA00000001 /* KBTopThreeView.m */; };
A1B2E1022EBC7AAA00000001 /* HomeHotCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2E0042EBC7AAA00000001 /* HomeHotCell.m */; }; A1B2E1022EBC7AAA00000001 /* HomeHotCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2E0042EBC7AAA00000001 /* HomeHotCell.m */; };
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 */; };
EB72B60040437E3C0A4890FC /* KBShopThemeDetailModel.m in Sources */ = {isa = PBXBuildFile; fileRef = B9F60894E529C3EDAF6BAC3D /* KBShopThemeDetailModel.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 */; }; ECC9EE02174D86E8D792472F /* Pods_keyBoard.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 967065BB5230E43F293B3AF9 /* Pods_keyBoard.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@@ -392,10 +403,6 @@
048908D82EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UICollectionViewLeftAlignedLayout.m; sourceTree = "<group>"; }; 048908D82EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UICollectionViewLeftAlignedLayout.m; sourceTree = "<group>"; };
048908DB2EBF67EB00FABA60 /* KBSearchResultVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSearchResultVC.h; sourceTree = "<group>"; }; 048908DB2EBF67EB00FABA60 /* KBSearchResultVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSearchResultVC.h; sourceTree = "<group>"; };
048908DC2EBF67EB00FABA60 /* KBSearchResultVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSearchResultVC.m; sourceTree = "<group>"; }; 048908DC2EBF67EB00FABA60 /* KBSearchResultVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSearchResultVC.m; sourceTree = "<group>"; };
05A1B2C42F5B1A2B3C4D5E60 /* KBSearchVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSearchVM.h; sourceTree = "<group>"; };
05A1B2C52F5B1A2B3C4D5E60 /* KBSearchVM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSearchVM.m; sourceTree = "<group>"; };
05A1B2C62F5B1A2B3C4D5E60 /* KBSearchThemeModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSearchThemeModel.h; sourceTree = "<group>"; };
05A1B2C72F5B1A2B3C4D5E60 /* KBSearchThemeModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSearchThemeModel.m; sourceTree = "<group>"; };
048908DE2EBF73DC00FABA60 /* MySkinVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinVC.h; sourceTree = "<group>"; }; 048908DE2EBF73DC00FABA60 /* MySkinVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinVC.h; sourceTree = "<group>"; };
048908DF2EBF73DC00FABA60 /* MySkinVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MySkinVC.m; sourceTree = "<group>"; }; 048908DF2EBF73DC00FABA60 /* MySkinVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MySkinVC.m; sourceTree = "<group>"; };
048908E12EBF760000FABA60 /* MySkinCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinCell.h; sourceTree = "<group>"; }; 048908E12EBF760000FABA60 /* MySkinCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MySkinCell.h; sourceTree = "<group>"; };
@@ -453,6 +460,10 @@
0498BD8A2EE69E15006CC1D5 /* KBTagItemModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBTagItemModel.m; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 0498BDDC2EE81508006CC1D5 /* KBShopVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBShopVM.h; sourceTree = "<group>"; };
@@ -540,10 +551,6 @@
04FC956C2EB054B7007BD342 /* KBKeyboardView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardView.m; sourceTree = "<group>"; }; 04FC956C2EB054B7007BD342 /* KBKeyboardView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardView.m; sourceTree = "<group>"; };
04FC956E2EB09516007BD342 /* KBFunctionView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFunctionView.h; sourceTree = "<group>"; }; 04FC956E2EB09516007BD342 /* KBFunctionView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFunctionView.h; sourceTree = "<group>"; };
04FC956F2EB09516007BD342 /* KBFunctionView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFunctionView.m; sourceTree = "<group>"; }; 04FC956F2EB09516007BD342 /* KBFunctionView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFunctionView.m; sourceTree = "<group>"; };
A1B2C9012FBD000100000001 /* KBBackspaceLongPressHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBBackspaceLongPressHandler.h; sourceTree = "<group>"; };
A1B2C9022FBD000100000001 /* KBBackspaceLongPressHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBBackspaceLongPressHandler.m; sourceTree = "<group>"; };
A1B2C9032FBD000200000001 /* KBBackspaceUndoManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBBackspaceUndoManager.h; sourceTree = "<group>"; };
A1B2C9042FBD000200000001 /* KBBackspaceUndoManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBBackspaceUndoManager.m; sourceTree = "<group>"; };
04FC95712EB09570007BD342 /* KBFunctionBarView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFunctionBarView.h; sourceTree = "<group>"; }; 04FC95712EB09570007BD342 /* KBFunctionBarView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFunctionBarView.h; sourceTree = "<group>"; };
04FC95722EB09570007BD342 /* KBFunctionBarView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFunctionBarView.m; sourceTree = "<group>"; }; 04FC95722EB09570007BD342 /* KBFunctionBarView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFunctionBarView.m; sourceTree = "<group>"; };
04FC95742EB095DE007BD342 /* KBFunctionPasteView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFunctionPasteView.h; sourceTree = "<group>"; }; 04FC95742EB095DE007BD342 /* KBFunctionPasteView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFunctionPasteView.h; sourceTree = "<group>"; };
@@ -600,6 +607,10 @@
04FEDC312F00030000999999 /* KBKeyboardSubscriptionFeatureItemView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardSubscriptionFeatureItemView.m; 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>"; }; 04FEDC402F00040000999999 /* KBKeyboardSubscriptionFeatureMarqueeView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKeyboardSubscriptionFeatureMarqueeView.h; sourceTree = "<group>"; };
04FEDC412F00040000999999 /* KBKeyboardSubscriptionFeatureMarqueeView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardSubscriptionFeatureMarqueeView.m; sourceTree = "<group>"; }; 04FEDC412F00040000999999 /* KBKeyboardSubscriptionFeatureMarqueeView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardSubscriptionFeatureMarqueeView.m; sourceTree = "<group>"; };
05A1B2C42F5B1A2B3C4D5E60 /* KBSearchVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSearchVM.h; sourceTree = "<group>"; };
05A1B2C52F5B1A2B3C4D5E60 /* KBSearchVM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSearchVM.m; sourceTree = "<group>"; };
05A1B2C62F5B1A2B3C4D5E60 /* KBSearchThemeModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSearchThemeModel.h; sourceTree = "<group>"; };
05A1B2C72F5B1A2B3C4D5E60 /* KBSearchThemeModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSearchThemeModel.m; sourceTree = "<group>"; };
180D662EC4DB3A7FFF83FF18 /* KBMyTheme.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyTheme.m; sourceTree = "<group>"; }; 180D662EC4DB3A7FFF83FF18 /* KBMyTheme.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyTheme.m; sourceTree = "<group>"; };
2C1092FB2B452F95B15D4263 /* Pods_CustomKeyboard.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CustomKeyboard.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2C1092FB2B452F95B15D4263 /* Pods_CustomKeyboard.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CustomKeyboard.framework; sourceTree = BUILT_PRODUCTS_DIR; };
35E2B1C590E060D912A4E7F4 /* KBShopThemeTagModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBShopThemeTagModel.h; sourceTree = "<group>"; }; 35E2B1C590E060D912A4E7F4 /* KBShopThemeTagModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBShopThemeTagModel.h; sourceTree = "<group>"; };
@@ -614,18 +625,35 @@
A1B2C3D32EB0A0A100000001 /* KBFunctionTagCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFunctionTagCell.m; sourceTree = "<group>"; }; A1B2C3D32EB0A0A100000001 /* KBFunctionTagCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFunctionTagCell.m; sourceTree = "<group>"; };
A1B2C3E02EB0C0A100000001 /* KBNetworkManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBNetworkManager.h; sourceTree = "<group>"; }; A1B2C3E02EB0C0A100000001 /* KBNetworkManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBNetworkManager.h; sourceTree = "<group>"; };
A1B2C3E12EB0C0A100000001 /* KBNetworkManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBNetworkManager.m; sourceTree = "<group>"; }; A1B2C3E12EB0C0A100000001 /* KBNetworkManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBNetworkManager.m; sourceTree = "<group>"; };
A1B2C3E62F20000000000001 /* KBSuggestionEngine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSuggestionEngine.h; sourceTree = "<group>"; };
A1B2C3E72F20000000000001 /* KBSuggestionEngine.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSuggestionEngine.m; sourceTree = "<group>"; };
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>"; };
A1B2C3F12EB35A9900000001 /* KBFullAccessGuideView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFullAccessGuideView.h; 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>"; }; 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>"; }; A1B2C4002EB4A0A100000001 /* KBAuthManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAuthManager.h; sourceTree = "<group>"; };
A1B2C4002EB4A0A100000002 /* KBAuthManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAuthManager.m; sourceTree = "<group>"; }; A1B2C4002EB4A0A100000002 /* KBAuthManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAuthManager.m; sourceTree = "<group>"; };
A1B2C4222EB4B7A100000001 /* KBKeyboardPermissionManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardPermissionManager.m; sourceTree = "<group>"; }; A1B2C4222EB4B7A100000001 /* KBKeyboardPermissionManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyboardPermissionManager.m; sourceTree = "<group>"; };
A1B2C4232EB4B7A100000001 /* KBKeyboardPermissionManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKeyboardPermissionManager.h; sourceTree = "<group>"; }; A1B2C4232EB4B7A100000001 /* KBKeyboardPermissionManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKeyboardPermissionManager.h; sourceTree = "<group>"; };
A1B2C9012FBD000100000001 /* KBBackspaceLongPressHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBBackspaceLongPressHandler.h; sourceTree = "<group>"; };
A1B2C9022FBD000100000001 /* KBBackspaceLongPressHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBBackspaceLongPressHandler.m; sourceTree = "<group>"; };
A1B2C9032FBD000200000001 /* KBBackspaceUndoManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBBackspaceUndoManager.h; sourceTree = "<group>"; };
A1B2C9042FBD000200000001 /* KBBackspaceUndoManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBBackspaceUndoManager.m; sourceTree = "<group>"; };
A1B2C9072FBD000200000003 /* KBInputBufferManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBInputBufferManager.h; sourceTree = "<group>"; };
A1B2C9082FBD000200000004 /* KBInputBufferManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBInputBufferManager.m; sourceTree = "<group>"; };
A1B2D7002EB8C00100000001 /* KBLangTestVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBLangTestVC.h; sourceTree = "<group>"; }; A1B2D7002EB8C00100000001 /* KBLangTestVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBLangTestVC.h; sourceTree = "<group>"; };
A1B2D7012EB8C00100000001 /* KBLangTestVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBLangTestVC.m; sourceTree = "<group>"; }; A1B2D7012EB8C00100000001 /* KBLangTestVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBLangTestVC.m; sourceTree = "<group>"; };
A1B2E0012EBC7AAA00000001 /* KBTopThreeView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBTopThreeView.h; sourceTree = "<group>"; }; A1B2E0012EBC7AAA00000001 /* KBTopThreeView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBTopThreeView.h; sourceTree = "<group>"; };
A1B2E0022EBC7AAA00000001 /* KBTopThreeView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBTopThreeView.m; sourceTree = "<group>"; }; A1B2E0022EBC7AAA00000001 /* KBTopThreeView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBTopThreeView.m; sourceTree = "<group>"; };
A1B2E0032EBC7AAA00000001 /* HomeHotCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HomeHotCell.h; sourceTree = "<group>"; }; A1B2E0032EBC7AAA00000001 /* HomeHotCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HomeHotCell.h; sourceTree = "<group>"; };
A1B2E0042EBC7AAA00000001 /* HomeHotCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HomeHotCell.m; sourceTree = "<group>"; }; A1B2E0042EBC7AAA00000001 /* HomeHotCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HomeHotCell.m; sourceTree = "<group>"; };
A1F0C1A02F1234567890ABCD /* KBConsumptionRecord.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBConsumptionRecord.h; sourceTree = "<group>"; };
A1F0C1A12F1234567890ABCD /* KBConsumptionRecord.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBConsumptionRecord.m; sourceTree = "<group>"; };
A1F0C1A22F1234567890ABCD /* KBConsumptionRecordCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBConsumptionRecordCell.h; sourceTree = "<group>"; };
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>"; };
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>"; }; 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>"; }; 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>"; }; B9F60894E529C3EDAF6BAC3D /* KBShopThemeDetailModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBShopThemeDetailModel.m; sourceTree = "<group>"; };
@@ -657,6 +685,7 @@
041007D02ECE010100D203BB /* Resource */ = { 041007D02ECE010100D203BB /* Resource */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
A1B2C3EC2F20000000000001 /* kb_words.txt */,
0498BDF42EEC50EE006CC1D5 /* emoji_categories.json */, 0498BDF42EEC50EE006CC1D5 /* emoji_categories.json */,
041007D12ECE012000D203BB /* KBSkinIconMap.strings */, 041007D12ECE012000D203BB /* KBSkinIconMap.strings */,
041007D32ECE012500D203BB /* 002.zip */, 041007D32ECE012500D203BB /* 002.zip */,
@@ -815,6 +844,8 @@
A1B2C9022FBD000100000001 /* KBBackspaceLongPressHandler.m */, A1B2C9022FBD000100000001 /* KBBackspaceLongPressHandler.m */,
A1B2C9032FBD000200000001 /* KBBackspaceUndoManager.h */, A1B2C9032FBD000200000001 /* KBBackspaceUndoManager.h */,
A1B2C9042FBD000200000001 /* KBBackspaceUndoManager.m */, A1B2C9042FBD000200000001 /* KBBackspaceUndoManager.m */,
A1B2C9072FBD000200000003 /* KBInputBufferManager.h */,
A1B2C9082FBD000200000004 /* KBInputBufferManager.m */,
); );
path = Utils; path = Utils;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1086,6 +1117,8 @@
children = ( children = (
04A9FE102EB4D0D20020DB6D /* KBFullAccessManager.h */, 04A9FE102EB4D0D20020DB6D /* KBFullAccessManager.h */,
04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */, 04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */,
A1B2C3E62F20000000000001 /* KBSuggestionEngine.h */,
A1B2C3E72F20000000000001 /* KBSuggestionEngine.m */,
04FEDA9F2EEDB00100123456 /* KBEmojiDataProvider.h */, 04FEDA9F2EEDB00100123456 /* KBEmojiDataProvider.h */,
04FEDAA02EEDB00100123456 /* KBEmojiDataProvider.m */, 04FEDAA02EEDB00100123456 /* KBEmojiDataProvider.m */,
); );
@@ -1154,6 +1187,8 @@
046131132ECF454500A6FADF /* KBKeyPreviewView.m */, 046131132ECF454500A6FADF /* KBKeyPreviewView.m */,
04FC95772EB09BC8007BD342 /* KBKeyBoardMainView.h */, 04FC95772EB09BC8007BD342 /* KBKeyBoardMainView.h */,
04FC95782EB09BC8007BD342 /* KBKeyBoardMainView.m */, 04FC95782EB09BC8007BD342 /* KBKeyBoardMainView.m */,
A1B2C3E82F20000000000001 /* KBSuggestionBarView.h */,
A1B2C3E92F20000000000001 /* KBSuggestionBarView.m */,
04FC956E2EB09516007BD342 /* KBFunctionView.h */, 04FC956E2EB09516007BD342 /* KBFunctionView.h */,
04FC956F2EB09516007BD342 /* KBFunctionView.m */, 04FC956F2EB09516007BD342 /* KBFunctionView.m */,
04FC95712EB09570007BD342 /* KBFunctionBarView.h */, 04FC95712EB09570007BD342 /* KBFunctionBarView.h */,
@@ -1320,8 +1355,12 @@
0498BD8A2EE69E15006CC1D5 /* KBTagItemModel.m */, 0498BD8A2EE69E15006CC1D5 /* KBTagItemModel.m */,
0498BD8D2EE6A3BD006CC1D5 /* KBMyMainModel.h */, 0498BD8D2EE6A3BD006CC1D5 /* KBMyMainModel.h */,
0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */, 0498BD8E2EE6A3BD006CC1D5 /* KBMyMainModel.m */,
A1F0C1C02FABCDEF12345678 /* KBInviteCodeModel.h */,
A1F0C1C12FABCDEF12345678 /* KBInviteCodeModel.m */,
7ECBD0E320F971D0FBEDD7BC /* KBMyTheme.h */, 7ECBD0E320F971D0FBEDD7BC /* KBMyTheme.h */,
180D662EC4DB3A7FFF83FF18 /* KBMyTheme.m */, 180D662EC4DB3A7FFF83FF18 /* KBMyTheme.m */,
A1F0C1A02F1234567890ABCD /* KBConsumptionRecord.h */,
A1F0C1A12F1234567890ABCD /* KBConsumptionRecord.m */,
); );
path = M; path = M;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1333,6 +1372,8 @@
049FB31C2EC21BCD00FAB05D /* KBMyKeyboardCell.m */, 049FB31C2EC21BCD00FAB05D /* KBMyKeyboardCell.m */,
048908E12EBF760000FABA60 /* MySkinCell.h */, 048908E12EBF760000FABA60 /* MySkinCell.h */,
048908E22EBF760000FABA60 /* MySkinCell.m */, 048908E22EBF760000FABA60 /* MySkinCell.m */,
A1F0C1A22F1234567890ABCD /* KBConsumptionRecordCell.h */,
A1F0C1A32F1234567890ABCD /* KBConsumptionRecordCell.m */,
048908E42EBF841B00FABA60 /* KBSkinDetailTagCell.h */, 048908E42EBF841B00FABA60 /* KBSkinDetailTagCell.h */,
048908E52EBF841B00FABA60 /* KBSkinDetailTagCell.m */, 048908E52EBF841B00FABA60 /* KBSkinDetailTagCell.m */,
048908E72EBF843000FABA60 /* KBSkinDetailHeaderCell.h */, 048908E72EBF843000FABA60 /* KBSkinDetailHeaderCell.h */,
@@ -1366,6 +1407,8 @@
049FB2192EC20A9E00FAB05D /* KBMyKeyBoardVC.m */, 049FB2192EC20A9E00FAB05D /* KBMyKeyBoardVC.m */,
048908DE2EBF73DC00FABA60 /* MySkinVC.h */, 048908DE2EBF73DC00FABA60 /* MySkinVC.h */,
048908DF2EBF73DC00FABA60 /* MySkinVC.m */, 048908DF2EBF73DC00FABA60 /* MySkinVC.m */,
A1F0C1A42F1234567890ABCD /* KBConsumptionRecordVC.h */,
A1F0C1A52F1234567890ABCD /* KBConsumptionRecordVC.m */,
049FB2212EC311F900FAB05D /* KBPersonInfoVC.h */, 049FB2212EC311F900FAB05D /* KBPersonInfoVC.h */,
049FB2222EC311F900FAB05D /* KBPersonInfoVC.m */, 049FB2222EC311F900FAB05D /* KBPersonInfoVC.m */,
04791F902ED48010004E8522 /* KBNoticeVC.h */, 04791F902ED48010004E8522 /* KBNoticeVC.h */,
@@ -1612,6 +1655,8 @@
0498BD842EE1B255006CC1D5 /* KBSignUtils.m */, 0498BD842EE1B255006CC1D5 /* KBSignUtils.m */,
047920482EDDCE25004E8522 /* KBUserSessionManager.h */, 047920482EDDCE25004E8522 /* KBUserSessionManager.h */,
047920492EDDCE25004E8522 /* KBUserSessionManager.m */, 047920492EDDCE25004E8522 /* KBUserSessionManager.m */,
A1F0C1D02FACAD0012345678 /* KBMaiPointReporter.h */,
A1F0C1D12FACAD0012345678 /* KBMaiPointReporter.m */,
); );
path = Shared; path = Shared;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1773,6 +1818,7 @@
04A9FE202EB893F10020DB6D /* Localizable.strings in Resources */, 04A9FE202EB893F10020DB6D /* Localizable.strings in Resources */,
041007D42ECE012500D203BB /* 002.zip in Resources */, 041007D42ECE012500D203BB /* 002.zip in Resources */,
041007D22ECE012000D203BB /* KBSkinIconMap.strings in Resources */, 041007D22ECE012000D203BB /* KBSkinIconMap.strings in Resources */,
A1B2C3ED2F20000000000001 /* kb_words.txt in Resources */,
04791FFB2ED5EAB8004E8522 /* fense.zip in Resources */, 04791FFB2ED5EAB8004E8522 /* fense.zip in Resources */,
0498BDF52EEC50EE006CC1D5 /* emoji_categories.json in Resources */, 0498BDF52EEC50EE006CC1D5 /* emoji_categories.json in Resources */,
04791FF72ED5B985004E8522 /* Christmas.zip in Resources */, 04791FF72ED5B985004E8522 /* Christmas.zip in Resources */,
@@ -1877,6 +1923,7 @@
0450AC4A2EF2C3ED00B6AF06 /* KBKeyboardSubscriptionOptionCell.m in Sources */, 0450AC4A2EF2C3ED00B6AF06 /* KBKeyboardSubscriptionOptionCell.m in Sources */,
04A9FE0F2EB481100020DB6D /* KBHUD.m in Sources */, 04A9FE0F2EB481100020DB6D /* KBHUD.m in Sources */,
04C6EADD2EAF8CEB0089C901 /* KBToolBar.m in Sources */, 04C6EADD2EAF8CEB0089C901 /* KBToolBar.m in Sources */,
A1B2C3EB2F20000000000001 /* KBSuggestionBarView.m in Sources */,
04FC95792EB09BC8007BD342 /* KBKeyBoardMainView.m in Sources */, 04FC95792EB09BC8007BD342 /* KBKeyBoardMainView.m in Sources */,
04FEDAB32EEDB05000123456 /* KBEmojiPanelView.m in Sources */, 04FEDAB32EEDB05000123456 /* KBEmojiPanelView.m in Sources */,
04FEDB032EFE000000123456 /* KBEmojiBottomBarView.m in Sources */, 04FEDB032EFE000000123456 /* KBEmojiBottomBarView.m in Sources */,
@@ -1889,6 +1936,7 @@
04FC95762EB095DE007BD342 /* KBFunctionPasteView.m in Sources */, 04FC95762EB095DE007BD342 /* KBFunctionPasteView.m in Sources */,
A1B2C3D42EB0A0A100000001 /* KBFunctionTagCell.m in Sources */, A1B2C3D42EB0A0A100000001 /* KBFunctionTagCell.m in Sources */,
04A9FE1A2EB892460020DB6D /* KBLocalizationManager.m in Sources */, 04A9FE1A2EB892460020DB6D /* KBLocalizationManager.m in Sources */,
A1B2C3EA2F20000000000001 /* KBSuggestionEngine.m in Sources */,
A1B2C3E22EB0C0A100000001 /* KBNetworkManager.m in Sources */, A1B2C3E22EB0C0A100000001 /* KBNetworkManager.m in Sources */,
049FB2352EC45C6A00FAB05D /* NetworkStreamHandler.m in Sources */, 049FB2352EC45C6A00FAB05D /* NetworkStreamHandler.m in Sources */,
04FC956A2EB05497007BD342 /* KBKeyButton.m in Sources */, 04FC956A2EB05497007BD342 /* KBKeyButton.m in Sources */,
@@ -1904,12 +1952,15 @@
04FC95702EB09516007BD342 /* KBFunctionView.m in Sources */, 04FC95702EB09516007BD342 /* KBFunctionView.m in Sources */,
A1B2C9032FBD000100000001 /* KBBackspaceLongPressHandler.m in Sources */, A1B2C9032FBD000100000001 /* KBBackspaceLongPressHandler.m in Sources */,
A1B2C9052FBD000200000001 /* KBBackspaceUndoManager.m in Sources */, A1B2C9052FBD000200000001 /* KBBackspaceUndoManager.m in Sources */,
A1B2C9092FBD000200000005 /* KBInputBufferManager.m in Sources */,
049FB23F2EC4B6EF00FAB05D /* KBULBridgeNotification.m in Sources */, 049FB23F2EC4B6EF00FAB05D /* KBULBridgeNotification.m in Sources */,
04791F992ED49CE7004E8522 /* KBFont.m in Sources */, 04791F992ED49CE7004E8522 /* KBFont.m in Sources */,
04FC956D2EB054B7007BD342 /* KBKeyboardView.m in Sources */, 04FC956D2EB054B7007BD342 /* KBKeyboardView.m in Sources */,
04FC95672EB0546C007BD342 /* KBKey.m in Sources */, 04FC95672EB0546C007BD342 /* KBKey.m in Sources */,
A1B2C3F42EB35A9900000001 /* KBFullAccessGuideView.m in Sources */, A1B2C3F42EB35A9900000001 /* KBFullAccessGuideView.m in Sources */,
0498BD8F2EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */, 0498BD8F2EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */,
A1F0C1C22FABCDEF12345678 /* KBInviteCodeModel.m in Sources */,
A1F0C1D22FACAD0012345678 /* KBMaiPointReporter.m in Sources */,
04FEDC222F00020000999999 /* KBKeyboardSubscriptionProduct.m in Sources */, 04FEDC222F00020000999999 /* KBKeyboardSubscriptionProduct.m in Sources */,
0450AA742EF013D000B6AF06 /* KBEmojiCollectionCell.m in Sources */, 0450AA742EF013D000B6AF06 /* KBEmojiCollectionCell.m in Sources */,
550CB2630FA4A7B4B9782EFA /* KBMyTheme.m in Sources */, 550CB2630FA4A7B4B9782EFA /* KBMyTheme.m in Sources */,
@@ -1973,7 +2024,12 @@
04A9FE1B2EB892460020DB6D /* KBLocalizationManager.m in Sources */, 04A9FE1B2EB892460020DB6D /* KBLocalizationManager.m in Sources */,
048908BC2EBE1FCB00FABA60 /* BaseViewController.m in Sources */, 048908BC2EBE1FCB00FABA60 /* BaseViewController.m in Sources */,
0498BD902EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */, 0498BD902EE6A3BD006CC1D5 /* KBMyMainModel.m in Sources */,
A1F0C1C32FABCDEF12345678 /* KBInviteCodeModel.m in Sources */,
A1F0C1D32FACAD0012345678 /* KBMaiPointReporter.m in Sources */,
471CAD3574798685B72ADD55 /* KBMyTheme.m in Sources */, 471CAD3574798685B72ADD55 /* KBMyTheme.m in Sources */,
A1F0C1B12F1234567890ABCD /* KBConsumptionRecord.m in Sources */,
A1F0C1B22F1234567890ABCD /* KBConsumptionRecordCell.m in Sources */,
A1F0C1B32F1234567890ABCD /* KBConsumptionRecordVC.m in Sources */,
04FC95D72EB1EA16007BD342 /* BaseTableView.m in Sources */, 04FC95D72EB1EA16007BD342 /* BaseTableView.m in Sources */,
0498BD712EE02A41006CC1D5 /* KBForgetPwdNewPwdVC.m in Sources */, 0498BD712EE02A41006CC1D5 /* KBForgetPwdNewPwdVC.m in Sources */,
048908EF2EBF861800FABA60 /* KBSkinSectionTitleCell.m in Sources */, 048908EF2EBF861800FABA60 /* KBSkinSectionTitleCell.m in Sources */,

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "my_chongzhi_bg@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "my_chongzhi_bg@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "my_record_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "my_record_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -5,12 +5,12 @@
"scale" : "1x" "scale" : "1x"
}, },
{ {
"filename" : "placeholder_image_icon@2x.png", "filename" : "切图 232@2x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"filename" : "placeholder_image_icon@3x.png", "filename" : "切图 232@3x.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x" "scale" : "3x"
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "shop_goumai_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "shop_goumai_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -11,6 +11,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface UIColor (Extension) @interface UIColor (Extension)
+ (UIColor *)colorWithHex:(int)hexValue; + (UIColor *)colorWithHex:(int)hexValue;
+ (nullable UIColor *)colorWithHexString:(NSString *)hexString;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@@ -20,4 +20,25 @@
{ {
return [UIColor colorWithHex:hexValue alpha:1.0]; return [UIColor colorWithHex:hexValue alpha:1.0];
} }
+ (UIColor *)colorWithHexString:(NSString *)hexString
{
if (hexString.length == 0) { return nil; }
NSString *clean = [[hexString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] uppercaseString];
if ([clean hasPrefix:@"#"]) {
clean = [clean substringFromIndex:1];
} else if ([clean hasPrefix:@"0X"]) {
clean = [clean substringFromIndex:2];
}
if (clean.length != 6 && clean.length != 8) { return nil; }
unsigned long long value = 0;
NSScanner *scanner = [NSScanner scannerWithString:clean];
if (![scanner scanHexLongLong:&value]) { return nil; }
if (clean.length == 6) {
return [UIColor colorWithHex:(int)value];
}
CGFloat alpha = ((value & 0xFF000000) >> 24) / 255.0;
int rgb = (int)(value & 0xFFFFFF);
return [UIColor colorWithHex:rgb alpha:alpha];
}
@end @end

View File

@@ -15,6 +15,12 @@ static inline SDWebImageOptions KBWebImageDefaultOptions(void) {
SDWebImageProgressiveLoad; // SDWebImageProgressiveLoad; //
} }
static inline void KBWebImageEnsureIndicator(UIImageView *imageView) {
if (!imageView.sd_imageIndicator) {
imageView.sd_imageIndicator = SDWebImageActivityIndicator.mediumIndicator;
}
}
static inline NSURL *_KBURL(id url) { static inline NSURL *_KBURL(id url) {
if (!url) return nil; if (!url) return nil;
if ([url isKindOfClass:NSURL.class]) return url; if ([url isKindOfClass:NSURL.class]) return url;
@@ -32,6 +38,7 @@ static inline NSURL *_KBURL(id url) {
NSURL *u = _KBURL(url); NSURL *u = _KBURL(url);
// //
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
KBWebImageEnsureIndicator(self);
[self sd_setImageWithURL:u [self sd_setImageWithURL:u
placeholderImage:placeholder placeholderImage:placeholder
options:options options:options
@@ -66,6 +73,7 @@ static inline NSURL *_KBURL(id url) {
} }
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
KBWebImageEnsureIndicator(self);
[self sd_setImageWithURL:u [self sd_setImageWithURL:u
placeholderImage:placeholder placeholderImage:placeholder
options:options options:options
@@ -106,6 +114,7 @@ static inline NSURL *_KBURL(id url) {
NSDictionary *context = @{ SDWebImageContextImageTransformer : pipeline }; NSDictionary *context = @{ SDWebImageContextImageTransformer : pipeline };
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
KBWebImageEnsureIndicator(self);
[self sd_setImageWithURL:u [self sd_setImageWithURL:u
placeholderImage:placeholder placeholderImage:placeholder
options:options options:options

View File

@@ -58,6 +58,12 @@ static __weak UIView *sContainerView = nil; // 缺省承载视图(
if (!hostView) { return nil; } if (!hostView) { return nil; }
MBProgressHUD *hud = sHUD; MBProgressHUD *hud = sHUD;
if (hud && hud.superview != hostView) {
// Host view changed or HUD was removed; discard and recreate.
[hud removeFromSuperview];
hud = nil;
sHUD = nil;
}
if (!hud) { if (!hud) {
hud = [MBProgressHUD showHUDAddedTo:hostView animated:YES]; hud = [MBProgressHUD showHUDAddedTo:hostView animated:YES];
sHUD = hud; sHUD = hud;
@@ -70,6 +76,9 @@ static __weak UIView *sContainerView = nil; // 缺省承载视图(
hud.margin = 16; hud.margin = 16;
hud.label.numberOfLines = 0; hud.label.numberOfLines = 0;
hud.detailsLabel.numberOfLines = 0; hud.detailsLabel.numberOfLines = 0;
} else {
// If a previous hide is in progress, bring it back.
[hud showAnimated:YES];
} }
// show // show
[self applyMaskType:mask hud:hud]; [self applyMaskType:mask hud:hud];

View File

@@ -228,9 +228,10 @@
self.passwordTextField.text.length); self.passwordTextField.text.length);
NSString *email = self.emailTextField.text ? self.emailTextField.text : @""; NSString *email = self.emailTextField.text ? self.emailTextField.text : @"";
NSString *password = self.passwordTextField.text ? self.passwordTextField.text : @""; NSString *password = self.passwordTextField.text ? self.passwordTextField.text : @"";
KBWeakSelf;
[self.loginVM emailLoginEmail:email password:password WithCompletion:^(BOOL success, NSError * _Nullable error) { [self.loginVM emailLoginEmail:email password:password WithCompletion:^(BOOL success, NSError * _Nullable error) {
if (success) { if (success) {
[weakSelf report:email];
[[NSUserDefaults standardUserDefaults] setValue:email forKey:KBUserEmailKey]; [[NSUserDefaults standardUserDefaults] setValue:email forKey:KBUserEmailKey];
[[NSUserDefaults standardUserDefaults] synchronize]; [[NSUserDefaults standardUserDefaults] synchronize];
id<UIApplicationDelegate> appDelegate = UIApplication.sharedApplication.delegate; id<UIApplicationDelegate> appDelegate = UIApplication.sharedApplication.delegate;
@@ -248,6 +249,11 @@
}]; }];
} }
- (void)report:(NSString *)account{
[[KBMaiPointReporter sharedReporter] reportNewAccountWithType:@"登录用户" account:account completion:^(BOOL success, NSError * _Nullable error) {
KBLOG(@"====");
}];
}
- (void)onTapForgotPassword { - (void)onTapForgotPassword {
KBLOG(@"KBEmailLoginVC onTapForgotPassword"); KBLOG(@"KBEmailLoginVC onTapForgotPassword");

View File

@@ -100,6 +100,7 @@
self.params[@"gender"] = genderNumber; self.params[@"gender"] = genderNumber;
} }
NSString *email = self.params[@"mailAddress"] ? self.params[@"mailAddress"] : @""; NSString *email = self.params[@"mailAddress"] ? self.params[@"mailAddress"] : @"";
KBWeakSelf
[self.loginVM emailRegisterParams:self.params withCompletion:^(BOOL success, NSError * _Nullable error) { [self.loginVM emailRegisterParams:self.params withCompletion:^(BOOL success, NSError * _Nullable error) {
if (success) { if (success) {
[KBHUD showInfo:KBLocalized(@"Signed in successfully")]; [KBHUD showInfo:KBLocalized(@"Signed in successfully")];
@@ -107,6 +108,7 @@
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[[NSUserDefaults standardUserDefaults] setValue:email forKey:KBUserEmailKey]; [[NSUserDefaults standardUserDefaults] setValue:email forKey:KBUserEmailKey];
[[NSUserDefaults standardUserDefaults] synchronize]; [[NSUserDefaults standardUserDefaults] synchronize];
[weakSelf report:email];
KBEmailLoginVC *vc = [[KBEmailLoginVC alloc] init]; KBEmailLoginVC *vc = [[KBEmailLoginVC alloc] init];
[KB_CURRENT_NAV pushViewController:vc animated:true]; [KB_CURRENT_NAV pushViewController:vc animated:true];
// id<UIApplicationDelegate> appDelegate = UIApplication.sharedApplication.delegate; // id<UIApplicationDelegate> appDelegate = UIApplication.sharedApplication.delegate;
@@ -129,6 +131,12 @@
}]; }];
} }
- (void)report:(NSString *)account{
[[KBMaiPointReporter sharedReporter] reportNewAccountWithType:@"注册账号" account:account completion:^(BOOL success, NSError * _Nullable error) {
}];
}
- (nullable NSNumber *)kb_localGenderParamIfAvailable { - (nullable NSNumber *)kb_localGenderParamIfAvailable {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
// //

View File

@@ -106,7 +106,16 @@ static NSString * const kKBAppleUserIdentifierKey = @"com.company.keyboard.apple
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)) { - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)) {
if (self.completion) { if (self.completion) {
self.completion(nil, error); NSError *finalError = error;
if (@available(iOS 13.0, *)) {
if ([error.domain isEqualToString:ASAuthorizationErrorDomain] && error.code == ASAuthorizationErrorCanceled) {
finalError = [NSError errorWithDomain:error.domain
code:error.code
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Sign-in canceled")
}];
}
}
// self.completion(nil, finalError);
} }
self.completion = nil; self.completion = nil;
self.presentingVC = nil; self.presentingVC = nil;

View File

@@ -42,6 +42,13 @@
if (completion) completion(NO); if (completion) completion(NO);
return; return;
} }
BOOL forceDownload = NO;
id forceValue = skinJSON[@"force_download"];
if ([forceValue respondsToSelector:@selector(boolValue)]) {
forceDownload = [forceValue boolValue];
}
NSLog(@"🧩[SkinService] apply mode=%lu id=%@ force=%d",
(unsigned long)mode, skinJSON[@"id"], forceDownload);
// // 1. & 访 // // 1. & 访
// KBKeyboardPermissionManager *perm = [KBKeyboardPermissionManager shared]; // KBKeyboardPermissionManager *perm = [KBKeyboardPermissionManager shared];
@@ -282,7 +289,7 @@
zipURL:zipName zipURL:zipName
themeJSON:themeJSON]; themeJSON:themeJSON];
} }
[KBHUD showInfo:(ok ? KBLocalized(@"已应用,切到键盘查看") : KBLocalized(@"应用皮肤失败"))]; [KBHUD showInfo:(ok ? KBLocalized(@"Applied. Switch to the keyboard to view.") : KBLocalized(@"Application to the skin failed"))];
}); });
} }

View File

@@ -0,0 +1,25 @@
//
// KBConsumptionRecord.h
// keyBoard
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// 消费/购买记录模型 1 消费 2充值
typedef NS_ENUM(NSInteger, KBConsumptionRecordType) {
KBConsumptionRecordTypeConsumption = 1,
KBConsumptionRecordTypeRecharge = 2,
};
@interface KBConsumptionRecord : NSObject
@property (nonatomic, strong, nullable) NSNumber *amount;
@property (nonatomic, assign) KBConsumptionRecordType type;
@property (nonatomic, strong, nullable) NSNumber *beforeBalance;
@property (nonatomic, strong, nullable) NSNumber *afterBalance;
@property (nonatomic, copy, nullable) NSString *kbdescription;
@property (nonatomic, copy, nullable) NSString *createdAt;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,16 @@
//
// KBConsumptionRecord.m
// keyBoard
//
#import "KBConsumptionRecord.h"
@implementation KBConsumptionRecord
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
// JSON: { "id": 0, "tagName": "xxx" }
// Model: tagId / tagName
return @{
@"kbdescription" : @"description",
};
}
@end

View File

@@ -0,0 +1,20 @@
//
// KBInviteCodeModel.h
// keyBoard
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// 邀请码信息
@interface KBInviteCodeModel : NSObject
@property (nonatomic, copy, nullable) NSString *inviteCode;
@property (nonatomic, copy, nullable) NSString *h5Link;
@property (nonatomic, assign) NSInteger status;
@property (nonatomic, assign) NSInteger usedCount;
@property (nonatomic, strong, nullable) NSNumber *maxUses;
@property (nonatomic, copy, nullable) NSString *expiresAt;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,17 @@
//
// KBInviteCodeModel.m
// keyBoard
//
#import "KBInviteCodeModel.h"
#import <MJExtension/MJExtension.h>
@implementation KBInviteCodeModel
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
return @{
@"inviteCode": @"code"
};
}
@end

View File

@@ -0,0 +1,16 @@
//
// KBConsumptionRecordCell.h
// keyBoard
//
#import "BaseCell.h"
@class KBConsumptionRecord;
NS_ASSUME_NONNULL_BEGIN
@interface KBConsumptionRecordCell : BaseCell
+ (NSString *)reuseId;
- (void)configWithRecord:(KBConsumptionRecord *)record;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,123 @@
//
// KBConsumptionRecordCell.m
// keyBoard
//
#import "KBConsumptionRecordCell.h"
#import "KBConsumptionRecord.h"
#import <Masonry/Masonry.h>
#import "UIColor+Extension.h"
@interface KBConsumptionRecordCell ()
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UILabel *timeLabel;
@property (nonatomic, strong) UILabel *amountLabel;
@property (nonatomic, strong) UIView *lineView;
@end
@implementation KBConsumptionRecordCell
- (void)setupUI {
self.contentView.backgroundColor = [UIColor whiteColor];
[self.contentView addSubview:self.titleLabel];
[self.contentView addSubview:self.timeLabel];
[self.contentView addSubview:self.amountLabel];
[self.contentView addSubview:self.lineView];
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.contentView).offset(16);
make.top.equalTo(self.contentView).offset(14);
make.right.lessThanOrEqualTo(self.amountLabel.mas_left).offset(-12);
}];
[self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.titleLabel);
make.top.equalTo(self.titleLabel.mas_bottom).offset(6);
make.bottom.equalTo(self.contentView).offset(-14);
make.right.lessThanOrEqualTo(self.amountLabel.mas_left).offset(-12);
}];
[self.amountLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.contentView).offset(-16);
make.centerY.equalTo(self.titleLabel);
}];
[self.lineView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.contentView).offset(16);
make.right.equalTo(self.contentView).offset(-16);
make.bottom.equalTo(self.contentView);
make.height.mas_equalTo(KB_ONE_PIXEL);
}];
}
- (void)configWithRecord:(KBConsumptionRecord *)record {
NSString *title = record.kbdescription.length ? record.kbdescription : KBLocalized(@"Consumption");
self.titleLabel.text = title;
self.timeLabel.text = record.createdAt.length ? record.createdAt : @"--";
NSString *displayAmount = [self kb_formatAmountText:record.amount];
self.amountLabel.text = displayAmount;
UIColor *incomeColor = [UIColor colorWithHex:0x66CD7C];
UIColor *expenseColor = [UIColor colorWithHex:0xCD2853];
self.amountLabel.textColor = (record.type == KBConsumptionRecordTypeRecharge) ? incomeColor : expenseColor;
}
- (NSString *)kb_formatAmountText:(NSNumber *)amount {
if (![amount isKindOfClass:NSNumber.class]) { return @"--"; }
static NSNumberFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterDecimalStyle;
formatter.minimumFractionDigits = 2;
formatter.maximumFractionDigits = 2;
formatter.minimumIntegerDigits = 1;
formatter.positivePrefix = @"+";
});
return [formatter stringFromNumber:amount] ?: @"--";
}
#pragma mark - Lazy
- (UILabel *)titleLabel {
if (!_titleLabel) {
_titleLabel = [UILabel new];
_titleLabel.font = [KBFont medium:15];
_titleLabel.textColor = [UIColor colorWithHex:KBBlackValue];
_titleLabel.text = KBLocalized(@"Consumption");
}
return _titleLabel;
}
- (UILabel *)timeLabel {
if (!_timeLabel) {
_timeLabel = [UILabel new];
_timeLabel.font = [KBFont regular:12];
_timeLabel.textColor = [UIColor colorWithHex:0x9FA5B5];
_timeLabel.text = @"--";
}
return _timeLabel;
}
- (UILabel *)amountLabel {
if (!_amountLabel) {
_amountLabel = [UILabel new];
_amountLabel.font = [KBFont medium:16];
_amountLabel.textColor = [UIColor colorWithHex:0xE36464];
_amountLabel.textAlignment = NSTextAlignmentRight;
_amountLabel.text = @"--";
}
return _amountLabel;
}
- (UIView *)lineView {
if (!_lineView) {
_lineView = [UIView new];
_lineView.backgroundColor = [UIColor colorWithHex:0xEFEFF1];
}
return _lineView;
}
@end

View File

@@ -24,6 +24,7 @@
@property (nonatomic, strong) UIImageView *cardRight; @property (nonatomic, strong) UIImageView *cardRight;
@property (nonatomic, strong) UIImageView *avatarEditIcon; // @property (nonatomic, strong) UIImageView *avatarEditIcon; //
@property (nonatomic, strong) KBUser *userModel; @property (nonatomic, strong) KBUser *userModel;
@property (nonatomic, assign) NSInteger kb_vipLayoutState; // -1: unknown, 0: non-vip, 1: vip
//@property (nonatomic, strong) MASConstraint *vipIconWidthConstraint; //@property (nonatomic, strong) MASConstraint *vipIconWidthConstraint;
//@property (nonatomic, strong) MASConstraint *nameRightToVipConstraint; //@property (nonatomic, strong) MASConstraint *nameRightToVipConstraint;
//@property (nonatomic, strong) MASConstraint *nameRightToSuperviewConstraint; //@property (nonatomic, strong) MASConstraint *nameRightToSuperviewConstraint;
@@ -34,6 +35,7 @@
- (instancetype)initWithFrame:(CGRect)frame { - (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) { if (self = [super initWithFrame:frame]) {
self.kb_vipLayoutState = -1;
self.backgroundColor = [UIColor clearColor]; // self.backgroundColor = [UIColor clearColor]; //
[self addSubview:self.titleLabel]; [self addSubview:self.titleLabel];
@@ -131,11 +133,54 @@
[self.avatarView kb_setAvatarURL:user.avatarUrl placeholder:KBAvatarPlaceholderImage]; [self.avatarView kb_setAvatarURL:user.avatarUrl placeholder:KBAvatarPlaceholderImage];
BOOL isVip = user.isVip; BOOL isVip = user.isVip;
[self kb_applyVipLayout:isVip];
self.vipIconView.hidden = !isVip; self.vipIconView.hidden = !isVip;
self.vipExpiryLabel.hidden = !isVip; self.vipExpiryLabel.hidden = !isVip;
self.vipExpiryLabel.text = isVip ? [self vipExpiryDisplayTextFrom:user.vipExpiry] : nil; self.vipExpiryLabel.text = isVip ? [self vipExpiryDisplayTextFrom:user.vipExpiry] : nil;
} }
- (void)kb_applyVipLayout:(BOOL)isVip {
NSInteger state = isVip ? 1 : 0;
if (self.kb_vipLayoutState == state) { return; }
self.kb_vipLayoutState = state;
if (isVip) {
[self.nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.avatarView.mas_right).offset(10);
make.bottom.equalTo(self.avatarView.mas_centerY).offset(-4);
}];
[self.vipIconView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.nameLabel);
make.left.equalTo(self.nameLabel.mas_right).offset(10);
make.right.lessThanOrEqualTo(self).offset(-16);
make.width.mas_equalTo(51);
make.height.mas_equalTo(20);
}];
[self.vipExpiryLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.avatarView.mas_centerY).offset(4);
make.left.equalTo(self.nameLabel);
make.right.lessThanOrEqualTo(self).offset(-16);
}];
return;
}
[self.nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.avatarView.mas_right).offset(10);
make.centerY.equalTo(self.avatarView);
make.right.lessThanOrEqualTo(self).offset(-16);
}];
[self.vipIconView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.nameLabel.mas_right);
make.centerY.equalTo(self.nameLabel);
make.width.height.mas_equalTo(0);
}];
[self.vipExpiryLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.nameLabel.mas_bottom);
make.left.equalTo(self.nameLabel);
make.width.height.mas_equalTo(0);
}];
}
- (NSString *)vipExpiryDisplayTextFrom:(NSString *)rawDate { - (NSString *)vipExpiryDisplayTextFrom:(NSString *)rawDate {
NSString *prefix = KBLocalized(@"Due On"); NSString *prefix = KBLocalized(@"Due On");
if (rawDate.length == 0) { if (rawDate.length == 0) {
@@ -254,7 +299,7 @@
- (UILabel *)nameLabel { - (UILabel *)nameLabel {
if (!_nameLabel) { if (!_nameLabel) {
_nameLabel = [UILabel new]; _nameLabel = [UILabel new];
_nameLabel.text = @"Notice"; _nameLabel.text = @"--";
_nameLabel.font = [KBFont medium:20]; _nameLabel.font = [KBFont medium:20];
_nameLabel.textColor = [UIColor colorWithHex:KBBlackValue]; _nameLabel.textColor = [UIColor colorWithHex:KBBlackValue];
_nameLabel.lineBreakMode = NSLineBreakByTruncatingTail; _nameLabel.lineBreakMode = NSLineBreakByTruncatingTail;

View File

@@ -12,9 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
@class KBShopThemeDetailModel; @class KBShopThemeDetailModel;
@interface KBSkinDetailHeaderCell : UICollectionViewCell @interface KBSkinDetailHeaderCell : UICollectionViewCell
@property (nonatomic, strong) UIImageView *coverView; // 顶部大图
@property (nonatomic, strong) UILabel *leftLabel; // 下方左侧文案(#1B1F1A
@property (nonatomic, strong) UILabel *rightLabel; // 下方右侧文案(#02BEAC
- (void)configWithDetail:(nullable KBShopThemeDetailModel *)detail; - (void)configWithDetail:(nullable KBShopThemeDetailModel *)detail;
@end @end

View File

@@ -8,7 +8,11 @@
#import "KBSkinDetailHeaderCell.h" #import "KBSkinDetailHeaderCell.h"
#import "UIImageView+KBWebImage.h" #import "UIImageView+KBWebImage.h"
#import "KBShopThemeDetailModel.h" #import "KBShopThemeDetailModel.h"
@interface KBSkinDetailHeaderCell()
@property (nonatomic, strong) UIImageView *coverView; //
@property (nonatomic, strong) UILabel *leftLabel; // #1B1F1A
@property (nonatomic, strong) UILabel *rightLabel; // #02BEAC
@end
@implementation KBSkinDetailHeaderCell @implementation KBSkinDetailHeaderCell
- (instancetype)initWithFrame:(CGRect)frame { - (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) { if (self = [super initWithFrame:frame]) {

View File

@@ -9,10 +9,12 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@class KBShopThemeTagModel;
@interface KBSkinDetailTagCell : UICollectionViewCell @interface KBSkinDetailTagCell : UICollectionViewCell
- (void)config:(NSString *)text; - (void)configWithTag:(KBShopThemeTagModel *)tag;
/// 根据文案计算自适应宽度(外部布局用) /// 根据标签计算自适应宽度(外部布局用)
+ (CGSize)sizeForText:(NSString *)text; + (CGSize)sizeForTag:(KBShopThemeTagModel *)tag;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@@ -6,6 +6,8 @@
// //
#import "KBSkinDetailTagCell.h" #import "KBSkinDetailTagCell.h"
#import "KBShopThemeTagModel.h"
#import "UIColor+Extension.h"
@interface KBSkinDetailTagCell () @interface KBSkinDetailTagCell ()
@property (nonatomic, strong) UILabel *titleLabel; @property (nonatomic, strong) UILabel *titleLabel;
@end @end
@@ -23,11 +25,14 @@
return self; return self;
} }
- (void)config:(NSString *)text { - (void)configWithTag:(KBShopThemeTagModel *)tag {
self.titleLabel.text = text ?: @""; NSString *text = tag.label ?: @"";
self.titleLabel.text = text;
self.contentView.backgroundColor = [UIColor colorWithHexString:tag.color];
} }
+ (CGSize)sizeForText:(NSString *)text { + (CGSize)sizeForTag:(KBShopThemeTagModel *)tag {
NSString *text = tag.label ?: @"";
if (text.length == 0) { return CGSizeMake(40, 24); } if (text.length == 0) { return CGSizeMake(40, 24); }
CGSize s = [text sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14]}]; CGSize s = [text sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14]}];
// 12 + 12 32 // 12 + 12 32

View File

@@ -9,12 +9,14 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@class KBShopThemeTagModel;
@interface KBSkinTagsContainerCell : UICollectionViewCell <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout> @interface KBSkinTagsContainerCell : UICollectionViewCell <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property (nonatomic, strong) UICollectionView *tagsView; // 内部标签列表 @property (nonatomic, strong) UICollectionView *tagsView; // 内部标签列表
@property (nonatomic, copy) NSArray<NSString *> *tags; // 标签文案 @property (nonatomic, copy) NSArray<KBShopThemeTagModel *> *tags; // 标签数据
- (void)configWithTags:(NSArray<NSString *> *)tags; - (void)configWithTags:(NSArray<KBShopThemeTagModel *> *)tags;
/// 根据给定宽度,计算该容器需要的高度(用于外部 sizeForItem /// 根据给定宽度,计算该容器需要的高度(用于外部 sizeForItem
+ (CGFloat)heightForTags:(NSArray<NSString *> *)tags width:(CGFloat)width; + (CGFloat)heightForTags:(NSArray<KBShopThemeTagModel *> *)tags width:(CGFloat)width;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@@ -8,6 +8,7 @@
#import "KBSkinTagsContainerCell.h" #import "KBSkinTagsContainerCell.h"
#import "KBSkinDetailTagCell.h" #import "KBSkinDetailTagCell.h"
#import "UICollectionViewLeftAlignedLayout.h" #import "UICollectionViewLeftAlignedLayout.h"
#import "KBShopThemeTagModel.h"
static NSString * const kInnerTagCellId = @"kInnerTagCellId"; static NSString * const kInnerTagCellId = @"kInnerTagCellId";
@implementation KBSkinTagsContainerCell @implementation KBSkinTagsContainerCell
@@ -23,7 +24,7 @@ static NSString * const kInnerTagCellId = @"kInnerTagCellId";
return self; return self;
} }
- (void)configWithTags:(NSArray<NSString *> *)tags { - (void)configWithTags:(NSArray<KBShopThemeTagModel *> *)tags {
self.tags = tags; self.tags = tags;
[self.tagsView reloadData]; [self.tagsView reloadData];
} }
@@ -35,25 +36,25 @@ static NSString * const kInnerTagCellId = @"kInnerTagCellId";
} }
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
KBSkinDetailTagCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kInnerTagCellId forIndexPath:indexPath]; KBSkinDetailTagCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kInnerTagCellId forIndexPath:indexPath];
[cell config:self.tags[indexPath.item]]; [cell configWithTag:self.tags[indexPath.item]];
return cell; return cell;
} }
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
// 32 // 32
return [KBSkinDetailTagCell sizeForText:self.tags[indexPath.item]]; return [KBSkinDetailTagCell sizeForTag:self.tags[indexPath.item]];
} }
#pragma mark - Height Helper #pragma mark - Height Helper
+ (CGFloat)heightForTags:(NSArray<NSString *> *)tags width:(CGFloat)width { + (CGFloat)heightForTags:(NSArray<KBShopThemeTagModel *> *)tags width:(CGFloat)width {
if (tags.count == 0) { return 0; } if (tags.count == 0) { return 0; }
// 8item 8sectionInsets = {0,16,0,16} // 8item 8sectionInsets = {0,16,0,16}
CGFloat leftRight = 16 * 2; // VC sectionInset=16 CGFloat leftRight = 16 * 2; // VC sectionInset=16
CGFloat maxWidth = width - leftRight; CGFloat maxWidth = width - leftRight;
CGFloat x = 0; CGFloat x = 0;
CGFloat rows = 1; CGFloat rows = 1;
for (NSString *t in tags) { for (KBShopThemeTagModel *tag in tags) {
CGSize s = [KBSkinDetailTagCell sizeForText:t]; CGSize s = [KBSkinDetailTagCell sizeForTag:tag];
CGFloat iw = ceil(s.width); CGFloat iw = ceil(s.width);
if (x == 0) { if (x == 0) {
x = iw; x = iw;

View File

@@ -0,0 +1,14 @@
//
// KBConsumptionRecordVC.h
// keyBoard
//
#import "BaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface KBConsumptionRecordVC : BaseViewController
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,341 @@
//
// KBConsumptionRecordVC.m
// keyBoard
//
#import "KBConsumptionRecordVC.h"
#import "KBConsumptionRecord.h"
#import "KBConsumptionRecordCell.h"
#import "KBMyVM.h"
#import "KBShopVM.h"
#import "KBJfPay.h"
#import <Masonry/Masonry.h>
#import "UIColor+Extension.h"
#import "KBHUD.h"
#import <MJRefresh/MJRefresh.h>
@interface KBConsumptionRecordVC () <UITableViewDataSource, UITableViewDelegate>
@property (nonatomic, strong) BaseTableView *tableView;
@property (nonatomic, strong) UIView *headerView;
@property (nonatomic, strong) UIView *cardView;
@property (nonatomic, strong) UILabel *pointsTitleLabel;
@property (nonatomic, strong) UILabel *pointsLabel;
@property (nonatomic, strong) UIImageView *pointsIconView;
@property (nonatomic, strong) UIButton *rechargeButton;
@property (nonatomic, strong) UIImageView *sectionIconView;
@property (nonatomic, strong) UILabel *sectionTitleLabel;
@property (nonatomic, strong) NSMutableArray<KBConsumptionRecord *> *records;
@property (nonatomic, strong) KBMyVM *viewModel;
@property (nonatomic, strong) KBShopVM *shopVM;
@property (nonatomic, strong) UIImageView *bgImageView; //
@property (nonatomic, assign) NSInteger pageNumber;
@property (nonatomic, assign) NSInteger pageSize;
@property (nonatomic, assign) BOOL isLoading;
@end
@implementation KBConsumptionRecordVC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.kb_titleLabel.text = KBLocalized(@"Consumption Record");
self.kb_navView.backgroundColor = [UIColor clearColor];
self.bgImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"my_bg_icon"]];
self.bgImageView.contentMode = UIViewContentModeScaleAspectFill;
[self.view insertSubview:self.bgImageView belowSubview:self.kb_navView];
[self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
self.records = [NSMutableArray array];
[self.view addSubview:self.tableView];
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.equalTo(self.view);
make.top.equalTo(self.view).offset(KB_NAV_TOTAL_HEIGHT);
}];
self.tableView.tableHeaderView = self.headerView;
self.pageNumber = 1;
self.pageSize = 10;
[self setupRefresh];
[self fetchWalletBalance];
[self.tableView.mj_header beginRefreshing];
}
#pragma mark - Data
- (void)fetchWalletBalance {
__weak typeof(self) weakSelf = self;
[self.shopVM fetchWalletBalanceWithCompletion:^(NSString * _Nullable balance, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!error && balance.length > 0) {
weakSelf.pointsLabel.text = balance;
}
});
}];
}
- (void)setupRefresh {
__weak typeof(self) weakSelf = self;
self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
[weakSelf refreshRecords];
}];
self.tableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{
[weakSelf loadMoreRecords];
}];
self.tableView.mj_footer.hidden = YES;
}
- (void)refreshRecords {
if (self.isLoading) {
[self.tableView.mj_header endRefreshing];
return;
}
self.pageNumber = 1;
[self.tableView.mj_footer resetNoMoreData];
[self fetchPurchaseRecordsIsRefresh:YES];
}
- (void)loadMoreRecords {
if (self.isLoading || self.tableView.mj_footer.state == MJRefreshStateNoMoreData) {
[self.tableView.mj_footer endRefreshing];
return;
}
self.pageNumber += 1;
[self fetchPurchaseRecordsIsRefresh:NO];
}
- (void)fetchPurchaseRecordsIsRefresh:(BOOL)isRefresh {
self.isLoading = YES;
BOOL showHUD = !self.tableView.mj_header.isRefreshing && !self.tableView.mj_footer.isRefreshing;
if (showHUD) {
[KBHUD show];
}
__weak typeof(self) weakSelf = self;
[self.viewModel fetchWalletTransactionsWithPage:self.pageNumber
pageSize:self.pageSize
completion:^(NSArray<KBConsumptionRecord *> * _Nullable records, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.isLoading = NO;
if (showHUD) {
[KBHUD dismiss];
}
if (isRefresh) {
[weakSelf.tableView.mj_header endRefreshing];
} else {
[weakSelf.tableView.mj_footer endRefreshing];
}
if (error) {
if (!isRefresh) {
weakSelf.pageNumber = MAX(1, weakSelf.pageNumber - 1);
}
NSString *msg = error.localizedDescription ?: KBLocalized(@"Network error");
[KBHUD showInfo:msg];
return;
}
if (isRefresh) {
[weakSelf.records removeAllObjects];
}
if (records.count > 0) {
[weakSelf.records addObjectsFromArray:records];
}
[weakSelf.tableView reloadData];
weakSelf.tableView.mj_footer.hidden = (weakSelf.records.count == 0);
if (records.count < weakSelf.pageSize) {
[weakSelf.tableView.mj_footer endRefreshingWithNoMoreData];
}
});
}];
}
#pragma mark - UITableView
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.records.count;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return KBFit(78);
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
KBConsumptionRecordCell *cell = [tableView dequeueReusableCellWithIdentifier:[KBConsumptionRecordCell reuseId]
forIndexPath:indexPath];
if (indexPath.row < self.records.count) {
[cell configWithRecord:self.records[indexPath.row]];
}
return cell;
}
#pragma mark - Actions
- (void)onRecharge {
KBJfPay *vc = [[KBJfPay alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
#pragma mark - Lazy
- (BaseTableView *)tableView {
if (!_tableView) {
_tableView = [[BaseTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
_tableView.backgroundColor = [UIColor clearColor];
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_tableView.dataSource = self;
_tableView.delegate = self;
[_tableView registerClass:KBConsumptionRecordCell.class forCellReuseIdentifier:[KBConsumptionRecordCell reuseId]];
}
return _tableView;
}
- (UIView *)headerView {
if (!_headerView) {
CGFloat headerHeight = KBFit(210);
_headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, KB_SCREEN_WIDTH, headerHeight)];
_headerView.backgroundColor = [UIColor clearColor];
[_headerView addSubview:self.cardView];
[_headerView addSubview:self.sectionIconView];
[_headerView addSubview:self.sectionTitleLabel];
[self.cardView addSubview:self.pointsTitleLabel];
[self.cardView addSubview:self.pointsIconView];
[self.cardView addSubview:self.pointsLabel];
[self.cardView addSubview:self.rechargeButton];
[self.cardView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(_headerView).offset(16);
make.right.equalTo(_headerView).offset(-16);
make.top.equalTo(_headerView).offset(24);
make.height.mas_equalTo(KBFit(126));
}];
[self.pointsTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.cardView).offset(16);
make.top.equalTo(self.cardView).offset(18);
}];
[self.pointsIconView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.cardView).offset(16);
make.centerY.equalTo(self.cardView);
make.width.height.mas_equalTo(38);
}];
[self.pointsLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.pointsIconView.mas_right).offset(8);
make.centerY.equalTo(self.pointsIconView).offset(0);
make.right.lessThanOrEqualTo(self.rechargeButton.mas_left).offset(-12);
}];
[self.rechargeButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.cardView).offset(-10);
make.centerY.equalTo(self.pointsIconView);
make.width.mas_equalTo(114);
make.height.mas_equalTo(42);
}];
[self.sectionIconView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(_headerView).offset(16);
make.top.equalTo(self.cardView.mas_bottom).offset(18);
make.width.height.mas_equalTo(24);
}];
[self.sectionTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.sectionIconView);
make.left.equalTo(self.sectionIconView.mas_right).offset(8);
}];
}
return _headerView;
}
- (UIView *)cardView {
if (!_cardView) {
_cardView = [UIView new];
_cardView.backgroundColor = [UIColor colorWithHex:0xC5FFF6];
_cardView.layer.cornerRadius = 20;
_cardView.layer.masksToBounds = YES;
}
return _cardView;
}
- (UILabel *)pointsTitleLabel {
if (!_pointsTitleLabel) {
_pointsTitleLabel = [UILabel new];
_pointsTitleLabel.text = KBLocalized(@"My Points");
_pointsTitleLabel.font = [KBFont medium:14];
_pointsTitleLabel.textColor = [UIColor colorWithHex:KBBlackValue];
}
return _pointsTitleLabel;
}
- (UILabel *)pointsLabel {
if (!_pointsLabel) {
_pointsLabel = [UILabel new];
_pointsLabel.text = @"0";
_pointsLabel.font = [KBFont bold:40];
_pointsLabel.textColor = [UIColor colorWithHex:0x02BEAC];
_pointsLabel.adjustsFontSizeToFitWidth = YES;
_pointsLabel.minimumScaleFactor = 0.6;
_pointsLabel.numberOfLines = 1;
}
return _pointsLabel;
}
- (UIImageView *)pointsIconView {
if (!_pointsIconView) {
_pointsIconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"shop_jb_icon"]];
_pointsIconView.contentMode = UIViewContentModeScaleAspectFit;
}
return _pointsIconView;
}
- (UIButton *)rechargeButton {
if (!_rechargeButton) {
_rechargeButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_rechargeButton setTitle:KBLocalized(@"Recharge") forState:UIControlStateNormal];
_rechargeButton.titleLabel.font = [KBFont medium:13];
[_rechargeButton setTitleColor:[UIColor colorWithHex:0x1B1F1A] forState:UIControlStateNormal];
// _rechargeButton.backgroundColor = [UIColor colorWithHex:0xCFF7EA];
[_rechargeButton setBackgroundImage:[UIImage imageNamed:@"my_chongzhi_bg"] forState:UIControlStateNormal];
_rechargeButton.layer.cornerRadius = 21;
_rechargeButton.layer.masksToBounds = YES;
[_rechargeButton addTarget:self action:@selector(onRecharge) forControlEvents:UIControlEventTouchUpInside];
}
return _rechargeButton;
}
- (UIImageView *)sectionIconView {
if (!_sectionIconView) {
_sectionIconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"shop_jb_icon"]];
_sectionIconView.contentMode = UIViewContentModeScaleAspectFit;
}
return _sectionIconView;
}
- (UILabel *)sectionTitleLabel {
if (!_sectionTitleLabel) {
_sectionTitleLabel = [UILabel new];
_sectionTitleLabel.text = KBLocalized(@"Consumption Details");
_sectionTitleLabel.font = [KBFont medium:14];
_sectionTitleLabel.textColor = [UIColor colorWithHex:KBBlackValue];
}
return _sectionTitleLabel;
}
- (KBMyVM *)viewModel {
if (!_viewModel) {
_viewModel = [[KBMyVM alloc] init];
}
return _viewModel;
}
- (KBShopVM *)shopVM {
if (!_shopVM) {
_shopVM = [[KBShopVM alloc] init];
}
return _shopVM;
}
@end

View File

@@ -76,7 +76,7 @@ static NSString * const kMySkinCellId = @"kMySkinCellId";
[self.collectionView kb_endLoadingForEmpty]; [self.collectionView kb_endLoadingForEmpty];
// + // +
self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(fetchDownloadedThemes)]; self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(fetchPurchasedThemes)];
// //
[self.collectionView.mj_header beginRefreshing]; [self.collectionView.mj_header beginRefreshing];
@@ -85,9 +85,9 @@ static NSString * const kMySkinCellId = @"kMySkinCellId";
self.bottomView.hidden = YES; self.bottomView.hidden = YES;
} }
- (void)fetchDownloadedThemes { - (void)fetchPurchasedThemes {
KBWeakSelf KBWeakSelf
[self.viewModel fetchDownloadedThemesWithCompletion:^(NSArray<KBMyTheme *> * _Nullable themes, NSError * _Nullable error) { [self.viewModel fetchPurchasedThemesWithCompletion:^(NSArray<KBMyTheme *> * _Nullable themes, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if ([weakSelf.collectionView.mj_header isRefreshing]) { if ([weakSelf.collectionView.mj_header isRefreshing]) {
[weakSelf.collectionView.mj_header endRefreshing]; [weakSelf.collectionView.mj_header endRefreshing];
@@ -177,7 +177,7 @@ static NSString * const kMySkinCellId = @"kMySkinCellId";
[KBHUD show]; [KBHUD show];
KBWeakSelf KBWeakSelf
[self.viewModel deleteDownloadedThemesWithIds:themeIds [self.viewModel deletePurchasedThemesWithIds:themeIds
completion:^(BOOL success, NSError * _Nullable error) { completion:^(BOOL success, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD dismiss]; [KBHUD dismiss];

View File

@@ -14,6 +14,9 @@
#import "KBNoticeVC.h" #import "KBNoticeVC.h"
#import "KBFeedBackVC.h" #import "KBFeedBackVC.h"
#import "KBMyVM.h" #import "KBMyVM.h"
#import "KBConsumptionRecordVC.h"
#import "KBHUD.h"
@interface MyVC () <UITableViewDelegate, UITableViewDataSource> @interface MyVC () <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) BaseTableView *tableView; // @property (nonatomic, strong) BaseTableView *tableView; //
@@ -37,10 +40,11 @@
make.edges.equalTo(self.view); make.edges.equalTo(self.view);
}]; }];
// title + SF Symbols + // title + SF Symbols + my_record_icon
self.data = @[ self.data = @[
@[@{ @"title": KBLocalized(@"Consumption Record"), @"icon": @"my_record_icon", @"color": @(0x60A3FF),@"id":@"8" }],
@[@{ @"title": KBLocalized(@"Notice"), @"icon": @"my_notice_icon", @"color": @(0x60A3FF),@"id":@"1" }], @[@{ @"title": KBLocalized(@"Notice"), @"icon": @"my_notice_icon", @"color": @(0x60A3FF),@"id":@"1" }],
@[@{ @"title": KBLocalized(@"Share App"), @"icon": @"my_share_icon", @"color": @(0xF5A623),@"id":@"2" }], @[@{ @"title": KBLocalized(@"invite"), @"icon": @"my_share_icon", @"color": @(0xF5A623),@"id":@"2" }],
@[@{ @"title": KBLocalized(@"Feedback"), @"icon": @"my_feedback_icon", @"color": @(0xB06AFD),@"id":@"3" }, @[@{ @"title": KBLocalized(@"Feedback"), @"icon": @"my_feedback_icon", @"color": @(0xB06AFD),@"id":@"3" },
@{ @"title": KBLocalized(@"E-mail"), @"icon": @"my_email_icon", @"color": @(0xFF8A65),@"id":@"4" }, @{ @"title": KBLocalized(@"E-mail"), @"icon": @"my_email_icon", @"color": @(0xFF8A65),@"id":@"4" },
@{ @"title": KBLocalized(@"Agreement"), @"icon": @"my_agreement_icon", @"color": @(0x4CD964),@"id":@"5" }, @{ @"title": KBLocalized(@"Agreement"), @"icon": @"my_agreement_icon", @"color": @(0x4CD964),@"id":@"5" },
@@ -120,6 +124,29 @@
[self.navigationController pushViewController:[KBNoticeVC new] animated:true]; [self.navigationController pushViewController:[KBNoticeVC new] animated:true];
}else if ([itemID isEqualToString:@"2"]){ }else if ([itemID isEqualToString:@"2"]){
//
if (!self.viewModel) {
self.viewModel = [[KBMyVM alloc] init];
}
[KBHUD show];
[self.viewModel fetchInviteCodeWithCompletion:^(KBInviteCodeModel * _Nullable inviteCode, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
[KBHUD dismiss];
if (error) {
[KBHUD showInfo:error.localizedDescription ?: KBLocalized(@"Network error")];
return;
}
NSString *textToCopy = [NSString stringWithFormat:@"%@?code=%@",inviteCode.h5Link,inviteCode.inviteCode];
if (textToCopy.length == 0) {
[KBHUD showInfo:KBLocalized(@"Failed")];
return;
}
UIPasteboard.generalPasteboard.string = textToCopy;
[KBHUD showInfo:KBLocalized(@"Copy Success")];
});
}];
}else if ([itemID isEqualToString:@"3"]){ }else if ([itemID isEqualToString:@"3"]){
[self.navigationController pushViewController:[KBFeedBackVC new] animated:true]; [self.navigationController pushViewController:[KBFeedBackVC new] animated:true];
@@ -130,6 +157,9 @@
}else if ([itemID isEqualToString:@"6"]){ }else if ([itemID isEqualToString:@"6"]){
}else if ([itemID isEqualToString:@"8"]){
KBConsumptionRecordVC *vc = [[KBConsumptionRecordVC alloc] init];
[self.navigationController pushViewController:vc animated:true];
}else if ([itemID isEqualToString:@"7"]){ }else if ([itemID isEqualToString:@"7"]){
KBTestVC *vc = [[KBTestVC alloc] init]; KBTestVC *vc = [[KBTestVC alloc] init];
[self.navigationController pushViewController:vc animated:true]; [self.navigationController pushViewController:vc animated:true];

View File

@@ -8,6 +8,8 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "KBCharacter.h" #import "KBCharacter.h"
#import "KBMyTheme.h" #import "KBMyTheme.h"
#import "KBConsumptionRecord.h"
#import "KBInviteCodeModel.h"
@class KBUser; @class KBUser;
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@@ -26,14 +28,27 @@ typedef void(^KBDeleteUserCharacterCompletion)(BOOL success, NSError * _Nullable
typedef void(^KBMyPurchasedThemesCompletion)(NSArray<KBMyTheme *> *_Nullable themes, NSError *_Nullable error); typedef void(^KBMyPurchasedThemesCompletion)(NSArray<KBMyTheme *> *_Nullable themes, NSError *_Nullable error);
typedef void(^KBDeleteThemesCompletion)(BOOL success, NSError *_Nullable error); typedef void(^KBDeleteThemesCompletion)(BOOL success, NSError *_Nullable error);
typedef void(^KBSubmitFeedbackCompletion)(BOOL success, NSError *_Nullable error); typedef void(^KBSubmitFeedbackCompletion)(BOOL success, NSError *_Nullable error);
typedef void(^KBMyPurchaseRecordCompletion)(NSArray<KBConsumptionRecord *> *_Nullable records, NSError *_Nullable error);
typedef void(^KBMyInviteCodeCompletion)(KBInviteCodeModel *_Nullable inviteCode, NSError *_Nullable error);
@interface KBMyVM : NSObject @interface KBMyVM : NSObject
/// 获取当前用户详情(/user/detail /// 获取当前用户详情(/user/detail
- (void)fetchUserDetailWithCompletion:(KBMyUserDetailCompletion)completion; - (void)fetchUserDetailWithCompletion:(KBMyUserDetailCompletion)completion;
/// 查询邀请码(/user/inviteCode
- (void)fetchInviteCodeWithCompletion:(KBMyInviteCodeCompletion)completion;
/// 用户人设列表(/character/listByUser /// 用户人设列表(/character/listByUser
- (void)fetchCharacterListByUserWithCompletion:(KBCharacterListCompletion)completion; - (void)fetchCharacterListByUserWithCompletion:(KBCharacterListCompletion)completion;
/// 已购主题列表(/themes/purchased
- (void)fetchPurchasedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion;
/// 批量删除用户主题(/user-themes/batch-delete
- (void)deletePurchasedThemesWithIds:(NSArray<NSString *> *)themeIds
completion:(KBDeleteThemesCompletion)completion;
/// 分页查询钱包交易记录(/wallet/transactions
- (void)fetchWalletTransactionsWithPage:(NSInteger)pageNum
pageSize:(NSInteger)pageSize
completion:(KBMyPurchaseRecordCompletion)completion;
/// 本地已下载主题列表 /// 本地已下载主题列表
- (void)fetchDownloadedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion; - (void)fetchDownloadedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion;
/// 删除本地主题资源 /// 删除本地主题资源

View File

@@ -51,6 +51,31 @@ NSString * const KBUserCharacterDeletedNotification = @"KBUserCharacterDeletedNo
}]; }];
} }
- (void)fetchInviteCodeWithCompletion:(KBMyInviteCodeCompletion)completion {
[[KBNetworkManager shared] GET:API_USER_INVITE_CODE
parameters:nil
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *jsonOrData, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
if (completion) completion(nil, error);
return;
}
id dataObj = jsonOrData[KBData] ?: jsonOrData[@"data"];
if (![dataObj isKindOfClass:[NSDictionary class]]) {
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain
code:KBNetworkErrorInvalidResponse
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid response")}];
if (completion) completion(nil, e);
return;
}
KBInviteCodeModel *model = [KBInviteCodeModel mj_objectWithKeyValues:(NSDictionary *)dataObj];
if (completion) completion(model, nil);
}];
}
- (void)fetchCharacterListByUserWithCompletion:(KBCharacterListCompletion)completion{ - (void)fetchCharacterListByUserWithCompletion:(KBCharacterListCompletion)completion{
[[KBNetworkManager shared] GET:KB_API_CHARACTER_LISTBYUSER [[KBNetworkManager shared] GET:KB_API_CHARACTER_LISTBYUSER
parameters:nil parameters:nil
@@ -89,6 +114,116 @@ NSString * const KBUserCharacterDeletedNotification = @"KBUserCharacterDeletedNo
}]; }];
} }
- (void)fetchPurchasedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion {
[[KBNetworkManager shared] GET:API_THEME_PURCHASED
parameters:nil
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary * _Nullable json,
NSURLResponse * _Nullable response,
NSError * _Nullable error) {
if (error) {
if (completion) completion(nil, error);
return;
}
id dataObj = json[KBData] ?: json[@"data"];
if (![dataObj isKindOfClass:[NSArray class]]) {
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain
code:KBNetworkErrorInvalidResponse
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid response")}];
if (completion) completion(nil, e);
return;
}
NSArray<KBMyTheme *> *themes = [KBMyTheme mj_objectArrayWithKeyValuesArray:(NSArray *)dataObj];
if (completion) completion(themes, nil);
}];
}
- (void)deletePurchasedThemesWithIds:(NSArray<NSString *> *)themeIds
completion:(KBDeleteThemesCompletion)completion {
if (themeIds.count == 0) {
if (completion) {
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain
code:KBNetworkErrorInvalidResponse
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid parameter")}];
completion(NO, e);
}
return;
}
NSMutableArray *payloadIds = [NSMutableArray arrayWithCapacity:themeIds.count];
NSNumberFormatter *formatter = [NSNumberFormatter new];
for (NSString *themeId in themeIds) {
if (![themeId isKindOfClass:NSString.class]) { continue; }
NSString *trim = [themeId stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (trim.length == 0) { continue; }
NSNumber *number = [formatter numberFromString:trim];
[payloadIds addObject:number ?: trim];
}
if (payloadIds.count == 0) {
if (completion) {
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain
code:KBNetworkErrorInvalidResponse
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid parameter")}];
completion(NO, e);
}
return;
}
NSDictionary *body = @{@"themeIds": payloadIds};
[[KBNetworkManager shared] POST:API_USER_THEMES_BATCH_DELETE
jsonBody:body
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary * _Nullable json,
NSURLResponse * _Nullable response,
NSError * _Nullable error) {
if (completion) completion(error == nil, error);
}];
}
- (void)fetchWalletTransactionsWithPage:(NSInteger)pageNum
pageSize:(NSInteger)pageSize
completion:(KBMyPurchaseRecordCompletion)completion {
NSInteger safePageNum = pageNum > 0 ? pageNum : 1;
NSInteger safePageSize = pageSize > 0 ? pageSize : 10;
NSDictionary *body = @{
@"pageNum": @(safePageNum),
@"pageSize": @(safePageSize)
};
[[KBNetworkManager shared] POST:API_WALLET_TRANSACTIONS
jsonBody:body
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary * _Nullable json,
NSURLResponse * _Nullable response,
NSError * _Nullable error) {
if (error) {
if (completion) completion(nil, error);
return;
}
id dataObj = json[KBData] ?: json[@"data"];
if (![dataObj isKindOfClass:[NSDictionary class]]) {
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain
code:KBNetworkErrorInvalidResponse
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid response")}];
if (completion) completion(nil, e);
return;
}
id recordsObj = [(NSDictionary *)dataObj objectForKey:@"records"];
if (![recordsObj isKindOfClass:[NSArray class]]) {
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain
code:KBNetworkErrorInvalidResponse
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid response")}];
if (completion) completion(nil, e);
return;
}
NSArray<KBConsumptionRecord *> *records = [KBConsumptionRecord mj_objectArrayWithKeyValuesArray:(NSArray *)recordsObj];
if (completion) completion(records, nil);
}];
}
- (void)fetchDownloadedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion { - (void)fetchDownloadedThemesWithCompletion:(KBMyPurchasedThemesCompletion)completion {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
NSArray<KBSkinDownloadRecord *> *records = [KBSkinInstallBridge installedSkinRecords]; NSArray<KBSkinDownloadRecord *> *records = [KBSkinInstallBridge installedSkinRecords];

View File

@@ -17,12 +17,12 @@
- (NSString *)coinsDisplayText { - (NSString *)coinsDisplayText {
NSString *name = self.name ?: @""; NSString *name = self.name ?: @"";
NSString *unit = self.unit ?: @""; // NSString *unit = self.unit ?: @"";
if (name.length && unit.length) { if (name.length) {
return [NSString stringWithFormat:@"%@ %@", name, unit]; return [NSString stringWithFormat:@"%@", name];
} }
if (name.length) { return name; } if (name.length) { return name; }
if (unit.length) { return unit; } // if (unit.length) { return unit; }
return @""; return @"";
} }

View File

@@ -239,6 +239,9 @@
"symbols_toggle_123" = "key_symbols_123"; "symbols_toggle_123" = "key_symbols_123";
/* 自定义 AI 功能键 */ /* 自定义 AI 功能键 */
"ai" = "key_ai"; "ai" = "key_ai";
//"emoji" = "key_emoji";
"emoji_panel" = "key_emoji";
/* 发送/换行键 */ /* 发送/换行键 */
"return" = "key_send"; "return" = "key_send";

View File

@@ -22,8 +22,8 @@ NS_ASSUME_NONNULL_BEGIN
/// 点击键盘 return 或右侧按钮时回调 /// 点击键盘 return 或右侧按钮时回调
@property (nonatomic, copy) void(^onSearch)(NSString *keyword); @property (nonatomic, copy) void(^onSearch)(NSString *keyword);
/// 占位文字默认“Themes” ///// 占位文字默认“Themes”
@property (nonatomic, copy) NSString *placeholder; //@property (nonatomic, copy) NSString *placeholder;
/// 外部可设置关键字 /// 外部可设置关键字
- (void)updateKeyword:(NSString *)keyword; - (void)updateKeyword:(NSString *)keyword;

View File

@@ -9,7 +9,7 @@
@interface KBSearchBarView () <UITextFieldDelegate> @interface KBSearchBarView () <UITextFieldDelegate>
@property (nonatomic, strong) UIView *bgView; // @property (nonatomic, strong) UIView *bgView; //
@property (nonatomic, strong, readwrite) UITextField *textField; // @property (nonatomic, strong) UITextField *textField; //
@property (nonatomic, strong) UIButton *searchButton; // @property (nonatomic, strong) UIButton *searchButton; //
@end @end
@@ -90,7 +90,7 @@
_textField.textColor = [UIColor colorWithHex:0x1B1F1A]; _textField.textColor = [UIColor colorWithHex:0x1B1F1A];
_textField.clearButtonMode = UITextFieldViewModeWhileEditing; _textField.clearButtonMode = UITextFieldViewModeWhileEditing;
_textField.returnKeyType = UIReturnKeySearch; _textField.returnKeyType = UIReturnKeySearch;
_textField.placeholder = @"Themes"; // _textField.placeholder = KBLocalized(@"Search Themes");
// //
UIView *pad = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 8, 8)]; UIView *pad = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 8, 8)];
@@ -105,7 +105,7 @@
if (!_searchButton) { if (!_searchButton) {
_searchButton = [UIButton buttonWithType:UIButtonTypeSystem]; _searchButton = [UIButton buttonWithType:UIButtonTypeSystem];
_searchButton.titleLabel.font = [KBFont regular:14]; _searchButton.titleLabel.font = [KBFont regular:14];
[_searchButton setTitle:@"Search" forState:UIControlStateNormal]; [_searchButton setTitle:KBLocalized(@"Search") forState:UIControlStateNormal];
// 绿 // 绿
[_searchButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; [_searchButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_searchButton.backgroundColor = [UIColor colorWithRed:0.15 green:0.72 blue:0.62 alpha:1.0]; _searchButton.backgroundColor = [UIColor colorWithRed:0.15 green:0.72 blue:0.62 alpha:1.0];
@@ -118,10 +118,10 @@
#pragma mark - Public #pragma mark - Public
- (void)setPlaceholder:(NSString *)placeholder { //- (void)setPlaceholder:(NSString *)placeholder {
_placeholder = [placeholder copy]; // _placeholder = [placeholder copy];
self.textField.placeholder = placeholder; // self.textField.placeholder = placeholder;
} //}
- (void)setBgCornerRadius:(CGFloat)bgCornerRadius { - (void)setBgCornerRadius:(CGFloat)bgCornerRadius {
_bgCornerRadius = bgCornerRadius; _bgCornerRadius = bgCornerRadius;

View File

@@ -53,7 +53,9 @@
[self.priceBtn setTitle:priceText forState:UIControlStateNormal]; [self.priceBtn setTitle:priceText forState:UIControlStateNormal];
// //
self.coverView.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1.0]; // self.coverView.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1.0];
[self.coverView kb_setImageURL:[NSURL URLWithString:url] placeholder:KBPlaceholderImage];
} }
#pragma mark - Lazy #pragma mark - Lazy

View File

@@ -5,6 +5,8 @@
#import "KBTagCell.h" #import "KBTagCell.h"
static const CGFloat kKBTagMinWidth = 50.0;
@interface KBTagCell () @interface KBTagCell ()
@property (nonatomic, strong) UILabel *titleLabel; @property (nonatomic, strong) UILabel *titleLabel;
@end @end
@@ -29,10 +31,11 @@
} }
+ (CGSize)sizeForText:(NSString *)text { + (CGSize)sizeForText:(NSString *)text {
if (text.length == 0) { return CGSizeMake(40, 32); } if (text.length == 0) { return CGSizeMake(kKBTagMinWidth, 32); }
CGSize s = [text sizeWithAttributes:@{NSFontAttributeName:[KBFont regular:13]}]; CGSize s = [text sizeWithAttributes:@{NSFontAttributeName:[KBFont regular:13]}];
// 12 + 12 32 // 12 + 12 32
return CGSizeMake(ceil(s.width) + 24, 32); CGFloat width = ceil(s.width) + 24;
return CGSizeMake(MAX(width, kKBTagMinWidth), 32);
} }
#pragma mark - Lazy #pragma mark - Lazy
@@ -42,9 +45,9 @@
_titleLabel = [[UILabel alloc] init]; _titleLabel = [[UILabel alloc] init];
_titleLabel.font = [KBFont regular:13]; _titleLabel.font = [KBFont regular:13];
_titleLabel.textColor = [UIColor colorWithHex:0x1B1F1A]; _titleLabel.textColor = [UIColor colorWithHex:0x1B1F1A];
_titleLabel.textAlignment = NSTextAlignmentCenter;
} }
return _titleLabel; return _titleLabel;
} }
@end @end

View File

@@ -154,7 +154,7 @@ static NSString * const kResultCellId = @"KBSkinCardCell";
- (KBSearchBarView *)searchBarView { - (KBSearchBarView *)searchBarView {
if (!_searchBarView) { if (!_searchBarView) {
_searchBarView = [[KBSearchBarView alloc] init]; _searchBarView = [[KBSearchBarView alloc] init];
_searchBarView.placeholder = @"Themes"; // _searchBarView.placeholder = @"Themes";
KBWeakSelf KBWeakSelf
_searchBarView.onSearch = ^(NSString * _Nonnull keyword) { _searchBarView.onSearch = ^(NSString * _Nonnull keyword) {
[weakSelf performSearch:keyword]; [weakSelf performSearch:keyword];

View File

@@ -307,11 +307,11 @@ typedef NS_ENUM(NSInteger, KBSearchSection) {
KBSearchSectionHeader *header = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:kHeaderId forIndexPath:indexPath]; KBSearchSectionHeader *header = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:kHeaderId forIndexPath:indexPath];
if (indexPath.section == KBSearchSectionHistory) { if (indexPath.section == KBSearchSectionHistory) {
// sizeForHeader 0 // sizeForHeader 0
[header configWithTitle:@"Historical Search" showTrash:self.historyWords.count > 0]; [header configWithTitle:KBLocalized(@"Historical Search") showTrash:self.historyWords.count > 0];
KBWeakSelf KBWeakSelf
header.onTapTrash = ^{ [weakSelf clearHistory]; }; header.onTapTrash = ^{ [weakSelf clearHistory]; };
} else { } else {
[header configWithTitle:@"Recommended Skin" showTrash:NO]; [header configWithTitle:KBLocalized(@"Recommended Skin") showTrash:NO];
} }
return header; return header;
} }
@@ -391,7 +391,7 @@ typedef NS_ENUM(NSInteger, KBSearchSection) {
if (!_searchBarView) { if (!_searchBarView) {
_searchBarView = [[KBSearchBarView alloc] init]; _searchBarView = [[KBSearchBarView alloc] init];
_searchBarView.bgCornerRadius = 18; _searchBarView.bgCornerRadius = 18;
_searchBarView.placeholder = @"Themes"; // _searchBarView.placeholder = @"Themes";
KBWeakSelf KBWeakSelf
_searchBarView.onSearch = ^(NSString * _Nonnull keyword) { _searchBarView.onSearch = ^(NSString * _Nonnull keyword) {
// + // +

View File

@@ -32,7 +32,7 @@
} }
- (void)fetchRecommendedThemesWithCompletion:(KBSearchRecommendedCompletion)completion { - (void)fetchRecommendedThemesWithCompletion:(KBSearchRecommendedCompletion)completion {
[self.shopVM fetchRecommendedThemesWithCompletion:^(NSArray<KBShopThemeModel *> * _Nullable themes, NSError * _Nullable error) { [self.shopVM fetchRecommendedThemesWithThemeId:nil completion:^(NSArray<KBShopThemeModel *> * _Nullable themes, NSError * _Nullable error) {
if (completion) completion(themes, error); if (completion) completion(themes, error);
}]; }];
} }
@@ -74,4 +74,3 @@
} }
@end @end

View File

@@ -10,6 +10,8 @@
#import <JXCategoryView/JXCategoryView.h> #import <JXCategoryView/JXCategoryView.h>
#import <JXCategoryView/JXCategoryTitleCellModel.h> #import <JXCategoryView/JXCategoryTitleCellModel.h>
static const CGFloat kKBCategoryMinTotalWidth = 56.0;
@implementation KBCategoryTitleView @implementation KBCategoryTitleView
- (Class)preferredCellClass { - (Class)preferredCellClass {
@@ -29,4 +31,10 @@
right.titleCurrentColor = right.isSelected ? right.titleSelectedColor : right.titleNormalColor; right.titleCurrentColor = right.isSelected ? right.titleSelectedColor : right.titleNormalColor;
} }
- (CGFloat)preferredCellWidthAtIndex:(NSInteger)index {
CGFloat width = [super preferredCellWidthAtIndex:index];
CGFloat minPreferredWidth = MAX(0.0, kKBCategoryMinTotalWidth - self.cellWidthIncrement);
return MAX(width, minPreferredWidth);
}
@end @end

Some files were not shown because too many files have changed in this diff Show More