@maccesar/titools 2.2.4 → 2.2.8
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/README.md +23 -23
- package/lib/commands/update.js +4 -12
- package/package.json +1 -1
- package/skills/alloy-guides/SKILL.md +31 -31
- package/skills/alloy-guides/references/CONCEPTS.md +3 -3
- package/skills/alloy-guides/references/CONTROLLERS.md +1 -1
- package/skills/alloy-guides/references/MODELS.md +6 -6
- package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +1 -1
- package/skills/alloy-guides/references/VIEWS_XML.md +1 -1
- package/skills/alloy-guides/references/WIDGETS.md +1 -1
- package/skills/alloy-howtos/SKILL.md +27 -27
- package/skills/alloy-howtos/references/best_practices.md +9 -9
- package/skills/alloy-howtos/references/cli_reference.md +14 -14
- package/skills/alloy-howtos/references/config_files.md +16 -16
- package/skills/alloy-howtos/references/custom_tags.md +16 -16
- package/skills/alloy-howtos/references/debugging_troubleshooting.md +11 -15
- package/skills/alloy-howtos/references/samples.md +19 -19
- package/skills/purgetss/SKILL.md +11 -1
- package/skills/purgetss/references/animation-system.md +1 -1
- package/skills/purgetss/references/cli-commands.md +3 -3
- package/skills/purgetss/references/customization-deep-dive.md +1 -1
- package/skills/purgetss/references/dynamic-component-creation.md +1 -1
- package/skills/purgetss/references/icon-fonts.md +4 -0
- package/skills/purgetss/references/installation-setup.md +8 -1
- package/skills/purgetss/references/migration-guide.md +4 -0
- package/skills/purgetss/references/tikit-components.md +193 -204
- package/skills/purgetss/references/ui-ux-design.md +1 -1
- package/skills/ti-expert/SKILL.md +78 -118
- package/skills/ti-expert/references/alloy-builtins.md +18 -18
- package/skills/ti-expert/references/alloy-structure.md +21 -21
- package/skills/ti-expert/references/anti-patterns.md +15 -15
- package/skills/ti-expert/references/cli-expert.md +15 -15
- package/skills/ti-expert/references/code-conventions.md +38 -38
- package/skills/ti-expert/references/contracts.md +8 -8
- package/skills/ti-expert/references/controller-patterns.md +14 -14
- package/skills/ti-expert/references/error-handling.md +11 -11
- package/skills/ti-expert/references/examples.md +12 -12
- package/skills/ti-expert/references/migration-patterns.md +24 -24
- package/skills/ti-expert/references/patterns.md +10 -10
- package/skills/ti-expert/references/performance-listview.md +16 -16
- package/skills/ti-expert/references/performance-optimization.md +41 -41
- package/skills/ti-expert/references/security-device.md +22 -22
- package/skills/ti-expert/references/security-fundamentals.md +19 -19
- package/skills/ti-expert/references/state-management.md +33 -33
- package/skills/ti-expert/references/testing-e2e-ci.md +25 -25
- package/skills/ti-expert/references/testing-unit.md +24 -24
- package/skills/ti-expert/references/theming.md +15 -15
- package/skills/ti-guides/SKILL.md +58 -60
- package/skills/ti-guides/references/advanced-data-and-images.md +33 -33
- package/skills/ti-guides/references/android-manifest.md +15 -15
- package/skills/ti-guides/references/app-distribution.md +70 -166
- package/skills/ti-guides/references/application-frameworks.md +96 -114
- package/skills/ti-guides/references/cli-reference.md +294 -294
- package/skills/ti-guides/references/coding-best-practices.md +42 -33
- package/skills/ti-guides/references/commonjs-advanced.md +57 -51
- package/skills/ti-guides/references/hello-world.md +36 -36
- package/skills/ti-guides/references/hyperloop-native-access.md +66 -66
- package/skills/ti-guides/references/javascript-primer.md +83 -101
- package/skills/ti-guides/references/reserved-words.md +9 -9
- package/skills/ti-guides/references/resources.md +75 -83
- package/skills/ti-guides/references/style-and-conventions.md +35 -28
- package/skills/ti-guides/references/tiapp-config.md +110 -74
- package/skills/ti-howtos/SKILL.md +88 -92
- package/skills/ti-howtos/references/android-platform-deep-dives.md +104 -104
- package/skills/ti-howtos/references/automation-fastlane-appium.md +39 -39
- package/skills/ti-howtos/references/buffer-codec-streams.md +60 -60
- package/skills/ti-howtos/references/cross-platform-development.md +115 -136
- package/skills/ti-howtos/references/debugging-profiling.md +167 -181
- package/skills/ti-howtos/references/extending-titanium.md +121 -121
- package/skills/ti-howtos/references/google-maps-v2.md +84 -82
- package/skills/ti-howtos/references/ios-map-kit.md +65 -60
- package/skills/ti-howtos/references/ios-platform-deep-dives.md +123 -123
- package/skills/ti-howtos/references/local-data-sources.md +79 -78
- package/skills/ti-howtos/references/location-and-maps.md +116 -120
- package/skills/ti-howtos/references/media-apis.md +87 -86
- package/skills/ti-howtos/references/notification-services.md +250 -260
- package/skills/ti-howtos/references/remote-data-sources.md +98 -93
- package/skills/ti-howtos/references/tutorials.md +226 -216
- package/skills/ti-howtos/references/using-modules.md +73 -102
- package/skills/ti-howtos/references/web-content-integration.md +101 -103
- package/skills/ti-howtos/references/webpack-build-pipeline.md +52 -44
- package/skills/ti-ui/SKILL.md +85 -85
- package/skills/ti-ui/references/accessibility-deep-dive.md +57 -57
- package/skills/ti-ui/references/animation-and-matrices.md +79 -79
- package/skills/ti-ui/references/application-structures.md +96 -99
- package/skills/ti-ui/references/custom-fonts-styling.md +99 -99
- package/skills/ti-ui/references/event-handling.md +58 -58
- package/skills/ti-ui/references/gestures.md +62 -64
- package/skills/ti-ui/references/icons-and-splash-screens.md +88 -88
- package/skills/ti-ui/references/layouts-and-positioning.md +87 -97
- package/skills/ti-ui/references/listviews-and-performance.md +107 -107
- package/skills/ti-ui/references/orientation.md +87 -88
- package/skills/ti-ui/references/platform-ui-android.md +87 -81
- package/skills/ti-ui/references/platform-ui-ios.md +63 -61
- package/skills/ti-ui/references/scrolling-views.md +29 -29
- package/skills/ti-ui/references/tableviews.md +56 -56
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
# Push and
|
|
1
|
+
# Push and local notifications
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Guide to local and push notifications on iOS and Android.
|
|
4
4
|
|
|
5
|
-
## Local
|
|
5
|
+
## Local notifications: Android
|
|
6
6
|
|
|
7
|
-
### Notification
|
|
7
|
+
### Notification display locations
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
9
|
+
- Notification drawer: swipe down from status bar
|
|
10
|
+
- Lock screen: Android 5.0+ (API 21+)
|
|
11
|
+
- Status bar: icon and ticker text
|
|
12
12
|
|
|
13
|
-
### Create a
|
|
13
|
+
### Create a notification
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Basic layout:
|
|
16
16
|
```javascript
|
|
17
17
|
const notification = Ti.Android.createNotification({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
27
|
});
|
|
28
28
|
|
|
29
29
|
// Send immediately
|
|
30
30
|
Ti.Android.NotificationManager.notify(1, notification);
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
Icon paths:
|
|
34
34
|
- Density-specific: `/app/assets/android/images/` (Alloy) or `/Resources/android/images/` (Classic), reference as `/images/filename.png`
|
|
35
35
|
- Drawable folder: `/platform/android/res/drawable/filename.png`, reference as `Ti.App.Android.R.drawable.filename`
|
|
36
36
|
|
|
@@ -38,8 +38,8 @@ Ti.Android.NotificationManager.notify(1, notification);
|
|
|
38
38
|
|
|
39
39
|
```javascript
|
|
40
40
|
const notification = Ti.Android.createNotification({
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
// ... other properties
|
|
42
|
+
sound: `${Ti.Filesystem.getResRawDirectory()}sound.wav`
|
|
43
43
|
});
|
|
44
44
|
```
|
|
45
45
|
|
|
@@ -48,9 +48,9 @@ Play only once (add flag):
|
|
|
48
48
|
notification.flags |= Ti.Android.FLAG_ONLY_ALERT_ONCE;
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
### Custom
|
|
51
|
+
### Custom layout with RemoteViews
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
1. Create XML layout (`/platform/android/res/layout/customview.xml`):
|
|
54
54
|
```xml
|
|
55
55
|
<?xml version="1.0" encoding="utf-8"?>
|
|
56
56
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
@@ -68,64 +68,64 @@ notification.flags |= Ti.Android.FLAG_ONLY_ALERT_ONCE;
|
|
|
68
68
|
</LinearLayout>
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
2. Create RemoteViews and bind intents:
|
|
72
72
|
```javascript
|
|
73
73
|
const customView = Ti.Android.createRemoteViews({
|
|
74
|
-
|
|
74
|
+
layoutId: Ti.App.Android.R.layout.customview
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
// Modify text
|
|
78
|
-
customView.setTextViewText(Ti.App.Android.R.id.message,
|
|
78
|
+
customView.setTextViewText(Ti.App.Android.R.id.message, 'Update available!');
|
|
79
79
|
|
|
80
80
|
// Bind intents to buttons
|
|
81
|
-
const downloadIntent = Ti.Android.createPendingIntent({intent: Ti.Android.createIntent()});
|
|
81
|
+
const downloadIntent = Ti.Android.createPendingIntent({ intent: Ti.Android.createIntent() });
|
|
82
82
|
customView.setOnClickPendingIntent(Ti.App.Android.R.id.okbutton, downloadIntent);
|
|
83
83
|
|
|
84
84
|
const notification = Ti.Android.createNotification({
|
|
85
|
-
|
|
85
|
+
contentView: customView // Avoid setting contentTitle/contentText or they override this
|
|
86
86
|
});
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
### Additional
|
|
89
|
+
### Additional properties
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
Defaults (device settings):
|
|
92
92
|
```javascript
|
|
93
|
-
notification.defaults = Ti.Android.DEFAULT_ALL;
|
|
93
|
+
notification.defaults = Ti.Android.DEFAULT_ALL; // or DEFAULT_SOUND, DEFAULT_VIBRATE, DEFAULT_LIGHTS
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
Flags (behavior):
|
|
97
97
|
```javascript
|
|
98
|
-
notification.flags |= Ti.Android.FLAG_AUTO_CANCEL;
|
|
99
|
-
notification.flags |= Ti.Android.FLAG_INSISTENT;
|
|
100
|
-
notification.flags |= Ti.Android.FLAG_NO_CLEAR;
|
|
101
|
-
notification.flags |= Ti.Android.FLAG_ONGOING_EVENT;
|
|
102
|
-
notification.flags |= Ti.Android.FLAG_ONLY_ALERT_ONCE; //
|
|
103
|
-
notification.flags |= Ti.Android.FLAG_SHOW_LIGHTS;
|
|
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
104
|
```
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
Priority (Android 4.1+):
|
|
107
107
|
```javascript
|
|
108
|
-
notification.priority = Ti.Android.PRIORITY_HIGH;
|
|
108
|
+
notification.priority = Ti.Android.PRIORITY_HIGH; // or PRIORITY_MAX, PRIORITY_DEFAULT, PRIORITY_LOW, PRIORITY_MIN
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
Category (Android 5.0+):
|
|
112
112
|
```javascript
|
|
113
|
-
notification.category = Ti.Android.CATEGORY_MESSAGE;
|
|
113
|
+
notification.category = Ti.Android.CATEGORY_MESSAGE; // or CATEGORY_ALARM, CALL, EMAIL, ERROR, EVENT, etc.
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
Visibility (Android 5.0+ - lock screen):
|
|
117
117
|
```javascript
|
|
118
|
-
notification.visibility = Ti.Android.VISIBILITY_PUBLIC;
|
|
119
|
-
// or VISIBILITY_PRIVATE (hide content), VISIBILITY_SECRET (
|
|
118
|
+
notification.visibility = Ti.Android.VISIBILITY_PUBLIC; // Show all
|
|
119
|
+
// or VISIBILITY_PRIVATE (hide content), VISIBILITY_SECRET (do not show)
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
### Update
|
|
122
|
+
### Update notification
|
|
123
123
|
|
|
124
124
|
```javascript
|
|
125
125
|
notification.setLatestEventInfo('New Title', 'New Message', notification.contentIntent);
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
### Remove
|
|
128
|
+
### Remove notifications
|
|
129
129
|
|
|
130
130
|
```javascript
|
|
131
131
|
// Remove specific notification
|
|
@@ -135,35 +135,35 @@ Ti.Android.NotificationManager.cancel(1);
|
|
|
135
135
|
Ti.Android.NotificationManager.cancelAll();
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
### Respond to
|
|
138
|
+
### Respond to notification tap
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
Launch app when tapped:
|
|
141
141
|
```javascript
|
|
142
142
|
const intent = Ti.Android.createIntent({
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
action: Ti.Android.ACTION_MAIN,
|
|
144
|
+
className: 'com.titaniumsdk.testapp.MyappActivity', // ProjectName + Activity
|
|
145
|
+
packageName: 'com.titaniumsdk.testapp'
|
|
146
146
|
});
|
|
147
147
|
intent.flags |= Ti.Android.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Ti.Android.FLAG_ACTIVITY_SINGLE_TOP;
|
|
148
148
|
intent.addCategory(Ti.Android.CATEGORY_LAUNCHER);
|
|
149
149
|
|
|
150
|
-
notification.contentIntent = Ti.Android.createPendingIntent({intent: intent});
|
|
150
|
+
notification.contentIntent = Ti.Android.createPendingIntent({ intent: intent });
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
-
### Delete
|
|
153
|
+
### Delete intent (notification cleared)
|
|
154
154
|
|
|
155
155
|
```javascript
|
|
156
156
|
// Execute intent when user clears notification
|
|
157
157
|
notification.deleteIntent = Ti.Android.createPendingIntent({
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
intent: Ti.Android.createIntent({
|
|
159
|
+
action: 'com.myapp.NOTIFICATION_CLEARED'
|
|
160
|
+
})
|
|
161
161
|
});
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
-
### Schedule
|
|
164
|
+
### Schedule future notification (background service)
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
`tiapp.xml`:
|
|
167
167
|
```xml
|
|
168
168
|
<android xmlns:android="http://schemas.android.com/apk/res/android">
|
|
169
169
|
<services>
|
|
@@ -172,379 +172,369 @@ notification.deleteIntent = Ti.Android.createPendingIntent({
|
|
|
172
172
|
</android>
|
|
173
173
|
```
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
`ExampleService.js`:
|
|
176
176
|
```javascript
|
|
177
177
|
const service = Ti.Android.currentService;
|
|
178
178
|
const serviceIntent = service.getIntent();
|
|
179
179
|
const timestamp = new Date(serviceIntent.getStringExtra('timestamp'));
|
|
180
180
|
|
|
181
181
|
if (new Date() > timestamp) {
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
const title = serviceIntent.getStringExtra('title');
|
|
183
|
+
const message = serviceIntent.getStringExtra('message');
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
185
|
+
const notification = Ti.Android.createNotification({
|
|
186
|
+
contentTitle: title,
|
|
187
|
+
contentText: message,
|
|
188
|
+
contentIntent: Ti.Android.createPendingIntent({ intent: Ti.Android.createIntent() })
|
|
189
|
+
});
|
|
190
190
|
|
|
191
|
-
|
|
192
|
-
|
|
191
|
+
Ti.Android.NotificationManager.notify(1, notification);
|
|
192
|
+
Ti.Android.stopService(serviceIntent);
|
|
193
193
|
}
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
-
|
|
196
|
+
Main app:
|
|
197
197
|
```javascript
|
|
198
|
-
const intent = Ti.Android.createServiceIntent({url: 'ExampleService.js'});
|
|
199
|
-
intent.putExtra('interval', 5000);
|
|
200
|
-
intent.putExtra('timestamp', new Date(new Date().getTime() + (30 * 1000)));
|
|
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
201
|
intent.putExtra('title', 'Scheduled Notification');
|
|
202
202
|
intent.putExtra('message', 'This was scheduled!');
|
|
203
203
|
Ti.Android.startService(intent);
|
|
204
204
|
```
|
|
205
205
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
## Local Notifications iOS
|
|
206
|
+
## Local notifications: iOS
|
|
209
207
|
|
|
210
|
-
### Notification
|
|
208
|
+
### Notification display locations
|
|
211
209
|
|
|
212
|
-
-
|
|
213
|
-
-
|
|
214
|
-
-
|
|
215
|
-
-
|
|
216
|
-
-
|
|
217
|
-
-
|
|
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
|
|
218
216
|
|
|
219
|
-
### Register for
|
|
217
|
+
### Register for notifications (iOS 8+)
|
|
220
218
|
|
|
221
219
|
```javascript
|
|
222
220
|
if (Ti.Platform.name === 'iPhone OS' && parseInt(Ti.Platform.version.split('.')[0]) >= 8) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
+
});
|
|
231
229
|
}
|
|
232
230
|
```
|
|
233
231
|
|
|
234
|
-
|
|
232
|
+
Note: iOS shows a system prompt the first time. Check current settings with:
|
|
235
233
|
```javascript
|
|
236
234
|
Ti.App.iOS.addEventListener('usernotificationsettings', (e) => {
|
|
237
|
-
|
|
235
|
+
Ti.API.info(`Notification types allowed: ${e.types}`);
|
|
238
236
|
});
|
|
239
237
|
```
|
|
240
238
|
|
|
241
|
-
### Schedule
|
|
239
|
+
### Schedule local notification
|
|
242
240
|
|
|
243
241
|
```javascript
|
|
244
242
|
const notification = Ti.App.iOS.scheduleLocalNotification({
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
|
255
253
|
});
|
|
256
254
|
```
|
|
257
255
|
|
|
258
|
-
### Monitor
|
|
256
|
+
### Monitor notifications
|
|
259
257
|
|
|
260
|
-
|
|
258
|
+
While app is in foreground or returns to foreground:
|
|
261
259
|
```javascript
|
|
262
260
|
Ti.App.iOS.addEventListener('notification', (e) => {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
+
}
|
|
275
273
|
});
|
|
276
274
|
```
|
|
277
275
|
|
|
278
|
-
### Cancel
|
|
276
|
+
### Cancel notifications
|
|
279
277
|
|
|
280
278
|
```javascript
|
|
281
279
|
// Cancel all
|
|
282
280
|
Ti.App.iOS.cancelAllLocalNotifications();
|
|
283
281
|
|
|
284
282
|
// Cancel specific notification
|
|
285
|
-
const notification = Ti.App.iOS.scheduleLocalNotification({...});
|
|
283
|
+
const notification = Ti.App.iOS.scheduleLocalNotification({ ... });
|
|
286
284
|
notification.cancel();
|
|
287
285
|
|
|
288
286
|
// Or by ID
|
|
289
287
|
Ti.App.iOS.scheduleLocalNotification({
|
|
290
|
-
|
|
291
|
-
|
|
288
|
+
userInfo: { id: 'foo' },
|
|
289
|
+
alertBody: 'Test'
|
|
292
290
|
});
|
|
293
291
|
Ti.App.iOS.cancelLocalNotification('foo');
|
|
294
292
|
```
|
|
295
293
|
|
|
296
|
-
|
|
294
|
+
## Interactive notifications: iOS (iOS 8+)
|
|
297
295
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
### Create Notification Actions
|
|
296
|
+
### Create notification actions
|
|
301
297
|
|
|
302
298
|
```javascript
|
|
303
299
|
const acceptAction = Ti.App.iOS.createUserNotificationAction({
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
|
309
305
|
});
|
|
310
306
|
|
|
311
307
|
const rejectAction = Ti.App.iOS.createUserNotificationAction({
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
|
317
313
|
});
|
|
318
314
|
```
|
|
319
315
|
|
|
320
|
-
### Create
|
|
316
|
+
### Create notification category
|
|
321
317
|
|
|
322
318
|
```javascript
|
|
323
319
|
const category = Ti.App.iOS.createUserNotificationCategory({
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
320
|
+
identifier: 'INVITE_CATEGORY',
|
|
321
|
+
actionsForDefaultContext: [acceptAction, rescheduleAction, delayAction, rejectAction], // Alert dialog (4 max)
|
|
322
|
+
actionsForMinimalContext: [acceptAction, rejectAction] // Other styles (2 max)
|
|
327
323
|
});
|
|
328
324
|
```
|
|
329
325
|
|
|
330
|
-
### Register
|
|
326
|
+
### Register categories
|
|
331
327
|
|
|
332
328
|
```javascript
|
|
333
329
|
Ti.App.iOS.registerUserNotificationSettings({
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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]
|
|
340
336
|
});
|
|
341
337
|
```
|
|
342
338
|
|
|
343
|
-
### Monitor
|
|
339
|
+
### Monitor interactive notifications
|
|
344
340
|
|
|
345
|
-
|
|
341
|
+
Local notifications:
|
|
346
342
|
```javascript
|
|
347
343
|
Ti.App.iOS.addEventListener('localnotificationaction', (e) => {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (e.category === 'INVITE_CATEGORY' && e.identifier === 'ACCEPT_IDENTIFIER') {
|
|
353
|
-
// Handle accept
|
|
354
|
-
if (e.userInfo && e.userInfo.url) {
|
|
355
|
-
// Process URL
|
|
356
|
-
}
|
|
357
|
-
}
|
|
344
|
+
// e.category: category identifier
|
|
345
|
+
// e.identifier: action identifier
|
|
346
|
+
// e.userInfo: custom data
|
|
358
347
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
348
|
+
if (e.category === 'INVITE_CATEGORY' && e.identifier === 'ACCEPT_IDENTIFIER') {
|
|
349
|
+
// Handle accept
|
|
350
|
+
if (e.userInfo && e.userInfo.url) {
|
|
351
|
+
// Process URL
|
|
362
352
|
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Reset badge
|
|
356
|
+
if (e.badge > 0) {
|
|
357
|
+
Ti.App.iOS.scheduleLocalNotification({ date: new Date(), badge: -1 });
|
|
358
|
+
}
|
|
363
359
|
});
|
|
364
360
|
```
|
|
365
361
|
|
|
366
|
-
|
|
362
|
+
Push notifications:
|
|
367
363
|
```javascript
|
|
368
364
|
Ti.App.iOS.addEventListener('remotenotificationaction', (e) => {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
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
|
|
377
372
|
}
|
|
373
|
+
}
|
|
378
374
|
});
|
|
379
375
|
```
|
|
380
376
|
|
|
381
|
-
### Schedule
|
|
377
|
+
### Schedule interactive local notification
|
|
382
378
|
|
|
383
379
|
```javascript
|
|
384
380
|
Ti.App.iOS.scheduleLocalNotification({
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
|
390
386
|
});
|
|
391
387
|
```
|
|
392
388
|
|
|
393
|
-
### Send
|
|
389
|
+
### Send interactive push notification
|
|
394
390
|
|
|
395
|
-
|
|
391
|
+
Payload format:
|
|
396
392
|
```json
|
|
397
393
|
{
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
394
|
+
"alert": "New content available! Download now?",
|
|
395
|
+
"badge": 1,
|
|
396
|
+
"url": "http://example.com/data.json",
|
|
397
|
+
"category": "INVITE_CATEGORY"
|
|
402
398
|
}
|
|
403
399
|
```
|
|
404
400
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
## Push Notifications
|
|
401
|
+
## Push notifications
|
|
408
402
|
|
|
409
403
|
### Overview
|
|
410
404
|
|
|
411
405
|
Push notifications allow server-to-device communication. Infrastructure differs by platform:
|
|
412
406
|
|
|
413
|
-
-
|
|
414
|
-
-
|
|
407
|
+
- Android: Firebase Cloud Messaging (FCM)
|
|
408
|
+
- iOS: Apple Push Notification service (APNs)
|
|
415
409
|
|
|
416
|
-
|
|
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.
|
|
417
411
|
|
|
418
|
-
|
|
412
|
+
Google has deprecated Google Cloud Messaging (GCM) in favor of FCM. All new implementations should use FCM.
|
|
419
413
|
|
|
420
|
-
For FCM on Titanium, use the community
|
|
414
|
+
For FCM on Titanium, use the community module `titanium-firebase-cloud-messaging` (by Hans Knöchel).
|
|
421
415
|
|
|
422
|
-
### Register for
|
|
416
|
+
### Register for push notifications
|
|
423
417
|
|
|
424
418
|
```javascript
|
|
425
419
|
Ti.Network.registerForPushNotifications({
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
+
}
|
|
440
434
|
});
|
|
441
435
|
```
|
|
442
436
|
|
|
443
|
-
### APNs
|
|
437
|
+
### APNs configuration (iOS)
|
|
444
438
|
|
|
445
|
-
1. Register an
|
|
446
|
-
2. Generate an APNs certificate (Development for sandbox, Production for App Store)
|
|
447
|
-
3. Export the certificate as PKCS#12 (.p12) from Keychain Access
|
|
448
|
-
4. Upload the .p12 to your push service provider
|
|
449
|
-
5. In your app, call `Ti.Network.registerForPushNotifications()` to get the device token
|
|
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.
|
|
450
444
|
|
|
451
|
-
### FCM
|
|
445
|
+
### FCM configuration (Android)
|
|
452
446
|
|
|
453
|
-
1. Create a project in Firebase Console
|
|
454
|
-
2. Add your Android app (use your app's package name)
|
|
455
|
-
3. Download `google-services.json` and place in project root
|
|
456
|
-
4. Add
|
|
457
|
-
5. Register for push and obtain the FCM token
|
|
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.
|
|
458
452
|
|
|
459
|
-
### Push
|
|
453
|
+
### Push notification payload format
|
|
460
454
|
|
|
461
|
-
|
|
455
|
+
iOS:
|
|
462
456
|
```json
|
|
463
457
|
{
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
458
|
+
"aps": {
|
|
459
|
+
"alert": "Message text",
|
|
460
|
+
"badge": 1,
|
|
461
|
+
"sound": "default"
|
|
462
|
+
},
|
|
463
|
+
"custom_field": "custom_value"
|
|
470
464
|
}
|
|
471
465
|
```
|
|
472
466
|
|
|
473
|
-
|
|
467
|
+
Android:
|
|
474
468
|
```json
|
|
475
469
|
{
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
470
|
+
"message": "Message text",
|
|
471
|
+
"title": "Notification Title",
|
|
472
|
+
"custom_field": "custom_value"
|
|
479
473
|
}
|
|
480
474
|
```
|
|
481
475
|
|
|
482
|
-
### Send
|
|
476
|
+
### Send push notifications
|
|
483
477
|
|
|
484
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.
|
|
485
479
|
|
|
486
|
-
### Best
|
|
480
|
+
### Best practices
|
|
487
481
|
|
|
488
|
-
1.
|
|
489
|
-
2.
|
|
490
|
-
3.
|
|
491
|
-
4.
|
|
492
|
-
5.
|
|
493
|
-
6.
|
|
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.
|
|
494
488
|
|
|
495
|
-
|
|
489
|
+
## Common patterns
|
|
496
490
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
### Check Notification Permissions
|
|
491
|
+
### Check notification permissions
|
|
500
492
|
|
|
501
493
|
```javascript
|
|
502
494
|
// Android 4.1+ can disable notifications per app
|
|
503
495
|
// Check via Settings or handle gracefully (notification may not be shown)
|
|
504
496
|
```
|
|
505
497
|
|
|
506
|
-
### PendingIntent for
|
|
498
|
+
### PendingIntent for notification actions
|
|
507
499
|
|
|
508
500
|
```javascript
|
|
509
501
|
const intent = Ti.Android.createIntent({
|
|
510
|
-
|
|
511
|
-
|
|
502
|
+
action: 'com.example.MY_ACTION'
|
|
503
|
+
// Add data/flags
|
|
512
504
|
});
|
|
513
505
|
const pendingIntent = Ti.Android.createPendingIntent({
|
|
514
|
-
|
|
515
|
-
|
|
506
|
+
intent: intent,
|
|
507
|
+
flags: Ti.Android.FLAG_UPDATE_CURRENT
|
|
516
508
|
});
|
|
517
509
|
```
|
|
518
510
|
|
|
519
|
-
### Notification
|
|
511
|
+
### Notification groups (Android 7.0+)
|
|
520
512
|
|
|
521
513
|
```javascript
|
|
522
514
|
// Requires API 24+; check documentation for latest implementation
|
|
523
515
|
```
|
|
524
516
|
|
|
525
|
-
### Rich
|
|
517
|
+
### Rich notifications (iOS 10+)
|
|
526
518
|
|
|
527
519
|
Use `UNMutableNotificationContent` with attachments (images, video, audio).
|
|
528
520
|
|
|
529
|
-
### Silent
|
|
521
|
+
### Silent push
|
|
530
522
|
|
|
531
523
|
```javascript
|
|
532
524
|
// iOS: include "content-available": 1 in payload
|
|
533
525
|
// Android: use data messages (FCM)
|
|
534
526
|
```
|
|
535
527
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
## Platform-Specific Notes
|
|
528
|
+
## Platform-specific notes
|
|
539
529
|
|
|
540
530
|
### Android
|
|
541
531
|
|
|
542
|
-
-
|
|
543
|
-
-
|
|
544
|
-
-
|
|
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
|
+
- Limitations: legacy MapView only supports a single map view.
|
|
545
535
|
|
|
546
536
|
### iOS
|
|
547
537
|
|
|
548
|
-
-
|
|
549
|
-
-
|
|
550
|
-
-
|
|
538
|
+
- Sandbox vs production: use correct certificates and endpoints.
|
|
539
|
+
- Background app refresh: may be needed for certain push scenarios.
|
|
540
|
+
- LiveView: callbacks only work properly when LiveView is enabled.
|