1082815dcSEvan Baconimport { ExpoConfig } from '@expo/config-types'; 2082815dcSEvan Bacon 3*8a424bebSJames Ideimport { InfoPlist } from './IosConfig.types'; 4082815dcSEvan Baconimport { createInfoPlistPlugin } from '../plugins/ios-plugins'; 5082815dcSEvan Baconimport { gteSdkVersion } from '../utils/versions'; 6082815dcSEvan Baconimport { addWarningIOS } from '../utils/warnings'; 7082815dcSEvan Bacon 8082815dcSEvan Baconexport const withRequiresFullScreen = createInfoPlistPlugin( 9082815dcSEvan Bacon setRequiresFullScreen, 10082815dcSEvan Bacon 'withRequiresFullScreen' 11082815dcSEvan Bacon); 12082815dcSEvan Bacon 13082815dcSEvan Bacon// NOTES: This is defaulted to `true` for now to match the behavior prior to SDK 14082815dcSEvan Bacon// 34, but will change to `false` in SDK +43. 15082815dcSEvan Baconexport function getRequiresFullScreen(config: Pick<ExpoConfig, 'ios' | 'sdkVersion'>) { 16082815dcSEvan Bacon // Yes, the property is called ios.requireFullScreen, without the s - not "requires" 17082815dcSEvan Bacon // This is confusing indeed because the actual property name does have the s 18082815dcSEvan Bacon if (config.ios?.hasOwnProperty('requireFullScreen')) { 19082815dcSEvan Bacon return !!config.ios.requireFullScreen; 20082815dcSEvan Bacon } else { 21082815dcSEvan Bacon // In SDK 43, the `requireFullScreen` default has been changed to false. 22082815dcSEvan Bacon if ( 23082815dcSEvan Bacon gteSdkVersion(config, '43.0.0') 24082815dcSEvan Bacon // TODO: Uncomment after SDK 43 is released. 25082815dcSEvan Bacon // || !config.sdkVersion 26082815dcSEvan Bacon ) { 27082815dcSEvan Bacon return false; 28082815dcSEvan Bacon } 29082815dcSEvan Bacon return true; 30082815dcSEvan Bacon } 31082815dcSEvan Bacon} 32082815dcSEvan Bacon 33082815dcSEvan Baconconst iPadInterfaceKey = 'UISupportedInterfaceOrientations~ipad'; 34082815dcSEvan Bacon 35082815dcSEvan Baconconst requiredIPadInterface = [ 36082815dcSEvan Bacon 'UIInterfaceOrientationPortrait', 37082815dcSEvan Bacon 'UIInterfaceOrientationPortraitUpsideDown', 38082815dcSEvan Bacon 'UIInterfaceOrientationLandscapeLeft', 39082815dcSEvan Bacon 'UIInterfaceOrientationLandscapeRight', 40082815dcSEvan Bacon]; 41082815dcSEvan Bacon 42082815dcSEvan Baconfunction isStringArray(value: any): value is string[] { 43082815dcSEvan Bacon return Array.isArray(value) && value.every((value) => typeof value === 'string'); 44082815dcSEvan Bacon} 45082815dcSEvan Bacon 46082815dcSEvan Baconfunction hasMinimumOrientations(masks: string[]): boolean { 47082815dcSEvan Bacon return requiredIPadInterface.every((mask) => masks.includes(mask)); 48082815dcSEvan Bacon} 49082815dcSEvan Bacon 50082815dcSEvan Bacon/** 51082815dcSEvan Bacon * Require full screen being disabled requires all ipad interfaces to to be added, 52082815dcSEvan Bacon * otherwise submissions to the iOS App Store will fail. 53082815dcSEvan Bacon * 54082815dcSEvan Bacon * ERROR ITMS-90474: "Invalid Bundle. iPad Multitasking support requires these orientations: 'UIInterfaceOrientationPortrait,UIInterfaceOrientationPortraitUpsideDown,UIInterfaceOrientationLandscapeLeft,UIInterfaceOrientationLandscapeRight'. Found 'UIInterfaceOrientationPortrait,UIInterfaceOrientationPortraitUpsideDown' in bundle 'com.bacon.app'." 55082815dcSEvan Bacon * 56082815dcSEvan Bacon * @param interfaceOrientations 57082815dcSEvan Bacon * @returns 58082815dcSEvan Bacon */ 59082815dcSEvan Baconfunction resolveExistingIpadInterfaceOrientations(interfaceOrientations: any): string[] { 60082815dcSEvan Bacon if ( 61082815dcSEvan Bacon // Ensure type. 62082815dcSEvan Bacon isStringArray(interfaceOrientations) && 63082815dcSEvan Bacon // Don't warn if it's an empty array, this is invalid regardless. 64082815dcSEvan Bacon interfaceOrientations.length && 65082815dcSEvan Bacon // Check if the minimum requirements are met. 66082815dcSEvan Bacon !hasMinimumOrientations(interfaceOrientations) 67082815dcSEvan Bacon ) { 68082815dcSEvan Bacon const existingList = interfaceOrientations!.join(', '); 69082815dcSEvan Bacon addWarningIOS( 70082815dcSEvan Bacon 'ios.requireFullScreen', 71082815dcSEvan Bacon `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}` 72082815dcSEvan Bacon ); 73082815dcSEvan Bacon return interfaceOrientations; 74082815dcSEvan Bacon } 75082815dcSEvan Bacon return []; 76082815dcSEvan Bacon} 77082815dcSEvan Bacon 78082815dcSEvan Bacon// Whether requires full screen on iPad 79082815dcSEvan Baconexport function setRequiresFullScreen( 80082815dcSEvan Bacon config: Pick<ExpoConfig, 'ios'>, 81082815dcSEvan Bacon infoPlist: InfoPlist 82082815dcSEvan Bacon): InfoPlist { 83082815dcSEvan Bacon const requiresFullScreen = getRequiresFullScreen(config); 84082815dcSEvan Bacon if (!requiresFullScreen) { 85082815dcSEvan Bacon const existing = resolveExistingIpadInterfaceOrientations(infoPlist[iPadInterfaceKey]); 86082815dcSEvan Bacon 87384598e2SBrent Vatne // There currently exists no mechanism to safely undo this feature besides `npx expo prebuild --clear`, 88082815dcSEvan Bacon // this seems ok though because anyone using `UISupportedInterfaceOrientations~ipad` probably 89082815dcSEvan Bacon // wants them to be defined to this value anyways. This is also the default value used in the Xcode iOS template. 90082815dcSEvan Bacon 91082815dcSEvan Bacon // Merge any previous interfaces with the required interfaces. 92082815dcSEvan Bacon infoPlist[iPadInterfaceKey] = [...new Set(existing.concat(requiredIPadInterface))]; 93082815dcSEvan Bacon } 94082815dcSEvan Bacon 95082815dcSEvan Bacon return { 96082815dcSEvan Bacon ...infoPlist, 97082815dcSEvan Bacon UIRequiresFullScreen: requiresFullScreen, 98082815dcSEvan Bacon }; 99082815dcSEvan Bacon} 100