@react-native-tvos/config-tv 0.0.5 → 0.0.7

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.
package/README.md CHANGED
@@ -38,7 +38,16 @@ or
38
38
  "showVerboseWarnings": false,
39
39
  "tvosDeploymentTarget": "13.4",
40
40
  "removeFlipperOnAndroid": true,
41
- "androidTVBanner": "assets/images/tv_banner.png"
41
+ "androidTVBanner": "assets/images/tv_banner.png",
42
+ "appleTVImages": {
43
+ "icon": "./assets/images/myimage-tvos-1280x768.png",
44
+ "iconSmall": "./assets/images/myimage-tvos-400x240.png",
45
+ "iconSmall2x": "./assets/images/myimage-tvos-800x480.png",
46
+ "topShelf": "./assets/images/myimage-tvos-1920x720.png",
47
+ "topShelf2x": "./assets/images/myimage-tvos-3840x1440.png",
48
+ "topShelfWide": "./assets/images/myimage-tvos-2320x720.png",
49
+ "topShelfWide2x": "./assets/images/myimage-tvos-4640x1440.png"
50
+ }
42
51
  }
43
52
  ]
44
53
  ]
@@ -52,12 +61,20 @@ _Plugin parameters_:
52
61
 
53
62
  - `isTV`: (optional boolean, default false) If true, prebuild should generate or modify Android and iOS files to build for TV (Android TV and Apple TV). If false, the plugin will have no effect. Setting the environment variable EXPO_TV to "true" or "1" will override this value and force a TV build.
54
63
  - `showVerboseWarnings`: Deprecated. Verbose logging is now shown as in other config plugins, by setting an environment variable:
55
- - EXPO_DEBUG=1 (shows debug messages from all plugins)
56
- - DEBUG=expo:\* (shows debug messages from all plugins)
57
- - DEBUG=expo:react-native-tvos:config-tv (shows debug messages from this plugin only)
64
+ - `EXPO_DEBUG=1` (shows debug messages from all plugins)
65
+ - `DEBUG=expo:*` (shows debug messages from all plugins)
66
+ - `DEBUG=expo:react-native-tvos:config-tv` (shows debug messages from this plugin only)
58
67
  - `tvosDeploymentTarget`: (optional string, default '13.4') Used to set the tvOS deployment target version in the Xcode project.
59
68
  - `removeFlipperOnAndroid`: (optional boolean, default true) Used to remove the Flipper dependency from `MainApplication.kt` (or `MainApplication.java`) and `android/app/build.gradle`. This is necessary for React Native TV 0.73 and higher, since Flipper integration is removed from these versions. If this causes issues, set the value to false, run `npx expo prebuild --clean` again, and then remove Flipper from your Android source manually.
60
69
  - `androidTVBanner`: (optional string) If set, this should be a path to an existing PNG file appropriate for an Android TV banner image. See https://developer.android.com/design/ui/tv/guides/system/tv-app-icon-guidelines#banner . The Android manifest will be modified to reference this image, and the image will be copied into Android resource drawable directories.
70
+ - `appleTVImages`: (optional object) If set, this is an object with the paths to images needed to construct the Apple TV icon and top shelf brand assets. The images will be used to construct a brand asset catalog in the Xcode project Image catalog, and the project updated to use the brand assets as the source for the app icons. If this property is set, all image paths must be defined and the files must exist, or an error will be thrown. The images need to be the exact sizes shown here, in order to avoid errors during Xcode compilation and on submission to the App Store or TestFlight.
71
+ - `icon`: (string) Path to a 1280x760 image
72
+ - `iconSmall`: (string) Path to a 400x240 image
73
+ - `iconSmall2x`: (string) Path to a 800x480 image
74
+ - `topShelf`: (string) Path to a 1920x720 image
75
+ - `topShelf2x`: (string) Path to a 3840x1440 image
76
+ - `topShelfWide`: (string) Path to a 2320x720 image
77
+ - `topShelfWide2x`: (string) Path to a 4640x1440 image
61
78
 
62
79
  _Warning_:
63
80
 
@@ -66,7 +83,7 @@ When this plugin is used to generate files in the iOS directory that build an Ap
66
83
  ```json
67
84
  {
68
85
  "dependencies": {
69
- "react-native": "npm:react-native-tvos@^0.73.2-0"
86
+ "react-native": "npm:react-native-tvos@^0.73.6-0"
70
87
  }
71
88
  }
72
89
  ```
package/build/types.d.ts CHANGED
@@ -1,3 +1,33 @@
1
+ export type AppleTVImages = {
2
+ /**
3
+ * Path to 400x240 image
4
+ */
5
+ iconSmall: string;
6
+ /**
7
+ * Path to 800x480 image
8
+ */
9
+ iconSmall2x: string;
10
+ /**
11
+ * Path to 1280x760 image
12
+ */
13
+ icon: string;
14
+ /**
15
+ * Path to 1920x720 image
16
+ */
17
+ topShelf: string;
18
+ /**
19
+ * Path to 3840x1440 image
20
+ */
21
+ topShelf2x: string;
22
+ /**
23
+ * Path to 2320x720 image
24
+ */
25
+ topShelfWide: string;
26
+ /**
27
+ * Path to 4640x1440 image
28
+ */
29
+ topShelfWide2x: string;
30
+ };
1
31
  export type ConfigData = {
2
32
  /**
3
33
  * If true, prebuild should generate Android and iOS files for TV (Android TV and Apple TV).
@@ -30,4 +60,13 @@ export type ConfigData = {
30
60
  * Android resource drawable directories.
31
61
  */
32
62
  androidTVBanner?: string;
63
+ /**
64
+ * If set, this is an object with the paths to images needed to construct the Apple TV icon and
65
+ * top shelf brand assets. The images will be used to construct a brand asset catalog in the Xcode
66
+ * project Image catalog, and the project updated to use the brand assets as the source for the app
67
+ * icons. If this property is set, all six image paths must be defined and the files must exist,
68
+ * or an error will be thrown. The images need to be the exact sizes shown here, in order to avoid
69
+ * errors during Xcode compilation and on submission to the App Store or TestFlight.
70
+ */
71
+ appleTVImages?: AppleTVImages;
33
72
  };
@@ -0,0 +1,85 @@
1
+ export type ContentsJsonImageIdiom = 'tv';
2
+ export type ContentsJsonImageAppearance = {
3
+ appearance: 'luminosity';
4
+ value: 'dark';
5
+ };
6
+ export type ContentsJsonImageScale = '1x' | '2x' | '3x';
7
+ export type ContentsJsonImageRole = 'primary-app-icon' | 'top-shelf-image' | 'top-shelf-image-wide';
8
+ export interface ContentsJsonImage {
9
+ appearances?: ContentsJsonImageAppearance[];
10
+ idiom: ContentsJsonImageIdiom;
11
+ size?: string;
12
+ scale?: ContentsJsonImageScale;
13
+ filename: string;
14
+ }
15
+ export interface ContentsJsonImageLayer {
16
+ filename: string;
17
+ }
18
+ export interface ContentsJsonAsset {
19
+ filename?: string;
20
+ idiom?: ContentsJsonImageIdiom;
21
+ role?: ContentsJsonImageRole;
22
+ size?: string;
23
+ }
24
+ export interface ContentsJson {
25
+ assets?: ContentsJsonAsset[];
26
+ images?: ContentsJsonImage[];
27
+ layers?: ContentsJsonImageLayer[];
28
+ info: {
29
+ version: number;
30
+ author: string;
31
+ };
32
+ }
33
+ export interface SourceImageJson {
34
+ path: string;
35
+ scale?: string;
36
+ }
37
+ export interface SourceImageSetJson {
38
+ name: string;
39
+ sourceImages: SourceImageJson[];
40
+ }
41
+ export interface SourceImageLayerJson {
42
+ name: string;
43
+ sourceImages: SourceImageJson[];
44
+ }
45
+ export interface SourceImageStackJson {
46
+ name: string;
47
+ sourceLayers: SourceImageLayerJson[];
48
+ }
49
+ export interface SourceBrandAssetJson {
50
+ role: ContentsJsonImageRole;
51
+ size: string;
52
+ imageStack?: SourceImageStackJson;
53
+ imageSet?: SourceImageSetJson;
54
+ }
55
+ export interface SourceBrandAssetsJson {
56
+ name: string;
57
+ assets: SourceBrandAssetJson[];
58
+ }
59
+ /**
60
+ * Writes the Config.json which is used to assign images to their respective platform, dpi, and idiom.
61
+ *
62
+ * @param directory path to add the Contents.json to.
63
+ * @param contents image json data
64
+ */
65
+ export declare function writeContentsJsonAsync(directory: string, options: {
66
+ assets?: ContentsJsonAsset[];
67
+ images?: ContentsJsonImage[];
68
+ layers?: ContentsJsonImageLayer[];
69
+ }): Promise<void>;
70
+ /**
71
+ * Creates an image set directory with its Contents.json and any images
72
+ */
73
+ export declare function createImageSetAsync(destinationPath: string, imageSet: SourceImageSetJson): Promise<void>;
74
+ /**
75
+ * Creates an image stack layer directory with its Contents.json and any images
76
+ */
77
+ export declare function createImageStackLayerAsync(destinationPath: string, layer: SourceImageLayerJson): Promise<void>;
78
+ /**
79
+ * Creates an image stack directory with its Contents.json and any layers
80
+ */
81
+ export declare function createImageStackAsync(destinationPath: string, stack: SourceImageStackJson): Promise<void>;
82
+ /**
83
+ * Creates a brand assets directory with its Contents.json and any assets
84
+ */
85
+ export declare function createBrandAssetsAsync(destinationPath: string, brandAssets: SourceBrandAssetsJson): Promise<void>;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createBrandAssetsAsync = exports.createImageStackAsync = exports.createImageStackLayerAsync = exports.createImageSetAsync = exports.writeContentsJsonAsync = void 0;
7
+ const fs_1 = require("fs");
8
+ const path_1 = __importDefault(require("path"));
9
+ /**
10
+ * Writes the Config.json which is used to assign images to their respective platform, dpi, and idiom.
11
+ *
12
+ * @param directory path to add the Contents.json to.
13
+ * @param contents image json data
14
+ */
15
+ async function writeContentsJsonAsync(directory, options) {
16
+ await fs_1.promises.mkdir(directory, { recursive: true });
17
+ await fs_1.promises.writeFile(path_1.default.join(directory, 'Contents.json'), JSON.stringify({
18
+ assets: options.assets,
19
+ images: options.images,
20
+ layers: options.layers,
21
+ info: {
22
+ version: 1,
23
+ // common practice is for the tool that generated the icons to be the "author"
24
+ author: 'expo',
25
+ },
26
+ }, null, 2));
27
+ }
28
+ exports.writeContentsJsonAsync = writeContentsJsonAsync;
29
+ /**
30
+ * Creates an image set directory with its Contents.json and any images
31
+ */
32
+ async function createImageSetAsync(destinationPath, imageSet) {
33
+ const imageSetPath = path_1.default.join(destinationPath, `${imageSet.name}.imageset`);
34
+ await writeContentsJsonAsync(imageSetPath, {
35
+ images: imageSet.sourceImages.map((image) => ({
36
+ filename: path_1.default.basename(image.path),
37
+ idiom: 'tv',
38
+ scale: image.scale,
39
+ })),
40
+ });
41
+ for (const image of imageSet.sourceImages) {
42
+ await fs_1.promises.copyFile(image.path, path_1.default.join(imageSetPath, path_1.default.basename(image.path)));
43
+ }
44
+ }
45
+ exports.createImageSetAsync = createImageSetAsync;
46
+ /**
47
+ * Creates an image stack layer directory with its Contents.json and any images
48
+ */
49
+ async function createImageStackLayerAsync(destinationPath, layer) {
50
+ const imageStackLayerPath = path_1.default.join(destinationPath, `${layer.name}.imagestacklayer`);
51
+ await writeContentsJsonAsync(imageStackLayerPath, {});
52
+ await createImageSetAsync(imageStackLayerPath, {
53
+ name: 'Content',
54
+ sourceImages: layer.sourceImages,
55
+ });
56
+ }
57
+ exports.createImageStackLayerAsync = createImageStackLayerAsync;
58
+ /**
59
+ * Creates an image stack directory with its Contents.json and any layers
60
+ */
61
+ async function createImageStackAsync(destinationPath, stack) {
62
+ const imageStackPath = path_1.default.join(destinationPath, `${stack.name}.imagestack`);
63
+ await writeContentsJsonAsync(imageStackPath, {
64
+ layers: stack.sourceLayers.map((layer) => ({
65
+ filename: `${layer.name}.imagestacklayer`,
66
+ })),
67
+ });
68
+ for (const sourceLayer of stack.sourceLayers) {
69
+ await createImageStackLayerAsync(imageStackPath, {
70
+ name: sourceLayer.name,
71
+ sourceImages: sourceLayer.sourceImages,
72
+ });
73
+ }
74
+ }
75
+ exports.createImageStackAsync = createImageStackAsync;
76
+ /**
77
+ * Creates a brand assets directory with its Contents.json and any assets
78
+ */
79
+ async function createBrandAssetsAsync(destinationPath, brandAssets) {
80
+ const brandAssetsPath = path_1.default.join(destinationPath, `${brandAssets.name}.brandassets`);
81
+ await writeContentsJsonAsync(brandAssetsPath, {
82
+ assets: brandAssets.assets.map((brandAsset) => {
83
+ if (brandAsset.imageStack) {
84
+ return {
85
+ filename: `${brandAsset.imageStack.name}.imagestack`,
86
+ role: brandAsset.role,
87
+ size: brandAsset.size,
88
+ idiom: 'tv',
89
+ };
90
+ }
91
+ else if (brandAsset.imageSet) {
92
+ return {
93
+ filename: `${brandAsset.imageSet.name}.imageset`,
94
+ role: brandAsset.role,
95
+ size: brandAsset.size,
96
+ idiom: 'tv',
97
+ };
98
+ }
99
+ else {
100
+ return {}; // Should never happen, but need this to keep Typescript happy
101
+ }
102
+ }),
103
+ });
104
+ for (const asset of brandAssets.assets) {
105
+ if (asset.imageSet) {
106
+ await createImageSetAsync(brandAssetsPath, asset.imageSet);
107
+ }
108
+ if (asset.imageStack) {
109
+ await createImageStackAsync(brandAssetsPath, asset.imageStack);
110
+ }
111
+ }
112
+ }
113
+ exports.createBrandAssetsAsync = createBrandAssetsAsync;
@@ -1,12 +1,8 @@
1
- import { ConfigData } from './types';
1
+ import { ConfigData } from '../types';
2
2
  export declare const packageNameAndVersion: string;
3
3
  export declare function isTVEnabled(params: ConfigData): boolean;
4
- export declare function showVerboseWarnings(params: ConfigData): boolean;
5
4
  export declare function tvosDeploymentTarget(params: ConfigData): string;
6
5
  export declare function shouldRemoveFlipperOnAndroid(params: ConfigData): boolean;
7
6
  export declare function androidTVBanner(params: ConfigData): string | undefined;
8
- export declare function verboseLog(message: string, options?: {
9
- params?: ConfigData;
10
- platform?: 'android' | 'ios';
11
- property?: string;
12
- }): void;
7
+ export declare const appleTVImageTypes: string[];
8
+ export declare function appleTVImagePathForType(params: ConfigData, imageType: string): string | undefined;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.appleTVImagePathForType = exports.appleTVImageTypes = exports.androidTVBanner = exports.shouldRemoveFlipperOnAndroid = exports.tvosDeploymentTarget = exports.isTVEnabled = exports.packageNameAndVersion = void 0;
4
+ const getenv_1 = require("getenv");
5
+ class Env {
6
+ /** Enable prebuild for TV */
7
+ get EXPO_TV() {
8
+ return (0, getenv_1.boolish)('EXPO_TV', false);
9
+ }
10
+ }
11
+ const env = new Env();
12
+ const pkg = require('../../package.json');
13
+ const defaultTvosDeploymentVersion = '13.4';
14
+ exports.packageNameAndVersion = `${pkg.name}@${pkg.version}`;
15
+ function isTVEnabled(params) {
16
+ return env.EXPO_TV || (params?.isTV ?? false);
17
+ }
18
+ exports.isTVEnabled = isTVEnabled;
19
+ function tvosDeploymentTarget(params) {
20
+ return params?.tvosDeploymentTarget ?? defaultTvosDeploymentVersion;
21
+ }
22
+ exports.tvosDeploymentTarget = tvosDeploymentTarget;
23
+ function shouldRemoveFlipperOnAndroid(params) {
24
+ return params?.removeFlipperOnAndroid ?? true;
25
+ }
26
+ exports.shouldRemoveFlipperOnAndroid = shouldRemoveFlipperOnAndroid;
27
+ function androidTVBanner(params) {
28
+ return params?.androidTVBanner;
29
+ }
30
+ exports.androidTVBanner = androidTVBanner;
31
+ exports.appleTVImageTypes = [
32
+ 'icon',
33
+ 'iconSmall',
34
+ 'iconSmall2x',
35
+ 'topShelf',
36
+ 'topShelf2x',
37
+ 'topShelfWide',
38
+ 'topShelfWide2x',
39
+ ];
40
+ function appleTVImagePathForType(params, imageType) {
41
+ switch (imageType) {
42
+ case 'icon':
43
+ return params?.appleTVImages?.icon;
44
+ case 'iconSmall':
45
+ return params?.appleTVImages?.iconSmall;
46
+ case 'iconSmall2x':
47
+ return params?.appleTVImages?.iconSmall2x;
48
+ case 'topShelf':
49
+ return params?.appleTVImages?.topShelf;
50
+ case 'topShelf2x':
51
+ return params?.appleTVImages?.topShelf2x;
52
+ case 'topShelfWide':
53
+ return params?.appleTVImages?.topShelfWide;
54
+ case 'topShelfWide2x':
55
+ return params?.appleTVImages?.topShelfWide2x;
56
+ default:
57
+ return undefined;
58
+ }
59
+ }
60
+ exports.appleTVImagePathForType = appleTVImagePathForType;
@@ -0,0 +1,3 @@
1
+ export * from './appleBrandAssets';
2
+ export * from './config';
3
+ export * from './log';
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./appleBrandAssets"), exports);
18
+ __exportStar(require("./config"), exports);
19
+ __exportStar(require("./log"), exports);
@@ -0,0 +1,6 @@
1
+ import { ConfigData } from '../types';
2
+ export declare function verboseLog(message: string, options?: {
3
+ params?: ConfigData;
4
+ platform?: 'android' | 'ios';
5
+ property?: string;
6
+ }): void;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verboseLog = void 0;
4
+ const debug = require('debug')('expo:react-native-tvos:config-tv');
5
+ function verboseLog(message, options) {
6
+ const tokens = [message];
7
+ options?.property && tokens.unshift(options?.property);
8
+ options?.platform && tokens.unshift(options?.platform);
9
+ debug(tokens.join(': '));
10
+ }
11
+ exports.verboseLog = verboseLog;
package/build/withTV.js CHANGED
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const config_plugins_1 = require("expo/config-plugins");
4
4
  const withTVAndroidManifest_1 = require("./withTVAndroidManifest");
5
+ const withTVAppleIconImages_1 = require("./withTVAppleIconImages");
6
+ const withTVInfoPlist_1 = require("./withTVInfoPlist");
5
7
  const withTVPodfile_1 = require("./withTVPodfile");
6
8
  const withTVSplashScreen_1 = require("./withTVSplashScreen");
7
9
  const withTVXcodeProject_1 = require("./withTVXcodeProject");
@@ -18,8 +20,10 @@ const withTVPlugin = (config, params = {}) => {
18
20
  config = withTVNoEffect(config, params);
19
21
  return config;
20
22
  }
23
+ config = (0, withTVAppleIconImages_1.withTVAppleIconImages)(config, params); // This should be done before Apple Xcode project config
21
24
  config = (0, withTVXcodeProject_1.withTVXcodeProject)(config, params);
22
25
  config = (0, withTVPodfile_1.withTVPodfile)(config, params);
26
+ config = (0, withTVInfoPlist_1.withTVInfoPlist)(config, params);
23
27
  config = (0, withTVSplashScreen_1.withTVSplashScreen)(config, params);
24
28
  config = (0, withTVAndroidBannerImage_1.withTVAndroidBannerImage)(config, params); // This should be done before Android manifest config
25
29
  config = (0, withTVAndroidManifest_1.withTVAndroidManifest)(config, params);
@@ -5,3 +5,4 @@ export declare const withTVAndroidManifest: ConfigPlugin<ConfigData>;
5
5
  export declare function setLeanBackLauncherIntent(_config: Pick<ExpoConfig, 'android'>, androidManifest: AndroidConfig.Manifest.AndroidManifest, params: ConfigData): AndroidConfig.Manifest.AndroidManifest;
6
6
  export declare function removePortraitOrientation(_config: Pick<ExpoConfig, 'android'>, androidManifest: AndroidConfig.Manifest.AndroidManifest, params: ConfigData): AndroidConfig.Manifest.AndroidManifest;
7
7
  export declare function setTVBanner(_config: Pick<ExpoConfig, 'android'>, androidManifest: AndroidConfig.Manifest.AndroidManifest, params: ConfigData, androidTVBannerPath: string | undefined): AndroidConfig.Manifest.AndroidManifest;
8
+ export declare function addTouchscreenHardwareFeatureToManifest(_config: Pick<ExpoConfig, 'android'>, androidManifest: AndroidConfig.Manifest.AndroidManifest, params: ConfigData): AndroidConfig.Manifest.AndroidManifest;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setTVBanner = exports.removePortraitOrientation = exports.setLeanBackLauncherIntent = exports.withTVAndroidManifest = void 0;
3
+ exports.addTouchscreenHardwareFeatureToManifest = exports.setTVBanner = exports.removePortraitOrientation = exports.setLeanBackLauncherIntent = exports.withTVAndroidManifest = void 0;
4
4
  const config_plugins_1 = require("expo/config-plugins");
5
5
  const utils_1 = require("./utils");
6
6
  const { getMainActivity, getMainApplication } = config_plugins_1.AndroidConfig.Manifest;
@@ -9,6 +9,7 @@ const withTVAndroidManifest = (config, params = {}) => {
9
9
  return (0, config_plugins_1.withAndroidManifest)(config, (config) => {
10
10
  config.modResults = setLeanBackLauncherIntent(config, config.modResults, params);
11
11
  config.modResults = removePortraitOrientation(config, config.modResults, params);
12
+ config.modResults = addTouchscreenHardwareFeatureToManifest(config, config.modResults, params);
12
13
  if (androidTVBannerPath) {
13
14
  config.modResults = setTVBanner(config, config.modResults, params, androidTVBannerPath);
14
15
  }
@@ -90,3 +91,39 @@ function setTVBanner(_config, androidManifest, params, androidTVBannerPath) {
90
91
  return androidManifest;
91
92
  }
92
93
  exports.setTVBanner = setTVBanner;
94
+ function addTouchscreenHardwareFeatureToManifest(_config, androidManifest, params) {
95
+ // Add `<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>` to the AndroidManifest.xml
96
+ if (!Array.isArray(androidManifest.manifest['uses-feature'])) {
97
+ androidManifest.manifest['uses-feature'] = [];
98
+ }
99
+ if (!androidManifest.manifest['uses-feature'].find((item) => item.$['android:name'] === 'android.hardware.touchscreen') &&
100
+ !androidManifest.manifest['uses-feature'].find((item) => item.$['android:name'] === 'android.hardware.faketouch') &&
101
+ !androidManifest.manifest['uses-feature'].find((item) => item.$['android:name'] === 'android.software.leanback')) {
102
+ (0, utils_1.verboseLog)('adding TV touchscreen hardware feature tag to AndroidManifest.xml', {
103
+ params,
104
+ platform: 'android',
105
+ property: 'manifest',
106
+ });
107
+ androidManifest.manifest['uses-feature']?.push({
108
+ $: {
109
+ 'android:name': 'android.hardware.touchscreen',
110
+ 'android:required': 'false',
111
+ },
112
+ });
113
+ androidManifest.manifest['uses-feature']?.push({
114
+ $: {
115
+ 'android:name': 'android.hardware.faketouch',
116
+ 'android:required': 'false',
117
+ },
118
+ });
119
+ // add android.software.leanback to false
120
+ androidManifest.manifest['uses-feature']?.push({
121
+ $: {
122
+ 'android:name': 'android.software.leanback',
123
+ 'android:required': 'false',
124
+ },
125
+ });
126
+ }
127
+ return androidManifest;
128
+ }
129
+ exports.addTouchscreenHardwareFeatureToManifest = addTouchscreenHardwareFeatureToManifest;
@@ -0,0 +1,7 @@
1
+ import { ConfigPlugin } from 'expo/config-plugins';
2
+ import { ConfigData } from './types';
3
+ /**
4
+ * Constructs Apple TV brand assets from images passed into the `appleTVImages` plugin property
5
+ * If any images do not exist, an exception is thrown.
6
+ */
7
+ export declare const withTVAppleIconImages: ConfigPlugin<ConfigData>;
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.withTVAppleIconImages = void 0;
7
+ const config_plugins_1 = require("expo/config-plugins");
8
+ const fs_1 = require("fs");
9
+ const path_1 = __importDefault(require("path"));
10
+ const utils_1 = require("./utils");
11
+ const { getProjectName } = config_plugins_1.IOSConfig.XcodeUtils;
12
+ /**
13
+ * Constructs Apple TV brand assets from images passed into the `appleTVImages` plugin property
14
+ * If any images do not exist, an exception is thrown.
15
+ */
16
+ const withTVAppleIconImages = (c, params = {}) => {
17
+ return (0, config_plugins_1.withDangerousMod)(c, [
18
+ 'ios',
19
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
20
+ async (config) => {
21
+ if (!params.appleTVImages) {
22
+ return config;
23
+ }
24
+ (0, utils_1.verboseLog)(`adding Apple TV brand assets to Apple TV native code`, {
25
+ params,
26
+ platform: 'ios',
27
+ property: 'xcodeproject',
28
+ });
29
+ utils_1.appleTVImageTypes.forEach((imageType) => {
30
+ const imagePath = (0, utils_1.appleTVImagePathForType)(params, imageType);
31
+ if (!imagePath) {
32
+ throw new Error(`One or more image paths not defined`);
33
+ }
34
+ if (!(0, fs_1.existsSync)(imagePath)) {
35
+ throw new Error(`No image found at path ${imagePath}`);
36
+ }
37
+ });
38
+ const projectRoot = config.modRequest.projectRoot;
39
+ const iosImagesPath = path_1.default.join(getIosNamedProjectPath(projectRoot), IMAGES_PATH);
40
+ const iconSmallSourceImages = [
41
+ {
42
+ path: params.appleTVImages.iconSmall,
43
+ scale: '1x',
44
+ },
45
+ {
46
+ path: params.appleTVImages.iconSmall2x,
47
+ scale: '2x',
48
+ },
49
+ ];
50
+ const iconLargeSourceImages = [
51
+ {
52
+ path: params.appleTVImages.icon,
53
+ scale: '1x',
54
+ },
55
+ ];
56
+ /*
57
+ const appStoreIconSourceImages: SourceImageJson[] = [
58
+ {
59
+ path: params.appleTVImages.icon,
60
+ },
61
+ ];
62
+ */
63
+ const topShelfSourceImages = [
64
+ {
65
+ path: params.appleTVImages.topShelf,
66
+ scale: '1x',
67
+ },
68
+ {
69
+ path: params.appleTVImages.topShelf2x,
70
+ scale: '2x',
71
+ },
72
+ ];
73
+ const topShelfWideSourceImages = [
74
+ {
75
+ path: params.appleTVImages.topShelfWide,
76
+ scale: '1x',
77
+ },
78
+ {
79
+ path: params.appleTVImages.topShelfWide2x,
80
+ scale: '2x',
81
+ },
82
+ ];
83
+ const sourceBrandAssets = {
84
+ name: 'TVAppIcon',
85
+ assets: [
86
+ {
87
+ role: 'top-shelf-image',
88
+ size: '1920x720',
89
+ imageSet: {
90
+ name: 'Top Shelf Image',
91
+ sourceImages: topShelfSourceImages,
92
+ },
93
+ },
94
+ {
95
+ role: 'top-shelf-image-wide',
96
+ size: '2320x720',
97
+ imageSet: {
98
+ name: 'Top Shelf Image Wide',
99
+ sourceImages: topShelfWideSourceImages,
100
+ },
101
+ },
102
+ /*
103
+ {
104
+ role: 'primary-app-icon',
105
+ size: '1280x768',
106
+ imageStack: {
107
+ name: 'App Icon - App Store',
108
+ sourceLayers: [
109
+ {
110
+ name: 'Front',
111
+ sourceImages: appStoreIconSourceImages,
112
+ },
113
+ {
114
+ name: 'Middle',
115
+ sourceImages: appStoreIconSourceImages,
116
+ },
117
+ {
118
+ name: 'Back',
119
+ sourceImages: appStoreIconSourceImages,
120
+ },
121
+ ],
122
+ },
123
+ },
124
+ */
125
+ {
126
+ role: 'primary-app-icon',
127
+ size: '400x240',
128
+ imageStack: {
129
+ name: 'App Icon - Small',
130
+ sourceLayers: [
131
+ {
132
+ name: 'Front',
133
+ sourceImages: iconSmallSourceImages,
134
+ },
135
+ {
136
+ name: 'Middle',
137
+ sourceImages: iconSmallSourceImages,
138
+ },
139
+ {
140
+ name: 'Back',
141
+ sourceImages: iconSmallSourceImages,
142
+ },
143
+ ],
144
+ },
145
+ },
146
+ {
147
+ role: 'primary-app-icon',
148
+ size: '1280x768',
149
+ imageStack: {
150
+ name: 'App Icon - Large',
151
+ sourceLayers: [
152
+ {
153
+ name: 'Front',
154
+ sourceImages: iconLargeSourceImages,
155
+ },
156
+ {
157
+ name: 'Middle',
158
+ sourceImages: iconLargeSourceImages,
159
+ },
160
+ {
161
+ name: 'Back',
162
+ sourceImages: iconLargeSourceImages,
163
+ },
164
+ ],
165
+ },
166
+ },
167
+ ],
168
+ };
169
+ await (0, utils_1.createBrandAssetsAsync)(iosImagesPath, sourceBrandAssets);
170
+ return config;
171
+ },
172
+ ]);
173
+ };
174
+ exports.withTVAppleIconImages = withTVAppleIconImages;
175
+ function getIosNamedProjectPath(projectRoot) {
176
+ const projectName = getProjectName(projectRoot);
177
+ return path_1.default.join(projectRoot, 'ios', projectName);
178
+ }
179
+ const IMAGES_PATH = 'Images.xcassets';
@@ -0,0 +1,3 @@
1
+ import { ConfigPlugin } from 'expo/config-plugins';
2
+ import { ConfigData } from './types';
3
+ export declare const withTVInfoPlist: ConfigPlugin<ConfigData>;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withTVInfoPlist = void 0;
4
+ const config_plugins_1 = require("expo/config-plugins");
5
+ const utils_1 = require("./utils");
6
+ const withTVInfoPlist = (c, params = {}) => {
7
+ (0, utils_1.verboseLog)('Modifying UIRequiredDeviceCapabilities for TV', {
8
+ params,
9
+ platform: 'ios',
10
+ property: 'Info.plist',
11
+ });
12
+ return (0, config_plugins_1.withInfoPlist)(c, (config) => {
13
+ config.modResults.UIRequiredDeviceCapabilities = ['arm64'];
14
+ return config;
15
+ });
16
+ };
17
+ exports.withTVInfoPlist = withTVInfoPlist;
@@ -21,18 +21,24 @@ function setXcodeProjectBuildSettings(config, { project, params, deploymentTarge
21
21
  for (const { buildSettings } of Object.values(configurations || {})) {
22
22
  // Guessing that this is the best way to emulate Xcode.
23
23
  // Using `project.addToBuildSettings` modifies too many targets.
24
+ if (buildSettings !== undefined) {
25
+ buildSettings.SDKROOT = 'appletvos';
26
+ }
24
27
  if (typeof buildSettings?.PRODUCT_NAME !== 'undefined') {
25
- if (buildSettings.TARGETED_DEVICE_FAMILY !== '3') {
26
- (0, utils_1.verboseLog)(`modifying target ${buildSettings?.PRODUCT_NAME} for tvOS`, {
27
- params,
28
- platform: 'ios',
29
- property: 'xcodeproject',
30
- });
31
- buildSettings.TARGETED_DEVICE_FAMILY = '3';
32
- buildSettings.TVOS_DEPLOYMENT_TARGET = deploymentTarget;
33
- buildSettings.SDKROOT = 'appletvos';
34
- if (typeof buildSettings?.IOS_DEPLOYMENT_TARGET !== 'undefined') {
35
- delete buildSettings?.IOS_DEPLOYMENT_TARGET;
28
+ (0, utils_1.verboseLog)(`modifying target ${buildSettings?.PRODUCT_NAME} for tvOS`, {
29
+ params,
30
+ platform: 'ios',
31
+ property: 'xcodeproject',
32
+ });
33
+ buildSettings.TARGETED_DEVICE_FAMILY = '3';
34
+ buildSettings.TVOS_DEPLOYMENT_TARGET = deploymentTarget;
35
+ if (typeof buildSettings?.IOS_DEPLOYMENT_TARGET !== 'undefined') {
36
+ delete buildSettings?.IOS_DEPLOYMENT_TARGET;
37
+ }
38
+ if (params.appleTVImages) {
39
+ // set the app icon source
40
+ if (buildSettings.ASSETCATALOG_COMPILER_APPICON_NAME === 'AppIcon') {
41
+ buildSettings.ASSETCATALOG_COMPILER_APPICON_NAME = 'TVAppIcon';
36
42
  }
37
43
  }
38
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native-tvos/config-tv",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Config plugin to reconfigure native directories for Apple TV and Android TV development if needed",
5
5
  "main": "build/withTV.js",
6
6
  "types": "build/withTV.d.ts",
@@ -39,7 +39,8 @@
39
39
  "devDependencies": {
40
40
  "@types/getenv": "^1.0.1",
41
41
  "expo-module-scripts": "^3.0.3",
42
- "jest": "^29.7.0"
42
+ "jest": "^29.7.0",
43
+ "memfs": "^4.7.6"
43
44
  },
44
45
  "upstreamPackage": "tv",
45
46
  "dependencies": {
package/build/utils.js DELETED
@@ -1,42 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.verboseLog = exports.androidTVBanner = exports.shouldRemoveFlipperOnAndroid = exports.tvosDeploymentTarget = exports.showVerboseWarnings = exports.isTVEnabled = exports.packageNameAndVersion = void 0;
4
- const getenv_1 = require("getenv");
5
- class Env {
6
- /** Enable prebuild for TV */
7
- get EXPO_TV() {
8
- return (0, getenv_1.boolish)('EXPO_TV', false);
9
- }
10
- }
11
- const debug = require('debug')('expo:react-native-tvos:config-tv');
12
- const env = new Env();
13
- const pkg = require('../package.json');
14
- const defaultTvosDeploymentVersion = '13.4';
15
- exports.packageNameAndVersion = `${pkg.name}@${pkg.version}`;
16
- function isTVEnabled(params) {
17
- return env.EXPO_TV || (params?.isTV ?? false);
18
- }
19
- exports.isTVEnabled = isTVEnabled;
20
- function showVerboseWarnings(params) {
21
- return params?.showVerboseWarnings ?? false;
22
- }
23
- exports.showVerboseWarnings = showVerboseWarnings;
24
- function tvosDeploymentTarget(params) {
25
- return params?.tvosDeploymentTarget ?? defaultTvosDeploymentVersion;
26
- }
27
- exports.tvosDeploymentTarget = tvosDeploymentTarget;
28
- function shouldRemoveFlipperOnAndroid(params) {
29
- return params?.removeFlipperOnAndroid ?? true;
30
- }
31
- exports.shouldRemoveFlipperOnAndroid = shouldRemoveFlipperOnAndroid;
32
- function androidTVBanner(params) {
33
- return params?.androidTVBanner;
34
- }
35
- exports.androidTVBanner = androidTVBanner;
36
- function verboseLog(message, options) {
37
- const tokens = [message];
38
- options?.property && tokens.unshift(options?.property);
39
- options?.platform && tokens.unshift(options?.platform);
40
- debug(tokens.join(': '));
41
- }
42
- exports.verboseLog = verboseLog;