@maccesar/titools 4.22.0 → 4.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/purgetss/SKILL.md +1 -1
- package/skills/purgetss/references/app-branding.md +38 -4
- package/skills/purgetss/references/cli-commands.md +3 -2
- package/skills/purgetss/references/customization-deep-dive.md +3 -2
- package/skills/purgetss/references/version-history.md +4 -0
- package/skills/ti-expert/references/push-notifications.md +20 -3
package/package.json
CHANGED
package/skills/purgetss/SKILL.md
CHANGED
|
@@ -468,7 +468,7 @@ Load these only when needed:
|
|
|
468
468
|
- [PurgeTSS UI in Classic](references/purgetss-ui-classic.md) - Native JavaScript setup, public exports, animation objects, cleanup, and platform notes
|
|
469
469
|
|
|
470
470
|
### Release Notes
|
|
471
|
-
- [Version History](references/version-history.md) - Release-by-release feature additions and behavior changes (v7.4.0 → v7.17.
|
|
471
|
+
- [Version History](references/version-history.md) - Release-by-release feature additions and behavior changes (v7.4.0 → v7.17.1)
|
|
472
472
|
|
|
473
473
|
> **💡 TEXT FONTS (Google Fonts, Roboto, etc.)**
|
|
474
474
|
> For text fonts, see [Custom Fonts](references/custom-fonts.md).
|
|
@@ -25,6 +25,7 @@ For the terse flag reference, see the [`brand` command reference](./cli-commands
|
|
|
25
25
|
- [Android dark mode](#android-dark-mode)
|
|
26
26
|
- [Android 12+ splash artwork](#android-12-splash-artwork)
|
|
27
27
|
- [Android pre-12 splash](#android-pre-12-splash)
|
|
28
|
+
- [FCM notification icon](#fcm-notification-icon)
|
|
28
29
|
- [The iOS launch screen and LaunchLogo.png](#the-ios-launch-screen-and-launchlogopng)
|
|
29
30
|
- [iPhone launch images](#iphone-launch-images)
|
|
30
31
|
- [iOS 18+ Dark and Tinted variants](#ios-18-dark-and-tinted-variants)
|
|
@@ -164,9 +165,10 @@ brand: {
|
|
|
164
165
|
appicon: { padding: '10%' }, // appicon.png (128×128)
|
|
165
166
|
androidSplash: { padding: '26%' }, // assets/android/default.png + images/res-*/default.png × 11
|
|
166
167
|
|
|
167
|
-
// Opt-in: inert until
|
|
168
|
+
// Opt-in: splash_icon stays inert until the Android theme points at it.
|
|
169
|
+
// notificationicon is read by firebase.cloudmessaging, by that exact name.
|
|
168
170
|
splashIcon: { enabled: false }, // drawable-*/splash_icon.png × 5
|
|
169
|
-
notificationIcon: { enabled: false }, // drawable-*/
|
|
171
|
+
notificationIcon: { enabled: false }, // drawable-*/notificationicon.png × 5
|
|
170
172
|
ninePatch: { enabled: false } // background.9.png (not implemented yet)
|
|
171
173
|
}
|
|
172
174
|
```
|
|
@@ -258,12 +260,12 @@ And these live at the top level:
|
|
|
258
260
|
| `appicon` | `appicon` | `appicon.png` (128×128) | `10%` | yes |
|
|
259
261
|
| `android-splash` | `androidSplash` | `assets/android/default.png` + `images/res-*/default.png` × 11 | `26%` | yes |
|
|
260
262
|
| `splash-icon` | `splashIcon` | `drawable-*/splash_icon.png` × 5 | — | `--splash-icon` |
|
|
261
|
-
| `notification-icon` | `notificationIcon` | `drawable-*/
|
|
263
|
+
| `notification-icon` | `notificationIcon` | `drawable-*/notificationicon.png` × 5 | — | `--notification-icon` |
|
|
262
264
|
| `nine-patch` | `ninePatch` | `background.9.png` | — | `--nine-patch` (not implemented yet) |
|
|
263
265
|
|
|
264
266
|
`ic_launcher.xml` always travels inside `adaptive`; it is never generated on its own.
|
|
265
267
|
|
|
266
|
-
|
|
268
|
+
Three pieces are opt-in, because none of them does anything on its own. `splash_icon.png` is inert until the theme points `windowSplashScreenAnimatedIcon` at it. `notificationicon.png` needs `firebase.cloudmessaging` in the project, plus a `meta-data` entry for the notification messages that do not reach it by name — see [FCM notification icon](#fcm-notification-icon). `background.9.png` is not implemented yet.
|
|
267
269
|
|
|
268
270
|
### `background` is inherited, `padding` is not
|
|
269
271
|
|
|
@@ -497,6 +499,34 @@ Earlier versions regenerated only the first file and hid the other 11 behind a `
|
|
|
497
499
|
> A solid `windowBackground` wins
|
|
498
500
|
> If the launch theme sets `android:windowBackground` to a plain color — which is what [Matching the launch background](#matching-the-launch-background) recommends — that color takes precedence over this artwork on Android <12. Drop the `windowBackground` item if you want the image to show instead.
|
|
499
501
|
|
|
502
|
+
## FCM notification icon
|
|
503
|
+
|
|
504
|
+
`notification-icon` is off by default. Turn it on with `brand.notificationIcon.enabled`, or for a single run with `--notification-icon`. It writes five densities to `app/platform/android/res/drawable-*/notificationicon.png` (`platform/android/res/drawable-*/notificationicon.png` in Classic), white on transparent, because **Android tints status-bar icons at runtime**: every non-transparent pixel comes out white and the color in the artwork is discarded.
|
|
505
|
+
|
|
506
|
+
The filename is fixed, like `appicon.png` and `DefaultIcon.png`, because the consumer dictates it. `firebase.cloudmessaging` resolves the drawable by name inside `TiFirebaseMessagingService.showNotification()`, through `getResource("notificationicon")`, and that lookup is the only path a **data** message has. When the drawable is missing the module falls back to `appicon` — the opaque launcher icon, which the status bar renders as a white blob, since only the alpha channel survives the tint.
|
|
507
|
+
|
|
508
|
+
**Notification** messages take the configurable path instead. Two `meta-data` entries go under `<application>` in `tiapp.xml`:
|
|
509
|
+
|
|
510
|
+
```xml
|
|
511
|
+
<meta-data android:name="com.google.firebase.messaging.default_notification_icon"
|
|
512
|
+
android:resource="@drawable/notificationicon"/>
|
|
513
|
+
<meta-data android:name="com.google.firebase.messaging.default_notification_color"
|
|
514
|
+
android:resource="@color/notification_tint"/>
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
The second one colors the notification's accent, not the icon. Create or merge `app/platform/android/res/values/colors.xml` (`platform/android/res/values/colors.xml` in Classic):
|
|
518
|
+
|
|
519
|
+
```xml
|
|
520
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
521
|
+
<resources>
|
|
522
|
+
<color name="notification_tint">#0B1326</color>
|
|
523
|
+
</resources>
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
> **Renamed in v7.17.1**
|
|
527
|
+
>
|
|
528
|
+
> The piece used to write `ic_stat_notify.png`, the Android convention for status-bar drawables but not the name the module looks up. Under the old name a data message never found the icon, whatever the `meta-data` said. A project that wired `@drawable/ic_stat_notify` by hand changes that one line and deletes the five stale files.
|
|
529
|
+
|
|
500
530
|
## The iOS launch screen and LaunchLogo.png
|
|
501
531
|
|
|
502
532
|
Titanium builds `LaunchLogo.imageset` itself on every iOS build, resizing one source into the five sizes it needs. It looks for `LaunchLogo.png` first and falls back to `DefaultIcon.png`.
|
|
@@ -744,6 +774,10 @@ cp docs/my-logo-mono.svg purgetss/brand/logo-mono.svg
|
|
|
744
774
|
purgetss brand
|
|
745
775
|
```
|
|
746
776
|
|
|
777
|
+
### The notification shows a white square, or the app icon instead of my notification icon
|
|
778
|
+
|
|
779
|
+
`firebase.cloudmessaging` did not find the drawable and fell back to `appicon`, which the status bar tints into a solid blob. Three causes, in order of likelihood: the piece was never generated (`brand.notificationIcon.enabled` is `false` by default — run `purgetss brand --only notification-icon`), the project still carries `ic_stat_notify.png` from before v7.17.1, or the `meta-data` entry points at the old name. See [FCM notification icon](#fcm-notification-icon).
|
|
780
|
+
|
|
747
781
|
### iOS rejects the app icon upload ("contains transparency")
|
|
748
782
|
|
|
749
783
|
Apple requires App Store icons to have no alpha channel. `DefaultIcon-ios.png` is always flattened on `brand.background` for that reason. If you edited the file manually and reintroduced alpha, re-run `purgetss brand`.
|
|
@@ -82,7 +82,8 @@ module.exports = {
|
|
|
82
82
|
adaptive: { padding: '18%' }, legacyIcon: { padding: '10%' },
|
|
83
83
|
appicon: { padding: '10%' }, androidSplash: { padding: '26%' },
|
|
84
84
|
|
|
85
|
-
// Opt-in: inert until
|
|
85
|
+
// Opt-in: splash_icon stays inert until the Android theme points at it.
|
|
86
|
+
// notificationicon is read by firebase.cloudmessaging, by that exact name.
|
|
86
87
|
splashIcon: { enabled: false }, notificationIcon: { enabled: false },
|
|
87
88
|
ninePatch: { enabled: false }
|
|
88
89
|
},
|
|
@@ -236,7 +237,7 @@ Radius precedence and the outputs intentionally left unmasked are documented in
|
|
|
236
237
|
|
|
237
238
|
| Flag | Purpose |
|
|
238
239
|
| --- | --- |
|
|
239
|
-
| `--notification-icon` | Also emit `
|
|
240
|
+
| `--notification-icon` | Also emit `notificationicon.png × 5`. |
|
|
240
241
|
| `--splash-icon` | Also emit `splash_icon.png × 5`. |
|
|
241
242
|
| `--nine-patch` | Declared but not implemented yet; prints a warning and writes nothing. |
|
|
242
243
|
**Logo variants & overrides**
|
|
@@ -70,9 +70,10 @@ module.exports = {
|
|
|
70
70
|
appicon: { padding: '10%' }, // appicon.png (128×128)
|
|
71
71
|
androidSplash: { padding: '26%' }, // assets/android/default.png + images/res-*/default.png × 11
|
|
72
72
|
|
|
73
|
-
// Opt-in: inert until
|
|
73
|
+
// Opt-in: splash_icon stays inert until the Android theme points at it.
|
|
74
|
+
// notificationicon is read by firebase.cloudmessaging, by that exact name.
|
|
74
75
|
splashIcon: { enabled: false }, // drawable-*/splash_icon.png × 5
|
|
75
|
-
notificationIcon: { enabled: false }, // drawable-*/
|
|
76
|
+
notificationIcon: { enabled: false }, // drawable-*/notificationicon.png × 5
|
|
76
77
|
ninePatch: { enabled: false } // background.9.png (not implemented yet)
|
|
77
78
|
},
|
|
78
79
|
// Sources in purgetss/images/ are 4x masters: a 1024px file yields
|
|
@@ -8,6 +8,10 @@ When in doubt about whether a class, flag, or config key exists in the user's in
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
## v7.17.1
|
|
12
|
+
- **The `notification-icon` piece writes `notificationicon.png`, not `ic_stat_notify.png`.** `firebase.cloudmessaging` hardcodes that name — `TiFirebaseMessagingService.showNotification()` calls `getResource("notificationicon")` — and it is the only path a **data** message has. The `default_notification_icon` meta-data is the configurable one and covers **notification** messages alone, so under the old name no manifest entry could rescue a data message: the module fell back to the opaque `appicon`, which the status bar renders as a white blob. The filename is fixed rather than a config key, for the same reason `appicon.png` and `DefaultIcon.png` are. Projects that wired `@drawable/ic_stat_notify` by hand need to update that one meta-data line and delete the five stale files. See [app-branding.md](app-branding.md).
|
|
13
|
+
- The comment above the opt-in pieces in the generated config now names each consumer instead of claiming both are inert without hand-edited XML. Only `splash_icon` is.
|
|
14
|
+
|
|
11
15
|
## v7.17.0
|
|
12
16
|
- The `images:` section rejects unknown keys instead of ignoring them, at both levels: the five top-level keys (`quality`, `format`, `autoSync`, `confirmOverwrites`, `files`) and the three inside each `files[]` entry (`filename`, `width`, `height`, with `filename` required). Nothing is generated when validation fails. Never suggest `width`, `opacity`, `padding` or `output` as config keys — they are CLI flags and now abort the run. See [multi-density-images.md](multi-density-images.md).
|
|
13
17
|
- The generated `images:` block gained four comment lines explaining the 4× master convention, and the `quality` comment now names the formats it reaches: `webp`, `jpeg`, `avif`, `tiff`. PNG uses `compressionLevel: 9` and GIF takes no quality parameter, so neither is affected. Both copies changed — the one patched into an existing config and the one `init` seeds.
|
|
@@ -11,7 +11,6 @@ This file is about the community module [`firebase.cloudmessaging`](https://gith
|
|
|
11
11
|
<!-- TOC-START -->
|
|
12
12
|
## Contents
|
|
13
13
|
|
|
14
|
-
- [Contents](#contents)
|
|
15
14
|
- [1. The four doors](#1-the-four-doors)
|
|
16
15
|
- [2. The payload decides how much of the module runs](#2-the-payload-decides-how-much-of-the-module-runs)
|
|
17
16
|
- [3. A tap does not fire `didReceiveMessage`](#3-a-tap-does-not-fire-didreceivemessage)
|
|
@@ -34,7 +33,7 @@ This file is about the community module [`firebase.cloudmessaging`](https://gith
|
|
|
34
33
|
| Backgrounded, person taps the notification | `Ti.App` `resumed`, then read the Intent | nothing |
|
|
35
34
|
| **Already in front**, person taps a notification sitting in the tray | `newintent` on the root activity | `launchMode="singleTop"` |
|
|
36
35
|
|
|
37
|
-
A module that fires `didOpenNotification` replaces rows
|
|
36
|
+
A module that fires `didOpenNotification` replaces rows three and four with one listener. Row two still arrives in the Intent, because the module does not exist yet when the notification is opened. See [If your module fires `didOpenNotification`](#if-your-module-fires-didopennotification).
|
|
38
37
|
|
|
39
38
|
Verified case by case on a physical Android phone, reading a log line that named which door each message came through rather than watching the screen.
|
|
40
39
|
|
|
@@ -93,6 +92,12 @@ That last symptom is worth naming because it sends people down the wrong road. I
|
|
|
93
92
|
|
|
94
93
|
Firebase's own server documentation teaches the `notification` block, which is why this is such a common shape to arrive at.
|
|
95
94
|
|
|
95
|
+
### The icon, whichever route posts it
|
|
96
|
+
|
|
97
|
+
Both routes need a drawable named `notificationicon`, because the module resolves it by that exact name: `TiFirebaseMessagingService.showNotification()` calls `getResource("notificationicon")`, and that lookup is the only path a data message has. When it is missing the module falls back to `appicon` — the opaque launcher icon, which the status bar tints into a white square, since only the alpha channel survives. A notification message takes the configurable path instead, the `com.google.firebase.messaging.default_notification_icon` meta-data, which is why the same project can show a correct icon on one route and a blob on the other.
|
|
98
|
+
|
|
99
|
+
PurgeTSS generates the five densities, white on transparent and under the right name, with `purgetss brand --only notification-icon` — off by default. Its `app-branding.md` § "FCM notification icon" carries both meta-data entries and the `colors.xml` resource that `default_notification_color` needs.
|
|
100
|
+
|
|
96
101
|
## 3. A tap does not fire `didReceiveMessage`
|
|
97
102
|
|
|
98
103
|
When the module posts the notification and the person taps it, `PushHandlerActivity` puts the payload on the launcher Intent as the `fcm_data` extra and starts the activity. It does not fire `didReceiveMessage`.
|
|
@@ -134,7 +139,16 @@ Take the activity name from the built manifest rather than guessing it: `build/a
|
|
|
134
139
|
|
|
135
140
|
### If your module fires `didOpenNotification`
|
|
136
141
|
|
|
137
|
-
Everything above is what the published module needs. A module that fires `didOpenNotification` from `setNotificationData()`
|
|
142
|
+
Everything above is what the published module needs. A module that fires `didOpenNotification` from `setNotificationData()` drops the `singleTop`, the `newintent` listener and the Intent read on resume. Two routes remain instead of four, and they are exclusive:
|
|
143
|
+
|
|
144
|
+
| What the app was doing | Where the payload arrives |
|
|
145
|
+
| --- | --- |
|
|
146
|
+
| Open, in front, message arrives | `didReceiveMessage` |
|
|
147
|
+
| Backgrounded, person taps | `didOpenNotification` |
|
|
148
|
+
| Already in front, person taps a notification in the tray | `didOpenNotification` |
|
|
149
|
+
| Killed, person taps | `fcm_data` extra on the launch Intent |
|
|
150
|
+
|
|
151
|
+
Verified on a physical Android 15 phone and an API 35 emulator, each row confirmed by a log line naming the route. The first three ran back to back on one process id with a game in progress, which survived; only the fourth starts a new process.
|
|
138
152
|
|
|
139
153
|
```javascript
|
|
140
154
|
modulo.addEventListener('didOpenNotification', (e) => {
|
|
@@ -148,6 +162,8 @@ The Intent is still how a cold start arrives, so keep that path. Check `timodule
|
|
|
148
162
|
|
|
149
163
|
Do not run both. Where the module also puts the payload on the Intent, `resumed` finds it again on Titanium's second pass and the panel opens twice. That is a real symptom, not a hypothetical: it shows up on Android 10 and not on Android 15, because the two versions differ in whether the launcher Intent gets delivered to a task that already exists.
|
|
150
164
|
|
|
165
|
+
To test the fourth row, swipe the app out of Recents. `adb shell am kill` looks like the same thing and is not: it kills the process but Android keeps the activity's saved state, and on restore Titanium puts `tiLaunchIntentExtras` from the previous launch back over the new Intent, taking `fcm_data` with it. The `TiRootActivity` checkpoint tells you which one you got — `savedInstanceState: null` is a real cold start, anything else is a restore. `adb shell am force-stop` is worse: it leaves the app in the stopped state, where Android stops delivering FCM messages until someone launches it by hand.
|
|
166
|
+
|
|
151
167
|
## 5. Why two splash screens flash by
|
|
152
168
|
|
|
153
169
|
Tap a notification and you may see one or two blank pages slide past before the app appears, and the splash icon never finishes animating. Opening the same app from the launcher icon looks fine.
|
|
@@ -297,6 +313,7 @@ Do not test with the app added to the battery whitelist. That measures a phone y
|
|
|
297
313
|
| --- | --- | --- |
|
|
298
314
|
| Tapping restarts the app from zero and loses state | Top-level `notification` block; the system owns the intent | Section 2 |
|
|
299
315
|
| `image`, `big_text`, `channelId`, `sound`, `vibrate` ignored | Same | Section 2 |
|
|
316
|
+
| A white square or the launcher icon in the status bar | No `notificationicon` drawable; the module fell back to `appicon` | Section 2 |
|
|
300
317
|
| Foreground and cold start work, backgrounded tap does nothing | Nobody reads the Intent on resume | Section 3 |
|
|
301
318
|
| Nothing at all happens when tapping a stored notification while using the app | No `resumed`, no `newintent` listener, or no `singleTop` | Section 4 |
|
|
302
319
|
| Blank pages slide past before the app appears | `PushHandlerActivity` has no theme; Titanium spawns a duplicate root activity | Section 5 |
|