FLEXManager.m 13 KB

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