diff --git a/CustomKeyboard/KeyboardViewController.m b/CustomKeyboard/KeyboardViewController.m index 74a30bf..20b6069 100644 --- a/CustomKeyboard/KeyboardViewController.m +++ b/CustomKeyboard/KeyboardViewController.m @@ -277,7 +277,16 @@ 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]; } // 可选:把当前显示的视图置顶,避免层级遮挡 @@ -291,6 +300,10 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, /// 显示/隐藏设置页(高度与 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; @@ -342,6 +355,10 @@ 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) { @@ -427,6 +444,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 +459,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 +476,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 +525,11 @@ 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]; @@ -510,10 +559,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]; } @@ -588,6 +651,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]; } diff --git a/CustomKeyboard/View/Function/KBFunctionTagListView.m b/CustomKeyboard/View/Function/KBFunctionTagListView.m index c389ac2..95c3a09 100644 --- a/CustomKeyboard/View/Function/KBFunctionTagListView.m +++ b/CustomKeyboard/View/Function/KBFunctionTagListView.m @@ -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]; } } diff --git a/CustomKeyboard/View/KBFunctionView.m b/CustomKeyboard/View/KBFunctionView.m index 8b6fa66..8faf6b1 100644 --- a/CustomKeyboard/View/KBFunctionView.m +++ b/CustomKeyboard/View/KBFunctionView.m @@ -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 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); diff --git a/KBMaiPointEventTable.md b/KBMaiPointEventTable.md new file mode 100644 index 0000000..84801ff --- /dev/null +++ b/KBMaiPointEventTable.md @@ -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 }` | diff --git a/KBMaiPointEventTable.xlsx b/KBMaiPointEventTable.xlsx new file mode 100644 index 0000000..785e13b Binary files /dev/null and b/KBMaiPointEventTable.xlsx differ diff --git a/Shared/KBMaiPointReporter.h b/Shared/KBMaiPointReporter.h index 8b3ec19..2d886db 100644 --- a/Shared/KBMaiPointReporter.h +++ b/Shared/KBMaiPointReporter.h @@ -6,7 +6,7 @@ #import #ifndef KB_MAI_POINT_BASE_URL -#define KB_MAI_POINT_BASE_URL @"http://192.168.2.188:35310/api" +#define KB_MAI_POINT_BASE_URL @"http://192.168.2.21:35310/api" #endif #ifndef KB_MAI_POINT_PATH_NEW_ACCOUNT @@ -20,6 +20,8 @@ NS_ASSUME_NONNULL_BEGIN extern NSString * const KBMaiPointErrorDomain; +extern NSString * const KBMaiPointEventTypePageExposure; +extern NSString * const KBMaiPointEventTypeClick; typedef void (^KBMaiPointReportCompletion)(BOOL success, NSError * _Nullable error); @@ -39,6 +41,28 @@ typedef NS_ENUM(NSInteger, KBMaiPointGenericReportType) { + (instancetype)sharedReporter; +/// 统一埋点:POST /genericData +/// - eventType: 建议取值 `page_exposure` / `click` +/// - eventName: 统一事件名(如 enter_xxx / click_xxx) +/// - value: 事件参数字典(内部会自动注入 token;无 token 时为 @"") +- (void)reportEventType:(NSString *)eventType + eventName:(NSString *)eventName + value:(nullable NSDictionary *)value + completion:(KBMaiPointReportCompletion _Nullable)completion; + +/// 页面曝光快捷方法:内部会补齐 page_id +- (void)reportPageExposureWithEventName:(NSString *)eventName + pageId:(NSString *)pageId + extra:(nullable NSDictionary *)extra + completion:(KBMaiPointReportCompletion _Nullable)completion; + +/// 点击快捷方法:内部会补齐 page_id / element_id +- (void)reportClickWithEventName:(NSString *)eventName + pageId:(NSString *)pageId + elementId:(NSString *)elementId + extra:(nullable NSDictionary *)extra + completion:(KBMaiPointReportCompletion _Nullable)completion; + /// POST /newAccount with type + account. - (void)reportNewAccountWithType:(NSString *)type account:(nullable NSString *)account diff --git a/Shared/KBMaiPointReporter.m b/Shared/KBMaiPointReporter.m index 427541e..2f29c62 100644 --- a/Shared/KBMaiPointReporter.m +++ b/Shared/KBMaiPointReporter.m @@ -5,8 +5,15 @@ #import "KBMaiPointReporter.h" #import "KBLog.h" +#import "KBAuthManager.h" +#if __has_include() +#import +#import +#endif NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain"; +NSString * const KBMaiPointEventTypePageExposure = @"page_exposure"; +NSString * const KBMaiPointEventTypeClick = @"click"; #if DEBUG static void KBMaiPoint_DebugLogURL(NSURLRequest *request) { @@ -47,12 +54,91 @@ static void KBMaiPoint_DebugLogError(NSURLResponse *response, NSError *error) { return [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] ?: @""; } +- (NSString *)kb_currentTokenOrEmpty { + NSString *t = [KBAuthManager shared].current.accessToken; + return [self kb_trimmedStringOrEmpty:t]; +} + +- (void)reportEventType:(NSString *)eventType + eventName:(NSString *)eventName + value:(NSDictionary * _Nullable)value + completion:(KBMaiPointReportCompletion _Nullable)completion { + NSString *trimmedType = [self kb_trimmedStringOrEmpty:eventType]; + NSString *trimmedName = [self kb_trimmedStringOrEmpty:eventName]; + if (trimmedType.length == 0 || trimmedName.length == 0) { + NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain + code:-1 + userInfo:@{NSLocalizedDescriptionKey: @"Invalid parameter"}]; + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(NO, error); + }); + } + return; + } + + NSMutableDictionary *val = [NSMutableDictionary dictionary]; + if ([value isKindOfClass:[NSDictionary class]] && value.count > 0) { + [val addEntriesFromDictionary:value]; + } + if (![val[@"token"] isKindOfClass:NSString.class]) { + val[@"token"] = [self kb_currentTokenOrEmpty]; + } else { + // 若外部传了 token,也做一次兜底(nil -> @"" / trim) + val[@"token"] = [self kb_trimmedStringOrEmpty:val[@"token"]]; + } + + NSDictionary *params = @{ + // 字段兼容:后端若用 eventId 统计,也能直接用 eventName + @"eventType": trimmedType, + @"eventName": trimmedName, + @"eventId": trimmedName, + @"value": val.copy + }; + [self postPath:KB_MAI_POINT_PATH_GENERIC_DATA parameters:params completion:completion]; +} + +- (void)reportPageExposureWithEventName:(NSString *)eventName + pageId:(NSString *)pageId + extra:(NSDictionary * _Nullable)extra + completion:(KBMaiPointReportCompletion _Nullable)completion { + NSString *pid = [self kb_trimmedStringOrEmpty:pageId]; + NSMutableDictionary *val = [NSMutableDictionary dictionary]; + if (pid.length > 0) { + val[@"page_id"] = pid; + } + if ([extra isKindOfClass:[NSDictionary class]] && extra.count > 0) { + [val addEntriesFromDictionary:extra]; + } + [self reportEventType:KBMaiPointEventTypePageExposure eventName:eventName value:val completion:completion]; +} + +- (void)reportClickWithEventName:(NSString *)eventName + pageId:(NSString *)pageId + elementId:(NSString *)elementId + extra:(NSDictionary * _Nullable)extra + completion:(KBMaiPointReportCompletion _Nullable)completion { + NSString *pid = [self kb_trimmedStringOrEmpty:pageId]; + NSString *eid = [self kb_trimmedStringOrEmpty:elementId]; + NSMutableDictionary *val = [NSMutableDictionary dictionary]; + if (pid.length > 0) { + val[@"page_id"] = pid; + } + if (eid.length > 0) { + val[@"element_id"] = eid; + } + if ([extra isKindOfClass:[NSDictionary class]] && extra.count > 0) { + [val addEntriesFromDictionary:extra]; + } + [self reportEventType:KBMaiPointEventTypeClick eventName:eventName value:val completion:completion]; +} + - (void)reportNewAccountWithType:(NSString *)type account:(NSString * _Nullable)account completion:(KBMaiPointReportCompletion _Nullable)completion { NSString *trimmedType = [self kb_trimmedStringOrEmpty:type]; NSString *trimmedAccount = [self kb_trimmedStringOrEmpty:account]; - if (trimmedType.length == 0 || trimmedAccount.length == 0) { + if (trimmedType.length == 0) { NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Invalid parameter"}]; @@ -66,7 +152,8 @@ static void KBMaiPoint_DebugLogError(NSURLResponse *response, NSError *error) { NSDictionary *params = @{ @"type": trimmedType, - @"account": trimmedAccount + @"account": trimmedAccount ?: @"", + @"token": [self kb_currentTokenOrEmpty] }; [self postPath:KB_MAI_POINT_PATH_NEW_ACCOUNT parameters:params completion:completion]; } @@ -83,27 +170,20 @@ static void KBMaiPoint_DebugLogError(NSURLResponse *response, NSError *error) { - (void)reportGenericDataWithEventType:(KBMaiPointGenericReportType)eventType account:(nullable NSString *)account completion:(KBMaiPointReportCompletion _Nullable)completion{ -// if ([KBUserSessionManager shared].isLoggedIn == false) { -// return; -// } - NSString *trimmedAccount = [self kb_trimmedStringOrEmpty:account]; - if (trimmedAccount.length == 0) { - NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain - code:-1 - userInfo:@{NSLocalizedDescriptionKey: @"Invalid parameter"}]; - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(NO, error); - }); - } - return; + // 兼容旧接口:没有 eventName 时给一个默认值,避免调用方崩溃 + NSString *typeStr = @"unknown"; + switch (eventType) { + case KBMaiPointGenericReportTypeClick: typeStr = KBMaiPointEventTypeClick; break; + case KBMaiPointGenericReportTypeExposure: typeStr = @"exposure"; break; + case KBMaiPointGenericReportTypePage: typeStr = KBMaiPointEventTypePageExposure; break; + default: break; } - - NSDictionary *params = @{ - @"eventId": @"123", - @"account": account - }; - [self postPath:KB_MAI_POINT_PATH_GENERIC_DATA parameters:params completion:completion]; + NSMutableDictionary *val = [NSMutableDictionary dictionary]; + NSString *trimmedAccount = [self kb_trimmedStringOrEmpty:account]; + if (trimmedAccount.length > 0) { + val[@"account"] = trimmedAccount; + } + [self reportEventType:typeStr eventName:@"generic_event" value:val completion:completion]; } - (void)postPath:(NSString *)path @@ -197,3 +277,123 @@ static void KBMaiPoint_DebugLogError(NSURLResponse *response, NSError *error) { } @end + +#if __has_include() + +// ============================ +// 自动页面曝光(viewDidAppear) +// 说明:仅对“在表里登记过的 VC”生效;其它 VC 不处理。 +// ============================ + +static NSDictionary *KBMaiPoint_PageExposureMap(void) { + static 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 diff --git a/keyBoard/Class/Guard/V/KBGuideTopCell.m b/keyBoard/Class/Guard/V/KBGuideTopCell.m index 4b0e28d..46c83f1 100644 --- a/keyBoard/Class/Guard/V/KBGuideTopCell.m +++ b/keyBoard/Class/Guard/V/KBGuideTopCell.m @@ -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]]; } diff --git a/keyBoard/Class/Home/V/HomeHeadView.m b/keyBoard/Class/Home/V/HomeHeadView.m index 9f5e896..e9e7759 100644 --- a/keyBoard/Class/Home/V/HomeHeadView.m +++ b/keyBoard/Class/Home/V/HomeHeadView.m @@ -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]; diff --git a/keyBoard/Class/Home/VC/HomeMainVC.m b/keyBoard/Class/Home/VC/HomeMainVC.m index 9aacc63..387de89 100644 --- a/keyBoard/Class/Home/VC/HomeMainVC.m +++ b/keyBoard/Class/Home/VC/HomeMainVC.m @@ -48,15 +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]; }; - - - [[KBMaiPointReporter sharedReporter] reportGenericDataWithEventType:KBMaiPointGenericReportTypePage account:@"123" completion:^(BOOL success, NSError * _Nullable error) { - NSLog(@"==="); - }]; - + // 测试groups // NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:AppGroup]; // // 写入一个简单字符串 diff --git a/keyBoard/Class/Login/VC/KBEmailLoginVC.m b/keyBoard/Class/Login/VC/KBEmailLoginVC.m index be003d8..841e315 100644 --- a/keyBoard/Class/Login/VC/KBEmailLoginVC.m +++ b/keyBoard/Class/Login/VC/KBEmailLoginVC.m @@ -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; diff --git a/keyBoard/Class/Login/VC/KBEmailRegistVC.m b/keyBoard/Class/Login/VC/KBEmailRegistVC.m index b121ad4..d7726e4 100644 --- a/keyBoard/Class/Login/VC/KBEmailRegistVC.m +++ b/keyBoard/Class/Login/VC/KBEmailRegistVC.m @@ -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; diff --git a/keyBoard/Class/Login/VC/KBForgetPwdNewPwdVC.m b/keyBoard/Class/Login/VC/KBForgetPwdNewPwdVC.m index 8994c6b..cd379b1 100644 --- a/keyBoard/Class/Login/VC/KBForgetPwdNewPwdVC.m +++ b/keyBoard/Class/Login/VC/KBForgetPwdNewPwdVC.m @@ -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")]; diff --git a/keyBoard/Class/Login/VC/KBForgetPwdVC.m b/keyBoard/Class/Login/VC/KBForgetPwdVC.m index 3105530..971c6a4 100644 --- a/keyBoard/Class/Login/VC/KBForgetPwdVC.m +++ b/keyBoard/Class/Login/VC/KBForgetPwdVC.m @@ -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; diff --git a/keyBoard/Class/Login/VC/KBForgetVerPwdVC.m b/keyBoard/Class/Login/VC/KBForgetVerPwdVC.m index e4b61f2..9dd1aee 100644 --- a/keyBoard/Class/Login/VC/KBForgetVerPwdVC.m +++ b/keyBoard/Class/Login/VC/KBForgetVerPwdVC.m @@ -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; diff --git a/keyBoard/Class/Login/VC/KBLoginVC.m b/keyBoard/Class/Login/VC/KBLoginVC.m index 2b93580..e591f85 100644 --- a/keyBoard/Class/Login/VC/KBLoginVC.m +++ b/keyBoard/Class/Login/VC/KBLoginVC.m @@ -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]]) { diff --git a/keyBoard/Class/Login/VC/KBRegistVerEmailVC.m b/keyBoard/Class/Login/VC/KBRegistVerEmailVC.m index 9772155..b9ec71b 100644 --- a/keyBoard/Class/Login/VC/KBRegistVerEmailVC.m +++ b/keyBoard/Class/Login/VC/KBRegistVerEmailVC.m @@ -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; diff --git a/keyBoard/Class/Me/VC/KBFeedBackVC.m b/keyBoard/Class/Me/VC/KBFeedBackVC.m index 7922985..38435da 100644 --- a/keyBoard/Class/Me/VC/KBFeedBackVC.m +++ b/keyBoard/Class/Me/VC/KBFeedBackVC.m @@ -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; } diff --git a/keyBoard/Class/Me/VC/KBMyKeyBoardVC.m b/keyBoard/Class/Me/VC/KBMyKeyBoardVC.m index a1c2bda..7506031 100644 --- a/keyBoard/Class/Me/VC/KBMyKeyBoardVC.m +++ b/keyBoard/Class/Me/VC/KBMyKeyBoardVC.m @@ -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]; } diff --git a/keyBoard/Class/Me/VC/KBPersonInfoVC.m b/keyBoard/Class/Me/VC/KBPersonInfoVC.m index d0f5038..003aaba 100644 --- a/keyBoard/Class/Me/VC/KBPersonInfoVC.m +++ b/keyBoard/Class/Me/VC/KBPersonInfoVC.m @@ -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]; } diff --git a/keyBoard/Class/Me/VC/MySkinVC.m b/keyBoard/Class/Me/VC/MySkinVC.m index 5ad823c..872d5ab 100644 --- a/keyBoard/Class/Me/VC/MySkinVC.m +++ b/keyBoard/Class/Me/VC/MySkinVC.m @@ -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 *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 *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]; diff --git a/keyBoard/Class/Me/VC/MyVC.m b/keyBoard/Class/Me/VC/MyVC.m index baa45dd..988317e 100644 --- a/keyBoard/Class/Me/VC/MyVC.m +++ b/keyBoard/Class/Me/VC/MyVC.m @@ -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]; @@ -159,6 +172,11 @@ popover.permittedArrowDirections = 0; } UIPasteboard.generalPasteboard.string = textToCopy; + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_my_invite_copy" + pageId:@"my" + elementId:@"invite_copy" + extra:nil + completion:nil]; [self presentViewController:activityVC animated:YES completion:nil]; }); }]; diff --git a/keyBoard/Class/Pay/VC/KBJfPay.m b/keyBoard/Class/Pay/VC/KBJfPay.m index bd42600..611709e 100644 --- a/keyBoard/Class/Pay/VC/KBJfPay.m +++ b/keyBoard/Class/Pay/VC/KBJfPay.m @@ -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")]; diff --git a/keyBoard/Class/Pay/VC/KBVipPay.m b/keyBoard/Class/Pay/VC/KBVipPay.m index b43f65c..9d91517 100644 --- a/keyBoard/Class/Pay/VC/KBVipPay.m +++ b/keyBoard/Class/Pay/VC/KBVipPay.m @@ -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]; diff --git a/keyBoard/Class/Search/VC/KBSearchResultVC.m b/keyBoard/Class/Search/VC/KBSearchResultVC.m index d85738a..ddcabc5 100644 --- a/keyBoard/Class/Search/VC/KBSearchResultVC.m +++ b/keyBoard/Class/Search/VC/KBSearchResultVC.m @@ -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]; diff --git a/keyBoard/Class/Search/VC/KBSearchVC.m b/keyBoard/Class/Search/VC/KBSearchVC.m index 87f5f40..e456a4a 100644 --- a/keyBoard/Class/Search/VC/KBSearchVC.m +++ b/keyBoard/Class/Search/VC/KBSearchVC.m @@ -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]; diff --git a/keyBoard/Class/Shop/VC/KBShopItemVC.m b/keyBoard/Class/Shop/VC/KBShopItemVC.m index 90610f0..e6840fb 100644 --- a/keyBoard/Class/Shop/VC/KBShopItemVC.m +++ b/keyBoard/Class/Shop/VC/KBShopItemVC.m @@ -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]; diff --git a/keyBoard/Class/Shop/VC/KBShopVC.m b/keyBoard/Class/Shop/VC/KBShopVC.m index 88e23d7..b379c21 100644 --- a/keyBoard/Class/Shop/VC/KBShopVC.m +++ b/keyBoard/Class/Shop/VC/KBShopVC.m @@ -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]; } diff --git a/keyBoard/Class/Shop/VC/KBSkinDetailVC.m b/keyBoard/Class/Shop/VC/KBSkinDetailVC.m index 7c37042..6f8e88e 100644 --- a/keyBoard/Class/Shop/VC/KBSkinDetailVC.m +++ b/keyBoard/Class/Shop/VC/KBSkinDetailVC.m @@ -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]; diff --git a/keyBoard/VC/KBPermissionViewController.m b/keyBoard/VC/KBPermissionViewController.m index 380543a..9e44830 100644 --- a/keyBoard/VC/KBPermissionViewController.m +++ b/keyBoard/VC/KBPermissionViewController.m @@ -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 {