This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
425 lines
12 KiB
425 lines
12 KiB
/** |
|
* Copyright (c) 2015-present, Facebook, Inc. |
|
* |
|
* This source code is licensed under the MIT license found in the |
|
* LICENSE file in the root directory of this source tree. |
|
*/ |
|
|
|
#import "RCTDevMenu.h" |
|
|
|
#import "RCTBridge+Private.h" |
|
#import "RCTDevSettings.h" |
|
#import "RCTKeyCommands.h" |
|
#import "RCTLog.h" |
|
#import "RCTUtils.h" |
|
|
|
#if RCT_DEV |
|
|
|
#if RCT_ENABLE_INSPECTOR |
|
#import "RCTInspectorDevServerHelper.h" |
|
#endif |
|
|
|
NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification"; |
|
|
|
@implementation UIWindow (RCTDevMenu) |
|
|
|
- (void)RCT_motionEnded:(__unused UIEventSubtype)motion withEvent:(UIEvent *)event |
|
{ |
|
if (event.subtype == UIEventSubtypeMotionShake) { |
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil]; |
|
} |
|
} |
|
|
|
@end |
|
|
|
@implementation RCTDevMenuItem |
|
{ |
|
RCTDevMenuItemTitleBlock _titleBlock; |
|
dispatch_block_t _handler; |
|
} |
|
|
|
- (instancetype)initWithTitleBlock:(RCTDevMenuItemTitleBlock)titleBlock |
|
handler:(dispatch_block_t)handler |
|
{ |
|
if ((self = [super init])) { |
|
_titleBlock = [titleBlock copy]; |
|
_handler = [handler copy]; |
|
} |
|
return self; |
|
} |
|
|
|
RCT_NOT_IMPLEMENTED(- (instancetype)init) |
|
|
|
+ (instancetype)buttonItemWithTitleBlock:(NSString *(^)(void))titleBlock handler:(dispatch_block_t)handler |
|
{ |
|
return [[self alloc] initWithTitleBlock:titleBlock handler:handler]; |
|
} |
|
|
|
+ (instancetype)buttonItemWithTitle:(NSString *)title |
|
handler:(dispatch_block_t)handler |
|
{ |
|
return [[self alloc] initWithTitleBlock:^NSString *{ return title; } handler:handler]; |
|
} |
|
|
|
- (void)callHandler |
|
{ |
|
if (_handler) { |
|
_handler(); |
|
} |
|
} |
|
|
|
- (NSString *)title |
|
{ |
|
if (_titleBlock) { |
|
return _titleBlock(); |
|
} |
|
return nil; |
|
} |
|
|
|
@end |
|
|
|
typedef void(^RCTDevMenuAlertActionHandler)(UIAlertAction *action); |
|
|
|
@interface RCTDevMenu () <RCTBridgeModule, RCTInvalidating> |
|
|
|
@end |
|
|
|
@implementation RCTDevMenu |
|
{ |
|
UIAlertController *_actionSheet; |
|
NSMutableArray<RCTDevMenuItem *> *_extraMenuItems; |
|
} |
|
|
|
@synthesize bridge = _bridge; |
|
|
|
RCT_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. |
|
RCTSwapInstanceMethods([UIWindow class], @selector(motionEnded:withEvent:), @selector(RCT_motionEnded:withEvent:)); |
|
} |
|
|
|
+ (BOOL)requiresMainQueueSetup |
|
{ |
|
return YES; |
|
} |
|
|
|
- (instancetype)init |
|
{ |
|
if ((self = [super init])) { |
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(showOnShake) |
|
name:RCTShowDevMenuNotification |
|
object:nil]; |
|
_extraMenuItems = [NSMutableArray new]; |
|
|
|
#if TARGET_OS_SIMULATOR |
|
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance]; |
|
__weak __typeof(self) weakSelf = self; |
|
|
|
// Toggle debug menu |
|
[commands registerKeyCommandWithInput:@"d" |
|
modifierFlags:UIKeyModifierCommand |
|
action:^(__unused UIKeyCommand *command) { |
|
[weakSelf toggle]; |
|
}]; |
|
|
|
// Toggle element inspector |
|
[commands registerKeyCommandWithInput:@"i" |
|
modifierFlags:UIKeyModifierCommand |
|
action:^(__unused UIKeyCommand *command) { |
|
[weakSelf.bridge.devSettings toggleElementInspector]; |
|
}]; |
|
|
|
// Reload in normal mode |
|
[commands registerKeyCommandWithInput:@"n" |
|
modifierFlags:UIKeyModifierCommand |
|
action:^(__unused UIKeyCommand *command) { |
|
[weakSelf.bridge.devSettings setIsDebuggingRemotely:NO]; |
|
}]; |
|
#endif |
|
} |
|
return self; |
|
} |
|
|
|
- (dispatch_queue_t)methodQueue |
|
{ |
|
return dispatch_get_main_queue(); |
|
} |
|
|
|
- (void)invalidate |
|
{ |
|
_presentedItems = nil; |
|
[_actionSheet dismissViewControllerAnimated:YES completion:^(void){}]; |
|
[[NSNotificationCenter defaultCenter] removeObserver:self]; |
|
} |
|
|
|
- (void)showOnShake |
|
{ |
|
if ([_bridge.devSettings isShakeToShowDevMenuEnabled]) { |
|
[self show]; |
|
} |
|
} |
|
|
|
- (void)toggle |
|
{ |
|
if (_actionSheet) { |
|
[_actionSheet dismissViewControllerAnimated:YES completion:^(void){}]; |
|
_actionSheet = nil; |
|
} else { |
|
[self show]; |
|
} |
|
} |
|
|
|
- (BOOL)isActionSheetShown |
|
{ |
|
return _actionSheet != nil; |
|
} |
|
|
|
- (void)addItem:(NSString *)title handler:(void(^)(void))handler |
|
{ |
|
[self addItem:[RCTDevMenuItem buttonItemWithTitle:title handler:handler]]; |
|
} |
|
|
|
- (void)addItem:(RCTDevMenuItem *)item |
|
{ |
|
[_extraMenuItems addObject:item]; |
|
} |
|
|
|
- (NSArray<RCTDevMenuItem *> *)_menuItemsToPresent |
|
{ |
|
NSMutableArray<RCTDevMenuItem *> *items = [NSMutableArray new]; |
|
|
|
// Add built-in items |
|
__weak RCTBridge *bridge = _bridge; |
|
__weak RCTDevSettings *devSettings = _bridge.devSettings; |
|
|
|
[items addObject:[RCTDevMenuItem buttonItemWithTitle:@"Reload" handler:^{ |
|
[bridge reload]; |
|
}]]; |
|
|
|
if (devSettings.isNuclideDebuggingAvailable) { |
|
[items addObject:[RCTDevMenuItem buttonItemWithTitle:[NSString stringWithFormat:@"Debug JS in Nuclide %@", @"\U0001F4AF"] handler:^{ |
|
#if RCT_ENABLE_INSPECTOR |
|
[RCTInspectorDevServerHelper attachDebugger:@"ReactNative" withBundleURL:bridge.bundleURL withView: RCTPresentedViewController()]; |
|
#endif |
|
}]]; |
|
} |
|
|
|
if (!devSettings.isRemoteDebuggingAvailable) { |
|
[items addObject:[RCTDevMenuItem buttonItemWithTitle:@"Remote JS Debugger Unavailable" handler:^{ |
|
UIAlertController *alertController = [UIAlertController |
|
alertControllerWithTitle:@"Remote JS Debugger Unavailable" |
|
message:@"You need to include the RCTWebSocket library to enable remote JS debugging" |
|
preferredStyle:UIAlertControllerStyleAlert]; |
|
__weak typeof(alertController) weakAlertController = alertController; |
|
[alertController addAction: |
|
[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){ |
|
[weakAlertController dismissViewControllerAnimated:YES completion:nil]; |
|
}]]; |
|
[RCTPresentedViewController() presentViewController:alertController animated:YES completion:NULL]; |
|
}]]; |
|
} else { |
|
[items addObject:[RCTDevMenuItem buttonItemWithTitleBlock:^NSString *{ |
|
NSString *title = devSettings.isDebuggingRemotely ? @"Stop Remote JS Debugging" : @"Debug JS Remotely"; |
|
if (devSettings.isNuclideDebuggingAvailable) { |
|
return [NSString stringWithFormat:@"%@ %@", title, @"\U0001F645"]; |
|
} else { |
|
return title; |
|
} |
|
} handler:^{ |
|
devSettings.isDebuggingRemotely = !devSettings.isDebuggingRemotely; |
|
}]]; |
|
} |
|
|
|
if (devSettings.isLiveReloadAvailable) { |
|
[items addObject:[RCTDevMenuItem buttonItemWithTitleBlock:^NSString *{ |
|
return devSettings.isLiveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload"; |
|
} handler:^{ |
|
devSettings.isLiveReloadEnabled = !devSettings.isLiveReloadEnabled; |
|
}]]; |
|
[items addObject:[RCTDevMenuItem buttonItemWithTitleBlock:^NSString *{ |
|
return devSettings.isProfilingEnabled ? @"Stop Systrace" : @"Start Systrace"; |
|
} handler:^{ |
|
if (devSettings.isDebuggingRemotely) { |
|
UIAlertController *alertController = [UIAlertController |
|
alertControllerWithTitle:@"Systrace Unavailable" |
|
message:@"You need to stop remote JS debugging to enable Systrace" |
|
preferredStyle:UIAlertControllerStyleAlert]; |
|
__weak typeof(alertController) weakAlertController = alertController; |
|
[alertController addAction: |
|
[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){ |
|
[weakAlertController dismissViewControllerAnimated:YES completion:nil]; |
|
}]]; |
|
[RCTPresentedViewController() presentViewController:alertController animated:YES completion:NULL]; |
|
} else { |
|
devSettings.isProfilingEnabled = !devSettings.isProfilingEnabled; |
|
} |
|
}]]; |
|
} |
|
|
|
if (_bridge.devSettings.isHotLoadingAvailable) { |
|
[items addObject:[RCTDevMenuItem buttonItemWithTitleBlock:^NSString *{ |
|
return devSettings.isHotLoadingEnabled ? @"Disable Hot Reloading" : @"Enable Hot Reloading"; |
|
} handler:^{ |
|
devSettings.isHotLoadingEnabled = !devSettings.isHotLoadingEnabled; |
|
}]]; |
|
} |
|
|
|
if (devSettings.isJSCSamplingProfilerAvailable) { |
|
[items addObject:[RCTDevMenuItem buttonItemWithTitle:@"Start / Stop JS Sampling Profiler" handler:^{ |
|
[devSettings toggleJSCSamplingProfiler]; |
|
}]]; |
|
} |
|
|
|
[items addObject:[RCTDevMenuItem buttonItemWithTitleBlock:^NSString *{ |
|
return @"Toggle Inspector"; |
|
} handler:^{ |
|
[devSettings toggleElementInspector]; |
|
}]]; |
|
|
|
[items addObjectsFromArray:_extraMenuItems]; |
|
return items; |
|
} |
|
|
|
RCT_EXPORT_METHOD(show) |
|
{ |
|
if (_actionSheet || !_bridge || RCTRunningInAppExtension()) { |
|
return; |
|
} |
|
|
|
NSString *desc = _bridge.bridgeDescription; |
|
if (desc.length == 0) { |
|
desc = NSStringFromClass([_bridge class]); |
|
} |
|
NSString *title = [NSString stringWithFormat:@"React Native: Development (%@)", desc]; |
|
// On larger devices we don't have an anchor point for the action sheet |
|
UIAlertControllerStyle style = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone ? UIAlertControllerStyleActionSheet : UIAlertControllerStyleAlert; |
|
_actionSheet = [UIAlertController alertControllerWithTitle:title |
|
message:@"" |
|
preferredStyle:style]; |
|
|
|
NSArray<RCTDevMenuItem *> *items = [self _menuItemsToPresent]; |
|
for (RCTDevMenuItem *item in items) { |
|
[_actionSheet addAction:[UIAlertAction actionWithTitle:item.title |
|
style:UIAlertActionStyleDefault |
|
handler:[self alertActionHandlerForDevItem:item]]]; |
|
} |
|
|
|
[_actionSheet addAction:[UIAlertAction actionWithTitle:@"Cancel" |
|
style:UIAlertActionStyleCancel |
|
handler:[self alertActionHandlerForDevItem:nil]]]; |
|
|
|
_presentedItems = items; |
|
[RCTPresentedViewController() presentViewController:_actionSheet animated:YES completion:nil]; |
|
} |
|
|
|
- (RCTDevMenuAlertActionHandler)alertActionHandlerForDevItem:(RCTDevMenuItem *__nullable)item |
|
{ |
|
return ^(__unused UIAlertAction *action) { |
|
if (item) { |
|
[item callHandler]; |
|
} |
|
|
|
self->_actionSheet = nil; |
|
}; |
|
} |
|
|
|
#pragma mark - deprecated methods and properties |
|
|
|
#define WARN_DEPRECATED_DEV_MENU_EXPORT() RCTLogWarn(@"Using deprecated method %s, use RCTDevSettings instead", __func__) |
|
|
|
- (void)setShakeToShow:(BOOL)shakeToShow |
|
{ |
|
_bridge.devSettings.isShakeToShowDevMenuEnabled = shakeToShow; |
|
} |
|
|
|
- (BOOL)shakeToShow |
|
{ |
|
return _bridge.devSettings.isShakeToShowDevMenuEnabled; |
|
} |
|
|
|
RCT_EXPORT_METHOD(reload) |
|
{ |
|
WARN_DEPRECATED_DEV_MENU_EXPORT(); |
|
[_bridge reload]; |
|
} |
|
|
|
RCT_EXPORT_METHOD(debugRemotely:(BOOL)enableDebug) |
|
{ |
|
WARN_DEPRECATED_DEV_MENU_EXPORT(); |
|
_bridge.devSettings.isDebuggingRemotely = enableDebug; |
|
} |
|
|
|
RCT_EXPORT_METHOD(setProfilingEnabled:(BOOL)enabled) |
|
{ |
|
WARN_DEPRECATED_DEV_MENU_EXPORT(); |
|
_bridge.devSettings.isProfilingEnabled = enabled; |
|
} |
|
|
|
- (BOOL)profilingEnabled |
|
{ |
|
return _bridge.devSettings.isProfilingEnabled; |
|
} |
|
|
|
RCT_EXPORT_METHOD(setLiveReloadEnabled:(BOOL)enabled) |
|
{ |
|
WARN_DEPRECATED_DEV_MENU_EXPORT(); |
|
_bridge.devSettings.isLiveReloadEnabled = enabled; |
|
} |
|
|
|
- (BOOL)liveReloadEnabled |
|
{ |
|
return _bridge.devSettings.isLiveReloadEnabled; |
|
} |
|
|
|
RCT_EXPORT_METHOD(setHotLoadingEnabled:(BOOL)enabled) |
|
{ |
|
WARN_DEPRECATED_DEV_MENU_EXPORT(); |
|
_bridge.devSettings.isHotLoadingEnabled = enabled; |
|
} |
|
|
|
- (BOOL)hotLoadingEnabled |
|
{ |
|
return _bridge.devSettings.isHotLoadingEnabled; |
|
} |
|
|
|
@end |
|
|
|
#else // Unavailable when not in dev mode |
|
|
|
@implementation RCTDevMenu |
|
|
|
- (void)show {} |
|
- (void)reload {} |
|
- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler {} |
|
- (void)addItem:(RCTDevMenu *)item {} |
|
- (BOOL)isActionSheetShown { return NO; } |
|
|
|
@end |
|
|
|
@implementation RCTDevMenuItem |
|
|
|
+ (instancetype)buttonItemWithTitle:(NSString *)title handler:(void(^)(void))handler {return nil;} |
|
+ (instancetype)buttonItemWithTitleBlock:(NSString * (^)(void))titleBlock |
|
handler:(void(^)(void))handler {return nil;} |
|
|
|
@end |
|
|
|
#endif |
|
|
|
@implementation RCTBridge (RCTDevMenu) |
|
|
|
- (RCTDevMenu *)devMenu |
|
{ |
|
#if RCT_DEV |
|
return [self moduleForClass:[RCTDevMenu class]]; |
|
#else |
|
return nil; |
|
#endif |
|
} |
|
|
|
@end
|
|
|