添加键盘背景
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
#import "Masonry.h"
|
#import "Masonry.h"
|
||||||
#import "KBAuthManager.h"
|
#import "KBAuthManager.h"
|
||||||
#import "KBFullAccessManager.h"
|
#import "KBFullAccessManager.h"
|
||||||
|
#import "KBSkinManager.h"
|
||||||
|
|
||||||
static CGFloat KEYBOARDHEIGHT = 256 + 20;
|
static CGFloat KEYBOARDHEIGHT = 256 + 20;
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ static CGFloat KEYBOARDHEIGHT = 256 + 20;
|
|||||||
@property (nonatomic, strong) KBKeyBoardMainView *keyBoardMainView; // 功能面板视图(点击工具栏第0个时显示)
|
@property (nonatomic, strong) KBKeyBoardMainView *keyBoardMainView; // 功能面板视图(点击工具栏第0个时显示)
|
||||||
@property (nonatomic, strong) KBFunctionView *functionView; // 功能面板视图(点击工具栏第0个时显示)
|
@property (nonatomic, strong) KBFunctionView *functionView; // 功能面板视图(点击工具栏第0个时显示)
|
||||||
@property (nonatomic, strong) KBSettingView *settingView; // 设置页
|
@property (nonatomic, strong) KBSettingView *settingView; // 设置页
|
||||||
|
@property (nonatomic, strong) UIImageView *bgImageView; // 背景图(在底层)
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation KeyboardViewController
|
@implementation KeyboardViewController
|
||||||
@@ -40,12 +42,23 @@ static CGFloat KEYBOARDHEIGHT = 256 + 20;
|
|||||||
__unused id token = [[NSNotificationCenter defaultCenter] addObserverForName:KBFullAccessChangedNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(__unused NSNotification * _Nonnull note) {
|
__unused id token = [[NSNotificationCenter defaultCenter] addObserverForName:KBFullAccessChangedNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(__unused NSNotification * _Nonnull note) {
|
||||||
// 如需,可在此刷新与完全访问相关的 UI
|
// 如需,可在此刷新与完全访问相关的 UI
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
// 皮肤变化时,立即应用
|
||||||
|
__unused id token2 = [[NSNotificationCenter defaultCenter] addObserverForName:KBSkinDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(__unused NSNotification * _Nonnull note) {
|
||||||
|
[self kb_applyTheme];
|
||||||
|
}];
|
||||||
|
[self kb_applyTheme];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (void)setupUI {
|
- (void)setupUI {
|
||||||
// 固定键盘整体高度
|
// 固定键盘整体高度
|
||||||
[self.view.heightAnchor constraintEqualToConstant:KEYBOARDHEIGHT].active = YES;
|
[self.view.heightAnchor constraintEqualToConstant:KEYBOARDHEIGHT].active = YES;
|
||||||
|
// 背景图铺底
|
||||||
|
[self.view addSubview:self.bgImageView];
|
||||||
|
[self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.edges.equalTo(self.view);
|
||||||
|
}];
|
||||||
// 预置功能面板(默认隐藏),与键盘区域共享相同布局
|
// 预置功能面板(默认隐藏),与键盘区域共享相同布局
|
||||||
self.functionView.hidden = YES;
|
self.functionView.hidden = YES;
|
||||||
[self.view addSubview:self.functionView];
|
[self.view addSubview:self.functionView];
|
||||||
@@ -217,4 +230,40 @@ static CGFloat KEYBOARDHEIGHT = 256 + 20;
|
|||||||
__unused typeof(weakSelf) selfStrong = weakSelf;
|
__unused typeof(weakSelf) selfStrong = weakSelf;
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - Theme
|
||||||
|
|
||||||
|
- (void)kb_applyTheme {
|
||||||
|
KBSkinTheme *t = [KBSkinManager shared].current;
|
||||||
|
UIImage *img = [[KBSkinManager shared] currentBackgroundImage];
|
||||||
|
self.bgImageView.image = img;
|
||||||
|
BOOL hasImg = (img != nil);
|
||||||
|
self.view.backgroundColor = hasImg ? [UIColor clearColor] : t.keyboardBackground;
|
||||||
|
self.keyBoardMainView.backgroundColor = hasImg ? [UIColor clearColor] : t.keyboardBackground;
|
||||||
|
// 触发键区按主题重绘
|
||||||
|
if ([self.keyBoardMainView respondsToSelector:@selector(kb_applyTheme)]) {
|
||||||
|
// method declared in KBKeyBoardMainView.h
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||||
|
[self.keyBoardMainView performSelector:@selector(kb_applyTheme)];
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
}
|
||||||
|
if ([self.functionView respondsToSelector:@selector(kb_applyTheme)]) {
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||||
|
[self.functionView performSelector:@selector(kb_applyTheme)];
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Lazy
|
||||||
|
|
||||||
|
- (UIImageView *)bgImageView {
|
||||||
|
if (!_bgImageView) {
|
||||||
|
_bgImageView = [[UIImageView alloc] init];
|
||||||
|
_bgImageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||||
|
_bgImageView.clipsToBounds = YES;
|
||||||
|
}
|
||||||
|
return _bgImageView;
|
||||||
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
@property (nonatomic, strong, readonly) UIButton *clearButton; // 右侧-清空
|
@property (nonatomic, strong, readonly) UIButton *clearButton; // 右侧-清空
|
||||||
@property (nonatomic, strong, readonly) UIButton *sendButton; // 右侧-发送
|
@property (nonatomic, strong, readonly) UIButton *sendButton; // 右侧-发送
|
||||||
|
|
||||||
|
/// 应用当前皮肤(更新背景/强调色)
|
||||||
|
- (void)kb_applyTheme;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#import <MBProgressHUD.h>
|
#import <MBProgressHUD.h>
|
||||||
#import "KBFullAccessGuideView.h"
|
#import "KBFullAccessGuideView.h"
|
||||||
#import "KBFullAccessManager.h"
|
#import "KBFullAccessManager.h"
|
||||||
|
#import "KBSkinManager.h"
|
||||||
|
|
||||||
static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId";
|
static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId";
|
||||||
|
|
||||||
@@ -40,8 +41,8 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId";
|
|||||||
|
|
||||||
- (instancetype)initWithFrame:(CGRect)frame {
|
- (instancetype)initWithFrame:(CGRect)frame {
|
||||||
if (self = [super initWithFrame:frame]) {
|
if (self = [super initWithFrame:frame]) {
|
||||||
// 整体绿色背景(接近截图效果,项目可自行替换素材)
|
// 背景使用当前主题强调色
|
||||||
self.backgroundColor = [UIColor colorWithRed:0.77 green:0.93 blue:0.82 alpha:1.0];
|
[self kb_applyTheme];
|
||||||
|
|
||||||
[self setupUI];
|
[self setupUI];
|
||||||
[self reloadDemoData];
|
[self reloadDemoData];
|
||||||
@@ -52,6 +53,15 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId";
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - Theme
|
||||||
|
|
||||||
|
- (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];
|
||||||
|
BOOL hasImg = ([mgr currentBackgroundImage] != nil);
|
||||||
|
self.backgroundColor = hasImg ? [accent colorWithAlphaComponent:0.65] : accent;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
[self stopPasteboardMonitor];
|
[self stopPasteboardMonitor];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
@interface KBKeyBoardMainView : UIView
|
@interface KBKeyBoardMainView : UIView
|
||||||
@property (nonatomic, weak) id<KBKeyBoardMainViewDelegate> delegate;
|
@property (nonatomic, weak) id<KBKeyBoardMainViewDelegate> delegate;
|
||||||
|
|
||||||
|
/// 应用当前皮肤(会触发键区重载以应用按键颜色)
|
||||||
|
- (void)kb_applyTheme;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#import "KBFunctionView.h"
|
#import "KBFunctionView.h"
|
||||||
#import "KBKey.h"
|
#import "KBKey.h"
|
||||||
#import "Masonry.h"
|
#import "Masonry.h"
|
||||||
|
#import "KBSkinManager.h"
|
||||||
|
|
||||||
@interface KBKeyBoardMainView ()<KBToolBarDelegate, KBKeyboardViewDelegate>
|
@interface KBKeyBoardMainView ()<KBToolBarDelegate, KBKeyboardViewDelegate>
|
||||||
@property (nonatomic, strong) KBToolBar *topBar;
|
@property (nonatomic, strong) KBToolBar *topBar;
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
|
|
||||||
- (instancetype)initWithFrame:(CGRect)frame {
|
- (instancetype)initWithFrame:(CGRect)frame {
|
||||||
if (self = [super initWithFrame:frame]) {
|
if (self = [super initWithFrame:frame]) {
|
||||||
self.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0];
|
self.backgroundColor = [KBSkinManager shared].current.keyboardBackground;
|
||||||
// 顶部栏
|
// 顶部栏
|
||||||
self.topBar = [[KBToolBar alloc] init];
|
self.topBar = [[KBToolBar alloc] init];
|
||||||
self.topBar.delegate = self;
|
self.topBar.delegate = self;
|
||||||
@@ -114,5 +115,15 @@
|
|||||||
// 切换功能面板交由外部控制器处理(此处不再实现)
|
// 切换功能面板交由外部控制器处理(此处不再实现)
|
||||||
|
|
||||||
// 设置页展示改由 KeyboardViewController 统一处理
|
// 设置页展示改由 KeyboardViewController 统一处理
|
||||||
|
#pragma mark - Theme
|
||||||
|
|
||||||
|
- (void)kb_applyTheme {
|
||||||
|
KBSkinManager *mgr = [KBSkinManager shared];
|
||||||
|
BOOL hasImg = ([mgr currentBackgroundImage] != nil);
|
||||||
|
UIColor *bg = mgr.current.keyboardBackground;
|
||||||
|
self.backgroundColor = hasImg ? [UIColor clearColor] : bg;
|
||||||
|
self.keyboardView.backgroundColor = hasImg ? [UIColor clearColor] : bg;
|
||||||
|
[self.keyboardView reloadKeys];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#import "KBKeyButton.h"
|
#import "KBKeyButton.h"
|
||||||
#import "KBKey.h"
|
#import "KBKey.h"
|
||||||
|
#import "KBSkinManager.h"
|
||||||
|
|
||||||
@implementation KBKeyButton
|
@implementation KBKeyButton
|
||||||
|
|
||||||
@@ -16,10 +17,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)applyDefaultStyle {
|
- (void)applyDefaultStyle {
|
||||||
self.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightSemibold]; // 字体样式
|
self.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightSemibold];
|
||||||
[self setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
|
KBSkinTheme *t = [KBSkinManager shared].current;
|
||||||
[self setTitleColor:[UIColor blackColor] forState:UIControlStateHighlighted];
|
[self setTitleColor:t.keyTextColor forState:UIControlStateNormal];
|
||||||
self.backgroundColor = [UIColor whiteColor];
|
[self setTitleColor:t.keyTextColor forState:UIControlStateHighlighted];
|
||||||
|
self.backgroundColor = t.keyBackground;
|
||||||
self.layer.cornerRadius = 6.0; // 圆角
|
self.layer.cornerRadius = 6.0; // 圆角
|
||||||
self.layer.masksToBounds = NO;
|
self.layer.masksToBounds = NO;
|
||||||
self.layer.shadowColor = [UIColor colorWithWhite:0 alpha:0.1].CGColor; // 阴影效果
|
self.layer.shadowColor = [UIColor colorWithWhite:0 alpha:0.1].CGColor; // 阴影效果
|
||||||
@@ -46,10 +48,11 @@
|
|||||||
|
|
||||||
- (void)refreshStateAppearance {
|
- (void)refreshStateAppearance {
|
||||||
// 选中态用于 Shift/CapsLock 等特殊按键的高亮显示
|
// 选中态用于 Shift/CapsLock 等特殊按键的高亮显示
|
||||||
|
KBSkinTheme *t = [KBSkinManager shared].current;
|
||||||
if (self.isSelected) {
|
if (self.isSelected) {
|
||||||
self.backgroundColor = [UIColor colorWithWhite:0.85 alpha:1.0];
|
self.backgroundColor = t.keyHighlightBackground ?: t.keyBackground;
|
||||||
} else {
|
} else {
|
||||||
self.backgroundColor = [UIColor whiteColor];
|
self.backgroundColor = t.keyBackground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#import "KBKeyButton.h"
|
#import "KBKeyButton.h"
|
||||||
#import "KBKey.h"
|
#import "KBKey.h"
|
||||||
#import "KBResponderUtils.h" // 封装的响应链工具
|
#import "KBResponderUtils.h" // 封装的响应链工具
|
||||||
|
#import "KBSkinManager.h"
|
||||||
|
|
||||||
@interface KBKeyboardView ()
|
@interface KBKeyboardView ()
|
||||||
@property (nonatomic, strong) UIView *row1;
|
@property (nonatomic, strong) UIView *row1;
|
||||||
@@ -22,7 +23,7 @@
|
|||||||
|
|
||||||
- (instancetype)initWithFrame:(CGRect)frame {
|
- (instancetype)initWithFrame:(CGRect)frame {
|
||||||
if (self = [super initWithFrame:frame]) {
|
if (self = [super initWithFrame:frame]) {
|
||||||
self.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0];
|
self.backgroundColor = [KBSkinManager shared].current.keyboardBackground;
|
||||||
_layoutStyle = KBKeyboardLayoutStyleLetters;
|
_layoutStyle = KBKeyboardLayoutStyleLetters;
|
||||||
// 默认小写:与需求一致,初始不开启 Shift
|
// 默认小写:与需求一致,初始不开启 Shift
|
||||||
_shiftOn = NO;
|
_shiftOn = NO;
|
||||||
|
|||||||
58
Shared/KBSkinManager.h
Normal file
58
Shared/KBSkinManager.h
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
//
|
||||||
|
// KBSkinManager.h
|
||||||
|
// App & Keyboard Extension shared skin/theme manager.
|
||||||
|
//
|
||||||
|
// Stores a lightweight theme (colors, identifiers) to shared keychain so
|
||||||
|
// both targets see the same current skin. Cross-process updates are delivered
|
||||||
|
// via Darwin notification. Intended for immediate reflection in extension.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
extern NSString * const KBSkinDidChangeNotification; // in-process
|
||||||
|
extern NSString * const KBDarwinSkinChanged; // cross-process
|
||||||
|
|
||||||
|
/// Simple theme model (colors only; assets can be added later via App Group)
|
||||||
|
@interface KBSkinTheme : NSObject <NSSecureCoding>
|
||||||
|
@property (nonatomic, copy) NSString *skinId; // e.g. "mint"
|
||||||
|
@property (nonatomic, copy) NSString *name; // display name
|
||||||
|
@property (nonatomic, strong) UIColor *keyboardBackground;
|
||||||
|
@property (nonatomic, strong) UIColor *keyBackground;
|
||||||
|
@property (nonatomic, strong) UIColor *keyTextColor;
|
||||||
|
@property (nonatomic, strong) UIColor *keyHighlightBackground; // selected/highlighted
|
||||||
|
@property (nonatomic, strong) UIColor *accentColor; // function view accents
|
||||||
|
/// 可选:键盘背景图片的 PNG/JPEG 数据(若存在,优先显示图片)
|
||||||
|
@property (nonatomic, strong, nullable) NSData *backgroundImageData;
|
||||||
|
@end
|
||||||
|
|
||||||
|
/// Shared skin manager (Keychain Sharing based)
|
||||||
|
@interface KBSkinManager : NSObject
|
||||||
|
|
||||||
|
+ (instancetype)shared;
|
||||||
|
|
||||||
|
@property (atomic, strong, readonly) KBSkinTheme *current; // never nil (fallback to default)
|
||||||
|
|
||||||
|
/// Save theme from JSON dictionary (keys: id, name, background, key_bg, key_text, key_highlight, accent)
|
||||||
|
- (BOOL)applyThemeFromJSON:(NSDictionary *)json;
|
||||||
|
|
||||||
|
/// Save explicit theme
|
||||||
|
- (BOOL)applyTheme:(KBSkinTheme *)theme;
|
||||||
|
|
||||||
|
/// Reset to default theme
|
||||||
|
- (void)resetToDefault;
|
||||||
|
|
||||||
|
/// 直接应用图片皮肤(使用 JPEG/PNG 数据)。建议大小 < 512KB。
|
||||||
|
- (BOOL)applyImageSkinWithData:(NSData *)imageData skinId:(NSString *)skinId name:(NSString *)name;
|
||||||
|
|
||||||
|
/// 当前背景图片(若存在)
|
||||||
|
- (nullable UIImage *)currentBackgroundImage;
|
||||||
|
|
||||||
|
/// Parse a hex color string like "#RRGGBB"/"#RRGGBBAA"
|
||||||
|
+ (UIColor *)colorFromHexString:(NSString *)hex defaultColor:(UIColor *)fallback;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
214
Shared/KBSkinManager.m
Normal file
214
Shared/KBSkinManager.m
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
//
|
||||||
|
// KBSkinManager.m
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "KBSkinManager.h"
|
||||||
|
#import <Security/Security.h>
|
||||||
|
#import "KBConfig.h"
|
||||||
|
|
||||||
|
NSString * const KBSkinDidChangeNotification = @"KBSkinDidChangeNotification";
|
||||||
|
NSString * const KBDarwinSkinChanged = @"com.loveKey.nyx.skin.changed";
|
||||||
|
|
||||||
|
static NSString * const kKBSkinService = @"com.loveKey.nyx.skin"; // Keychain service
|
||||||
|
static NSString * const kKBSkinAccount = @"current"; // Keychain account
|
||||||
|
|
||||||
|
@implementation KBSkinTheme
|
||||||
|
|
||||||
|
+ (BOOL)supportsSecureCoding { return YES; }
|
||||||
|
|
||||||
|
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||||
|
[coder encodeObject:self.skinId forKey:@"skinId"];
|
||||||
|
[coder encodeObject:self.name forKey:@"name"];
|
||||||
|
[coder encodeObject:self.keyboardBackground forKey:@"keyboardBackground"];
|
||||||
|
[coder encodeObject:self.keyBackground forKey:@"keyBackground"];
|
||||||
|
[coder encodeObject:self.keyTextColor forKey:@"keyTextColor"];
|
||||||
|
[coder encodeObject:self.keyHighlightBackground forKey:@"keyHighlightBackground"];
|
||||||
|
[coder encodeObject:self.accentColor forKey:@"accentColor"];
|
||||||
|
if (self.backgroundImageData) {
|
||||||
|
[coder encodeObject:self.backgroundImageData forKey:@"backgroundImageData"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||||
|
if (self = [super init]) {
|
||||||
|
_skinId = [coder decodeObjectOfClass:NSString.class forKey:@"skinId"] ?: @"default";
|
||||||
|
_name = [coder decodeObjectOfClass:NSString.class forKey:@"name"] ?: @"Default";
|
||||||
|
_keyboardBackground = [coder decodeObjectOfClass:UIColor.class forKey:@"keyboardBackground"] ?: [UIColor colorWithWhite:0.95 alpha:1.0];
|
||||||
|
_keyBackground = [coder decodeObjectOfClass:UIColor.class forKey:@"keyBackground"] ?: UIColor.whiteColor;
|
||||||
|
_keyTextColor = [coder decodeObjectOfClass:UIColor.class forKey:@"keyTextColor"] ?: UIColor.blackColor;
|
||||||
|
_keyHighlightBackground = [coder decodeObjectOfClass:UIColor.class forKey:@"keyHighlightBackground"] ?: [UIColor colorWithWhite:0.85 alpha:1.0];
|
||||||
|
_accentColor = [coder decodeObjectOfClass:UIColor.class forKey:@"accentColor"] ?: [UIColor colorWithRed:0.77 green:0.93 blue:0.82 alpha:1.0];
|
||||||
|
_backgroundImageData = [coder decodeObjectOfClass:NSData.class forKey:@"backgroundImageData"];
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface KBSkinManager ()
|
||||||
|
@property (atomic, strong, readwrite) KBSkinTheme *current;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation KBSkinManager
|
||||||
|
|
||||||
|
+ (instancetype)shared {
|
||||||
|
static KBSkinManager *m; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ m = [KBSkinManager new]; });
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)init {
|
||||||
|
if (self = [super init]) {
|
||||||
|
_current = [self p_loadFromKeychain] ?: [self.class defaultTheme];
|
||||||
|
// Observe Darwin notification for cross-process updates
|
||||||
|
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
|
||||||
|
(__bridge const void *)(self),
|
||||||
|
KBSkinDarwinCallback,
|
||||||
|
(__bridge CFStringRef)KBDarwinSkinChanged,
|
||||||
|
NULL,
|
||||||
|
CFNotificationSuspensionBehaviorDeliverImmediately);
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
|
||||||
|
KBSkinManager *self = (__bridge KBSkinManager *)observer;
|
||||||
|
[self p_reloadFromKeychainAndBroadcast:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc {
|
||||||
|
CFNotificationCenterRemoveObserver(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)(self), (__bridge CFStringRef)KBDarwinSkinChanged, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Public
|
||||||
|
|
||||||
|
- (BOOL)applyThemeFromJSON:(NSDictionary *)json {
|
||||||
|
if (json.count == 0) return NO;
|
||||||
|
KBSkinTheme *t = [KBSkinTheme new];
|
||||||
|
t.skinId = [json[@"id"] isKindOfClass:NSString.class] ? json[@"id"] : @"custom";
|
||||||
|
t.name = [json[@"name"] isKindOfClass:NSString.class] ? json[@"name"] : t.skinId;
|
||||||
|
t.keyboardBackground = [self.class colorFromHexString:json[@"background"] defaultColor:[self.class defaultTheme].keyboardBackground];
|
||||||
|
t.keyBackground = [self.class colorFromHexString:json[@"key_bg"] defaultColor:[self.class defaultTheme].keyBackground];
|
||||||
|
t.keyTextColor = [self.class colorFromHexString:json[@"key_text"] defaultColor:[self.class defaultTheme].keyTextColor];
|
||||||
|
t.keyHighlightBackground = [self.class colorFromHexString:json[@"key_highlight"] defaultColor:[self.class defaultTheme].keyHighlightBackground];
|
||||||
|
t.accentColor = [self.class colorFromHexString:json[@"accent"] defaultColor:[self.class defaultTheme].accentColor];
|
||||||
|
return [self applyTheme:t];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)applyTheme:(KBSkinTheme *)theme {
|
||||||
|
if (!theme) return NO;
|
||||||
|
if ([self p_saveToKeychain:theme]) {
|
||||||
|
self.current = theme;
|
||||||
|
[[NSNotificationCenter defaultCenter] postNotificationName:KBSkinDidChangeNotification object:nil];
|
||||||
|
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge CFStringRef)KBDarwinSkinChanged, NULL, NULL, true);
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)resetToDefault {
|
||||||
|
[self applyTheme:[self.class defaultTheme]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)applyImageSkinWithData:(NSData *)imageData skinId:(NSString *)skinId name:(NSString *)name {
|
||||||
|
if (imageData.length == 0) return NO;
|
||||||
|
// 构造新主题,继承当前配色作为按键/强调色的默认值
|
||||||
|
KBSkinTheme *base = self.current ?: [self.class defaultTheme];
|
||||||
|
KBSkinTheme *t = [KBSkinTheme new];
|
||||||
|
t.skinId = skinId ?: @"image";
|
||||||
|
t.name = name ?: t.skinId;
|
||||||
|
t.keyboardBackground = base.keyboardBackground ?: [self.class defaultTheme].keyboardBackground;
|
||||||
|
t.keyBackground = base.keyBackground ?: [self.class defaultTheme].keyBackground;
|
||||||
|
t.keyTextColor = base.keyTextColor ?: [self.class defaultTheme].keyTextColor;
|
||||||
|
t.keyHighlightBackground = base.keyHighlightBackground ?: [self.class defaultTheme].keyHighlightBackground;
|
||||||
|
t.accentColor = base.accentColor ?: [self.class defaultTheme].accentColor;
|
||||||
|
t.backgroundImageData = imageData;
|
||||||
|
return [self applyTheme:t];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIImage *)currentBackgroundImage {
|
||||||
|
NSData *d = self.current.backgroundImageData;
|
||||||
|
if (d.length == 0) return nil;
|
||||||
|
return [UIImage imageWithData:d scale:[UIScreen mainScreen].scale] ?: nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (UIColor *)colorFromHexString:(NSString *)hex defaultColor:(UIColor *)fallback {
|
||||||
|
if (![hex isKindOfClass:NSString.class] || hex.length == 0) return fallback;
|
||||||
|
NSString *s = [[hex stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] lowercaseString];
|
||||||
|
if ([s hasPrefix:@"#"]) s = [s substringFromIndex:1];
|
||||||
|
unsigned long long v = 0; NSScanner *scanner = [NSScanner scannerWithString:s];
|
||||||
|
if (![scanner scanHexLongLong:&v]) return fallback;
|
||||||
|
if (s.length == 6) { // RRGGBB
|
||||||
|
CGFloat r = ((v >> 16) & 0xFF) / 255.0;
|
||||||
|
CGFloat g = ((v >> 8) & 0xFF) / 255.0;
|
||||||
|
CGFloat b = (v & 0xFF) / 255.0;
|
||||||
|
return [UIColor colorWithRed:r green:g blue:b alpha:1.0];
|
||||||
|
} else if (s.length == 8) { // RRGGBBAA
|
||||||
|
CGFloat r = ((v >> 24) & 0xFF) / 255.0;
|
||||||
|
CGFloat g = ((v >> 16) & 0xFF) / 255.0;
|
||||||
|
CGFloat b = ((v >> 8) & 0xFF) / 255.0;
|
||||||
|
CGFloat a = (v & 0xFF) / 255.0;
|
||||||
|
return [UIColor colorWithRed:r green:g blue:b alpha:a];
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Defaults
|
||||||
|
|
||||||
|
+ (KBSkinTheme *)defaultTheme {
|
||||||
|
KBSkinTheme *t = [KBSkinTheme new];
|
||||||
|
t.skinId = @"default";
|
||||||
|
t.name = @"Default";
|
||||||
|
t.keyboardBackground = [UIColor colorWithWhite:0.95 alpha:1.0];
|
||||||
|
t.keyBackground = UIColor.whiteColor;
|
||||||
|
t.keyTextColor = UIColor.blackColor;
|
||||||
|
t.keyHighlightBackground = [UIColor colorWithWhite:0.85 alpha:1.0];
|
||||||
|
t.accentColor = [UIColor colorWithRed:0.77 green:0.93 blue:0.82 alpha:1.0];
|
||||||
|
t.backgroundImageData = nil;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Keychain
|
||||||
|
|
||||||
|
- (NSMutableDictionary *)baseKCQuery {
|
||||||
|
NSMutableDictionary *q = [@{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
||||||
|
(__bridge id)kSecAttrService: kKBSkinService,
|
||||||
|
(__bridge id)kSecAttrAccount: kKBSkinAccount } mutableCopy];
|
||||||
|
q[(__bridge id)kSecAttrAccessGroup] = KB_KEYCHAIN_ACCESS_GROUP;
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)p_saveToKeychain:(KBSkinTheme *)theme {
|
||||||
|
NSError *err = nil;
|
||||||
|
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:theme requiringSecureCoding:YES error:&err];
|
||||||
|
if (err || data.length == 0) return NO;
|
||||||
|
NSMutableDictionary *q = [self baseKCQuery];
|
||||||
|
SecItemDelete((__bridge CFDictionaryRef)q);
|
||||||
|
q[(__bridge id)kSecValueData] = data;
|
||||||
|
q[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
|
||||||
|
OSStatus st = SecItemAdd((__bridge CFDictionaryRef)q, NULL);
|
||||||
|
return (st == errSecSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (KBSkinTheme *)p_loadFromKeychain {
|
||||||
|
NSMutableDictionary *q = [self baseKCQuery];
|
||||||
|
q[(__bridge id)kSecReturnData] = @YES;
|
||||||
|
q[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
|
||||||
|
CFTypeRef dataRef = NULL; OSStatus st = SecItemCopyMatching((__bridge CFDictionaryRef)q, &dataRef);
|
||||||
|
if (st != errSecSuccess || !dataRef) return nil;
|
||||||
|
NSData *data = (__bridge_transfer NSData *)dataRef;
|
||||||
|
if (data.length == 0) return nil;
|
||||||
|
@try {
|
||||||
|
KBSkinTheme *t = [NSKeyedUnarchiver unarchivedObjectOfClass:KBSkinTheme.class fromData:data error:NULL];
|
||||||
|
return t;
|
||||||
|
} @catch (__unused NSException *e) { return nil; }
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)p_reloadFromKeychainAndBroadcast:(BOOL)broadcast {
|
||||||
|
KBSkinTheme *t = [self p_loadFromKeychain] ?: [self.class defaultTheme];
|
||||||
|
self.current = t;
|
||||||
|
if (broadcast) {
|
||||||
|
[[NSNotificationCenter defaultCenter] postNotificationName:KBSkinDidChangeNotification object:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -8,6 +8,9 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
043FBCD22EAF97630036AFE1 /* KBPermissionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 04C6EAE12EAF940F0089C901 /* KBPermissionViewController.m */; };
|
043FBCD22EAF97630036AFE1 /* KBPermissionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 04C6EAE12EAF940F0089C901 /* KBPermissionViewController.m */; };
|
||||||
|
0459D1B42EBA284C00F2D189 /* KBSkinCenterVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */; };
|
||||||
|
0459D1B72EBA287900F2D189 /* KBSkinManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B62EBA287900F2D189 /* KBSkinManager.m */; };
|
||||||
|
0459D1B82EBA287900F2D189 /* KBSkinManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B62EBA287900F2D189 /* KBSkinManager.m */; };
|
||||||
04A9FE0F2EB481100020DB6D /* KBHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC97082EB31B14007BD342 /* KBHUD.m */; };
|
04A9FE0F2EB481100020DB6D /* KBHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC97082EB31B14007BD342 /* KBHUD.m */; };
|
||||||
04A9FE132EB4D0D20020DB6D /* KBFullAccessManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */; };
|
04A9FE132EB4D0D20020DB6D /* KBFullAccessManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */; };
|
||||||
04A9FE162EB873C80020DB6D /* UIViewController+Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE152EB873C80020DB6D /* UIViewController+Extension.m */; };
|
04A9FE162EB873C80020DB6D /* UIViewController+Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE152EB873C80020DB6D /* UIViewController+Extension.m */; };
|
||||||
@@ -87,6 +90,10 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
0459D1B22EBA284C00F2D189 /* KBSkinCenterVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinCenterVC.h; sourceTree = "<group>"; };
|
||||||
|
0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinCenterVC.m; sourceTree = "<group>"; };
|
||||||
|
0459D1B52EBA287900F2D189 /* KBSkinManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinManager.h; sourceTree = "<group>"; };
|
||||||
|
0459D1B62EBA287900F2D189 /* KBSkinManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinManager.m; sourceTree = "<group>"; };
|
||||||
04A9A67D2EB9E1690023B8F4 /* KBResponderUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBResponderUtils.h; sourceTree = "<group>"; };
|
04A9A67D2EB9E1690023B8F4 /* KBResponderUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBResponderUtils.h; sourceTree = "<group>"; };
|
||||||
04A9FE102EB4D0D20020DB6D /* KBFullAccessManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFullAccessManager.h; sourceTree = "<group>"; };
|
04A9FE102EB4D0D20020DB6D /* KBFullAccessManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFullAccessManager.h; sourceTree = "<group>"; };
|
||||||
04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFullAccessManager.m; sourceTree = "<group>"; };
|
04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFullAccessManager.m; sourceTree = "<group>"; };
|
||||||
@@ -329,6 +336,8 @@
|
|||||||
04FC95CE2EB1E7A1007BD342 /* HomeVC.m */,
|
04FC95CE2EB1E7A1007BD342 /* HomeVC.m */,
|
||||||
A1B2D7002EB8C00100000001 /* KBLangTestVC.h */,
|
A1B2D7002EB8C00100000001 /* KBLangTestVC.h */,
|
||||||
A1B2D7012EB8C00100000001 /* KBLangTestVC.m */,
|
A1B2D7012EB8C00100000001 /* KBLangTestVC.m */,
|
||||||
|
0459D1B22EBA284C00F2D189 /* KBSkinCenterVC.h */,
|
||||||
|
0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */,
|
||||||
);
|
);
|
||||||
path = VC;
|
path = VC;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -582,6 +591,8 @@
|
|||||||
A1B2C4222EB4B7A100000001 /* KBKeyboardPermissionManager.m */,
|
A1B2C4222EB4B7A100000001 /* KBKeyboardPermissionManager.m */,
|
||||||
04A9FE182EB892460020DB6D /* KBLocalizationManager.h */,
|
04A9FE182EB892460020DB6D /* KBLocalizationManager.h */,
|
||||||
04A9FE192EB892460020DB6D /* KBLocalizationManager.m */,
|
04A9FE192EB892460020DB6D /* KBLocalizationManager.m */,
|
||||||
|
0459D1B52EBA287900F2D189 /* KBSkinManager.h */,
|
||||||
|
0459D1B62EBA287900F2D189 /* KBSkinManager.m */,
|
||||||
);
|
);
|
||||||
path = Shared;
|
path = Shared;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -817,6 +828,7 @@
|
|||||||
04FC95792EB09BC8007BD342 /* KBKeyBoardMainView.m in Sources */,
|
04FC95792EB09BC8007BD342 /* KBKeyBoardMainView.m in Sources */,
|
||||||
04FC95732EB09570007BD342 /* KBFunctionBarView.m in Sources */,
|
04FC95732EB09570007BD342 /* KBFunctionBarView.m in Sources */,
|
||||||
04C6EAD82EAF870B0089C901 /* KeyboardViewController.m in Sources */,
|
04C6EAD82EAF870B0089C901 /* KeyboardViewController.m in Sources */,
|
||||||
|
0459D1B82EBA287900F2D189 /* KBSkinManager.m in Sources */,
|
||||||
04FC95762EB095DE007BD342 /* KBFunctionPasteView.m in Sources */,
|
04FC95762EB095DE007BD342 /* KBFunctionPasteView.m in Sources */,
|
||||||
A1B2C3D42EB0A0A100000001 /* KBFunctionTagCell.m in Sources */,
|
A1B2C3D42EB0A0A100000001 /* KBFunctionTagCell.m in Sources */,
|
||||||
04A9FE1A2EB892460020DB6D /* KBLocalizationManager.m in Sources */,
|
04A9FE1A2EB892460020DB6D /* KBLocalizationManager.m in Sources */,
|
||||||
@@ -860,10 +872,12 @@
|
|||||||
A1B2D7022EB8C00100000001 /* KBLangTestVC.m in Sources */,
|
A1B2D7022EB8C00100000001 /* KBLangTestVC.m in Sources */,
|
||||||
04C6EABF2EAF86530089C901 /* main.m in Sources */,
|
04C6EABF2EAF86530089C901 /* main.m in Sources */,
|
||||||
04FC95CC2EB1E780007BD342 /* BaseTabBarController.m in Sources */,
|
04FC95CC2EB1E780007BD342 /* BaseTabBarController.m in Sources */,
|
||||||
|
0459D1B72EBA287900F2D189 /* KBSkinManager.m in Sources */,
|
||||||
04FC95F42EB339C1007BD342 /* AppleSignInManager.m in Sources */,
|
04FC95F42EB339C1007BD342 /* AppleSignInManager.m in Sources */,
|
||||||
04C6EAC12EAF86530089C901 /* ViewController.m in Sources */,
|
04C6EAC12EAF86530089C901 /* ViewController.m in Sources */,
|
||||||
A1B2C4002EB4A0A100000004 /* KBAuthManager.m in Sources */,
|
A1B2C4002EB4A0A100000004 /* KBAuthManager.m in Sources */,
|
||||||
A1B2C4212EB4B7A100000001 /* KBKeyboardPermissionManager.m in Sources */,
|
A1B2C4212EB4B7A100000001 /* KBKeyboardPermissionManager.m in Sources */,
|
||||||
|
0459D1B42EBA284C00F2D189 /* KBSkinCenterVC.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
self.tableView.tableHeaderView = header;
|
self.tableView.tableHeaderView = header;
|
||||||
[self.view addSubview:self.tableView];
|
[self.view addSubview:self.tableView];
|
||||||
|
|
||||||
self.items = @[ KBLocalized(@"home_item_lang_test"), KBLocalized(@"home_item_keyboard_permission") ];
|
self.items = @[ KBLocalized(@"home_item_lang_test"), KBLocalized(@"home_item_keyboard_permission"), @"皮肤中心" ];
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{ [self.textView becomeFirstResponder]; });
|
dispatch_async(dispatch_get_main_queue(), ^{ [self.textView becomeFirstResponder]; });
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
- (void)viewWillAppear:(BOOL)animated{
|
- (void)viewWillAppear:(BOOL)animated{
|
||||||
[super viewWillAppear:animated];
|
[super viewWillAppear:animated];
|
||||||
self.title = KBLocalized(@"home_title");
|
self.title = KBLocalized(@"home_title");
|
||||||
self.items = @[ KBLocalized(@"home_item_lang_test"), KBLocalized(@"home_item_keyboard_permission") ];
|
self.items = @[ KBLocalized(@"home_item_lang_test"), KBLocalized(@"home_item_keyboard_permission"), @"皮肤中心" ];
|
||||||
[self.tableView reloadData];
|
[self.tableView reloadData];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +85,10 @@
|
|||||||
// 键盘权限引导页
|
// 键盘权限引导页
|
||||||
KBGuideVC *vc = [KBGuideVC new];
|
KBGuideVC *vc = [KBGuideVC new];
|
||||||
[self.navigationController pushViewController:vc animated:YES];
|
[self.navigationController pushViewController:vc animated:YES];
|
||||||
|
} else if (indexPath.row == 2) {
|
||||||
|
// 皮肤中心
|
||||||
|
UIViewController *vc = [NSClassFromString(@"KBSkinCenterVC") new];
|
||||||
|
if (vc) { [self.navigationController pushViewController:vc animated:YES]; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
keyBoard/Class/Home/VC/KBSkinCenterVC.h
Normal file
11
keyBoard/Class/Home/VC/KBSkinCenterVC.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
//
|
||||||
|
// KBSkinCenterVC.h
|
||||||
|
// 简单的皮肤中心:展示两款示例皮肤,点击“下载并应用”后立即同步到键盘扩展。
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
@interface KBSkinCenterVC : UIViewController
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
132
keyBoard/Class/Home/VC/KBSkinCenterVC.m
Normal file
132
keyBoard/Class/Home/VC/KBSkinCenterVC.m
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
//
|
||||||
|
// KBSkinCenterVC.m
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "KBSkinCenterVC.h"
|
||||||
|
#import "Masonry.h"
|
||||||
|
#import "KBNetworkManager.h"
|
||||||
|
#import "KBSkinManager.h"
|
||||||
|
#import "KBHUD.h"
|
||||||
|
|
||||||
|
@interface KBSkinCell : UITableViewCell
|
||||||
|
@property (nonatomic, strong) UIButton *applyBtn;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation KBSkinCell
|
||||||
|
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||||
|
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
|
||||||
|
self.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||||
|
_applyBtn = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||||
|
[_applyBtn setTitle:@"下载并应用" forState:UIControlStateNormal];
|
||||||
|
_applyBtn.layer.cornerRadius = 6; _applyBtn.layer.borderWidth = 1;
|
||||||
|
_applyBtn.layer.borderColor = [UIColor colorWithWhite:0.85 alpha:1].CGColor;
|
||||||
|
[self.contentView addSubview:_applyBtn];
|
||||||
|
[_applyBtn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.right.equalTo(self.contentView).offset(-16);
|
||||||
|
make.centerY.equalTo(self.contentView);
|
||||||
|
make.width.mas_equalTo(110);
|
||||||
|
make.height.mas_equalTo(34);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface KBSkinCenterVC () <UITableViewDelegate, UITableViewDataSource>
|
||||||
|
@property (nonatomic, strong) UITableView *tableView;
|
||||||
|
@property (nonatomic, copy) NSArray<NSDictionary *> *skins; // id, name, img(url relative to KB_BASE_URL)
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation KBSkinCenterVC
|
||||||
|
|
||||||
|
- (void)viewDidLoad {
|
||||||
|
[super viewDidLoad];
|
||||||
|
self.title = @"皮肤中心";
|
||||||
|
self.view.backgroundColor = [UIColor whiteColor];
|
||||||
|
// 绝对 URL 的测试皮肤图片(无需 KB_BASE_URL)。
|
||||||
|
// 说明:使用 picsum.photos 的固定 id,稳定可直接访问。
|
||||||
|
self.skins = @[
|
||||||
|
@{ @"id": @"aurora", @"name": @"极光", @"img": @"https://picsum.photos/id/1018/1600/900.jpg" },
|
||||||
|
@{ @"id": @"alps", @"name": @"雪山", @"img": @"https://picsum.photos/id/1016/1600/900.jpg" },
|
||||||
|
@{ @"id": @"lake", @"name": @"湖面", @"img": @"https://picsum.photos/id/1039/1600/900.jpg" },
|
||||||
|
];
|
||||||
|
|
||||||
|
self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleInsetGrouped];
|
||||||
|
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||||
|
self.tableView.delegate = self; self.tableView.dataSource = self;
|
||||||
|
[self.view addSubview:self.tableView];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - UITableView
|
||||||
|
|
||||||
|
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; }
|
||||||
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.skins.count; }
|
||||||
|
|
||||||
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
static NSString *cid = @"skin.cell";
|
||||||
|
KBSkinCell *cell = [tableView dequeueReusableCellWithIdentifier:cid];
|
||||||
|
if (!cell) { cell = [[KBSkinCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cid]; }
|
||||||
|
NSDictionary *skin = self.skins[indexPath.row];
|
||||||
|
cell.textLabel.text = skin[@"name"]; cell.detailTextLabel.text = skin[@"id"];
|
||||||
|
[cell.applyBtn removeTarget:nil action:NULL forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
[cell.applyBtn addTarget:self action:@selector(onApplyBtn:) forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
cell.applyBtn.tag = indexPath.row;
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)onApplyBtn:(UIButton *)sender {
|
||||||
|
NSInteger idx = sender.tag;
|
||||||
|
if (idx < 0 || idx >= self.skins.count) return;
|
||||||
|
NSDictionary *skin = self.skins[idx];
|
||||||
|
NSString *path = skin[@"img"] ?: @""; // 相对 KB_BASE_URL
|
||||||
|
|
||||||
|
// 下载图片数据(非 JSON 将以 NSData 返回)
|
||||||
|
[[KBNetworkManager shared] GET:path parameters:nil headers:nil completion:^(id jsonOrData, NSURLResponse *response, NSError *error) {
|
||||||
|
NSData *data = ([jsonOrData isKindOfClass:NSData.class] ? (NSData *)jsonOrData : nil);
|
||||||
|
// 尝试压缩尺寸,避免 Keychain 过大:将宽度限制到 1500px
|
||||||
|
if (data && data.length > 0) {
|
||||||
|
UIImage *img = [UIImage imageWithData:data];
|
||||||
|
if (img) {
|
||||||
|
CGFloat maxW = 1500.0;
|
||||||
|
if (img.size.width > maxW) {
|
||||||
|
CGFloat scale = maxW / img.size.width;
|
||||||
|
CGSize newSize = CGSizeMake(maxW, floor(img.size.height * scale));
|
||||||
|
UIGraphicsBeginImageContextWithOptions(newSize, YES, 1.0);
|
||||||
|
[img drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
|
||||||
|
UIImage *resized = UIGraphicsGetImageFromCurrentImageContext();
|
||||||
|
UIGraphicsEndImageContext();
|
||||||
|
img = resized ?: img;
|
||||||
|
}
|
||||||
|
data = UIImageJPEGRepresentation(img, 0.85) ?: data; // 压成 JPEG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
NSData *payload = data;
|
||||||
|
if (payload.length == 0) {
|
||||||
|
// 兜底:生成一张简单渐变图片
|
||||||
|
CGSize size = CGSizeMake(1200, 600);
|
||||||
|
UIGraphicsBeginImageContextWithOptions(size, YES, 1.0);
|
||||||
|
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||||
|
UIColor *c1 = [UIColor colorWithRed:0.76 green:0.91 blue:0.86 alpha:1];
|
||||||
|
UIColor *c2 = [UIColor colorWithRed:0.93 green:0.97 blue:0.91 alpha:1];
|
||||||
|
if ([skin[@"id"] hasPrefix:@"dark"]) {
|
||||||
|
c1 = [UIColor colorWithRed:0.1 green:0.12 blue:0.16 alpha:1];
|
||||||
|
c2 = [UIColor colorWithRed:0.22 green:0.24 blue:0.28 alpha:1];
|
||||||
|
}
|
||||||
|
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
|
||||||
|
NSArray *colors = @[(__bridge id)c1.CGColor, (__bridge id)c2.CGColor];
|
||||||
|
CGFloat locs[] = {0,1};
|
||||||
|
CGGradientRef grad = CGGradientCreateWithColors(space, (__bridge CFArrayRef)colors, locs);
|
||||||
|
CGContextDrawLinearGradient(ctx, grad, CGPointZero, CGPointMake(size.width, size.height), 0);
|
||||||
|
CGGradientRelease(grad); CGColorSpaceRelease(space);
|
||||||
|
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
|
||||||
|
UIGraphicsEndImageContext();
|
||||||
|
payload = UIImageJPEGRepresentation(img, 0.9);
|
||||||
|
}
|
||||||
|
BOOL ok = (payload.length > 0) ? [[KBSkinManager shared] applyImageSkinWithData:payload skinId:skin[@"id"] name:skin[@"name"]] : NO;
|
||||||
|
[KBHUD showInfo:(ok ? @"已应用,切到键盘查看" : @"应用失败")];
|
||||||
|
});
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
Reference in New Issue
Block a user