1import {
2  AndroidConfig,
3  withProjectBuildGradle,
4  ConfigPlugin,
5  createRunOncePlugin,
6  withInfoPlist,
7} from '@expo/config-plugins';
8import {
9  createGeneratedHeaderComment,
10  MergeResults,
11  removeGeneratedContents,
12} from '@expo/config-plugins/build/utils/generateCode';
13
14const pkg = require('expo-camera/package.json');
15
16const CAMERA_USAGE = 'Allow $(PRODUCT_NAME) to access your camera';
17const MICROPHONE_USAGE = 'Allow $(PRODUCT_NAME) to access your microphone';
18
19// Because we need the package to be added AFTER the React and Google maven packages, we create a new allprojects.
20// It's ok to have multiple allprojects.repositories, so we create a new one since it's cheaper than tokenizing
21// the existing block to find the correct place to insert our camera maven.
22const gradleMaven = [
23  `def expoCameraMavenPath = new File(["node", "--print", "require.resolve('expo-camera/package.json')"].execute(null, rootDir).text.trim(), "../android/maven")`,
24  `allprojects { repositories { maven { url(expoCameraMavenPath) } } }`,
25].join('\n');
26
27const withAndroidCameraGradle: ConfigPlugin = (config) => {
28  return withProjectBuildGradle(config, (config) => {
29    if (config.modResults.language === 'groovy') {
30      config.modResults.contents = addCameraImport(config.modResults.contents).contents;
31    } else {
32      throw new Error('Cannot add camera maven gradle because the build.gradle is not groovy');
33    }
34    return config;
35  });
36};
37
38export function addCameraImport(src: string): MergeResults {
39  return appendContents({
40    tag: 'expo-camera-import',
41    src,
42    newSrc: gradleMaven,
43    comment: '//',
44  });
45}
46
47// Fork of config-plugins mergeContents, but appends the contents to the end of the file.
48function appendContents({
49  src,
50  newSrc,
51  tag,
52  comment,
53}: {
54  src: string;
55  newSrc: string;
56  tag: string;
57  comment: string;
58}): MergeResults {
59  const header = createGeneratedHeaderComment(newSrc, tag, comment);
60  if (!src.includes(header)) {
61    // Ensure the old generated contents are removed.
62    const sanitizedTarget = removeGeneratedContents(src, tag);
63    const contentsToAdd = [
64      // @something
65      header,
66      // contents
67      newSrc,
68      // @end
69      `${comment} @generated end ${tag}`,
70    ].join('\n');
71
72    return {
73      contents: sanitizedTarget ?? src + contentsToAdd,
74      didMerge: true,
75      didClear: !!sanitizedTarget,
76    };
77  }
78  return { contents: src, didClear: false, didMerge: false };
79}
80
81const withCamera: ConfigPlugin<
82  {
83    cameraPermission?: string;
84    microphonePermission?: string;
85  } | void
86> = (config, { cameraPermission, microphonePermission } = {}) => {
87  config = withInfoPlist(config, (config) => {
88    config.modResults.NSCameraUsageDescription =
89      cameraPermission || config.modResults.NSCameraUsageDescription || CAMERA_USAGE;
90
91    config.modResults.NSMicrophoneUsageDescription =
92      microphonePermission || config.modResults.NSMicrophoneUsageDescription || MICROPHONE_USAGE;
93
94    return config;
95  });
96
97  config = AndroidConfig.Permissions.withPermissions(config, [
98    'android.permission.CAMERA',
99    // Optional
100    'android.permission.RECORD_AUDIO',
101  ]);
102
103  return withAndroidCameraGradle(config);
104};
105
106export default createRunOncePlugin(withCamera, pkg.name, pkg.version);
107