react-native-tvos 0.64.2-1 → 0.64.2-5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/Libraries/Components/AppleTV/TVFocusGuideView.js +24 -1
  2. package/Libraries/Components/Pressable/Pressable.js +4 -4
  3. package/Libraries/Components/StatusBar/NativeStatusBarManagerIOS.js +14 -8
  4. package/Libraries/Components/Touchable/TouchableHighlight.js +1 -1
  5. package/Libraries/NewAppScreen/components/HermesBadge.js +39 -12
  6. package/README.md +8 -2
  7. package/React/Base/RCTRootView.m +5 -35
  8. package/React/Base/RCTTVRemoteHandler.h +5 -2
  9. package/React/Base/RCTTVRemoteHandler.m +173 -70
  10. package/React/Modules/RCTTVMenuBridge.m +3 -0
  11. package/React/Views/RCTModalHostView.m +6 -9
  12. package/React/Views/RCTTVFocusGuideView.m +1 -1
  13. package/ReactAndroid/gradle.properties +1 -1
  14. package/ReactAndroid/src/androidTest/assets/AndroidTestBundle.js +47 -24
  15. package/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +10 -9
  16. package/ReactAndroid/src/main/java/com/facebook/react/modules/core/ReactAndroidHWInputDeviceHelper.java +133 -0
  17. package/ReactAndroid/src/main/java/com/facebook/react/views/modal/BUCK +1 -0
  18. package/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java +21 -0
  19. package/ReactCommon/hermes/inspector/tools/msggen/yarn.lock +3 -3
  20. package/ReactCommon/react/renderer/components/rncore/ComponentDescriptors.h +32 -0
  21. package/ReactCommon/react/renderer/components/rncore/ComponentDescriptors.h.bak +32 -0
  22. package/ReactCommon/react/renderer/components/rncore/EventEmitters.cpp +149 -0
  23. package/ReactCommon/react/renderer/components/rncore/EventEmitters.cpp.bak +149 -0
  24. package/ReactCommon/react/renderer/components/rncore/EventEmitters.h +237 -0
  25. package/ReactCommon/react/renderer/components/rncore/EventEmitters.h.bak +237 -0
  26. package/ReactCommon/react/renderer/components/rncore/Props.cpp +218 -0
  27. package/ReactCommon/react/renderer/components/rncore/Props.cpp.bak +218 -0
  28. package/ReactCommon/react/renderer/components/rncore/Props.h +576 -0
  29. package/ReactCommon/react/renderer/components/rncore/Props.h.bak +576 -0
  30. package/ReactCommon/react/renderer/components/rncore/RCTComponentViewHelpers.h +273 -0
  31. package/ReactCommon/react/renderer/components/rncore/RCTComponentViewHelpers.h.bak +273 -0
  32. package/ReactCommon/react/renderer/components/rncore/ShadowNodes.cpp +29 -0
  33. package/ReactCommon/react/renderer/components/rncore/ShadowNodes.cpp.bak +29 -0
  34. package/ReactCommon/react/renderer/components/rncore/ShadowNodes.h +126 -0
  35. package/ReactCommon/react/renderer/components/rncore/ShadowNodes.h.bak +126 -0
  36. package/android/com/facebook/react/react-native/{0.64.2-1/react-native-0.64.2-1-sources.jar → 0.64.2-5/react-native-0.64.2-5-sources.jar} +0 -0
  37. package/android/com/facebook/react/react-native/0.64.2-5/react-native-0.64.2-5-sources.jar.md5 +1 -0
  38. package/android/com/facebook/react/react-native/0.64.2-5/react-native-0.64.2-5-sources.jar.sha1 +1 -0
  39. package/android/com/facebook/react/react-native/0.64.2-5/react-native-0.64.2-5.aar +0 -0
  40. package/android/com/facebook/react/react-native/0.64.2-5/react-native-0.64.2-5.aar.md5 +1 -0
  41. package/android/com/facebook/react/react-native/0.64.2-5/react-native-0.64.2-5.aar.sha1 +1 -0
  42. package/android/com/facebook/react/react-native/{0.64.2-1/react-native-0.64.2-1.pom → 0.64.2-5/react-native-0.64.2-5.pom} +1 -1
  43. package/android/com/facebook/react/react-native/0.64.2-5/react-native-0.64.2-5.pom.md5 +1 -0
  44. package/android/com/facebook/react/react-native/0.64.2-5/react-native-0.64.2-5.pom.sha1 +1 -0
  45. package/android/com/facebook/react/react-native/maven-metadata.xml +3 -3
  46. package/android/com/facebook/react/react-native/maven-metadata.xml.md5 +1 -1
  47. package/android/com/facebook/react/react-native/maven-metadata.xml.sha1 +1 -1
  48. package/local-cli/cli.js +0 -0
  49. package/package.json +1 -1
  50. package/template/App.js +1 -2
  51. package/template/ios/HelloWorld.xcodeproj/project.pbxproj.rej +1034 -0
  52. package/template/package.json +1 -1
  53. package/Libraries/.npmignore +0 -1
  54. package/ReactAndroid/.npmignore +0 -3
  55. package/ReactAndroid/build.gradle.orig +0 -479
  56. package/ReactAndroid/src/main/java/com/facebook/react/ReactAndroidHWInputDeviceHelper.java +0 -98
  57. package/ReactAndroid/src/main/jni/first-party/fbjni/.gitignore +0 -2
  58. package/ReactCommon/hermes/inspector/tools/msggen/.gitignore +0 -3
  59. package/android/com/facebook/react/react-native/0.64.2-1/react-native-0.64.2-1-sources.jar.md5 +0 -1
  60. package/android/com/facebook/react/react-native/0.64.2-1/react-native-0.64.2-1-sources.jar.sha1 +0 -1
  61. package/android/com/facebook/react/react-native/0.64.2-1/react-native-0.64.2-1.aar +0 -0
  62. package/android/com/facebook/react/react-native/0.64.2-1/react-native-0.64.2-1.aar.md5 +0 -1
  63. package/android/com/facebook/react/react-native/0.64.2-1/react-native-0.64.2-1.aar.sha1 +0 -1
  64. package/android/com/facebook/react/react-native/0.64.2-1/react-native-0.64.2-1.pom.md5 +0 -1
  65. package/android/com/facebook/react/react-native/0.64.2-1/react-native-0.64.2-1.pom.sha1 +0 -1
  66. package/third-party-podspecs/hermes-engine.podspec +0 -45
@@ -42,9 +42,19 @@ class TVFocusGuideView extends React.Component<FocusGuideProps> {
42
42
  return (
43
43
  // Container view must have nonzero size
44
44
  <ReactNative.View style={[{minHeight: 1, minWidth: 1}, this.props.style]}>
45
+ {
46
+ /**
47
+ * The client specified layout(using 'style' prop) should be applied the container view ReactNative.View.
48
+ * And the focusGuide's layout shoule be overrided to wrap it fully inside the container view.
49
+ * For example, if the client specifies 'marginLeft' property in the style prop,
50
+ * then the TVFocusGuideView will apply the 'marginLeft' for both the parentView and the focusGuideView.
51
+ * and so, the left margin is getting added twice and UI becomes incorrect.
52
+ * The same is applicable for other layout properties.
53
+ */
54
+ }
45
55
  {Platform.isTVOS ? (
46
56
  <RNFocusGuide
47
- style={this.props.style}
57
+ style={[this.props.style, styles.focusGuideLayout]}
48
58
  ref={ref => (this._focusGuideRef = ref)}
49
59
  destinationTags={this._destinationTags}>
50
60
  {this.props.children}
@@ -56,6 +66,19 @@ class TVFocusGuideView extends React.Component<FocusGuideProps> {
56
66
  }
57
67
  }
58
68
 
69
+ const styles = ReactNative.StyleSheet.create({
70
+ focusGuideLayout: {
71
+ left: 0,
72
+ top: 0,
73
+ right: 0,
74
+ bottom: 0,
75
+ marginLeft: 0,
76
+ marginTop: 0,
77
+ marginRight: 0,
78
+ marginBottom: 0,
79
+ },
80
+ });
81
+
59
82
  const RNFocusGuide = requireNativeComponent('RCTTVFocusGuideView');
60
83
 
61
84
  module.exports = TVFocusGuideView;
@@ -201,6 +201,7 @@ function Pressable(props: Props, forwardedRef): React.Node {
201
201
  ...android_rippleConfig?.viewProps,
202
202
  accessible: accessible !== false,
203
203
  focusable: focusable !== false,
204
+ isTVSelectable: isTVSelectable !== false && accessible !== false,
204
205
  hitSlop,
205
206
  };
206
207
 
@@ -257,7 +258,7 @@ function Pressable(props: Props, forwardedRef): React.Node {
257
258
 
258
259
  const pressableTVEventHandler = (evt: Event) => {
259
260
  if (props.isTVSelectable !== false || props.focusable !== false) {
260
- if (viewRef.current._nativeTag === evt.target) {
261
+ if (viewRef.current && viewRef.current._nativeTag === evt.target) {
261
262
  if (evt?.eventType === 'focus') {
262
263
  setFocused(true);
263
264
  onFocus && onFocus(evt);
@@ -285,10 +286,9 @@ function Pressable(props: Props, forwardedRef): React.Node {
285
286
  {...restPropsWithDefaults}
286
287
  {...eventHandlers}
287
288
  ref={viewRef}
288
- isTVSelectable={isTVSelectable !== false && accessible !== false}
289
- style={typeof style === 'function' ? style({pressed}) : style}
289
+ style={typeof style === 'function' ? style({pressed, focused}) : style}
290
290
  collapsable={false}>
291
- {typeof children === 'function' ? children({pressed}) : children}
291
+ {typeof children === 'function' ? children({pressed, focused}) : children}
292
292
  {__DEV__ ? <PressabilityDebugView color="red" hitSlop={hitSlop} /> : null}
293
293
  </View>
294
294
  );
@@ -13,6 +13,8 @@
13
13
  import type {TurboModule} from '../../TurboModule/RCTExport';
14
14
  import * as TurboModuleRegistry from '../../TurboModule/TurboModuleRegistry';
15
15
 
16
+ const Platform = require('../../Utilities/Platform');
17
+
16
18
  export interface Spec extends TurboModule {
17
19
  +getConstants: () => {|
18
20
  +HEIGHT: number,
@@ -38,7 +40,9 @@ export interface Spec extends TurboModule {
38
40
  +setHidden: (hidden: boolean, withAnimation: string) => void;
39
41
  }
40
42
 
41
- const NativeModule = TurboModuleRegistry.getEnforcing<Spec>('StatusBarManager');
43
+ const NativeModule = Platform.isTVOS
44
+ ? null
45
+ : TurboModuleRegistry.getEnforcing<Spec>('StatusBarManager');
42
46
  let constants = null;
43
47
 
44
48
  const NativeStatusBarManager = {
@@ -47,26 +51,28 @@ const NativeStatusBarManager = {
47
51
  +DEFAULT_BACKGROUND_COLOR?: number,
48
52
  |} {
49
53
  if (constants == null) {
50
- constants = NativeModule.getConstants();
54
+ constants = NativeModule
55
+ ? NativeModule.getConstants()
56
+ : {HEIGHT: 0, DEFAULT_BACKGROUND_COLOR: 0};
51
57
  }
52
58
  return constants;
53
59
  },
54
60
 
55
61
  // TODO(T47754272) Can we remove this method?
56
62
  getHeight(callback: (result: {|height: number|}) => void): void {
57
- NativeModule.getHeight(callback);
63
+ NativeModule ? NativeModule.getHeight(callback) : callback({height: 0});
58
64
  },
59
65
 
60
66
  setNetworkActivityIndicatorVisible(visible: boolean): void {
61
- NativeModule.setNetworkActivityIndicatorVisible(visible);
67
+ NativeModule && NativeModule.setNetworkActivityIndicatorVisible(visible);
62
68
  },
63
69
 
64
70
  addListener(eventType: string): void {
65
- NativeModule.addListener(eventType);
71
+ NativeModule && NativeModule.addListener(eventType);
66
72
  },
67
73
 
68
74
  removeListeners(count: number): void {
69
- NativeModule.removeListeners(count);
75
+ NativeModule && NativeModule.removeListeners(count);
70
76
  },
71
77
 
72
78
  /**
@@ -76,14 +82,14 @@ const NativeStatusBarManager = {
76
82
  * - 'light-content'
77
83
  */
78
84
  setStyle(statusBarStyle?: ?string, animated: boolean): void {
79
- NativeModule.setStyle(statusBarStyle, animated);
85
+ NativeModule && NativeModule.setStyle(statusBarStyle, animated);
80
86
  },
81
87
 
82
88
  /**
83
89
  * - withAnimation can be: 'none' | 'fade' | 'slide'
84
90
  */
85
91
  setHidden(hidden: boolean, withAnimation: string): void {
86
- NativeModule.setHidden(hidden, withAnimation);
92
+ NativeModule && NativeModule.setHidden(hidden, withAnimation);
87
93
  },
88
94
  };
89
95
 
@@ -354,7 +354,7 @@ class TouchableHighlight extends React.Component<Props, State> {
354
354
  }
355
355
  },
356
356
  onPress: event => {
357
- if (this.props.onPress != null) {
357
+ if (this.props.onPress != null && Platform.OS !== 'android') {
358
358
  this.props.onPress(event);
359
359
  }
360
360
  },
@@ -10,24 +10,51 @@
10
10
 
11
11
  import React from 'react';
12
12
  import type {Node} from 'react';
13
- import {StyleSheet, Text, useColorScheme, View} from 'react-native';
13
+ import {
14
+ Platform,
15
+ StyleSheet,
16
+ Text,
17
+ useColorScheme,
18
+ useTVEventHandler,
19
+ View,
20
+ } from 'react-native';
14
21
  import Colors from './Colors';
15
22
 
16
23
  const HermesBadge = (): Node => {
24
+ const [lastEventType, setLastEventType] = React.useState('');
25
+ const myTVEventHandler = evt => {
26
+ setLastEventType(evt.eventType);
27
+ };
28
+ if (Platform.isTV) {
29
+ useTVEventHandler(myTVEventHandler);
30
+ }
17
31
  const isDarkMode = useColorScheme() === 'dark';
18
- return global.HermesInternal ? (
32
+ return (
19
33
  <View style={styles.badge}>
20
- <Text
21
- style={[
22
- styles.badgeText,
23
- {
24
- color: isDarkMode ? Colors.light : Colors.dark,
25
- },
26
- ]}>
27
- Engine: Hermes
28
- </Text>
34
+ {Platform.isTV ? (
35
+ <Text
36
+ style={[
37
+ styles.badgeText,
38
+ {
39
+ color: isDarkMode ? Colors.light : Colors.dark,
40
+ },
41
+ ]}>
42
+ TVEvent: {lastEventType}
43
+ </Text>
44
+ ) : null}
45
+ {global.HermesInternal ? (
46
+ <Text
47
+ style={[
48
+ styles.badgeText,
49
+ {
50
+ color: isDarkMode ? Colors.light : Colors.dark,
51
+ },
52
+ ]}>
53
+ Engine: Hermes
54
+ </Text>
55
+ ) : null}
29
56
  </View>
30
- ) : null;
57
+ );
31
58
  };
32
59
 
33
60
  const styles = StyleSheet.create({
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Going forward, Apple TV support for React Native will be maintained here and in the corresponding `react-native-tvos` NPM package, and not in the [core repo](https://github.com/facebook/react-native/). This is a full fork of the main repository, with only the changes needed to support Apple TV.
4
4
 
5
- Releases of `react-native-tvos` will be based on a public release of `react-native`; e.g. the 0.64.2-0 release of this package will be derived from the 0.64.2 release of `react-native`. All releases of this repo will follow the 0.xx.x-y format, where x digits are from a specific RN core release, and y represents the additional versioning from this repo.
5
+ Releases of `react-native-tvos` will be based on a public release of `react-native`; e.g. the 0.64.2-5 release of this package will be derived from the 0.64.2 release of `react-native`. All releases of this repo will follow the 0.xx.x-y format, where x digits are from a specific RN core release, and y represents the additional versioning from this repo.
6
6
 
7
7
  Releases will be published on npmjs.org and you may find the latest release version here: https://www.npmjs.com/package/react-native-tvos?activeTab=versions or use the tag `@latest`
8
8
 
@@ -23,6 +23,8 @@ Due to the nature of the typing resolution, the current solution to include type
23
23
  - install `@types/react-native` as a dev dependency
24
24
  - put `import 'react-native/tvos-types.d'` in any of your `.ts` files (root suggested)
25
25
 
26
+ See the "Build Changes" section below for how to start a new project that will automatically use Typescript.
27
+
26
28
  ## General support for Apple TV
27
29
 
28
30
  TV devices support has been implemented with the intention of making existing React Native applications "just work" on Apple TV, with few or no changes needed in the JavaScript code for the applications.
@@ -67,7 +69,11 @@ react-native init TestApp --template=react-native-tvos@latest
67
69
  cd TestApp && react-native run-ios --simulator "Apple TV" --scheme "TestApp-tvOS"
68
70
  ```
69
71
 
72
+ A minimal Typescript starter template can be used to start a new project using the community react-native CLI. The process is the same as above except for the template:
70
73
 
74
+ ```sh
75
+ react-native init TestApp --template=react-native-template-typescript-tv
76
+ ```
71
77
 
72
78
  - _JavaScript layer_: Support for Apple TV has been added to `Platform.ios.js`. You can check whether code is running on AppleTV by doing
73
79
 
@@ -92,7 +98,7 @@ var running_on_apple_tv = Platform.isTVOS;
92
98
  - `onBlur` will be executed when the touchable view goes out of focus
93
99
  - `onPress` will be executed when the touchable view is actually selected by pressing the "select" button on the TV remote.
94
100
 
95
- - _TV remote/keyboard input_: A native class, `RCTTVRemoteHandler`, sets up gesture recognizers for TV remote events. When TV remote events occur, this class fires notifications that are picked up by `RCTTVNavigationEventEmitter` (a subclass of `RCTEventEmitter`), that fires a JS event. This event will be picked up by instances of the `TVEventHandler` JavaScript object. Application code that needs to implement custom handling of TV remote events can create an instance of `TVEventHandler` and listen for these events. In 0.63.1-1, we have added `useTVEventHandler`, which wraps `useEffect` to make this more convenient and simpler for use with functional components.
101
+ - _TV remote/keyboard input_: A native class, `RCTTVRemoteHandler`, sets up gesture recognizers for TV remote events. When TV remote events occur, this class fires notifications that are picked up by `RCTTVNavigationEventEmitter` (a subclass of `RCTEventEmitter`), that fires a JS event. This event will be picked up by instances of the `TVEventHandler` JavaScript object. Application code that needs to implement custom handling of TV remote events can create an instance of `TVEventHandler` and listen for these events. In 0.63.1-1, we have added `useTVEventHandler`, which wraps `useEffect` to make this more convenient and simpler for use with functional components. In 0.64.2-2, we added a TV event display to the new app template using `useTVEventHandler`.
96
102
 
97
103
  ```javascript
98
104
 
@@ -98,15 +98,6 @@ NSString *const RCTTVDisableMenuKeyNotification = @"RCTTVDisableMenuKeyNotificat
98
98
  object:self];
99
99
 
100
100
  #if TARGET_OS_TV
101
- [[NSNotificationCenter defaultCenter] addObserver:self
102
- selector:@selector(enableTVMenuKey)
103
- name:RCTTVEnableMenuKeyNotification
104
- object:nil];
105
-
106
- [[NSNotificationCenter defaultCenter] addObserver:self
107
- selector:@selector(disableTVMenuKey)
108
- name:RCTTVDisableMenuKeyNotification
109
- object:nil];
110
101
 
111
102
  #if RCT_DEV
112
103
  [[NSNotificationCenter defaultCenter] addObserver:self
@@ -115,11 +106,7 @@ NSString *const RCTTVDisableMenuKeyNotification = @"RCTTVDisableMenuKeyNotificat
115
106
  object:nil];
116
107
  #endif
117
108
 
118
- self.tvRemoteHandler = [RCTTVRemoteHandler new];
119
- for (NSString *key in [self.tvRemoteHandler.tvRemoteGestureRecognizers allKeys]) {
120
- [self addGestureRecognizer:self.tvRemoteHandler.tvRemoteGestureRecognizers[key]];
121
- }
122
- // [self addGestureRecognizer:self.tvRemoteHandler.tvMenuKeyRecognizer];
109
+ self.tvRemoteHandler = [[RCTTVRemoteHandler alloc] initWithView:self];
123
110
  #endif
124
111
 
125
112
  [self showLoadingView];
@@ -134,32 +121,12 @@ NSString *const RCTTVDisableMenuKeyNotification = @"RCTTVDisableMenuKeyNotificat
134
121
  return self;
135
122
  }
136
123
 
137
- #if TARGET_OS_TV
138
-
139
- - (void)enableTVMenuKey {
140
- dispatch_async(dispatch_get_main_queue(), ^{
141
- if (![[self gestureRecognizers] containsObject:self.tvRemoteHandler.tvMenuKeyRecognizer]) {
142
- [self addGestureRecognizer:self.tvRemoteHandler.tvMenuKeyRecognizer];
143
- }
144
- });
145
- }
146
-
147
- - (void)disableTVMenuKey {
148
- dispatch_async(dispatch_get_main_queue(), ^{
149
- if ([[self gestureRecognizers] containsObject:self.tvRemoteHandler.tvMenuKeyRecognizer]) {
150
- [self removeGestureRecognizer:self.tvRemoteHandler.tvMenuKeyRecognizer];
151
- }
152
- });
153
- }
154
-
155
- #if RCT_DEV
124
+ #if TARGET_OS_TV && RCT_DEV
156
125
  - (void)showDevMenu {
157
126
  dispatch_async(dispatch_get_main_queue(), ^{
158
127
  [[self->_bridge devMenu] show];
159
128
  });
160
129
  }
161
- #endif
162
-
163
130
  #endif // TARGET_OS_TV
164
131
 
165
132
  - (instancetype)initWithBundleURL:(NSURL *)bundleURL
@@ -443,6 +410,9 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder)
443
410
 
444
411
  - (void)dealloc
445
412
  {
413
+ #if TARGET_OS_TV
414
+ self.tvRemoteHandler = nil;
415
+ #endif
446
416
  [_contentView invalidate];
447
417
  }
448
418
 
@@ -26,7 +26,10 @@ extern NSString *const RCTTVRemoteEventSwipeDown;
26
26
 
27
27
  @interface RCTTVRemoteHandler : NSObject
28
28
 
29
- @property (nonatomic, copy, readonly) NSDictionary *tvRemoteGestureRecognizers;
30
- @property (nonatomic, strong) UITapGestureRecognizer *tvMenuKeyRecognizer;
29
+ - (instancetype _Nonnull )initWithView:(UIView * _Nonnull)view;
30
+ - (instancetype _Nonnull )init __attribute__((unavailable("init not available, use initWithView:")));
31
+
32
+ + (BOOL)useMenuKey;
33
+ + (void)setUseMenuKey:(BOOL)useMenuKey;
31
34
 
32
35
  @end
@@ -37,87 +37,189 @@ NSString *const RCTTVRemoteEventSwipeRight = @"swipeRight";
37
37
  NSString *const RCTTVRemoteEventSwipeUp = @"swipeUp";
38
38
  NSString *const RCTTVRemoteEventSwipeDown = @"swipeDown";
39
39
 
40
+ @interface RCTTVRemoteHandler()
41
+
42
+ @property (nonatomic, copy, readonly) NSDictionary *tvRemoteGestureRecognizers;
43
+ @property (nonatomic, strong) UITapGestureRecognizer *tvMenuKeyRecognizer;
44
+ @property (nonatomic, weak) UIView *view;
45
+
46
+ @end
47
+
40
48
  @implementation RCTTVRemoteHandler {
41
49
  NSMutableDictionary<NSString *, UIGestureRecognizer *> *_tvRemoteGestureRecognizers;
42
50
  }
43
51
 
44
- - (instancetype)init
52
+ #pragma mark -
53
+ #pragma mark Static setting for using menu key
54
+
55
+ static __volatile BOOL __useMenuKey = NO;
56
+
57
+ + (BOOL)useMenuKey
58
+ {
59
+ return __useMenuKey;
60
+ }
61
+
62
+ + (void)setUseMenuKey:(BOOL)useMenuKey
63
+ {
64
+ __useMenuKey = useMenuKey;
65
+ }
66
+
67
+ #pragma mark -
68
+ #pragma mark Public methods
69
+
70
+ - (instancetype)initWithView:(UIView *)view
45
71
  {
46
72
  if ((self = [super init])) {
73
+ _view = view;
74
+ [self setUpGestureRecognizers];
75
+ [self attachToView];
76
+ }
77
+ return self;
78
+ }
79
+
80
+ - (void)dealloc
81
+ {
82
+ [self detachFromView];
83
+ }
84
+
85
+ #pragma mark -
86
+ #pragma mark Private methods
87
+
88
+ - (void)setUpGestureRecognizers
89
+ {
47
90
  _tvRemoteGestureRecognizers = [NSMutableDictionary dictionary];
91
+ // Recognizers for Apple TV remote buttons
92
+ // Menu recognizer
93
+ self.tvMenuKeyRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(menuPressed:)];
94
+ self.tvMenuKeyRecognizer.allowedPressTypes = @[@(UIPressTypeMenu)];
95
+
96
+ // Play/Pause
97
+ [self addTapGestureRecognizerWithSelector:@selector(playPausePressed:)
98
+ pressType:UIPressTypePlayPause
99
+ name:RCTTVRemoteEventPlayPause];
100
+
101
+ // Select
102
+ [self addTapGestureRecognizerWithSelector:@selector(selectPressed:)
103
+ pressType:UIPressTypeSelect
104
+ name:RCTTVRemoteEventSelect];
105
+
106
+ // Up
107
+ [self addTapGestureRecognizerWithSelector:@selector(tappedUp:)
108
+ pressType:UIPressTypeUpArrow
109
+ name:RCTTVRemoteEventUp];
110
+
111
+ // Down
112
+ [self addTapGestureRecognizerWithSelector:@selector(tappedDown:)
113
+ pressType:UIPressTypeDownArrow
114
+ name:RCTTVRemoteEventDown];
115
+
116
+ // Left
117
+ [self addTapGestureRecognizerWithSelector:@selector(tappedLeft:)
118
+ pressType:UIPressTypeLeftArrow
119
+ name:RCTTVRemoteEventLeft];
120
+
121
+ // Right
122
+ [self addTapGestureRecognizerWithSelector:@selector(tappedRight:)
123
+ pressType:UIPressTypeRightArrow
124
+ name:RCTTVRemoteEventRight];
125
+
126
+ // Recognizers for long button presses
127
+ // We don't intercept long menu press -- that's used by the system to go to the home screen
128
+
129
+ [self addLongPressGestureRecognizerWithSelector:@selector(longPlayPausePressed:)
130
+ pressType:UIPressTypePlayPause
131
+ name:RCTTVRemoteEventLongPlayPause];
132
+
133
+ [self addLongPressGestureRecognizerWithSelector:@selector(longSelectPressed:)
134
+ pressType:UIPressTypeSelect
135
+ name:RCTTVRemoteEventLongSelect];
136
+
137
+ // Recognizers for Apple TV remote trackpad swipes
138
+
139
+ // Up
140
+ [self addSwipeGestureRecognizerWithSelector:@selector(swipedUp:)
141
+ direction:UISwipeGestureRecognizerDirectionUp
142
+ name:RCTTVRemoteEventSwipeUp];
143
+
144
+ // Down
145
+ [self addSwipeGestureRecognizerWithSelector:@selector(swipedDown:)
146
+ direction:UISwipeGestureRecognizerDirectionDown
147
+ name:RCTTVRemoteEventSwipeDown];
148
+
149
+ // Left
150
+ [self addSwipeGestureRecognizerWithSelector:@selector(swipedLeft:)
151
+ direction:UISwipeGestureRecognizerDirectionLeft
152
+ name:RCTTVRemoteEventSwipeLeft];
153
+
154
+ // Right
155
+ [self addSwipeGestureRecognizerWithSelector:@selector(swipedRight:)
156
+ direction:UISwipeGestureRecognizerDirectionRight
157
+ name:RCTTVRemoteEventSwipeRight];
48
158
 
49
- // Recognizers for Apple TV remote buttons
50
- // Menu recognizer
51
- self.tvMenuKeyRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(menuPressed:)];
52
- self.tvMenuKeyRecognizer.allowedPressTypes = @[@(UIPressTypeMenu)];
53
-
54
- // Play/Pause
55
- [self addTapGestureRecognizerWithSelector:@selector(playPausePressed:)
56
- pressType:UIPressTypePlayPause
57
- name:RCTTVRemoteEventPlayPause];
58
-
59
- // Select
60
- [self addTapGestureRecognizerWithSelector:@selector(selectPressed:)
61
- pressType:UIPressTypeSelect
62
- name:RCTTVRemoteEventSelect];
63
-
64
- // Up
65
- [self addTapGestureRecognizerWithSelector:@selector(tappedUp:)
66
- pressType:UIPressTypeUpArrow
67
- name:RCTTVRemoteEventUp];
68
-
69
- // Down
70
- [self addTapGestureRecognizerWithSelector:@selector(tappedDown:)
71
- pressType:UIPressTypeDownArrow
72
- name:RCTTVRemoteEventDown];
73
-
74
- // Left
75
- [self addTapGestureRecognizerWithSelector:@selector(tappedLeft:)
76
- pressType:UIPressTypeLeftArrow
77
- name:RCTTVRemoteEventLeft];
78
-
79
- // Right
80
- [self addTapGestureRecognizerWithSelector:@selector(tappedRight:)
81
- pressType:UIPressTypeRightArrow
82
- name:RCTTVRemoteEventRight];
83
-
84
- // Recognizers for long button presses
85
- // We don't intercept long menu press -- that's used by the system to go to the home screen
86
-
87
- [self addLongPressGestureRecognizerWithSelector:@selector(longPlayPausePressed:)
88
- pressType:UIPressTypePlayPause
89
- name:RCTTVRemoteEventLongPlayPause];
90
-
91
- [self addLongPressGestureRecognizerWithSelector:@selector(longSelectPressed:)
92
- pressType:UIPressTypeSelect
93
- name:RCTTVRemoteEventLongSelect];
94
-
95
- // Recognizers for Apple TV remote trackpad swipes
96
-
97
- // Up
98
- [self addSwipeGestureRecognizerWithSelector:@selector(swipedUp:)
99
- direction:UISwipeGestureRecognizerDirectionUp
100
- name:RCTTVRemoteEventSwipeUp];
101
-
102
- // Down
103
- [self addSwipeGestureRecognizerWithSelector:@selector(swipedDown:)
104
- direction:UISwipeGestureRecognizerDirectionDown
105
- name:RCTTVRemoteEventSwipeDown];
106
-
107
- // Left
108
- [self addSwipeGestureRecognizerWithSelector:@selector(swipedLeft:)
109
- direction:UISwipeGestureRecognizerDirectionLeft
110
- name:RCTTVRemoteEventSwipeLeft];
111
-
112
- // Right
113
- [self addSwipeGestureRecognizerWithSelector:@selector(swipedRight:)
114
- direction:UISwipeGestureRecognizerDirectionRight
115
- name:RCTTVRemoteEventSwipeRight];
116
- }
159
+ }
117
160
 
118
- return self;
161
+ - (void)attachToView
162
+ {
163
+ [[NSNotificationCenter defaultCenter] addObserver:self
164
+ selector:@selector(enableTVMenuKey)
165
+ name:RCTTVEnableMenuKeyNotification
166
+ object:nil];
167
+
168
+ [[NSNotificationCenter defaultCenter] addObserver:self
169
+ selector:@selector(disableTVMenuKey)
170
+ name:RCTTVDisableMenuKeyNotification
171
+ object:nil];
172
+ for (NSString *key in [self.tvRemoteGestureRecognizers allKeys]) {
173
+ [_view addGestureRecognizer:self.tvRemoteGestureRecognizers[key]];
174
+ }
175
+ if ([RCTTVRemoteHandler useMenuKey]) {
176
+ [self enableTVMenuKey];
177
+ } else {
178
+ [self disableTVMenuKey];
179
+ }
180
+ }
181
+
182
+ - (void)detachFromView
183
+ {
184
+ if ([[self.view gestureRecognizers] containsObject:self.tvMenuKeyRecognizer]) {
185
+ [self.view removeGestureRecognizer:self.tvMenuKeyRecognizer];
186
+ }
187
+ for (NSString *key in [self.tvRemoteGestureRecognizers allKeys]) {
188
+ [_view removeGestureRecognizer:self.tvRemoteGestureRecognizers[key]];
189
+ }
190
+ [[NSNotificationCenter defaultCenter] removeObserver:self
191
+ name:RCTTVEnableMenuKeyNotification
192
+ object:nil];
193
+ [[NSNotificationCenter defaultCenter] removeObserver:self
194
+ name:RCTTVDisableMenuKeyNotification
195
+ object:nil];
196
+
197
+ }
198
+
199
+ # pragma mark -
200
+ # pragma mark Notification handlers
201
+
202
+ - (void)enableTVMenuKey
203
+ {
204
+ dispatch_async(dispatch_get_main_queue(), ^{
205
+ if (![[self.view gestureRecognizers] containsObject:self.tvMenuKeyRecognizer]) {
206
+ [self.view addGestureRecognizer:self.tvMenuKeyRecognizer];
207
+ }
208
+ });
119
209
  }
120
210
 
211
+ - (void)disableTVMenuKey
212
+ {
213
+ dispatch_async(dispatch_get_main_queue(), ^{
214
+ if ([[self.view gestureRecognizers] containsObject:self.tvMenuKeyRecognizer]) {
215
+ [self.view removeGestureRecognizer:self.tvMenuKeyRecognizer];
216
+ }
217
+ });
218
+ }
219
+
220
+ # pragma mark -
221
+ # pragma mark Gesture handlers
222
+
121
223
  - (void)playPausePressed:(UIGestureRecognizer *)r
122
224
  {
123
225
  [self sendAppleTVEvent:RCTTVRemoteEventPlayPause toView:r.view];
@@ -189,6 +291,7 @@ NSString *const RCTTVRemoteEventSwipeDown = @"swipeDown";
189
291
  }
190
292
 
191
293
  #pragma mark -
294
+ #pragma mark Convenience methods for adding gesture recognizers
192
295
 
193
296
  - (void)addLongPressGestureRecognizerWithSelector:(nonnull SEL)selector
194
297
  pressType:(UIPressType)pressType
@@ -6,6 +6,7 @@
6
6
 
7
7
  #import "RCTTVMenuBridge.h"
8
8
  #import <React/RCTRootView.h>
9
+ #import <React/RCTTVRemoteHandler.h>
9
10
 
10
11
  @implementation RCTTVMenuBridge
11
12
 
@@ -13,11 +14,13 @@ RCT_EXPORT_MODULE();
13
14
 
14
15
  RCT_EXPORT_METHOD(enableTVMenuKey)
15
16
  {
17
+ [RCTTVRemoteHandler setUseMenuKey:YES];
16
18
  [[NSNotificationCenter defaultCenter] postNotificationName:RCTTVEnableMenuKeyNotification object:nil];
17
19
  }
18
20
 
19
21
  RCT_EXPORT_METHOD(disableTVMenuKey)
20
22
  {
23
+ [RCTTVRemoteHandler setUseMenuKey:NO];
21
24
  [[NSNotificationCenter defaultCenter] postNotificationName:RCTTVDisableMenuKeyNotification object:nil];
22
25
  }
23
26
 
@@ -49,7 +49,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : coder)
49
49
  _menuButtonGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
50
50
  action:@selector(menuButtonPressed:)];
51
51
  _menuButtonGestureRecognizer.allowedPressTypes = @[ @(UIPressTypeMenu) ];
52
- self.tvRemoteHandler = [RCTTVRemoteHandler new];
52
+ self.tvRemoteHandler = [[RCTTVRemoteHandler alloc] initWithView:self];
53
53
  #endif
54
54
  _isPresented = NO;
55
55
 
@@ -63,6 +63,11 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : coder)
63
63
  }
64
64
 
65
65
  #if TARGET_OS_TV
66
+ - (void)dealloc
67
+ {
68
+ self.tvRemoteHandler = nil;
69
+ }
70
+
66
71
  - (void)menuButtonPressed:(__unused UIGestureRecognizer *)gestureRecognizer
67
72
  {
68
73
  if (_onRequestClose) {
@@ -119,11 +124,6 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : coder)
119
124
  [super insertReactSubview:subview atIndex:atIndex];
120
125
  [_touchHandler attachToView:subview];
121
126
  #if TARGET_OS_TV
122
- for (NSString *key in [self.tvRemoteHandler.tvRemoteGestureRecognizers allKeys]) {
123
- if (![key isEqualToString:RCTTVRemoteEventMenu]) {
124
- [subview addGestureRecognizer:self.tvRemoteHandler.tvRemoteGestureRecognizers[key]];
125
- }
126
- }
127
127
  if (_onRequestClose) {
128
128
  [subview addGestureRecognizer:_menuButtonGestureRecognizer];
129
129
  }
@@ -143,9 +143,6 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : coder)
143
143
  if (_menuButtonGestureRecognizer) {
144
144
  [subview removeGestureRecognizer:_menuButtonGestureRecognizer];
145
145
  }
146
- for (UIGestureRecognizer *gr in self.tvRemoteHandler.tvRemoteGestureRecognizers) {
147
- [subview removeGestureRecognizer:gr];
148
- }
149
146
  #endif
150
147
  _reactSubview = nil;
151
148
  }