Files
TREK/client/scripts/generate-native-icons.mjs
T
2026-08-24 00:23:51 +02:00

68 lines
2.7 KiB
JavaScript

/**
* Generates the Android status-bar notification icon from the master
* white-silhouette SVG.
*
* Android renders the small notification icon as a mask: every non-transparent
* pixel becomes the tint colour and everything else is dropped. Feeding it the
* launcher icon (a full-bleed coloured square) therefore produces a solid white
* blob, which is the single most common Capacitor push-notification complaint.
* public/icons/icon-white.svg is already white-on-transparent, so we only have
* to rasterise it at the five densities and flatten the colour to pure white so
* any future edit to that SVG's fill cannot silently change the mask.
*
* Referenced from AndroidManifest.xml as @drawable/notification_icon.
* Run: node scripts/generate-native-icons.mjs
*/
import { mkdirSync } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import sharp from 'sharp';
const __dirname = dirname(fileURLToPath(import.meta.url));
const clientDir = join(__dirname, '..');
const source = join(clientDir, 'public', 'icons', 'icon-white.svg');
const resDir = join(clientDir, 'android', 'app', 'src', 'main', 'res');
// Android notification icon sizes, in px, per density bucket (24dp baseline).
const densities = [
{ dir: 'drawable-mdpi', size: 24 },
{ dir: 'drawable-hdpi', size: 36 },
{ dir: 'drawable-xhdpi', size: 48 },
{ dir: 'drawable-xxhdpi', size: 72 },
{ dir: 'drawable-xxxhdpi', size: 96 },
];
for (const { dir, size } of densities) {
const outDir = join(resDir, dir);
mkdirSync(outDir, { recursive: true });
// Inset the glyph slightly: Android's status bar clips tight to the 24dp box
// and a mark that touches the edge reads as cropped.
const glyph = Math.round(size * 0.86);
const mask = await sharp(source, { density: 512 })
.resize(glyph, glyph, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
.extend({
top: Math.floor((size - glyph) / 2),
bottom: Math.ceil((size - glyph) / 2),
left: Math.floor((size - glyph) / 2),
right: Math.ceil((size - glyph) / 2),
background: { r: 0, g: 0, b: 0, alpha: 0 },
})
.ensureAlpha()
.toBuffer();
// Keep the alpha, force every visible pixel to pure white. `dest-in` composites
// the white plate through the glyph's alpha channel.
await sharp({
create: { width: size, height: size, channels: 4, background: { r: 255, g: 255, b: 255, alpha: 1 } },
})
.composite([{ input: mask, blend: 'dest-in' }])
.png({ compressionLevel: 9 })
.toFile(join(outDir, 'notification_icon.png'));
console.log(` ✓ ${dir}/notification_icon.png (${size}x${size})`);
}
console.log('Android notification icons generated.');