diff --git a/README.md b/README.md index b1ebcb2..ab8963d 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Icon management for Mobile Apps. Create icons, generate all required sizes, labe * [Generating Icons](#generating-icons) * [Labelling Icons](#labelling-icons) * [Adaptive Icons](#adaptive-icons) + * [Rounded Icons on Android](#rounded-icons-on-android) * [Developer Guide](#developer-guide) * [Initial Setup](#initial-setup) * [Running Tests](#running-tests) @@ -180,6 +181,23 @@ To test how adaptive icons will look when animated, swiped, etc, the [Adaptive I Note that Adaptive Icons of *all* supported sizes are generated. However, we also generate the `res/mipmap-anydpi-v26/` adaptive icon. This is a large size icon which Android from v26 onwards will automatically rescale as needed to all other sizes. This technically makes the density specific icons redundant. The reason we generate both is to ensure that after `generate` is run, *all* icons in the project will be consistent. +### Rounded Icons on Android + +The default behaviour when generating icons is that both normal and rounded icons are generated from a single image. If you want to use different image files for normal and rounded icons, you can set the `--rounded` option to specify behaviour. + +Setting `--rounded none` means that the tool will not generate any `ic_launcher_round.png` icons for Android. While setting `--rounded only` means that it will generate **only** `ic_launcher_round.png` icons. + +In this way, you can generate "normal" and "rounded" icons from different image files. + +Sample usage: +```bash +# generates all iOS icons and "normal" icons on Android: +app-icon generate -i icon.png --platforms=android,ios --rounded none + +# generates just rounded icons on Android: +app-icon generate -i icon.round.png --platforms=android --rounded only +``` + ## Developer Guide The only dependencies are Node 8 (or above) and Yarn. diff --git a/bin/app-icon.js b/bin/app-icon.js index 8e65574..0cbe543 100755 --- a/bin/app-icon.js +++ b/bin/app-icon.js @@ -46,6 +46,7 @@ program .option('--background-icon [optional]', "The background icon path. Defaults to 'icon.background.png'") .option('--foreground-icon [optional]', "The foregroud icon path. Defaults to 'icon.foregroud.png'") .option('--adaptive-icons [optional]', "Additionally, generate Android Adaptive Icon templates. Defaults to 'false'") + .option('--rounded [optional]', 'Filter options for rounded icons on Android, Possible values: [none, only]. No default, which means both normal and rounded icons are generated.') .action(async (parameters) => { const { icon, @@ -54,6 +55,7 @@ program search, platforms, adaptiveIcons, + rounded, } = parameters; await imageMagickCheck(); @@ -75,6 +77,7 @@ program searchRoot: search, platforms, adaptiveIcons, + rounded, }); } catch (err) { console.error(chalk.red(`An error occurred generating the icons: ${err.message}`)); diff --git a/src/android/generate-manifest-icons.js b/src/android/generate-manifest-icons.js index 65fb9b9..6da8a9b 100644 --- a/src/android/generate-manifest-icons.js +++ b/src/android/generate-manifest-icons.js @@ -8,7 +8,7 @@ const androidManifestIcons = require('./AndroidManifest.icons.json'); const resizeImage = require('../resize/resize-image'); // Generate Android Manifest icons given a manifest file. -module.exports = async function generateManifestIcons(sourceIcon, manifest) { +module.exports = async function generateManifestIcons(sourceIcon, manifest, rounded) { // Create the object we will return. const results = { icons: [], @@ -17,8 +17,21 @@ module.exports = async function generateManifestIcons(sourceIcon, manifest) { // We've got the manifest file, get the parent folder. const manifestFolder = path.dirname(manifest); + function filterRounded(icon) { + if (!rounded) return true; + + switch (rounded) { + case 'only': + return icon.path.indexOf('ic_launcher_round.png') !== -1; + case 'none': + return icon.path.indexOf('ic_launcher_round.png') === -1; + default: + return true; + } + } + // Generate each image in the full icon set, updating the contents. - await Promise.all(androidManifestIcons.icons.map(async (icon) => { + await Promise.all(androidManifestIcons.icons.filter(filterRounded).map(async (icon) => { const targetPath = path.join(manifestFolder, icon.path); // Each icon lives in its own folder, so we'd better make sure that folder diff --git a/src/android/generate-manifest-icons.specs.js b/src/android/generate-manifest-icons.specs.js index 5e5b157..fd8ca9c 100644 --- a/src/android/generate-manifest-icons.specs.js +++ b/src/android/generate-manifest-icons.specs.js @@ -36,6 +36,42 @@ const testManifests = [{ describe('generate-manifest-icons', () => { // Run each test. testManifests.forEach(({ projectName, manifestPath }) => { + it(`should be able to specify only round icons for the ${projectName} manifest`, async () => { + // Get the manifest folder, create an array of every icon we expect to see. + const manifestFolder = path.dirname(manifestPath); + const resourceFolders = expectedFolders.map((f) => path.join(manifestFolder, f)); + const resourceFoldersFiles = resourceFolders.reduce((allFiles, folder) => { + expectedFiles.forEach((ef) => allFiles.push(path.join(folder, ef))); + return allFiles; + }, []); + + // Delete all of the folders we're expecting to create, then generate the icons. + await Promise.all(resourceFolders.map(deleteFolderIfExists)); + await (generateManifestIcons(sourceIcon, manifestPath, 'only')); + const filesDoExist = await Promise.all(resourceFoldersFiles.map(fileExists)); + filesDoExist.forEach((exists, index) => { + expect(exists, `${resourceFoldersFiles[index]} should be generated`).to.equal(index % 2 !== 0); + }); + }); + + it(`should be able to specify none round icons for the ${projectName} manifest`, async () => { + // Get the manifest folder, create an array of every icon we expect to see. + const manifestFolder = path.dirname(manifestPath); + const resourceFolders = expectedFolders.map((f) => path.join(manifestFolder, f)); + const resourceFoldersFiles = resourceFolders.reduce((allFiles, folder) => { + expectedFiles.forEach((ef) => allFiles.push(path.join(folder, ef))); + return allFiles; + }, []); + + // Delete all of the folders we're expecting to create, then generate the icons. + await Promise.all(resourceFolders.map(deleteFolderIfExists)); + await (generateManifestIcons(sourceIcon, manifestPath, 'none')); + const filesDoExist = await Promise.all(resourceFoldersFiles.map(fileExists)); + filesDoExist.forEach((exists, index) => { + expect(exists, `${resourceFoldersFiles[index]} should be generated`).to.equal(index % 2 === 0); + }); + }); + it(`should be able to generate icons and round icons for the ${projectName} manifest`, async () => { // Get the manifest folder, create an array of every icon we expect to see. const manifestFolder = path.dirname(manifestPath); diff --git a/src/generate.js b/src/generate.js index 49c81ce..e337b66 100644 --- a/src/generate.js +++ b/src/generate.js @@ -15,6 +15,7 @@ module.exports = async function generate(parameters) { searchRoot, platforms, adaptiveIcons, + rounded, } = validateParameters(parameters || {}); // Set up the results object. @@ -43,7 +44,7 @@ module.exports = async function generate(parameters) { if (!platforms.includes('android')) return null; console.log(`Found Android Manifest: ${manifest}...`); - const manResult = await generateManifestIcons(sourceIcon, manifest); + const manResult = await generateManifestIcons(sourceIcon, manifest, rounded); results.manifests.push({ manifest, icons: manResult.icons }); manResult.icons.forEach((icon) => { console.log(` ${chalk.green('✓')} Generated icon ${icon}`); diff --git a/src/validate-parameters.js b/src/validate-parameters.js index b8abc68..bb8681f 100644 --- a/src/validate-parameters.js +++ b/src/validate-parameters.js @@ -21,6 +21,11 @@ module.exports = function validateParameters(parameters) { const backgroundIcon = parameters.backgroundIcon || 'icon.background.png'; const foregroundIcon = parameters.foregroundIcon || 'icon.foreground.png'; + const { rounded } = parameters; + if (rounded && (rounded !== 'none' && rounded !== 'only')) { + throw new Error(`--rounded option '${rounded}' unrecognized, expected 'none' or 'only'.`); + } + return { sourceIcon, backgroundIcon, @@ -28,5 +33,6 @@ module.exports = function validateParameters(parameters) { searchRoot, platforms, adaptiveIcons, + rounded, }; };