FLEXManager.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. //
  2. // FLEXManager.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 4/4/14.
  6. // Copyright (c) 2014 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXManager.h"
  9. #import "FLEXExplorerViewController.h"
  10. #import "FLEXWindow.h"
  11. #import "FLEXGlobalsTableViewControllerEntry.h"
  12. #import "FLEXObjectExplorerFactory.h"
  13. #import "FLEXObjectExplorerViewController.h"
  14. #import "FLEXNetworkObserver.h"
  15. #import "FLEXNetworkRecorder.h"
  16. #import "FLEXKeyboardShortcutManager.h"
  17. #import "FLEXFileBrowserTableViewController.h"
  18. #import "FLEXNetworkHistoryTableViewController.h"
  19. #import "FLEXKeyboardHelpViewController.h"
  20. @interface FLEXManager () <FLEXWindowEventDelegate, FLEXExplorerViewControllerDelegate>
  21. @property (nonatomic, strong) FLEXWindow *explorerWindow;
  22. @property (nonatomic, strong) FLEXExplorerViewController *explorerViewController;
  23. @property (nonatomic, readonly, strong) NSMutableArray<FLEXGlobalsTableViewControllerEntry *> *userGlobalEntries;
  24. @end
  25. @implementation FLEXManager
  26. + (instancetype)sharedManager
  27. {
  28. static FLEXManager *sharedManager = nil;
  29. static dispatch_once_t onceToken;
  30. dispatch_once(&onceToken, ^{
  31. sharedManager = [[[self class] alloc] init];
  32. });
  33. return sharedManager;
  34. }
  35. - (instancetype)init
  36. {
  37. self = [super init];
  38. if (self) {
  39. _userGlobalEntries = [NSMutableArray array];
  40. }
  41. return self;
  42. }
  43. - (FLEXWindow *)explorerWindow
  44. {
  45. NSAssert([NSThread isMainThread], @"You must use %@ from the main thread only.", NSStringFromClass([self class]));
  46. if (!_explorerWindow) {
  47. _explorerWindow = [[FLEXWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
  48. _explorerWindow.eventDelegate = self;
  49. _explorerWindow.rootViewController = self.explorerViewController;
  50. }
  51. return _explorerWindow;
  52. }
  53. - (FLEXExplorerViewController *)explorerViewController
  54. {
  55. if (!_explorerViewController) {
  56. _explorerViewController = [[FLEXExplorerViewController alloc] init];
  57. _explorerViewController.delegate = self;
  58. }
  59. return _explorerViewController;
  60. }
  61. - (void)showExplorer
  62. {
  63. self.explorerWindow.hidden = NO;
  64. }
  65. - (void)hideExplorer
  66. {
  67. self.explorerWindow.hidden = YES;
  68. }
  69. - (void)toggleExplorer {
  70. if (self.explorerWindow.isHidden) {
  71. [self showExplorer];
  72. } else {
  73. [self hideExplorer];
  74. }
  75. }
  76. - (BOOL)isHidden
  77. {
  78. return self.explorerWindow.isHidden;
  79. }
  80. - (BOOL)isNetworkDebuggingEnabled
  81. {
  82. return [FLEXNetworkObserver isEnabled];
  83. }
  84. - (void)setNetworkDebuggingEnabled:(BOOL)networkDebuggingEnabled
  85. {
  86. [FLEXNetworkObserver setEnabled:networkDebuggingEnabled];
  87. }
  88. - (NSUInteger)networkResponseCacheByteLimit
  89. {
  90. return [[FLEXNetworkRecorder defaultRecorder] responseCacheByteLimit];
  91. }
  92. - (void)setNetworkResponseCacheByteLimit:(NSUInteger)networkResponseCacheByteLimit
  93. {
  94. [[FLEXNetworkRecorder defaultRecorder] setResponseCacheByteLimit:networkResponseCacheByteLimit];
  95. }
  96. - (void)setNetworkRequestHostBlacklist:(NSArray<NSString *> *)networkRequestHostBlacklist
  97. {
  98. [FLEXNetworkRecorder defaultRecorder].hostBlacklist = networkRequestHostBlacklist;
  99. }
  100. - (NSArray<NSString *> *)hostBlacklist
  101. {
  102. return [FLEXNetworkRecorder defaultRecorder].hostBlacklist;
  103. }
  104. #pragma mark - FLEXWindowEventDelegate
  105. - (BOOL)shouldHandleTouchAtPoint:(CGPoint)pointInWindow
  106. {
  107. // Ask the explorer view controller
  108. return [self.explorerViewController shouldReceiveTouchAtWindowPoint:pointInWindow];
  109. }
  110. - (BOOL)canBecomeKeyWindow
  111. {
  112. // Only when the explorer view controller wants it because it needs to accept key input & affect the status bar.
  113. return [self.explorerViewController wantsWindowToBecomeKey];
  114. }
  115. #pragma mark - FLEXExplorerViewControllerDelegate
  116. - (void)explorerViewControllerDidFinish:(FLEXExplorerViewController *)explorerViewController
  117. {
  118. [self hideExplorer];
  119. }
  120. #pragma mark - Simulator Shortcuts
  121. - (void)registerSimulatorShortcutWithKey:(NSString *)key modifiers:(UIKeyModifierFlags)modifiers action:(dispatch_block_t)action description:(NSString *)description
  122. {
  123. # if TARGET_OS_SIMULATOR
  124. [[FLEXKeyboardShortcutManager sharedManager] registerSimulatorShortcutWithKey:key modifiers:modifiers action:action description:description];
  125. #endif
  126. }
  127. - (void)setSimulatorShortcutsEnabled:(BOOL)simulatorShortcutsEnabled
  128. {
  129. # if TARGET_OS_SIMULATOR
  130. [[FLEXKeyboardShortcutManager sharedManager] setEnabled:simulatorShortcutsEnabled];
  131. #endif
  132. }
  133. - (BOOL)simulatorShortcutsEnabled
  134. {
  135. # if TARGET_OS_SIMULATOR
  136. return [[FLEXKeyboardShortcutManager sharedManager] isEnabled];
  137. #else
  138. return NO;
  139. #endif
  140. }
  141. - (void)registerDefaultSimulatorShortcuts
  142. {
  143. [self registerSimulatorShortcutWithKey:@"f" modifiers:0 action:^{
  144. [self toggleExplorer];
  145. } description:@"Toggle FLEX toolbar"];
  146. [self registerSimulatorShortcutWithKey:@"g" modifiers:0 action:^{
  147. [self showExplorerIfNeeded];
  148. [self.explorerViewController toggleMenuTool];
  149. } description:@"Toggle FLEX globals menu"];
  150. [self registerSimulatorShortcutWithKey:@"v" modifiers:0 action:^{
  151. [self showExplorerIfNeeded];
  152. [self.explorerViewController toggleViewsTool];
  153. } description:@"Toggle view hierarchy menu"];
  154. [self registerSimulatorShortcutWithKey:@"s" modifiers:0 action:^{
  155. [self showExplorerIfNeeded];
  156. [self.explorerViewController toggleSelectTool];
  157. } description:@"Toggle select tool"];
  158. [self registerSimulatorShortcutWithKey:@"m" modifiers:0 action:^{
  159. [self showExplorerIfNeeded];
  160. [self.explorerViewController toggleMoveTool];
  161. } description:@"Toggle move tool"];
  162. [self registerSimulatorShortcutWithKey:@"n" modifiers:0 action:^{
  163. [self toggleTopViewControllerOfClass:[FLEXNetworkHistoryTableViewController class]];
  164. } description:@"Toggle network history view"];
  165. [self registerSimulatorShortcutWithKey:UIKeyInputDownArrow modifiers:0 action:^{
  166. if ([self isHidden]) {
  167. [self tryScrollDown];
  168. } else {
  169. [self.explorerViewController handleDownArrowKeyPressed];
  170. }
  171. } description:@"Cycle view selection\n\t\tMove view down\n\t\tScroll down"];
  172. [self registerSimulatorShortcutWithKey:UIKeyInputUpArrow modifiers:0 action:^{
  173. if ([self isHidden]) {
  174. [self tryScrollUp];
  175. } else {
  176. [self.explorerViewController handleUpArrowKeyPressed];
  177. }
  178. } description:@"Cycle view selection\n\t\tMove view up\n\t\tScroll up"];
  179. [self registerSimulatorShortcutWithKey:UIKeyInputRightArrow modifiers:0 action:^{
  180. if (![self isHidden]) {
  181. [self.explorerViewController handleRightArrowKeyPressed];
  182. }
  183. } description:@"Move selected view right"];
  184. [self registerSimulatorShortcutWithKey:UIKeyInputLeftArrow modifiers:0 action:^{
  185. if ([self isHidden]) {
  186. [self tryGoBack];
  187. } else {
  188. [self.explorerViewController handleLeftArrowKeyPressed];
  189. }
  190. } description:@"Move selected view left"];
  191. [self registerSimulatorShortcutWithKey:@"?" modifiers:0 action:^{
  192. [self toggleTopViewControllerOfClass:[FLEXKeyboardHelpViewController class]];
  193. } description:@"Toggle (this) help menu"];
  194. [self registerSimulatorShortcutWithKey:UIKeyInputEscape modifiers:0 action:^{
  195. [[[self topViewController] presentingViewController] dismissViewControllerAnimated:YES completion:nil];
  196. } description:@"End editing text\n\t\tDismiss top view controller"];
  197. [self registerSimulatorShortcutWithKey:@"o" modifiers:UIKeyModifierCommand|UIKeyModifierShift action:^{
  198. [self toggleTopViewControllerOfClass:[FLEXFileBrowserTableViewController class]];
  199. } description:@"Toggle file browser menu"];
  200. }
  201. + (void)load
  202. {
  203. dispatch_async(dispatch_get_main_queue(), ^{
  204. [[[self class] sharedManager] registerDefaultSimulatorShortcuts];
  205. });
  206. }
  207. #pragma mark - Extensions
  208. - (void)registerGlobalEntryWithName:(NSString *)entryName objectFutureBlock:(id (^)(void))objectFutureBlock
  209. {
  210. NSParameterAssert(entryName);
  211. NSParameterAssert(objectFutureBlock);
  212. NSAssert([NSThread isMainThread], @"This method must be called from the main thread.");
  213. entryName = entryName.copy;
  214. FLEXGlobalsTableViewControllerEntry *entry = [FLEXGlobalsTableViewControllerEntry entryWithNameFuture:^NSString *{
  215. return entryName;
  216. } viewControllerFuture:^UIViewController *{
  217. return [FLEXObjectExplorerFactory explorerViewControllerForObject:objectFutureBlock()];
  218. }];
  219. [self.userGlobalEntries addObject:entry];
  220. }
  221. - (void)registerGlobalEntryWithName:(NSString *)entryName viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock
  222. {
  223. NSParameterAssert(entryName);
  224. NSParameterAssert(viewControllerFutureBlock);
  225. NSAssert([NSThread isMainThread], @"This method must be called from the main thread.");
  226. entryName = entryName.copy;
  227. FLEXGlobalsTableViewControllerEntry *entry = [FLEXGlobalsTableViewControllerEntry entryWithNameFuture:^NSString *{
  228. return entryName;
  229. } viewControllerFuture:^UIViewController *{
  230. UIViewController *viewController = viewControllerFutureBlock();
  231. NSCAssert(viewController, @"'%@' entry returned nil viewController. viewControllerFutureBlock should never return nil.", entryName);
  232. return viewController;
  233. }];
  234. [self.userGlobalEntries addObject:entry];
  235. }
  236. - (void)tryScrollDown
  237. {
  238. UIScrollView *firstScrollView = [self firstScrollView];
  239. CGPoint contentOffset = [firstScrollView contentOffset];
  240. CGFloat distance = floor(firstScrollView.frame.size.height / 2.0);
  241. CGFloat maxContentOffsetY = firstScrollView.contentSize.height + firstScrollView.contentInset.bottom - firstScrollView.frame.size.height;
  242. distance = MIN(maxContentOffsetY - firstScrollView.contentOffset.y, distance);
  243. contentOffset.y += distance;
  244. [firstScrollView setContentOffset:contentOffset animated:YES];
  245. }
  246. - (void)tryScrollUp
  247. {
  248. UIScrollView *firstScrollView = [self firstScrollView];
  249. CGPoint contentOffset = [firstScrollView contentOffset];
  250. CGFloat distance = floor(firstScrollView.frame.size.height / 2.0);
  251. CGFloat minContentOffsetY = -firstScrollView.contentInset.top;
  252. distance = MIN(firstScrollView.contentOffset.y - minContentOffsetY, distance);
  253. contentOffset.y -= distance;
  254. [firstScrollView setContentOffset:contentOffset animated:YES];
  255. }
  256. - (UIScrollView *)firstScrollView
  257. {
  258. NSMutableArray<UIView *> *views = [[[[UIApplication sharedApplication] keyWindow] subviews] mutableCopy];
  259. UIScrollView *scrollView = nil;
  260. while ([views count] > 0) {
  261. UIView *view = [views firstObject];
  262. [views removeObjectAtIndex:0];
  263. if ([view isKindOfClass:[UIScrollView class]]) {
  264. scrollView = (UIScrollView *)view;
  265. break;
  266. } else {
  267. [views addObjectsFromArray:[view subviews]];
  268. }
  269. }
  270. return scrollView;
  271. }
  272. - (void)tryGoBack
  273. {
  274. UINavigationController *navigationController = nil;
  275. UIViewController *topViewController = [self topViewController];
  276. if ([topViewController isKindOfClass:[UINavigationController class]]) {
  277. navigationController = (UINavigationController *)topViewController;
  278. } else {
  279. navigationController = topViewController.navigationController;
  280. }
  281. [navigationController popViewControllerAnimated:YES];
  282. }
  283. - (UIViewController *)topViewController
  284. {
  285. UIViewController *topViewController = [[[UIApplication sharedApplication] keyWindow] rootViewController];
  286. while ([topViewController presentedViewController]) {
  287. topViewController = [topViewController presentedViewController];
  288. }
  289. return topViewController;
  290. }
  291. - (void)toggleTopViewControllerOfClass:(Class)class
  292. {
  293. UIViewController *topViewController = [self topViewController];
  294. if ([topViewController isKindOfClass:[UINavigationController class]] && [[[(UINavigationController *)topViewController viewControllers] firstObject] isKindOfClass:[class class]]) {
  295. [[topViewController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
  296. } else {
  297. id viewController = [[class alloc] init];
  298. UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
  299. [topViewController presentViewController:navigationController animated:YES completion:nil];
  300. }
  301. }
  302. - (void)showExplorerIfNeeded
  303. {
  304. if ([self isHidden]) {
  305. [self showExplorer];
  306. }
  307. }
  308. @end