13 Commits

Author SHA1 Message Date
bdf2a9af80 修改人设长按排序后,键盘人设要同步问题 2026-01-07 19:05:52 +08:00
e858d35722 修改首页pop对钩问题 2026-01-07 15:45:01 +08:00
f2d5210313 1 2026-01-07 14:32:57 +08:00
1b0af3e2d6 修改颜色 2026-01-07 14:32:49 +08:00
0965cd3c7e 修改了横屏键盘不居中为题 2026-01-07 13:11:23 +08:00
c3909d63da 添加埋点 2026-01-06 19:25:34 +08:00
1096f24c57 修改hud 2025-12-31 17:32:39 +08:00
7ed84fd445 修改分享方式 2025-12-30 20:41:30 +08:00
4e2d7d2908 添加部分通用上报
修改bug 未登录在键盘点击充值要先去跳转登录
2025-12-30 15:27:35 +08:00
34089ddeea 1 2025-12-26 15:51:27 +08:00
6ec98468de Merge branch 'dev_处理立刻清空和撤销删除' into dev_st
# Conflicts:
#	CustomKeyboard/Utils/KBBackspaceLongPressHandler.m
解决冲突
2025-12-26 15:08:14 +08:00
ae37730da6 修改 我已经退出界面,然后从新进入界面弹起键盘,为什么撤销删除按钮显示? 2025-12-26 13:55:07 +08:00
203f104ece 修改键盘长按立即清空和撤销删除 2025-12-26 11:47:44 +08:00
67 changed files with 2024 additions and 155 deletions

View File

@@ -20,6 +20,7 @@
#import "KBKeyboardSubscriptionView.h" #import "KBKeyboardSubscriptionView.h"
#import "KBKeyboardSubscriptionProduct.h" #import "KBKeyboardSubscriptionProduct.h"
#import "KBBackspaceUndoManager.h" #import "KBBackspaceUndoManager.h"
#import "KBInputBufferManager.h"
#import "KBSuggestionEngine.h" #import "KBSuggestionEngine.h"
// 使 static kb_consumePendingShopSkin // 使 static kb_consumePendingShopSkin
@@ -46,6 +47,7 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
@interface KeyboardViewController () <KBKeyBoardMainViewDelegate, KBFunctionViewDelegate, KBKeyboardSubscriptionViewDelegate> @interface KeyboardViewController () <KBKeyBoardMainViewDelegate, KBFunctionViewDelegate, KBKeyboardSubscriptionViewDelegate>
@property (nonatomic, strong) UIButton *nextKeyboardButton; // @property (nonatomic, strong) UIButton *nextKeyboardButton; //
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) KBKeyBoardMainView *keyBoardMainView; // 0 @property (nonatomic, strong) KBKeyBoardMainView *keyBoardMainView; // 0
@property (nonatomic, strong) KBFunctionView *functionView; // 0 @property (nonatomic, strong) KBFunctionView *functionView; // 0
@property (nonatomic, strong) KBSettingView *settingView; // @property (nonatomic, strong) KBSettingView *settingView; //
@@ -54,6 +56,12 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
@property (nonatomic, strong) KBSuggestionEngine *suggestionEngine; @property (nonatomic, strong) KBSuggestionEngine *suggestionEngine;
@property (nonatomic, copy) NSString *currentWord; @property (nonatomic, copy) NSString *currentWord;
@property (nonatomic, assign) BOOL suppressSuggestions; @property (nonatomic, assign) BOOL suppressSuggestions;
@property (nonatomic, strong) MASConstraint *contentWidthConstraint;
@property (nonatomic, strong) MASConstraint *contentHeightConstraint;
@property (nonatomic, strong) NSLayoutConstraint *kb_heightConstraint;
@property (nonatomic, strong) NSLayoutConstraint *kb_widthConstraint;
@property (nonatomic, assign) CGFloat kb_lastPortraitWidth;
@property (nonatomic, assign) CGFloat kb_lastKeyboardHeight;
@end @end
@implementation KeyboardViewController @implementation KeyboardViewController
@@ -64,6 +72,8 @@ 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.suggestionEngine = [KBSuggestionEngine shared];
self.currentWord = @""; self.currentWord = @"";
@@ -92,20 +102,40 @@ 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];
} }
- (void)setupUI { - (void)setupUI {
self.view.translatesAutoresizingMaskIntoConstraints = NO; self.view.translatesAutoresizingMaskIntoConstraints = NO;
// / //
CGFloat keyboardHeight = KBFit(kKBKeyboardBaseHeight); CGFloat portraitWidth = [self kb_portraitWidth];
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; CGFloat keyboardHeight = [self kb_keyboardHeightForWidth:portraitWidth];
CGFloat screenWidth = CGRectGetWidth([UIScreen mainScreen].bounds);
CGFloat outerVerticalInset = KBFit(4.0f); CGFloat outerVerticalInset = KBFit(4.0f);
NSLayoutConstraint *h = [self.view.heightAnchor constraintEqualToConstant:keyboardHeight]; NSLayoutConstraint *h = [self.view.heightAnchor constraintEqualToConstant:keyboardHeight];
NSLayoutConstraint *w = [self.view.widthAnchor constraintEqualToConstant:screenWidth]; NSLayoutConstraint *w = [self.view.widthAnchor constraintEqualToConstant:screenWidth];
self.kb_heightConstraint = h;
self.kb_widthConstraint = w;
h.priority = UILayoutPriorityRequired; h.priority = UILayoutPriorityRequired;
w.priority = UILayoutPriorityRequired; w.priority = UILayoutPriorityRequired;
@@ -117,25 +147,30 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
iv.allowsSelfSizing = NO; iv.allowsSelfSizing = NO;
} }
} }
// //
[self.view addSubview:self.bgImageView]; [self.view addSubview:self.contentView];
[self.contentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view);
make.bottom.equalTo(self.view);
self.contentWidthConstraint = make.width.mas_equalTo(portraitWidth);
self.contentHeightConstraint = make.height.mas_equalTo(keyboardHeight);
}];
//
[self.contentView addSubview:self.bgImageView];
[self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) { [self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view); make.edges.equalTo(self.contentView);
}]; }];
// //
self.functionView.hidden = YES; self.functionView.hidden = YES;
[self.view addSubview:self.functionView]; [self.contentView addSubview:self.functionView];
[self.functionView mas_makeConstraints:^(MASConstraintMaker *make) { [self.functionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.view); make.edges.equalTo(self.contentView);
make.top.equalTo(self.view).offset(0);
make.bottom.equalTo(self.view).offset(0);
}]; }];
[self.view addSubview:self.keyBoardMainView]; [self.contentView addSubview:self.keyBoardMainView];
[self.keyBoardMainView mas_makeConstraints:^(MASConstraintMaker *make) { [self.keyBoardMainView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.view); make.edges.equalTo(self.contentView);
make.top.equalTo(self.view).offset(0);
make.bottom.equalTo(self.view.mas_bottom).offset(-0);
}]; }];
} }
@@ -257,36 +292,49 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
self.keyBoardMainView.hidden = show; self.keyBoardMainView.hidden = show;
if (show) { if (show) {
[[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:@"enter_keyboard_function_panel"
pageId:@"keyboard_function_panel"
extra:nil
completion:nil];
[self hideSubscriptionPanel]; [self hideSubscriptionPanel];
} else {
[[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:@"enter_keyboard_main_panel"
pageId:@"keyboard_main_panel"
extra:nil
completion:nil];
} }
// //
if (show) { if (show) {
[self.view bringSubviewToFront:self.functionView]; [self.contentView bringSubviewToFront:self.functionView];
} else { } else {
[self.view bringSubviewToFront:self.keyBoardMainView]; [self.contentView bringSubviewToFront:self.keyBoardMainView];
} }
} }
/// / keyBoardMainView / /// / keyBoardMainView /
- (void)showSettingView:(BOOL)show { - (void)showSettingView:(BOOL)show {
if (show) { if (show) {
[[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:@"enter_keyboard_settings"
pageId:@"keyboard_settings"
extra:nil
completion:nil];
// if (!self.settingView) { // if (!self.settingView) {
self.settingView = [[KBSettingView alloc] init]; self.settingView = [[KBSettingView alloc] init];
self.settingView.hidden = YES; self.settingView.hidden = YES;
[self.view addSubview:self.settingView]; [self.contentView addSubview:self.settingView];
[self.settingView mas_makeConstraints:^(MASConstraintMaker *make) { [self.settingView mas_makeConstraints:^(MASConstraintMaker *make) {
// //
make.edges.equalTo(self.keyBoardMainView); make.edges.equalTo(self.contentView);
}]; }];
[self.settingView.backButton addTarget:self action:@selector(onTapSettingsBack) forControlEvents:UIControlEventTouchUpInside]; [self.settingView.backButton addTarget:self action:@selector(onTapSettingsBack) forControlEvents:UIControlEventTouchUpInside];
// } // }
[self.view bringSubviewToFront:self.settingView]; [self.contentView bringSubviewToFront:self.settingView];
// keyBoardMainView self.view // keyBoardMainView self.view
[self.view layoutIfNeeded]; [self.contentView layoutIfNeeded];
CGFloat w = CGRectGetWidth(self.keyBoardMainView.bounds); CGFloat w = CGRectGetWidth(self.keyBoardMainView.bounds);
if (w <= 0) { w = CGRectGetWidth(self.view.bounds); } if (w <= 0) { w = CGRectGetWidth(self.contentView.bounds); }
if (w <= 0) { w = [UIScreen mainScreen].bounds.size.width; } if (w <= 0) { w = [self kb_portraitWidth]; }
self.settingView.transform = CGAffineTransformMakeTranslation(w, 0); self.settingView.transform = CGAffineTransformMakeTranslation(w, 0);
self.settingView.hidden = NO; self.settingView.hidden = NO;
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
@@ -295,8 +343,8 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
} else { } else {
if (!self.settingView || self.settingView.hidden) return; if (!self.settingView || self.settingView.hidden) return;
CGFloat w = CGRectGetWidth(self.keyBoardMainView.bounds); CGFloat w = CGRectGetWidth(self.keyBoardMainView.bounds);
if (w <= 0) { w = CGRectGetWidth(self.view.bounds); } if (w <= 0) { w = CGRectGetWidth(self.contentView.bounds); }
if (w <= 0) { w = [UIScreen mainScreen].bounds.size.width; } if (w <= 0) { w = [self kb_portraitWidth]; }
[UIView animateWithDuration:0.22 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{ [UIView animateWithDuration:0.22 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
self.settingView.transform = CGAffineTransformMakeTranslation(w, 0); self.settingView.transform = CGAffineTransformMakeTranslation(w, 0);
} completion:^(BOOL finished) { } completion:^(BOOL finished) {
@@ -322,19 +370,23 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view]; BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
return; return;
} }
[[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:@"enter_keyboard_subscription_panel"
pageId:@"keyboard_subscription_panel"
extra:nil
completion:nil];
[self showFunctionPanel:NO]; [self showFunctionPanel:NO];
KBKeyboardSubscriptionView *panel = self.subscriptionView; KBKeyboardSubscriptionView *panel = self.subscriptionView;
if (!panel.superview) { if (!panel.superview) {
panel.hidden = YES; panel.hidden = YES;
[self.view addSubview:panel]; [self.contentView addSubview:panel];
[panel mas_makeConstraints:^(MASConstraintMaker *make) { [panel mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.keyBoardMainView); make.edges.equalTo(self.contentView);
}]; }];
} }
[self.view bringSubviewToFront:panel]; [self.contentView bringSubviewToFront:panel];
panel.hidden = NO; panel.hidden = NO;
panel.alpha = 0.0; panel.alpha = 0.0;
CGFloat height = CGRectGetHeight(self.view.bounds); CGFloat height = CGRectGetHeight(self.contentView.bounds);
if (height <= 0) { height = 260; } if (height <= 0) { height = 260; }
panel.transform = CGAffineTransformMakeTranslation(0, height); panel.transform = CGAffineTransformMakeTranslation(0, height);
[panel refreshProductsIfNeeded]; [panel refreshProductsIfNeeded];
@@ -347,7 +399,7 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
- (void)hideSubscriptionPanel { - (void)hideSubscriptionPanel {
if (!self.subscriptionView || self.subscriptionView.hidden) { return; } if (!self.subscriptionView || self.subscriptionView.hidden) { return; }
CGFloat height = CGRectGetHeight(self.subscriptionView.bounds); CGFloat height = CGRectGetHeight(self.subscriptionView.bounds);
if (height <= 0) { height = CGRectGetHeight(self.view.bounds); } if (height <= 0) { height = CGRectGetHeight(self.contentView.bounds); }
KBKeyboardSubscriptionView *panel = self.subscriptionView; KBKeyboardSubscriptionView *panel = self.subscriptionView;
[UIView animateWithDuration:0.22 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{ [UIView animateWithDuration:0.22 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
panel.alpha = 0.0; panel.alpha = 0.0;
@@ -369,22 +421,27 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
NSString *text = key.output ?: key.title ?: @""; NSString *text = key.output ?: key.title ?: @"";
[self.textDocumentProxy insertText:text]; [self.textDocumentProxy insertText:text];
[self kb_updateCurrentWordWithInsertedText:text]; [self kb_updateCurrentWordWithInsertedText:text];
[[KBInputBufferManager shared] appendText:text];
} break; } break;
case KBKeyTypeBackspace: case KBKeyTypeBackspace:
[[KBBackspaceUndoManager shared] recordDeletionSnapshotBefore:self.textDocumentProxy.documentContextBeforeInput [[KBInputBufferManager shared] refreshFromProxyIfPossible:self.textDocumentProxy];
after:self.textDocumentProxy.documentContextAfterInput]; [[KBInputBufferManager shared] prepareSnapshotForDeleteWithContextBefore:self.textDocumentProxy.documentContextBeforeInput
[self.textDocumentProxy deleteBackward]; after:self.textDocumentProxy.documentContextAfterInput];
[[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:self.textDocumentProxy count:1];
[self kb_scheduleContextRefreshResetSuppression:NO]; [self kb_scheduleContextRefreshResetSuppression:NO];
[[KBInputBufferManager shared] applyHoldDeleteCount:1];
break; break;
case KBKeyTypeSpace: case KBKeyTypeSpace:
[[KBBackspaceUndoManager shared] registerNonClearAction]; [[KBBackspaceUndoManager shared] registerNonClearAction];
[self.textDocumentProxy insertText:@" "]; [self.textDocumentProxy insertText:@" "];
[self kb_clearCurrentWord]; [self kb_clearCurrentWord];
[[KBInputBufferManager shared] appendText:@" "];
break; break;
case KBKeyTypeReturn: case KBKeyTypeReturn:
[[KBBackspaceUndoManager shared] registerNonClearAction]; [[KBBackspaceUndoManager shared] registerNonClearAction];
[self.textDocumentProxy insertText:@"\n"]; [self.textDocumentProxy insertText:@"\n"];
[self kb_clearCurrentWord]; [self kb_clearCurrentWord];
[[KBInputBufferManager shared] appendText:@"\n"];
break; break;
case KBKeyTypeGlobe: case KBKeyTypeGlobe:
[self advanceToNextInputMode]; break; [self advanceToNextInputMode]; break;
@@ -402,6 +459,12 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
} }
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didTapToolActionAtIndex:(NSInteger)index { - (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didTapToolActionAtIndex:(NSInteger)index {
NSDictionary *extra = @{@"index": @(index)};
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_toolbar_action"
pageId:@"keyboard_main_panel"
elementId:@"toolbar_action"
extra:extra
completion:nil];
if (index == 0) { if (index == 0) {
[self showFunctionPanel:YES]; [self showFunctionPanel:YES];
[self kb_clearCurrentWord]; [self kb_clearCurrentWord];
@@ -411,6 +474,11 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
} }
- (void)keyBoardMainViewDidTapSettings:(KBKeyBoardMainView *)keyBoardMainView { - (void)keyBoardMainViewDidTapSettings:(KBKeyBoardMainView *)keyBoardMainView {
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_settings_btn"
pageId:@"keyboard_main_panel"
elementId:@"settings_btn"
extra:nil
completion:nil];
[self showSettingView:YES]; [self showSettingView:YES];
} }
@@ -419,19 +487,37 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
[[KBBackspaceUndoManager shared] registerNonClearAction]; [[KBBackspaceUndoManager shared] registerNonClearAction];
[self.textDocumentProxy insertText:emoji]; [self.textDocumentProxy insertText:emoji];
[self kb_clearCurrentWord]; [self kb_clearCurrentWord];
[[KBInputBufferManager shared] appendText:emoji];
} }
- (void)keyBoardMainViewDidTapUndo:(KBKeyBoardMainView *)keyBoardMainView { - (void)keyBoardMainViewDidTapUndo:(KBKeyBoardMainView *)keyBoardMainView {
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_undo_btn"
pageId:@"keyboard_main_panel"
elementId:@"undo_btn"
extra:nil
completion:nil];
[[KBBackspaceUndoManager shared] performUndoFromResponder:self.view]; [[KBBackspaceUndoManager shared] performUndoFromResponder:self.view];
[self kb_scheduleContextRefreshResetSuppression:YES]; [self kb_scheduleContextRefreshResetSuppression:YES];
} }
- (void)keyBoardMainViewDidTapEmojiSearch:(KBKeyBoardMainView *)keyBoardMainView { - (void)keyBoardMainViewDidTapEmojiSearch:(KBKeyBoardMainView *)keyBoardMainView {
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_emoji_search_btn"
pageId:@"keyboard_main_panel"
elementId:@"emoji_search_btn"
extra:nil
completion:nil];
[KBHUD showInfo:KBLocalized(@"Search coming soon")]; [KBHUD showInfo:KBLocalized(@"Search coming soon")];
} }
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didSelectSuggestion:(NSString *)suggestion { - (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didSelectSuggestion:(NSString *)suggestion {
if (suggestion.length == 0) { return; } if (suggestion.length == 0) { return; }
NSDictionary *extra = @{@"suggestion_len": @(suggestion.length)};
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_suggestion_item"
pageId:@"keyboard_main_panel"
elementId:@"suggestion_item"
extra:extra
completion:nil];
[[KBBackspaceUndoManager shared] registerNonClearAction];
NSString *current = self.currentWord ?: @""; NSString *current = self.currentWord ?: @"";
if (current.length > 0) { if (current.length > 0) {
for (NSUInteger i = 0; i < current.length; i++) { for (NSUInteger i = 0; i < current.length; i++) {
@@ -443,6 +529,7 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
[self.suggestionEngine recordSelection:suggestion]; [self.suggestionEngine recordSelection:suggestion];
self.suppressSuggestions = YES; self.suppressSuggestions = YES;
[self.keyBoardMainView kb_setSuggestions:@[]]; [self.keyBoardMainView kb_setSuggestions:@[]];
[[KBInputBufferManager shared] replaceTailWithText:suggestion deleteCount:current.length];
} }
// MARK: - KBFunctionViewDelegate // MARK: - KBFunctionViewDelegate
@@ -453,6 +540,18 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
} }
} }
- (void)functionView:(KBFunctionView *_Nullable)functionView didRightTapToolActionAtIndex:(NSInteger)index{ - (void)functionView:(KBFunctionView *_Nullable)functionView didRightTapToolActionAtIndex:(NSInteger)index{
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_right_action"
pageId:@"keyboard_function_panel"
elementId:@"right_action"
extra:@{@"action": @"login_or_recharge"}
completion:nil];
if (!KBAuthManager.shared.isLoggedIn) {
NSString *schemeStr = [NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr];
// UIApplication App
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
return;
}
NSString *schemeStr = [NSString stringWithFormat:@"%@://recharge?src=keyboard", KB_APP_SCHEME]; NSString *schemeStr = [NSString stringWithFormat:@"%@://recharge?src=keyboard", KB_APP_SCHEME];
NSURL *scheme = [NSURL URLWithString:schemeStr]; NSURL *scheme = [NSURL URLWithString:schemeStr];
// //
@@ -475,10 +574,24 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
#pragma mark - KBKeyboardSubscriptionViewDelegate #pragma mark - KBKeyboardSubscriptionViewDelegate
- (void)subscriptionViewDidTapClose:(KBKeyboardSubscriptionView *)view { - (void)subscriptionViewDidTapClose:(KBKeyboardSubscriptionView *)view {
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_subscription_close_btn"
pageId:@"keyboard_subscription_panel"
elementId:@"close_btn"
extra:nil
completion:nil];
[self hideSubscriptionPanel]; [self hideSubscriptionPanel];
} }
- (void)subscriptionView:(KBKeyboardSubscriptionView *)view didTapPurchaseForProduct:(KBKeyboardSubscriptionProduct *)product { - (void)subscriptionView:(KBKeyboardSubscriptionView *)view didTapPurchaseForProduct:(KBKeyboardSubscriptionProduct *)product {
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
if ([product.productId isKindOfClass:NSString.class] && product.productId.length > 0) {
extra[@"product_id"] = product.productId;
}
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_subscription_product_btn"
pageId:@"keyboard_subscription_panel"
elementId:@"product_btn"
extra:extra.copy
completion:nil];
[self hideSubscriptionPanel]; [self hideSubscriptionPanel];
[self kb_openRechargeForProduct:product]; [self kb_openRechargeForProduct:product];
} }
@@ -553,6 +666,11 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
} }
- (void)onTapSettingsBack { - (void)onTapSettingsBack {
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_settings_back_btn"
pageId:@"keyboard_settings"
elementId:@"back_btn"
extra:nil
completion:nil];
[self showSettingView:NO]; [self showSettingView:NO];
} }
@@ -576,6 +694,21 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
// } // }
} }
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self kb_updateKeyboardLayoutIfNeeded];
}
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
__weak typeof(self) weakSelf = self;
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[weakSelf kb_updateKeyboardLayoutIfNeeded];
} completion:^(__unused id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[weakSelf kb_updateKeyboardLayoutIfNeeded];
}];
}
//- (void)kb_tryOpenContainerForLoginIfNeeded { //- (void)kb_tryOpenContainerForLoginIfNeeded {
// // 使 App Scheme // // 使 App Scheme
// NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@@//login?src=keyboard", KB_APP_SCHEME]]; // NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@@//login?src=keyboard", KB_APP_SCHEME]];
@@ -596,6 +729,7 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
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;
self.contentView.backgroundColor = hasImg ? [UIColor clearColor] : t.keyboardBackground;
self.keyBoardMainView.backgroundColor = hasImg ? [UIColor clearColor] : t.keyboardBackground; self.keyBoardMainView.backgroundColor = hasImg ? [UIColor clearColor] : t.keyboardBackground;
// //
if ([self.keyBoardMainView respondsToSelector:@selector(kb_applyTheme)]) { if ([self.keyBoardMainView respondsToSelector:@selector(kb_applyTheme)]) {
@@ -629,8 +763,62 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
}]; }];
} }
#pragma mark - Layout Helpers
- (CGFloat)kb_portraitWidth {
CGSize s = [UIScreen mainScreen].bounds.size;
return MIN(s.width, s.height);
}
- (CGFloat)kb_keyboardHeightForWidth:(CGFloat)width {
if (width <= 0) { width = KB_DESIGN_WIDTH; }
return kKBKeyboardBaseHeight * (width / KB_DESIGN_WIDTH);
}
- (void)kb_updateKeyboardLayoutIfNeeded {
CGFloat portraitWidth = [self kb_portraitWidth];
CGFloat keyboardHeight = [self kb_keyboardHeightForWidth:portraitWidth];
CGFloat containerWidth = CGRectGetWidth(self.view.superview.bounds);
if (containerWidth <= 0) {
containerWidth = CGRectGetWidth(self.view.window.bounds);
}
if (containerWidth <= 0) {
containerWidth = CGRectGetWidth([UIScreen mainScreen].bounds);
}
BOOL widthChanged = (fabs(self.kb_lastPortraitWidth - portraitWidth) >= 0.5);
BOOL heightChanged = (fabs(self.kb_lastKeyboardHeight - keyboardHeight) >= 0.5);
if (!widthChanged && !heightChanged && containerWidth > 0 && self.kb_widthConstraint.constant == containerWidth) {
return;
}
self.kb_lastPortraitWidth = portraitWidth;
self.kb_lastKeyboardHeight = keyboardHeight;
if (self.kb_heightConstraint) {
self.kb_heightConstraint.constant = keyboardHeight;
}
if (containerWidth > 0 && self.kb_widthConstraint) {
self.kb_widthConstraint.constant = containerWidth;
}
if (self.contentWidthConstraint) {
[self.contentWidthConstraint setOffset:portraitWidth];
}
if (self.contentHeightConstraint) {
[self.contentHeightConstraint setOffset:keyboardHeight];
}
[self.view layoutIfNeeded];
}
#pragma mark - Lazy #pragma mark - Lazy
- (UIView *)contentView {
if (!_contentView) {
_contentView = [[UIView alloc] init];
_contentView.backgroundColor = [UIColor clearColor];
}
return _contentView;
}
- (UIImageView *)bgImageView { - (UIImageView *)bgImageView {
if (!_bgImageView) { if (!_bgImageView) {
_bgImageView = [[UIImageView alloc] init]; _bgImageView = [[UIImageView alloc] init];

View File

@@ -22,6 +22,7 @@
#import "KBHUD.h" // 复用 App 内的 HUD 封装 #import "KBHUD.h" // 复用 App 内的 HUD 封装
#import "KBLocalizationManager.h" // 复用多语言封装(可在扩展内使用) #import "KBLocalizationManager.h" // 复用多语言封装(可在扩展内使用)
#import "KBMaiPointReporter.h" #import "KBMaiPointReporter.h"
//#import "KBLog.h"
// 通用链接Universal Links统一配置 // 通用链接Universal Links统一配置

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 = 34; 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;
@@ -50,6 +57,7 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
@property (nonatomic, strong) UILabel *backspaceClearLabel; @property (nonatomic, strong) UILabel *backspaceClearLabel;
@property (nonatomic, copy) NSString *pendingClearBefore; @property (nonatomic, copy) NSString *pendingClearBefore;
@property (nonatomic, copy) NSString *pendingClearAfter; @property (nonatomic, copy) NSString *pendingClearAfter;
@property (nonatomic, assign) KBClearPhase backspaceClearPhase;
@end @end
@implementation KBBackspaceLongPressHandler @implementation KBBackspaceLongPressHandler
@@ -57,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;
} }
@@ -103,9 +112,17 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
} }
switch (gr.state) { switch (gr.state) {
case UIGestureRecognizerStateBegan: { case UIGestureRecognizerStateBegan: {
[self kb_captureDeletionSnapshotIfNeeded]; 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) { if (self.showClearLabelEnabled) {
[self kb_capturePendingClearSnapshotIfNeeded]; [self kb_capturePendingClearSnapshotIfNeeded];
[[KBInputBufferManager shared] beginPendingClearSnapshot];
} }
self.backspaceHoldToken += 1; self.backspaceHoldToken += 1;
NSUInteger token = self.backspaceHoldToken; NSUInteger token = self.backspaceHoldToken;
@@ -141,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) {
@@ -152,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;
@@ -193,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;
} }
deleteCount += 1; 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;
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;
} }
}]; }];
@@ -229,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];
}); });
@@ -310,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;
@@ -320,6 +389,8 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
} else { } else {
self.pendingClearBefore = nil; self.pendingClearBefore = nil;
self.pendingClearAfter = nil; self.pendingClearAfter = nil;
[[KBInputBufferManager shared] clearPendingClearSnapshot];
[[KBInputBufferManager shared] commitLiveToManual];
} }
} }
@@ -431,13 +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 = self.pendingClearBefore ?: (ivc.textDocumentProxy.documentContextBeforeInput ?: @""); id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
NSString *after = self.pendingClearAfter ?: (ivc.textDocumentProxy.documentContextAfterInput ?: @""); [[KBInputBufferManager shared] refreshFromProxyIfPossible:proxy];
[[KBBackspaceUndoManager shared] recordClearWithContextBefore:before after:after];
} }
self.pendingClearBefore = nil; self.pendingClearBefore = nil;
self.pendingClearAfter = 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];
} }
@@ -450,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)),
@@ -513,7 +646,6 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
} }
- (void)kb_capturePendingClearSnapshotIfNeeded { - (void)kb_capturePendingClearSnapshotIfNeeded {
if ([KBBackspaceUndoManager shared].hasUndo) { return; }
if (self.pendingClearBefore.length > 0 || self.pendingClearAfter.length > 0) { return; } if (self.pendingClearBefore.length > 0 || self.pendingClearAfter.length > 0) { return; }
UIResponder *start = (UIResponder *)([self kb_hostView] ?: self.backspaceButton); UIResponder *start = (UIResponder *)([self kb_hostView] ?: self.backspaceButton);
UIInputViewController *ivc = KBFindInputViewController(start); UIInputViewController *ivc = KBFindInputViewController(start);
@@ -521,6 +653,10 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy; id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
self.pendingClearBefore = proxy.documentContextBeforeInput ?: @""; self.pendingClearBefore = proxy.documentContextBeforeInput ?: @"";
self.pendingClearAfter = proxy.documentContextAfterInput ?: @""; 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

@@ -21,6 +21,9 @@ extern NSNotificationName const KBBackspaceUndoStateDidChangeNotification;
/// 记录一次“立刻清空”删除的内容(基于 documentContextBeforeInput/AfterInput /// 记录一次“立刻清空”删除的内容(基于 documentContextBeforeInput/AfterInput
- (void)recordClearWithContextBefore:(NSString *)before after:(NSString *)after; - (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;

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, copy) NSString *undoText; @property (nonatomic, copy) NSString *undoText;
@property (nonatomic, assign) NSInteger undoAfterLength; @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
@@ -29,55 +54,189 @@ NSNotificationName const KBBackspaceUndoStateDidChangeNotification = @"KBBackspa
if (self = [super init]) { if (self = [super init]) {
_undoText = @""; _undoText = @"";
_undoAfterLength = 0; _undoAfterLength = 0;
_snapshotSource = KBUndoSnapshotSourceNone;
_undoDeletedPieces = [NSMutableArray array];
} }
return self; return self;
} }
- (void)captureAndDeleteBackwardFromProxy:(id<UITextDocumentProxy>)proxy count:(NSUInteger)count {
if (!proxy || count == 0) { return; }
NSString *selected = proxy.selectedText ?: @"";
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];
}
#if DEBUG
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 { - (void)recordDeletionSnapshotBefore:(NSString *)before after:(NSString *)after {
if (self.undoText.length > 0) { return; } 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 *safeBefore = before ?: @"";
NSString *safeAfter = after ?: @""; NSString *safeAfter = after ?: @"";
NSString *full = [safeBefore stringByAppendingString:safeAfter]; NSString *full = [safeBefore stringByAppendingString:safeAfter];
if (full.length == 0) { return; } if (full.length == 0) { return; }
self.undoText = full; self.undoText = full;
self.undoAfterLength = (NSInteger)safeAfter.length; 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 { - (void)recordClearWithContextBefore:(NSString *)before after:(NSString *)after {
if (self.undoText.length == 0) { NSString *pending = [KBInputBufferManager shared].pendingClearSnapshot;
NSString *safeBefore = before ?: @""; NSString *manual = [KBInputBufferManager shared].manualSnapshot;
NSString *safeAfter = after ?: @""; NSString *fallbackText = (pending.length > 0) ? pending : ((manual.length > 0) ? manual : [KBInputBufferManager shared].liveText);
NSString *full = [safeBefore stringByAppendingString:safeAfter];
if (full.length > 0) { NSString *safeBefore = before ?: @"";
self.undoText = full; NSString *safeAfter = after ?: @"";
self.undoAfterLength = (NSInteger)safeAfter.length; 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;
} }
} }
if (self.undoText.length == 0) { 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.undoText.length == 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;
[self kb_clearAllTextForProxy:proxy]; NSString *curBefore = proxy.documentContextBeforeInput ?: @"";
[proxy insertText:self.undoText]; NSString *curAfter = proxy.documentContextAfterInput ?: @"";
if (self.undoAfterLength > 0 && KB_UNDO_LOG(@"performUndo/currentBefore", curBefore);
[proxy respondsToSelector:@selector(adjustTextPositionByCharacterOffset:)]) { KB_UNDO_LOG(@"performUndo/currentAfter", curAfter);
[proxy adjustTextPositionByCharacterOffset:-self.undoAfterLength]; NSString *insertText = [self kb_buildUndoInsertTextFromPieces];
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.undoText = @"";
self.undoAfterLength = 0; self.undoAfterLength = 0;
self.snapshotSource = KBUndoSnapshotSourceNone;
[self.undoDeletedPieces removeAllObjects];
[self kb_updateHasUndo:NO]; [self kb_updateHasUndo:NO];
} }
- (void)registerNonClearAction { - (void)registerNonClearAction {
if (self.undoText.length == 0) { return; } if (!self.hasUndo) { return; }
if (self.undoText.length > 0) {
KB_UNDO_LOG(@"registerNonClearAction/clearUndoText", self.undoText);
}
if (self.undoDeletedPieces.count > 0) {
KB_UNDO_LOG(@"registerNonClearAction/clearDeletedPieces", [self kb_buildUndoInsertTextFromPieces]);
}
self.undoText = @""; self.undoText = @"";
self.undoAfterLength = 0; self.undoAfterLength = 0;
self.snapshotSource = KBUndoSnapshotSourceNone;
[self.undoDeletedPieces removeAllObjects];
[self kb_updateHasUndo:NO]; [self kb_updateHasUndo:NO];
} }
@@ -89,6 +248,29 @@ NSNotificationName const KBBackspaceUndoStateDidChangeNotification = @"KBBackspa
[[NSNotificationCenter defaultCenter] postNotificationName:KBBackspaceUndoStateDidChangeNotification object:self]; [[NSNotificationCenter defaultCenter] postNotificationName:KBBackspaceUndoStateDidChangeNotification object:self];
} }
- (NSString *)kb_lastComposedCharacterFromString:(NSString *)text {
if (text.length == 0) { return @""; }
__block NSString *last = @"";
[text enumerateSubstringsInRange:NSMakeRange(0, text.length)
options:NSStringEnumerationByComposedCharacterSequences | NSStringEnumerationReverse
usingBlock:^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, BOOL *stop) {
last = substring ?: @"";
*stop = YES;
}];
return last ?: @"";
}
- (NSString *)kb_buildUndoInsertTextFromPieces {
if (self.undoDeletedPieces.count == 0) { return @""; }
NSMutableString *result = [NSMutableString string];
for (NSInteger i = (NSInteger)self.undoDeletedPieces.count - 1; i >= 0; i--) {
NSString *piece = self.undoDeletedPieces[(NSUInteger)i] ?: @"";
if (piece.length == 0) { continue; }
[result appendString:piece];
}
return result;
}
static const NSInteger kKBUndoClearMaxRounds = 200; static const NSInteger kKBUndoClearMaxRounds = 200;
- (void)kb_clearAllTextForProxy:(id<UITextDocumentProxy>)proxy { - (void)kb_clearAllTextForProxy:(id<UITextDocumentProxy>)proxy {

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

@@ -4,6 +4,7 @@
#import "KBFunctionTagListView.h" #import "KBFunctionTagListView.h"
#import "KBFunctionTagCell.h" #import "KBFunctionTagCell.h"
#import "KBMaiPointReporter.h"
static NSString * const kKBFunctionTagCellId2 = @"KBFunctionTagCellId2"; static NSString * const kKBFunctionTagCellId2 = @"KBFunctionTagCellId2";
static CGFloat const kKBItemSpace = 4; static CGFloat const kKBItemSpace = 4;
@@ -66,8 +67,23 @@ static CGFloat const kKBItemSpace = 4;
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return kKBItemSpace; } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return kKBItemSpace; }
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
KBTagItemModel *model = (indexPath.item < self.items.count) ? self.items[indexPath.item] : [KBTagItemModel new];
NSInteger personaId = 0;
if ([model isKindOfClass:KBTagItemModel.class]) {
personaId = model.characterId > 0 ? model.characterId : model.tagId;
}
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
extra[@"index"] = @(indexPath.item);
extra[@"id"] = @(personaId);
if ([model.characterName isKindOfClass:NSString.class] && model.characterName.length > 0) {
extra[@"name"] = model.characterName;
}
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_tag_item"
pageId:@"keyboard_function_panel"
elementId:@"renshe_item"
extra:extra.copy
completion:nil];
if ([self.delegate respondsToSelector:@selector(tagListView:didSelectIndex:title:)]) { if ([self.delegate respondsToSelector:@selector(tagListView:didSelectIndex:title:)]) {
KBTagItemModel *model = (indexPath.item < self.items.count) ? self.items[indexPath.item] : [KBTagItemModel new];
[self.delegate tagListView:self didSelectIndex:indexPath.item title:model.characterName]; [self.delegate tagListView:self didSelectIndex:indexPath.item title:model.characterName];
} }
} }

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
@@ -639,6 +640,7 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
// UL App Scheme访 // UL App Scheme访
// 访 KBStreamTextView // 访 KBStreamTextView
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
// KBFunctionTagListView id/name
// + 访访 // + 访访
if ([[KBFullAccessManager shared] hasFullAccess]) { if ([[KBFullAccessManager shared] hasFullAccess]) {
KBTagItemModel *selModel = self.modelArray[indexPath.item]; KBTagItemModel *selModel = self.modelArray[indexPath.item];
@@ -681,6 +683,11 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
#pragma mark - Button Actions #pragma mark - Button Actions
- (void)onTapPaste { - (void)onTapPaste {
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_paste_btn"
pageId:@"keyboard_function_panel"
elementId:@"paste_btn"
extra:nil
completion:nil];
// //
// - iOS16+ App // - iOS16+ App
// - iOS15 // - iOS15
@@ -721,6 +728,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;
@@ -773,24 +782,42 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
- (void)onTapDelete { - (void)onTapDelete {
NSLog(@"点击:删除"); NSLog(@"点击:删除");
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_delete_btn"
pageId:@"keyboard_function_panel"
elementId:@"delete_btn"
extra:nil
completion:nil];
UIInputViewController *ivc = KBFindInputViewController(self); UIInputViewController *ivc = KBFindInputViewController(self);
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy; id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
[[KBBackspaceUndoManager shared] recordDeletionSnapshotBefore:proxy.documentContextBeforeInput [[KBInputBufferManager shared] refreshFromProxyIfPossible:proxy];
after:proxy.documentContextAfterInput]; [[KBInputBufferManager shared] prepareSnapshotForDeleteWithContextBefore:proxy.documentContextBeforeInput
[proxy deleteBackward]; after:proxy.documentContextAfterInput];
[[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:proxy count:1];
[[KBInputBufferManager shared] applyHoldDeleteCount:1];
} }
- (void)onTapClear { - (void)onTapClear {
NSLog(@"点击:清空"); NSLog(@"点击:清空");
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_clear_btn"
pageId:@"keyboard_function_panel"
elementId:@"clear_btn"
extra:nil
completion:nil];
[self.backspaceHandler performClearAction]; [self.backspaceHandler performClearAction];
} }
- (void)onTapSend { - (void)onTapSend {
NSLog(@"点击:发送"); NSLog(@"点击:发送");
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_send_btn"
pageId:@"keyboard_function_panel"
elementId:@"send_btn"
extra:nil
completion:nil];
[[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

@@ -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 ?: @""];
} }
} }
} }

148
KBMaiPointEventTable.md Normal file
View File

@@ -0,0 +1,148 @@
# KBMaiPoint 埋点事件表统一口径iOS / Android / 后端)
## 统一约定(全端一致)
### 1事件类型event_type
- 页面曝光:`page_exposure`
- 点击事件:`click`
> iOS 侧可映射为:`KBMaiPointGenericReportTypePage / KBMaiPointGenericReportTypeClick`
### 2事件名称event_name
- 统一使用 `lower_snake_case`,不绑定任何端的类名/资源名
- 页面曝光统一前缀:`enter_`
- 点击事件统一前缀:`click_`
### 3事件参数value / params
- **所有事件都固定带**`token``NSString`,有就传真实值;没有就传空字符串 `""`
- 建议额外固定带:`page_id`(页面/区域统一ID
- 点击类事件建议固定带:`element_id`(控件/入口统一ID
- 列表/集合类点击建议带:`index``NSInteger`)与业务 `id`(如 `theme_id` / `product_id`
参数示例(最小):
```json
{ "token": "", "page_id": "shop", "element_id": "search_btn" }
```
---
## A. 主工程keyBoard
### A1页面曝光触发VC 的 `viewDidAppear`
| 注释 | 事件类型 | 事件名称 | page_id | iOS 对应页面 | Android 对应页面 | 触发时机 | 事件参数(示例) |
|---|---|---|---|---|---|---|---|
| 进入首页 | page_exposure | enter_home_main | home_main | HomeMainVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_main" }` |
| 进入首页Tab容器 | page_exposure | enter_home | home | HomeVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home" }` |
| 进入热门页 | page_exposure | enter_home_hot | home_hot | HomeHotVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_hot" }` |
| 进入排行榜页 | page_exposure | enter_home_rank | home_rank | HomeRankVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_rank" }` |
| 进入排行榜内容页 | page_exposure | enter_home_rank_content | home_rank_content | HomeRankContentVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_rank_content" }` |
| 进入首页底部弹层 | page_exposure | enter_home_sheet | home_sheet | HomeSheetVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_sheet" }` |
| 进入社区页 | page_exposure | enter_community | community | KBCommunityVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"community" }` |
| 进入搜索页 | page_exposure | enter_search | search | KBSearchVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"search" }` |
| 进入搜索结果页 | page_exposure | enter_search_result | search_result | KBSearchResultVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"search_result" }` |
| 进入商店页 | page_exposure | enter_shop | shop | KBShopVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"shop" }` |
| 进入商店分类列表页 | page_exposure | enter_shop_item_list | shop_item_list | KBShopItemVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"shop_item_list" }` |
| 进入皮肤详情页 | page_exposure | enter_skin_detail | skin_detail | KBSkinDetailVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"skin_detail", "theme_id":"" }` |
| 进入我的页 | page_exposure | enter_my | my | MyVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"my" }` |
| 进入我的皮肤页 | page_exposure | enter_my_skin | my_skin | MySkinVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"my_skin" }` |
| 进入我的键盘配置页 | page_exposure | enter_my_keyboard | my_keyboard | KBMyKeyBoardVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"my_keyboard" }` |
| 进入个人信息页 | page_exposure | enter_person_info | person_info | KBPersonInfoVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"person_info" }` |
| 进入反馈页 | page_exposure | enter_feedback | feedback | KBFeedBackVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"feedback" }` |
| 进入公告页 | page_exposure | enter_notice | notice | KBNoticeVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"notice" }` |
| 进入消费记录页 | page_exposure | enter_consumption_record | consumption_record | KBConsumptionRecordVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"consumption_record" }` |
| 进入VIP购买页 | page_exposure | enter_vip_pay | vip_pay | KBVipPay | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"vip_pay" }` |
| 进入积分充值页 | page_exposure | enter_points_recharge | points_recharge | KBJfPay | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"points_recharge" }` |
| 进入登录页 | page_exposure | enter_login | login | KBLoginVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"login" }` |
| 进入邮箱登录页 | page_exposure | enter_login_email | login_email | KBEmailLoginVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"login_email" }` |
| 进入邮箱注册页 | page_exposure | enter_register_email | register_email | KBEmailRegistVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"register_email" }` |
| 进入注册验证码页 | page_exposure | enter_register_verify_email | register_verify_email | KBRegistVerEmailVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"register_verify_email" }` |
| 进入忘记密码页 | page_exposure | enter_forgot_password_email | forgot_password_email | KBForgetPwdVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"forgot_password_email" }` |
| 进入忘记密码验证码页 | page_exposure | enter_forgot_password_verify | forgot_password_verify | KBForgetVerPwdVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"forgot_password_verify" }` |
| 进入忘记密码新密码页 | page_exposure | enter_forgot_password_newpwd | forgot_password_newpwd | KBForgetPwdNewPwdVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"forgot_password_newpwd" }` |
| 进入键盘权限引导页App内 | page_exposure | enter_keyboard_permission_guide | keyboard_permission_guide | KBPermissionViewController | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"keyboard_permission_guide" }` |
| 进入首次引导页 | page_exposure | enter_guide | guide | KBGuideVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"guide" }` |
| 进入性别选择页 | page_exposure | enter_sex_select | sex_select | KBSexSelVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"sex_select" }` |
| 进入WebView页 | page_exposure | enter_webview | webview | KBWebViewViewController | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"webview", "url":"" }` |
> 测试/工具页(建议仅 DEBUG 或按需接入):`KBTestVC / KBLangTestVC / KBSkinCenterVC / ViewController / LoginViewController / KBLoginSheetViewController`。
### A2点击事件按钮/列表/入口)
| 注释 | 事件类型 | 事件名称 | page_id | element_id | iOS 对应控件/方法 | Android 对应控件 | 触发时机 | 事件参数(示例) |
|---|---|---|---|---|---|---|---|---|
| 首页点击“购买会员” | click | click_home_buy_vip_btn | home_main | buy_vip_btn | HomeHeadView `onTapBuyAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"home_main", "element_id":"buy_vip_btn" }` |
| 首页点击“权限悬浮按钮” | click | click_home_permission_float_btn | home_main | permission_float_btn | HomeMainVC `keyPermissButton.clickDragViewBlock` | Android 自定义 | 点击悬浮按钮 | `{ "token":"", "page_id":"home_main", "element_id":"permission_float_btn" }` |
| 权限引导页点击“去设置” | click | click_permission_open_settings_btn | keyboard_permission_guide | open_settings_btn | KBPermissionViewController `openSettings` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_permission_guide", "element_id":"open_settings_btn" }` |
| 权限引导页点击“关闭” | click | click_permission_close_btn | keyboard_permission_guide | close_btn | KBPermissionViewController `closeButtonAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_permission_guide", "element_id":"close_btn" }` |
| 商店页点击“搜索” | click | click_shop_search_btn | shop | search_btn | KBShopVC `searchBtnAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"shop", "element_id":"search_btn" }` |
| 商店页点击“我的皮肤” | click | click_shop_my_skin_btn | shop | my_skin_btn | KBShopVC `skinBtnAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"shop", "element_id":"my_skin_btn" }` |
| 商店列表点击皮肤卡片 | click | click_shop_theme_card | shop_item_list | theme_card | KBShopItemVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"shop_item_list", "element_id":"theme_card", "theme_id":"", "index":0 }` |
| 皮肤详情点击“下载/购买” | click | click_skin_download_btn | skin_detail | download_btn | KBSkinDetailVC `handleDownloadAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"skin_detail", "element_id":"download_btn", "theme_id":"", "purchased":0 }` |
| 皮肤详情点击“推荐皮肤” | click | click_skin_recommend_card | skin_detail | recommend_card | KBSkinDetailVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"skin_detail", "element_id":"recommend_card", "from_theme_id":"", "to_theme_id":"", "index":0 }` |
| 搜索栏点击搜索 | click | click_search_submit | search | search_submit | KBSearchBarView `onSearch` | Android 自定义 | 点击搜索 | `{ "token":"", "page_id":"search", "element_id":"search_submit", "keyword_len":0 }` |
| 搜索页点击历史词条 | click | click_search_history_item | search | history_item | KBSearchVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"search", "element_id":"history_item", "index":0 }` |
| 搜索页点击“展开更多历史” | click | click_search_history_more | search | history_more | KBSearchVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"search", "element_id":"history_more" }` |
| 搜索页点击“清空历史” | click | click_search_clear_history | search | clear_history | KBSearchVC `clearHistory`header trash | Android 自定义 | 点击垃圾桶 | `{ "token":"", "page_id":"search", "element_id":"clear_history" }` |
| 搜索页点击推荐皮肤 | click | click_search_recommend_theme | search | recommend_theme_card | KBSearchVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"search", "element_id":"recommend_theme_card", "theme_id":"", "index":0 }` |
| 搜索结果页点击皮肤 | click | click_search_result_theme | search_result | result_theme_card | KBSearchResultVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"search_result", "element_id":"result_theme_card", "theme_id":"", "index":0 }` |
| 我的页点击菜单项 | click | click_my_menu_item | my | menu_item | MyVC `didSelectRowAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"my", "element_id":"menu_item", "item_id":"", "item_title":"" }` |
| 我的页点击“邀请”成功复制 | click | click_my_invite_copy | my | invite_copy | MyVC邀请分支 | Android 自定义 | 复制时机 | `{ "token":"", "page_id":"my", "element_id":"invite_copy" }` |
| 反馈页点击提交 | click | click_feedback_commit_btn | feedback | commit_btn | KBFeedBackVC `onTapCommit` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"feedback", "element_id":"commit_btn", "content_len":0 }` |
| 个人信息点击更换头像 | click | click_person_avatar_edit | person_info | avatar_edit | KBPersonInfoVC `onTapAvatarEdit` | Android 自定义 | tapGesture | `{ "token":"", "page_id":"person_info", "element_id":"avatar_edit" }` |
| 个人信息点击退出登录 | click | click_person_logout_btn | person_info | logout_btn | KBPersonInfoVC `onTapLogout` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"person_info", "element_id":"logout_btn" }` |
| 我的键盘页点击保存 | click | click_my_keyboard_save_btn | my_keyboard | save_btn | KBMyKeyBoardVC `onSave` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"my_keyboard", "element_id":"save_btn" }` |
| 我的皮肤页点击编辑/取消 | click | click_my_skin_toggle_edit | my_skin | toggle_edit | MySkinVC `onToggleEdit` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"my_skin", "element_id":"toggle_edit", "editing":0 }` |
| 我的皮肤页点击删除 | click | click_my_skin_delete_btn | my_skin | delete_btn | MySkinVC `onDelete` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"my_skin", "element_id":"delete_btn", "selected_count":0 }` |
| 我的皮肤页点击皮肤(进入详情) | click | click_my_skin_theme_card | my_skin | theme_card | MySkinVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"my_skin", "element_id":"theme_card", "theme_id":"", "index":0 }` |
| 登录页点击 Apple 登录 | click | click_login_apple_btn | login | apple_btn | KBLoginVC `onTapAppleLogin` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login", "element_id":"apple_btn" }` |
| 登录页点击邮箱登录 | click | click_login_email_btn | login | email_btn | KBLoginVC `onTapEmailLogin` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login", "element_id":"email_btn" }` |
| 登录页点击注册 | click | click_login_signup_btn | login | signup_btn | KBLoginVC `onTapSignUp` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login", "element_id":"signup_btn" }` |
| 登录页点击忘记密码 | click | click_login_forgot_btn | login | forgot_btn | KBLoginVC `onTapForgotPassword` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login", "element_id":"forgot_btn" }` |
| 邮箱登录页点击提交 | click | click_login_email_submit_btn | login_email | submit_btn | KBEmailLoginVC `onTapSubmit` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login_email", "element_id":"submit_btn" }` |
| 邮箱注册页点击提交 | click | click_register_email_submit_btn | register_email | submit_btn | KBEmailRegistVC `onTapSubmit` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"register_email", "element_id":"submit_btn" }` |
| 注册验证码页点击确认 | click | click_register_verify_confirm_btn | register_verify_email | confirm_btn | KBRegistVerEmailVC `onTapConfirm` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"register_verify_email", "element_id":"confirm_btn" }` |
| 忘记密码(邮箱)点击下一步 | click | click_forgot_email_next_btn | forgot_password_email | next_btn | KBForgetPwdVC `onTapNext` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"forgot_password_email", "element_id":"next_btn" }` |
| 忘记密码(验证码)点击下一步 | click | click_forgot_verify_next_btn | forgot_password_verify | next_btn | KBForgetVerPwdVC `onTapNext` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"forgot_password_verify", "element_id":"next_btn" }` |
| 忘记密码(新密码)点击下一步 | click | click_forgot_newpwd_next_btn | forgot_password_newpwd | next_btn | KBForgetPwdNewPwdVC `onTapNext` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"forgot_password_newpwd", "element_id":"next_btn" }` |
| VIP页选择套餐 | click | click_vip_select_plan | vip_pay | plan_item | KBVipPay `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"vip_pay", "element_id":"plan_item", "product_id":"", "index":0 }` |
| VIP页点击支付 | click | click_vip_pay_btn | vip_pay | pay_btn | KBVipPay `onTapPayButton` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"vip_pay", "element_id":"pay_btn", "product_id":"" }` |
| VIP页点击恢复购买 | click | click_vip_restore_btn | vip_pay | restore_btn | KBVipPay `onTapRestoreButton` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"vip_pay", "element_id":"restore_btn" }` |
| VIP页点击关闭 | click | click_vip_close_btn | vip_pay | close_btn | KBVipPay `onTapClose` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"vip_pay", "element_id":"close_btn" }` |
| 积分充值页选择商品 | click | click_points_select_product | points_recharge | product_item | KBJfPay `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"points_recharge", "element_id":"product_item", "product_id":"", "index":0 }` |
| 积分充值页点击充值 | click | click_points_pay_btn | points_recharge | pay_btn | KBJfPay `onTapPayButton` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"points_recharge", "element_id":"pay_btn", "product_id":"" }` |
| 引导页点击复制示例1 | click | click_guide_copy_example_1 | guide | copy_example_1 | KBGuideTopCell `kb_onTapQ1` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"guide", "element_id":"copy_example_1" }` |
| 引导页点击复制示例2 | click | click_guide_copy_example_2 | guide | copy_example_2 | KBGuideTopCell `kb_onTapQ2` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"guide", "element_id":"copy_example_2" }` |
---
## B. 键盘扩展CustomKeyboard
### B1页面曝光触发显示/切换时机)
| 注释 | 事件类型 | 事件名称 | page_id | iOS 对应页面/视图 | Android 对应页面 | 触发时机 | 事件参数(示例) |
|---|---|---|---|---|---|---|---|
| 键盘首次显示 | page_exposure | enter_keyboard | keyboard | KeyboardViewController | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"keyboard" }` |
| 打开功能面板 | page_exposure | enter_keyboard_function_panel | keyboard_function_panel | KBFunctionView | Android 自定义 | showFunctionPanel:YES | `{ "token":"", "page_id":"keyboard_function_panel" }` |
| 关闭功能面板(回到主键盘) | page_exposure | enter_keyboard_main_panel | keyboard_main_panel | KBKeyBoardMainView | Android 自定义 | showFunctionPanel:NO | `{ "token":"", "page_id":"keyboard_main_panel" }` |
| 打开设置页 | page_exposure | enter_keyboard_settings | keyboard_settings | KBSettingView | Android 自定义 | showSettingView:YES | `{ "token":"", "page_id":"keyboard_settings" }` |
| 打开订阅/充值面板 | page_exposure | enter_keyboard_subscription_panel | keyboard_subscription_panel | KBKeyboardSubscriptionView | Android 自定义 | showSubscriptionPanel | `{ "token":"", "page_id":"keyboard_subscription_panel" }` |
### B2点击事件键盘工具栏 / 功能面板 / 订阅面板)
| 注释 | 事件类型 | 事件名称 | page_id | element_id | iOS 对应控件/方法 | Android 对应控件 | 触发时机 | 事件参数(示例) |
|---|---|---|---|---|---|---|---|---|
| 点击键盘顶部工具栏index=0 打开功能面板) | click | click_keyboard_toolbar_action | keyboard_main_panel | toolbar_action | KBKeyBoardMainViewDelegate `didTapToolActionAtIndex:` | Android 自定义 | 点击工具栏 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"toolbar_action", "index":0 }` |
| 点击键盘设置按钮 | click | click_keyboard_settings_btn | keyboard_main_panel | settings_btn | `keyBoardMainViewDidTapSettings:` | Android 自定义 | 点击设置 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"settings_btn" }` |
| 点击设置页返回 | click | click_keyboard_settings_back_btn | keyboard_settings | back_btn | KeyboardViewController `onTapSettingsBack` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_settings", "element_id":"back_btn" }` |
| 点击撤销删除 | click | click_keyboard_undo_btn | keyboard_main_panel | undo_btn | `keyBoardMainViewDidTapUndo:` | Android 自定义 | 点击撤销 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"undo_btn" }` |
| 点击表情面板搜索 | click | click_keyboard_emoji_search_btn | keyboard_main_panel | emoji_search_btn | `keyBoardMainViewDidTapEmojiSearch:` | Android 自定义 | 点击搜索 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"emoji_search_btn" }` |
| 点击联想词条 | click | click_keyboard_suggestion_item | keyboard_main_panel | suggestion_item | `didSelectSuggestion:` | Android 自定义 | 点击候选 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"suggestion_item", "index":0 }` |
| 功能面板点击“粘贴” | click | click_keyboard_function_paste_btn | keyboard_function_panel | paste_btn | KBFunctionView `onTapPaste` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"paste_btn" }` |
| 功能面板点击“删除” | click | click_keyboard_function_delete_btn | keyboard_function_panel | delete_btn | KBFunctionView `onTapDelete` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"delete_btn" }` |
| 功能面板点击“清空” | click | click_keyboard_function_clear_btn | keyboard_function_panel | clear_btn | KBFunctionView `onTapClear` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"clear_btn" }` |
| 功能面板点击“发送” | click | click_keyboard_function_send_btn | keyboard_function_panel | send_btn | KBFunctionView `onTapSend` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"send_btn" }` |
| 功能面板点击“人设/标签”条目 | click | click_keyboard_function_tag_item | keyboard_function_panel | renshe_item | KBFunctionTagListView `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"renshe_item", "index":0, "id":456, "name":"" }` |
| 功能面板右侧点击“登录/充值”入口(未登录走登录) | click | click_keyboard_function_right_action | keyboard_function_panel | right_action | KeyboardViewController `didRightTapToolActionAtIndex:` | Android 自定义 | 点击右侧入口 | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"right_action", "action":"login_or_recharge" }` |
| 订阅面板点击关闭 | click | click_keyboard_subscription_close_btn | keyboard_subscription_panel | close_btn | `subscriptionViewDidTapClose:` | Android 自定义 | 点击关闭 | `{ "token":"", "page_id":"keyboard_subscription_panel", "element_id":"close_btn" }` |
| 订阅面板点击购买某商品 | click | click_keyboard_subscription_product_btn | keyboard_subscription_panel | product_btn | `didTapPurchaseForProduct:` | Android 自定义 | 点击购买 | `{ "token":"", "page_id":"keyboard_subscription_panel", "element_id":"product_btn", "product_id":"", "index":0 }` |

BIN
KBMaiPointEventTable.xlsx Normal file

Binary file not shown.

View File

@@ -88,7 +88,8 @@
#if __OBJC__ #if __OBJC__
static inline CGFloat KBScreenWidth(void) { static inline CGFloat KBScreenWidth(void) {
return [UIScreen mainScreen].bounds.size.width; CGSize size = [UIScreen mainScreen].bounds.size;
return MIN(size.width, size.height);
} }
static inline CGFloat KBScaleFactor(void) { static inline CGFloat KBScaleFactor(void) {

20
Shared/KBLog.h Normal file
View File

@@ -0,0 +1,20 @@
//
// KBLog.h
// Shared debug logging macro (App + Extension)
//
#import <Foundation/Foundation.h>
#ifndef KBLOG
// 调试专用日志DEBUG 打印RELEASE 不打印)。尽量显眼,包含函数与行号。
#if DEBUG
#define KBLOG(fmt, ...) do { \
NSString *kb_msg__ = [NSString stringWithFormat:(fmt), ##__VA_ARGS__]; \
NSString *kb_full_msg__ = [NSString stringWithFormat:@"\n==============================[KB DEBUG]==============================\n[Function] %s\n[Line] %d\n%@\n=====================================================================\n", __PRETTY_FUNCTION__, __LINE__, kb_msg__]; \
fprintf(stderr, "%s", kb_full_msg__.UTF8String); \
} while(0)
#else
#define KBLOG(...)
#endif
#endif

View File

@@ -6,29 +6,77 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#ifndef KB_MAI_POINT_BASE_URL #ifndef KB_MAI_POINT_BASE_URL
#define KB_MAI_POINT_BASE_URL @"http://192.168.1.188:35310/api" #define KB_MAI_POINT_BASE_URL @"http://192.168.2.21:35310/api"
#endif #endif
#ifndef KB_MAI_POINT_PATH_NEW_ACCOUNT #ifndef KB_MAI_POINT_PATH_NEW_ACCOUNT
#define KB_MAI_POINT_PATH_NEW_ACCOUNT @"/newAccount" #define KB_MAI_POINT_PATH_NEW_ACCOUNT @"/newAccount"
#endif #endif
#ifndef KB_MAI_POINT_PATH_GENERIC_DATA
#define KB_MAI_POINT_PATH_GENERIC_DATA @"/genericData"
#endif
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
extern NSString * const KBMaiPointErrorDomain; extern NSString * const KBMaiPointErrorDomain;
extern NSString * const KBMaiPointEventTypePageExposure;
extern NSString * const KBMaiPointEventTypeClick;
typedef void (^KBMaiPointReportCompletion)(BOOL success, NSError * _Nullable error); typedef void (^KBMaiPointReportCompletion)(BOOL success, NSError * _Nullable error);
typedef NS_ENUM(NSInteger, KBMaiPointGenericReportType) {
/// 未知/默认类型(按需扩展,具体含义以服务端约定为准)
KBMaiPointGenericReportTypeUnknown = 0,
/// 点击
KBMaiPointGenericReportTypeClick = 1,
/// 曝光
KBMaiPointGenericReportTypeExposure = 2,
/// 页面/进入
KBMaiPointGenericReportTypePage = 3,
};
/// Lightweight reporter for Mai point tracking. Safe for app + extension. /// Lightweight reporter for Mai point tracking. Safe for app + extension.
@interface KBMaiPointReporter : NSObject @interface KBMaiPointReporter : NSObject
+ (instancetype)sharedReporter; + (instancetype)sharedReporter;
/// 统一埋点POST /genericData
/// - eventType: 建议取值 `page_exposure` / `click`
/// - eventName: 统一事件名(如 enter_xxx / click_xxx
/// - value: 事件参数字典(内部会自动注入 token无 token 时为 @""
- (void)reportEventType:(NSString *)eventType
eventName:(NSString *)eventName
value:(nullable NSDictionary *)value
completion:(KBMaiPointReportCompletion _Nullable)completion;
/// 页面曝光快捷方法:内部会补齐 page_id
- (void)reportPageExposureWithEventName:(NSString *)eventName
pageId:(NSString *)pageId
extra:(nullable NSDictionary *)extra
completion:(KBMaiPointReportCompletion _Nullable)completion;
/// 点击快捷方法:内部会补齐 page_id / element_id
- (void)reportClickWithEventName:(NSString *)eventName
pageId:(NSString *)pageId
elementId:(NSString *)elementId
extra:(nullable NSDictionary *)extra
completion:(KBMaiPointReportCompletion _Nullable)completion;
/// POST /newAccount with type + account. /// POST /newAccount with type + account.
- (void)reportNewAccountWithType:(NSString *)type - (void)reportNewAccountWithType:(NSString *)type
account:(NSString *)account account:(nullable NSString *)account
completion:(KBMaiPointReportCompletion _Nullable)completion; completion:(KBMaiPointReportCompletion _Nullable)completion;
//- (void)reportGenericDataWithEvent:(NSString *)event
// account:(nullable NSString *)account
// completion:(KBMaiPointReportCompletion _Nullable)completion;
/// POST /genericData with type + event + account.
- (void)reportGenericDataWithEventType:(KBMaiPointGenericReportType)type
account:(nullable NSString *)account
completion:(KBMaiPointReportCompletion _Nullable)completion;
/// Generic POST for future endpoints. /// Generic POST for future endpoints.
- (void)postPath:(NSString *)path - (void)postPath:(NSString *)path
parameters:(NSDictionary *)parameters parameters:(NSDictionary *)parameters

View File

@@ -4,8 +4,39 @@
// //
#import "KBMaiPointReporter.h" #import "KBMaiPointReporter.h"
#import "KBLog.h"
#import "KBAuthManager.h"
#if __has_include(<UIKit/UIKit.h>)
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#endif
NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain"; NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain";
NSString * const KBMaiPointEventTypePageExposure = @"page_exposure";
NSString * const KBMaiPointEventTypeClick = @"click";
#if DEBUG
static void KBMaiPoint_DebugLogURL(NSURLRequest *request) {
NSString *url = request.URL.absoluteString ?: @"";
KBLOG(@"🍃[KBMaiPointReporter] url=%@", url);
}
static void KBMaiPoint_DebugLogError(NSURLResponse *response, NSError *error) {
if (error) {
NSString *msg = error.localizedDescription ?: @"(no description)";
KBLOG(@"🍃[KBMaiPointReporter] error=%@ domain=%@ code=%ld", msg, error.domain ?: @"", (long)error.code);
return;
}
if ([response isKindOfClass:NSHTTPURLResponse.class]) {
NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
if (statusCode >= 200 && statusCode < 300) {
KBLOG(@"🍃[KBMaiPointReporter] status=HTTP_%ld", (long)statusCode);
} else {
KBLOG(@"🍃[KBMaiPointReporter] error=HTTP_%ld", (long)statusCode);
}
}
}
#endif
@implementation KBMaiPointReporter @implementation KBMaiPointReporter
@@ -18,12 +49,96 @@ NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain";
return reporter; return reporter;
} }
- (NSString *)kb_trimmedStringOrEmpty:(NSString * _Nullable)string {
NSString *value = [string isKindOfClass:[NSString class]] ? string : @"";
return [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] ?: @"";
}
- (NSString *)kb_currentTokenOrEmpty {
NSString *t = [KBAuthManager shared].current.accessToken;
return [self kb_trimmedStringOrEmpty:t];
}
- (void)reportEventType:(NSString *)eventType
eventName:(NSString *)eventName
value:(NSDictionary * _Nullable)value
completion:(KBMaiPointReportCompletion _Nullable)completion {
NSString *trimmedType = [self kb_trimmedStringOrEmpty:eventType];
NSString *trimmedName = [self kb_trimmedStringOrEmpty:eventName];
if (trimmedType.length == 0 || trimmedName.length == 0) {
NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"Invalid parameter"}];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(NO, error);
});
}
return;
}
NSMutableDictionary *val = [NSMutableDictionary dictionary];
if ([value isKindOfClass:[NSDictionary class]] && value.count > 0) {
[val addEntriesFromDictionary:value];
}
if (![val[@"token"] isKindOfClass:NSString.class]) {
val[@"token"] = [self kb_currentTokenOrEmpty];
} else {
// tokennil -> @"" / trim
val[@"token"] = [self kb_trimmedStringOrEmpty:val[@"token"]];
}
NSDictionary *params = @{
// eventId eventName
@"eventType": trimmedType,
@"eventName": trimmedName,
@"eventId": trimmedName,
@"value": val.copy
};
[self postPath:KB_MAI_POINT_PATH_GENERIC_DATA parameters:params completion:completion];
}
- (void)reportPageExposureWithEventName:(NSString *)eventName
pageId:(NSString *)pageId
extra:(NSDictionary * _Nullable)extra
completion:(KBMaiPointReportCompletion _Nullable)completion {
NSString *pid = [self kb_trimmedStringOrEmpty:pageId];
NSMutableDictionary *val = [NSMutableDictionary dictionary];
if (pid.length > 0) {
val[@"page_id"] = pid;
}
if ([extra isKindOfClass:[NSDictionary class]] && extra.count > 0) {
[val addEntriesFromDictionary:extra];
}
[self reportEventType:KBMaiPointEventTypePageExposure eventName:eventName value:val completion:completion];
}
- (void)reportClickWithEventName:(NSString *)eventName
pageId:(NSString *)pageId
elementId:(NSString *)elementId
extra:(NSDictionary * _Nullable)extra
completion:(KBMaiPointReportCompletion _Nullable)completion {
NSString *pid = [self kb_trimmedStringOrEmpty:pageId];
NSString *eid = [self kb_trimmedStringOrEmpty:elementId];
NSMutableDictionary *val = [NSMutableDictionary dictionary];
if (pid.length > 0) {
val[@"page_id"] = pid;
}
if (eid.length > 0) {
val[@"element_id"] = eid;
}
if ([extra isKindOfClass:[NSDictionary class]] && extra.count > 0) {
[val addEntriesFromDictionary:extra];
}
[self reportEventType:KBMaiPointEventTypeClick eventName:eventName value:val completion:completion];
}
- (void)reportNewAccountWithType:(NSString *)type - (void)reportNewAccountWithType:(NSString *)type
account:(NSString *)account account:(NSString * _Nullable)account
completion:(KBMaiPointReportCompletion)completion { completion:(KBMaiPointReportCompletion _Nullable)completion {
NSString *trimmedType = [type stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; NSString *trimmedType = [self kb_trimmedStringOrEmpty:type];
NSString *trimmedAccount = [account stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; NSString *trimmedAccount = [self kb_trimmedStringOrEmpty:account];
if (trimmedType.length == 0 || trimmedAccount.length == 0) { if (trimmedType.length == 0) {
NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain
code:-1 code:-1
userInfo:@{NSLocalizedDescriptionKey: @"Invalid parameter"}]; userInfo:@{NSLocalizedDescriptionKey: @"Invalid parameter"}];
@@ -37,14 +152,43 @@ NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain";
NSDictionary *params = @{ NSDictionary *params = @{
@"type": trimmedType, @"type": trimmedType,
@"account": trimmedAccount @"account": trimmedAccount ?: @"",
@"token": [self kb_currentTokenOrEmpty]
}; };
[self postPath:KB_MAI_POINT_PATH_NEW_ACCOUNT parameters:params completion:completion]; [self postPath:KB_MAI_POINT_PATH_NEW_ACCOUNT parameters:params completion:completion];
} }
//- (void)reportGenericDataWithEvent:(NSString *)event
// account:(NSString * _Nullable)account
// completion:(KBMaiPointReportCompletion _Nullable)completion {
// [self reportGenericDataWithType:KBMaiPointGenericReportTypeUnknown
// event:event
// account:account
// completion:completion];
//}
- (void)reportGenericDataWithEventType:(KBMaiPointGenericReportType)eventType
account:(nullable NSString *)account
completion:(KBMaiPointReportCompletion _Nullable)completion{
// eventName
NSString *typeStr = @"unknown";
switch (eventType) {
case KBMaiPointGenericReportTypeClick: typeStr = KBMaiPointEventTypeClick; break;
case KBMaiPointGenericReportTypeExposure: typeStr = @"exposure"; break;
case KBMaiPointGenericReportTypePage: typeStr = KBMaiPointEventTypePageExposure; break;
default: break;
}
NSMutableDictionary *val = [NSMutableDictionary dictionary];
NSString *trimmedAccount = [self kb_trimmedStringOrEmpty:account];
if (trimmedAccount.length > 0) {
val[@"account"] = trimmedAccount;
}
[self reportEventType:typeStr eventName:@"generic_event" value:val completion:completion];
}
- (void)postPath:(NSString *)path - (void)postPath:(NSString *)path
parameters:(NSDictionary *)parameters parameters:(NSDictionary *)parameters
completion:(KBMaiPointReportCompletion)completion { completion:(KBMaiPointReportCompletion _Nullable)completion {
if (path.length == 0 || ![parameters isKindOfClass:[NSDictionary class]]) { if (path.length == 0 || ![parameters isKindOfClass:[NSDictionary class]]) {
NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain
code:-1 code:-1
@@ -89,6 +233,10 @@ NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
request.HTTPBody = body; request.HTTPBody = body;
#if DEBUG
KBMaiPoint_DebugLogURL(request);
#endif
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration]; NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
@@ -115,6 +263,10 @@ NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain";
} }
} }
#if DEBUG
KBMaiPoint_DebugLogError(response, finalError);
#endif
if (completion) { if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
completion(success, finalError); completion(success, finalError);
@@ -125,3 +277,123 @@ NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain";
} }
@end @end
#if __has_include(<UIKit/UIKit.h>)
// ============================
// viewDidAppear
// VC VC
// ============================
static NSDictionary<NSString *, NSDictionary *> *KBMaiPoint_PageExposureMap(void) {
static NSDictionary<NSString *, NSDictionary *> *m;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
m = @{
//
@"HomeMainVC": @{@"event_name": @"enter_home_main", @"page_id": @"home_main"},
@"HomeVC": @{@"event_name": @"enter_home", @"page_id": @"home"},
@"HomeHotVC": @{@"event_name": @"enter_home_hot", @"page_id": @"home_hot"},
@"HomeRankVC": @{@"event_name": @"enter_home_rank", @"page_id": @"home_rank"},
@"HomeRankContentVC": @{@"event_name": @"enter_home_rank_content", @"page_id": @"home_rank_content"},
@"HomeSheetVC": @{@"event_name": @"enter_home_sheet", @"page_id": @"home_sheet"},
@"KBCommunityVC": @{@"event_name": @"enter_community", @"page_id": @"community"},
@"KBSearchVC": @{@"event_name": @"enter_search", @"page_id": @"search"},
@"KBSearchResultVC": @{@"event_name": @"enter_search_result", @"page_id": @"search_result"},
@"KBShopVC": @{@"event_name": @"enter_shop", @"page_id": @"shop"},
@"KBShopItemVC": @{@"event_name": @"enter_shop_item_list", @"page_id": @"shop_item_list"},
@"KBSkinDetailVC": @{@"event_name": @"enter_skin_detail", @"page_id": @"skin_detail"},
@"MyVC": @{@"event_name": @"enter_my", @"page_id": @"my"},
@"MySkinVC": @{@"event_name": @"enter_my_skin", @"page_id": @"my_skin"},
@"KBMyKeyBoardVC": @{@"event_name": @"enter_my_keyboard", @"page_id": @"my_keyboard"},
@"KBPersonInfoVC": @{@"event_name": @"enter_person_info", @"page_id": @"person_info"},
@"KBFeedBackVC": @{@"event_name": @"enter_feedback", @"page_id": @"feedback"},
@"KBNoticeVC": @{@"event_name": @"enter_notice", @"page_id": @"notice"},
@"KBConsumptionRecordVC": @{@"event_name": @"enter_consumption_record", @"page_id": @"consumption_record"},
@"KBVipPay": @{@"event_name": @"enter_vip_pay", @"page_id": @"vip_pay"},
@"KBJfPay": @{@"event_name": @"enter_points_recharge", @"page_id": @"points_recharge"},
@"KBLoginVC": @{@"event_name": @"enter_login", @"page_id": @"login"},
@"KBEmailLoginVC": @{@"event_name": @"enter_login_email", @"page_id": @"login_email"},
@"KBEmailRegistVC": @{@"event_name": @"enter_register_email", @"page_id": @"register_email"},
@"KBRegistVerEmailVC": @{@"event_name": @"enter_register_verify_email", @"page_id": @"register_verify_email"},
@"KBForgetPwdVC": @{@"event_name": @"enter_forgot_password_email", @"page_id": @"forgot_password_email"},
@"KBForgetVerPwdVC": @{@"event_name": @"enter_forgot_password_verify", @"page_id": @"forgot_password_verify"},
@"KBForgetPwdNewPwdVC": @{@"event_name": @"enter_forgot_password_newpwd", @"page_id": @"forgot_password_newpwd"},
@"KBPermissionViewController": @{@"event_name": @"enter_keyboard_permission_guide", @"page_id": @"keyboard_permission_guide"},
@"KBGuideVC": @{@"event_name": @"enter_guide", @"page_id": @"guide"},
@"KBSexSelVC": @{@"event_name": @"enter_sex_select", @"page_id": @"sex_select"},
@"KBWebViewViewController": @{@"event_name": @"enter_webview", @"page_id": @"webview"},
//
@"KeyboardViewController": @{@"event_name": @"enter_keyboard", @"page_id": @"keyboard"},
};
});
return m;
}
static inline void KBMaiPoint_SwizzleInstanceMethod(Class cls, SEL originalSel, SEL swizzledSel) {
Method original = class_getInstanceMethod(cls, originalSel);
Method swizzled = class_getInstanceMethod(cls, swizzledSel);
if (!original || !swizzled) return;
BOOL added = class_addMethod(cls,
originalSel,
method_getImplementation(swizzled),
method_getTypeEncoding(swizzled));
if (added) {
class_replaceMethod(cls,
swizzledSel,
method_getImplementation(original),
method_getTypeEncoding(original));
} else {
method_exchangeImplementations(original, swizzled);
}
}
@interface UIViewController (KBMaiPointAutoReport)
@end
@implementation UIViewController (KBMaiPointAutoReport)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
KBMaiPoint_SwizzleInstanceMethod(self, @selector(viewDidAppear:), @selector(kb_maipoint_viewDidAppear:));
});
}
- (void)kb_maipoint_viewDidAppear:(BOOL)animated {
[self kb_maipoint_viewDidAppear:animated];
NSString *clsName = NSStringFromClass(self.class);
NSDictionary *cfg = KBMaiPoint_PageExposureMap()[clsName];
if (![cfg isKindOfClass:NSDictionary.class]) { return; }
NSString *eventName = cfg[@"event_name"];
NSString *pageId = cfg[@"page_id"];
if (![eventName isKindOfClass:NSString.class] || ![pageId isKindOfClass:NSString.class]) { return; }
//
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
if ([clsName isEqualToString:@"KBSkinDetailVC"]) {
id themeId = nil;
@try { themeId = [self valueForKey:@"themeId"]; } @catch (__unused NSException *e) { themeId = nil; }
if ([themeId isKindOfClass:NSString.class] && ((NSString *)themeId).length > 0) {
extra[@"theme_id"] = themeId;
}
} else if ([clsName isEqualToString:@"KBWebViewViewController"]) {
id url = nil;
@try { url = [self valueForKey:@"url"]; } @catch (__unused NSException *e) { url = nil; }
if ([url isKindOfClass:NSString.class] && ((NSString *)url).length > 0) {
extra[@"url"] = url;
}
}
[[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:eventName
pageId:pageId
extra:(extra.count > 0 ? extra.copy : nil)
completion:nil];
}
@end
#endif

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

@@ -225,6 +225,7 @@
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 */; }; A1B2C9032FBD000100000001 /* KBBackspaceLongPressHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C9022FBD000100000001 /* KBBackspaceLongPressHandler.m */; };
A1B2C9052FBD000200000001 /* KBBackspaceUndoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C9042FBD000200000001 /* KBBackspaceUndoManager.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 */; };
@@ -639,6 +640,8 @@
A1B2C9022FBD000100000001 /* KBBackspaceLongPressHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBBackspaceLongPressHandler.m; 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>"; }; 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>"; }; 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>"; };
@@ -841,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>";
@@ -1947,6 +1952,7 @@
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 */,

View File

@@ -37,6 +37,10 @@
[self kb_updateBackButtonVisibility]; [self kb_updateBackButtonVisibility];
} }
- (void)dealloc{
KBLOG(@"页面销毁 -- 💥💥 -- %@",[self class]);
}
#pragma mark - Custom NavBar #pragma mark - Custom NavBar

View File

@@ -181,11 +181,21 @@
/// ///
- (void)kb_onTapQ1 { - (void)kb_onTapQ1 {
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_guide_copy_example_1"
pageId:@"guide"
elementId:@"copy_example_1"
extra:nil
completion:nil];
[self kb_copyTextToPasteboard:[self.q1Button titleForState:UIControlStateNormal]]; [self kb_copyTextToPasteboard:[self.q1Button titleForState:UIControlStateNormal]];
} }
/// ///
- (void)kb_onTapQ2 { - (void)kb_onTapQ2 {
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_guide_copy_example_2"
pageId:@"guide"
elementId:@"copy_example_2"
extra:nil
completion:nil];
[self kb_copyTextToPasteboard:[self.q2Button titleForState:UIControlStateNormal]]; [self kb_copyTextToPasteboard:[self.q2Button titleForState:UIControlStateNormal]];
} }

View File

@@ -164,6 +164,11 @@
#pragma mark - Actions #pragma mark - Actions
- (void)onTapBuyAction { - (void)onTapBuyAction {
// if (self.onTapBuy) { self.onTapBuy(); } // if (self.onTapBuy) { self.onTapBuy(); }
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_home_buy_vip_btn"
pageId:@"home_main"
elementId:@"buy_vip_btn"
extra:nil
completion:nil];
// //
if (![KBUserSessionManager shared].isLoggedIn) { if (![KBUserSessionManager shared].isLoggedIn) {
[[KBUserSessionManager shared] goLoginVC]; [[KBUserSessionManager shared] goLoginVC];

View File

@@ -92,7 +92,7 @@
if (joined) { if (joined) {
// //
[self.actionButton setTitle:@"✓" forState:UIControlStateNormal]; [self.actionButton setTitle:@"✓" forState:UIControlStateNormal];
[self.actionButton setTitleColor:[UIColor colorWithWhite:0.45 alpha:1] forState:UIControlStateNormal]; [self.actionButton setTitleColor:[UIColor colorWithHex:0xBCBCBC] forState:UIControlStateNormal];
self.actionButton.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1]; self.actionButton.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1];
} else { } else {
// 绿 // 绿

View File

@@ -59,6 +59,7 @@
self.downloadLabel.text = character.download ?: @""; self.downloadLabel.text = character.download ?: @"";
self.descLabel.text = character.characterBackground ?: @""; self.descLabel.text = character.characterBackground ?: @"";
[self.avatarView kb_setImageURL:character.avatarUrl placeholder:KBAvatarPlaceholderImage]; [self.avatarView kb_setImageURL:character.avatarUrl placeholder:KBAvatarPlaceholderImage];
[self kb_updateSaveButtonWithAdded:character.added];
} }
#pragma mark - Build UI #pragma mark - Build UI
@@ -138,6 +139,24 @@
if (self.closeHandler) self.closeHandler(); if (self.closeHandler) self.closeHandler();
} }
#pragma mark - UI State
- (void)kb_updateSaveButtonWithAdded:(BOOL)added {
if (added) {
self.saveButton.enabled = NO;
self.saveButton.backgroundColor = [UIColor colorWithWhite:0.93 alpha:1.0];
[self.saveButton setTitle:@"✓" forState:UIControlStateNormal];
[self.saveButton setTitle:@"✓" forState:UIControlStateDisabled];
[self.saveButton setTitleColor:[UIColor colorWithWhite:0.55 alpha:1.0] forState:UIControlStateNormal];
[self.saveButton setTitleColor:[UIColor colorWithWhite:0.55 alpha:1.0] forState:UIControlStateDisabled];
} else {
self.saveButton.enabled = YES;
self.saveButton.backgroundColor = [UIColor colorWithRed:0.02 green:0.75 blue:0.67 alpha:1.0];
[self.saveButton setTitle:@"Save" forState:UIControlStateNormal];
[self.saveButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
}
}
#pragma mark - Helpers #pragma mark - Helpers
- (UIImage *)placeholderAvatar { - (UIImage *)placeholderAvatar {
@@ -246,4 +265,3 @@
} }
@end @end

View File

@@ -281,7 +281,7 @@
if (added) { if (added) {
// + // +
[button setTitle:@"✓" forState:UIControlStateNormal]; [button setTitle:@"✓" forState:UIControlStateNormal];
[button setTitleColor:[UIColor colorWithWhite:0.45 alpha:1.0] forState:UIControlStateNormal]; [button setTitleColor:[UIColor colorWithHex:0xBCBCBC] forState:UIControlStateNormal];
button.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1.0]; button.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1.0];
button.enabled = NO; button.enabled = NO;
} else { } else {

View File

@@ -284,9 +284,44 @@
pop.isClickBgDismiss = YES; // pop.isClickBgDismiss = YES; //
pop.cornerRadius = 0; // view pop.cornerRadius = 0; // view
KBWeakSelf
__weak typeof(pop) weakPop = pop; __weak typeof(pop) weakPop = pop;
content.saveHandler = ^{ [weakPop dismiss]; }; content.saveHandler = ^{
content.closeHandler = ^{ [weakPop dismiss]; }; [weakPop dismiss];
if (![KBUserSessionManager shared].isLoggedIn) {
[[KBUserSessionManager shared] goLoginVC];
return;
}
__strong typeof(weakSelf) self = weakSelf;
if (!self) { return; }
if (indexPath.row >= self.listCharacters.count) { return; }
KBCharacter *mc = self.listCharacters[indexPath.row];
if (mc.added) { return; }
NSString *cidStr = mc.ID ?: @"";
if (cidStr.length == 0) { return; }
NSNumber *cid = @([cidStr integerValue]);
NSString *emoji = mc.emoji ? mc.emoji : @"";
[self.homeVM addUserCharacterWithId:cid emoji : emoji
completion:^(BOOL success, NSError * _Nullable error) {
if (success) {
mc.added = YES;
NSMutableArray *m = [self.listCharacters mutableCopy];
[m replaceObjectAtIndex:indexPath.row withObject:mc];
self.listCharacters = [m copy];
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self kb_refreshTopThreeView];
}
}];
};
content.closeHandler = ^{
[weakPop dismiss];
};
[pop pop]; [pop pop];

View File

@@ -48,6 +48,11 @@
KBWeakSelf KBWeakSelf
self.keyPermissButton.clickDragViewBlock = ^(WMDragView *dragView){ self.keyPermissButton.clickDragViewBlock = ^(WMDragView *dragView){
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_home_permission_float_btn"
pageId:@"home_main"
elementId:@"permission_float_btn"
extra:nil
completion:nil];
KBGuideVC *vc = [KBGuideVC new]; KBGuideVC *vc = [KBGuideVC new];
[weakSelf.navigationController pushViewController:vc animated:true]; [weakSelf.navigationController pushViewController:vc animated:true];
}; };

View File

@@ -210,9 +210,42 @@
pop.isClickBgDismiss = YES; // pop.isClickBgDismiss = YES; //
pop.cornerRadius = 0; // view pop.cornerRadius = 0; // view
KBWeakSelf
__weak typeof(pop) weakPop = pop; __weak typeof(pop) weakPop = pop;
content.saveHandler = ^{ [weakPop dismiss]; }; content.saveHandler = ^{
content.closeHandler = ^{ [weakPop dismiss]; }; [weakPop dismiss];
if (![KBUserSessionManager shared].isLoggedIn) {
[[KBUserSessionManager shared] goLoginVC];
return;
}
__strong typeof(weakSelf) self = weakSelf;
if (!self) { return; }
if (indexPath.item >= self.characters.count) { return; }
KBCharacter *mc = self.characters[indexPath.item];
if (mc.added) { return; }
NSString *cidStr = mc.ID ?: @"";
if (cidStr.length == 0) { return; }
NSNumber *cid = @([cidStr integerValue]);
NSString *emoji = mc.emoji ? mc.emoji : @"";
[self.homeVM addUserCharacterWithId:cid emoji : emoji
completion:^(BOOL success, NSError * _Nullable error) {
if (success) {
mc.added = YES;
NSMutableArray *m = [self.characters mutableCopy];
[m replaceObjectAtIndex:indexPath.item withObject:mc];
self.characters = [m copy];
[self.collectionView reloadItemsAtIndexPaths:@[indexPath]];
}
}];
};
content.closeHandler = ^{
[weakPop dismiss];
};
[pop pop]; [pop pop];
} }

View File

@@ -226,6 +226,11 @@
KBLOG(@"KBEmailLoginVC onTapSubmit, email=%@, pwdLen=%zd", KBLOG(@"KBEmailLoginVC onTapSubmit, email=%@, pwdLen=%zd",
self.emailTextField.text, self.emailTextField.text,
self.passwordTextField.text.length); self.passwordTextField.text.length);
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_login_email_submit_btn"
pageId:@"login_email"
elementId:@"submit_btn"
extra:nil
completion:nil];
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; KBWeakSelf;

View File

@@ -265,6 +265,12 @@
NSString *pwd = self.passwordTextField.text ?: @""; NSString *pwd = self.passwordTextField.text ?: @"";
NSString *repeat = self.repeatPasswordTextField.text ?: @""; NSString *repeat = self.repeatPasswordTextField.text ?: @"";
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_register_email_submit_btn"
pageId:@"register_email"
elementId:@"submit_btn"
extra:nil
completion:nil];
if (email.length == 0 || pwd.length == 0 || repeat.length == 0) { if (email.length == 0 || pwd.length == 0 || repeat.length == 0) {
[KBHUD showInfo:KBLocalized(@"Please complete all fields")]; [KBHUD showInfo:KBLocalized(@"Please complete all fields")];
return; return;

View File

@@ -7,7 +7,7 @@
#import "KBForgetPwdNewPwdVC.h" #import "KBForgetPwdNewPwdVC.h"
#import "KBLoginVM.h" #import "KBLoginVM.h"
#import "KBEmailRegistVC.h" #import "KBLoginVC.h"
@interface KBForgetPwdNewPwdVC () <UITextFieldDelegate> @interface KBForgetPwdNewPwdVC () <UITextFieldDelegate>
@property (nonatomic, strong) UILabel *titleLabel; // Reset Password @property (nonatomic, strong) UILabel *titleLabel; // Reset Password
@@ -114,6 +114,12 @@
NSString *pwd = self.passwordTextField.text ?: @""; NSString *pwd = self.passwordTextField.text ?: @"";
NSString *repeat = self.repeatPasswordTextField.text ?: @""; NSString *repeat = self.repeatPasswordTextField.text ?: @"";
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_forgot_newpwd_next_btn"
pageId:@"forgot_password_newpwd"
elementId:@"next_btn"
extra:nil
completion:nil];
if (pwd.length == 0 || repeat.length == 0) { if (pwd.length == 0 || repeat.length == 0) {
[KBHUD showInfo:KBLocalized(@"Please complete all fields")]; [KBHUD showInfo:KBLocalized(@"Please complete all fields")];
return; return;
@@ -131,7 +137,7 @@
if (success) { if (success) {
UIViewController *targetVC = nil; UIViewController *targetVC = nil;
for (UIViewController *vc in KB_CURRENT_NAV.viewControllers) { for (UIViewController *vc in KB_CURRENT_NAV.viewControllers) {
if ([vc isKindOfClass:[KBEmailRegistVC class]]) { if ([vc isKindOfClass:[KBLoginVC class]]) {
targetVC = vc; targetVC = vc;
break; break;
} }

View File

@@ -66,6 +66,11 @@
- (void)onTapNext { - (void)onTapNext {
NSString *email = [self.emailTextField.text ?: @"" stringByTrimmingCharactersInSet: NSString *email = [self.emailTextField.text ?: @"" stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]]; [NSCharacterSet whitespaceAndNewlineCharacterSet]];
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_forgot_email_next_btn"
pageId:@"forgot_password_email"
elementId:@"next_btn"
extra:nil
completion:nil];
if (email.length == 0) { if (email.length == 0) {
[KBHUD showInfo:KBLocalized(@"Enter Email Address")]; [KBHUD showInfo:KBLocalized(@"Enter Email Address")];
return; return;

View File

@@ -76,6 +76,11 @@
- (void)onTapNext { - (void)onTapNext {
NSString *code = self.codeInputView.textValue ?: @""; NSString *code = self.codeInputView.textValue ?: @"";
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_forgot_verify_next_btn"
pageId:@"forgot_password_verify"
elementId:@"next_btn"
extra:nil
completion:nil];
if (code.length == 0) { if (code.length == 0) {
[KBHUD showInfo:KBLocalized(@"Enter Email Verification Code")]; [KBHUD showInfo:KBLocalized(@"Enter Email Verification Code")];
return; return;

View File

@@ -169,6 +169,11 @@
- (void)onTapAppleLogin { - (void)onTapAppleLogin {
KBLOG(@"onTapAppleLogin"); KBLOG(@"onTapAppleLogin");
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_login_apple_btn"
pageId:@"login"
elementId:@"apple_btn"
extra:nil
completion:nil];
[[KBLoginVM shared] signInWithAppleFromViewController:KB_CURRENT_NAV completion:^(BOOL success, NSError * _Nullable error) { [[KBLoginVM shared] signInWithAppleFromViewController:KB_CURRENT_NAV completion:^(BOOL success, NSError * _Nullable error) {
if (success) { if (success) {
[KBHUD showInfo:KBLocalized(@"Signed in successfully")]; [KBHUD showInfo:KBLocalized(@"Signed in successfully")];
@@ -190,6 +195,11 @@
- (void)onTapEmailLogin { - (void)onTapEmailLogin {
// //
KBLOG(@"onTapEmailLogin"); KBLOG(@"onTapEmailLogin");
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_login_email_btn"
pageId:@"login"
elementId:@"email_btn"
extra:nil
completion:nil];
KBEmailLoginVC *vc = [[KBEmailLoginVC alloc] init]; KBEmailLoginVC *vc = [[KBEmailLoginVC alloc] init];
UINavigationController *nav = KB_CURRENT_NAV; UINavigationController *nav = KB_CURRENT_NAV;
if ([nav isKindOfClass:[BaseNavigationController class]]) { if ([nav isKindOfClass:[BaseNavigationController class]]) {
@@ -207,6 +217,11 @@
- (void)onTapSignUp { - (void)onTapSignUp {
// //
KBLOG(@"onTapSignUp"); KBLOG(@"onTapSignUp");
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_login_signup_btn"
pageId:@"login"
elementId:@"signup_btn"
extra:nil
completion:nil];
KBEmailRegistVC *vc = [[KBEmailRegistVC alloc] init]; KBEmailRegistVC *vc = [[KBEmailRegistVC alloc] init];
UINavigationController *nav = KB_CURRENT_NAV; UINavigationController *nav = KB_CURRENT_NAV;
if ([nav isKindOfClass:[BaseNavigationController class]]) { if ([nav isKindOfClass:[BaseNavigationController class]]) {
@@ -219,6 +234,11 @@
- (void)onTapForgotPassword { - (void)onTapForgotPassword {
// //
KBLOG(@"onTapForgotPassword"); KBLOG(@"onTapForgotPassword");
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_login_forgot_btn"
pageId:@"login"
elementId:@"forgot_btn"
extra:nil
completion:nil];
KBForgetPwdVC *vc = [[KBForgetPwdVC alloc] init]; KBForgetPwdVC *vc = [[KBForgetPwdVC alloc] init];
UINavigationController *nav = KB_CURRENT_NAV; UINavigationController *nav = KB_CURRENT_NAV;
if ([nav isKindOfClass:[BaseNavigationController class]]) { if ([nav isKindOfClass:[BaseNavigationController class]]) {

View File

@@ -89,6 +89,11 @@
- (void)onTapConfirm { - (void)onTapConfirm {
NSString *code = self.codeInputView.textValue ?: @""; NSString *code = self.codeInputView.textValue ?: @"";
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_register_verify_confirm_btn"
pageId:@"register_verify_email"
elementId:@"confirm_btn"
extra:nil
completion:nil];
if (code.length == 0) { if (code.length == 0) {
[KBHUD showInfo:KBLocalized(@"Enter Email Verification Code")]; [KBHUD showInfo:KBLocalized(@"Enter Email Verification Code")];
return; return;

View File

@@ -90,6 +90,12 @@
[KBHUD showInfo:KBLocalized(@"Please Enter The Content")]; [KBHUD showInfo:KBLocalized(@"Please Enter The Content")];
return; return;
} }
NSDictionary *extra = @{@"content_len": @(content.length)};
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_feedback_commit_btn"
pageId:@"feedback"
elementId:@"commit_btn"
extra:extra
completion:nil];
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
[self.viewModel submitFeedbackWithContent:content completion:^(BOOL success, NSError * _Nullable error) { [self.viewModel submitFeedbackWithContent:content completion:^(BOOL success, NSError * _Nullable error) {
if (!success) { return; } if (!success) { return; }

View File

@@ -257,6 +257,11 @@ static NSString * const kKBMyKeyboardCellId = @"kKBMyKeyboardCellId";
- (void)onSave { - (void)onSave {
// //
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_my_keyboard_save_btn"
pageId:@"my_keyboard"
elementId:@"save_btn"
extra:nil
completion:nil];
[self kb_updateUserCharacterSortWithShowHUD:YES]; [self kb_updateUserCharacterSortWithShowHUD:YES];
} }
@@ -302,7 +307,11 @@ static NSString * const kKBMyKeyboardCellId = @"kKBMyKeyboardCellId";
[KBHUD showInfo:msg]; [KBHUD showInfo:msg];
return; return;
} }
[self.viewModel fetchCharacterListByUserWithCompletion:^(NSArray<KBCharacter *> * _Nonnull characterArray, NSError * _Nullable error) {
if (error) {
NSLog(@"[KBHomeVM] refresh user characters failed: %@", error);
}
}];
if (showHUD) { if (showHUD) {
[KBHUD showSuccess:KBLocalized(@"Saved")]; [KBHUD showSuccess:KBLocalized(@"Saved")];
} }

View File

@@ -257,9 +257,21 @@
#pragma mark - Actions #pragma mark - Actions
- (void)onTapAvatarEdit { [self presentImagePicker]; } - (void)onTapAvatarEdit {
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_person_avatar_edit"
pageId:@"person_info"
elementId:@"avatar_edit"
extra:nil
completion:nil];
[self presentImagePicker];
}
- (void)onTapLogout { - (void)onTapLogout {
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_person_logout_btn"
pageId:@"person_info"
elementId:@"logout_btn"
extra:nil
completion:nil];
[self.myVM logout]; [self.myVM logout];
} }

View File

@@ -111,6 +111,13 @@ static NSString * const kMySkinCellId = @"kMySkinCellId";
- (void)onToggleEdit { - (void)onToggleEdit {
self.editingMode = !self.editingMode; self.editingMode = !self.editingMode;
NSDictionary *extra = @{@"editing": @(self.isEditingMode)};
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_my_skin_toggle_edit"
pageId:@"my_skin"
elementId:@"toggle_edit"
extra:extra
completion:nil];
// //
[self.kb_rightButton setTitle:(self.isEditingMode ? KBLocalized(@"Cancel") : KBLocalized(@"Editor")) [self.kb_rightButton setTitle:(self.isEditingMode ? KBLocalized(@"Cancel") : KBLocalized(@"Editor"))
forState:UIControlStateNormal]; forState:UIControlStateNormal];
@@ -154,6 +161,13 @@ static NSString * const kMySkinCellId = @"kMySkinCellId";
NSArray<NSIndexPath *> *selectedIndexPaths = [[self.collectionView indexPathsForSelectedItems] sortedArrayUsingSelector:@selector(compare:)]; NSArray<NSIndexPath *> *selectedIndexPaths = [[self.collectionView indexPathsForSelectedItems] sortedArrayUsingSelector:@selector(compare:)];
if (selectedIndexPaths.count == 0) return; if (selectedIndexPaths.count == 0) return;
NSDictionary *preExtra = @{@"selected_count": @(selectedIndexPaths.count)};
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_my_skin_delete_btn"
pageId:@"my_skin"
elementId:@"delete_btn"
extra:preExtra
completion:nil];
NSMutableArray<NSString *> *themeIds = [NSMutableArray arrayWithCapacity:selectedIndexPaths.count]; NSMutableArray<NSString *> *themeIds = [NSMutableArray arrayWithCapacity:selectedIndexPaths.count];
for (NSIndexPath *ip in selectedIndexPaths) { for (NSIndexPath *ip in selectedIndexPaths) {
if (ip.item >= self.data.count) { continue; } if (ip.item >= self.data.count) { continue; }
@@ -245,6 +259,16 @@ static NSString * const kMySkinCellId = @"kMySkinCellId";
// //
KBMyTheme *theme = self.data[indexPath.item]; KBMyTheme *theme = self.data[indexPath.item];
[collectionView deselectItemAtIndexPath:indexPath animated:YES]; [collectionView deselectItemAtIndexPath:indexPath animated:YES];
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
if ([theme.themeId isKindOfClass:NSString.class] && theme.themeId.length > 0) {
extra[@"theme_id"] = theme.themeId;
}
extra[@"index"] = @(indexPath.item);
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_my_skin_theme_card"
pageId:@"my_skin"
elementId:@"theme_card"
extra:extra.copy
completion:nil];
KBSkinDetailVC *vc = [[KBSkinDetailVC alloc] init]; KBSkinDetailVC *vc = [[KBSkinDetailVC alloc] init];
vc.themeId = theme.themeId; vc.themeId = theme.themeId;
[self.navigationController pushViewController:vc animated:true]; [self.navigationController pushViewController:vc animated:true];

View File

@@ -120,6 +120,19 @@
[tableView deselectRowAtIndexPath:indexPath animated:YES]; [tableView deselectRowAtIndexPath:indexPath animated:YES];
NSDictionary *info = self.data[indexPath.section][indexPath.row]; NSDictionary *info = self.data[indexPath.section][indexPath.row];
NSString *itemID = info[@"id"]; NSString *itemID = info[@"id"];
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
if ([itemID isKindOfClass:NSString.class] && itemID.length > 0) {
extra[@"item_id"] = itemID;
}
NSString *title = info[@"title"];
if ([title isKindOfClass:NSString.class] && title.length > 0) {
extra[@"item_title"] = title;
}
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_my_menu_item"
pageId:@"my"
elementId:@"menu_item"
extra:extra.copy
completion:nil];
if ([itemID isEqualToString:@"1"]) { if ([itemID isEqualToString:@"1"]) {
[self.navigationController pushViewController:[KBNoticeVC new] animated:true]; [self.navigationController pushViewController:[KBNoticeVC new] animated:true];
@@ -128,9 +141,12 @@
if (!self.viewModel) { if (!self.viewModel) {
self.viewModel = [[KBMyVM alloc] init]; self.viewModel = [[KBMyVM alloc] init];
} }
__weak typeof(self) weakSelf = self;
[KBHUD show]; [KBHUD show];
[self.viewModel fetchInviteCodeWithCompletion:^(KBInviteCodeModel * _Nullable inviteCode, NSError * _Nullable error) { [self.viewModel fetchInviteCodeWithCompletion:^(KBInviteCodeModel * _Nullable inviteCode, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) self = weakSelf;
if (!self) { return; }
[KBHUD dismiss]; [KBHUD dismiss];
if (error) { if (error) {
[KBHUD showInfo:error.localizedDescription ?: KBLocalized(@"Network error")]; [KBHUD showInfo:error.localizedDescription ?: KBLocalized(@"Network error")];
@@ -143,8 +159,25 @@
return; return;
} }
id shareItem = textToCopy;
NSURL *url = [NSURL URLWithString:textToCopy];
if (url) {
shareItem = url;
}
UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:@[shareItem] applicationActivities:nil];
UIPopoverPresentationController *popover = activityVC.popoverPresentationController;
if (popover) {
popover.sourceView = self.view;
popover.sourceRect = CGRectMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds), 1, 1);
popover.permittedArrowDirections = 0;
}
UIPasteboard.generalPasteboard.string = textToCopy; UIPasteboard.generalPasteboard.string = textToCopy;
[KBHUD showInfo:KBLocalized(@"Copy Success")]; [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_my_invite_copy"
pageId:@"my"
elementId:@"invite_copy"
extra:nil
completion:nil];
[self presentViewController:activityVC animated:YES completion:nil];
}); });
}]; }];

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

@@ -202,6 +202,18 @@ static NSString * const kKBJfPayCellId = @"kKBJfPayCellId";
NSInteger old = self.selectedIndex; NSInteger old = self.selectedIndex;
self.selectedIndex = indexPath.item; self.selectedIndex = indexPath.item;
KBPayProductModel *item = (indexPath.item < self.data.count) ? self.data[indexPath.item] : nil;
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
extra[@"index"] = @(indexPath.item);
if ([item.productId isKindOfClass:NSString.class] && item.productId.length > 0) {
extra[@"product_id"] = item.productId;
}
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_points_select_product"
pageId:@"points_recharge"
elementId:@"product_item"
extra:extra.copy
completion:nil];
KBJfPayCell *newCell = (KBJfPayCell *)[collectionView cellForItemAtIndexPath:indexPath]; KBJfPayCell *newCell = (KBJfPayCell *)[collectionView cellForItemAtIndexPath:indexPath];
[newCell applySelected:YES animated:YES]; [newCell applySelected:YES animated:YES];
if (old >= 0 && old < self.data.count) { if (old >= 0 && old < self.data.count) {
@@ -319,6 +331,15 @@ static NSString * const kKBJfPayCellId = @"kKBJfPayCellId";
[KBHUD showInfo:KBLocalized(@"Please select a product")]; [KBHUD showInfo:KBLocalized(@"Please select a product")];
return; return;
} }
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
if ([selectedItem.productId isKindOfClass:NSString.class] && selectedItem.productId.length > 0) {
extra[@"product_id"] = selectedItem.productId;
}
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_points_pay_btn"
pageId:@"points_recharge"
elementId:@"pay_btn"
extra:extra.copy
completion:nil];
NSString *productId = selectedItem.productId; NSString *productId = selectedItem.productId;
if (productId.length == 0) { if (productId.length == 0) {
[KBHUD showInfo:KBLocalized(@"Product unavailable")]; [KBHUD showInfo:KBLocalized(@"Product unavailable")];

View File

@@ -293,6 +293,11 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
#pragma mark - Action #pragma mark - Action
- (void)onTapClose{ - (void)onTapClose{
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_vip_close_btn"
pageId:@"vip_pay"
elementId:@"close_btn"
extra:nil
completion:nil];
[self.navigationController popViewControllerAnimated:true]; [self.navigationController popViewControllerAnimated:true];
} }
@@ -303,6 +308,15 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
[KBHUD showInfo:KBLocalized(@"Please select a product")]; [KBHUD showInfo:KBLocalized(@"Please select a product")];
return; return;
} }
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
if ([plan.productId isKindOfClass:NSString.class] && plan.productId.length > 0) {
extra[@"product_id"] = plan.productId;
}
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_vip_pay_btn"
pageId:@"vip_pay"
elementId:@"pay_btn"
extra:extra.copy
completion:nil];
NSString *productId = plan.productId; NSString *productId = plan.productId;
if (productId.length == 0) { if (productId.length == 0) {
[KBHUD showInfo:KBLocalized(@"Product unavailable")]; [KBHUD showInfo:KBLocalized(@"Product unavailable")];
@@ -326,6 +340,11 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
} }
- (void)onTapRestoreButton { - (void)onTapRestoreButton {
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_vip_restore_btn"
pageId:@"vip_pay"
elementId:@"restore_btn"
extra:nil
completion:nil];
[KBHUD show]; [KBHUD show];
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
[[KBStoreKitBridge shared] restorePurchasesWithCompletion:^(BOOL success, NSString * _Nullable message) { [[KBStoreKitBridge shared] restorePurchasesWithCompletion:^(BOOL success, NSString * _Nullable message) {
@@ -387,6 +406,18 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
NSInteger old = self.selectedIndex; NSInteger old = self.selectedIndex;
self.selectedIndex = indexPath.item; self.selectedIndex = indexPath.item;
KBPayProductModel *plan = (indexPath.item < self.plans.count) ? self.plans[indexPath.item] : nil;
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
extra[@"index"] = @(indexPath.item);
if ([plan.productId isKindOfClass:NSString.class] && plan.productId.length > 0) {
extra[@"product_id"] = plan.productId;
}
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_vip_select_plan"
pageId:@"vip_pay"
elementId:@"plan_item"
extra:extra.copy
completion:nil];
KBVipSubscribeCell *newCell = (KBVipSubscribeCell *)[collectionView cellForItemAtIndexPath:indexPath]; KBVipSubscribeCell *newCell = (KBVipSubscribeCell *)[collectionView cellForItemAtIndexPath:indexPath];
[newCell applySelected:YES animated:YES]; [newCell applySelected:YES animated:YES];
if (old >= 0 && old < self.plans.count) { if (old >= 0 && old < self.plans.count) {

View File

@@ -14,8 +14,9 @@
- (void)applePayReqWithParams:(NSDictionary *)params - (void)applePayReqWithParams:(NSDictionary *)params
needShow:(BOOL)needShow needShow:(BOOL)needShow
completion:(KBPayCompletion)completion { completion:(KBPayCompletion)completion {
// if (needShow) { [KBHUD show]; } if (needShow) {
[KBHUD showWithStatus:@"Please wait"]; [KBHUD showWithStatus:@"Please wait"];
}
[[KBNetworkManager shared] POST:API_VALIDATE_RECEIPT [[KBNetworkManager shared] POST:API_VALIDATE_RECEIPT
jsonBody:params jsonBody:params
headers:nil headers:nil
@@ -31,11 +32,15 @@
NSNumber *codeNum = error.userInfo[@"code"]; NSNumber *codeNum = error.userInfo[@"code"];
NSInteger bizCode = codeNum.integerValue; // code NSInteger bizCode = codeNum.integerValue; // code
// bizCode completion // bizCode completion
if (completion) completion(bizCode, error.localizedDescription ?: KBLocalized(@"Network error")); NSString *msg = error.localizedDescription ?: KBLocalized(@"Network error");
if (needShow) { [KBHUD showError:msg]; }
if (completion) completion(bizCode, msg);
return; return;
} }
// if (completion) completion(ERROR_CODE, error.localizedDescription ?: KBLocalized(@"Network error")); NSString *msg = error.localizedDescription ?: KBLocalized(@"Network error");
if (needShow) { [KBHUD showError:msg]; }
if (completion) completion(KBBizCodeSystemError, msg);
return; return;
} }

View File

@@ -120,6 +120,13 @@ static NSString * const kResultCellId = @"KBSkinCardCell";
KBSearchThemeModel *model = self.resultItems[indexPath.item]; KBSearchThemeModel *model = self.resultItems[indexPath.item];
NSString *themeId = [model.themeId stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; NSString *themeId = [model.themeId stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (themeId.length == 0) { return; } if (themeId.length == 0) { return; }
NSDictionary *extra = @{@"theme_id": themeId,
@"index": @(indexPath.item)};
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_search_result_theme"
pageId:@"search_result"
elementId:@"result_theme_card"
extra:extra
completion:nil];
KBSkinDetailVC *vc = [[KBSkinDetailVC alloc] init]; KBSkinDetailVC *vc = [[KBSkinDetailVC alloc] init];
vc.themeId = themeId; vc.themeId = themeId;
[self.navigationController pushViewController:vc animated:YES]; [self.navigationController pushViewController:vc animated:YES];

View File

@@ -244,6 +244,11 @@ typedef NS_ENUM(NSInteger, KBSearchSection) {
/// ///
- (void)clearHistory { - (void)clearHistory {
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_search_clear_history"
pageId:@"search"
elementId:@"clear_history"
extra:nil
completion:nil];
[self.historyWords removeAllObjects]; [self.historyWords removeAllObjects];
self.historyExpanded = NO; self.historyExpanded = NO;
[self saveHistoryWordsToLocal:self.historyWords]; [self saveHistoryWordsToLocal:self.historyWords];
@@ -369,10 +374,22 @@ typedef NS_ENUM(NSInteger, KBSearchSection) {
NSArray *list = [self currentDisplayHistory]; NSArray *list = [self currentDisplayHistory];
NSString *kw = list[indexPath.item]; NSString *kw = list[indexPath.item];
if ([kw isEqualToString:kMoreToken]) { if ([kw isEqualToString:kMoreToken]) {
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_search_history_more"
pageId:@"search"
elementId:@"history_more"
extra:nil
completion:nil];
// //
self.historyExpanded = YES; self.historyExpanded = YES;
[self.collectionView reloadData]; [self.collectionView reloadData];
} else { } else {
NSDictionary *extra = @{@"index": @(indexPath.item),
@"keyword_len": @(kw.length)};
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_search_history_item"
pageId:@"search"
elementId:@"history_item"
extra:extra
completion:nil];
[self.searchBarView updateKeyword:kw]; [self.searchBarView updateKeyword:kw];
[self performSearch:kw]; [self performSearch:kw];
[self openResultForKeyword:kw]; [self openResultForKeyword:kw];
@@ -381,6 +398,16 @@ typedef NS_ENUM(NSInteger, KBSearchSection) {
} }
if (indexPath.section == KBSearchSectionRecommend) { if (indexPath.section == KBSearchSectionRecommend) {
KBShopThemeModel *model = self.recommendedThemes[indexPath.item]; KBShopThemeModel *model = self.recommendedThemes[indexPath.item];
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
extra[@"index"] = @(indexPath.item);
if ([model.themeId isKindOfClass:NSString.class] && model.themeId.length > 0) {
extra[@"theme_id"] = model.themeId;
}
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_search_recommend_theme"
pageId:@"search"
elementId:@"recommend_theme_card"
extra:extra.copy
completion:nil];
[self openDetailForThemeId:model.themeId ?: @""]; [self openDetailForThemeId:model.themeId ?: @""];
} }
} }
@@ -394,6 +421,12 @@ typedef NS_ENUM(NSInteger, KBSearchSection) {
// _searchBarView.placeholder = @"Themes"; // _searchBarView.placeholder = @"Themes";
KBWeakSelf KBWeakSelf
_searchBarView.onSearch = ^(NSString * _Nonnull keyword) { _searchBarView.onSearch = ^(NSString * _Nonnull keyword) {
NSDictionary *extra = @{@"keyword_len": @(keyword.length)};
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_search_submit"
pageId:@"search"
elementId:@"search_submit"
extra:extra
completion:nil];
// + // +
[weakSelf performSearch:keyword]; [weakSelf performSearch:keyword];
[weakSelf openResultForKeyword:keyword]; [weakSelf openResultForKeyword:keyword];

View File

@@ -173,6 +173,16 @@
- (void)kb_handleShopTapAtIndexPath:(NSIndexPath *)indexPath { - (void)kb_handleShopTapAtIndexPath:(NSIndexPath *)indexPath {
KBShopThemeModel *selTheme = (indexPath.item < self.dataSource.count) ? self.dataSource[indexPath.item] : nil; KBShopThemeModel *selTheme = (indexPath.item < self.dataSource.count) ? self.dataSource[indexPath.item] : nil;
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
if ([selTheme.themeId isKindOfClass:NSString.class] && selTheme.themeId.length > 0) {
extra[@"theme_id"] = selTheme.themeId;
}
extra[@"index"] = @(indexPath.item);
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_shop_theme_card"
pageId:@"shop_item_list"
elementId:@"theme_card"
extra:extra.copy
completion:nil];
KBSkinDetailVC *vc = [[KBSkinDetailVC alloc] init]; KBSkinDetailVC *vc = [[KBSkinDetailVC alloc] init];
vc.themeId = selTheme.themeId; vc.themeId = selTheme.themeId;
[self.navigationController pushViewController:vc animated:true]; [self.navigationController pushViewController:vc animated:true];

View File

@@ -244,11 +244,21 @@ static const CGFloat JXheightForHeaderInSection = 50;
#pragma mark - action #pragma mark - action
- (void)searchBtnAction{ - (void)searchBtnAction{
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_shop_search_btn"
pageId:@"shop"
elementId:@"search_btn"
extra:nil
completion:nil];
KBSearchVC *vc = [[KBSearchVC alloc] init]; KBSearchVC *vc = [[KBSearchVC alloc] init];
[self.navigationController pushViewController:vc animated:true]; [self.navigationController pushViewController:vc animated:true];
} }
- (void)skinBtnAction{ - (void)skinBtnAction{
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_shop_my_skin_btn"
pageId:@"shop"
elementId:@"my_skin_btn"
extra:nil
completion:nil];
MySkinVC *vc = [[MySkinVC alloc] init]; MySkinVC *vc = [[MySkinVC alloc] init];
[self.navigationController pushViewController:vc animated:true]; [self.navigationController pushViewController:vc animated:true];
} }

View File

@@ -130,6 +130,18 @@ typedef NS_ENUM(NSInteger, KBSkinDetailSection) {
if (nextThemeId.length == 0) { return; } if (nextThemeId.length == 0) { return; }
if ([nextThemeId isEqualToString:self.themeId ?: @""]) { return; } if ([nextThemeId isEqualToString:self.themeId ?: @""]) { return; }
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
if ([self.themeId isKindOfClass:NSString.class] && self.themeId.length > 0) {
extra[@"from_theme_id"] = self.themeId;
}
extra[@"to_theme_id"] = nextThemeId;
extra[@"index"] = @(indexPath.item);
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_skin_recommend_card"
pageId:@"skin_detail"
elementId:@"recommend_card"
extra:extra.copy
completion:nil];
// themeId // themeId
self.themeId = nextThemeId; self.themeId = nextThemeId;
self.detailModel = nil; self.detailModel = nil;
@@ -242,6 +254,16 @@ typedef NS_ENUM(NSInteger, KBSkinDetailSection) {
[KBHUD showInfo:KBLocalized(@"正在加载主题详情")]; [KBHUD showInfo:KBLocalized(@"正在加载主题详情")];
return; return;
} }
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
extra[@"theme_id"] = self.themeId ?: @"";
extra[@"purchased"] = @(self.detailModel.isPurchased);
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_skin_download_btn"
pageId:@"skin_detail"
elementId:@"download_btn"
extra:extra.copy
completion:nil];
NSLog(@"🧩[SkinDetail] action themeId=%@ purchased=%d", self.themeId, self.detailModel.isPurchased); NSLog(@"🧩[SkinDetail] action themeId=%@ purchased=%d", self.themeId, self.detailModel.isPurchased);
if (self.detailModel.isPurchased) { if (self.detailModel.isPurchased) {
[self requestDownload]; [self requestDownload];
@@ -296,6 +318,7 @@ typedef NS_ENUM(NSInteger, KBSkinDetailSection) {
fromViewController:self fromViewController:self
mode:KBSkinSourceModeRemoteZip mode:KBSkinSourceModeRemoteZip
completion:^(BOOL success) { completion:^(BOOL success) {
[KBHUD dismiss];
NSLog(@"%@[SkinDetail] download result id=%@", NSLog(@"%@[SkinDetail] download result id=%@",
(success ? @"✅" : @"❌"), (success ? @"✅" : @"❌"),
self.detailModel.themeId); self.detailModel.themeId);

View File

@@ -53,6 +53,7 @@
//-----------------------------------------------宏定义全局----------------------------------------------------------/ //-----------------------------------------------宏定义全局----------------------------------------------------------/
// 调试专用日志DEBUG 打印RELEASE 不打印)。尽量显眼,包含函数与行号。 // 调试专用日志DEBUG 打印RELEASE 不打印)。尽量显眼,包含函数与行号。
#ifndef KBLOG
#if DEBUG #if DEBUG
#define KBLOG(fmt, ...) do { \ #define KBLOG(fmt, ...) do { \
NSString *kb_msg__ = [NSString stringWithFormat:(fmt), ##__VA_ARGS__]; \ NSString *kb_msg__ = [NSString stringWithFormat:(fmt), ##__VA_ARGS__]; \
@@ -62,6 +63,7 @@
#else #else
#define KBLOG(...) #define KBLOG(...)
#endif #endif
#endif
// 通用链接Universal Links统一配置 // 通用链接Universal Links统一配置
// 仅需修改这里的域名/前缀,工程内所有使用 UL 的地方都会同步。 // 仅需修改这里的域名/前缀,工程内所有使用 UL 的地方都会同步。

View File

@@ -193,7 +193,11 @@ static void *KBPermPlayerPresentationSizeContext = &KBPermPlayerPresentationSize
} }
- (void)openSettings { - (void)openSettings {
[self report]; [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_permission_open_settings_btn"
pageId:@"keyboard_permission_guide"
elementId:@"open_settings_btn"
extra:nil
completion:nil];
NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString]; NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
UIApplication *app = [UIApplication sharedApplication]; UIApplication *app = [UIApplication sharedApplication];
if ([app canOpenURL:url]) { if ([app canOpenURL:url]) {
@@ -206,6 +210,11 @@ static void *KBPermPlayerPresentationSizeContext = &KBPermPlayerPresentationSize
} }
- (void)closeButtonAction{ - (void)closeButtonAction{
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_permission_close_btn"
pageId:@"keyboard_permission_guide"
elementId:@"close_btn"
extra:nil
completion:nil];
[self.navigationController popViewControllerAnimated:true]; [self.navigationController popViewControllerAnimated:true];
} }
@@ -286,13 +295,6 @@ static void *KBPermPlayerPresentationSizeContext = &KBPermPlayerPresentationSize
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
} }
#pragma mark - network
- (void)report{
[[KBMaiPointReporter sharedReporter] reportNewAccountWithType:@"键盘申请授权" account:nil completion:^(BOOL success, NSError * _Nullable error) {
}];
}
#pragma mark - Lazy Subviews #pragma mark - Lazy Subviews
- (UIButton *)backButton { - (UIButton *)backButton {