FLEXManager.m 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. //
  2. // FLEXManager.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 4/4/14.
  6. // Copyright (c) 2020 FLEX Team. All rights reserved.
  7. //
  8. #import "FLEXManager.h"
  9. #import "FLEXUtility.h"
  10. #import "FLEXExplorerViewController.h"
  11. #import "FLEXWindow.h"
  12. #import "FLEXObjectExplorerViewController.h"
  13. #import "FLEXFileBrowserController.h"
  14. #import "NSObject+FLEX_Reflection.h"
  15. @interface FLEXManager () <FLEXWindowEventDelegate, FLEXExplorerViewControllerDelegate>
  16. @property (nonatomic, readonly, getter=isHidden) BOOL hidden;
  17. @property (nonatomic) FLEXWindow *explorerWindow;
  18. @property (nonatomic) FLEXExplorerViewController *explorerViewController;
  19. @property (nonatomic, readonly) NSMutableArray<FLEXGlobalsEntry *> *userGlobalEntries;
  20. @property (nonatomic, readonly) NSMutableDictionary<NSString *, FLEXCustomContentViewerFuture> *customContentTypeViewers;
  21. @end
  22. @implementation FLEXManager
  23. + (instancetype)sharedManager {
  24. static FLEXManager *sharedManager = nil;
  25. static dispatch_once_t onceToken;
  26. dispatch_once(&onceToken, ^{
  27. sharedManager = [self new];
  28. });
  29. return sharedManager;
  30. }
  31. - (instancetype)init {
  32. self = [super init];
  33. if (self) {
  34. _userGlobalEntries = [NSMutableArray new];
  35. _customContentTypeViewers = [NSMutableDictionary new];
  36. }
  37. return self;
  38. }
  39. - (FLEXWindow *)explorerWindow {
  40. NSAssert(NSThread.isMainThread, @"You must use %@ from the main thread only.", NSStringFromClass([self class]));
  41. if (!_explorerWindow) {
  42. _explorerWindow = [[FLEXWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
  43. _explorerWindow.eventDelegate = self;
  44. _explorerWindow.rootViewController = self.explorerViewController;
  45. }
  46. return _explorerWindow;
  47. }
  48. - (void)showHintsIfNecessary {
  49. BOOL dontShowHints = [[NSUserDefaults standardUserDefaults] boolForKey:@"DontShowHintsOnLaunch"];
  50. if (!dontShowHints){
  51. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  52. [self showHintsAlert];
  53. });
  54. }
  55. }
  56. - (void)showHintsAlert {
  57. UIAlertController *alertController = [UIAlertController
  58. alertControllerWithTitle:@"Usage Guide"
  59. message:@"Triple tap 'Play/Pause' to hide or show the explorer view.\nIn selection mode 'Menu' will exit to the toolbar, and a long press on 'Select' or 'Play/Pause' will bring up a contextual menu for view info & movement.\nWhen browsing view/object details a long press on 'Select' will bring up the controller to edit view details.\n If you inject into HeadBoard the siri remote will NOT work for navigation and you will need to use something like AirMagic to navigate."
  60. preferredStyle:UIAlertControllerStyleAlert];
  61. UIAlertAction *hideForeverAction = [UIAlertAction
  62. actionWithTitle:@"Don't Show This Again"
  63. style:UIAlertActionStyleDestructive
  64. handler:^(UIAlertAction *action)
  65. {
  66. [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"DontShowHintsOnLaunch"];
  67. [[NSUserDefaults standardUserDefaults] synchronize];
  68. [self showExplorer];
  69. }];
  70. UIAlertAction *showForeverAction = [UIAlertAction
  71. actionWithTitle:@"Always Show On Launch"
  72. style:UIAlertActionStyleDestructive
  73. handler:^(UIAlertAction *action)
  74. {
  75. [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"DontShowHintsOnLaunch"];
  76. [[NSUserDefaults standardUserDefaults] synchronize];
  77. [self showExplorer];
  78. }];
  79. UIAlertAction *cancelAction = [UIAlertAction
  80. actionWithTitle:@"Dismiss"
  81. style:UIAlertActionStyleCancel
  82. handler:^(UIAlertAction *action)
  83. {
  84. [self showExplorer];
  85. }];
  86. if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DontShowHintsOnLaunch"]) {
  87. [alertController addAction:showForeverAction];
  88. }
  89. else {
  90. [alertController addAction:hideForeverAction];
  91. }
  92. [alertController addAction:cancelAction];
  93. #if TARGET_OS_TV
  94. [[self topViewController] presentViewController:alertController animated:YES completion:nil];
  95. #endif
  96. }
  97. - (void)tripleTap:(UITapGestureRecognizer *)tapRecognizer {
  98. FXLog(@"triple tap!");
  99. if ([self isHidden]){
  100. [self showExplorer];
  101. } else {
  102. [self hideExplorer];
  103. }
  104. }
  105. - (void)_addTVOSGestureRecognizer:(UIViewController *)explorer {
  106. UITapGestureRecognizer *tripleTaps = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tripleTap:)];
  107. tripleTaps.allowedPressTypes = @[[NSNumber numberWithInteger:UIPressTypePlayPause]];
  108. tripleTaps.numberOfTapsRequired = 3;
  109. [explorer.view addGestureRecognizer:tripleTaps];
  110. }
  111. - (FLEXExplorerViewController *)explorerViewController {
  112. if (!_explorerViewController) {
  113. _explorerViewController = [FLEXExplorerViewController new];
  114. _explorerViewController.delegate = self;
  115. #if TARGET_OS_TV
  116. [self _addTVOSGestureRecognizer:_explorerViewController];
  117. #endif
  118. }
  119. return _explorerViewController;
  120. }
  121. - (void)showExplorer {
  122. UIWindow *flex = self.explorerWindow;
  123. flex.hidden = NO;
  124. #if TARGET_OS_TV
  125. FLEXWindow *exp = [self explorerWindow];
  126. [exp makeKeyWindow];
  127. #endif
  128. #if FLEX_AT_LEAST_IOS13_SDK
  129. if (@available(iOS 13.0, *)) {
  130. // Only look for a new scene if we don't have one
  131. if (!flex.windowScene) {
  132. flex.windowScene = FLEXUtility.activeScene;
  133. }
  134. }
  135. #endif
  136. }
  137. - (void)hideExplorer {
  138. self.explorerWindow.hidden = YES;
  139. }
  140. - (void)toggleExplorer {
  141. if (self.explorerWindow.isHidden) {
  142. [self showExplorer];
  143. } else {
  144. [self hideExplorer];
  145. }
  146. }
  147. - (void)showExplorerFromScene:(UIWindowScene *)scene {
  148. #if FLEX_AT_LEAST_IOS13_SDK
  149. if (@available(iOS 13.0, *)) {
  150. self.explorerWindow.windowScene = scene;
  151. }
  152. #endif
  153. self.explorerWindow.hidden = NO;
  154. }
  155. - (BOOL)isHidden {
  156. return self.explorerWindow.isHidden;
  157. }
  158. - (FLEXExplorerToolbar *)toolbar {
  159. return self.explorerViewController.explorerToolbar;
  160. }
  161. #pragma mark - FLEXWindowEventDelegate
  162. - (BOOL)shouldHandleTouchAtPoint:(CGPoint)pointInWindow {
  163. // Ask the explorer view controller
  164. return [self.explorerViewController shouldReceiveTouchAtWindowPoint:pointInWindow];
  165. }
  166. - (BOOL)canBecomeKeyWindow {
  167. // Only when the explorer view controller wants it because
  168. // it needs to accept key input & affect the status bar.
  169. return self.explorerViewController.wantsWindowToBecomeKey;
  170. }
  171. #pragma mark - FLEXExplorerViewControllerDelegate
  172. - (void)explorerViewControllerDidFinish:(FLEXExplorerViewController *)explorerViewController {
  173. [self hideExplorer];
  174. }
  175. @end