@maccesar/titools 3.3.0 → 4.0.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.
Files changed (101) hide show
  1. package/README.md +27 -61
  2. package/lib/cleanup.js +29 -0
  3. package/lib/commands/agents.js +8 -7
  4. package/lib/commands/skills.js +118 -23
  5. package/lib/config.js +13 -15
  6. package/lib/installer.js +5 -3
  7. package/lib/utils.js +36 -0
  8. package/package.json +1 -1
  9. package/skills/alloy-guides/SKILL.md +188 -0
  10. package/skills/alloy-guides/references/CLI_TASKS.md +233 -0
  11. package/skills/alloy-guides/references/CONCEPTS.md +171 -0
  12. package/skills/alloy-guides/references/CONTROLLERS.md +279 -0
  13. package/skills/alloy-guides/references/MODELS.md +742 -0
  14. package/skills/alloy-guides/references/MODELS_ADVANCED.md +482 -0
  15. package/skills/alloy-guides/references/PURGETSS.md +57 -0
  16. package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +235 -0
  17. package/skills/alloy-guides/references/VIEWS_STYLES.md +375 -0
  18. package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +102 -0
  19. package/skills/alloy-guides/references/VIEWS_XML.md +581 -0
  20. package/skills/alloy-guides/references/WIDGETS.md +160 -0
  21. package/skills/alloy-howtos/SKILL.md +179 -0
  22. package/skills/alloy-howtos/references/best_practices.md +121 -0
  23. package/skills/alloy-howtos/references/cli_reference.md +230 -0
  24. package/skills/alloy-howtos/references/config_files.md +158 -0
  25. package/skills/alloy-howtos/references/custom_tags.md +148 -0
  26. package/skills/alloy-howtos/references/debugging_troubleshooting.md +78 -0
  27. package/skills/alloy-howtos/references/samples.md +156 -0
  28. package/skills/purgetss/SKILL.md +6 -3
  29. package/skills/purgetss/references/EXAMPLES.md +8 -6
  30. package/skills/purgetss/references/app-branding.md +5 -0
  31. package/skills/purgetss/references/apply-directive.md +1 -1
  32. package/skills/purgetss/references/class-categories.md +1 -1
  33. package/skills/purgetss/references/class-index-properties.md +618 -0
  34. package/skills/purgetss/references/class-index.md +4 -610
  35. package/skills/purgetss/references/cli-commands.md +15 -0
  36. package/skills/purgetss/references/custom-fonts.md +39 -36
  37. package/skills/purgetss/references/custom-rules.md +1 -1
  38. package/skills/purgetss/references/customization-deep-dive.md +64 -1
  39. package/skills/purgetss/references/icon-fonts.md +2 -0
  40. package/skills/purgetss/references/migration-guide.md +53 -1
  41. package/skills/purgetss/references/multi-density-images.md +53 -2
  42. package/skills/purgetss/references/svg-pipeline.md +249 -0
  43. package/skills/purgetss/references/tikit-components.md +2 -2
  44. package/skills/purgetss/references/titanium-resets.md +2 -2
  45. package/skills/purgetss/references/ui-ux-design.md +7 -5
  46. package/skills/purgetss/references/version-history.md +12 -0
  47. package/skills/ti-api/SKILL.md +128 -0
  48. package/skills/ti-api/references/api-android.md +675 -0
  49. package/skills/ti-api/references/api-app-platform.md +636 -0
  50. package/skills/ti-api/references/api-core.md +764 -0
  51. package/skills/ti-api/references/api-data-network.md +641 -0
  52. package/skills/ti-api/references/api-media.md +655 -0
  53. package/skills/ti-api/references/api-modules-ble-bluetooth.md +657 -0
  54. package/skills/ti-api/references/api-modules-coremotion-urlsession.md +411 -0
  55. package/skills/ti-api/references/api-modules-map.md +632 -0
  56. package/skills/ti-api/references/api-modules-nfc.md +725 -0
  57. package/skills/ti-api/references/api-modules-social-misc.md +526 -0
  58. package/skills/ti-api/references/api-services.md +700 -0
  59. package/skills/ti-api/references/api-ui-android.md +499 -0
  60. package/skills/ti-api/references/api-ui-extras.md +702 -0
  61. package/skills/ti-api/references/api-ui-ios-animator.md +378 -0
  62. package/skills/ti-api/references/api-ui-ios.md +756 -0
  63. package/skills/ti-api/references/api-ui-lists.md +581 -0
  64. package/skills/ti-api/references/api-ui-text-input.md +607 -0
  65. package/skills/ti-api/references/api-ui-views.md +572 -0
  66. package/skills/ti-api/references/api-ui-windows-navigation.md +676 -0
  67. package/skills/ti-api/references/api-xml-global.md +743 -0
  68. package/skills/ti-guides/SKILL.md +82 -0
  69. package/skills/ti-guides/references/advanced-data-and-images.md +155 -0
  70. package/skills/ti-guides/references/android-manifest.md +97 -0
  71. package/skills/ti-guides/references/app-distribution.md +373 -0
  72. package/skills/ti-guides/references/application-frameworks.md +366 -0
  73. package/skills/ti-guides/references/cli-reference.md +700 -0
  74. package/skills/ti-guides/references/coding-best-practices.md +150 -0
  75. package/skills/ti-guides/references/commonjs-advanced.md +279 -0
  76. package/skills/ti-guides/references/hello-world.md +99 -0
  77. package/skills/ti-guides/references/hyperloop-native-access.md +458 -0
  78. package/skills/ti-guides/references/javascript-primer.md +402 -0
  79. package/skills/ti-guides/references/reserved-words.md +36 -0
  80. package/skills/ti-guides/references/resources.md +172 -0
  81. package/skills/ti-guides/references/style-and-conventions.md +104 -0
  82. package/skills/ti-guides/references/tiapp-config.md +655 -0
  83. package/skills/ti-howtos/SKILL.md +131 -0
  84. package/skills/ti-howtos/references/android-platform-deep-dives.md +609 -0
  85. package/skills/ti-howtos/references/automation-fastlane-appium.md +96 -0
  86. package/skills/ti-howtos/references/buffer-codec-streams.md +162 -0
  87. package/skills/ti-howtos/references/cross-platform-development.md +358 -0
  88. package/skills/ti-howtos/references/debugging-profiling.md +473 -0
  89. package/skills/ti-howtos/references/extending-titanium.md +684 -0
  90. package/skills/ti-howtos/references/google-maps-v2.md +172 -0
  91. package/skills/ti-howtos/references/ios-map-kit.md +149 -0
  92. package/skills/ti-howtos/references/ios-platform-deep-dives.md +595 -0
  93. package/skills/ti-howtos/references/local-data-sources.md +310 -0
  94. package/skills/ti-howtos/references/location-and-maps.md +267 -0
  95. package/skills/ti-howtos/references/media-apis.md +268 -0
  96. package/skills/ti-howtos/references/notification-services.md +539 -0
  97. package/skills/ti-howtos/references/remote-data-sources.md +339 -0
  98. package/skills/ti-howtos/references/tutorials.md +552 -0
  99. package/skills/ti-howtos/references/using-modules.md +182 -0
  100. package/skills/ti-howtos/references/web-content-integration.md +288 -0
  101. package/skills/ti-howtos/references/webpack-build-pipeline.md +125 -0
@@ -0,0 +1,539 @@
1
+ # Push and local notifications
2
+
3
+ Guide to local and push notifications on iOS and Android.
4
+
5
+ ## Local notifications: Android
6
+
7
+ ### Notification display locations
8
+
9
+ - Notification drawer: swipe down from status bar
10
+ - Lock screen: Android 5.0+ (API 21+)
11
+ - Status bar: icon and ticker text
12
+
13
+ ### Create a notification
14
+
15
+ Basic layout:
16
+ ```javascript
17
+ const notification = Ti.Android.createNotification({
18
+ contentTitle: 'Notification Title',
19
+ contentText: 'Notification message',
20
+ contentIntent: Ti.Android.createPendingIntent({
21
+ intent: Ti.Android.createIntent({})
22
+ }),
23
+ icon: Ti.App.Android.R.drawable.warn, // or '/images/warn.png'
24
+ number: 5, // Badge number
25
+ when: new Date(), // Timestamp (does not schedule)
26
+ tickerText: 'Text in status bar when notification first appears'
27
+ });
28
+
29
+ // Send immediately
30
+ Ti.Android.NotificationManager.notify(1, notification);
31
+ ```
32
+
33
+ Icon paths:
34
+ - Density-specific: `/app/assets/android/images/` (Alloy) or `/Resources/android/images/` (Classic), reference as `/images/filename.png`
35
+ - Drawable folder: `/platform/android/res/drawable/filename.png`, reference as `Ti.App.Android.R.drawable.filename`
36
+
37
+ ### Sound
38
+
39
+ ```javascript
40
+ const notification = Ti.Android.createNotification({
41
+ // ... other properties
42
+ sound: `${Ti.Filesystem.getResRawDirectory()}sound.wav`
43
+ });
44
+ ```
45
+
46
+ Play only once (add flag):
47
+ ```javascript
48
+ notification.flags |= Ti.Android.FLAG_ONLY_ALERT_ONCE;
49
+ ```
50
+
51
+ ### Custom layout with RemoteViews
52
+
53
+ 1. Create XML layout (`/platform/android/res/layout/customview.xml`):
54
+ ```xml
55
+ <?xml version="1.0" encoding="utf-8"?>
56
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
57
+ android:layout_width="match_parent"
58
+ android:layout_height="match_parent"
59
+ android:orientation="horizontal">
60
+ <TextView android:id="@+id/message"
61
+ android:layout_width="wrap_content"
62
+ android:layout_height="wrap_content"
63
+ android:text="Default text" />
64
+ <Button android:id="@+id/okbutton"
65
+ android:layout_width="wrap_content"
66
+ android:layout_height="wrap_content"
67
+ android:text="OK" />
68
+ </LinearLayout>
69
+ ```
70
+
71
+ 2. Create RemoteViews and bind intents:
72
+ ```javascript
73
+ const customView = Ti.Android.createRemoteViews({
74
+ layoutId: Ti.App.Android.R.layout.customview
75
+ });
76
+
77
+ // Modify text
78
+ customView.setTextViewText(Ti.App.Android.R.id.message, 'Update available!');
79
+
80
+ // Bind intents to buttons
81
+ const downloadIntent = Ti.Android.createPendingIntent({ intent: Ti.Android.createIntent() });
82
+ customView.setOnClickPendingIntent(Ti.App.Android.R.id.okbutton, downloadIntent);
83
+
84
+ const notification = Ti.Android.createNotification({
85
+ contentView: customView // Avoid setting contentTitle/contentText or they override this
86
+ });
87
+ ```
88
+
89
+ ### Additional properties
90
+
91
+ Defaults (device settings):
92
+ ```javascript
93
+ notification.defaults = Ti.Android.DEFAULT_ALL; // or DEFAULT_SOUND, DEFAULT_VIBRATE, DEFAULT_LIGHTS
94
+ ```
95
+
96
+ Flags (behavior):
97
+ ```javascript
98
+ notification.flags |= Ti.Android.FLAG_AUTO_CANCEL; // Clear on tap
99
+ notification.flags |= Ti.Android.FLAG_INSISTENT; // Repeat sound until canceled
100
+ notification.flags |= Ti.Android.FLAG_NO_CLEAR; // Do not clear with "clear all"
101
+ notification.flags |= Ti.Android.FLAG_ONGOING_EVENT; // Ongoing event (for example, music player)
102
+ notification.flags |= Ti.Android.FLAG_ONLY_ALERT_ONCE; // Do not replay sound/vibrate
103
+ notification.flags |= Ti.Android.FLAG_SHOW_LIGHTS; // Use LED (if device allows)
104
+ ```
105
+
106
+ Priority (Android 4.1+):
107
+ ```javascript
108
+ notification.priority = Ti.Android.PRIORITY_HIGH; // or PRIORITY_MAX, PRIORITY_DEFAULT, PRIORITY_LOW, PRIORITY_MIN
109
+ ```
110
+
111
+ Category (Android 5.0+):
112
+ ```javascript
113
+ notification.category = Ti.Android.CATEGORY_MESSAGE; // or CATEGORY_ALARM, CALL, EMAIL, ERROR, EVENT, etc.
114
+ ```
115
+
116
+ Visibility (Android 5.0+ - lock screen):
117
+ ```javascript
118
+ notification.visibility = Ti.Android.VISIBILITY_PUBLIC; // Show all
119
+ // or VISIBILITY_PRIVATE (hide content), VISIBILITY_SECRET (do not show)
120
+ ```
121
+
122
+ ### Update notification
123
+
124
+ ```javascript
125
+ notification.setLatestEventInfo('New Title', 'New Message', notification.contentIntent);
126
+ ```
127
+
128
+ ### Remove notifications
129
+
130
+ ```javascript
131
+ // Remove specific notification
132
+ Ti.Android.NotificationManager.cancel(1);
133
+
134
+ // Remove all notifications
135
+ Ti.Android.NotificationManager.cancelAll();
136
+ ```
137
+
138
+ ### Respond to notification tap
139
+
140
+ Launch app when tapped:
141
+ ```javascript
142
+ const intent = Ti.Android.createIntent({
143
+ action: Ti.Android.ACTION_MAIN,
144
+ className: 'com.titaniumsdk.testapp.MyappActivity', // ProjectName + Activity
145
+ packageName: 'com.titaniumsdk.testapp'
146
+ });
147
+ intent.flags |= Ti.Android.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Ti.Android.FLAG_ACTIVITY_SINGLE_TOP;
148
+ intent.addCategory(Ti.Android.CATEGORY_LAUNCHER);
149
+
150
+ notification.contentIntent = Ti.Android.createPendingIntent({ intent: intent });
151
+ ```
152
+
153
+ ### Delete intent (notification cleared)
154
+
155
+ ```javascript
156
+ // Execute intent when user clears notification
157
+ notification.deleteIntent = Ti.Android.createPendingIntent({
158
+ intent: Ti.Android.createIntent({
159
+ action: 'com.myapp.NOTIFICATION_CLEARED'
160
+ })
161
+ });
162
+ ```
163
+
164
+ ### Schedule future notification (background service)
165
+
166
+ `tiapp.xml`:
167
+ ```xml
168
+ <android xmlns:android="http://schemas.android.com/apk/res/android">
169
+ <services>
170
+ <service url="ExampleService.js" type="interval" />
171
+ </services>
172
+ </android>
173
+ ```
174
+
175
+ `ExampleService.js`:
176
+ ```javascript
177
+ const service = Ti.Android.currentService;
178
+ const serviceIntent = service.getIntent();
179
+ const timestamp = new Date(serviceIntent.getStringExtra('timestamp'));
180
+
181
+ if (new Date() > timestamp) {
182
+ const title = serviceIntent.getStringExtra('title');
183
+ const message = serviceIntent.getStringExtra('message');
184
+
185
+ const notification = Ti.Android.createNotification({
186
+ contentTitle: title,
187
+ contentText: message,
188
+ contentIntent: Ti.Android.createPendingIntent({ intent: Ti.Android.createIntent() })
189
+ });
190
+
191
+ Ti.Android.NotificationManager.notify(1, notification);
192
+ Ti.Android.stopService(serviceIntent);
193
+ }
194
+ ```
195
+
196
+ Main app:
197
+ ```javascript
198
+ const intent = Ti.Android.createServiceIntent({ url: 'ExampleService.js' });
199
+ intent.putExtra('interval', 5000); // Check every 5 seconds
200
+ intent.putExtra('timestamp', new Date(new Date().getTime() + (30 * 1000))); // Fire in 30 seconds
201
+ intent.putExtra('title', 'Scheduled Notification');
202
+ intent.putExtra('message', 'This was scheduled!');
203
+ Ti.Android.startService(intent);
204
+ ```
205
+
206
+ ## Local notifications: iOS
207
+
208
+ ### Notification display locations
209
+
210
+ - Alert dialog: Open or Close buttons (background, unlocked)
211
+ - Banner message: swipe down for actions, tap to launch (background, unlocked)
212
+ - Lock screen: swipe right to launch
213
+ - Notification Center: queued notifications
214
+ - Badge: number on app icon
215
+ - Sound: audio alert
216
+
217
+ ### Register for notifications (iOS 8+)
218
+
219
+ ```javascript
220
+ if (Ti.Platform.name === 'iPhone OS' && parseInt(Ti.Platform.version.split('.')[0]) >= 8) {
221
+ Ti.App.iOS.registerUserNotificationSettings({
222
+ types: [
223
+ Ti.App.iOS.USER_NOTIFICATION_TYPE_ALERT,
224
+ Ti.App.iOS.USER_NOTIFICATION_TYPE_SOUND,
225
+ Ti.App.iOS.USER_NOTIFICATION_TYPE_BADGE
226
+ ]
227
+ // Add 'categories' for interactive notifications
228
+ });
229
+ }
230
+ ```
231
+
232
+ Note: iOS shows a system prompt the first time. Check current settings with:
233
+ ```javascript
234
+ Ti.App.iOS.addEventListener('usernotificationsettings', (e) => {
235
+ Ti.API.info(`Notification types allowed: ${e.types}`);
236
+ });
237
+ ```
238
+
239
+ ### Schedule local notification
240
+
241
+ ```javascript
242
+ const notification = Ti.App.iOS.scheduleLocalNotification({
243
+ date: new Date(new Date().getTime() + 3000), // 3 seconds from now
244
+ alertBody: 'New content available!',
245
+ alertAction: 'update', // Changes "slide to view" to "slide to update" or "Open" to "Update"
246
+ alertLaunchImage: 'splash.png', // Custom splash image
247
+ badge: 1, // Set badge number (negative to clear)
248
+ sound: '/alert.wav', // Sound file in Resources or app/assets
249
+ category: 'CATEGORY_ID', // For interactive notifications
250
+ repeat: 'daily', // or 'weekly', 'monthly', 'yearly'
251
+ timezone: 'America/Los_Angeles', // Optional (default: system timezone)
252
+ userInfo: { url: 'http://example.com/data.json', id: '123' } // Custom data
253
+ });
254
+ ```
255
+
256
+ ### Monitor notifications
257
+
258
+ While app is in foreground or returns to foreground:
259
+ ```javascript
260
+ Ti.App.iOS.addEventListener('notification', (e) => {
261
+ // Process custom data
262
+ if (e.userInfo && e.userInfo.url) {
263
+ // Handle URL
264
+ }
265
+
266
+ // Reset badge
267
+ if (e.badge > 0) {
268
+ Ti.App.iOS.scheduleLocalNotification({
269
+ date: new Date(),
270
+ badge: -1
271
+ });
272
+ }
273
+ });
274
+ ```
275
+
276
+ ### Cancel notifications
277
+
278
+ ```javascript
279
+ // Cancel all
280
+ Ti.App.iOS.cancelAllLocalNotifications();
281
+
282
+ // Cancel specific notification
283
+ const notification = Ti.App.iOS.scheduleLocalNotification({ ... });
284
+ notification.cancel();
285
+
286
+ // Or by ID
287
+ Ti.App.iOS.scheduleLocalNotification({
288
+ userInfo: { id: 'foo' },
289
+ alertBody: 'Test'
290
+ });
291
+ Ti.App.iOS.cancelLocalNotification('foo');
292
+ ```
293
+
294
+ ## Interactive notifications: iOS (iOS 8+)
295
+
296
+ ### Create notification actions
297
+
298
+ ```javascript
299
+ const acceptAction = Ti.App.iOS.createUserNotificationAction({
300
+ identifier: 'ACCEPT_IDENTIFIER',
301
+ title: 'Accept',
302
+ activationMode: Ti.App.iOS.USER_NOTIFICATION_ACTIVATION_MODE_FOREGROUND, // or BACKGROUND
303
+ destructive: false,
304
+ authenticationRequired: true // Require device unlock
305
+ });
306
+
307
+ const rejectAction = Ti.App.iOS.createUserNotificationAction({
308
+ identifier: 'REJECT_IDENTIFIER',
309
+ title: 'Reject',
310
+ activationMode: Ti.App.iOS.USER_NOTIFICATION_ACTIVATION_MODE_BACKGROUND,
311
+ destructive: true, // Red background in lock screen/notification center
312
+ authenticationRequired: false
313
+ });
314
+ ```
315
+
316
+ ### Create notification category
317
+
318
+ ```javascript
319
+ const category = Ti.App.iOS.createUserNotificationCategory({
320
+ identifier: 'INVITE_CATEGORY',
321
+ actionsForDefaultContext: [acceptAction, rescheduleAction, delayAction, rejectAction], // Alert dialog (4 max)
322
+ actionsForMinimalContext: [acceptAction, rejectAction] // Other styles (2 max)
323
+ });
324
+ ```
325
+
326
+ ### Register categories
327
+
328
+ ```javascript
329
+ Ti.App.iOS.registerUserNotificationSettings({
330
+ types: [
331
+ Ti.App.iOS.USER_NOTIFICATION_TYPE_ALERT,
332
+ Ti.App.iOS.USER_NOTIFICATION_TYPE_BADGE,
333
+ Ti.App.iOS.USER_NOTIFICATION_TYPE_SOUND
334
+ ],
335
+ categories: [category]
336
+ });
337
+ ```
338
+
339
+ ### Monitor interactive notifications
340
+
341
+ Local notifications:
342
+ ```javascript
343
+ Ti.App.iOS.addEventListener('localnotificationaction', (e) => {
344
+ // e.category: category identifier
345
+ // e.identifier: action identifier
346
+ // e.userInfo: custom data
347
+
348
+ if (e.category === 'INVITE_CATEGORY' && e.identifier === 'ACCEPT_IDENTIFIER') {
349
+ // Handle accept
350
+ if (e.userInfo && e.userInfo.url) {
351
+ // Process URL
352
+ }
353
+ }
354
+
355
+ // Reset badge
356
+ if (e.badge > 0) {
357
+ Ti.App.iOS.scheduleLocalNotification({ date: new Date(), badge: -1 });
358
+ }
359
+ });
360
+ ```
361
+
362
+ Push notifications:
363
+ ```javascript
364
+ Ti.App.iOS.addEventListener('remotenotificationaction', (e) => {
365
+ // e.category: category identifier
366
+ // e.identifier: action identifier
367
+ // e.data: push notification payload (JSON)
368
+
369
+ if (e.category === 'DOWNLOAD_CATEGORY' && e.identifier === 'ACCEPT_IDENTIFIER') {
370
+ if (e.data && e.data.url) {
371
+ // Process URL
372
+ }
373
+ }
374
+ });
375
+ ```
376
+
377
+ ### Schedule interactive local notification
378
+
379
+ ```javascript
380
+ Ti.App.iOS.scheduleLocalNotification({
381
+ date: new Date(new Date().getTime() + 3000),
382
+ alertBody: 'New content available! Download now?',
383
+ badge: 1,
384
+ userInfo: { url: 'http://example.com/data.json' },
385
+ category: 'INVITE_CATEGORY' // Must match registered category identifier
386
+ });
387
+ ```
388
+
389
+ ### Send interactive push notification
390
+
391
+ Payload format:
392
+ ```json
393
+ {
394
+ "alert": "New content available! Download now?",
395
+ "badge": 1,
396
+ "url": "http://example.com/data.json",
397
+ "category": "INVITE_CATEGORY"
398
+ }
399
+ ```
400
+
401
+ ## Push notifications
402
+
403
+ ### Overview
404
+
405
+ Push notifications allow server-to-device communication. Infrastructure differs by platform:
406
+
407
+ - Android: Firebase Cloud Messaging (FCM)
408
+ - iOS: Apple Push Notification service (APNs)
409
+
410
+ Note: the legacy Appcelerator Cloud Services (ACS/ArrowDB) push backend is no longer available. Use FCM for Android and APNs for iOS, either directly or via a third-party service.
411
+
412
+ Google has deprecated Google Cloud Messaging (GCM) in favor of FCM. All new implementations should use FCM.
413
+
414
+ For FCM on Titanium, use the community module `titanium-firebase-cloud-messaging` (by Hans Knöchel).
415
+
416
+ ### Register for push notifications
417
+
418
+ ```javascript
419
+ Ti.Network.registerForPushNotifications({
420
+ success: (e) => {
421
+ // e.deviceToken: token to send to your push server
422
+ Ti.API.info(`Device Token: ${e.deviceToken}`);
423
+ // Send this token to your server
424
+ },
425
+ error: (e) => {
426
+ Ti.API.error(`Push registration error: ${e.error}`);
427
+ },
428
+ callback: (e) => {
429
+ // Incoming push notification
430
+ // e.data: push payload (iOS)
431
+ // e.data: JSON object (Android)
432
+ Ti.API.info(`Push received: ${JSON.stringify(e.data)}`);
433
+ }
434
+ });
435
+ ```
436
+
437
+ ### APNs configuration (iOS)
438
+
439
+ 1. Register an explicit App ID (not wildcard) with Push Notifications enabled in Apple Developer Portal.
440
+ 2. Generate an APNs certificate (Development for sandbox, Production for App Store).
441
+ 3. Export the certificate as PKCS#12 (.p12) from Keychain Access.
442
+ 4. Upload the .p12 to your push service provider.
443
+ 5. In your app, call `Ti.Network.registerForPushNotifications()` to get the device token.
444
+
445
+ ### FCM configuration (Android)
446
+
447
+ 1. Create a project in Firebase Console.
448
+ 2. Add your Android app (use your app's package name).
449
+ 3. Download `google-services.json` and place it in the project root.
450
+ 4. Add `titanium-firebase-cloud-messaging` to `tiapp.xml`.
451
+ 5. Register for push and obtain the FCM token.
452
+
453
+ ### Push notification payload format
454
+
455
+ iOS:
456
+ ```json
457
+ {
458
+ "aps": {
459
+ "alert": "Message text",
460
+ "badge": 1,
461
+ "sound": "default"
462
+ },
463
+ "custom_field": "custom_value"
464
+ }
465
+ ```
466
+
467
+ Android:
468
+ ```json
469
+ {
470
+ "message": "Message text",
471
+ "title": "Notification Title",
472
+ "custom_field": "custom_value"
473
+ }
474
+ ```
475
+
476
+ ### Send push notifications
477
+
478
+ Send push notifications from your server using the FCM HTTP API or APNs. Collect device tokens from client registration and target them with your push payload.
479
+
480
+ ### Best practices
481
+
482
+ 1. Permissions: iOS requires user permission (prompted on first call to `registerForPushNotifications`).
483
+ 2. Device tokens: cache and send to your server; they can change on app updates.
484
+ 3. Background handling: use proper activation modes for interactive notifications.
485
+ 4. Badge management: reset the badge after processing a notification.
486
+ 5. Error handling: handle registration failures gracefully.
487
+ 6. Testing: use development certificates/sandbox for iOS testing.
488
+
489
+ ## Common patterns
490
+
491
+ ### Check notification permissions
492
+
493
+ ```javascript
494
+ // Android 4.1+ can disable notifications per app
495
+ // Check via Settings or handle gracefully (notification may not be shown)
496
+ ```
497
+
498
+ ### PendingIntent for notification actions
499
+
500
+ ```javascript
501
+ const intent = Ti.Android.createIntent({
502
+ action: 'com.example.MY_ACTION'
503
+ // Add data/flags
504
+ });
505
+ const pendingIntent = Ti.Android.createPendingIntent({
506
+ intent: intent,
507
+ flags: Ti.Android.FLAG_UPDATE_CURRENT
508
+ });
509
+ ```
510
+
511
+ ### Notification groups (Android 7.0+)
512
+
513
+ ```javascript
514
+ // Requires API 24+; check documentation for latest implementation
515
+ ```
516
+
517
+ ### Rich notifications (iOS 10+)
518
+
519
+ Use `UNMutableNotificationContent` with attachments (images, video, audio).
520
+
521
+ ### Silent push
522
+
523
+ ```javascript
524
+ // iOS: include "content-available": 1 in payload
525
+ // Android: use data messages (FCM)
526
+ ```
527
+
528
+ ## Platform-specific notes
529
+
530
+ ### Android
531
+
532
+ - Service lifecycle: services may be killed if app is swiped away. For critical notifications, use FCM instead of local services.
533
+ - Notification channels: Android 8.0+ requires notification channels.
534
+
535
+ ### iOS
536
+
537
+ - Sandbox vs production: use correct certificates and endpoints.
538
+ - Background app refresh: may be needed for certain push scenarios.
539
+ - LiveView: callbacks only work properly when LiveView is enabled.