diff --git a/CustomKeyboard/View/Function/KBFunctionTagListView.h b/CustomKeyboard/View/Function/KBFunctionTagListView.h new file mode 100644 index 0000000..bdfec03 --- /dev/null +++ b/CustomKeyboard/View/Function/KBFunctionTagListView.h @@ -0,0 +1,27 @@ +// +// KBFunctionTagListView.h +// 封装标签列表(UICollectionView) +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class KBFunctionTagListView; + +@protocol KBFunctionTagListViewDelegate +@optional +- (void)tagListView:(KBFunctionTagListView *)view didSelectIndex:(NSInteger)index title:(NSString *)title; +@end + +@interface KBFunctionTagListView : UIView + +@property (nonatomic, weak, nullable) id delegate; +@property (nonatomic, strong, readonly) UICollectionView *collectionView; + +- (void)setItems:(NSArray *)items; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/CustomKeyboard/View/Function/KBFunctionTagListView.m b/CustomKeyboard/View/Function/KBFunctionTagListView.m new file mode 100644 index 0000000..3fe2297 --- /dev/null +++ b/CustomKeyboard/View/Function/KBFunctionTagListView.m @@ -0,0 +1,68 @@ +// +// KBFunctionTagListView.m +// + +#import "KBFunctionTagListView.h" +#import "KBFunctionTagCell.h" + +static NSString * const kKBFunctionTagCellId2 = @"KBFunctionTagCellId2"; + +@interface KBFunctionTagListView () +@property (nonatomic, strong) UICollectionView *collectionViewInternal; +@property (nonatomic, copy) NSArray *items; +@end + +@implementation KBFunctionTagListView + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; + _collectionViewInternal = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; + _collectionViewInternal.backgroundColor = [UIColor clearColor]; + _collectionViewInternal.dataSource = self; + _collectionViewInternal.delegate = self; + [_collectionViewInternal registerClass:[KBFunctionTagCell class] forCellWithReuseIdentifier:kKBFunctionTagCellId2]; + [self addSubview:_collectionViewInternal]; + _collectionViewInternal.translatesAutoresizingMaskIntoConstraints = NO; + [NSLayoutConstraint activateConstraints:@[ + [_collectionViewInternal.topAnchor constraintEqualToAnchor:self.topAnchor], + [_collectionViewInternal.bottomAnchor constraintEqualToAnchor:self.bottomAnchor], + [_collectionViewInternal.leadingAnchor constraintEqualToAnchor:self.leadingAnchor], + [_collectionViewInternal.trailingAnchor constraintEqualToAnchor:self.trailingAnchor], + ]]; + _items = @[]; + } + return self; +} + +- (void)setItems:(NSArray *)items { _items = [items copy]; [self.collectionViewInternal reloadData]; } + +- (UICollectionView *)collectionView { return self.collectionViewInternal; } + +#pragma mark - UICollectionView + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return self.items.count; } + +- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + KBFunctionTagCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kKBFunctionTagCellId2 forIndexPath:indexPath]; + cell.titleLabel.text = (indexPath.item < self.items.count) ? self.items[indexPath.item] : @""; + return cell; +} + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { + CGFloat totalW = collectionView.bounds.size.width; CGFloat space = 10.0; NSInteger columns = 3; + CGFloat width = floor((totalW - space * (columns - 1)) / columns); + return CGSizeMake(width, 48); +} + +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { return 10.0; } +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return 12.0; } + +- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + if ([self.delegate respondsToSelector:@selector(tagListView:didSelectIndex:title:)]) { + NSString *title = (indexPath.item < self.items.count) ? self.items[indexPath.item] : @""; + [self.delegate tagListView:self didSelectIndex:indexPath.item title:title]; + } +} + +@end diff --git a/CustomKeyboard/View/Function/KBStreamOverlayView.h b/CustomKeyboard/View/Function/KBStreamOverlayView.h new file mode 100644 index 0000000..0e8d0f4 --- /dev/null +++ b/CustomKeyboard/View/Function/KBStreamOverlayView.h @@ -0,0 +1,28 @@ +// +// KBStreamOverlayView.h +// 自带关闭按钮的流式展示层,内部持有 KBStreamTextView。 +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class KBStreamTextView, KBStreamOverlayView; + +@protocol KBStreamOverlayViewDelegate +@optional +- (void)streamOverlayDidTapClose:(KBStreamOverlayView *)overlay; +@end + +@interface KBStreamOverlayView : UIView + +@property (nonatomic, strong, readonly) KBStreamTextView *textView; +@property (nonatomic, weak, nullable) id delegate; + +- (void)appendChunk:(NSString *)text; +- (void)finish; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/CustomKeyboard/View/Function/KBStreamOverlayView.m b/CustomKeyboard/View/Function/KBStreamOverlayView.m new file mode 100644 index 0000000..ede0a02 --- /dev/null +++ b/CustomKeyboard/View/Function/KBStreamOverlayView.m @@ -0,0 +1,80 @@ +// +// KBStreamOverlayView.m +// + +#import "KBStreamOverlayView.h" +#import "KBStreamTextView.h" +#import "Masonry.h" + +@interface KBStreamOverlayView () +@property (nonatomic, strong) KBStreamTextView *textViewInternal; +@property (nonatomic, strong) UIButton *closeButton; +@end + +@implementation KBStreamOverlayView + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + self.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.92]; + self.layer.cornerRadius = 12.0; self.layer.masksToBounds = YES; + + [self addSubview:self.textViewInternal]; + [self addSubview:self.closeButton]; + + [self.textViewInternal mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.mas_left).offset(0); + make.right.equalTo(self.mas_right).offset(0); + make.bottom.equalTo(self.mas_bottom).offset(0); + make.top.equalTo(self.mas_top).offset(0); + }]; + + [self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.equalTo(self.mas_top).offset(8); + make.right.equalTo(self.mas_right).offset(-8); + make.height.mas_equalTo(28); + make.width.mas_greaterThanOrEqualTo(56); + }]; + } + return self; +} + +- (KBStreamTextView *)textViewInternal { + if (!_textViewInternal) { + _textViewInternal = [[KBStreamTextView alloc] init]; + } + return _textViewInternal; +} + +- (UIButton *)closeButton { + if (!_closeButton) { + UIButton *del = [UIButton buttonWithType:UIButtonTypeSystem]; + del.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.35]; + del.layer.cornerRadius = 14; del.layer.masksToBounds = YES; + del.titleLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightSemibold]; + [del setTitle:@"删除" forState:UIControlStateNormal]; + [del setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; + [del addTarget:self action:@selector(onTapClose) forControlEvents:UIControlEventTouchUpInside]; + _closeButton = del; + } + return _closeButton; +} + +- (void)onTapClose { + if ([self.delegate respondsToSelector:@selector(streamOverlayDidTapClose:)]) { + [self.delegate streamOverlayDidTapClose:self]; + } +} + +- (void)appendChunk:(NSString *)text { + if (text.length == 0) return; + [self.textViewInternal appendStreamText:text]; +} + +- (void)finish { + [self.textViewInternal finishStreaming]; +} + +- (KBStreamTextView *)textView { return self.textViewInternal; } + +@end + diff --git a/CustomKeyboard/View/KBFunctionView.m b/CustomKeyboard/View/KBFunctionView.m index eea7ed9..25fad12 100644 --- a/CustomKeyboard/View/KBFunctionView.m +++ b/CustomKeyboard/View/KBFunctionView.m @@ -17,30 +17,25 @@ #import "KBSkinManager.h" #import "KBURLOpenBridge.h" // 兜底从扩展侧直接尝试 openURL: #import "KBStreamTextView.h" // 流式文本视图 +#import "KBStreamOverlayView.h" // 带关闭按钮的流式层 +#import "KBFunctionTagListView.h" #import "KBStreamFetcher.h" // 网络流封装 static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; -@interface KBFunctionView () +@interface KBFunctionView () // UI @property (nonatomic, strong) KBFunctionBarView *barViewInternal; @property (nonatomic, strong) KBFunctionPasteView *pasteViewInternal; -@property (nonatomic, strong) UICollectionView *collectionViewInternal; +@property (nonatomic, strong) KBFunctionTagListView *tagListView; @property (nonatomic, strong) UIView *rightButtonContainer; // 右侧竖排按钮容器 @property (nonatomic, strong) UIButton *pasteButtonInternal; @property (nonatomic, strong) UIButton *deleteButtonInternal; @property (nonatomic, strong) UIButton *clearButtonInternal; @property (nonatomic, strong) UIButton *sendButtonInternal; -// 临时:点击标签后展示的“流式文本视图”与其删除按钮 -@property (nonatomic, strong, nullable) KBStreamTextView *streamTextView; -@property (nonatomic, strong, nullable) UIButton *streamDeleteButton; -// 流式测试数据 & 定时器 -@property (nonatomic, strong, nullable) NSTimer *streamMockTimer; -@property (nonatomic, strong, nullable) NSArray *streamMockChunks; -@property (nonatomic, assign) NSInteger streamMockIndex; -@property (nonatomic, copy, nullable) NSString *streamMockSource; // 连续文本源(包含 \t 作为分段) -@property (nonatomic, assign) NSInteger streamMockCursor; // 当前光标位置 +// 叠层:流式文本视图 + 关闭按钮 +@property (nonatomic, strong, nullable) KBStreamOverlayView *streamOverlay; // 网络流式(封装) @property (nonatomic, strong, nullable) KBStreamFetcher *streamFetcher; @@ -152,9 +147,9 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; make.height.mas_equalTo(48); }]; - // 3. CollectionView - [self addSubview:self.collectionViewInternal]; - [self.collectionViewInternal mas_makeConstraints:^(MASConstraintMaker *make) { + // 3. Tag List View + [self addSubview:self.tagListView]; + [self.tagListView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.mas_left).offset(12); make.right.equalTo(self.rightButtonContainer.mas_left).offset(-12); make.top.equalTo(self.pasteViewInternal.mas_bottom).offset(10); @@ -167,7 +162,7 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; - (void)reloadDemoData { // 演示数据(可由外部替换) self.itemsInternal = @[@"高情商", @"暖味拉扯", @"风趣幽默", @"撩女生", @"社交惬匿", @"情场高手", @"一枚暖男", @"聊天搭子", @"表达爱意", @"更多话术"]; - [self.collectionViewInternal reloadData]; + [self.tagListView setItems:self.itemsInternal]; } #pragma mark - UICollectionView @@ -201,43 +196,21 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; - (void)kb_showStreamTextViewIfNeededWithTitle:(NSString *)title { // 已有则不重复创建 - if (self.streamTextView.superview) { return; } + if (self.streamOverlay.superview) { return; } // 隐藏标签列表,使用同一区域展示流式文本 - self.collectionViewInternal.hidden = YES; + self.tagListView.hidden = YES; - KBStreamTextView *sv = [[KBStreamTextView alloc] init]; - sv.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.92]; - sv.layer.cornerRadius = 12.0; sv.layer.masksToBounds = YES; - // 演示:可选填充一段初始文本 - if (title.length > 0) { - [sv appendStreamText:[NSString stringWithFormat:@"%@\t", title]]; // 以制表符结尾,生成一个段落 - } - [self addSubview:sv]; - [sv mas_makeConstraints:^(MASConstraintMaker *make) { + KBStreamOverlayView *overlay = [[KBStreamOverlayView alloc] init]; + overlay.delegate = (id)self; + [self addSubview:overlay]; + [overlay mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.mas_left).offset(12); make.right.equalTo(self.rightButtonContainer.mas_left).offset(-12); make.top.equalTo(self.pasteViewInternal.mas_bottom).offset(10); make.bottom.equalTo(self.mas_bottom).offset(-10); }]; - self.streamTextView = sv; - - // 右上角删除按钮 - UIButton *del = [UIButton buttonWithType:UIButtonTypeSystem]; - del.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.35]; - del.layer.cornerRadius = 14; del.layer.masksToBounds = YES; - del.titleLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightSemibold]; - [del setTitle:@"删除" forState:UIControlStateNormal]; - [del setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; - [del addTarget:self action:@selector(kb_onTapStreamDelete) forControlEvents:UIControlEventTouchUpInside]; - [sv addSubview:del]; - [del mas_makeConstraints:^(MASConstraintMaker *make) { - make.top.equalTo(sv.mas_top).offset(8); - make.right.equalTo(sv.mas_right).offset(-8); - make.height.mas_equalTo(28); - make.width.mas_greaterThanOrEqualTo(56); - }]; - self.streamDeleteButton = del; + self.streamOverlay = overlay; // 优先拉取后端测试数据(GET);失败则回落到本地演示 [self kb_startNetworkStreamingWithSeed:title]; @@ -246,11 +219,14 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; - (void)kb_onTapStreamDelete { // 关闭并销毁流式视图,恢复标签列表 [self kb_stopNetworkStreaming]; - [self.streamDeleteButton removeFromSuperview]; - self.streamDeleteButton = nil; - [self.streamTextView removeFromSuperview]; - self.streamTextView = nil; - self.collectionViewInternal.hidden = NO; + [self.streamOverlay removeFromSuperview]; + self.streamOverlay = nil; + self.tagListView.hidden = NO; +} + +// 叠层关闭回调 +- (void)streamOverlayDidTapClose:(KBStreamOverlayView *)overlay { + [self kb_onTapStreamDelete]; } @@ -282,12 +258,8 @@ static NSString * const kKBStreamDemoURL = @"http://192.168.1.144:7529/api/demo/ }; fetcher.onFinish = ^(NSError * _Nullable error) { __strong typeof(weakSelf) self = weakSelf; if (!self) return; - if (error) { - [KBHUD showInfo:@"拉取失败"]; - [self.streamTextView finishStreaming]; - } else { - [self.streamTextView finishStreaming]; - } + if (error) { [KBHUD showInfo:@"拉取失败"]; } + [self.streamOverlay finish]; }; self.streamFetcher = fetcher; [self.streamFetcher start]; @@ -305,11 +277,47 @@ static NSString * const kKBStreamDemoURL = @"http://192.168.1.144:7529/api/demo/ /// - 目前网络层(KBStreamFetcher)已做 “/t->\t、首段去一个 \t、段间去一个空格” /// - 这里仅负责附加到视图与标记首段状态,避免 UI 抖动 - (void)kb_appendChunkToStreamView:(NSString *)chunk { - if (chunk.length == 0 || !self.streamTextView) return; - [self.streamTextView appendStreamText:chunk]; + if (chunk.length == 0 || !self.streamOverlay) return; + [self.streamOverlay appendChunk:chunk]; self.streamHasOutput = YES; } +#pragma mark - KBFunctionTagListViewDelegate + +- (void)tagListView:(KBFunctionTagListView *)view didSelectIndex:(NSInteger)index title:(NSString *)title { + // 权限全部打开(键盘已启用 + 完全访问)。在扩展进程中仅需判断“完全访问”。 + if ([[KBFullAccessManager shared] hasFullAccess]) { + [self kb_showStreamTextViewIfNeededWithTitle:title ?: @""]; + return; + } + // 保留原有拉起主 App 的逻辑(若需要) + [KBHUD showInfo:@"处理中…"]; + UIInputViewController *ivc = KBFindInputViewController(self); + if (!ivc) return; + NSString *encodedTitle = [title stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]] ?: @""; + NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=functionView&index=%ld&title=%@", KB_UL_LOGIN, (long)index, encodedTitle]]; + if (!ul) return; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [ivc.extensionContext openURL:ul completionHandler:^(BOOL ok) { + if (ok) return; // Universal Link 成功 + NSURL *scheme = [NSURL URLWithString:[NSString stringWithFormat:@"%@@//login?src=functionView&index=%ld&title=%@", KB_APP_SCHEME, (long)index, encodedTitle]]; + [ivc.extensionContext openURL:scheme completionHandler:^(BOOL ok2) { + if (ok2) return; + BOOL bridged = NO; + @try { + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunguarded-availability" + bridged = [KBURLOpenBridge openURLViaResponder:scheme from:self]; + #pragma clang diagnostic pop + } @catch (__unused NSException *e) { bridged = NO; } + if (!bridged) { + dispatch_async(dispatch_get_main_queue(), ^{ [[KBFullAccessManager shared] ensureFullAccessOrGuideInView:self]; }); + } + }]; + }]; + }); +} + // 用户点击功能标签:优先 UL 拉起主App,失败再 Scheme;两次都失败则提示开启完全访问。 // 若已开启“完全访问”,则直接在键盘侧创建 KBStreamTextView,并在其右上角提供删除按钮关闭。 - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { @@ -501,17 +509,12 @@ static NSString * const kKBStreamDemoURL = @"http://192.168.1.144:7529/api/demo/ return _pasteViewInternal; } -- (UICollectionView *)collectionViewInternal { - if (!_collectionViewInternal) { - UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; - layout.sectionInset = UIEdgeInsetsZero; // 外边距交由约束控制 - _collectionViewInternal = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; - _collectionViewInternal.backgroundColor = [UIColor clearColor]; - _collectionViewInternal.dataSource = self; - _collectionViewInternal.delegate = self; - [_collectionViewInternal registerClass:[KBFunctionTagCell class] forCellWithReuseIdentifier:kKBFunctionTagCellId]; +- (KBFunctionTagListView *)tagListView { + if (!_tagListView) { + _tagListView = [[KBFunctionTagListView alloc] init]; + _tagListView.delegate = (id)self; } - return _collectionViewInternal; + return _tagListView; } - (UIView *)rightButtonContainer { @@ -580,7 +583,7 @@ static NSString * const kKBStreamDemoURL = @"http://192.168.1.144:7529/api/demo/ #pragma mark - Expose -- (UICollectionView *)collectionView { return self.collectionViewInternal; } +- (UICollectionView *)collectionView { return self.tagListView.collectionView; } - (NSArray *)items { return self.itemsInternal; } - (KBFunctionBarView *)barView { return self.barViewInternal; } - (KBFunctionPasteView *)pasteView { return self.pasteViewInternal; } diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 42ec51d..7667ac1 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -89,6 +89,8 @@ 049FB22F2EC34EB900FAB05D /* KBStreamTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB22E2EC34EB900FAB05D /* KBStreamTextView.m */; }; 049FB2322EC45A0000FAB05D /* KBStreamFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2312EC45A0000FAB05D /* KBStreamFetcher.m */; }; 049FB2352EC45C6A00FAB05D /* NetworkStreamHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2342EC45C6A00FAB05D /* NetworkStreamHandler.m */; }; + 049FB23B2EC4766700FAB05D /* KBFunctionTagListView.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2372EC4766700FAB05D /* KBFunctionTagListView.m */; }; + 049FB23C2EC4766700FAB05D /* KBStreamOverlayView.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2392EC4766700FAB05D /* KBStreamOverlayView.m */; }; 049FB31D2EC21BCD00FAB05D /* KBMyKeyboardCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB31C2EC21BCD00FAB05D /* KBMyKeyboardCell.m */; }; 04A9FE0F2EB481100020DB6D /* KBHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC97082EB31B14007BD342 /* KBHUD.m */; }; 04A9FE132EB4D0D20020DB6D /* KBFullAccessManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */; }; @@ -308,6 +310,10 @@ 049FB2312EC45A0000FAB05D /* KBStreamFetcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBStreamFetcher.m; sourceTree = ""; }; 049FB2332EC45C6A00FAB05D /* NetworkStreamHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NetworkStreamHandler.h; sourceTree = ""; }; 049FB2342EC45C6A00FAB05D /* NetworkStreamHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NetworkStreamHandler.m; sourceTree = ""; }; + 049FB2362EC4766700FAB05D /* KBFunctionTagListView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFunctionTagListView.h; sourceTree = ""; }; + 049FB2372EC4766700FAB05D /* KBFunctionTagListView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFunctionTagListView.m; sourceTree = ""; }; + 049FB2382EC4766700FAB05D /* KBStreamOverlayView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBStreamOverlayView.h; sourceTree = ""; }; + 049FB2392EC4766700FAB05D /* KBStreamOverlayView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBStreamOverlayView.m; sourceTree = ""; }; 049FB31B2EC21BCD00FAB05D /* KBMyKeyboardCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBMyKeyboardCell.h; sourceTree = ""; }; 049FB31C2EC21BCD00FAB05D /* KBMyKeyboardCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyKeyboardCell.m; sourceTree = ""; }; 04A9A67D2EB9E1690023B8F4 /* KBResponderUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBResponderUtils.h; sourceTree = ""; }; @@ -680,6 +686,17 @@ path = BMLongPressDragCellCollectionView; sourceTree = ""; }; + 049FB23A2EC4766700FAB05D /* Function */ = { + isa = PBXGroup; + children = ( + 049FB2362EC4766700FAB05D /* KBFunctionTagListView.h */, + 049FB2372EC4766700FAB05D /* KBFunctionTagListView.m */, + 049FB2382EC4766700FAB05D /* KBStreamOverlayView.h */, + 049FB2392EC4766700FAB05D /* KBStreamOverlayView.m */, + ); + path = Function; + sourceTree = ""; + }; 04A9FE122EB4D0D20020DB6D /* Manager */ = { isa = PBXGroup; children = ( @@ -759,6 +776,7 @@ 04FC95B12EB0B2CC007BD342 /* KBSettingView.m */, 049FB22D2EC34EB900FAB05D /* KBStreamTextView.h */, 049FB22E2EC34EB900FAB05D /* KBStreamTextView.m */, + 049FB23A2EC4766700FAB05D /* Function */, ); path = View; sourceTree = ""; @@ -1409,6 +1427,8 @@ 049FB2352EC45C6A00FAB05D /* NetworkStreamHandler.m in Sources */, 04FC956A2EB05497007BD342 /* KBKeyButton.m in Sources */, 04FC95B22EB0B2CC007BD342 /* KBSettingView.m in Sources */, + 049FB23B2EC4766700FAB05D /* KBFunctionTagListView.m in Sources */, + 049FB23C2EC4766700FAB05D /* KBStreamOverlayView.m in Sources */, 049FB22F2EC34EB900FAB05D /* KBStreamTextView.m in Sources */, 04FC95702EB09516007BD342 /* KBFunctionView.m in Sources */, 04FC956D2EB054B7007BD342 /* KBKeyboardView.m in Sources */,