修改暗黑模式功能UI
This commit is contained in:
@@ -4,15 +4,79 @@
|
|||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"filename" : "kb_del_icon@2x.png",
|
"filename" : "kb_del_icon@2x.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filename" : "kb_del_icon@2x 1.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filename" : "切图 256@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"filename" : "kb_del_icon@3x.png",
|
"filename" : "kb_del_icon@3x.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filename" : "kb_del_icon@3x 1.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filename" : "切图 256@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
|
|||||||
BIN
CustomKeyboard/KeyboardAssets.xcassets/kb_del_icon.imageset/kb_del_icon@2x 1.png
vendored
Normal file
BIN
CustomKeyboard/KeyboardAssets.xcassets/kb_del_icon.imageset/kb_del_icon@2x 1.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
CustomKeyboard/KeyboardAssets.xcassets/kb_del_icon.imageset/kb_del_icon@3x 1.png
vendored
Normal file
BIN
CustomKeyboard/KeyboardAssets.xcassets/kb_del_icon.imageset/kb_del_icon@3x 1.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
BIN
CustomKeyboard/KeyboardAssets.xcassets/kb_del_icon.imageset/切图 256@2x.png
vendored
Normal file
BIN
CustomKeyboard/KeyboardAssets.xcassets/kb_del_icon.imageset/切图 256@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1008 B |
BIN
CustomKeyboard/KeyboardAssets.xcassets/kb_del_icon.imageset/切图 256@3x.png
vendored
Normal file
BIN
CustomKeyboard/KeyboardAssets.xcassets/kb_del_icon.imageset/切图 256@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "KBFunctionTagCell.h"
|
#import "KBFunctionTagCell.h"
|
||||||
|
#import "KBFunctionView.h"
|
||||||
#import "Masonry.h"
|
#import "Masonry.h"
|
||||||
|
|
||||||
@interface KBFunctionTagCell ()
|
@interface KBFunctionTagCell ()
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
|
|
||||||
- (instancetype)initWithFrame:(CGRect)frame {
|
- (instancetype)initWithFrame:(CGRect)frame {
|
||||||
if (self = [super initWithFrame:frame]) {
|
if (self = [super initWithFrame:frame]) {
|
||||||
self.contentView.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9];
|
self.contentView.backgroundColor = [KBFunctionView kb_cellBackgroundColor];
|
||||||
self.contentView.layer.cornerRadius = 12;
|
self.contentView.layer.cornerRadius = 12;
|
||||||
self.contentView.layer.masksToBounds = YES;
|
self.contentView.layer.masksToBounds = YES;
|
||||||
|
|
||||||
@@ -73,7 +74,6 @@
|
|||||||
_emojiLabel.textAlignment = NSTextAlignmentCenter;
|
_emojiLabel.textAlignment = NSTextAlignmentCenter;
|
||||||
_emojiLabel.font = [KBFont medium:20];
|
_emojiLabel.font = [KBFont medium:20];
|
||||||
_emojiLabel.adjustsFontSizeToFitWidth = YES;
|
_emojiLabel.adjustsFontSizeToFitWidth = YES;
|
||||||
|
|
||||||
}
|
}
|
||||||
return _emojiLabel;
|
return _emojiLabel;
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
if (!_titleLabelInternal) {
|
if (!_titleLabelInternal) {
|
||||||
_titleLabelInternal = [[UILabel alloc] init];
|
_titleLabelInternal = [[UILabel alloc] init];
|
||||||
_titleLabelInternal.font = [KBFont medium:10];
|
_titleLabelInternal.font = [KBFont medium:10];
|
||||||
_titleLabelInternal.textColor = [UIColor colorWithHex:0x1B1F1A];
|
_titleLabelInternal.textColor = [KBFunctionView kb_cellTextColor];
|
||||||
// 最多两行,文本过长时末尾截断
|
// 最多两行,文本过长时末尾截断
|
||||||
_titleLabelInternal.numberOfLines = 2;
|
_titleLabelInternal.numberOfLines = 2;
|
||||||
_titleLabelInternal.lineBreakMode = NSLineBreakByTruncatingTail;
|
_titleLabelInternal.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||||
@@ -91,14 +91,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
|
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
|
||||||
static UIActivityIndicatorViewStyle KBSpinnerStyle(void) { return UIActivityIndicatorViewStyleMedium; }
|
static UIActivityIndicatorViewStyle KBSpinnerStyle(void) {
|
||||||
|
return UIActivityIndicatorViewStyleMedium;
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
static UIActivityIndicatorViewStyle KBSpinnerStyle(void) { return UIActivityIndicatorViewStyleGray; }
|
static UIActivityIndicatorViewStyle KBSpinnerStyle(void) {
|
||||||
|
return UIActivityIndicatorViewStyleGray;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
- (UIActivityIndicatorView *)loadingView {
|
- (UIActivityIndicatorView *)loadingView {
|
||||||
if (!_loadingView) {
|
if (!_loadingView) {
|
||||||
_loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:KBSpinnerStyle()];
|
_loadingView = [[UIActivityIndicatorView alloc]
|
||||||
|
initWithActivityIndicatorStyle:KBSpinnerStyle()];
|
||||||
_loadingView.hidesWhenStopped = YES;
|
_loadingView.hidesWhenStopped = YES;
|
||||||
_loadingView.color = [UIColor grayColor];
|
_loadingView.color = [UIColor grayColor];
|
||||||
_loadingView.hidden = YES;
|
_loadingView.hidden = YES;
|
||||||
@@ -108,7 +113,9 @@ static UIActivityIndicatorViewStyle KBSpinnerStyle(void) { return UIActivityIndi
|
|||||||
|
|
||||||
#pragma mark - Expose
|
#pragma mark - Expose
|
||||||
|
|
||||||
- (UILabel *)titleLabel { return self.titleLabelInternal; }
|
- (UILabel *)titleLabel {
|
||||||
|
return self.titleLabelInternal;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)setLoading:(BOOL)loading {
|
- (void)setLoading:(BOOL)loading {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
|||||||
@@ -10,9 +10,12 @@
|
|||||||
|
|
||||||
@protocol KBFunctionViewDelegate <NSObject>
|
@protocol KBFunctionViewDelegate <NSObject>
|
||||||
@optional
|
@optional
|
||||||
- (void)functionView:(KBFunctionView *_Nullable)functionView didTapToolActionAtIndex:(NSInteger)index;
|
- (void)functionView:(KBFunctionView *_Nullable)functionView
|
||||||
- (void)functionView:(KBFunctionView *_Nullable)functionView didRightTapToolActionAtIndex:(NSInteger)index;
|
didTapToolActionAtIndex:(NSInteger)index;
|
||||||
- (void)functionViewDidRequestSubscription:(KBFunctionView *_Nullable)functionView;
|
- (void)functionView:(KBFunctionView *_Nullable)functionView
|
||||||
|
didRightTapToolActionAtIndex:(NSInteger)index;
|
||||||
|
- (void)functionViewDidRequestSubscription:
|
||||||
|
(KBFunctionView *_Nullable)functionView;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -23,9 +26,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
|
|
||||||
@property(nonatomic, weak) id<KBFunctionViewDelegate> delegate;
|
@property(nonatomic, weak) id<KBFunctionViewDelegate> delegate;
|
||||||
|
|
||||||
|
@property(nonatomic, strong, readonly)
|
||||||
@property (nonatomic, strong, readonly) UICollectionView *collectionView; // 话术分类/标签列表
|
UICollectionView *collectionView; // 话术分类/标签列表
|
||||||
@property (nonatomic, strong, readonly) NSArray<NSString *> *items; // 简单数据源(演示用)
|
@property(nonatomic, strong, readonly)
|
||||||
|
NSArray<NSString *> *items; // 简单数据源(演示用)
|
||||||
|
|
||||||
// 子视图暴露,便于外部接入事件
|
// 子视图暴露,便于外部接入事件
|
||||||
@property(nonatomic, strong, readonly) KBFunctionBarView *barView;
|
@property(nonatomic, strong, readonly) KBFunctionBarView *barView;
|
||||||
@@ -39,6 +43,14 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
/// 应用当前皮肤(更新背景/强调色)
|
/// 应用当前皮肤(更新背景/强调色)
|
||||||
- (void)kb_applyTheme;
|
- (void)kb_applyTheme;
|
||||||
|
|
||||||
|
#pragma mark - Theme Colors (用于 Cell 获取暗黑模式颜色)
|
||||||
|
|
||||||
|
/// Cell 背景色:暗黑 #707070,浅色 白色90%透明度
|
||||||
|
+ (UIColor *)kb_cellBackgroundColor;
|
||||||
|
|
||||||
|
/// Cell 文字颜色:暗黑 #FFFFFF,浅色 #1B1F1A
|
||||||
|
+ (UIColor *)kb_cellTextColor;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -6,30 +6,32 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "KBFunctionView.h"
|
#import "KBFunctionView.h"
|
||||||
#import "KBResponderUtils.h" // 统一查找 UIInputViewController 的工具
|
#import "KBAuthManager.h" // 登录态判断(共享钥匙串)
|
||||||
|
#import "KBBackspaceLongPressHandler.h"
|
||||||
|
#import "KBBackspaceUndoManager.h"
|
||||||
|
#import "KBBizCode.h"
|
||||||
|
#import "KBFullAccessGuideView.h"
|
||||||
|
#import "KBFullAccessManager.h"
|
||||||
#import "KBFunctionBarView.h"
|
#import "KBFunctionBarView.h"
|
||||||
#import "KBFunctionPasteView.h"
|
#import "KBFunctionPasteView.h"
|
||||||
#import "KBFunctionTagCell.h"
|
#import "KBFunctionTagCell.h"
|
||||||
#import "Masonry.h"
|
|
||||||
#import <MBProgressHUD.h>
|
|
||||||
#import "KBFullAccessGuideView.h"
|
|
||||||
#import "KBFullAccessManager.h"
|
|
||||||
#import "KBSkinManager.h"
|
|
||||||
#import "KBAuthManager.h" // 登录态判断(共享钥匙串)
|
|
||||||
#import "KBULBridgeNotification.h" // Darwin 通知常量(UL 已处理)
|
|
||||||
#import "KBHostAppLauncher.h"
|
|
||||||
#import "KBStreamTextView.h" // 流式文本视图
|
|
||||||
#import "KBStreamOverlayView.h" // 带关闭按钮的流式层
|
|
||||||
#import "KBFunctionTagListView.h"
|
#import "KBFunctionTagListView.h"
|
||||||
#import "WJXEventSource.h"
|
#import "KBHostAppLauncher.h"
|
||||||
#import "KBTagItemModel.h"
|
|
||||||
#import <MJExtension/MJExtension.h>
|
|
||||||
#import "KBBizCode.h"
|
|
||||||
#import "KBBackspaceLongPressHandler.h"
|
|
||||||
#import "KBBackspaceUndoManager.h"
|
|
||||||
#import "KBInputBufferManager.h"
|
#import "KBInputBufferManager.h"
|
||||||
|
#import "KBResponderUtils.h" // 统一查找 UIInputViewController 的工具
|
||||||
|
#import "KBSkinManager.h"
|
||||||
|
#import "KBStreamOverlayView.h" // 带关闭按钮的流式层
|
||||||
|
#import "KBStreamTextView.h" // 流式文本视图
|
||||||
|
#import "KBTagItemModel.h"
|
||||||
|
#import "KBULBridgeNotification.h" // Darwin 通知常量(UL 已处理)
|
||||||
|
#import "Masonry.h"
|
||||||
|
#import "WJXEventSource.h"
|
||||||
|
#import <MBProgressHUD.h>
|
||||||
|
#import <MJExtension/MJExtension.h>
|
||||||
|
|
||||||
@interface KBFunctionView () <KBFunctionBarViewDelegate, KBStreamOverlayViewDelegate, KBFunctionTagListViewDelegate>
|
@interface KBFunctionView () <KBFunctionBarViewDelegate,
|
||||||
|
KBStreamOverlayViewDelegate,
|
||||||
|
KBFunctionTagListViewDelegate>
|
||||||
// UI
|
// UI
|
||||||
@property(nonatomic, strong) KBFunctionBarView *barViewInternal;
|
@property(nonatomic, strong) KBFunctionBarView *barViewInternal;
|
||||||
@property(nonatomic, strong) KBFunctionPasteView *pasteViewInternal;
|
@property(nonatomic, strong) KBFunctionPasteView *pasteViewInternal;
|
||||||
@@ -45,8 +47,10 @@
|
|||||||
|
|
||||||
// 网络流式(封装)
|
// 网络流式(封装)
|
||||||
@property(nonatomic, strong, nullable) WJXEventSource *eventSource;
|
@property(nonatomic, strong, nullable) WJXEventSource *eventSource;
|
||||||
@property (nonatomic, assign) BOOL streamHasOutput; // 是否已输出过正文(首段去首个 \t 用)
|
@property(nonatomic, assign)
|
||||||
@property (nonatomic, strong, nullable) NSNumber *loadingTagIndex; // 当前显示loading的标签index
|
BOOL streamHasOutput; // 是否已输出过正文(首段去首个 \t 用)
|
||||||
|
@property(nonatomic, strong, nullable)
|
||||||
|
NSNumber *loadingTagIndex; // 当前显示loading的标签index
|
||||||
@property(nonatomic, copy, nullable) NSString *loadingTagTitle;
|
@property(nonatomic, copy, nullable) NSString *loadingTagTitle;
|
||||||
@property(nonatomic, assign) BOOL eventSourceDidReceiveDone;
|
@property(nonatomic, assign) BOOL eventSourceDidReceiveDone;
|
||||||
@property(nonatomic, copy, nullable) NSString *eventSourceSplitPrefix;
|
@property(nonatomic, copy, nullable) NSString *eventSourceSplitPrefix;
|
||||||
@@ -56,8 +60,10 @@
|
|||||||
@property(nonatomic, strong) NSMutableArray<KBTagItemModel *> *modelArray;
|
@property(nonatomic, strong) NSMutableArray<KBTagItemModel *> *modelArray;
|
||||||
|
|
||||||
// 剪贴板自动检测
|
// 剪贴板自动检测
|
||||||
@property (nonatomic, strong) NSTimer *pasteboardTimer; // 轮询定时器(轻量、主线程)
|
@property(nonatomic, strong)
|
||||||
@property (nonatomic, assign) NSInteger lastHandledPBCount; // 上次处理过的 changeCount,避免重复弹窗
|
NSTimer *pasteboardTimer; // 轮询定时器(轻量、主线程)
|
||||||
|
@property(nonatomic, assign)
|
||||||
|
NSInteger lastHandledPBCount; // 上次处理过的 changeCount,避免重复弹窗
|
||||||
|
|
||||||
// UL 双路兜底
|
// UL 双路兜底
|
||||||
@property(nonatomic, assign) NSUInteger kb_ulSeq; // 当前 UL 发起序号
|
@property(nonatomic, assign) NSUInteger kb_ulSeq; // 当前 UL 发起序号
|
||||||
@@ -71,25 +77,29 @@
|
|||||||
if (self = [super initWithFrame:frame]) {
|
if (self = [super initWithFrame:frame]) {
|
||||||
// 背景使用当前主题强调色
|
// 背景使用当前主题强调色
|
||||||
[self kb_applyTheme];
|
[self kb_applyTheme];
|
||||||
self.backspaceHandler = [[KBBackspaceLongPressHandler alloc] initWithContainerView:self];
|
self.backspaceHandler =
|
||||||
|
[[KBBackspaceLongPressHandler alloc] initWithContainerView:self];
|
||||||
|
|
||||||
[self setupUI];
|
[self setupUI];
|
||||||
// [self reloadDemoData];
|
// [self reloadDemoData];
|
||||||
[self kb_reloadTagsFromSharedDefaults];
|
[self kb_reloadTagsFromSharedDefaults];
|
||||||
|
|
||||||
|
|
||||||
// 初始化剪贴板监控状态
|
// 初始化剪贴板监控状态
|
||||||
_lastHandledPBCount = [UIPasteboard generalPasteboard].changeCount;
|
_lastHandledPBCount = [UIPasteboard generalPasteboard].changeCount;
|
||||||
|
|
||||||
// 监听“完全访问”状态变化,动态启停剪贴板监控,避免在未开完全访问时触发 TCC/XPC 错误日志
|
// 监听“完全访问”状态变化,动态启停剪贴板监控,避免在未开完全访问时触发
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(kb_fullAccessChanged) name:KBFullAccessChangedNotification object:nil];
|
// TCC/XPC 错误日志
|
||||||
|
[[NSNotificationCenter defaultCenter]
|
||||||
|
addObserver:self
|
||||||
|
selector:@selector(kb_fullAccessChanged)
|
||||||
|
name:KBFullAccessChangedNotification
|
||||||
|
object:nil];
|
||||||
|
|
||||||
// 监听主 App 的 Darwin 确认(UL 已处理)
|
// 监听主 App 的 Darwin 确认(UL 已处理)
|
||||||
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
|
CFNotificationCenterAddObserver(
|
||||||
(__bridge const void *)(self),
|
CFNotificationCenterGetDarwinNotifyCenter(),
|
||||||
KBULDarwinCallback,
|
(__bridge const void *)(self), KBULDarwinCallback,
|
||||||
(__bridge CFStringRef)KBDarwinULHandled,
|
(__bridge CFStringRef)KBDarwinULHandled, NULL,
|
||||||
NULL,
|
|
||||||
CFNotificationSuspensionBehaviorDeliverImmediately);
|
CFNotificationSuspensionBehaviorDeliverImmediately);
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
@@ -99,11 +109,13 @@
|
|||||||
|
|
||||||
/// 从 App Group 的 NSUserDefaults 中读取真实 JSON,解析为 model + 标签文案
|
/// 从 App Group 的 NSUserDefaults 中读取真实 JSON,解析为 model + 标签文案
|
||||||
- (void)kb_reloadTagsFromSharedDefaults {
|
- (void)kb_reloadTagsFromSharedDefaults {
|
||||||
NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:AppGroup];
|
NSUserDefaults *sharedDefaults =
|
||||||
|
[[NSUserDefaults alloc] initWithSuiteName:AppGroup];
|
||||||
NSDictionary *jsonDict = [sharedDefaults objectForKey:AppGroup_MyKbJson];
|
NSDictionary *jsonDict = [sharedDefaults objectForKey:AppGroup_MyKbJson];
|
||||||
if (jsonDict != nil) {
|
if (jsonDict != nil) {
|
||||||
id dataObj = jsonDict[@"data"];
|
id dataObj = jsonDict[@"data"];
|
||||||
NSArray<KBTagItemModel *> *modelList = [KBTagItemModel mj_objectArrayWithKeyValuesArray:(NSArray *)dataObj];
|
NSArray<KBTagItemModel *> *modelList =
|
||||||
|
[KBTagItemModel mj_objectArrayWithKeyValuesArray:(NSArray *)dataObj];
|
||||||
if (modelList.count > 0) {
|
if (modelList.count > 0) {
|
||||||
self.modelArray = [NSMutableArray array];
|
self.modelArray = [NSMutableArray array];
|
||||||
[self.modelArray addObjectsFromArray:modelList];
|
[self.modelArray addObjectsFromArray:modelList];
|
||||||
@@ -115,21 +127,118 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Theme
|
#pragma mark - Theme
|
||||||
|
|
||||||
|
/// 判断当前是否为暗黑模式
|
||||||
|
- (BOOL)kb_isDarkMode {
|
||||||
|
if (@available(iOS 13.0, *)) {
|
||||||
|
return self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark;
|
||||||
|
}
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Theme Colors
|
||||||
|
|
||||||
|
/// 整体背景色:暗黑 #323232,浅色 #D0D3DA
|
||||||
|
+ (UIColor *)kb_backgroundColor {
|
||||||
|
if (@available(iOS 13.0, *)) {
|
||||||
|
return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
|
||||||
|
UITraitCollection *_Nonnull traitCollection) {
|
||||||
|
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
|
||||||
|
return [UIColor colorWithHex:0x2B2B2B];
|
||||||
|
} else {
|
||||||
|
return [UIColor colorWithHex:0xD0D3DA];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return [UIColor colorWithHex:0xD0D3DA];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cell 背景色:暗黑 #707070,浅色 白色90%透明度
|
||||||
|
+ (UIColor *)kb_cellBackgroundColor {
|
||||||
|
if (@available(iOS 13.0, *)) {
|
||||||
|
return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
|
||||||
|
UITraitCollection *_Nonnull traitCollection) {
|
||||||
|
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
|
||||||
|
return [UIColor colorWithHex:0x707070];
|
||||||
|
} else {
|
||||||
|
return [UIColor colorWithWhite:1 alpha:0.9];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return [UIColor colorWithWhite:1 alpha:0.9];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cell 文字颜色:暗黑 #FFFFFF,浅色 #1B1F1A
|
||||||
|
+ (UIColor *)kb_cellTextColor {
|
||||||
|
if (@available(iOS 13.0, *)) {
|
||||||
|
return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
|
||||||
|
UITraitCollection *_Nonnull traitCollection) {
|
||||||
|
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
|
||||||
|
return [UIColor whiteColor];
|
||||||
|
} else {
|
||||||
|
return [UIColor colorWithHex:0x1B1F1A];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return [UIColor colorWithHex:0x1B1F1A];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear 按钮文字颜色:暗黑白色,浅色黑色
|
||||||
|
+ (UIColor *)kb_clearButtonTextColor {
|
||||||
|
if (@available(iOS 13.0, *)) {
|
||||||
|
return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
|
||||||
|
UITraitCollection *_Nonnull traitCollection) {
|
||||||
|
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
|
||||||
|
return [UIColor whiteColor];
|
||||||
|
} else {
|
||||||
|
return [UIColor blackColor];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return [UIColor blackColor];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删除按钮背景色:暗黑 #707070,浅色 #B9BDC8
|
||||||
|
+ (UIColor *)kb_deleteButtonBackgroundColor {
|
||||||
|
if (@available(iOS 13.0, *)) {
|
||||||
|
return [UIColor colorWithDynamicProvider:^UIColor *_Nonnull(
|
||||||
|
UITraitCollection *_Nonnull traitCollection) {
|
||||||
|
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
|
||||||
|
return [UIColor colorWithHex:0x707070];
|
||||||
|
} else {
|
||||||
|
return [UIColor colorWithHex:0xB9BDC8];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return [UIColor colorWithHex:0xB9BDC8];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)kb_applyTheme {
|
- (void)kb_applyTheme {
|
||||||
// KBSkinManager *mgr = [KBSkinManager shared];
|
// 使用动态颜色设置背景
|
||||||
// UIColor *accent = mgr.current.accentColor ?: [UIColor colorWithRed:0.77 green:0.93 blue:0.82 alpha:1.0];
|
self.backgroundColor = [KBFunctionView kb_backgroundColor];
|
||||||
// BOOL hasImg = ([mgr currentBackgroundImage] != nil);
|
|
||||||
self.backgroundColor = [UIColor colorWithHex:0xD0D3DA];
|
// 更新按钮颜色
|
||||||
|
self.clearButtonInternal.backgroundColor =
|
||||||
|
[KBFunctionView kb_deleteButtonBackgroundColor];
|
||||||
|
[self.clearButtonInternal
|
||||||
|
setTitleColor:[KBFunctionView kb_clearButtonTextColor]
|
||||||
|
forState:UIControlStateNormal];
|
||||||
|
self.deleteButtonInternal.backgroundColor =
|
||||||
|
[KBFunctionView kb_deleteButtonBackgroundColor];
|
||||||
|
|
||||||
|
// 刷新 TagListView 以更新 cell 颜色
|
||||||
|
[self.tagListView.collectionView reloadData];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
[self stopPasteboardMonitor];
|
[self stopPasteboardMonitor];
|
||||||
[self kb_stopNetworkStreaming];
|
[self kb_stopNetworkStreaming];
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||||
CFNotificationCenterRemoveObserver(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)(self), (__bridge CFStringRef)KBDarwinULHandled, NULL);
|
CFNotificationCenterRemoveObserver(
|
||||||
|
CFNotificationCenterGetDarwinNotifyCenter(),
|
||||||
|
(__bridge const void *)(self), (__bridge CFStringRef)KBDarwinULHandled,
|
||||||
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - UI
|
#pragma mark - UI
|
||||||
@@ -196,7 +305,9 @@
|
|||||||
make.height.mas_equalTo(smallH);
|
make.height.mas_equalTo(smallH);
|
||||||
}];
|
}];
|
||||||
// 点击整个粘贴卡片按钮,行为与右侧「Paste」按钮保持一致
|
// 点击整个粘贴卡片按钮,行为与右侧「Paste」按钮保持一致
|
||||||
[self.pasteViewInternal.pasBtn addTarget:self action:@selector(onTapPaste) forControlEvents:UIControlEventTouchUpInside];
|
[self.pasteViewInternal.pasBtn addTarget:self
|
||||||
|
action:@selector(onTapPaste)
|
||||||
|
forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
|
||||||
// 3. Tag List View
|
// 3. Tag List View
|
||||||
[self addSubview:self.tagListView];
|
[self addSubview:self.tagListView];
|
||||||
@@ -229,7 +340,9 @@
|
|||||||
|
|
||||||
- (void)kb_showStreamTextViewIfNeededWithTitle:(NSString *)title {
|
- (void)kb_showStreamTextViewIfNeededWithTitle:(NSString *)title {
|
||||||
// 已有则不重复创建
|
// 已有则不重复创建
|
||||||
if (self.streamOverlay.superview) { return; }
|
if (self.streamOverlay.superview) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 隐藏标签列表,使用同一区域展示流式文本
|
// 隐藏标签列表,使用同一区域展示流式文本
|
||||||
self.tagListView.hidden = YES;
|
self.tagListView.hidden = YES;
|
||||||
@@ -277,16 +390,20 @@
|
|||||||
[self kb_onTapStreamDelete];
|
[self kb_onTapStreamDelete];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Network Streaming (WJXEventSource)
|
#pragma mark - Network Streaming (WJXEventSource)
|
||||||
|
|
||||||
- (void)kb_startNetworkStreamingWithSeed:(NSString *)seedTitle {
|
- (void)kb_startNetworkStreamingWithSeed:(NSString *)seedTitle {
|
||||||
[self kb_stopNetworkStreaming];
|
[self kb_stopNetworkStreaming];
|
||||||
if (![[KBFullAccessManager shared] hasFullAccess]) { return; }
|
if (![[KBFullAccessManager shared] hasFullAccess]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
NSString *apiUrl = [NSString stringWithFormat:@"%@%@", KB_BASE_URL, API_AI_TALK];
|
NSString *apiUrl =
|
||||||
|
[NSString stringWithFormat:@"%@%@", KB_BASE_URL, API_AI_TALK];
|
||||||
NSURL *url = [NSURL URLWithString:apiUrl];
|
NSURL *url = [NSURL URLWithString:apiUrl];
|
||||||
if (!url) { return; }
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
NSInteger characterId = 0;
|
NSInteger characterId = 0;
|
||||||
if (self.loadingTagIndex != nil) {
|
if (self.loadingTagIndex != nil) {
|
||||||
@@ -297,21 +414,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
NSInteger resolvedCharacterId = (characterId > 0) ? characterId : 75;
|
NSInteger resolvedCharacterId = (characterId > 0) ? characterId : 75;
|
||||||
NSString *message = seedTitle.length > 0 ? seedTitle : @"aliqua non cupidatat";
|
NSString *message =
|
||||||
// message = [NSString stringWithFormat:@"%@%d",message,arc4random() % 10000];
|
seedTitle.length > 0 ? seedTitle : @"aliqua non cupidatat";
|
||||||
NSDictionary *payload = @{
|
// message = [NSString stringWithFormat:@"%@%d",message,arc4random() %
|
||||||
@"characterId": @(resolvedCharacterId),
|
// 10000];
|
||||||
@"message": message
|
NSDictionary *payload =
|
||||||
};
|
@{@"characterId" : @(resolvedCharacterId), @"message" : message};
|
||||||
NSLog(@"[KBFunction] request payload: %@", payload);
|
NSLog(@"[KBFunction] request payload: %@", payload);
|
||||||
NSError *bodyError = nil;
|
NSError *bodyError = nil;
|
||||||
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:payload options:0 error:&bodyError];
|
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:payload
|
||||||
|
options:0
|
||||||
|
error:&bodyError];
|
||||||
if (bodyError || bodyData.length == 0) {
|
if (bodyError || bodyData.length == 0) {
|
||||||
NSLog(@"[KBFunction] build body failed: %@", bodyError);
|
NSLog(@"[KBFunction] build body failed: %@", bodyError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60];
|
NSMutableURLRequest *request =
|
||||||
|
[NSMutableURLRequest requestWithURL:url
|
||||||
|
cachePolicy:NSURLRequestReloadIgnoringCacheData
|
||||||
|
timeoutInterval:60];
|
||||||
request.HTTPMethod = @"POST";
|
request.HTTPMethod = @"POST";
|
||||||
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||||
NSString *token = KBAuthManager.shared.current.accessToken ?: @"";
|
NSString *token = KBAuthManager.shared.current.accessToken ?: @"";
|
||||||
@@ -327,14 +449,24 @@
|
|||||||
__weak typeof(self) weakSelf = self;
|
__weak typeof(self) weakSelf = self;
|
||||||
WJXEventSource *source = [[WJXEventSource alloc] initWithRquest:request];
|
WJXEventSource *source = [[WJXEventSource alloc] initWithRquest:request];
|
||||||
source.ignoreRetryAction = YES;
|
source.ignoreRetryAction = YES;
|
||||||
[source addListener:^(WJXEvent * _Nonnull event) {
|
[source
|
||||||
__strong typeof(weakSelf) self = weakSelf; if (!self) return;
|
addListener:^(WJXEvent *_Nonnull event) {
|
||||||
|
__strong typeof(weakSelf) self = weakSelf;
|
||||||
|
if (!self)
|
||||||
|
return;
|
||||||
[self kb_handleEventSourceMessage:event];
|
[self kb_handleEventSourceMessage:event];
|
||||||
} forEvent:WJXEventNameMessage queue:NSOperationQueue.mainQueue];
|
}
|
||||||
[source addListener:^(WJXEvent * _Nonnull event) {
|
forEvent:WJXEventNameMessage
|
||||||
__strong typeof(weakSelf) self = weakSelf; if (!self) return;
|
queue:NSOperationQueue.mainQueue];
|
||||||
|
[source
|
||||||
|
addListener:^(WJXEvent *_Nonnull event) {
|
||||||
|
__strong typeof(weakSelf) self = weakSelf;
|
||||||
|
if (!self)
|
||||||
|
return;
|
||||||
[self kb_handleEventSourceError:event.error];
|
[self kb_handleEventSourceError:event.error];
|
||||||
} forEvent:WJXEventNameError queue:NSOperationQueue.mainQueue];
|
}
|
||||||
|
forEvent:WJXEventNameError
|
||||||
|
queue:NSOperationQueue.mainQueue];
|
||||||
self.eventSource = source;
|
self.eventSource = source;
|
||||||
[self.eventSource open];
|
[self.eventSource open];
|
||||||
}
|
}
|
||||||
@@ -348,16 +480,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)kb_handleEventSourceMessage:(WJXEvent *)event {
|
- (void)kb_handleEventSourceMessage:(WJXEvent *)event {
|
||||||
if (event.data.length == 0) { return; }
|
if (event.data.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
NSLog(@"[KBStream] SSE raw payload: %@", event.data);
|
NSLog(@"[KBStream] SSE raw payload: %@", event.data);
|
||||||
NSData *jsonData = [event.data dataUsingEncoding:NSUTF8StringEncoding];
|
NSData *jsonData = [event.data dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
if (!jsonData) { return; }
|
if (!jsonData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
NSDictionary *payload = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
|
NSDictionary *payload = [NSJSONSerialization JSONObjectWithData:jsonData
|
||||||
if (error || ![payload isKindOfClass:[NSDictionary class]]) { return; }
|
options:0
|
||||||
if ([self kb_handleBizErrorIfNeeded:payload]) { return; }
|
error:&error];
|
||||||
|
if (error || ![payload isKindOfClass:[NSDictionary class]]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ([self kb_handleBizErrorIfNeeded:payload]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
NSString *type = payload[@"type"];
|
NSString *type = payload[@"type"];
|
||||||
if (![type isKindOfClass:[NSString class]]) { return; }
|
if (![type isKindOfClass:[NSString class]]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ([type isEqualToString:@"llm_chunk"]) {
|
if ([type isEqualToString:@"llm_chunk"]) {
|
||||||
NSString *chunk = [self kb_normalizedLLMChunkString:payload[@"data"]];
|
NSString *chunk = [self kb_normalizedLLMChunkString:payload[@"data"]];
|
||||||
@@ -381,7 +525,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)kb_handleEventSourceError:(NSError *_Nullable)error {
|
- (void)kb_handleEventSourceError:(NSError *_Nullable)error {
|
||||||
if (self.eventSourceDidReceiveDone) { return; }
|
if (self.eventSourceDidReceiveDone) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
[self kb_finishEventSourceWithError:error];
|
[self kb_finishEventSourceWithError:error];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,7 +560,8 @@
|
|||||||
if (msg.length == 0) {
|
if (msg.length == 0) {
|
||||||
msg = KBLocalized(@"拉取失败");
|
msg = KBLocalized(@"拉取失败");
|
||||||
}
|
}
|
||||||
NSError *bizError = [NSError errorWithDomain:@"KBStreamBizError"
|
NSError *bizError =
|
||||||
|
[NSError errorWithDomain:@"KBStreamBizError"
|
||||||
code:code
|
code:code
|
||||||
userInfo:@{NSLocalizedDescriptionKey : msg}];
|
userInfo:@{NSLocalizedDescriptionKey : msg}];
|
||||||
[self kb_finishEventSourceWithError:bizError];
|
[self kb_finishEventSourceWithError:bizError];
|
||||||
@@ -425,7 +572,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)kb_requestSubscriptionGuide {
|
- (void)kb_requestSubscriptionGuide {
|
||||||
if ([self.delegate respondsToSelector:@selector(functionViewDidRequestSubscription:)]) {
|
if ([self.delegate
|
||||||
|
respondsToSelector:@selector(functionViewDidRequestSubscription:)]) {
|
||||||
[self.delegate functionViewDidRequestSubscription:self];
|
[self.delegate functionViewDidRequestSubscription:self];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -433,7 +581,9 @@
|
|||||||
#pragma mark - Event Parsing
|
#pragma mark - Event Parsing
|
||||||
|
|
||||||
- (NSString *)kb_normalizedLLMChunkString:(id)dataValue {
|
- (NSString *)kb_normalizedLLMChunkString:(id)dataValue {
|
||||||
if (![dataValue isKindOfClass:[NSString class]]) { return @""; }
|
if (![dataValue isKindOfClass:[NSString class]]) {
|
||||||
|
return @"";
|
||||||
|
}
|
||||||
NSString *text = (NSString *)dataValue;
|
NSString *text = (NSString *)dataValue;
|
||||||
|
|
||||||
// 1. 处理上一个包遗留的 <SPLIT> 前缀(比如 "<SP" + "LIT>")
|
// 1. 处理上一个包遗留的 <SPLIT> 前缀(比如 "<SP" + "LIT>")
|
||||||
@@ -441,7 +591,9 @@
|
|||||||
text = [self.eventSourceSplitPrefix stringByAppendingString:text ?: @""];
|
text = [self.eventSourceSplitPrefix stringByAppendingString:text ?: @""];
|
||||||
self.eventSourceSplitPrefix = nil;
|
self.eventSourceSplitPrefix = nil;
|
||||||
}
|
}
|
||||||
if (text.length == 0) { return @""; }
|
if (text.length == 0) {
|
||||||
|
return @"";
|
||||||
|
}
|
||||||
|
|
||||||
// 2. 去掉开头多余换行(避免一开始就空一大块)
|
// 2. 去掉开头多余换行(避免一开始就空一大块)
|
||||||
while (text.length > 0) {
|
while (text.length > 0) {
|
||||||
@@ -452,7 +604,9 @@
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (text.length == 0) { return @""; }
|
if (text.length == 0) {
|
||||||
|
return @"";
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 处理结尾可能是不完整的 "<SPLIT" 之类,先截掉,放到下一个包里拼
|
// 3. 处理结尾可能是不完整的 "<SPLIT" 之类,先截掉,放到下一个包里拼
|
||||||
NSString *suffix = [self kb_pendingSplitSuffixForString:text];
|
NSString *suffix = [self kb_pendingSplitSuffixForString:text];
|
||||||
@@ -460,24 +614,29 @@
|
|||||||
self.eventSourceSplitPrefix = suffix;
|
self.eventSourceSplitPrefix = suffix;
|
||||||
text = [text substringToIndex:text.length - suffix.length];
|
text = [text substringToIndex:text.length - suffix.length];
|
||||||
}
|
}
|
||||||
if (text.length == 0) { return @""; }
|
if (text.length == 0) {
|
||||||
|
return @"";
|
||||||
|
}
|
||||||
|
|
||||||
// 4. 处理完整的 <SPLIT>,变成段落分隔符 \t
|
// 4. 处理完整的 <SPLIT>,变成段落分隔符 \t
|
||||||
text = [text stringByReplacingOccurrencesOfString:@"<SPLIT>" withString:@"\t"];
|
text = [text stringByReplacingOccurrencesOfString:@"<SPLIT>"
|
||||||
|
withString:@"\t"];
|
||||||
|
|
||||||
// 不再做其它替换,不合并 /t、不改行,只把真正内容原样丢给 UI
|
// 不再做其它替换,不合并 /t、不改行,只把真正内容原样丢给 UI
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (NSString *)kb_formattedSearchResultString:(id)dataValue {
|
- (NSString *)kb_formattedSearchResultString:(id)dataValue {
|
||||||
// data 不是数组就直接返回空串
|
// data 不是数组就直接返回空串
|
||||||
if (![dataValue isKindOfClass:[NSArray class]]) { return @""; }
|
if (![dataValue isKindOfClass:[NSArray class]]) {
|
||||||
|
return @"";
|
||||||
|
}
|
||||||
NSArray *list = (NSArray *)dataValue;
|
NSArray *list = (NSArray *)dataValue;
|
||||||
|
|
||||||
NSMutableArray<NSString *> *segments = [NSMutableArray array];
|
NSMutableArray<NSString *> *segments = [NSMutableArray array];
|
||||||
|
|
||||||
[list enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
[list enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx,
|
||||||
|
BOOL *_Nonnull stop) {
|
||||||
NSString *payload = nil;
|
NSString *payload = nil;
|
||||||
|
|
||||||
if ([obj isKindOfClass:[NSDictionary class]]) {
|
if ([obj isKindOfClass:[NSDictionary class]]) {
|
||||||
@@ -490,19 +649,24 @@
|
|||||||
payload = (NSString *)obj;
|
payload = (NSString *)obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
payload = [payload stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
payload = [payload
|
||||||
|
stringByTrimmingCharactersInSet:[NSCharacterSet
|
||||||
|
whitespaceAndNewlineCharacterSet]];
|
||||||
if (payload.length > 0) {
|
if (payload.length > 0) {
|
||||||
// 每一个 payload 就是一段
|
// 每一个 payload 就是一段
|
||||||
[segments addObject:payload];
|
[segments addObject:payload];
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
if (segments.count == 0) { return @""; }
|
if (segments.count == 0) {
|
||||||
|
return @"";
|
||||||
|
}
|
||||||
|
|
||||||
// 用 \t 拼起来,KBStreamTextView 会按 \t 拆成多个 label
|
// 用 \t 拼起来,KBStreamTextView 会按 \t 拆成多个 label
|
||||||
NSMutableString *result = [NSMutableString string];
|
NSMutableString *result = [NSMutableString string];
|
||||||
|
|
||||||
[segments enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
[segments enumerateObjectsUsingBlock:^(NSString *_Nonnull obj, NSUInteger idx,
|
||||||
|
BOOL *_Nonnull stop) {
|
||||||
// 每段前面加一个 \t,保证是新的一段
|
// 每段前面加一个 \t,保证是新的一段
|
||||||
[result appendFormat:@"\t%@", obj];
|
[result appendFormat:@"\t%@", obj];
|
||||||
}];
|
}];
|
||||||
@@ -510,12 +674,15 @@
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (NSString *)kb_pendingSplitSuffixForString:(NSString *)text {
|
- (NSString *)kb_pendingSplitSuffixForString:(NSString *)text {
|
||||||
static NSString *const token = @"<SPLIT>";
|
static NSString *const token = @"<SPLIT>";
|
||||||
if (text.length == 0) { return @""; }
|
if (text.length == 0) {
|
||||||
|
return @"";
|
||||||
|
}
|
||||||
NSUInteger tokenLen = token.length;
|
NSUInteger tokenLen = token.length;
|
||||||
if (tokenLen <= 1) { return @""; }
|
if (tokenLen <= 1) {
|
||||||
|
return @"";
|
||||||
|
}
|
||||||
NSUInteger maxLen = MIN(tokenLen - 1, text.length);
|
NSUInteger maxLen = MIN(tokenLen - 1, text.length);
|
||||||
for (NSUInteger len = maxLen; len > 0; len--) {
|
for (NSUInteger len = maxLen; len > 0; len--) {
|
||||||
NSString *suffix = [text substringFromIndex:text.length - len];
|
NSString *suffix = [text substringFromIndex:text.length - len];
|
||||||
@@ -533,16 +700,20 @@
|
|||||||
/// - 已将 `<SPLIT>` 转换为 `\t` 并去掉多余换行
|
/// - 已将 `<SPLIT>` 转换为 `\t` 并去掉多余换行
|
||||||
/// - 这里仅负责附加到视图与标记首段状态,避免 UI 抖动
|
/// - 这里仅负责附加到视图与标记首段状态,避免 UI 抖动
|
||||||
- (void)kb_appendChunkToStreamView:(NSString *)chunk {
|
- (void)kb_appendChunkToStreamView:(NSString *)chunk {
|
||||||
if (chunk.length == 0) return;
|
if (chunk.length == 0)
|
||||||
|
return;
|
||||||
// 第一次有数据才创建 overlay,并取消 cell 上的小菊花
|
// 第一次有数据才创建 overlay,并取消 cell 上的小菊花
|
||||||
if (!self.streamOverlay) {
|
if (!self.streamOverlay) {
|
||||||
[self kb_showStreamTextViewIfNeededWithTitle:self.loadingTagTitle ?: @""];
|
[self kb_showStreamTextViewIfNeededWithTitle:self.loadingTagTitle ?: @""];
|
||||||
if (self.loadingTagIndex) {
|
if (self.loadingTagIndex) {
|
||||||
[self.tagListView setLoading:NO atIndex:self.loadingTagIndex.integerValue];
|
[self.tagListView setLoading:NO
|
||||||
self.loadingTagIndex = nil; self.loadingTagTitle = nil;
|
atIndex:self.loadingTagIndex.integerValue];
|
||||||
|
self.loadingTagIndex = nil;
|
||||||
|
self.loadingTagTitle = nil;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!self.streamOverlay) return;
|
if (!self.streamOverlay)
|
||||||
|
return;
|
||||||
[self.streamOverlay appendChunk:chunk];
|
[self.streamOverlay appendChunk:chunk];
|
||||||
self.streamHasOutput = YES;
|
self.streamHasOutput = YES;
|
||||||
}
|
}
|
||||||
@@ -554,20 +725,24 @@
|
|||||||
if (text.length > 0) {
|
if (text.length > 0) {
|
||||||
NSString *displayText = text;
|
NSString *displayText = text;
|
||||||
if (displayText.length > 30) {
|
if (displayText.length > 30) {
|
||||||
displayText = [[displayText substringToIndex:30] stringByAppendingString:@"…"];
|
displayText =
|
||||||
|
[[displayText substringToIndex:30] stringByAppendingString:@"…"];
|
||||||
}
|
}
|
||||||
[self.pasteView.pasBtn setImage:nil forState:UIControlStateNormal];
|
[self.pasteView.pasBtn setImage:nil forState:UIControlStateNormal];
|
||||||
[self.pasteView.pasBtn setTitle:displayText forState:UIControlStateNormal];
|
[self.pasteView.pasBtn setTitle:displayText forState:UIControlStateNormal];
|
||||||
} else {
|
} else {
|
||||||
UIImage *img = [UIImage imageNamed:@"kb_zt_icon"];
|
UIImage *img = [UIImage imageNamed:@"kb_zt_icon"];
|
||||||
[self.pasteView.pasBtn setImage:img forState:UIControlStateNormal];
|
[self.pasteView.pasBtn setImage:img forState:UIControlStateNormal];
|
||||||
[self.pasteView.pasBtn setTitle:KBLocalized(@" Paste Ta's Words") forState:UIControlStateNormal];
|
[self.pasteView.pasBtn setTitle:KBLocalized(@" Paste Ta's Words")
|
||||||
|
forState:UIControlStateNormal];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - KBFunctionTagListViewDelegate
|
#pragma mark - KBFunctionTagListViewDelegate
|
||||||
|
|
||||||
- (void)tagListView:(KBFunctionTagListView *)view didSelectIndex:(NSInteger)index title:(NSString *)title {
|
- (void)tagListView:(KBFunctionTagListView *)view
|
||||||
|
didSelectIndex:(NSInteger)index
|
||||||
|
title:(NSString *)title {
|
||||||
// 1) 先判断权限:未开启“完全访问”则走引导逻辑
|
// 1) 先判断权限:未开启“完全访问”则走引导逻辑
|
||||||
if (![[KBFullAccessManager shared] hasFullAccess]) {
|
if (![[KBFullAccessManager shared] hasFullAccess]) {
|
||||||
// 未开启完全访问:保持原有引导路径
|
// 未开启完全访问:保持原有引导路径
|
||||||
@@ -576,38 +751,46 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) 权限没问题,再判断是否登录:未登录 -> 直接拉起主 App,由主 App 负责完成登录
|
// 2) 权限没问题,再判断是否登录:未登录 -> 直接拉起主 App,由主 App
|
||||||
|
// 负责完成登录
|
||||||
if (!KBAuthManager.shared.isLoggedIn) {
|
if (!KBAuthManager.shared.isLoggedIn) {
|
||||||
|
|
||||||
|
|
||||||
UIInputViewController *ivc = KBFindInputViewController(self);
|
UIInputViewController *ivc = KBFindInputViewController(self);
|
||||||
|
|
||||||
NSString *schemeStr = [NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME];
|
NSString *schemeStr =
|
||||||
|
[NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME];
|
||||||
NSURL *scheme = [NSURL URLWithString:schemeStr];
|
NSURL *scheme = [NSURL URLWithString:schemeStr];
|
||||||
// 从当前视图作为起点,通过响应链找到 UIApplication 再调起主 App
|
// 从当前视图作为起点,通过响应链找到 UIApplication 再调起主 App
|
||||||
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:ivc.view];
|
BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:ivc.view];
|
||||||
return;
|
return;
|
||||||
// if (!ivc) return;
|
// if (!ivc) return;
|
||||||
// NSString *encodedTitle = [title stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]] ?: @"";
|
// NSString *encodedTitle = [title
|
||||||
// NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=functionView&index=%ld&title=%@", KB_UL_LOGIN, (long)index, encodedTitle]];
|
// stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet
|
||||||
// if (!ul) return;
|
// URLQueryAllowedCharacterSet]] ?: @""; NSURL *ul = [NSURL
|
||||||
|
// URLWithString:[NSString
|
||||||
|
// stringWithFormat:@"%@?src=functionView&index=%ld&title=%@",
|
||||||
|
// KB_UL_LOGIN, (long)index, encodedTitle]]; if (!ul) return;
|
||||||
// // 发起 UL,不依赖 ok 结果
|
// // 发起 UL,不依赖 ok 结果
|
||||||
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 *
|
||||||
// [ivc.extensionContext openURL:ul completionHandler:^(__unused BOOL ok) {}];
|
// NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||||
|
// [ivc.extensionContext openURL:ul completionHandler:^(__unused
|
||||||
|
// BOOL ok) {}];
|
||||||
// });
|
// });
|
||||||
// // 双路兜底:500ms 内未收到主 App 确认,则回退到自定义 Scheme(通过宿主 UIApplication 打开)
|
// // 双路兜底:500ms 内未收到主 App 确认,则回退到自定义
|
||||||
// self.kb_ulHandledFlag = NO;
|
// Scheme(通过宿主 UIApplication 打开) self.kb_ulHandledFlag = NO;
|
||||||
// NSUInteger token = ++self.kb_ulSeq;
|
// NSUInteger token = ++self.kb_ulSeq;
|
||||||
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 *
|
||||||
|
// NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||||
// if (token != self.kb_ulSeq) return; // 已有新请求覆盖
|
// if (token != self.kb_ulSeq) return; // 已有新请求覆盖
|
||||||
// if (self.kb_ulHandledFlag) return; // 主 App 已确认处理
|
// if (self.kb_ulHandledFlag) return; // 主 App 已确认处理
|
||||||
// NSURL *scheme = [NSURL URLWithString:[NSString stringWithFormat:@"%@://login?src=functionView&index=%ld&title=%@", KB_APP_SCHEME, (long)index, encodedTitle]];
|
// NSURL *scheme = [NSURL URLWithString:[NSString
|
||||||
// if (!scheme) return;
|
// stringWithFormat:@"%@://login?src=functionView&index=%ld&title=%@",
|
||||||
// UIResponder *start = ivc.view ?: (UIResponder *)self;
|
// KB_APP_SCHEME, (long)index, encodedTitle]]; if (!scheme)
|
||||||
|
// return; UIResponder *start = ivc.view ?: (UIResponder *)self;
|
||||||
// // 让键盘失去焦点
|
// // 让键盘失去焦点
|
||||||
// [ivc dismissKeyboard];
|
// [ivc dismissKeyboard];
|
||||||
// BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:start];
|
// BOOL ok = [KBHostAppLauncher openHostAppURL:scheme
|
||||||
// if (!ok) {
|
// fromResponder:start]; if (!ok) {
|
||||||
// [KBHUD showInfo:KBLocalized(@"请切换到主App完成登录")];
|
// [KBHUD showInfo:KBLocalized(@"请切换到主App完成登录")];
|
||||||
// }else{
|
// }else{
|
||||||
//
|
//
|
||||||
@@ -615,7 +798,8 @@
|
|||||||
// });
|
// });
|
||||||
// return;
|
// return;
|
||||||
}
|
}
|
||||||
BOOL hasPasteText = ![self.pasteView.pasBtn.currentTitle isEqualToString:KBLocalized(@" Paste Ta's Words")];
|
BOOL hasPasteText = ![self.pasteView.pasBtn.currentTitle
|
||||||
|
isEqualToString:KBLocalized(@" Paste Ta's Words")];
|
||||||
// BOOL hasPasteText = (self.pasteView.pasBtn.imageView.image == nil);
|
// BOOL hasPasteText = (self.pasteView.pasBtn.imageView.image == nil);
|
||||||
if (!hasPasteText) {
|
if (!hasPasteText) {
|
||||||
[KBHUD showInfo:KBLocalized(@"Please copy the text first")];
|
[KBHUD showInfo:KBLocalized(@"Please copy the text first")];
|
||||||
@@ -631,15 +815,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Darwin 回调:主 App 已处理 UL
|
// Darwin 回调:主 App 已处理 UL
|
||||||
static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
|
static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer,
|
||||||
|
CFStringRef name, const void *object,
|
||||||
|
CFDictionaryRef userInfo) {
|
||||||
KBFunctionView *self_ = (__bridge KBFunctionView *)observer;
|
KBFunctionView *self_ = (__bridge KBFunctionView *)observer;
|
||||||
if (!self_) return;
|
if (!self_)
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{ self_.kb_ulHandledFlag = YES; });
|
return;
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
self_.kb_ulHandledFlag = YES;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户点击功能标签:优先 UL 拉起主App,失败再 Scheme;两次都失败则提示开启完全访问。
|
// 用户点击功能标签:优先 UL 拉起主App,失败再
|
||||||
// 若已开启“完全访问”,则直接在键盘侧创建 KBStreamTextView,并在其右上角提供删除按钮关闭。
|
// Scheme;两次都失败则提示开启完全访问。 若已开启“完全访问”,则直接在键盘侧创建
|
||||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
// KBStreamTextView,并在其右上角提供删除按钮关闭。
|
||||||
|
- (void)collectionView:(UICollectionView *)collectionView
|
||||||
|
didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
// 点击上报已下沉到 KBFunctionTagListView(保证能拿到人设 id/name)
|
// 点击上报已下沉到 KBFunctionTagListView(保证能拿到人设 id/name)
|
||||||
// 权限全部打开(键盘已启用 + 完全访问)。在扩展进程中仅需判断“完全访问”。
|
// 权限全部打开(键盘已启用 + 完全访问)。在扩展进程中仅需判断“完全访问”。
|
||||||
if ([[KBFullAccessManager shared] hasFullAccess]) {
|
if ([[KBFullAccessManager shared] hasFullAccess]) {
|
||||||
@@ -651,39 +842,61 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
|
|||||||
[KBHUD showInfo:KBLocalized(@"处理中…")];
|
[KBHUD showInfo:KBLocalized(@"处理中…")];
|
||||||
|
|
||||||
UIInputViewController *ivc = KBFindInputViewController(self);
|
UIInputViewController *ivc = KBFindInputViewController(self);
|
||||||
if (!ivc) return;
|
if (!ivc)
|
||||||
|
return;
|
||||||
|
|
||||||
NSString *title = self.modelArray[indexPath.item].characterName;
|
NSString *title = self.modelArray[indexPath.item].characterName;
|
||||||
NSString *encodedTitle = [title stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]] ?: @"";
|
NSString *encodedTitle =
|
||||||
|
[title stringByAddingPercentEncodingWithAllowedCharacters:
|
||||||
|
[NSCharacterSet URLQueryAllowedCharacterSet]]
|
||||||
|
?: @"";
|
||||||
|
|
||||||
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=functionView&index=%ld&title=%@", KB_UL_LOGIN, (long)indexPath.item, encodedTitle]];
|
NSURL *ul = [NSURL
|
||||||
if (!ul) return;
|
URLWithString:
|
||||||
|
[NSString stringWithFormat:@"%@?src=functionView&index=%ld&title=%@",
|
||||||
|
KB_UL_LOGIN, (long)indexPath.item,
|
||||||
|
encodedTitle]];
|
||||||
|
if (!ul)
|
||||||
|
return;
|
||||||
|
|
||||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
dispatch_after(
|
||||||
|
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)),
|
||||||
|
dispatch_get_main_queue(), ^{
|
||||||
// 先尝试通过 extensionContext 打开 UL
|
// 先尝试通过 extensionContext 打开 UL
|
||||||
[ivc.extensionContext openURL:ul completionHandler:^(BOOL ok) {
|
[ivc.extensionContext
|
||||||
|
openURL:ul
|
||||||
|
completionHandler:^(BOOL ok) {
|
||||||
if (ok) {
|
if (ok) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// UL 失败时,再通过宿主 UIApplication + 自定义 Scheme 兜底
|
// UL 失败时,再通过宿主 UIApplication + 自定义 Scheme 兜底
|
||||||
NSURL *scheme = [NSURL URLWithString:[NSString stringWithFormat:@"%@@//login?src=functionView&index=%ld&title=%@", KB_APP_SCHEME, (long)indexPath.item, encodedTitle]];
|
NSURL *scheme = [NSURL
|
||||||
|
URLWithString:
|
||||||
|
[NSString
|
||||||
|
stringWithFormat:
|
||||||
|
@"%@@//login?src=functionView&index=%ld&title=%@",
|
||||||
|
KB_APP_SCHEME, (long)indexPath.item,
|
||||||
|
encodedTitle]];
|
||||||
UIResponder *start = ivc.view ?: (UIResponder *)self;
|
UIResponder *start = ivc.view ?: (UIResponder *)self;
|
||||||
BOOL ok2 = [KBHostAppLauncher openHostAppURL:scheme fromResponder:start];
|
BOOL ok2 = [KBHostAppLauncher openHostAppURL:scheme
|
||||||
|
fromResponder:start];
|
||||||
if (!ok2) {
|
if (!ok2) {
|
||||||
// 两条路都失败:大概率未开完全访问或宿主拦截。统一交由 Manager 引导。
|
// 两条路都失败:大概率未开完全访问或宿主拦截。统一交由 Manager
|
||||||
|
// 引导。
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[[KBFullAccessManager shared] ensureFullAccessOrGuideInView:self];
|
[[KBFullAccessManager shared]
|
||||||
|
ensureFullAccessOrGuideInView:self];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Button Actions
|
#pragma mark - Button Actions
|
||||||
|
|
||||||
- (void)onTapPaste {
|
- (void)onTapPaste {
|
||||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_paste_btn"
|
[[KBMaiPointReporter sharedReporter]
|
||||||
|
reportClickWithEventName:@"click_keyboard_function_paste_btn"
|
||||||
pageId:@"keyboard_function_panel"
|
pageId:@"keyboard_function_panel"
|
||||||
elementId:@"paste_btn"
|
elementId:@"paste_btn"
|
||||||
extra:nil
|
extra:nil
|
||||||
@@ -691,8 +904,9 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
|
|||||||
// 用户点击“粘贴”时才读取剪贴板:
|
// 用户点击“粘贴”时才读取剪贴板:
|
||||||
// - iOS16+ 会在跨 App 首次读取时自动弹出系统权限弹窗;
|
// - iOS16+ 会在跨 App 首次读取时自动弹出系统权限弹窗;
|
||||||
// - iOS15 及以下不会弹窗,直接返回内容;
|
// - iOS15 及以下不会弹窗,直接返回内容;
|
||||||
// 注意:不要在非用户触发的时机主动读取(如 viewDidLoad),否则会造成“立刻弹窗”的体验。
|
// 注意:不要在非用户触发的时机主动读取(如
|
||||||
// 权限全部打开(键盘已启用 + 完全访问)。在扩展进程中仅需判断“完全访问”。
|
// viewDidLoad),否则会造成“立刻弹窗”的体验。 权限全部打开(键盘已启用 +
|
||||||
|
// 完全访问)。在扩展进程中仅需判断“完全访问”。
|
||||||
if (![[KBFullAccessManager shared] hasFullAccess]) {
|
if (![[KBFullAccessManager shared] hasFullAccess]) {
|
||||||
// 未开启完全访问:保持原有引导路径
|
// 未开启完全访问:保持原有引导路径
|
||||||
[[KBFullAccessManager shared] ensureFullAccessOrGuideInView:self];
|
[[KBFullAccessManager shared] ensureFullAccessOrGuideInView:self];
|
||||||
@@ -731,20 +945,31 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
|
|||||||
// 禁用自动读取剪贴板,避免触发系统“允许粘贴”弹窗
|
// 禁用自动读取剪贴板,避免触发系统“允许粘贴”弹窗
|
||||||
return;
|
return;
|
||||||
// 未开启“完全访问”时不做自动读取,避免宿主/系统拒绝并刷错误日志
|
// 未开启“完全访问”时不做自动读取,避免宿主/系统拒绝并刷错误日志
|
||||||
if (![[KBFullAccessManager shared] hasFullAccess]) return;
|
if (![[KBFullAccessManager shared] hasFullAccess])
|
||||||
if (self.pasteboardTimer) return;
|
return;
|
||||||
KBWeakSelf
|
if (self.pasteboardTimer)
|
||||||
self.pasteboardTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 repeats:YES block:^(NSTimer * _Nonnull timer) {
|
return;
|
||||||
__strong typeof(weakSelf) self = weakSelf; if (!self) return;
|
KBWeakSelf self.pasteboardTimer = [NSTimer
|
||||||
UIPasteboard *pb = [UIPasteboard generalPasteboard];
|
scheduledTimerWithTimeInterval:0.5
|
||||||
|
repeats:YES
|
||||||
|
block:^(NSTimer *_Nonnull timer) {
|
||||||
|
__strong typeof(weakSelf) self = weakSelf;
|
||||||
|
if (!self)
|
||||||
|
return;
|
||||||
|
UIPasteboard *pb =
|
||||||
|
[UIPasteboard generalPasteboard];
|
||||||
NSInteger cc = pb.changeCount;
|
NSInteger cc = pb.changeCount;
|
||||||
if (cc <= self.lastHandledPBCount) return; // 没有新复制
|
if (cc <= self.lastHandledPBCount)
|
||||||
self.lastHandledPBCount = cc; // 标记已处理,避免重复
|
return; // 没有新复制
|
||||||
|
self.lastHandledPBCount =
|
||||||
|
cc; // 标记已处理,避免重复
|
||||||
|
|
||||||
// 实际读取触发系统弹窗(iOS16+)
|
// 实际读取触发系统弹窗(iOS16+)
|
||||||
NSString *text = pb.string;
|
NSString *text = pb.string;
|
||||||
// 有文字 -> 仅展示文字;无文字/非文本 -> 恢复图标 + 原占位文案
|
// 有文字 -> 仅展示文字;无文字/非文本 ->
|
||||||
[self kb_updatePasteButtonWithDisplayText:text];
|
// 恢复图标 + 原占位文案
|
||||||
|
[self
|
||||||
|
kb_updatePasteButtonWithDisplayText:text];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -777,12 +1002,15 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)kb_fullAccessChanged {
|
- (void)kb_fullAccessChanged {
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{ [self kb_refreshPasteboardMonitor]; });
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self kb_refreshPasteboardMonitor];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onTapDelete {
|
- (void)onTapDelete {
|
||||||
NSLog(@"点击:删除");
|
NSLog(@"点击:删除");
|
||||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_delete_btn"
|
[[KBMaiPointReporter sharedReporter]
|
||||||
|
reportClickWithEventName:@"click_keyboard_function_delete_btn"
|
||||||
pageId:@"keyboard_function_panel"
|
pageId:@"keyboard_function_panel"
|
||||||
elementId:@"delete_btn"
|
elementId:@"delete_btn"
|
||||||
extra:nil
|
extra:nil
|
||||||
@@ -790,14 +1018,18 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
|
|||||||
UIInputViewController *ivc = KBFindInputViewController(self);
|
UIInputViewController *ivc = KBFindInputViewController(self);
|
||||||
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
|
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
|
||||||
[[KBInputBufferManager shared] refreshFromProxyIfPossible:proxy];
|
[[KBInputBufferManager shared] refreshFromProxyIfPossible:proxy];
|
||||||
[[KBInputBufferManager shared] prepareSnapshotForDeleteWithContextBefore:proxy.documentContextBeforeInput
|
[[KBInputBufferManager shared]
|
||||||
after:proxy.documentContextAfterInput];
|
prepareSnapshotForDeleteWithContextBefore:proxy.documentContextBeforeInput
|
||||||
[[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:proxy count:1];
|
after:proxy
|
||||||
|
.documentContextAfterInput];
|
||||||
|
[[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:proxy
|
||||||
|
count:1];
|
||||||
[[KBInputBufferManager shared] applyHoldDeleteCount:1];
|
[[KBInputBufferManager shared] applyHoldDeleteCount:1];
|
||||||
}
|
}
|
||||||
- (void)onTapClear {
|
- (void)onTapClear {
|
||||||
NSLog(@"点击:清空");
|
NSLog(@"点击:清空");
|
||||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_clear_btn"
|
[[KBMaiPointReporter sharedReporter]
|
||||||
|
reportClickWithEventName:@"click_keyboard_function_clear_btn"
|
||||||
pageId:@"keyboard_function_panel"
|
pageId:@"keyboard_function_panel"
|
||||||
elementId:@"clear_btn"
|
elementId:@"clear_btn"
|
||||||
extra:nil
|
extra:nil
|
||||||
@@ -807,7 +1039,8 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
|
|||||||
|
|
||||||
- (void)onTapSend {
|
- (void)onTapSend {
|
||||||
NSLog(@"点击:发送");
|
NSLog(@"点击:发送");
|
||||||
[[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_send_btn"
|
[[KBMaiPointReporter sharedReporter]
|
||||||
|
reportClickWithEventName:@"click_keyboard_function_send_btn"
|
||||||
pageId:@"keyboard_function_panel"
|
pageId:@"keyboard_function_panel"
|
||||||
elementId:@"send_btn"
|
elementId:@"send_btn"
|
||||||
extra:nil
|
extra:nil
|
||||||
@@ -832,16 +1065,20 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
|
|||||||
|
|
||||||
#pragma mark - KBFunctionBarViewDelegate
|
#pragma mark - KBFunctionBarViewDelegate
|
||||||
|
|
||||||
- (void)functionBarView:(KBFunctionBarView *)bar didTapLeftAtIndex:(NSInteger)index {
|
- (void)functionBarView:(KBFunctionBarView *)bar
|
||||||
|
didTapLeftAtIndex:(NSInteger)index {
|
||||||
// 将事件继续透传给上层(如键盘控制器),用于切换界面或其它业务
|
// 将事件继续透传给上层(如键盘控制器),用于切换界面或其它业务
|
||||||
if ([self.delegate respondsToSelector:@selector(functionView:didTapToolActionAtIndex:)]) {
|
if ([self.delegate respondsToSelector:@selector(functionView:
|
||||||
|
didTapToolActionAtIndex:)]) {
|
||||||
[self.delegate functionView:self didTapToolActionAtIndex:index];
|
[self.delegate functionView:self didTapToolActionAtIndex:index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)functionBarView:(KBFunctionBarView *)bar didTapRightAtIndex:(NSInteger)index {
|
- (void)functionBarView:(KBFunctionBarView *)bar
|
||||||
|
didTapRightAtIndex:(NSInteger)index {
|
||||||
// 右侧按钮点击,如收藏/宫格等,按需继续向外抛出(这里暂不定义单独协议方法,可在此内部处理或扩展)
|
// 右侧按钮点击,如收藏/宫格等,按需继续向外抛出(这里暂不定义单独协议方法,可在此内部处理或扩展)
|
||||||
if ([self.delegate respondsToSelector:@selector(functionView:didRightTapToolActionAtIndex:)]) {
|
if ([self.delegate respondsToSelector:@selector(functionView:
|
||||||
|
didRightTapToolActionAtIndex:)]) {
|
||||||
[self.delegate functionView:self didRightTapToolActionAtIndex:index];
|
[self.delegate functionView:self didRightTapToolActionAtIndex:index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -869,7 +1106,8 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
|
|||||||
return _rightButtonContainer;
|
return _rightButtonContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (UIButton *)buildRightButtonWithTitle:(NSString *)title color:(UIColor *)color {
|
- (UIButton *)buildRightButtonWithTitle:(NSString *)title
|
||||||
|
color:(UIColor *)color {
|
||||||
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
|
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||||
btn.backgroundColor = color;
|
btn.backgroundColor = color;
|
||||||
btn.layer.cornerRadius = 8.0;
|
btn.layer.cornerRadius = 8.0;
|
||||||
@@ -882,8 +1120,15 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
|
|||||||
|
|
||||||
- (UIButton *)pasteButtonInternal {
|
- (UIButton *)pasteButtonInternal {
|
||||||
if (!_pasteButtonInternal) {
|
if (!_pasteButtonInternal) {
|
||||||
_pasteButtonInternal = [self buildRightButtonWithTitle:KBLocalized(@"Paste") color:[UIColor colorWithRed:0.13 green:0.73 blue:0.60 alpha:1.0]];
|
_pasteButtonInternal =
|
||||||
[_pasteButtonInternal addTarget:self action:@selector(onTapPaste) forControlEvents:UIControlEventTouchUpInside];
|
[self buildRightButtonWithTitle:KBLocalized(@"Paste")
|
||||||
|
color:[UIColor colorWithRed:0.13
|
||||||
|
green:0.73
|
||||||
|
blue:0.60
|
||||||
|
alpha:1.0]];
|
||||||
|
[_pasteButtonInternal addTarget:self
|
||||||
|
action:@selector(onTapPaste)
|
||||||
|
forControlEvents:UIControlEventTouchUpInside];
|
||||||
}
|
}
|
||||||
return _pasteButtonInternal;
|
return _pasteButtonInternal;
|
||||||
}
|
}
|
||||||
@@ -894,10 +1139,14 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
|
|||||||
_deleteButtonInternal.backgroundColor = [UIColor colorWithHex:0xB9BDC8];
|
_deleteButtonInternal.backgroundColor = [UIColor colorWithHex:0xB9BDC8];
|
||||||
_deleteButtonInternal.layer.cornerRadius = 8.0;
|
_deleteButtonInternal.layer.cornerRadius = 8.0;
|
||||||
_deleteButtonInternal.layer.masksToBounds = YES;
|
_deleteButtonInternal.layer.masksToBounds = YES;
|
||||||
[_deleteButtonInternal setImage:[UIImage imageNamed:@"kb_del_icon"] forState:UIControlStateNormal];
|
[_deleteButtonInternal setImage:[UIImage imageNamed:@"kb_del_icon"]
|
||||||
|
forState:UIControlStateNormal];
|
||||||
|
|
||||||
[_deleteButtonInternal addTarget:self action:@selector(onTapDelete) forControlEvents:UIControlEventTouchUpInside];
|
[_deleteButtonInternal addTarget:self
|
||||||
[self.backspaceHandler bindDeleteButton:_deleteButtonInternal showClearLabel:NO];
|
action:@selector(onTapDelete)
|
||||||
|
forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
[self.backspaceHandler bindDeleteButton:_deleteButtonInternal
|
||||||
|
showClearLabel:NO];
|
||||||
}
|
}
|
||||||
return _deleteButtonInternal;
|
return _deleteButtonInternal;
|
||||||
}
|
}
|
||||||
@@ -909,32 +1158,53 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
|
|||||||
_clearButtonInternal.layer.cornerRadius = 8.0;
|
_clearButtonInternal.layer.cornerRadius = 8.0;
|
||||||
_clearButtonInternal.layer.masksToBounds = YES;
|
_clearButtonInternal.layer.masksToBounds = YES;
|
||||||
_clearButtonInternal.titleLabel.font = [KBFont medium:13];
|
_clearButtonInternal.titleLabel.font = [KBFont medium:13];
|
||||||
[_clearButtonInternal setTitle:KBLocalized(@"Clear") forState:UIControlStateNormal];
|
[_clearButtonInternal setTitle:KBLocalized(@"Clear")
|
||||||
[_clearButtonInternal setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
|
forState:UIControlStateNormal];
|
||||||
[_clearButtonInternal addTarget:self action:@selector(onTapClear) forControlEvents:UIControlEventTouchUpInside];
|
[_clearButtonInternal setTitleColor:[UIColor blackColor]
|
||||||
|
forState:UIControlStateNormal];
|
||||||
|
[_clearButtonInternal addTarget:self
|
||||||
|
action:@selector(onTapClear)
|
||||||
|
forControlEvents:UIControlEventTouchUpInside];
|
||||||
}
|
}
|
||||||
return _clearButtonInternal;
|
return _clearButtonInternal;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (UIButton *)sendButtonInternal {
|
- (UIButton *)sendButtonInternal {
|
||||||
if (!_sendButtonInternal) {
|
if (!_sendButtonInternal) {
|
||||||
_sendButtonInternal = [self buildRightButtonWithTitle:KBLocalized(@"Send") color:[UIColor colorWithHex:0x02BEAC]];
|
_sendButtonInternal =
|
||||||
[_sendButtonInternal addTarget:self action:@selector(onTapSend) forControlEvents:UIControlEventTouchUpInside];
|
[self buildRightButtonWithTitle:KBLocalized(@"Send")
|
||||||
|
color:[UIColor colorWithHex:0x02BEAC]];
|
||||||
|
[_sendButtonInternal addTarget:self
|
||||||
|
action:@selector(onTapSend)
|
||||||
|
forControlEvents:UIControlEventTouchUpInside];
|
||||||
}
|
}
|
||||||
return _sendButtonInternal;
|
return _sendButtonInternal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Expose
|
#pragma mark - Expose
|
||||||
|
|
||||||
- (UICollectionView *)collectionView { return self.tagListView.collectionView; }
|
- (UICollectionView *)collectionView {
|
||||||
|
return self.tagListView.collectionView;
|
||||||
|
}
|
||||||
//- (NSArray<NSString *> *)items { return self.itemsInternal; }
|
//- (NSArray<NSString *> *)items { return self.itemsInternal; }
|
||||||
- (KBFunctionBarView *)barView { return self.barViewInternal; }
|
- (KBFunctionBarView *)barView {
|
||||||
- (KBFunctionPasteView *)pasteView { return self.pasteViewInternal; }
|
return self.barViewInternal;
|
||||||
- (UIButton *)pasteButton { return self.pasteButtonInternal; }
|
}
|
||||||
- (UIButton *)deleteButton { return self.deleteButtonInternal; }
|
- (KBFunctionPasteView *)pasteView {
|
||||||
- (UIButton *)clearButton { return self.clearButtonInternal; }
|
return self.pasteViewInternal;
|
||||||
- (UIButton *)sendButton { return self.sendButtonInternal; }
|
}
|
||||||
|
- (UIButton *)pasteButton {
|
||||||
|
return self.pasteButtonInternal;
|
||||||
|
}
|
||||||
|
- (UIButton *)deleteButton {
|
||||||
|
return self.deleteButtonInternal;
|
||||||
|
}
|
||||||
|
- (UIButton *)clearButton {
|
||||||
|
return self.clearButtonInternal;
|
||||||
|
}
|
||||||
|
- (UIButton *)sendButton {
|
||||||
|
return self.sendButtonInternal;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Find Owner Controller
|
#pragma mark - Find Owner Controller
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user