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 toggle explorer view visibility.\nIn selection mode 'Menu' will re-enable the toolbar, and a long press on 'Select', 'Play/Pause' or a 'Right siri tap' triggers a contextual menu for view info & movement.\nBrowsing object details long press on 'Select' or a 'Right siri tap' triggers a detail editor.\nInjecting into HeadBoard BREAKS SIRI REMOTE. Suggest 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