FLEXManager+Extensibility.m 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. //
  2. // FLEXManager+Extensibility.m
  3. // FLEX
  4. //
  5. // Created by Tanner on 2/2/20.
  6. // Copyright © 2020 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXManager+Extensibility.h"
  9. #import "FLEXManager+Private.h"
  10. #import "FLEXNavigationController.h"
  11. #import "FLEXGlobalsEntry.h"
  12. #import "FLEXObjectExplorerFactory.h"
  13. #import "FLEXKeyboardShortcutManager.h"
  14. #import "FLEXExplorerViewController.h"
  15. #import "FLEXNetworkHistoryTableViewController.h"
  16. #import "FLEXKeyboardHelpViewController.h"
  17. #import "FLEXFileBrowserTableViewController.h"
  18. @interface FLEXManager (ExtensibilityPrivate)
  19. @property (nonatomic, readonly) UIViewController *topViewController;
  20. @end
  21. @implementation FLEXManager (Extensibility)
  22. #pragma mark - Globals Screen Entries
  23. - (void)registerGlobalEntryWithName:(NSString *)entryName objectFutureBlock:(id (^)(void))objectFutureBlock {
  24. NSParameterAssert(entryName);
  25. NSParameterAssert(objectFutureBlock);
  26. NSAssert([NSThread isMainThread], @"This method must be called from the main thread.");
  27. entryName = entryName.copy;
  28. FLEXGlobalsEntry *entry = [FLEXGlobalsEntry entryWithNameFuture:^NSString *{
  29. return entryName;
  30. } viewControllerFuture:^UIViewController *{
  31. return [FLEXObjectExplorerFactory explorerViewControllerForObject:objectFutureBlock()];
  32. }];
  33. [self.userGlobalEntries addObject:entry];
  34. }
  35. - (void)registerGlobalEntryWithName:(NSString *)entryName viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock {
  36. NSParameterAssert(entryName);
  37. NSParameterAssert(viewControllerFutureBlock);
  38. NSAssert([NSThread isMainThread], @"This method must be called from the main thread.");
  39. entryName = entryName.copy;
  40. FLEXGlobalsEntry *entry = [FLEXGlobalsEntry entryWithNameFuture:^NSString *{
  41. return entryName;
  42. } viewControllerFuture:^UIViewController *{
  43. UIViewController *viewController = viewControllerFutureBlock();
  44. NSCAssert(viewController, @"'%@' entry returned nil viewController. viewControllerFutureBlock should never return nil.", entryName);
  45. return viewController;
  46. }];
  47. [self.userGlobalEntries addObject:entry];
  48. }
  49. #pragma mark - Simulator Shortcuts
  50. - (void)registerSimulatorShortcutWithKey:(NSString *)key modifiers:(UIKeyModifierFlags)modifiers action:(dispatch_block_t)action description:(NSString *)description {
  51. #if TARGET_OS_SIMULATOR
  52. [[FLEXKeyboardShortcutManager sharedManager] registerSimulatorShortcutWithKey:key modifiers:modifiers action:action description:description];
  53. #endif
  54. }
  55. - (void)setSimulatorShortcutsEnabled:(BOOL)simulatorShortcutsEnabled {
  56. #if TARGET_OS_SIMULATOR
  57. [[FLEXKeyboardShortcutManager sharedManager] setEnabled:simulatorShortcutsEnabled];
  58. #endif
  59. }
  60. - (BOOL)simulatorShortcutsEnabled {
  61. #if TARGET_OS_SIMULATOR
  62. return [[FLEXKeyboardShortcutManager sharedManager] isEnabled];
  63. #else
  64. return NO;
  65. #endif
  66. }
  67. - (void)registerDefaultSimulatorShortcuts {
  68. [self registerSimulatorShortcutWithKey:@"f" modifiers:0 action:^{
  69. [self toggleExplorer];
  70. } description:@"Toggle FLEX toolbar"];
  71. [self registerSimulatorShortcutWithKey:@"g" modifiers:0 action:^{
  72. [self showExplorerIfNeeded];
  73. [self.explorerViewController toggleMenuTool];
  74. } description:@"Toggle FLEX globals menu"];
  75. [self registerSimulatorShortcutWithKey:@"v" modifiers:0 action:^{
  76. [self showExplorerIfNeeded];
  77. [self.explorerViewController toggleViewsTool];
  78. } description:@"Toggle view hierarchy menu"];
  79. [self registerSimulatorShortcutWithKey:@"s" modifiers:0 action:^{
  80. [self showExplorerIfNeeded];
  81. [self.explorerViewController toggleSelectTool];
  82. } description:@"Toggle select tool"];
  83. [self registerSimulatorShortcutWithKey:@"m" modifiers:0 action:^{
  84. [self showExplorerIfNeeded];
  85. [self.explorerViewController toggleMoveTool];
  86. } description:@"Toggle move tool"];
  87. [self registerSimulatorShortcutWithKey:@"n" modifiers:0 action:^{
  88. [self toggleTopViewControllerOfClass:[FLEXNetworkHistoryTableViewController class]];
  89. } description:@"Toggle network history view"];
  90. [self registerSimulatorShortcutWithKey:UIKeyInputDownArrow modifiers:0 action:^{
  91. if (self.isHidden || ![self.explorerViewController handleDownArrowKeyPressed]) {
  92. [self tryScrollDown];
  93. }
  94. } description:@"Cycle view selection\n\t\tMove view down\n\t\tScroll down"];
  95. [self registerSimulatorShortcutWithKey:UIKeyInputUpArrow modifiers:0 action:^{
  96. if (self.isHidden || ![self.explorerViewController handleUpArrowKeyPressed]) {
  97. [self tryScrollUp];
  98. }
  99. } description:@"Cycle view selection\n\t\tMove view up\n\t\tScroll up"];
  100. [self registerSimulatorShortcutWithKey:UIKeyInputRightArrow modifiers:0 action:^{
  101. if (!self.isHidden) {
  102. [self.explorerViewController handleRightArrowKeyPressed];
  103. }
  104. } description:@"Move selected view right"];
  105. [self registerSimulatorShortcutWithKey:UIKeyInputLeftArrow modifiers:0 action:^{
  106. if (self.isHidden) {
  107. [self tryGoBack];
  108. } else {
  109. [self.explorerViewController handleLeftArrowKeyPressed];
  110. }
  111. } description:@"Move selected view left"];
  112. [self registerSimulatorShortcutWithKey:@"?" modifiers:0 action:^{
  113. [self toggleTopViewControllerOfClass:[FLEXKeyboardHelpViewController class]];
  114. } description:@"Toggle (this) help menu"];
  115. [self registerSimulatorShortcutWithKey:UIKeyInputEscape modifiers:0 action:^{
  116. [[self.topViewController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
  117. } description:@"End editing text\n\t\tDismiss top view controller"];
  118. [self registerSimulatorShortcutWithKey:@"o" modifiers:UIKeyModifierCommand|UIKeyModifierShift action:^{
  119. [self toggleTopViewControllerOfClass:[FLEXFileBrowserTableViewController class]];
  120. } description:@"Toggle file browser menu"];
  121. }
  122. + (void)load {
  123. dispatch_async(dispatch_get_main_queue(), ^{
  124. [self.sharedManager registerDefaultSimulatorShortcuts];
  125. });
  126. }
  127. #pragma mark - Private
  128. - (UIEdgeInsets)contentInsetsOfScrollView:(UIScrollView *)scrollView {
  129. if (@available(iOS 11, *)) {
  130. return scrollView.adjustedContentInset;
  131. }
  132. return scrollView.contentInset;
  133. }
  134. - (void)tryScrollDown {
  135. UIScrollView *scrollview = [self firstScrollView];
  136. UIEdgeInsets insets = [self contentInsetsOfScrollView:scrollview];
  137. CGPoint contentOffset = scrollview.contentOffset;
  138. CGFloat maxYOffset = scrollview.contentSize.height - scrollview.bounds.size.height + insets.bottom;
  139. contentOffset.y = MIN(contentOffset.y + 200, maxYOffset);
  140. [scrollview setContentOffset:contentOffset animated:YES];
  141. }
  142. - (void)tryScrollUp {
  143. UIScrollView *scrollview = [self firstScrollView];
  144. UIEdgeInsets insets = [self contentInsetsOfScrollView:scrollview];
  145. CGPoint contentOffset = scrollview.contentOffset;
  146. contentOffset.y = MAX(contentOffset.y - 200, -insets.top);
  147. [scrollview setContentOffset:contentOffset animated:YES];
  148. }
  149. - (UIScrollView *)firstScrollView {
  150. NSMutableArray<UIView *> *views = FLEXUtility.appKeyWindow.subviews.copy;
  151. UIScrollView *scrollView = nil;
  152. while (views.count > 0) {
  153. UIView *view = views.firstObject;
  154. [views removeObjectAtIndex:0];
  155. if ([view isKindOfClass:[UIScrollView class]]) {
  156. scrollView = (UIScrollView *)view;
  157. break;
  158. } else {
  159. [views addObjectsFromArray:view.subviews];
  160. }
  161. }
  162. return scrollView;
  163. }
  164. - (void)tryGoBack {
  165. UINavigationController *navigationController = nil;
  166. UIViewController *topViewController = self.topViewController;
  167. if ([topViewController isKindOfClass:[UINavigationController class]]) {
  168. navigationController = (UINavigationController *)topViewController;
  169. } else {
  170. navigationController = topViewController.navigationController;
  171. }
  172. [navigationController popViewControllerAnimated:YES];
  173. }
  174. - (UIViewController *)topViewController {
  175. UIViewController *topViewController = UIApplication.sharedApplication.keyWindow.rootViewController;
  176. while (topViewController.presentedViewController) {
  177. topViewController = topViewController.presentedViewController;
  178. }
  179. return topViewController;
  180. }
  181. - (void)toggleTopViewControllerOfClass:(Class)class {
  182. UINavigationController *topViewController = (id)self.topViewController;
  183. if ([topViewController isKindOfClass:[UINavigationController class]] &&
  184. [topViewController.topViewController isKindOfClass:[class class]]) {
  185. [topViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
  186. } else {
  187. id viewController = [class new];
  188. UINavigationController *navigationController = [[FLEXNavigationController alloc] initWithRootViewController:viewController];
  189. [topViewController presentViewController:navigationController animated:YES completion:nil];
  190. }
  191. }
  192. - (void)showExplorerIfNeeded {
  193. if (self.isHidden) {
  194. [self showExplorer];
  195. }
  196. }
  197. @end