1import { ExpoConfig } from '@expo/config-types';
2
3import { createInfoPlistPlugin } from '../plugins/ios-plugins';
4import { gteSdkVersion } from '../utils/versions';
5import { addWarningIOS } from '../utils/warnings';
6import { InfoPlist } from './IosConfig.types';
7
8export const withRequiresFullScreen = createInfoPlistPlugin(
9  setRequiresFullScreen,
10  'withRequiresFullScreen'
11);
12
13// NOTES: This is defaulted to `true` for now to match the behavior prior to SDK
14// 34, but will change to `false` in SDK +43.
15export function getRequiresFullScreen(config: Pick<ExpoConfig, 'ios' | 'sdkVersion'>) {
16  // Yes, the property is called ios.requireFullScreen, without the s - not "requires"
17  // This is confusing indeed because the actual property name does have the s
18  if (config.ios?.hasOwnProperty('requireFullScreen')) {
19    return !!config.ios.requireFullScreen;
20  } else {
21    // In SDK 43, the `requireFullScreen` default has been changed to false.
22    if (
23      gteSdkVersion(config, '43.0.0')
24      // TODO: Uncomment after SDK 43 is released.
25      // || !config.sdkVersion
26    ) {
27      return false;
28    }
29    return true;
30  }
31}
32
33const iPadInterfaceKey = 'UISupportedInterfaceOrientations~ipad';
34
35const requiredIPadInterface = [
36  'UIInterfaceOrientationPortrait',
37  'UIInterfaceOrientationPortraitUpsideDown',
38  'UIInterfaceOrientationLandscapeLeft',
39  'UIInterfaceOrientationLandscapeRight',
40];
41
42function isStringArray(value: any): value is string[] {
43  return Array.isArray(value) && value.every((value) => typeof value === 'string');
44}
45
46function hasMinimumOrientations(masks: string[]): boolean {
47  return requiredIPadInterface.every((mask) => masks.includes(mask));
48}
49
50/**
51 * Require full screen being disabled requires all ipad interfaces to to be added,
52 * otherwise submissions to the iOS App Store will fail.
53 *
54 * ERROR ITMS-90474: "Invalid Bundle. iPad Multitasking support requires these orientations: 'UIInterfaceOrientationPortrait,UIInterfaceOrientationPortraitUpsideDown,UIInterfaceOrientationLandscapeLeft,UIInterfaceOrientationLandscapeRight'. Found 'UIInterfaceOrientationPortrait,UIInterfaceOrientationPortraitUpsideDown' in bundle 'com.bacon.app'."
55 *
56 * @param interfaceOrientations
57 * @returns
58 */
59function resolveExistingIpadInterfaceOrientations(interfaceOrientations: any): string[] {
60  if (
61    // Ensure type.
62    isStringArray(interfaceOrientations) &&
63    // Don't warn if it's an empty array, this is invalid regardless.
64    interfaceOrientations.length &&
65    // Check if the minimum requirements are met.
66    !hasMinimumOrientations(interfaceOrientations)
67  ) {
68    const existingList = interfaceOrientations!.join(', ');
69    addWarningIOS(
70      'ios.requireFullScreen',
71      `iPad multitasking requires all \`${iPadInterfaceKey}\` orientations to be defined in the Info.plist. The Info.plist currently defines values that are incompatible with multitasking, these will be overwritten to prevent submission failure. Existing: ${existingList}`
72    );
73    return interfaceOrientations;
74  }
75  return [];
76}
77
78// Whether requires full screen on iPad
79export function setRequiresFullScreen(
80  config: Pick<ExpoConfig, 'ios'>,
81  infoPlist: InfoPlist
82): InfoPlist {
83  const requiresFullScreen = getRequiresFullScreen(config);
84  if (!requiresFullScreen) {
85    const existing = resolveExistingIpadInterfaceOrientations(infoPlist[iPadInterfaceKey]);
86
87    // There currently exists no mechanism to safely undo this feature besides `expo prebuild --clear`,
88    // this seems ok though because anyone using `UISupportedInterfaceOrientations~ipad` probably
89    // wants them to be defined to this value anyways. This is also the default value used in the Xcode iOS template.
90
91    // Merge any previous interfaces with the required interfaces.
92    infoPlist[iPadInterfaceKey] = [...new Set(existing.concat(requiredIPadInterface))];
93  }
94
95  return {
96    ...infoPlist,
97    UIRequiresFullScreen: requiresFullScreen,
98  };
99}
100