From c3e037e070f31d453f6ddfb93e5e4da0d9bbd835 Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Fri, 27 Feb 2026 14:49:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=9A=90=E7=A7=81=EF=BC=8C?= =?UTF-8?q?=E6=B3=A8=E9=94=80=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 3 +- CustomKeyboard/PrivacyInfo.xcprivacy | 44 + Shared/KBAPI.h | 1 + .../Localization/en.lproj/Localizable.strings | 2 + .../zh-Hans.lproj/Localizable.strings | 2 + keyBoard.xcodeproj/project.pbxproj | 8 + keyBoard/Class/AiTalk/VC/KBAiMainVC.h | 17 - keyBoard/Class/AiTalk/VC/KBAiMainVC.m | 809 ------------------ keyBoard/Class/Me/VC/KBPersonInfoVC.m | 50 +- keyBoard/Class/Me/VM/KBMyVM.h | 4 + keyBoard/Class/Me/VM/KBMyVM.m | 73 +- keyBoard/Info.plist | 4 - keyBoard/PrivacyInfo.xcprivacy | 76 ++ 13 files changed, 247 insertions(+), 846 deletions(-) create mode 100644 CustomKeyboard/PrivacyInfo.xcprivacy delete mode 100644 keyBoard/Class/AiTalk/VC/KBAiMainVC.h delete mode 100644 keyBoard/Class/AiTalk/VC/KBAiMainVC.m create mode 100644 keyBoard/PrivacyInfo.xcprivacy diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 1765a18..7ee8df5 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -6,7 +6,8 @@ "Bash(xcodebuild:*)", "Bash(plutil:*)", "Bash(find:*)", - "Bash(ls:*)" + "Bash(ls:*)", + "Bash(wc:*)" ] } } diff --git a/CustomKeyboard/PrivacyInfo.xcprivacy b/CustomKeyboard/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..decaaca --- /dev/null +++ b/CustomKeyboard/PrivacyInfo.xcprivacy @@ -0,0 +1,44 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherUserContent + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + + diff --git a/Shared/KBAPI.h b/Shared/KBAPI.h index 27615db..957913d 100644 --- a/Shared/KBAPI.h +++ b/Shared/KBAPI.h @@ -28,6 +28,7 @@ #define API_LOGOUT @"/user/logout" // 退出登录 +#define API_USER_CANCEL_ACCOUNT @"/user/cancelAccount" // 注销账户 #define API_UPDATA_INFO @"/user/updateInfo" // 更新用户 diff --git a/Shared/Localization/en.lproj/Localizable.strings b/Shared/Localization/en.lproj/Localizable.strings index 7a0f9f1..bc127b3 100644 --- a/Shared/Localization/en.lproj/Localizable.strings +++ b/Shared/Localization/en.lproj/Localizable.strings @@ -172,6 +172,8 @@ "Delete" = "Delete"; "Points\nMall" = "Points\nMall"; "Log Out" = "Log Out"; +"Cancel Account" = "Cancel Account"; +"After cancellation, your account will be deactivated and local login data will be cleared. Continue?" = "After cancellation, your account will be deactivated and local login data will be cleared. Continue?"; "Ranking List" = "Ranking List"; "Persona circle" = "Persona circle"; "Clear" = "Clear"; diff --git a/Shared/Localization/zh-Hans.lproj/Localizable.strings b/Shared/Localization/zh-Hans.lproj/Localizable.strings index 1f46e91..aaab542 100644 --- a/Shared/Localization/zh-Hans.lproj/Localizable.strings +++ b/Shared/Localization/zh-Hans.lproj/Localizable.strings @@ -172,6 +172,8 @@ "Delete" = "删除"; "Points\nMall" = "积分\n商城"; "Log Out" = "退出"; +"Cancel Account" = "注销账户"; +"After cancellation, your account will be deactivated and local login data will be cleared. Continue?" = "注销后账号将被停用,并清除本地登录数据,是否继续?"; "Ranking List" = "排行榜"; "Persona circle" = "圈子"; "Clear" = "立刻清空"; diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index e4ca847..1e0d681 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -233,6 +233,8 @@ 04E0B2022F300002002CA5A0 /* KBVoiceRecordManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E0B2012F300002002CA5A0 /* KBVoiceRecordManager.m */; }; 04E161832F10E6470022C23B /* normal_hei_them.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04E161812F10E6470022C23B /* normal_hei_them.zip */; }; 04E161842F10E6470022C23B /* normal_them.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04E161822F10E6470022C23B /* normal_them.zip */; }; + 04E2277D2F516EBD001A8F14 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 04E2277C2F516EBD001A8F14 /* PrivacyInfo.xcprivacy */; }; + 04E2277F2F516ED3001A8F14 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 04E2277E2F516ED3001A8F14 /* PrivacyInfo.xcprivacy */; }; 04F4C0AA2F32274000E8F08C /* KBPayMainVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F4C0A92F32274000E8F08C /* KBPayMainVC.m */; }; 04F4C0AD2F32288600E8F08C /* KBPaySvipVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F4C0AC2F32288600E8F08C /* KBPaySvipVC.m */; }; 04F4C0B02F322EF200E8F08C /* PagingViewTableHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F4C0AF2F322EF200E8F08C /* PagingViewTableHeaderView.m */; }; @@ -719,6 +721,8 @@ 04E0B2012F300002002CA5A0 /* KBVoiceRecordManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBVoiceRecordManager.m; sourceTree = ""; }; 04E161812F10E6470022C23B /* normal_hei_them.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = normal_hei_them.zip; sourceTree = ""; }; 04E161822F10E6470022C23B /* normal_them.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = normal_them.zip; sourceTree = ""; }; + 04E2277C2F516EBD001A8F14 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 04E2277E2F516ED3001A8F14 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 04F4C0A82F32274000E8F08C /* KBPayMainVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBPayMainVC.h; sourceTree = ""; }; 04F4C0A92F32274000E8F08C /* KBPayMainVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBPayMainVC.m; sourceTree = ""; }; 04F4C0AB2F32288600E8F08C /* KBPaySvipVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBPaySvipVC.h; sourceTree = ""; }; @@ -1568,6 +1572,7 @@ 04C6EAB92EAF86530089C901 /* keyBoard */ = { isa = PBXGroup; children = ( + 04E2277C2F516EBD001A8F14 /* PrivacyInfo.xcprivacy */, 04FC95F52EB33B52007BD342 /* keyBoard.entitlements */, 04FC95BF2EB1E3B1007BD342 /* Class */, 04C6EAE32EAF942E0089C901 /* VC */, @@ -1588,6 +1593,7 @@ 04C6EAD72EAF870B0089C901 /* CustomKeyboard */ = { isa = PBXGroup; children = ( + 04E2277E2F516ED3001A8F14 /* PrivacyInfo.xcprivacy */, 0419C9632F2C7630002E86D3 /* VM */, 041007D02ECE010100D203BB /* Resource */, 0477BD942EBAFF4E0055D639 /* Utils */, @@ -2257,6 +2263,7 @@ 0460866B2F18D75500757C95 /* ai_test.m4a in Resources */, 041007D42ECE012500D203BB /* 002.zip in Resources */, 041007D22ECE012000D203BB /* KBSkinIconMap.strings in Resources */, + 04E2277F2F516ED3001A8F14 /* PrivacyInfo.xcprivacy in Resources */, A1B2C3ED2F20000000000001 /* kb_words.txt in Resources */, A1B2C3F12F20000000000002 /* kb_keyboard_layout_config.json in Resources */, 0498BDF52EEC50EE006CC1D5 /* emoji_categories.json in Resources */, @@ -2272,6 +2279,7 @@ 04286A0F2ECDA71B00CE730C /* 001.zip in Resources */, 04E038D82F20BFFB002CA5A0 /* websocket-api.md in Resources */, 0479200B2ED87CEE004E8522 /* permiss_video.mp4 in Resources */, + 04E2277D2F516EBD001A8F14 /* PrivacyInfo.xcprivacy in Resources */, 04C6EABA2EAF86530089C901 /* Assets.xcassets in Resources */, 04A9FE212EB893F10020DB6D /* Localizable.strings in Resources */, 047920072ED86ABC004E8522 /* kb_guide_keyboard.gif in Resources */, diff --git a/keyBoard/Class/AiTalk/VC/KBAiMainVC.h b/keyBoard/Class/AiTalk/VC/KBAiMainVC.h deleted file mode 100644 index d1c4ea7..0000000 --- a/keyBoard/Class/AiTalk/VC/KBAiMainVC.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// KBAiMainVC.h -// keyBoard -// -// Created by Mac on 2026/1/15. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -/// AI 语音陪伴聊天主界面 -@interface KBAiMainVC : BaseViewController - -@end - -NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/AiTalk/VC/KBAiMainVC.m b/keyBoard/Class/AiTalk/VC/KBAiMainVC.m deleted file mode 100644 index 228bff0..0000000 --- a/keyBoard/Class/AiTalk/VC/KBAiMainVC.m +++ /dev/null @@ -1,809 +0,0 @@ -// -// KBAiMainVC.m -// keyBoard -// -// Created by Mac on 2026/1/15. -// - -#import "KBAiMainVC.h" -#import "ConversationOrchestrator.h" -#import "AiVM.h" -#import "AudioSessionManager.h" -#import "DeepgramStreamingManager.h" -#import "KBAICommentView.h" -#import "KBChatTableView.h" -#import "KBAiRecordButton.h" -#import "KBHUD.h" -#import "KBChatLimitPopView.h" -#import "KBPayMainVC.h" -#import "LSTPopView.h" -#import "VoiceChatStreamingManager.h" -#import "KBUserSessionManager.h" -#import - -@interface KBAiMainVC () -@property(nonatomic, weak) LSTPopView *popView; -@property(nonatomic, weak) LSTPopView *limitPopView; - -// UI -@property(nonatomic, strong) KBChatTableView *chatView; -@property(nonatomic, strong) KBAiRecordButton *recordButton; -@property(nonatomic, strong) UILabel *statusLabel; -@property(nonatomic, strong) UILabel *transcriptLabel; -@property(nonatomic, strong) UIButton *commentButton; -@property(nonatomic, strong) KBAICommentView *commentView; -@property(nonatomic, strong) UIView *tabbarBackgroundView; -@property(nonatomic, strong) UIVisualEffectView *blurEffectView; -@property(nonatomic, strong) CAGradientLayer *gradientLayer; -@property(nonatomic, strong) UIImageView *personImageView; - -// 核心模块 -@property(nonatomic, strong) ConversationOrchestrator *orchestrator; -@property(nonatomic, strong) VoiceChatStreamingManager *streamingManager; -@property(nonatomic, strong) DeepgramStreamingManager *deepgramManager; -@property(nonatomic, strong) AiVM *aiVM; -@property(nonatomic, strong) AVAudioPlayer *aiAudioPlayer; -@property(nonatomic, strong) NSMutableData *voiceChatAudioBuffer; - -// 文本跟踪 -@property(nonatomic, strong) NSMutableString *assistantVisibleText; -@property(nonatomic, strong) NSMutableString *deepgramFullText; - -// 日志节流 -@property(nonatomic, assign) NSTimeInterval lastRMSLogTime; - -@end - -@implementation KBAiMainVC - -#pragma mark - Lifecycle - -- (void)viewDidLoad { - [super viewDidLoad]; - - // 让视图延伸到屏幕边缘(包括状态栏和导航栏下方) - self.edgesForExtendedLayout = UIRectEdgeAll; - self.extendedLayoutIncludesOpaqueBars = YES; - - [self setupUI]; - [self setupOrchestrator]; - [self setupStreamingManager]; - [self setupDeepgramManager]; -} - -- (void)viewWillAppear:(BOOL)animated { - [super viewWillAppear:animated]; - // TabBar 背景色由 BaseTabBarController 统一管理,这里不需要设置 -} - -- (void)viewWillDisappear:(BOOL)animated { - [super viewWillDisappear:animated]; - - // 页面消失时停止对话 - [self.orchestrator stop]; - [self.streamingManager disconnect]; - [self.deepgramManager disconnect]; -} - -- (void)viewDidLayoutSubviews { - [super viewDidLayoutSubviews]; - - // 只更新 mask 的 frame(mask 已在 setupUI 中创建) - if (self.blurEffectView.layer.mask) { - self.blurEffectView.layer.mask.frame = self.blurEffectView.bounds; - } -} - -#pragma mark - UI Setup - -- (void)setupUI { - self.view.backgroundColor = [UIColor whiteColor]; - self.title = @"AI 助手"; - - // 安全区域 - UILayoutGuide *safeArea = self.view.safeAreaLayoutGuide; - - // PersonImageView(背景图,最底层) - self.personImageView = - [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"person_icon"]]; - [self.view addSubview:self.personImageView]; - [self.personImageView mas_makeConstraints:^(MASConstraintMaker *make) { - make.left.right.top.bottom.equalTo(self.view); - }]; - - // TabBar 毛玻璃模糊背景(在 personImageView 之上) - self.tabbarBackgroundView = [[UIView alloc] init]; - self.tabbarBackgroundView.translatesAutoresizingMaskIntoConstraints = NO; - self.tabbarBackgroundView.clipsToBounds = YES; - [self.view addSubview:self.tabbarBackgroundView]; - - // 模糊效果 - UIBlurEffect *blurEffect = - [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; - self.blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; - self.blurEffectView.translatesAutoresizingMaskIntoConstraints = NO; - [self.tabbarBackgroundView addSubview:self.blurEffectView]; - - // 为 blurEffectView 创建透明度渐变 - // mask(从底部到中间不透明,从中间到顶部透明) - CAGradientLayer *maskLayer = [CAGradientLayer layer]; - maskLayer.startPoint = CGPointMake(0.5, 1); // 底部 - maskLayer.endPoint = CGPointMake(0.5, 0); // 顶部 - // 底部到中间保持不透明,从中间到顶部过渡透明 - maskLayer.colors = @[ - (__bridge id)[UIColor whiteColor].CGColor, // 底部:完全不透明 - (__bridge id)[UIColor whiteColor].CGColor, // 中间:完全不透明 - (__bridge id)[UIColor clearColor].CGColor // 顶部:完全透明 - ]; - maskLayer.locations = @[ @(0.0), @(0.5), @(1.0) ]; - self.blurEffectView.layer.mask = maskLayer; - - // 状态标签 - self.statusLabel = [[UILabel alloc] init]; - self.statusLabel.text = @"按住按钮开始对话"; - self.statusLabel.font = [UIFont systemFontOfSize:14]; - self.statusLabel.textColor = [UIColor secondaryLabelColor]; - self.statusLabel.textAlignment = NSTextAlignmentCenter; - self.statusLabel.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:self.statusLabel]; - - // 转写文本标签 - self.transcriptLabel = [[UILabel alloc] init]; - self.transcriptLabel.text = @""; - self.transcriptLabel.font = [UIFont systemFontOfSize:16]; - self.transcriptLabel.textColor = [UIColor labelColor]; - self.transcriptLabel.numberOfLines = 0; - self.transcriptLabel.textAlignment = NSTextAlignmentRight; - self.transcriptLabel.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:self.transcriptLabel]; - - // 聊天视图 - self.chatView = [[KBChatTableView alloc] init]; - self.chatView.backgroundColor = [UIColor clearColor]; - self.chatView.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:self.chatView]; - - // 录音按钮 - self.recordButton = [[KBAiRecordButton alloc] init]; - self.recordButton.delegate = self; - self.recordButton.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:self.recordButton]; - - // 评论按钮(聊天视图右侧居中) - self.commentButton = [UIButton buttonWithType:UIButtonTypeCustom]; - [self.commentButton setImage:[UIImage systemImageNamed:@"bubble.right.fill"] - forState:UIControlStateNormal]; - self.commentButton.tintColor = [UIColor whiteColor]; - self.commentButton.backgroundColor = [UIColor systemBlueColor]; - self.commentButton.layer.cornerRadius = 25; - self.commentButton.layer.shadowColor = [UIColor blackColor].CGColor; - self.commentButton.layer.shadowOffset = CGSizeMake(0, 2); - self.commentButton.layer.shadowOpacity = 0.3; - self.commentButton.layer.shadowRadius = 4; - self.commentButton.translatesAutoresizingMaskIntoConstraints = NO; - [self.commentButton addTarget:self - action:@selector(showComment) - forControlEvents:UIControlEventTouchUpInside]; - [self.view addSubview:self.commentButton]; - - // 布局约束 - 使用 Masonry - [self.tabbarBackgroundView mas_makeConstraints:^(MASConstraintMaker *make) { - make.left.right.bottom.equalTo(self.view); - make.height.mas_equalTo(KBFit(238)); - }]; - - [self.blurEffectView mas_makeConstraints:^(MASConstraintMaker *make) { - make.edges.equalTo(self.tabbarBackgroundView); - }]; - - [self.statusLabel mas_makeConstraints:^(MASConstraintMaker *make) { - make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop).offset(8); - make.left.equalTo(self.view).offset(16); - make.right.equalTo(self.view).offset(-16); - // 设置固定高度,避免内容变化导致布局跳动 - make.height.mas_equalTo(20); // 单行文本高度 - }]; - - [self.transcriptLabel mas_makeConstraints:^(MASConstraintMaker *make) { - make.top.equalTo(self.statusLabel.mas_bottom).offset(8); - make.left.equalTo(self.view).offset(16); - make.right.equalTo(self.view).offset(-16); - // 设置固定高度,避免内容变化导致布局跳动 - make.height.mas_equalTo(60); // 根据实际需要调整高度 - }]; - // 设置内容压缩阻力,避免被压缩 - [self.transcriptLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow - forAxis:UILayoutConstraintAxisVertical]; - - [self.chatView mas_makeConstraints:^(MASConstraintMaker *make) { - make.left.right.equalTo(self.view); - make.bottom.equalTo(self.tabbarBackgroundView.mas_top).offset(-8); - make.top.equalTo(self.transcriptLabel.mas_bottom).offset(8); - // 设置最小高度,避免被压缩为 0 - make.height.greaterThanOrEqualTo(@100).priority(MASLayoutPriorityDefaultHigh); - }]; - // chatView 应该尽可能占据空间 - [self.chatView setContentCompressionResistancePriority:UILayoutPriorityRequired - forAxis:UILayoutConstraintAxisVertical]; - - [self.recordButton mas_makeConstraints:^(MASConstraintMaker *make) { - make.left.equalTo(self.view.mas_safeAreaLayoutGuideLeft).offset(20); - make.right.equalTo(self.view.mas_safeAreaLayoutGuideRight).offset(-20); - make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-16); - make.height.mas_equalTo(50); - }]; - - [self.commentButton mas_makeConstraints:^(MASConstraintMaker *make) { - make.right.equalTo(self.view.mas_safeAreaLayoutGuideRight).offset(-16); - make.centerY.equalTo(self.view); - make.width.height.mas_equalTo(50); - }]; -} - -#pragma mark - Orchestrator Setup - -- (void)setupOrchestrator { - self.orchestrator = [[ConversationOrchestrator alloc] init]; - - // 配置服务器地址 - // 1. ASR 语音识别服务(WebSocket) - self.orchestrator.asrServerURL = @"ws://192.168.2.21:7529/ws/asr"; - - // 2. LLM 大语言模型服务(HTTP Stream) - self.orchestrator.llmServerURL = @"http://192.168.2.21:7529/api/chat/stream"; - - // 3. TTS 语音合成服务(HTTP) - self.orchestrator.ttsServerURL = @"http://192.168.2.21:7529/api/tts/stream"; - - __weak typeof(self) weakSelf = self; - - // 状态变化回调 - self.orchestrator.onStateChange = ^(ConversationState state) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) - return; - - [strongSelf updateStatusForState:state]; - }; - - // 实时识别文本回调 - self.orchestrator.onPartialText = ^(NSString *text) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) - return; - - strongSelf.statusLabel.text = text.length > 0 ? text : @"正在识别..."; - }; - - // 用户最终文本回调 - self.orchestrator.onUserFinalText = ^(NSString *text) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) - return; - - if (text.length > 0) { - [strongSelf.chatView addUserMessage:text]; - } - }; - - // AI 可见文本回调(打字机效果) - self.orchestrator.onAssistantVisibleText = ^(NSString *text) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) - return; - - [strongSelf.chatView updateLastAssistantMessage:text]; - }; - - // AI 完整回复回调 - self.orchestrator.onAssistantFullText = ^(NSString *text) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) - return; - - [strongSelf.chatView updateLastAssistantMessage:text]; - [strongSelf.chatView markLastAssistantMessageComplete]; - }; - - // 音量更新回调 - self.orchestrator.onVolumeUpdate = ^(float rms) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) - return; - - [strongSelf.recordButton updateVolumeRMS:rms]; - }; - - // AI 开始说话 - self.orchestrator.onSpeakingStart = ^{ - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) - return; - - // 添加空的 AI 消息占位 - [strongSelf.chatView addAssistantMessage:@"" audioDuration:0 audioData:nil]; - }; - - // AI 说话结束 - self.orchestrator.onSpeakingEnd = ^{ - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) - return; - - [strongSelf.chatView markLastAssistantMessageComplete]; - }; - - // 错误回调 - self.orchestrator.onError = ^(NSError *error) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) - return; - - [strongSelf showError:error]; - }; -} - -#pragma mark - Streaming Manager - -- (void)setupStreamingManager { - self.streamingManager = [[VoiceChatStreamingManager alloc] init]; - self.streamingManager.delegate = self; - self.streamingManager.serverURL = @"ws://192.168.2.21:7529/api/ws/chat"; - self.assistantVisibleText = [[NSMutableString alloc] init]; - self.voiceChatAudioBuffer = [[NSMutableData alloc] init]; - self.lastRMSLogTime = 0; -} - -#pragma mark - Deepgram Manager - -- (void)setupDeepgramManager { - self.deepgramManager = [[DeepgramStreamingManager alloc] init]; - self.deepgramManager.delegate = self; - self.deepgramManager.serverURL = @"wss://api.deepgram.com/v1/listen"; - self.deepgramManager.apiKey = @"9c792eb63a65d644cbc95785155754cd1e84f8cf"; - self.deepgramManager.language = @"en"; - self.deepgramManager.model = @"nova-3"; - self.deepgramManager.punctuate = YES; - self.deepgramManager.smartFormat = YES; - self.deepgramManager.interimResults = YES; - self.deepgramManager.encoding = @"linear16"; - self.deepgramManager.sampleRate = 16000.0; - self.deepgramManager.channels = 1; - [self.deepgramManager prepareConnection]; - - self.deepgramFullText = [[NSMutableString alloc] init]; - self.aiVM = [[AiVM alloc] init]; -} - -#pragma mark - 事件 -- (void)showComment { - CGFloat customViewHeight = KB_SCREEN_HEIGHT * (0.8); - KBAICommentView *customView = [[KBAICommentView alloc] - initWithFrame:CGRectMake(0, 0, KB_SCREEN_WIDTH, customViewHeight)]; - LSTPopView *popView = - [LSTPopView initWithCustomView:customView - parentView:nil - popStyle:LSTPopStyleSmoothFromBottom - dismissStyle:LSTDismissStyleSmoothToBottom]; - self.popView = popView; - popView.priority = 1000; - popView.isAvoidKeyboard = false; - popView.hemStyle = LSTHemStyleBottom; - popView.dragStyle = LSTDragStyleY_Positive; - popView.dragDistance = customViewHeight * 0.5; - popView.sweepStyle = LSTSweepStyleY_Positive; - popView.swipeVelocity = 1600; - popView.sweepDismissStyle = LSTSweepDismissStyleSmooth; - - [popView pop]; -} - -- (void)showCommentDirectly { - if (self.commentView.superview) { - [self.view bringSubviewToFront:self.commentView]; - return; - } - - CGFloat customViewHeight = KB_SCREEN_HEIGHT * (0.8); - KBAICommentView *customView = - [[KBAICommentView alloc] initWithFrame:CGRectZero]; - customView.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:customView]; - [NSLayoutConstraint activateConstraints:@[ - [customView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], - [customView.trailingAnchor - constraintEqualToAnchor:self.view.trailingAnchor], - [customView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor], - [customView.heightAnchor constraintEqualToConstant:customViewHeight], - ]]; - self.commentView = customView; -} - -#pragma mark - 次数用尽弹窗 - -- (void)showChatLimitPopWithMessage:(NSString *)message { - if (self.limitPopView) { - [self.limitPopView dismiss]; - } - - CGFloat width = 252.0; - CGFloat height = 252.0 + 18.0 + 53.0 + 18.0 + 28.0; - KBChatLimitPopView *content = - [[KBChatLimitPopView alloc] initWithFrame:CGRectMake(0, 0, width, height)]; - content.message = message; - content.delegate = self; - - LSTPopView *popView = - [LSTPopView initWithCustomView:content - parentView:nil - popStyle:LSTPopStyleFade - dismissStyle:LSTDismissStyleFade]; - popView.bgColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; - popView.hemStyle = LSTHemStyleCenter; - popView.isClickBgDismiss = YES; - popView.isAvoidKeyboard = NO; - self.limitPopView = popView; - [popView pop]; -} - -#pragma mark - KBChatLimitPopViewDelegate - -- (void)chatLimitPopViewDidTapCancel:(KBChatLimitPopView *)view { - [self.limitPopView dismiss]; -} - -- (void)chatLimitPopViewDidTapRecharge:(KBChatLimitPopView *)view { - [self.limitPopView dismiss]; - if (![KBUserSessionManager shared].isLoggedIn) { - [[KBUserSessionManager shared] goLoginVC]; - return; - } - KBPayMainVC *vc = [[KBPayMainVC alloc] init]; - vc.initialSelectedIndex = 1; // SVIP - [KB_CURRENT_NAV pushViewController:vc animated:true]; -} - -#pragma mark - UI Updates - -- (void)updateStatusForState:(ConversationState)state { - switch (state) { - case ConversationStateIdle: - self.statusLabel.text = @"按住按钮开始对话"; - self.recordButton.state = KBAiRecordButtonStateNormal; - break; - - case ConversationStateListening: - self.statusLabel.text = @"正在聆听..."; - self.recordButton.state = KBAiRecordButtonStateRecording; - break; - - case ConversationStateRecognizing: - self.statusLabel.text = @"正在识别..."; - self.recordButton.state = KBAiRecordButtonStateNormal; - break; - - case ConversationStateThinking: - self.statusLabel.text = @"AI 正在思考..."; - self.recordButton.state = KBAiRecordButtonStateNormal; - break; - - case ConversationStateSpeaking: - self.statusLabel.text = @"AI 正在回复..."; - self.recordButton.state = KBAiRecordButtonStateNormal; - break; - } -} - -- (void)showError:(NSError *)error { - UIAlertController *alert = - [UIAlertController alertControllerWithTitle:@"错误" - message:error.localizedDescription - preferredStyle:UIAlertControllerStyleAlert]; - [alert addAction:[UIAlertAction actionWithTitle:@"确定" - style:UIAlertActionStyleDefault - handler:nil]]; - [self presentViewController:alert animated:YES completion:nil]; -} - -#pragma mark - KBAiRecordButtonDelegate - -- (void)recordButtonDidBeginPress:(KBAiRecordButton *)button { - NSLog(@"[KBAiMainVC] Record button began press"); - - // 停止正在播放的音频 - [self.chatView stopPlayingAudio]; - - NSString *token = [[KBUserSessionManager shared] accessToken] ?: @""; - if (token.length == 0) { - [[KBUserSessionManager shared] goLoginVC]; - return; - } - - self.statusLabel.text = @"正在连接..."; - self.recordButton.state = KBAiRecordButtonStateRecording; - [self.deepgramFullText setString:@""]; - self.transcriptLabel.text = @""; - [self.deepgramManager start]; -} - -- (void)recordButtonDidEndPress:(KBAiRecordButton *)button { - NSLog(@"[KBAiMainVC] Record button end press"); - [self.deepgramManager stopAndFinalize]; -} - -- (void)recordButtonDidCancelPress:(KBAiRecordButton *)button { - NSLog(@"[KBAiMainVC] Record button cancel press"); - [self.deepgramManager cancel]; -} - -#pragma mark - VoiceChatStreamingManagerDelegate - -- (void)voiceChatStreamingManagerDidConnect { - self.statusLabel.text = @"已连接,准备中..."; -} - -- (void)voiceChatStreamingManagerDidDisconnect:(NSError *_Nullable)error { - self.recordButton.state = KBAiRecordButtonStateNormal; - if (error) { - [self showError:error]; - } -} - -- (void)voiceChatStreamingManagerDidStartSession:(NSString *)sessionId { - self.statusLabel.text = @"正在聆听..."; - self.recordButton.state = KBAiRecordButtonStateRecording; -} - -- (void)voiceChatStreamingManagerDidStartTurn:(NSInteger)turnIndex { - self.statusLabel.text = @"正在聆听..."; - self.recordButton.state = KBAiRecordButtonStateRecording; -} - -- (void)voiceChatStreamingManagerDidReceiveEagerEndOfTurnWithTranscript:(NSString *)text - confidence:(double)confidence { - self.statusLabel.text = @"准备响应..."; -} - -- (void)voiceChatStreamingManagerDidResumeTurn { - self.statusLabel.text = @"正在聆听..."; -} - -- (void)voiceChatStreamingManagerDidUpdateRMS:(float)rms { - [self.recordButton updateVolumeRMS:rms]; - NSTimeInterval now = [[NSDate date] timeIntervalSince1970]; - if (now - self.lastRMSLogTime >= 1.0) { - self.lastRMSLogTime = now; - NSLog(@"[KBAiMainVC] RMS: %.3f", rms); - } -} - -- (void)voiceChatStreamingManagerDidReceiveInterimTranscript:(NSString *)text { - self.statusLabel.text = @"正在识别..."; - if (text.length > 0) { - self.transcriptLabel.text = text; - } -} - -- (void)voiceChatStreamingManagerDidReceiveFinalTranscript:(NSString *)text { - if (text.length > 0) { - self.transcriptLabel.text = @""; - [self.chatView addUserMessage:text]; - } -} - -- (void)voiceChatStreamingManagerDidReceiveLLMStart { - self.statusLabel.text = @"AI 正在思考..."; - [self.assistantVisibleText setString:@""]; - [self.chatView addAssistantMessage:@"" audioDuration:0 audioData:nil]; - [self.voiceChatAudioBuffer setLength:0]; -} - -- (void)voiceChatStreamingManagerDidReceiveLLMToken:(NSString *)token { - if (token.length == 0) { - return; - } - - [self.assistantVisibleText appendString:token]; - [self.chatView updateLastAssistantMessage:self.assistantVisibleText]; -} - -- (void)voiceChatStreamingManagerDidReceiveAudioChunk:(NSData *)audioData { - if (audioData.length == 0) { - return; - } - [self.voiceChatAudioBuffer appendData:audioData]; -} - -- (void)voiceChatStreamingManagerDidCompleteWithTranscript:(NSString *)transcript - aiResponse:(NSString *)aiResponse { - NSString *finalText = aiResponse.length > 0 ? aiResponse : self.assistantVisibleText; - if (aiResponse.length > 0) { - [self.assistantVisibleText setString:aiResponse]; - } - - // 计算音频时长 - NSTimeInterval duration = 0; - if (self.voiceChatAudioBuffer.length > 0) { - NSError *error = nil; - AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithData:self.voiceChatAudioBuffer - error:&error]; - if (!error && player) { - duration = player.duration; - } - } - - if (finalText.length > 0) { - [self.chatView updateLastAssistantMessage:finalText]; - [self.chatView markLastAssistantMessageComplete]; - } else if (transcript.length > 0) { - [self.chatView addAssistantMessage:transcript - audioDuration:duration - audioData:self.voiceChatAudioBuffer.length > 0 ? self.voiceChatAudioBuffer : nil]; - } - - if (self.voiceChatAudioBuffer.length > 0) { - [self playAiAudioData:self.voiceChatAudioBuffer]; - [self.voiceChatAudioBuffer setLength:0]; - } - - self.recordButton.state = KBAiRecordButtonStateNormal; - self.statusLabel.text = @"完成"; -} - -- (void)voiceChatStreamingManagerDidFail:(NSError *)error { - self.recordButton.state = KBAiRecordButtonStateNormal; - [self showError:error]; -} - -#pragma mark - DeepgramStreamingManagerDelegate - -- (void)deepgramStreamingManagerDidConnect { - self.statusLabel.text = @"已连接,准备中..."; -} - -- (void)deepgramStreamingManagerDidDisconnect:(NSError *_Nullable)error { - self.recordButton.state = KBAiRecordButtonStateNormal; - if (error) { - [self showError:error]; - } -} - -- (void)deepgramStreamingManagerDidUpdateRMS:(float)rms { - [self.recordButton updateVolumeRMS:rms]; - NSTimeInterval now = [[NSDate date] timeIntervalSince1970]; - if (now - self.lastRMSLogTime >= 1.0) { - self.lastRMSLogTime = now; - NSLog(@"[KBAiMainVC] RMS: %.3f", rms); - } -} - -- (void)deepgramStreamingManagerDidReceiveInterimTranscript:(NSString *)text { - self.statusLabel.text = @"正在识别..."; - NSString *displayText = text ?: @""; - if (self.deepgramFullText.length > 0 && displayText.length > 0) { - displayText = - [NSString stringWithFormat:@"%@ %@", self.deepgramFullText, displayText]; - } else if (self.deepgramFullText.length > 0) { - displayText = [self.deepgramFullText copy]; - } - self.transcriptLabel.text = displayText; -} - -- (void)deepgramStreamingManagerDidReceiveFinalTranscript:(NSString *)text { - if (text.length > 0) { - if (self.deepgramFullText.length > 0) { - [self.deepgramFullText appendString:@" "]; - } - [self.deepgramFullText appendString:text]; - } - self.transcriptLabel.text = self.deepgramFullText; - self.statusLabel.text = @"识别完成"; - self.recordButton.state = KBAiRecordButtonStateNormal; - - NSString *finalText = [self.deepgramFullText copy]; - if (finalText.length == 0) { - return; - } - - // 添加用户消息 - [self.chatView addUserMessage:finalText]; - - __weak typeof(self) weakSelf = self; - [KBHUD showWithStatus:@"AI 思考中..."]; - - // 请求 chat/message 接口 - [self.aiVM requestChatMessageWithContent:finalText - companionId:0 - completion:^(KBAiMessageResponse *_Nullable response, - NSError *_Nullable error) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - [KBHUD dismiss]; - - if (error) { - [KBHUD showError:error.localizedDescription ?: @"请求失败"]; - return; - } - - if (response.code == 50030) { - NSString *message = response.message ?: @""; - [strongSelf showChatLimitPopWithMessage:message]; - return; - } - - if (!response || !response.data) { - NSString *message = response.message ?: @"AI 回复为空"; - [KBHUD showError:message]; - return; - } - - // 获取 AI 回复文本 - NSString *aiResponse = response.data.aiResponse ?: response.data.content ?: response.data.text ?: response.data.message ?: @""; - - if (aiResponse.length == 0) { - [KBHUD showError:@"AI 回复为空"]; - return; - } - - // 获取 audioId - NSString *audioId = response.data.audioId; - - // 添加 AI 消息(带 audioId) - [strongSelf.chatView addAssistantMessage:aiResponse - audioId:audioId]; - }); - }]; -} - -- (void)deepgramStreamingManagerDidFail:(NSError *)error { - self.recordButton.state = KBAiRecordButtonStateNormal; - [self showError:error]; -} - -#pragma mark - Audio Playback - -- (void)playAiAudioData:(NSData *)audioData { - if (audioData.length == 0) { - return; - } - - NSError *sessionError = nil; - AudioSessionManager *audioSession = [AudioSessionManager sharedManager]; - if (![audioSession configureForPlayback:&sessionError]) { - NSLog(@"[KBAiMainVC] Configure playback failed: %@", - sessionError.localizedDescription ?: @""); - } - if (![audioSession activateSession:&sessionError]) { - NSLog(@"[KBAiMainVC] Activate playback session failed: %@", - sessionError.localizedDescription ?: @""); - } - - NSError *error = nil; - self.aiAudioPlayer = [[AVAudioPlayer alloc] initWithData:audioData - error:&error]; - if (error || !self.aiAudioPlayer) { - NSLog(@"[KBAiMainVC] Audio player init failed: %@", - error.localizedDescription ?: @""); - return; - } - self.aiAudioPlayer.delegate = self; - [self.aiAudioPlayer prepareToPlay]; - [self.aiAudioPlayer play]; -} - -#pragma mark - AVAudioPlayerDelegate - -- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player - successfully:(BOOL)flag { - [[AudioSessionManager sharedManager] deactivateSession]; -} - -@end diff --git a/keyBoard/Class/Me/VC/KBPersonInfoVC.m b/keyBoard/Class/Me/VC/KBPersonInfoVC.m index 003aaba..98aa6c7 100644 --- a/keyBoard/Class/Me/VC/KBPersonInfoVC.m +++ b/keyBoard/Class/Me/VC/KBPersonInfoVC.m @@ -14,6 +14,7 @@ #import "KBChangeNicknamePopView.h" #import "KBGenderPickerPopView.h" #import "KBMyVM.h" +#import "KBAlert.h" @interface KBPersonInfoVC () // 列表 @@ -25,8 +26,10 @@ @property (nonatomic, strong) UIButton *editBadge; // 头像右下角的小铅笔 @property (nonatomic, strong) UILabel *modifyLabel; // “Modify” 文案 -// 底部退出按钮(固定在屏幕底部) +// 底部退出登录按钮 @property (nonatomic, strong) UIButton *logoutBtn; +// 底部注销账户按钮 +@property (nonatomic, strong) UIButton *cancelBtn; // 数据 @property (nonatomic, copy) NSArray *items; // {title,value,arrow,copy} @@ -64,9 +67,18 @@ // 表头 self.tableView.tableHeaderView = self.headerView; - // 底部退出按钮固定在屏幕底部 + // 底部退出登录按钮 [self.view addSubview:self.logoutBtn]; [self.logoutBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.view).offset(16); + make.right.equalTo(self.view).offset(-16); + make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-(12 + 56 + 10)); + make.height.mas_equalTo(56); + }]; + + // 底部注销账户按钮 + [self.view addSubview:self.cancelBtn]; + [self.cancelBtn mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.view).offset(16); make.right.equalTo(self.view).offset(-16); make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-12); @@ -75,7 +87,7 @@ // 列表底部腾出空间,避免被按钮挡住 UIEdgeInsets inset = self.tableView.contentInset; - inset.bottom = 56 + 24; // 按钮高度 + 额外间距 + inset.bottom = 56 + 10 + 56 + 24; // 两个按钮高度 + 间距 + 额外间距 self.tableView.contentInset = inset; self.viewModel = [[KBMyVM alloc] init]; __weak typeof(self) weakSelf = self; @@ -275,6 +287,25 @@ [self.myVM logout]; } +- (void)onTapCancelAccount { + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_person_cancel_account_btn" + pageId:@"person_info" + elementId:@"cancel_account_btn" + extra:nil + completion:nil]; + KBWeakSelf; + [KBAlert confirmTitle:KBLocalized(@"Cancel Account") + message:KBLocalized(@"After cancellation, your account will be deactivated and local login data will be cleared. Continue?") + ok:KBLocalized(@"Confirm") + cancel:KBLocalized(@"Cancel") + okColor:[UIColor colorWithHex:0xFF0000] + cancelColor:nil + completion:^(BOOL ok) { + if (!ok) { return; } + [weakSelf.myVM cancelAccountWithCompletion:nil]; + }]; +} + #pragma mark - Lazy UI(懒加载) - (UITableView *)tableView { @@ -378,6 +409,19 @@ return _logoutBtn; } +- (UIButton *)cancelBtn { + if (!_cancelBtn) { + _cancelBtn = [UIButton buttonWithType:UIButtonTypeSystem]; + [_cancelBtn setTitle:KBLocalized(@"Cancel Account") forState:UIControlStateNormal]; + [_cancelBtn setTitleColor:[UIColor colorWithHex:0xFF0000] forState:UIControlStateNormal]; + _cancelBtn.titleLabel.font = [KBFont medium:16]; + _cancelBtn.backgroundColor = UIColor.whiteColor; + _cancelBtn.layer.cornerRadius = 12; _cancelBtn.layer.masksToBounds = YES; + [_cancelBtn addTarget:self action:@selector(onTapCancelAccount) forControlEvents:UIControlEventTouchUpInside]; + } + return _cancelBtn; +} + #pragma mark - Image Picker - (void)presentImagePicker { diff --git a/keyBoard/Class/Me/VM/KBMyVM.h b/keyBoard/Class/Me/VM/KBMyVM.h index 4f06002..c4ee0b8 100644 --- a/keyBoard/Class/Me/VM/KBMyVM.h +++ b/keyBoard/Class/Me/VM/KBMyVM.h @@ -31,6 +31,7 @@ typedef void(^KBSubmitFeedbackCompletion)(BOOL success, NSError *_Nullable error typedef void(^KBMyPurchaseRecordCompletion)(NSArray *_Nullable records, NSError *_Nullable error); typedef void(^KBMyInviteCodeCompletion)(KBInviteCodeModel *_Nullable inviteCode, NSError *_Nullable error); typedef void(^KBMyCustomerMailCompletion)(NSString *_Nullable customerMail, NSError *_Nullable error); +typedef void(^KBCancelAccountCompletion)(BOOL success, NSError *_Nullable error); @interface KBMyVM : NSObject @@ -77,6 +78,9 @@ typedef void(^KBMyCustomerMailCompletion)(NSString *_Nullable customerMail, NSEr /// 退出登录 - (void)logout; + +/// 注销账号(/user/cancelAccount) +- (void)cancelAccountWithCompletion:(KBCancelAccountCompletion)completion; @end NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/Me/VM/KBMyVM.m b/keyBoard/Class/Me/VM/KBMyVM.m index 68d56a6..d2ba7a0 100644 --- a/keyBoard/Class/Me/VM/KBMyVM.m +++ b/keyBoard/Class/Me/VM/KBMyVM.m @@ -457,18 +457,67 @@ NSString * const KBUserCharacterDeletedNotification = @"KBUserCharacterDeletedNo NSString *message = jsonOrData[KBMessage] ?: KBLocalized(@"Success"); [KBHUD showSuccess:message]; - - // 本地会话退出 - [[KBUserSessionManager shared] logout]; - - // 回到登录 / 主界面 - dispatch_async(dispatch_get_main_queue(), ^{ - id appDelegate = UIApplication.sharedApplication.delegate; - if ([appDelegate respondsToSelector:@selector(toMainTabbarVC)]) { - AppDelegate *delegate = (AppDelegate *)appDelegate; - [delegate toMainTabbarVC]; - } - }); + [self kb_clearLoginInfoAndRouteHome]; }]; } + +- (void)cancelAccountWithCompletion:(KBCancelAccountCompletion)completion { + [KBHUD show]; + [[KBNetworkManager shared] POST:API_USER_CANCEL_ACCOUNT + jsonBody:nil + headers:nil + autoShowBusinessError:NO + completion:^(NSDictionary *jsonOrData, NSURLResponse * _Nullable response, NSError * _Nullable error) { + [KBHUD dismiss]; + + if (error) { + NSString *msg = KBBizMessageFromJSONObject(jsonOrData) ?: error.localizedDescription ?: KBLocalized(@"Network error"); + [KBHUD showInfo:msg]; + if (completion) { + completion(NO, error); + } + return; + } + + NSString *message = jsonOrData[KBMessage] ?: KBLocalized(@"Success"); + [KBHUD showSuccess:message]; + + [self kb_clearLoginInfoAndRouteHome]; + if (completion) { + completion(YES, nil); + } + }]; +} + +#pragma mark - Private + +/// 清理本地登录态及关联缓存,并回到首页。 +- (void)kb_clearLoginInfoAndRouteHome { + [[KBUserSessionManager shared] logout]; + + NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:AppGroup]; + NSArray *sharedKeys = @[ + AppGroup_MyKbJson, + AppGroup_UserAvatarURL, + AppGroup_SubscriptionPrefillPayload, + AppGroup_ChatUpdatedCompanionId, + @"AppGroup_SelectedPersona" + ]; + for (NSString *key in sharedKeys) { + [sharedDefaults removeObjectForKey:key]; + } + [sharedDefaults synchronize]; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [defaults removeObjectForKey:@"KBAISelectedPersonaId"]; + [defaults synchronize]; + + dispatch_async(dispatch_get_main_queue(), ^{ + id appDelegate = UIApplication.sharedApplication.delegate; + if ([appDelegate respondsToSelector:@selector(toMainTabbarVC)]) { + AppDelegate *delegate = (AppDelegate *)appDelegate; + [delegate toMainTabbarVC]; + } + }); +} @end diff --git a/keyBoard/Info.plist b/keyBoard/Info.plist index ab9455d..2aeed88 100644 --- a/keyBoard/Info.plist +++ b/keyBoard/Info.plist @@ -24,10 +24,6 @@ NSAllowsArbitraryLoads - UIBackgroundModes - - audio - UIDesignRequiresCompatibility diff --git a/keyBoard/PrivacyInfo.xcprivacy b/keyBoard/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..da12d3c --- /dev/null +++ b/keyBoard/PrivacyInfo.xcprivacy @@ -0,0 +1,76 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeEmailAddress + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeUserID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherUserContent + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryActiveKeyboards + NSPrivacyAccessedAPITypeReasons + + 3EC4.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + +