UNPKG

expo-splash-screen

Version:

Provides a module to allow keeping the native Splash Screen visible until you choose to hide it.

310 lines (286 loc) 7.99 kB
import { generateImageAsync, ImageOptions } from '@expo/image-utils'; import { ConfigPlugin, IOSConfig, withDangerousMod } from 'expo/config-plugins'; import fs from 'fs'; import path from 'path'; import { IOSSplashConfig } from './types'; const IMAGE_CACHE_NAME = 'splash-ios'; const IMAGESET_PATH = 'Images.xcassets/SplashScreenLogo.imageset'; const LEGACY_IMAGESET_PATH = 'Images.xcassets/SplashScreenLegacy.imageset'; const PNG_FILENAME = 'image'; const DARK_PNG_FILENAME = 'dark_image'; const TABLET_PNG_FILENAME = 'tablet_image'; const DARK_TABLET_PNG_FILENAME = 'dark_tablet_image'; const darkAppearances = [{ appearance: 'luminosity', value: 'dark' }] as const; interface ContentsJsonImage { appearances?: typeof darkAppearances; filename?: string; idiom: 'universal' | 'ipad'; scale?: '1x' | '2x' | '3x'; } export const withIosSplashAssets: ConfigPlugin<IOSSplashConfig> = (config, splash) => { return withDangerousMod(config, [ 'ios', async (config) => { const iosNamedProjectRoot = IOSConfig.Paths.getSourceRoot(config.modRequest.projectRoot); await configureImageAssets({ projectRoot: config.modRequest.projectRoot, iosNamedProjectRoot, image: splash.image, darkImage: splash.dark?.image, tabletImage: splash.tabletImage, darkTabletImage: splash.dark?.tabletImage, imageWidth: splash.imageWidth, enableFullScreenImage: splash.enableFullScreenImage_legacy, }); return config; }, ]); }; /** * Creates imageset containing image for Splash/Launch Screen. */ async function configureImageAssets({ projectRoot, iosNamedProjectRoot, image, darkImage, tabletImage, darkTabletImage, imageWidth, enableFullScreenImage, }: { projectRoot: string; iosNamedProjectRoot: string; image: string | undefined; darkImage: string | undefined; tabletImage: string | undefined; darkTabletImage: string | undefined; imageWidth: number; enableFullScreenImage: boolean; }) { const imagePath = enableFullScreenImage ? LEGACY_IMAGESET_PATH : IMAGESET_PATH; const imageSetPath = path.resolve(iosNamedProjectRoot, imagePath); // remove legacy imageSet if it is not used if (!enableFullScreenImage) { const legacyImageSetPath = path.resolve(iosNamedProjectRoot, LEGACY_IMAGESET_PATH); await fs.promises.rm(legacyImageSetPath, { force: true, recursive: true }); } // ensure old SplashScreen imageSet is removed await fs.promises.rm(imageSetPath, { force: true, recursive: true }); if (!image) { return; } await writeContentsJsonAsync({ assetPath: imageSetPath, image: PNG_FILENAME, darkImage: darkImage ? DARK_PNG_FILENAME : undefined, tabletImage: tabletImage ? TABLET_PNG_FILENAME : undefined, darkTabletImage: darkTabletImage ? DARK_TABLET_PNG_FILENAME : undefined, }); await copyImageFiles({ projectRoot, iosNamedProjectRoot, image, darkImage, tabletImage, darkTabletImage, imageWidth, enableFullScreenImage, }); } async function copyImageFiles({ projectRoot, iosNamedProjectRoot, image, darkImage, tabletImage, darkTabletImage, imageWidth, enableFullScreenImage, }: { projectRoot: string; iosNamedProjectRoot: string; image: string; darkImage: string | undefined; tabletImage: string | undefined; darkTabletImage: string | undefined; imageWidth: number; enableFullScreenImage: boolean; }) { await generateImagesAssetsAsync({ async generateImageAsset(item, fileName) { await Promise.all( [ { ratio: 1, suffix: '' }, { ratio: 2, suffix: '@2x' }, { ratio: 3, suffix: '@3x' }, ].map(async ({ ratio, suffix }) => { const size = imageWidth * ratio; // Using this method will cache the images in `.expo` based on the properties used to generate them. // this method also supports remote URLs and using the global sharp instance. const { source } = await generateImageAsync( { projectRoot, cacheType: IMAGE_CACHE_NAME }, { src: item, width: enableFullScreenImage ? undefined : size, height: enableFullScreenImage ? undefined : size, } as ImageOptions ); // Write image buffer to the file system. // const assetPath = join(iosNamedProjectRoot, IMAGESET_PATH, filename); await fs.promises.writeFile( path.resolve( iosNamedProjectRoot, enableFullScreenImage ? LEGACY_IMAGESET_PATH : IMAGESET_PATH, `${fileName}${suffix}.png` ), source ); }) ); }, anyItem: image, darkItem: darkImage, tabletItem: tabletImage, darkTabletItem: darkTabletImage, }); } async function generateImagesAssetsAsync({ generateImageAsset, anyItem, darkItem, tabletItem, darkTabletItem, }: { generateImageAsset: (item: string, fileName: string) => Promise<void>; anyItem: string; darkItem: string | undefined; tabletItem: string | undefined; darkTabletItem: string | undefined; }) { const items = [ [anyItem, PNG_FILENAME], [darkItem, DARK_PNG_FILENAME], [tabletItem, TABLET_PNG_FILENAME], [darkTabletItem, DARK_TABLET_PNG_FILENAME], ].filter((tuple): tuple is [string, string] => tuple[0] != null); await Promise.all(items.map(([item, fileName]) => generateImageAsset(item, fileName))); } export function buildContentsJsonImages({ image, darkImage, tabletImage, darkTabletImage, }: { image: string; tabletImage: string | undefined; darkImage: string | undefined; darkTabletImage: string | undefined; }): ContentsJsonImage[] { // Phone light const images: ContentsJsonImage[] = [ { idiom: 'universal', filename: `${image}.png`, scale: '1x', }, { idiom: 'universal', filename: `${image}@2x.png`, scale: '2x', }, { idiom: 'universal', filename: `${image}@3x.png`, scale: '3x', }, ]; // Phone dark if (darkImage) { images.push( { idiom: 'universal', appearances: darkAppearances, scale: '1x', filename: `${darkImage}.png`, }, { idiom: 'universal', appearances: darkAppearances, scale: '2x', filename: `${darkImage}@2x.png`, }, { idiom: 'universal', appearances: darkAppearances, scale: '3x', filename: `${darkImage}@3x.png`, } ); } // Tablet light if (tabletImage) { images.push( { idiom: 'ipad', filename: `${tabletImage}.png`, scale: '1x', }, { idiom: 'ipad', scale: '2x', filename: `${tabletImage}@2x.png`, } ); } // Tablet dark if (darkTabletImage) { images.push( { idiom: 'ipad', appearances: darkAppearances, filename: `${darkTabletImage}.png`, scale: '1x', }, { idiom: 'ipad', appearances: darkAppearances, filename: `${darkTabletImage}@2x.png`, scale: '2x', } ); } return images; } async function writeContentsJsonAsync({ assetPath, image, darkImage, tabletImage, darkTabletImage, }: { assetPath: string; image: string; darkImage: string | undefined; tabletImage: string | undefined; darkTabletImage: string | undefined; }) { const images = buildContentsJsonImages({ image, darkImage, tabletImage, darkTabletImage }); await fs.promises.mkdir(assetPath, { recursive: true }); await fs.promises.writeFile( path.join(assetPath, 'Contents.json'), JSON.stringify( { images, info: { version: 1, // common practice is for the tool that generated the icons to be the "author" author: 'expo', }, }, null, 2 ), 'utf8' ); }