Compare commits
10 Commits
6ec98468de
...
dev_上报埋点
| Author | SHA1 | Date | |
|---|---|---|---|
| bdf2a9af80 | |||
| e858d35722 | |||
| f2d5210313 | |||
| 1b0af3e2d6 | |||
| 0965cd3c7e | |||
| c3909d63da | |||
| 1096f24c57 | |||
| 7ed84fd445 | |||
| 4e2d7d2908 | |||
| 34089ddeea |
@@ -47,6 +47,7 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
|
||||
@interface KeyboardViewController () <KBKeyBoardMainViewDelegate, KBFunctionViewDelegate, KBKeyboardSubscriptionViewDelegate>
|
||||
@property (nonatomic, strong) UIButton *nextKeyboardButton; // 系统“下一个键盘”按钮(可选)
|
||||
@property (nonatomic, strong) UIView *contentView;
|
||||
@property (nonatomic, strong) KBKeyBoardMainView *keyBoardMainView; // 功能面板视图(点击工具栏第0个时显示)
|
||||
@property (nonatomic, strong) KBFunctionView *functionView; // 功能面板视图(点击工具栏第0个时显示)
|
||||
@property (nonatomic, strong) KBSettingView *settingView; // 设置页
|
||||
@@ -55,6 +56,12 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
@property (nonatomic, strong) KBSuggestionEngine *suggestionEngine;
|
||||
@property (nonatomic, copy) NSString *currentWord;
|
||||
@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
|
||||
|
||||
@implementation KeyboardViewController
|
||||
@@ -119,13 +126,16 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
- (void)setupUI {
|
||||
self.view.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
// 按屏幕宽度对设计值做等比缩放,避免在不同机型上键盘整体高度失真导致皮肤被压缩/拉伸
|
||||
CGFloat keyboardHeight = KBFit(kKBKeyboardBaseHeight);
|
||||
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
|
||||
// 按“短边”宽度等比缩放,横屏保持竖屏布局比例
|
||||
CGFloat portraitWidth = [self kb_portraitWidth];
|
||||
CGFloat keyboardHeight = [self kb_keyboardHeightForWidth:portraitWidth];
|
||||
CGFloat screenWidth = CGRectGetWidth([UIScreen mainScreen].bounds);
|
||||
CGFloat outerVerticalInset = KBFit(4.0f);
|
||||
|
||||
NSLayoutConstraint *h = [self.view.heightAnchor constraintEqualToConstant:keyboardHeight];
|
||||
NSLayoutConstraint *w = [self.view.widthAnchor constraintEqualToConstant:screenWidth];
|
||||
self.kb_heightConstraint = h;
|
||||
self.kb_widthConstraint = w;
|
||||
|
||||
h.priority = UILayoutPriorityRequired;
|
||||
w.priority = UILayoutPriorityRequired;
|
||||
@@ -137,25 +147,30 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
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) {
|
||||
make.edges.equalTo(self.view);
|
||||
make.edges.equalTo(self.contentView);
|
||||
}];
|
||||
// 预置功能面板(默认隐藏),与键盘区域共享相同布局
|
||||
self.functionView.hidden = YES;
|
||||
[self.view addSubview:self.functionView];
|
||||
[self.contentView addSubview:self.functionView];
|
||||
[self.functionView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self.view);
|
||||
make.top.equalTo(self.view).offset(0);
|
||||
make.bottom.equalTo(self.view).offset(0);
|
||||
make.edges.equalTo(self.contentView);
|
||||
}];
|
||||
|
||||
[self.view addSubview:self.keyBoardMainView];
|
||||
[self.contentView addSubview:self.keyBoardMainView];
|
||||
[self.keyBoardMainView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self.view);
|
||||
make.top.equalTo(self.view).offset(0);
|
||||
make.bottom.equalTo(self.view.mas_bottom).offset(-0);
|
||||
make.edges.equalTo(self.contentView);
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -277,36 +292,49 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
self.keyBoardMainView.hidden = show;
|
||||
|
||||
if (show) {
|
||||
[[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:@"enter_keyboard_function_panel"
|
||||
pageId:@"keyboard_function_panel"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
[self hideSubscriptionPanel];
|
||||
} else {
|
||||
[[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:@"enter_keyboard_main_panel"
|
||||
pageId:@"keyboard_main_panel"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
}
|
||||
|
||||
// 可选:把当前显示的视图置顶,避免层级遮挡
|
||||
if (show) {
|
||||
[self.view bringSubviewToFront:self.functionView];
|
||||
[self.contentView bringSubviewToFront:self.functionView];
|
||||
} else {
|
||||
[self.view bringSubviewToFront:self.keyBoardMainView];
|
||||
[self.contentView bringSubviewToFront:self.keyBoardMainView];
|
||||
}
|
||||
}
|
||||
|
||||
/// 显示/隐藏设置页(高度与 keyBoardMainView 一致),右侧滑入/滑出
|
||||
- (void)showSettingView:(BOOL)show {
|
||||
if (show) {
|
||||
[[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:@"enter_keyboard_settings"
|
||||
pageId:@"keyboard_settings"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
// if (!self.settingView) {
|
||||
self.settingView = [[KBSettingView alloc] init];
|
||||
self.settingView.hidden = YES;
|
||||
[self.view addSubview:self.settingView];
|
||||
[self.contentView addSubview:self.settingView];
|
||||
[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.view bringSubviewToFront:self.settingView];
|
||||
[self.contentView bringSubviewToFront:self.settingView];
|
||||
// 以 keyBoardMainView 的实际宽度为准,避免首次添加时 self.view 宽度尚未计算
|
||||
[self.view layoutIfNeeded];
|
||||
[self.contentView layoutIfNeeded];
|
||||
CGFloat w = CGRectGetWidth(self.keyBoardMainView.bounds);
|
||||
if (w <= 0) { w = CGRectGetWidth(self.view.bounds); }
|
||||
if (w <= 0) { w = [UIScreen mainScreen].bounds.size.width; }
|
||||
if (w <= 0) { w = CGRectGetWidth(self.contentView.bounds); }
|
||||
if (w <= 0) { w = [self kb_portraitWidth]; }
|
||||
self.settingView.transform = CGAffineTransformMakeTranslation(w, 0);
|
||||
self.settingView.hidden = NO;
|
||||
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
|
||||
@@ -315,8 +343,8 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
} else {
|
||||
if (!self.settingView || self.settingView.hidden) return;
|
||||
CGFloat w = CGRectGetWidth(self.keyBoardMainView.bounds);
|
||||
if (w <= 0) { w = CGRectGetWidth(self.view.bounds); }
|
||||
if (w <= 0) { w = [UIScreen mainScreen].bounds.size.width; }
|
||||
if (w <= 0) { w = CGRectGetWidth(self.contentView.bounds); }
|
||||
if (w <= 0) { w = [self kb_portraitWidth]; }
|
||||
[UIView animateWithDuration:0.22 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
|
||||
self.settingView.transform = CGAffineTransformMakeTranslation(w, 0);
|
||||
} completion:^(BOOL finished) {
|
||||
@@ -342,19 +370,23 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view];
|
||||
return;
|
||||
}
|
||||
[[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:@"enter_keyboard_subscription_panel"
|
||||
pageId:@"keyboard_subscription_panel"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
[self showFunctionPanel:NO];
|
||||
KBKeyboardSubscriptionView *panel = self.subscriptionView;
|
||||
if (!panel.superview) {
|
||||
panel.hidden = YES;
|
||||
[self.view addSubview:panel];
|
||||
[self.contentView addSubview:panel];
|
||||
[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.alpha = 0.0;
|
||||
CGFloat height = CGRectGetHeight(self.view.bounds);
|
||||
CGFloat height = CGRectGetHeight(self.contentView.bounds);
|
||||
if (height <= 0) { height = 260; }
|
||||
panel.transform = CGAffineTransformMakeTranslation(0, height);
|
||||
[panel refreshProductsIfNeeded];
|
||||
@@ -367,7 +399,7 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
- (void)hideSubscriptionPanel {
|
||||
if (!self.subscriptionView || self.subscriptionView.hidden) { return; }
|
||||
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;
|
||||
[UIView animateWithDuration:0.22 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
|
||||
panel.alpha = 0.0;
|
||||
@@ -427,6 +459,12 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
}
|
||||
|
||||
- (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) {
|
||||
[self showFunctionPanel:YES];
|
||||
[self kb_clearCurrentWord];
|
||||
@@ -436,6 +474,11 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
}
|
||||
|
||||
- (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];
|
||||
}
|
||||
|
||||
@@ -448,16 +491,32 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
}
|
||||
|
||||
- (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];
|
||||
[self kb_scheduleContextRefreshResetSuppression:YES];
|
||||
}
|
||||
|
||||
- (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")];
|
||||
}
|
||||
|
||||
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didSelectSuggestion:(NSString *)suggestion {
|
||||
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 ?: @"";
|
||||
if (current.length > 0) {
|
||||
@@ -481,6 +540,18 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
}
|
||||
}
|
||||
- (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];
|
||||
NSURL *scheme = [NSURL URLWithString:schemeStr];
|
||||
//
|
||||
@@ -503,10 +574,24 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
#pragma mark - KBKeyboardSubscriptionViewDelegate
|
||||
|
||||
- (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];
|
||||
}
|
||||
|
||||
- (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 kb_openRechargeForProduct:product];
|
||||
}
|
||||
@@ -581,6 +666,11 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
}
|
||||
|
||||
- (void)onTapSettingsBack {
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_settings_back_btn"
|
||||
pageId:@"keyboard_settings"
|
||||
elementId:@"back_btn"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
[self showSettingView:NO];
|
||||
}
|
||||
|
||||
@@ -604,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 {
|
||||
// // 使用与主 App 一致的自定义 Scheme
|
||||
// NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@@//login?src=keyboard", KB_APP_SCHEME]];
|
||||
@@ -624,6 +729,7 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
self.bgImageView.image = img;
|
||||
BOOL hasImg = (img != nil);
|
||||
self.view.backgroundColor = hasImg ? [UIColor clearColor] : t.keyboardBackground;
|
||||
self.contentView.backgroundColor = hasImg ? [UIColor clearColor] : t.keyboardBackground;
|
||||
self.keyBoardMainView.backgroundColor = hasImg ? [UIColor clearColor] : t.keyboardBackground;
|
||||
// 触发键区按主题重绘
|
||||
if ([self.keyBoardMainView respondsToSelector:@selector(kb_applyTheme)]) {
|
||||
@@ -657,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
|
||||
|
||||
- (UIView *)contentView {
|
||||
if (!_contentView) {
|
||||
_contentView = [[UIView alloc] init];
|
||||
_contentView.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
return _contentView;
|
||||
}
|
||||
|
||||
- (UIImageView *)bgImageView {
|
||||
if (!_bgImageView) {
|
||||
_bgImageView = [[UIImageView alloc] init];
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#import "KBHUD.h" // 复用 App 内的 HUD 封装
|
||||
#import "KBLocalizationManager.h" // 复用多语言封装(可在扩展内使用)
|
||||
#import "KBMaiPointReporter.h"
|
||||
//#import "KBLog.h"
|
||||
|
||||
|
||||
// 通用链接(Universal Links)统一配置
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#import "KBFunctionTagListView.h"
|
||||
#import "KBFunctionTagCell.h"
|
||||
#import "KBMaiPointReporter.h"
|
||||
|
||||
static NSString * const kKBFunctionTagCellId2 = @"KBFunctionTagCellId2";
|
||||
static CGFloat const kKBItemSpace = 4;
|
||||
@@ -66,8 +67,23 @@ static CGFloat const kKBItemSpace = 4;
|
||||
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return kKBItemSpace; }
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
KBTagItemModel *model = (indexPath.item < self.items.count) ? self.items[indexPath.item] : [KBTagItemModel new];
|
||||
NSInteger personaId = 0;
|
||||
if ([model isKindOfClass:KBTagItemModel.class]) {
|
||||
personaId = model.characterId > 0 ? model.characterId : model.tagId;
|
||||
}
|
||||
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
|
||||
extra[@"index"] = @(indexPath.item);
|
||||
extra[@"id"] = @(personaId);
|
||||
if ([model.characterName isKindOfClass:NSString.class] && model.characterName.length > 0) {
|
||||
extra[@"name"] = model.characterName;
|
||||
}
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_tag_item"
|
||||
pageId:@"keyboard_function_panel"
|
||||
elementId:@"renshe_item"
|
||||
extra:extra.copy
|
||||
completion:nil];
|
||||
if ([self.delegate respondsToSelector:@selector(tagListView:didSelectIndex:title:)]) {
|
||||
KBTagItemModel *model = (indexPath.item < self.items.count) ? self.items[indexPath.item] : [KBTagItemModel new];
|
||||
[self.delegate tagListView:self didSelectIndex:indexPath.item title:model.characterName];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -640,6 +640,7 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
|
||||
// 用户点击功能标签:优先 UL 拉起主App,失败再 Scheme;两次都失败则提示开启完全访问。
|
||||
// 若已开启“完全访问”,则直接在键盘侧创建 KBStreamTextView,并在其右上角提供删除按钮关闭。
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
// 点击上报已下沉到 KBFunctionTagListView(保证能拿到人设 id/name)
|
||||
// 权限全部打开(键盘已启用 + 完全访问)。在扩展进程中仅需判断“完全访问”。
|
||||
if ([[KBFullAccessManager shared] hasFullAccess]) {
|
||||
KBTagItemModel *selModel = self.modelArray[indexPath.item];
|
||||
@@ -682,6 +683,11 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
|
||||
#pragma mark - Button Actions
|
||||
|
||||
- (void)onTapPaste {
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_paste_btn"
|
||||
pageId:@"keyboard_function_panel"
|
||||
elementId:@"paste_btn"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
// 用户点击“粘贴”时才读取剪贴板:
|
||||
// - iOS16+ 会在跨 App 首次读取时自动弹出系统权限弹窗;
|
||||
// - iOS15 及以下不会弹窗,直接返回内容;
|
||||
@@ -776,6 +782,11 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
|
||||
|
||||
- (void)onTapDelete {
|
||||
NSLog(@"点击:删除");
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_delete_btn"
|
||||
pageId:@"keyboard_function_panel"
|
||||
elementId:@"delete_btn"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
UIInputViewController *ivc = KBFindInputViewController(self);
|
||||
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
|
||||
[[KBInputBufferManager shared] refreshFromProxyIfPossible:proxy];
|
||||
@@ -786,11 +797,21 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
|
||||
}
|
||||
- (void)onTapClear {
|
||||
NSLog(@"点击:清空");
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_clear_btn"
|
||||
pageId:@"keyboard_function_panel"
|
||||
elementId:@"clear_btn"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
[self.backspaceHandler performClearAction];
|
||||
}
|
||||
|
||||
- (void)onTapSend {
|
||||
NSLog(@"点击:发送");
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_send_btn"
|
||||
pageId:@"keyboard_function_panel"
|
||||
elementId:@"send_btn"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
[[KBBackspaceUndoManager shared] registerNonClearAction];
|
||||
// 发送:插入换行。大多数聊天类 App 会把回车视为“发送”
|
||||
UIInputViewController *ivc = KBFindInputViewController(self);
|
||||
|
||||
148
KBMaiPointEventTable.md
Normal file
148
KBMaiPointEventTable.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# KBMaiPoint 埋点事件表(统一口径:iOS / Android / 后端)
|
||||
|
||||
## 统一约定(全端一致)
|
||||
|
||||
### 1)事件类型(event_type)
|
||||
- 页面曝光:`page_exposure`
|
||||
- 点击事件:`click`
|
||||
|
||||
> iOS 侧可映射为:`KBMaiPointGenericReportTypePage / KBMaiPointGenericReportTypeClick`
|
||||
|
||||
### 2)事件名称(event_name)
|
||||
- 统一使用 `lower_snake_case`,不绑定任何端的类名/资源名
|
||||
- 页面曝光统一前缀:`enter_`
|
||||
- 点击事件统一前缀:`click_`
|
||||
|
||||
### 3)事件参数(value / params)
|
||||
- **所有事件都固定带**:`token`(`NSString`,有就传真实值;没有就传空字符串 `""`)
|
||||
- 建议额外固定带:`page_id`(页面/区域统一ID)
|
||||
- 点击类事件建议固定带:`element_id`(控件/入口统一ID)
|
||||
- 列表/集合类点击建议带:`index`(`NSInteger`)与业务 `id`(如 `theme_id` / `product_id`)
|
||||
|
||||
参数示例(最小):
|
||||
```json
|
||||
{ "token": "", "page_id": "shop", "element_id": "search_btn" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## A. 主工程(keyBoard)
|
||||
|
||||
### A1)页面曝光(触发:VC 的 `viewDidAppear`)
|
||||
|
||||
| 注释 | 事件类型 | 事件名称 | page_id | iOS 对应页面 | Android 对应页面 | 触发时机 | 事件参数(示例) |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 进入首页 | page_exposure | enter_home_main | home_main | HomeMainVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_main" }` |
|
||||
| 进入首页Tab容器 | page_exposure | enter_home | home | HomeVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home" }` |
|
||||
| 进入热门页 | page_exposure | enter_home_hot | home_hot | HomeHotVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_hot" }` |
|
||||
| 进入排行榜页 | page_exposure | enter_home_rank | home_rank | HomeRankVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_rank" }` |
|
||||
| 进入排行榜内容页 | page_exposure | enter_home_rank_content | home_rank_content | HomeRankContentVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_rank_content" }` |
|
||||
| 进入首页底部弹层 | page_exposure | enter_home_sheet | home_sheet | HomeSheetVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_sheet" }` |
|
||||
| 进入社区页 | page_exposure | enter_community | community | KBCommunityVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"community" }` |
|
||||
| 进入搜索页 | page_exposure | enter_search | search | KBSearchVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"search" }` |
|
||||
| 进入搜索结果页 | page_exposure | enter_search_result | search_result | KBSearchResultVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"search_result" }` |
|
||||
| 进入商店页 | page_exposure | enter_shop | shop | KBShopVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"shop" }` |
|
||||
| 进入商店分类列表页 | page_exposure | enter_shop_item_list | shop_item_list | KBShopItemVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"shop_item_list" }` |
|
||||
| 进入皮肤详情页 | page_exposure | enter_skin_detail | skin_detail | KBSkinDetailVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"skin_detail", "theme_id":"" }` |
|
||||
| 进入我的页 | page_exposure | enter_my | my | MyVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"my" }` |
|
||||
| 进入我的皮肤页 | page_exposure | enter_my_skin | my_skin | MySkinVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"my_skin" }` |
|
||||
| 进入我的键盘配置页 | page_exposure | enter_my_keyboard | my_keyboard | KBMyKeyBoardVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"my_keyboard" }` |
|
||||
| 进入个人信息页 | page_exposure | enter_person_info | person_info | KBPersonInfoVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"person_info" }` |
|
||||
| 进入反馈页 | page_exposure | enter_feedback | feedback | KBFeedBackVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"feedback" }` |
|
||||
| 进入公告页 | page_exposure | enter_notice | notice | KBNoticeVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"notice" }` |
|
||||
| 进入消费记录页 | page_exposure | enter_consumption_record | consumption_record | KBConsumptionRecordVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"consumption_record" }` |
|
||||
| 进入VIP购买页 | page_exposure | enter_vip_pay | vip_pay | KBVipPay | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"vip_pay" }` |
|
||||
| 进入积分充值页 | page_exposure | enter_points_recharge | points_recharge | KBJfPay | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"points_recharge" }` |
|
||||
| 进入登录页 | page_exposure | enter_login | login | KBLoginVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"login" }` |
|
||||
| 进入邮箱登录页 | page_exposure | enter_login_email | login_email | KBEmailLoginVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"login_email" }` |
|
||||
| 进入邮箱注册页 | page_exposure | enter_register_email | register_email | KBEmailRegistVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"register_email" }` |
|
||||
| 进入注册验证码页 | page_exposure | enter_register_verify_email | register_verify_email | KBRegistVerEmailVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"register_verify_email" }` |
|
||||
| 进入忘记密码页 | page_exposure | enter_forgot_password_email | forgot_password_email | KBForgetPwdVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"forgot_password_email" }` |
|
||||
| 进入忘记密码验证码页 | page_exposure | enter_forgot_password_verify | forgot_password_verify | KBForgetVerPwdVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"forgot_password_verify" }` |
|
||||
| 进入忘记密码新密码页 | page_exposure | enter_forgot_password_newpwd | forgot_password_newpwd | KBForgetPwdNewPwdVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"forgot_password_newpwd" }` |
|
||||
| 进入键盘权限引导页(App内) | page_exposure | enter_keyboard_permission_guide | keyboard_permission_guide | KBPermissionViewController | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"keyboard_permission_guide" }` |
|
||||
| 进入首次引导页 | page_exposure | enter_guide | guide | KBGuideVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"guide" }` |
|
||||
| 进入性别选择页 | page_exposure | enter_sex_select | sex_select | KBSexSelVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"sex_select" }` |
|
||||
| 进入WebView页 | page_exposure | enter_webview | webview | KBWebViewViewController | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"webview", "url":"" }` |
|
||||
|
||||
> 测试/工具页(建议仅 DEBUG 或按需接入):`KBTestVC / KBLangTestVC / KBSkinCenterVC / ViewController / LoginViewController / KBLoginSheetViewController`。
|
||||
|
||||
### A2)点击事件(按钮/列表/入口)
|
||||
|
||||
| 注释 | 事件类型 | 事件名称 | page_id | element_id | iOS 对应控件/方法 | Android 对应控件 | 触发时机 | 事件参数(示例) |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| 首页点击“购买会员” | click | click_home_buy_vip_btn | home_main | buy_vip_btn | HomeHeadView `onTapBuyAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"home_main", "element_id":"buy_vip_btn" }` |
|
||||
| 首页点击“权限悬浮按钮” | click | click_home_permission_float_btn | home_main | permission_float_btn | HomeMainVC `keyPermissButton.clickDragViewBlock` | Android 自定义 | 点击悬浮按钮 | `{ "token":"", "page_id":"home_main", "element_id":"permission_float_btn" }` |
|
||||
| 权限引导页点击“去设置” | click | click_permission_open_settings_btn | keyboard_permission_guide | open_settings_btn | KBPermissionViewController `openSettings` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_permission_guide", "element_id":"open_settings_btn" }` |
|
||||
| 权限引导页点击“关闭” | click | click_permission_close_btn | keyboard_permission_guide | close_btn | KBPermissionViewController `closeButtonAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_permission_guide", "element_id":"close_btn" }` |
|
||||
| 商店页点击“搜索” | click | click_shop_search_btn | shop | search_btn | KBShopVC `searchBtnAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"shop", "element_id":"search_btn" }` |
|
||||
| 商店页点击“我的皮肤” | click | click_shop_my_skin_btn | shop | my_skin_btn | KBShopVC `skinBtnAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"shop", "element_id":"my_skin_btn" }` |
|
||||
| 商店列表点击皮肤卡片 | click | click_shop_theme_card | shop_item_list | theme_card | KBShopItemVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"shop_item_list", "element_id":"theme_card", "theme_id":"", "index":0 }` |
|
||||
| 皮肤详情点击“下载/购买” | click | click_skin_download_btn | skin_detail | download_btn | KBSkinDetailVC `handleDownloadAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"skin_detail", "element_id":"download_btn", "theme_id":"", "purchased":0 }` |
|
||||
| 皮肤详情点击“推荐皮肤” | click | click_skin_recommend_card | skin_detail | recommend_card | KBSkinDetailVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"skin_detail", "element_id":"recommend_card", "from_theme_id":"", "to_theme_id":"", "index":0 }` |
|
||||
| 搜索栏点击搜索 | click | click_search_submit | search | search_submit | KBSearchBarView `onSearch` | Android 自定义 | 点击搜索 | `{ "token":"", "page_id":"search", "element_id":"search_submit", "keyword_len":0 }` |
|
||||
| 搜索页点击历史词条 | click | click_search_history_item | search | history_item | KBSearchVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"search", "element_id":"history_item", "index":0 }` |
|
||||
| 搜索页点击“展开更多历史” | click | click_search_history_more | search | history_more | KBSearchVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"search", "element_id":"history_more" }` |
|
||||
| 搜索页点击“清空历史” | click | click_search_clear_history | search | clear_history | KBSearchVC `clearHistory`(header trash) | Android 自定义 | 点击垃圾桶 | `{ "token":"", "page_id":"search", "element_id":"clear_history" }` |
|
||||
| 搜索页点击推荐皮肤 | click | click_search_recommend_theme | search | recommend_theme_card | KBSearchVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"search", "element_id":"recommend_theme_card", "theme_id":"", "index":0 }` |
|
||||
| 搜索结果页点击皮肤 | click | click_search_result_theme | search_result | result_theme_card | KBSearchResultVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"search_result", "element_id":"result_theme_card", "theme_id":"", "index":0 }` |
|
||||
| 我的页点击菜单项 | click | click_my_menu_item | my | menu_item | MyVC `didSelectRowAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"my", "element_id":"menu_item", "item_id":"", "item_title":"" }` |
|
||||
| 我的页点击“邀请”成功复制 | click | click_my_invite_copy | my | invite_copy | MyVC(邀请分支) | Android 自定义 | 复制时机 | `{ "token":"", "page_id":"my", "element_id":"invite_copy" }` |
|
||||
| 反馈页点击提交 | click | click_feedback_commit_btn | feedback | commit_btn | KBFeedBackVC `onTapCommit` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"feedback", "element_id":"commit_btn", "content_len":0 }` |
|
||||
| 个人信息点击更换头像 | click | click_person_avatar_edit | person_info | avatar_edit | KBPersonInfoVC `onTapAvatarEdit` | Android 自定义 | tapGesture | `{ "token":"", "page_id":"person_info", "element_id":"avatar_edit" }` |
|
||||
| 个人信息点击退出登录 | click | click_person_logout_btn | person_info | logout_btn | KBPersonInfoVC `onTapLogout` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"person_info", "element_id":"logout_btn" }` |
|
||||
| 我的键盘页点击保存 | click | click_my_keyboard_save_btn | my_keyboard | save_btn | KBMyKeyBoardVC `onSave` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"my_keyboard", "element_id":"save_btn" }` |
|
||||
| 我的皮肤页点击编辑/取消 | click | click_my_skin_toggle_edit | my_skin | toggle_edit | MySkinVC `onToggleEdit` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"my_skin", "element_id":"toggle_edit", "editing":0 }` |
|
||||
| 我的皮肤页点击删除 | click | click_my_skin_delete_btn | my_skin | delete_btn | MySkinVC `onDelete` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"my_skin", "element_id":"delete_btn", "selected_count":0 }` |
|
||||
| 我的皮肤页点击皮肤(进入详情) | click | click_my_skin_theme_card | my_skin | theme_card | MySkinVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"my_skin", "element_id":"theme_card", "theme_id":"", "index":0 }` |
|
||||
| 登录页点击 Apple 登录 | click | click_login_apple_btn | login | apple_btn | KBLoginVC `onTapAppleLogin` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login", "element_id":"apple_btn" }` |
|
||||
| 登录页点击邮箱登录 | click | click_login_email_btn | login | email_btn | KBLoginVC `onTapEmailLogin` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login", "element_id":"email_btn" }` |
|
||||
| 登录页点击注册 | click | click_login_signup_btn | login | signup_btn | KBLoginVC `onTapSignUp` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login", "element_id":"signup_btn" }` |
|
||||
| 登录页点击忘记密码 | click | click_login_forgot_btn | login | forgot_btn | KBLoginVC `onTapForgotPassword` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login", "element_id":"forgot_btn" }` |
|
||||
| 邮箱登录页点击提交 | click | click_login_email_submit_btn | login_email | submit_btn | KBEmailLoginVC `onTapSubmit` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login_email", "element_id":"submit_btn" }` |
|
||||
| 邮箱注册页点击提交 | click | click_register_email_submit_btn | register_email | submit_btn | KBEmailRegistVC `onTapSubmit` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"register_email", "element_id":"submit_btn" }` |
|
||||
| 注册验证码页点击确认 | click | click_register_verify_confirm_btn | register_verify_email | confirm_btn | KBRegistVerEmailVC `onTapConfirm` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"register_verify_email", "element_id":"confirm_btn" }` |
|
||||
| 忘记密码(邮箱)点击下一步 | click | click_forgot_email_next_btn | forgot_password_email | next_btn | KBForgetPwdVC `onTapNext` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"forgot_password_email", "element_id":"next_btn" }` |
|
||||
| 忘记密码(验证码)点击下一步 | click | click_forgot_verify_next_btn | forgot_password_verify | next_btn | KBForgetVerPwdVC `onTapNext` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"forgot_password_verify", "element_id":"next_btn" }` |
|
||||
| 忘记密码(新密码)点击下一步 | click | click_forgot_newpwd_next_btn | forgot_password_newpwd | next_btn | KBForgetPwdNewPwdVC `onTapNext` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"forgot_password_newpwd", "element_id":"next_btn" }` |
|
||||
| VIP页选择套餐 | click | click_vip_select_plan | vip_pay | plan_item | KBVipPay `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"vip_pay", "element_id":"plan_item", "product_id":"", "index":0 }` |
|
||||
| VIP页点击支付 | click | click_vip_pay_btn | vip_pay | pay_btn | KBVipPay `onTapPayButton` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"vip_pay", "element_id":"pay_btn", "product_id":"" }` |
|
||||
| VIP页点击恢复购买 | click | click_vip_restore_btn | vip_pay | restore_btn | KBVipPay `onTapRestoreButton` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"vip_pay", "element_id":"restore_btn" }` |
|
||||
| VIP页点击关闭 | click | click_vip_close_btn | vip_pay | close_btn | KBVipPay `onTapClose` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"vip_pay", "element_id":"close_btn" }` |
|
||||
| 积分充值页选择商品 | click | click_points_select_product | points_recharge | product_item | KBJfPay `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"points_recharge", "element_id":"product_item", "product_id":"", "index":0 }` |
|
||||
| 积分充值页点击充值 | click | click_points_pay_btn | points_recharge | pay_btn | KBJfPay `onTapPayButton` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"points_recharge", "element_id":"pay_btn", "product_id":"" }` |
|
||||
| 引导页点击复制示例1 | click | click_guide_copy_example_1 | guide | copy_example_1 | KBGuideTopCell `kb_onTapQ1` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"guide", "element_id":"copy_example_1" }` |
|
||||
| 引导页点击复制示例2 | click | click_guide_copy_example_2 | guide | copy_example_2 | KBGuideTopCell `kb_onTapQ2` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"guide", "element_id":"copy_example_2" }` |
|
||||
|
||||
---
|
||||
|
||||
## B. 键盘扩展(CustomKeyboard)
|
||||
|
||||
### B1)页面曝光(触发:显示/切换时机)
|
||||
|
||||
| 注释 | 事件类型 | 事件名称 | page_id | iOS 对应页面/视图 | Android 对应页面 | 触发时机 | 事件参数(示例) |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 键盘首次显示 | page_exposure | enter_keyboard | keyboard | KeyboardViewController | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"keyboard" }` |
|
||||
| 打开功能面板 | page_exposure | enter_keyboard_function_panel | keyboard_function_panel | KBFunctionView | Android 自定义 | showFunctionPanel:YES | `{ "token":"", "page_id":"keyboard_function_panel" }` |
|
||||
| 关闭功能面板(回到主键盘) | page_exposure | enter_keyboard_main_panel | keyboard_main_panel | KBKeyBoardMainView | Android 自定义 | showFunctionPanel:NO | `{ "token":"", "page_id":"keyboard_main_panel" }` |
|
||||
| 打开设置页 | page_exposure | enter_keyboard_settings | keyboard_settings | KBSettingView | Android 自定义 | showSettingView:YES | `{ "token":"", "page_id":"keyboard_settings" }` |
|
||||
| 打开订阅/充值面板 | page_exposure | enter_keyboard_subscription_panel | keyboard_subscription_panel | KBKeyboardSubscriptionView | Android 自定义 | showSubscriptionPanel | `{ "token":"", "page_id":"keyboard_subscription_panel" }` |
|
||||
|
||||
### B2)点击事件(键盘工具栏 / 功能面板 / 订阅面板)
|
||||
|
||||
| 注释 | 事件类型 | 事件名称 | page_id | element_id | iOS 对应控件/方法 | Android 对应控件 | 触发时机 | 事件参数(示例) |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| 点击键盘顶部工具栏(index=0 打开功能面板) | click | click_keyboard_toolbar_action | keyboard_main_panel | toolbar_action | KBKeyBoardMainViewDelegate `didTapToolActionAtIndex:` | Android 自定义 | 点击工具栏 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"toolbar_action", "index":0 }` |
|
||||
| 点击键盘设置按钮 | click | click_keyboard_settings_btn | keyboard_main_panel | settings_btn | `keyBoardMainViewDidTapSettings:` | Android 自定义 | 点击设置 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"settings_btn" }` |
|
||||
| 点击设置页返回 | click | click_keyboard_settings_back_btn | keyboard_settings | back_btn | KeyboardViewController `onTapSettingsBack` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_settings", "element_id":"back_btn" }` |
|
||||
| 点击撤销删除 | click | click_keyboard_undo_btn | keyboard_main_panel | undo_btn | `keyBoardMainViewDidTapUndo:` | Android 自定义 | 点击撤销 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"undo_btn" }` |
|
||||
| 点击表情面板搜索 | click | click_keyboard_emoji_search_btn | keyboard_main_panel | emoji_search_btn | `keyBoardMainViewDidTapEmojiSearch:` | Android 自定义 | 点击搜索 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"emoji_search_btn" }` |
|
||||
| 点击联想词条 | click | click_keyboard_suggestion_item | keyboard_main_panel | suggestion_item | `didSelectSuggestion:` | Android 自定义 | 点击候选 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"suggestion_item", "index":0 }` |
|
||||
| 功能面板点击“粘贴” | click | click_keyboard_function_paste_btn | keyboard_function_panel | paste_btn | KBFunctionView `onTapPaste` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"paste_btn" }` |
|
||||
| 功能面板点击“删除” | click | click_keyboard_function_delete_btn | keyboard_function_panel | delete_btn | KBFunctionView `onTapDelete` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"delete_btn" }` |
|
||||
| 功能面板点击“清空” | click | click_keyboard_function_clear_btn | keyboard_function_panel | clear_btn | KBFunctionView `onTapClear` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"clear_btn" }` |
|
||||
| 功能面板点击“发送” | click | click_keyboard_function_send_btn | keyboard_function_panel | send_btn | KBFunctionView `onTapSend` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"send_btn" }` |
|
||||
| 功能面板点击“人设/标签”条目 | click | click_keyboard_function_tag_item | keyboard_function_panel | renshe_item | KBFunctionTagListView `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"renshe_item", "index":0, "id":456, "name":"" }` |
|
||||
| 功能面板右侧点击“登录/充值”入口(未登录走登录) | click | click_keyboard_function_right_action | keyboard_function_panel | right_action | KeyboardViewController `didRightTapToolActionAtIndex:` | Android 自定义 | 点击右侧入口 | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"right_action", "action":"login_or_recharge" }` |
|
||||
| 订阅面板点击关闭 | click | click_keyboard_subscription_close_btn | keyboard_subscription_panel | close_btn | `subscriptionViewDidTapClose:` | Android 自定义 | 点击关闭 | `{ "token":"", "page_id":"keyboard_subscription_panel", "element_id":"close_btn" }` |
|
||||
| 订阅面板点击购买某商品 | click | click_keyboard_subscription_product_btn | keyboard_subscription_panel | product_btn | `didTapPurchaseForProduct:` | Android 自定义 | 点击购买 | `{ "token":"", "page_id":"keyboard_subscription_panel", "element_id":"product_btn", "product_id":"", "index":0 }` |
|
||||
BIN
KBMaiPointEventTable.xlsx
Normal file
BIN
KBMaiPointEventTable.xlsx
Normal file
Binary file not shown.
@@ -88,7 +88,8 @@
|
||||
|
||||
#if __OBJC__
|
||||
static inline CGFloat KBScreenWidth(void) {
|
||||
return [UIScreen mainScreen].bounds.size.width;
|
||||
CGSize size = [UIScreen mainScreen].bounds.size;
|
||||
return MIN(size.width, size.height);
|
||||
}
|
||||
|
||||
static inline CGFloat KBScaleFactor(void) {
|
||||
|
||||
20
Shared/KBLog.h
Normal file
20
Shared/KBLog.h
Normal 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
|
||||
|
||||
@@ -6,29 +6,77 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#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
|
||||
|
||||
#ifndef KB_MAI_POINT_PATH_NEW_ACCOUNT
|
||||
#define KB_MAI_POINT_PATH_NEW_ACCOUNT @"/newAccount"
|
||||
#endif
|
||||
|
||||
#ifndef KB_MAI_POINT_PATH_GENERIC_DATA
|
||||
#define KB_MAI_POINT_PATH_GENERIC_DATA @"/genericData"
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern NSString * const KBMaiPointErrorDomain;
|
||||
extern NSString * const KBMaiPointEventTypePageExposure;
|
||||
extern NSString * const KBMaiPointEventTypeClick;
|
||||
|
||||
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.
|
||||
@interface KBMaiPointReporter : NSObject
|
||||
|
||||
+ (instancetype)sharedReporter;
|
||||
|
||||
/// 统一埋点:POST /genericData
|
||||
/// - eventType: 建议取值 `page_exposure` / `click`
|
||||
/// - eventName: 统一事件名(如 enter_xxx / click_xxx)
|
||||
/// - value: 事件参数字典(内部会自动注入 token;无 token 时为 @"")
|
||||
- (void)reportEventType:(NSString *)eventType
|
||||
eventName:(NSString *)eventName
|
||||
value:(nullable NSDictionary *)value
|
||||
completion:(KBMaiPointReportCompletion _Nullable)completion;
|
||||
|
||||
/// 页面曝光快捷方法:内部会补齐 page_id
|
||||
- (void)reportPageExposureWithEventName:(NSString *)eventName
|
||||
pageId:(NSString *)pageId
|
||||
extra:(nullable NSDictionary *)extra
|
||||
completion:(KBMaiPointReportCompletion _Nullable)completion;
|
||||
|
||||
/// 点击快捷方法:内部会补齐 page_id / element_id
|
||||
- (void)reportClickWithEventName:(NSString *)eventName
|
||||
pageId:(NSString *)pageId
|
||||
elementId:(NSString *)elementId
|
||||
extra:(nullable NSDictionary *)extra
|
||||
completion:(KBMaiPointReportCompletion _Nullable)completion;
|
||||
|
||||
/// POST /newAccount with type + account.
|
||||
- (void)reportNewAccountWithType:(NSString *)type
|
||||
account:(NSString *)account
|
||||
account:(nullable NSString *)account
|
||||
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.
|
||||
- (void)postPath:(NSString *)path
|
||||
parameters:(NSDictionary *)parameters
|
||||
|
||||
@@ -4,8 +4,39 @@
|
||||
//
|
||||
|
||||
#import "KBMaiPointReporter.h"
|
||||
#import "KBLog.h"
|
||||
#import "KBAuthManager.h"
|
||||
#if __has_include(<UIKit/UIKit.h>)
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <objc/runtime.h>
|
||||
#endif
|
||||
|
||||
NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain";
|
||||
NSString * const KBMaiPointEventTypePageExposure = @"page_exposure";
|
||||
NSString * const KBMaiPointEventTypeClick = @"click";
|
||||
|
||||
#if DEBUG
|
||||
static void KBMaiPoint_DebugLogURL(NSURLRequest *request) {
|
||||
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
|
||||
|
||||
@@ -18,12 +49,96 @@ NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain";
|
||||
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 {
|
||||
// 若外部传了 token,也做一次兜底(nil -> @"" / trim)
|
||||
val[@"token"] = [self kb_trimmedStringOrEmpty:val[@"token"]];
|
||||
}
|
||||
|
||||
NSDictionary *params = @{
|
||||
// 字段兼容:后端若用 eventId 统计,也能直接用 eventName
|
||||
@"eventType": trimmedType,
|
||||
@"eventName": trimmedName,
|
||||
@"eventId": trimmedName,
|
||||
@"value": val.copy
|
||||
};
|
||||
[self postPath:KB_MAI_POINT_PATH_GENERIC_DATA parameters:params completion:completion];
|
||||
}
|
||||
|
||||
- (void)reportPageExposureWithEventName:(NSString *)eventName
|
||||
pageId:(NSString *)pageId
|
||||
extra:(NSDictionary * _Nullable)extra
|
||||
completion:(KBMaiPointReportCompletion _Nullable)completion {
|
||||
NSString *pid = [self kb_trimmedStringOrEmpty:pageId];
|
||||
NSMutableDictionary *val = [NSMutableDictionary dictionary];
|
||||
if (pid.length > 0) {
|
||||
val[@"page_id"] = pid;
|
||||
}
|
||||
if ([extra isKindOfClass:[NSDictionary class]] && extra.count > 0) {
|
||||
[val addEntriesFromDictionary:extra];
|
||||
}
|
||||
[self reportEventType:KBMaiPointEventTypePageExposure eventName:eventName value:val completion:completion];
|
||||
}
|
||||
|
||||
- (void)reportClickWithEventName:(NSString *)eventName
|
||||
pageId:(NSString *)pageId
|
||||
elementId:(NSString *)elementId
|
||||
extra:(NSDictionary * _Nullable)extra
|
||||
completion:(KBMaiPointReportCompletion _Nullable)completion {
|
||||
NSString *pid = [self kb_trimmedStringOrEmpty:pageId];
|
||||
NSString *eid = [self kb_trimmedStringOrEmpty:elementId];
|
||||
NSMutableDictionary *val = [NSMutableDictionary dictionary];
|
||||
if (pid.length > 0) {
|
||||
val[@"page_id"] = pid;
|
||||
}
|
||||
if (eid.length > 0) {
|
||||
val[@"element_id"] = eid;
|
||||
}
|
||||
if ([extra isKindOfClass:[NSDictionary class]] && extra.count > 0) {
|
||||
[val addEntriesFromDictionary:extra];
|
||||
}
|
||||
[self reportEventType:KBMaiPointEventTypeClick eventName:eventName value:val completion:completion];
|
||||
}
|
||||
|
||||
- (void)reportNewAccountWithType:(NSString *)type
|
||||
account:(NSString *)account
|
||||
completion:(KBMaiPointReportCompletion)completion {
|
||||
NSString *trimmedType = [type stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
NSString *trimmedAccount = [account stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
if (trimmedType.length == 0 || trimmedAccount.length == 0) {
|
||||
account:(NSString * _Nullable)account
|
||||
completion:(KBMaiPointReportCompletion _Nullable)completion {
|
||||
NSString *trimmedType = [self kb_trimmedStringOrEmpty:type];
|
||||
NSString *trimmedAccount = [self kb_trimmedStringOrEmpty:account];
|
||||
if (trimmedType.length == 0) {
|
||||
NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain
|
||||
code:-1
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"Invalid parameter"}];
|
||||
@@ -37,14 +152,43 @@ NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain";
|
||||
|
||||
NSDictionary *params = @{
|
||||
@"type": trimmedType,
|
||||
@"account": trimmedAccount
|
||||
@"account": trimmedAccount ?: @"",
|
||||
@"token": [self kb_currentTokenOrEmpty]
|
||||
};
|
||||
[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
|
||||
parameters:(NSDictionary *)parameters
|
||||
completion:(KBMaiPointReportCompletion)completion {
|
||||
completion:(KBMaiPointReportCompletion _Nullable)completion {
|
||||
if (path.length == 0 || ![parameters isKindOfClass:[NSDictionary class]]) {
|
||||
NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain
|
||||
code:-1
|
||||
@@ -89,6 +233,10 @@ NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain";
|
||||
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
request.HTTPBody = body;
|
||||
|
||||
#if DEBUG
|
||||
KBMaiPoint_DebugLogURL(request);
|
||||
#endif
|
||||
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
|
||||
config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
|
||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
|
||||
@@ -115,6 +263,10 @@ NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain";
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
KBMaiPoint_DebugLogError(response, finalError);
|
||||
#endif
|
||||
|
||||
if (completion) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
completion(success, finalError);
|
||||
@@ -125,3 +277,123 @@ NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain";
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
[self kb_updateBackButtonVisibility];
|
||||
}
|
||||
|
||||
- (void)dealloc{
|
||||
KBLOG(@"页面销毁 -- 💥💥 -- %@",[self class]);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Custom NavBar
|
||||
|
||||
|
||||
@@ -181,11 +181,21 @@
|
||||
|
||||
/// 点击第一条示例文案
|
||||
- (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]];
|
||||
}
|
||||
|
||||
/// 点击第二条示例文案
|
||||
- (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]];
|
||||
}
|
||||
|
||||
|
||||
@@ -164,6 +164,11 @@
|
||||
#pragma mark - Actions
|
||||
- (void)onTapBuyAction {
|
||||
// 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) {
|
||||
[[KBUserSessionManager shared] goLoginVC];
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
if (joined) {
|
||||
// 已加入状态:灰背景、打勾
|
||||
[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];
|
||||
} else {
|
||||
// 可加入状态:绿色加号
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
self.downloadLabel.text = character.download ?: @"";
|
||||
self.descLabel.text = character.characterBackground ?: @"";
|
||||
[self.avatarView kb_setImageURL:character.avatarUrl placeholder:KBAvatarPlaceholderImage];
|
||||
[self kb_updateSaveButtonWithAdded:character.added];
|
||||
}
|
||||
|
||||
#pragma mark - Build UI
|
||||
@@ -138,6 +139,24 @@
|
||||
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
|
||||
|
||||
- (UIImage *)placeholderAvatar {
|
||||
@@ -246,4 +265,3 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -281,7 +281,7 @@
|
||||
if (added) {
|
||||
// 已添加:灰色背景 + 勾选,且禁用点击
|
||||
[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.enabled = NO;
|
||||
} else {
|
||||
|
||||
@@ -284,9 +284,44 @@
|
||||
pop.isClickBgDismiss = YES; // 点击背景关闭
|
||||
pop.cornerRadius = 0; // 自定义 view 自处理圆角
|
||||
|
||||
KBWeakSelf
|
||||
__weak typeof(pop) weakPop = pop;
|
||||
content.saveHandler = ^{ [weakPop dismiss]; };
|
||||
content.closeHandler = ^{ [weakPop dismiss]; };
|
||||
content.saveHandler = ^{
|
||||
[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];
|
||||
|
||||
|
||||
@@ -48,10 +48,15 @@
|
||||
|
||||
KBWeakSelf
|
||||
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];
|
||||
[weakSelf.navigationController pushViewController:vc animated:true];
|
||||
};
|
||||
|
||||
|
||||
// 测试groups
|
||||
// NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:AppGroup];
|
||||
// // 写入一个简单字符串
|
||||
|
||||
@@ -210,9 +210,42 @@
|
||||
pop.isClickBgDismiss = YES; // 点击背景关闭
|
||||
pop.cornerRadius = 0; // 自定义 view 自处理圆角
|
||||
|
||||
KBWeakSelf
|
||||
__weak typeof(pop) weakPop = pop;
|
||||
content.saveHandler = ^{ [weakPop dismiss]; };
|
||||
content.closeHandler = ^{ [weakPop dismiss]; };
|
||||
content.saveHandler = ^{
|
||||
[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];
|
||||
}
|
||||
|
||||
@@ -226,6 +226,11 @@
|
||||
KBLOG(@"KBEmailLoginVC onTapSubmit, email=%@, pwdLen=%zd",
|
||||
self.emailTextField.text,
|
||||
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 *password = self.passwordTextField.text ? self.passwordTextField.text : @"";
|
||||
KBWeakSelf;
|
||||
|
||||
@@ -265,6 +265,12 @@
|
||||
NSString *pwd = self.passwordTextField.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) {
|
||||
[KBHUD showInfo:KBLocalized(@"Please complete all fields")];
|
||||
return;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#import "KBForgetPwdNewPwdVC.h"
|
||||
#import "KBLoginVM.h"
|
||||
#import "KBEmailRegistVC.h"
|
||||
#import "KBLoginVC.h"
|
||||
@interface KBForgetPwdNewPwdVC () <UITextFieldDelegate>
|
||||
|
||||
@property (nonatomic, strong) UILabel *titleLabel; // Reset Password
|
||||
@@ -113,6 +113,12 @@
|
||||
- (void)onTapNext {
|
||||
NSString *pwd = self.passwordTextField.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) {
|
||||
[KBHUD showInfo:KBLocalized(@"Please complete all fields")];
|
||||
@@ -131,7 +137,7 @@
|
||||
if (success) {
|
||||
UIViewController *targetVC = nil;
|
||||
for (UIViewController *vc in KB_CURRENT_NAV.viewControllers) {
|
||||
if ([vc isKindOfClass:[KBEmailRegistVC class]]) {
|
||||
if ([vc isKindOfClass:[KBLoginVC class]]) {
|
||||
targetVC = vc;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -66,6 +66,11 @@
|
||||
- (void)onTapNext {
|
||||
NSString *email = [self.emailTextField.text ?: @"" stringByTrimmingCharactersInSet:
|
||||
[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) {
|
||||
[KBHUD showInfo:KBLocalized(@"Enter Email Address")];
|
||||
return;
|
||||
|
||||
@@ -76,6 +76,11 @@
|
||||
|
||||
- (void)onTapNext {
|
||||
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) {
|
||||
[KBHUD showInfo:KBLocalized(@"Enter Email Verification Code")];
|
||||
return;
|
||||
|
||||
@@ -169,6 +169,11 @@
|
||||
|
||||
- (void)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) {
|
||||
if (success) {
|
||||
[KBHUD showInfo:KBLocalized(@"Signed in successfully")];
|
||||
@@ -190,6 +195,11 @@
|
||||
- (void)onTapEmailLogin {
|
||||
// 后续接入邮箱登录逻辑
|
||||
KBLOG(@"onTapEmailLogin");
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_login_email_btn"
|
||||
pageId:@"login"
|
||||
elementId:@"email_btn"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
KBEmailLoginVC *vc = [[KBEmailLoginVC alloc] init];
|
||||
UINavigationController *nav = KB_CURRENT_NAV;
|
||||
if ([nav isKindOfClass:[BaseNavigationController class]]) {
|
||||
@@ -207,6 +217,11 @@
|
||||
- (void)onTapSignUp {
|
||||
// 打开注册页
|
||||
KBLOG(@"onTapSignUp");
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_login_signup_btn"
|
||||
pageId:@"login"
|
||||
elementId:@"signup_btn"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
KBEmailRegistVC *vc = [[KBEmailRegistVC alloc] init];
|
||||
UINavigationController *nav = KB_CURRENT_NAV;
|
||||
if ([nav isKindOfClass:[BaseNavigationController class]]) {
|
||||
@@ -219,6 +234,11 @@
|
||||
- (void)onTapForgotPassword {
|
||||
// 打开忘记密码页
|
||||
KBLOG(@"onTapForgotPassword");
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_login_forgot_btn"
|
||||
pageId:@"login"
|
||||
elementId:@"forgot_btn"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
KBForgetPwdVC *vc = [[KBForgetPwdVC alloc] init];
|
||||
UINavigationController *nav = KB_CURRENT_NAV;
|
||||
if ([nav isKindOfClass:[BaseNavigationController class]]) {
|
||||
|
||||
@@ -89,6 +89,11 @@
|
||||
|
||||
- (void)onTapConfirm {
|
||||
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) {
|
||||
[KBHUD showInfo:KBLocalized(@"Enter Email Verification Code")];
|
||||
return;
|
||||
|
||||
@@ -90,6 +90,12 @@
|
||||
[KBHUD showInfo:KBLocalized(@"Please Enter The Content")];
|
||||
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;
|
||||
[self.viewModel submitFeedbackWithContent:content completion:^(BOOL success, NSError * _Nullable error) {
|
||||
if (!success) { return; }
|
||||
|
||||
@@ -257,6 +257,11 @@ static NSString * const kKBMyKeyboardCellId = @"kKBMyKeyboardCellId";
|
||||
|
||||
- (void)onSave {
|
||||
// 点击底部保存按钮时,调用更新用户人设排序接口
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_my_keyboard_save_btn"
|
||||
pageId:@"my_keyboard"
|
||||
elementId:@"save_btn"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
[self kb_updateUserCharacterSortWithShowHUD:YES];
|
||||
}
|
||||
|
||||
@@ -302,7 +307,11 @@ static NSString * const kKBMyKeyboardCellId = @"kKBMyKeyboardCellId";
|
||||
[KBHUD showInfo:msg];
|
||||
return;
|
||||
}
|
||||
|
||||
[self.viewModel fetchCharacterListByUserWithCompletion:^(NSArray<KBCharacter *> * _Nonnull characterArray, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"[KBHomeVM] refresh user characters failed: %@", error);
|
||||
}
|
||||
}];
|
||||
if (showHUD) {
|
||||
[KBHUD showSuccess:KBLocalized(@"Saved")];
|
||||
}
|
||||
|
||||
@@ -257,9 +257,21 @@
|
||||
|
||||
#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 {
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_person_logout_btn"
|
||||
pageId:@"person_info"
|
||||
elementId:@"logout_btn"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
[self.myVM logout];
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,13 @@ static NSString * const kMySkinCellId = @"kMySkinCellId";
|
||||
- (void)onToggleEdit {
|
||||
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"))
|
||||
forState:UIControlStateNormal];
|
||||
@@ -154,6 +161,13 @@ static NSString * const kMySkinCellId = @"kMySkinCellId";
|
||||
NSArray<NSIndexPath *> *selectedIndexPaths = [[self.collectionView indexPathsForSelectedItems] sortedArrayUsingSelector:@selector(compare:)];
|
||||
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];
|
||||
for (NSIndexPath *ip in selectedIndexPaths) {
|
||||
if (ip.item >= self.data.count) { continue; }
|
||||
@@ -245,6 +259,16 @@ static NSString * const kMySkinCellId = @"kMySkinCellId";
|
||||
// 非编辑态:可在此进入详情,当前示例不处理
|
||||
KBMyTheme *theme = self.data[indexPath.item];
|
||||
[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];
|
||||
vc.themeId = theme.themeId;
|
||||
[self.navigationController pushViewController:vc animated:true];
|
||||
|
||||
@@ -120,6 +120,19 @@
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
NSDictionary *info = self.data[indexPath.section][indexPath.row];
|
||||
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"]) {
|
||||
[self.navigationController pushViewController:[KBNoticeVC new] animated:true];
|
||||
|
||||
@@ -128,9 +141,12 @@
|
||||
if (!self.viewModel) {
|
||||
self.viewModel = [[KBMyVM alloc] init];
|
||||
}
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[KBHUD show];
|
||||
[self.viewModel fetchInviteCodeWithCompletion:^(KBInviteCodeModel * _Nullable inviteCode, NSError * _Nullable error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
if (!self) { return; }
|
||||
[KBHUD dismiss];
|
||||
if (error) {
|
||||
[KBHUD showInfo:error.localizedDescription ?: KBLocalized(@"Network error")];
|
||||
@@ -142,9 +158,26 @@
|
||||
[KBHUD showInfo:KBLocalized(@"Failed")];
|
||||
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;
|
||||
[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];
|
||||
});
|
||||
}];
|
||||
|
||||
|
||||
@@ -202,6 +202,18 @@ static NSString * const kKBJfPayCellId = @"kKBJfPayCellId";
|
||||
NSInteger old = self.selectedIndex;
|
||||
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];
|
||||
[newCell applySelected:YES animated:YES];
|
||||
if (old >= 0 && old < self.data.count) {
|
||||
@@ -319,6 +331,15 @@ static NSString * const kKBJfPayCellId = @"kKBJfPayCellId";
|
||||
[KBHUD showInfo:KBLocalized(@"Please select a product")];
|
||||
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;
|
||||
if (productId.length == 0) {
|
||||
[KBHUD showInfo:KBLocalized(@"Product unavailable")];
|
||||
|
||||
@@ -293,6 +293,11 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
|
||||
|
||||
#pragma mark - Action
|
||||
- (void)onTapClose{
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_vip_close_btn"
|
||||
pageId:@"vip_pay"
|
||||
elementId:@"close_btn"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
[self.navigationController popViewControllerAnimated:true];
|
||||
}
|
||||
|
||||
@@ -303,6 +308,15 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
|
||||
[KBHUD showInfo:KBLocalized(@"Please select a product")];
|
||||
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;
|
||||
if (productId.length == 0) {
|
||||
[KBHUD showInfo:KBLocalized(@"Product unavailable")];
|
||||
@@ -326,6 +340,11 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
|
||||
}
|
||||
|
||||
- (void)onTapRestoreButton {
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_vip_restore_btn"
|
||||
pageId:@"vip_pay"
|
||||
elementId:@"restore_btn"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
[KBHUD show];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[[KBStoreKitBridge shared] restorePurchasesWithCompletion:^(BOOL success, NSString * _Nullable message) {
|
||||
@@ -386,6 +405,18 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId";
|
||||
if (self.selectedIndex == indexPath.item) { return; }
|
||||
NSInteger old = self.selectedIndex;
|
||||
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];
|
||||
[newCell applySelected:YES animated:YES];
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
- (void)applePayReqWithParams:(NSDictionary *)params
|
||||
needShow:(BOOL)needShow
|
||||
completion:(KBPayCompletion)completion {
|
||||
// if (needShow) { [KBHUD show]; }
|
||||
[KBHUD showWithStatus:@"Please wait"];
|
||||
if (needShow) {
|
||||
[KBHUD showWithStatus:@"Please wait"];
|
||||
}
|
||||
[[KBNetworkManager shared] POST:API_VALIDATE_RECEIPT
|
||||
jsonBody:params
|
||||
headers:nil
|
||||
@@ -31,11 +32,15 @@
|
||||
NSNumber *codeNum = error.userInfo[@"code"];
|
||||
NSInteger bizCode = codeNum.integerValue; // 这里就是底层附带的业务 code
|
||||
// 根据 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -120,6 +120,13 @@ static NSString * const kResultCellId = @"KBSkinCardCell";
|
||||
KBSearchThemeModel *model = self.resultItems[indexPath.item];
|
||||
NSString *themeId = [model.themeId stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
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];
|
||||
vc.themeId = themeId;
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
|
||||
@@ -244,6 +244,11 @@ typedef NS_ENUM(NSInteger, KBSearchSection) {
|
||||
|
||||
/// 清空历史
|
||||
- (void)clearHistory {
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_search_clear_history"
|
||||
pageId:@"search"
|
||||
elementId:@"clear_history"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
[self.historyWords removeAllObjects];
|
||||
self.historyExpanded = NO;
|
||||
[self saveHistoryWordsToLocal:self.historyWords];
|
||||
@@ -369,10 +374,22 @@ typedef NS_ENUM(NSInteger, KBSearchSection) {
|
||||
NSArray *list = [self currentDisplayHistory];
|
||||
NSString *kw = list[indexPath.item];
|
||||
if ([kw isEqualToString:kMoreToken]) {
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_search_history_more"
|
||||
pageId:@"search"
|
||||
elementId:@"history_more"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
// 展开所有历史
|
||||
self.historyExpanded = YES;
|
||||
[self.collectionView reloadData];
|
||||
} 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 performSearch:kw];
|
||||
[self openResultForKeyword:kw];
|
||||
@@ -381,6 +398,16 @@ typedef NS_ENUM(NSInteger, KBSearchSection) {
|
||||
}
|
||||
if (indexPath.section == KBSearchSectionRecommend) {
|
||||
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 ?: @""];
|
||||
}
|
||||
}
|
||||
@@ -394,6 +421,12 @@ typedef NS_ENUM(NSInteger, KBSearchSection) {
|
||||
// _searchBarView.placeholder = @"Themes";
|
||||
KBWeakSelf
|
||||
_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 openResultForKeyword:keyword];
|
||||
|
||||
@@ -173,6 +173,16 @@
|
||||
|
||||
- (void)kb_handleShopTapAtIndexPath:(NSIndexPath *)indexPath {
|
||||
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];
|
||||
vc.themeId = selTheme.themeId;
|
||||
[self.navigationController pushViewController:vc animated:true];
|
||||
|
||||
@@ -244,11 +244,21 @@ static const CGFloat JXheightForHeaderInSection = 50;
|
||||
|
||||
#pragma mark - action
|
||||
- (void)searchBtnAction{
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_shop_search_btn"
|
||||
pageId:@"shop"
|
||||
elementId:@"search_btn"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
KBSearchVC *vc = [[KBSearchVC alloc] init];
|
||||
[self.navigationController pushViewController:vc animated:true];
|
||||
}
|
||||
|
||||
- (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];
|
||||
[self.navigationController pushViewController:vc animated:true];
|
||||
}
|
||||
|
||||
@@ -130,6 +130,18 @@ typedef NS_ENUM(NSInteger, KBSkinDetailSection) {
|
||||
if (nextThemeId.length == 0) { 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,重新请求详情并刷新当前页面
|
||||
self.themeId = nextThemeId;
|
||||
self.detailModel = nil;
|
||||
@@ -242,6 +254,16 @@ typedef NS_ENUM(NSInteger, KBSkinDetailSection) {
|
||||
[KBHUD showInfo:KBLocalized(@"正在加载主题详情")];
|
||||
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);
|
||||
if (self.detailModel.isPurchased) {
|
||||
[self requestDownload];
|
||||
@@ -296,6 +318,7 @@ typedef NS_ENUM(NSInteger, KBSkinDetailSection) {
|
||||
fromViewController:self
|
||||
mode:KBSkinSourceModeRemoteZip
|
||||
completion:^(BOOL success) {
|
||||
[KBHUD dismiss];
|
||||
NSLog(@"%@[SkinDetail] download result id=%@",
|
||||
(success ? @"✅" : @"❌"),
|
||||
self.detailModel.themeId);
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
|
||||
//-----------------------------------------------宏定义全局----------------------------------------------------------/
|
||||
// 调试专用日志(DEBUG 打印,RELEASE 不打印)。尽量显眼,包含函数与行号。
|
||||
#ifndef KBLOG
|
||||
#if DEBUG
|
||||
#define KBLOG(fmt, ...) do { \
|
||||
NSString *kb_msg__ = [NSString stringWithFormat:(fmt), ##__VA_ARGS__]; \
|
||||
@@ -62,6 +63,7 @@
|
||||
#else
|
||||
#define KBLOG(...)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// 通用链接(Universal Links)统一配置
|
||||
// 仅需修改这里的域名/前缀,工程内所有使用 UL 的地方都会同步。
|
||||
|
||||
@@ -193,7 +193,11 @@ static void *KBPermPlayerPresentationSizeContext = &KBPermPlayerPresentationSize
|
||||
}
|
||||
|
||||
- (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];
|
||||
UIApplication *app = [UIApplication sharedApplication];
|
||||
if ([app canOpenURL:url]) {
|
||||
@@ -206,6 +210,11 @@ static void *KBPermPlayerPresentationSizeContext = &KBPermPlayerPresentationSize
|
||||
}
|
||||
|
||||
- (void)closeButtonAction{
|
||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_permission_close_btn"
|
||||
pageId:@"keyboard_permission_guide"
|
||||
elementId:@"close_btn"
|
||||
extra:nil
|
||||
completion:nil];
|
||||
[self.navigationController popViewControllerAnimated:true];
|
||||
}
|
||||
|
||||
@@ -286,13 +295,6 @@ static void *KBPermPlayerPresentationSizeContext = &KBPermPlayerPresentationSize
|
||||
[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
|
||||
|
||||
- (UIButton *)backButton {
|
||||
|
||||
Reference in New Issue
Block a user