Generate push icon

This commit is contained in:
Jon Staab 2026-07-07 12:31:10 -07:00
parent 5843b3e33a
commit f00687d20e
7 changed files with 90 additions and 0 deletions

View file

@ -36,6 +36,17 @@
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
</provider> </provider>
<!-- FCM uses these when it renders a notification-payload message itself
(app backgrounded/killed). Without an explicit monochrome small icon
it falls back to the full-color adaptive launcher icon, which many
Android builds render as a blank square or drop entirely. -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_stat_notify" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id" />
</application> </application>
<!-- Permissions --> <!-- Permissions -->

View file

@ -1,5 +1,8 @@
package social.flotilla; package social.flotilla;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import com.getcapacitor.BridgeActivity; import com.getcapacitor.BridgeActivity;
@ -10,6 +13,28 @@ public class MainActivity extends BridgeActivity {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
registerPlugin(AndroidPushFallbackPlugin.class); registerPlugin(AndroidPushFallbackPlugin.class);
createPushNotificationChannel();
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
} }
// Ensures the channel named by the FCM default_notification_channel_id metadata
// exists. FCM does not create it for us; if it's missing when a notification
// arrives, Android falls back to a generic "Miscellaneous" channel. Channels
// persist once created, so doing this on launch is sufficient.
private void createPushNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager manager = getSystemService(NotificationManager.class);
String id = getString(R.string.default_notification_channel_id);
if (manager != null && manager.getNotificationChannel(id) == null) {
NotificationChannel channel = new NotificationChannel(
id,
"Notifications",
NotificationManager.IMPORTANCE_HIGH
);
channel.setDescription("New messages and activity");
manager.createNotificationChannel(channel);
}
}
}
} }

View file

@ -4,4 +4,5 @@
<string name="title_activity_main">Flotilla</string> <string name="title_activity_main">Flotilla</string>
<string name="package_name">social.flotilla</string> <string name="package_name">social.flotilla</string>
<string name="custom_url_scheme">social.flotilla</string> <string name="custom_url_scheme">social.flotilla</string>
<string name="default_notification_channel_id">flotilla_push</string>
</resources> </resources>

View file

@ -40,3 +40,6 @@ npx @capacitor/assets generate \
--iconBackgroundColorDark '#222222' \ --iconBackgroundColorDark '#222222' \
--splashBackgroundColor '#ffffff' \ --splashBackgroundColor '#ffffff' \
--splashBackgroundColorDark '#191E24' --splashBackgroundColorDark '#191E24'
# @capacitor/assets doesn't generate Android notification icons
node scripts/generate-notification-icon.mjs

View file

@ -35,6 +35,7 @@
"postcss": "^8.5.15", "postcss": "^8.5.15",
"prettier": "^3.8.1", "prettier": "^3.8.1",
"prettier-plugin-svelte": "^3.4.1", "prettier-plugin-svelte": "^3.4.1",
"sharp": "0.35.0-rc.0",
"svelte": "^5.55.9", "svelte": "^5.55.9",
"svelte-check": "^4.3.5", "svelte-check": "^4.3.5",
"tailwindcss": "^4.2.2", "tailwindcss": "^4.2.2",

Binary file not shown.

View file

@ -0,0 +1,49 @@
#!/usr/bin/env node
// Generates the Android notification (status-bar) small icon from the app's
// source logo. @capacitor/assets does NOT produce notification icons, so the
// build pipeline calls this after `@capacitor/assets generate` to keep the
// notification icon in sync with a custom platform logo (VITE_PLATFORM_LOGO).
//
// A notification small icon must be a monochrome silhouette: Android renders
// only its alpha channel and tints it. We take the logo's alpha channel as the
// shape and fill it white, with a little transparent padding so it isn't
// edge-to-edge in the status bar.
import sharp from "sharp"
import {mkdir} from "node:fs/promises"
import {dirname, join} from "node:path"
const source = process.argv[2] || "assets/logo.png"
const resDir = process.argv[3] || "android/app/src/main/res"
// density bucket -> small-icon size in px
const densities = {mdpi: 24, hdpi: 36, xhdpi: 48, xxhdpi: 72, xxxhdpi: 96}
const generate = async (size, out) => {
const pad = Math.round(size * 0.14)
const inner = size - pad * 2
// The logo's alpha channel is the shape we want to render.
const alpha = await sharp(source)
.resize(inner, inner, {fit: "contain", background: {r: 0, g: 0, b: 0, alpha: 0}})
.ensureAlpha()
.extractChannel(3)
.toColourspace("b-w")
.raw()
.toBuffer()
await sharp({
create: {width: inner, height: inner, channels: 3, background: {r: 255, g: 255, b: 255}},
})
.joinChannel(alpha, {raw: {width: inner, height: inner, channels: 1}})
.extend({top: pad, bottom: pad, left: pad, right: pad, background: {r: 0, g: 0, b: 0, alpha: 0}})
.png()
.toFile(out)
console.log(`wrote ${out} (${size}px)`)
}
for (const [density, size] of Object.entries(densities)) {
const out = join(resDir, `drawable-${density}`, "ic_stat_notify.png")
await mkdir(dirname(out), {recursive: true})
await generate(size, out)
}