Files
2022-11-03 18:36:35 +08:00

304 lines
9.0 KiB
Plaintext

/*!
* iOS SDK
*
* Tencent is pleased to support the open source community by making
* Hippy available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "HippyDevMenu.h"
#import "HippyEventDispatcher.h"
#import "HippyKeyCommands.h"
#import "HippyWebSocketProxy.h"
#import "HPAsserts.h"
#import "HPToolUtils.h"
#import "MacroDefines.h"
#include <objc/runtime.h>
#if HP_DEV
static NSString *const HippyShowDevMenuNotification = @"HippyShowDevMenuNotification";
static NSString *const HippyDevMenuSettingsKey = @"HippyDevMenu";
@implementation UIWindow (HippyDevMenu)
- (void)hippy_motionEnded:(__unused UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.subtype == UIEventSubtypeMotionShake) {
[[NSNotificationCenter defaultCenter] postNotificationName:HippyShowDevMenuNotification object:nil];
}
}
@end
typedef NS_ENUM(NSInteger, HippyDevMenuType) { HippyDevMenuTypeButton, HippyDevMenuTypeToggle };
@interface HippyDevMenuItem ()
@property (nonatomic, assign, readonly) HippyDevMenuType type;
@property (nonatomic, copy, readonly) NSString *key;
@property (nonatomic, copy, readonly) NSString *title;
@property (nonatomic, copy, readonly) NSString *selectedTitle;
@property (nonatomic, copy) id value;
@end
@implementation HippyDevMenuItem {
id _handler; // block
}
- (instancetype)initWithType:(HippyDevMenuType)type
key:(NSString *)key
title:(NSString *)title
selectedTitle:(NSString *)selectedTitle
handler:(id /* block */)handler {
if ((self = [super init])) {
_type = type;
_key = [key copy];
_title = [title copy];
_selectedTitle = [selectedTitle copy];
_handler = [handler copy];
_value = nil;
}
return self;
}
+ (instancetype)buttonItemWithTitle:(NSString *)title handler:(void (^)(void))handler {
return [[self alloc] initWithType:HippyDevMenuTypeButton key:nil title:title selectedTitle:nil handler:handler];
}
+ (instancetype)toggleItemWithKey:(NSString *)key
title:(NSString *)title
selectedTitle:(NSString *)selectedTitle
handler:(void (^)(BOOL selected))handler {
return [[self alloc] initWithType:HippyDevMenuTypeToggle key:key title:title selectedTitle:selectedTitle handler:handler];
}
- (void)callHandler {
switch (_type) {
case HippyDevMenuTypeButton: {
if (_handler) {
((void (^)(void))_handler)();
}
break;
}
case HippyDevMenuTypeToggle: {
if (_handler) {
((void (^)(BOOL selected))_handler)([_value boolValue]);
}
break;
}
}
}
@end
@interface HippyDevMenu () <HippyBridgeModule, HPInvalidating> {
__weak UIAlertController *_actionSheet;
NSUserDefaults *_defaults;
}
@end
@implementation HippyDevMenu
@synthesize bridge = _bridge;
HIPPY_EXPORT_MODULE()
+ (void)initialize {
// We're swizzling here because it's poor form to override methods in a category,
// however UIWindow doesn't actually implement motionEnded:withEvent:, so there's
// no need to call the original implementation.
HPSwapInstanceMethods([UIWindow class], @selector(motionEnded:withEvent:), @selector(hippy_motionEnded:withEvent:));
}
- (instancetype)init {
if ((self = [super init])) {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:@selector(showOnShake) name:HippyShowDevMenuNotification object:nil];
_defaults = [NSUserDefaults standardUserDefaults];
_shakeToShow = YES;
}
return self;
}
- (void)setBridge:(HippyBridge *)bridge {
_bridge = bridge;
#if TARGET_IPHONE_SIMULATOR
if (bridge.debugMode) {
__weak HippyDevMenu *weakSelf = self;
HippyKeyCommands *commands = [HippyKeyCommands sharedInstance];
// Toggle debug menu
[commands registerKeyCommandWithInput:@"d" modifierFlags:UIKeyModifierCommand action:^(__unused UIKeyCommand *command) {
[weakSelf toggle];
}];
// Toggle debug menu
[commands registerKeyCommandWithInput:@"e" modifierFlags:UIKeyModifierCommand action:^(__unused UIKeyCommand *command) {
[weakSelf toggle];
}];
// Toggle debug menu
[commands registerKeyCommandWithInput:@"b" modifierFlags:UIKeyModifierCommand action:^(__unused UIKeyCommand *command) {
[weakSelf toggle];
}];
// Toggle debug menu
[commands registerKeyCommandWithInput:@"u" modifierFlags:UIKeyModifierCommand action:^(__unused UIKeyCommand *command) {
[weakSelf toggle];
}];
// Toggle debug menu
[commands registerKeyCommandWithInput:@"g" modifierFlags:UIKeyModifierCommand action:^(__unused UIKeyCommand *command) {
[weakSelf toggle];
}];
}
#endif
}
- (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue();
}
- (void)invalidate {
[_actionSheet dismissViewControllerAnimated:YES completion:^(void) {
}];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)showOnShake {
if (_shakeToShow) {
[self show];
}
}
- (void)toggle {
if (!self.bridge.debugMode)
return;
if (_actionSheet) {
[_actionSheet dismissViewControllerAnimated:YES completion:^(void) {
}];
_actionSheet = nil;
} else {
[self show];
}
}
- (void)addItem:(NSString *)title handler:(void (^)(void))handler {
[self addItem:[HippyDevMenuItem buttonItemWithTitle:title handler:handler]];
}
- (void)addItem:(__unused HippyDevMenuItem *)item {
HPAssert(NO, @"[HippyDevMenu addItem:]方法没有实现,怎么没问题?");
}
- (NSArray<HippyDevMenuItem *> *)menuItems {
NSMutableArray<HippyDevMenuItem *> *items = [NSMutableArray new];
// Add built-in items
__weak HippyDevMenu *weakSelf = self;
[items addObject:[HippyDevMenuItem buttonItemWithTitle:@"Reload" handler:^{
[weakSelf reload];
}]];
return items;
}
// clang-format off
HIPPY_EXPORT_METHOD(reload) {
[_bridge requestReload];
}
// clang-format on
// clang-format off
HIPPY_EXPORT_METHOD(show) {
if (_actionSheet || !_bridge || HPRunningInAppExtension()) {
return;
}
NSString *title = [NSString stringWithFormat:@"Hippy: Development (%@)", [_bridge moduleName]];
// On larger devices we don't have an anchor point for the action sheet
UIAlertControllerStyle style = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone ? UIAlertControllerStyleActionSheet : UIAlertControllerStyleAlert;
UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:title
message:@""
preferredStyle:style];
_actionSheet = actionSheet;
NSArray<HippyDevMenuItem *> *items = [self menuItems];
for (HippyDevMenuItem *item in items) {
switch (item.type) {
case HippyDevMenuTypeButton: {
[_actionSheet addAction:[UIAlertAction actionWithTitle:item.title
style:UIAlertActionStyleDefault
handler:^(__unused UIAlertAction *action) {
// Cancel button tappped.
[item callHandler];
}]];
break;
}
default:
break;
}
}
[_actionSheet addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:^(__unused UIAlertAction *action) {
}]];
[HPPresentedViewController() presentViewController:_actionSheet animated:YES completion:^(void){}];
}
// clang-format on
@end
#else // Unavailable when not in dev mode
@implementation HippyDevMenu
- (void)show {
}
- (void)reload {
}
- (void)addItem:(__unused NSString *)title handler:(__unused dispatch_block_t)handler {
}
- (void)addItem:(__unused HippyDevMenu *)item {
}
@end
#endif
@implementation HippyBridge (HippyDevMenu)
- (HippyDevMenu *)devMenu {
#if HP_DEV
return [self moduleForClass:[HippyDevMenu class]];
#else
return nil;
#endif
}
@end