Skip to content

Instantly share code, notes, and snippets.

@piotr-cz
Last active April 28, 2022 11:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save piotr-cz/1a3f1360d189231792db21756cb4a72d to your computer and use it in GitHub Desktop.
Save piotr-cz/1a3f1360d189231792db21756cb4a72d to your computer and use it in GitHub Desktop.
Cordova splash screens hook, with Android 9-Patch generation
<?xml version='1.0' encoding='utf-8'?>
<widget>
<!-- ... -->
<hook src="scripts/cordova/media/splashscreens.js" type="before_platform_add" />
</widget>
#! /usr/bin/env node
/**
* Generate splash screens
* <cordova hook>
* <cli command>
* @see https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-splashscreen/index.html
*/
const path = require('path')
const sharp = require('sharp')
// Not available, but not relevant to this gist
const utils = require('../../utils')
// CLI command
if (require.main === module) {
dispatch(utils.getCliOpts(process))
// Cordova hook/ import
} else {
module.exports = ctx => dispatch(ctx.opts)
}
/**
* Dispatch
* @param {Array} platforms
* @param {Object} options
* @param {string} [options.productFlavor]
* @param {string} projectRoot
* @return {Promise}
*/
function dispatch({platforms = [], options = {}, projectRoot}) {
console.log('Generating splash screens...')
const productFlavorId = options.productFlavor || utils.getDefaultProductFlavorId(projectRoot)
const progressPromises = []
// Android
if (!platforms.length || utils.platformsIncludes(platforms, 'android')) {
progressPromises.push(createAndroidSplashscreens(projectRoot, productFlavorId))
}
// iOS
if (!platforms.length || utils.platformsIncludes(platforms, 'ios')) {
progressPromises.push(createIosSplashscreens(projectRoot, productFlavorId))
}
// Summary
return Promise.all(progressPromises)
.then(() => console.log('...done\n'))
.catch(error => console.error(error))
}
/**
* Create Android splash screens
* @param {string} projectRoot
* @param {string} productFlavorId
* @return {Promise}
*/
async function createAndroidSplashscreens(projectRoot, productFlavorId) {
const dimensions = [
// Portrait
['drawable-port-ldpi-screen', 200, 320],
['drawable-port-mdpi-screen', 320, 480],
['drawable-port-hdpi-screen', 480, 800],
['drawable-port-xhdpi-screen', 720, 1280],
['drawable-port-xxhdpi-screen', 960, 1600],
['drawable-port-xxxhdpi-screen', 1280, 1920],
// Landscape (not used)
['drawable-land-ldpi-screen', 320, 200],
['drawable-land-mdpi-screen', 480, 320],
['drawable-land-hdpi-screen', 800, 480],
['drawable-land-xhdpi-screen', 1280, 720],
['drawable-land-xxhdpi-screen', 1600, 960],
['drawable-land-xxxhdpi-screen', 1920, 1280],
]
const sourcePortaitPath = path.resolve(projectRoot, 'artwork', productFlavorId, 'splash--portrait.svg')
const buildDir = path.resolve(projectRoot, 'res/screen/android')
const metadata = await sharp(sourcePortaitPath).metadata()
return Promise.all(dimensions.map(([basename, width, height]) =>
createSplash(sourcePortaitPath, `${buildDir}/${basename}.9.png`, {width, height}, metadata, true)
))
}
/**
* Create iOS Splash screens
* @param {string} projectRoot
* @param {string} productFlavorId
* @return {Promise}
* @see https://github.com/apache/cordova-ios/blob/rel/5.0.0/bin/templates/scripts/cordova/lib/prepare.js#L416
* @see https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/adaptivity-and-layout/
*/
async function createIosSplashscreens(projectRoot, productFlavorId) {
const dimensions = [
// Lauch storyboard images
// Single-image launch screen
['Default@2x~universal~anyany', 2732, 2732], // Any
// Multi-image launch sreens (Universal)
['Default@2x~universal~comany', 1278, 2732], // Portrait
['Default@2x~universal~anycom', 2732, 1278], // Landscape (useless)
['Default@2x~universal~comcom', 1334, 750], // Landscape (narrow)
['Default@3x~universal~anyany', 2208, 2208], // Any
['Default@3x~universal~comany', 1242, 2208], // Portrait
['Default@3x~universal~anycom', 2208, 1242], // Landscape
['Default@3x~universal~comcom', 1242, 1242], // Landscape (narrow, useless)
]
const sourcePortaitPath = path.resolve(projectRoot, 'artwork', productFlavorId, 'splash--portrait.svg')
const buildDir = path.resolve(projectRoot, 'res/screen/ios')
const metadata = await sharp(sourcePortaitPath).metadata()
return Promise.all(dimensions.map(([basename, width, height]) =>
createSplash(sourcePortaitPath, `${buildDir}/${basename}.png`, {width, height}, metadata)
))
}
/**
* Create splash screen
* @param {string} sourcePath
* @param {string} destPath
* @param {Object} destSize
* @param {number} destSize.width
* @param {number} destSize.height
* @param {Object} metadata
* @param {number} metadata.width
* @param {number} metadata.height
* @param {number} metadata.density
* @param {boolean} use9patch
* @return {Promise}
*/
async function createSplash(sourcePath, destPath, destSize, metadata, use9patch = false) {
// Note: max density is 2400
const density = Math.max(destSize.width, destSize.height) / Math.max(metadata.width, metadata.height) * metadata.density
const composites = [
{ input: await sharp(sourcePath, {density}).resize(destSize.width, destSize.height).toBuffer() },
]
const canvasSize = { ...destSize }
if (use9patch) {
// Add offset on each side
canvasSize.width += 2
canvasSize.height += 2
// Add markers
composites.push({
input: await sharp(create9PatchMarkers(canvasSize)).toBuffer()
})
}
return sharp({
create: {
width: canvasSize.width,
height: canvasSize.height,
channels: 4,
background: 'transparent',
}
})
.composite(composites)
.toFile(destPath)
}
/**
* Create 9patch markers
* At position: top-left, top-right, left-top, left-bottom
* @param {Object} canvasSize
* @param {number} canvasSize.width
* @param {number} canvasSize.height
* @return {Buffer}
*/
function create9PatchMarkers({width, height}) {
const repeatableLength = Math.round(Math.min(width, height) * 0.15)
return Buffer.from(`
<svg viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg">
<line stroke="black" x1="1" x2="${repeatableLength}" y1=".5" y2=".5" />
<line stroke="black" x1="${width - repeatableLength}" x2="${width - 1}" y1=".5" y2=".5" />
<line stroke="black" x1=".5" x2=".5" y1="1" y2="${repeatableLength}" />
<line stroke="black" x1=".5" x2=".5" y1="${height - repeatableLength}" y2="${height - 1}" />
</svg>
`)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment