@maccesar/titools 2.2.11 → 2.3.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/README.md +255 -713
- package/bin/titools.js +1 -1
- package/lib/commands/update.js +5 -6
- package/lib/config.js +1 -0
- package/lib/downloader.js +10 -0
- package/package.json +1 -1
- package/skills/alloy-guides/SKILL.md +46 -48
- package/skills/alloy-guides/references/CONTROLLERS.md +4 -6
- package/skills/alloy-guides/references/MODELS.md +340 -184
- package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +3 -4
- package/skills/alloy-guides/references/VIEWS_STYLES.md +44 -46
- package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +4 -5
- package/skills/alloy-guides/references/VIEWS_XML.md +51 -17
- package/skills/alloy-guides/references/WIDGETS.md +1 -1
- package/skills/alloy-howtos/SKILL.md +12 -13
- package/skills/alloy-howtos/references/cli_reference.md +40 -41
- package/skills/alloy-howtos/references/samples.md +3 -4
- package/skills/purgetss/SKILL.md +369 -356
- package/skills/purgetss/references/EXAMPLES.md +24 -25
- package/skills/purgetss/references/animation-advanced.md +555 -0
- package/skills/purgetss/references/animation-system.md +395 -995
- package/skills/purgetss/references/apply-directive.md +111 -141
- package/skills/purgetss/references/arbitrary-values.md +206 -480
- package/skills/purgetss/references/class-categories.md +277 -0
- package/skills/purgetss/references/class-index.md +1 -420
- package/skills/purgetss/references/cli-commands.md +446 -556
- package/skills/purgetss/references/configurable-properties.md +163 -599
- package/skills/purgetss/references/custom-rules.md +33 -76
- package/skills/purgetss/references/customization-deep-dive.md +319 -518
- package/skills/purgetss/references/dynamic-component-creation.md +33 -37
- package/skills/purgetss/references/grid-layout.md +42 -371
- package/skills/purgetss/references/icon-fonts.md +82 -475
- package/skills/purgetss/references/installation-setup.md +159 -331
- package/skills/purgetss/references/migration-guide.md +54 -109
- package/skills/purgetss/references/opacity-modifier.md +25 -222
- package/skills/purgetss/references/performance-tips.md +2 -2
- package/skills/purgetss/references/platform-modifiers.md +21 -407
- package/skills/purgetss/references/tikit-components.md +237 -104
- package/skills/purgetss/references/titanium-resets.md +20 -21
- package/skills/purgetss/references/ui-ux-design.md +171 -1702
- package/skills/ti-api/SKILL.md +109 -0
- package/skills/ti-api/references/api-android.md +675 -0
- package/skills/ti-api/references/api-app-platform.md +636 -0
- package/skills/ti-api/references/api-core.md +764 -0
- package/skills/ti-api/references/api-data-network.md +641 -0
- package/skills/ti-api/references/api-media.md +655 -0
- package/skills/ti-api/references/api-modules-ble-bluetooth.md +657 -0
- package/skills/ti-api/references/api-modules-coremotion-urlsession.md +411 -0
- package/skills/ti-api/references/api-modules-map.md +632 -0
- package/skills/ti-api/references/api-modules-nfc.md +725 -0
- package/skills/ti-api/references/api-modules-social-misc.md +526 -0
- package/skills/ti-api/references/api-services.md +700 -0
- package/skills/ti-api/references/api-ui-android.md +499 -0
- package/skills/ti-api/references/api-ui-extras.md +702 -0
- package/skills/ti-api/references/api-ui-ios-animator.md +378 -0
- package/skills/ti-api/references/api-ui-ios.md +756 -0
- package/skills/ti-api/references/api-ui-lists.md +581 -0
- package/skills/ti-api/references/api-ui-text-input.md +607 -0
- package/skills/ti-api/references/api-ui-views.md +572 -0
- package/skills/ti-api/references/api-ui-windows-navigation.md +676 -0
- package/skills/ti-api/references/api-xml-global.md +743 -0
- package/skills/ti-expert/SKILL.md +106 -138
- package/skills/ti-expert/references/adaptive-layouts.md +414 -0
- package/skills/ti-expert/references/alloy-builtins.md +16 -20
- package/skills/ti-expert/references/alloy-structure.md +40 -42
- package/skills/ti-expert/references/anti-patterns.md +34 -0
- package/skills/ti-expert/references/architecture-tiers.md +248 -0
- package/skills/ti-expert/references/code-conventions.md +5 -3
- package/skills/ti-expert/references/error-handling.md +14 -7
- package/skills/ti-expert/references/examples.md +4 -2
- package/skills/ti-expert/references/performance-optimization.md +26 -24
- package/skills/ti-expert/references/security-device.md +17 -14
- package/skills/ti-expert/references/security-fundamentals.md +60 -101
- package/skills/ti-expert/references/state-management.md +15 -14
- package/skills/ti-expert/references/testing-e2e-ci.md +30 -21
- package/skills/ti-expert/references/theming.md +12 -20
- package/skills/ti-guides/SKILL.md +13 -17
- package/skills/ti-guides/references/advanced-data-and-images.md +30 -10
- package/skills/ti-guides/references/application-frameworks.md +3 -3
- package/skills/ti-guides/references/coding-best-practices.md +31 -2
- package/skills/ti-guides/references/commonjs-advanced.md +46 -4
- package/skills/ti-guides/references/hello-world.md +9 -13
- package/skills/ti-guides/references/hyperloop-native-access.md +4 -1
- package/skills/ti-guides/references/javascript-primer.md +13 -5
- package/skills/ti-guides/references/resources.md +0 -2
- package/skills/ti-guides/references/style-and-conventions.md +1 -0
- package/skills/ti-guides/references/tiapp-config.md +0 -32
- package/skills/ti-howtos/SKILL.md +43 -45
- package/skills/ti-howtos/references/buffer-codec-streams.md +25 -3
- package/skills/ti-howtos/references/debugging-profiling.md +14 -25
- package/skills/ti-howtos/references/google-maps-v2.md +3 -3
- package/skills/ti-howtos/references/ios-map-kit.md +8 -1
- package/skills/ti-howtos/references/notification-services.md +0 -1
- package/skills/ti-howtos/references/webpack-build-pipeline.md +3 -0
- package/skills/ti-ui/SKILL.md +47 -49
- package/skills/ti-ui/references/application-structures.md +3 -2
- package/skills/ti-ui/references/orientation.md +17 -9
- package/skills/ti-ui/references/platform-ui-ios.md +63 -0
- package/skills/ti-ui/references/scrolling-views.md +39 -0
|
@@ -4,107 +4,90 @@
|
|
|
4
4
|
|
|
5
5
|
**NEVER store tokens in:** `Ti.App.Properties` (plaintext), localStorage, or files.
|
|
6
6
|
|
|
7
|
-
**USE
|
|
7
|
+
**USE the `ti.identity` module** — it handles iOS Keychain and Android Keystore through a unified API. Both platforms use `Identity.createKeychainItem()`.
|
|
8
8
|
|
|
9
9
|
```javascript
|
|
10
10
|
// lib/services/tokenStorage.js
|
|
11
|
+
const Identity = require('ti.identity')
|
|
12
|
+
|
|
13
|
+
// Create a keychainItem once per identifier
|
|
14
|
+
function createItem(identifier) {
|
|
15
|
+
return Identity.createKeychainItem({ identifier })
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
exports.TokenStorage = {
|
|
12
19
|
save(token) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
})
|
|
18
|
-
keyStore.addEntry('authToken', token)
|
|
19
|
-
} else {
|
|
20
|
-
// Use iOS Keychain
|
|
21
|
-
Ti.KeychainItem.setItem({
|
|
22
|
-
identifier: 'authToken',
|
|
23
|
-
value: token,
|
|
24
|
-
accessGroup: 'com.yourapp.keychain'
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const item = createItem('authToken')
|
|
22
|
+
item.addEventListener('save', (e) => {
|
|
23
|
+
e.success ? resolve() : reject(new Error(e.error))
|
|
25
24
|
})
|
|
26
|
-
|
|
25
|
+
item.save(token)
|
|
26
|
+
})
|
|
27
27
|
},
|
|
28
28
|
|
|
29
29
|
get() {
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const item = createItem('authToken')
|
|
32
|
+
item.addEventListener('read', (e) => {
|
|
33
|
+
e.success ? resolve(e.value) : reject(new Error(e.error))
|
|
33
34
|
})
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
return Ti.KeychainItem.getItem({
|
|
37
|
-
identifier: 'authToken',
|
|
38
|
-
accessGroup: 'com.yourapp.keychain'
|
|
39
|
-
})
|
|
40
|
-
}
|
|
35
|
+
item.read()
|
|
36
|
+
})
|
|
41
37
|
},
|
|
42
38
|
|
|
43
39
|
clear() {
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
keyStore.removeEntry('authToken')
|
|
49
|
-
} else {
|
|
50
|
-
Ti.KeychainItem.removeItem({
|
|
51
|
-
identifier: 'authToken',
|
|
52
|
-
accessGroup: 'com.yourapp.keychain'
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const item = createItem('authToken')
|
|
42
|
+
item.addEventListener('reset', (e) => {
|
|
43
|
+
e.success ? resolve() : reject(new Error(e.error))
|
|
53
44
|
})
|
|
54
|
-
|
|
45
|
+
item.reset()
|
|
46
|
+
})
|
|
55
47
|
}
|
|
56
48
|
}
|
|
57
49
|
```
|
|
58
50
|
|
|
51
|
+
> Verified against official `ti.identity` module docs. `Identity.createKeychainItem({identifier})` is the cross-platform API — it maps to iOS Keychain and Android Keystore automatically. `Ti.Android.createKeyStore()` and `Ti.KeychainItem.setItem()` do NOT exist.
|
|
52
|
+
|
|
59
53
|
## Certificate pinning
|
|
60
54
|
|
|
61
|
-
Prevent man-in-the-middle attacks by pinning SSL certificates:
|
|
55
|
+
Prevent man-in-the-middle attacks by pinning SSL certificates using the **`ti.https` module** (community module — not built into the SDK):
|
|
62
56
|
|
|
63
57
|
```javascript
|
|
64
58
|
// lib/api/pinnedClient.js
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
// Requires: ti.https module — install via npm or tiapp.xml modules section
|
|
60
|
+
const HTTPS = require('ti.https')
|
|
61
|
+
|
|
62
|
+
const securityManager = HTTPS.createX509CertificatePinningSecurityManager([
|
|
63
|
+
{
|
|
64
|
+
url: 'https://api.example.com',
|
|
65
|
+
serverCertificate: Ti.Filesystem.getFile(
|
|
66
|
+
Ti.Filesystem.resourcesDirectory, 'certificates/api-pin.pem'
|
|
67
|
+
).read()
|
|
68
|
+
}
|
|
69
|
+
])
|
|
69
70
|
|
|
70
|
-
|
|
71
|
+
exports.createPinnedClient = function(options = {}) {
|
|
72
|
+
return Ti.Network.createHTTPClient({
|
|
73
|
+
securityManager, // must be set at creation time
|
|
71
74
|
validatesSecureCertificate: true,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// Success
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
onerror: (e) => {
|
|
78
|
-
// Certificate validation failed
|
|
79
|
-
if (e.error.indexOf('certificate') >= 0) {
|
|
80
|
-
Ti.API.error('Certificate pinning failed - possible MITM attack')
|
|
81
|
-
}
|
|
82
|
-
}
|
|
75
|
+
timeout: 10000,
|
|
76
|
+
...options
|
|
83
77
|
})
|
|
84
|
-
|
|
85
|
-
return client
|
|
86
78
|
}
|
|
87
79
|
```
|
|
88
80
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
```xml
|
|
92
|
-
<ti:app>
|
|
93
|
-
<certificates>
|
|
94
|
-
<certificate>
|
|
95
|
-
<name>api.example.com</name>
|
|
96
|
-
<type>rsa</type>
|
|
97
|
-
<file>certificates/api-pin.pem</file>
|
|
98
|
-
</certificate>
|
|
99
|
-
</certificates>
|
|
100
|
-
</ti:app>
|
|
101
|
-
```
|
|
81
|
+
> `certificatePinning: true` is NOT a valid `HTTPClient` property. Certificate pinning requires the `ti.https` module and must be set via the `securityManager` property when creating the client. Note that the `validatesSecureCertificate` property of `HTTPClient` is not honored for pinned URLs — the security manager takes precedence.
|
|
102
82
|
|
|
103
83
|
## Data encryption at rest
|
|
104
84
|
|
|
85
|
+
> **Community pattern** — `ti.crypto` is a third-party community module (not part of the Titanium SDK core). Verify availability and compatibility before using in production.
|
|
86
|
+
|
|
105
87
|
```javascript
|
|
106
88
|
// lib/services/encryption.js
|
|
107
89
|
// AES-256 encryption for sensitive local data
|
|
90
|
+
// Requires: ti.crypto community module
|
|
108
91
|
|
|
109
92
|
const crypto = require('ti.crypto')
|
|
110
93
|
|
|
@@ -125,30 +108,6 @@ exports.decrypt = function(encryptedData, key) {
|
|
|
125
108
|
options: { mode: crypto.CBC }
|
|
126
109
|
})
|
|
127
110
|
}
|
|
128
|
-
|
|
129
|
-
// Usage: Secure cache of sensitive user data
|
|
130
|
-
module.exports = class SecureCache {
|
|
131
|
-
constructor(encryptionKey) {
|
|
132
|
-
this.key = encryptionKey
|
|
133
|
-
this.cache = {}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
set(key, value) {
|
|
137
|
-
const encrypted = encrypt(JSON.stringify(value), this.key)
|
|
138
|
-
this.cache[key] = encrypted
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
get(key) {
|
|
142
|
-
if (!this.cache[key]) return null
|
|
143
|
-
|
|
144
|
-
const decrypted = decrypt(this.cache[key], this.key)
|
|
145
|
-
return JSON.parse(decrypted)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
clear() {
|
|
149
|
-
this.cache = {}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
111
|
```
|
|
153
112
|
|
|
154
113
|
## Secure HTTP communication
|
|
@@ -270,15 +229,15 @@ exports.Validator = {
|
|
|
270
229
|
|
|
271
230
|
## Owasp mobile security checklist
|
|
272
231
|
|
|
273
|
-
| Category
|
|
232
|
+
| Category | Check | Implementation |
|
|
274
233
|
| -------------------- | --------------------------- | ---------------------------------- |
|
|
275
|
-
| **Data Storage**
|
|
276
|
-
| **Data Storage**
|
|
277
|
-
| **Communication**
|
|
278
|
-
| **Communication**
|
|
279
|
-
| **Authentication**
|
|
280
|
-
| **Authentication**
|
|
281
|
-
| **Input Validation** | Server-side validation
|
|
282
|
-
| **Input Validation** | Sanitize user input
|
|
283
|
-
| **Cryptography**
|
|
284
|
-
| **Cryptography**
|
|
234
|
+
| **Data Storage** | Credentials stored securely | Keychain/KeyStore for tokens |
|
|
235
|
+
| **Data Storage** | Sensitive data encrypted | AES-256 for cached data |
|
|
236
|
+
| **Communication** | HTTPS only | `validatesSecureCertificate: true` |
|
|
237
|
+
| **Communication** | Certificate pinning | SSL pinning enabled |
|
|
238
|
+
| **Authentication** | Token refresh | Auto-refresh before expiry |
|
|
239
|
+
| **Authentication** | Session timeout | Auto-logout after inactivity |
|
|
240
|
+
| **Input Validation** | Server-side validation | Never trust client input |
|
|
241
|
+
| **Input Validation** | Sanitize user input | Remove XSS patterns |
|
|
242
|
+
| **Cryptography** | No hardcoded keys | Keys from secure storage |
|
|
243
|
+
| **Cryptography** | Use standard algorithms | AES-256, SHA-256 |
|
|
@@ -20,6 +20,7 @@ Create a single source of truth using Backbone.Events:
|
|
|
20
20
|
```javascript
|
|
21
21
|
// lib/services/stateStore.js
|
|
22
22
|
const Backbone = require('alloy/backbone')
|
|
23
|
+
const _ = require('alloy/underscore')._
|
|
23
24
|
|
|
24
25
|
class StateStore {
|
|
25
26
|
constructor() {
|
|
@@ -280,13 +281,13 @@ Ti.App.addEventListener('resume', () => {
|
|
|
280
281
|
|
|
281
282
|
## Anti-patterns
|
|
282
283
|
|
|
283
|
-
| Anti-Pattern
|
|
284
|
+
| Anti-Pattern | Why It's Bad | Solution |
|
|
284
285
|
| ------------------------------------- | ---------------------------- | ---------------------------------------- |
|
|
285
|
-
| `Ti.App.fireEvent` for state
|
|
286
|
-
| Direct collection mutation
|
|
287
|
-
| State in multiple places
|
|
288
|
-
| Global variables (`Alloy.Globals`)
|
|
289
|
-
| Controller-to-controller direct calls | Tight coupling
|
|
286
|
+
| `Ti.App.fireEvent` for state | No cleanup, memory leaks | Use StateStore with `offChange` |
|
|
287
|
+
| Direct collection mutation | Bypasses reactivity | Use collection methods (`add`, `remove`) |
|
|
288
|
+
| State in multiple places | Inconsistency bugs | Single source of truth |
|
|
289
|
+
| Global variables (`Alloy.Globals`) | No reactivity, hard to track | Use StateStore |
|
|
290
|
+
| Controller-to-controller direct calls | Tight coupling | Use StateStore or events |
|
|
290
291
|
|
|
291
292
|
## Persistence strategies
|
|
292
293
|
|
|
@@ -472,14 +473,14 @@ exports.appStore = {
|
|
|
472
473
|
|
|
473
474
|
### Choosing a strategy
|
|
474
475
|
|
|
475
|
-
| Data Type
|
|
476
|
-
| ------------------ | ------------------- |
|
|
477
|
-
| User preferences
|
|
478
|
-
| Auth tokens
|
|
479
|
-
| User profile
|
|
480
|
-
| Lists (100+ items) | SQLite
|
|
481
|
-
| Offline queue
|
|
482
|
-
| Cache
|
|
476
|
+
| Data Type | Strategy | Reason |
|
|
477
|
+
| ------------------ | ------------------- | ----------------------- |
|
|
478
|
+
| User preferences | Ti.App.Properties | Simple main-value, fast |
|
|
479
|
+
| Auth tokens | Keychain/KeyStore | Security |
|
|
480
|
+
| User profile | Properties + Secure | Mixed sensitivity |
|
|
481
|
+
| Lists (100+ items) | SQLite | Query, pagination |
|
|
482
|
+
| Offline queue | SQLite | Durability, FIFO |
|
|
483
|
+
| Cache | In-memory + SQLite | Speed + persistence |
|
|
483
484
|
|
|
484
485
|
## State middleware
|
|
485
486
|
|
|
@@ -127,21 +127,30 @@ describe('Login Flow', () => {
|
|
|
127
127
|
|
|
128
128
|
## Adding accessibility IDs for testing
|
|
129
129
|
|
|
130
|
+
Appium uses accessibility identifiers to locate elements. The correct properties are:
|
|
131
|
+
- **iOS**: `accessibilityIdentifier` — maps to `$('~id')` in WebdriverIO
|
|
132
|
+
- **Android**: `contentDescription` — also maps to `$('~id')` in WebdriverIO
|
|
133
|
+
|
|
134
|
+
`testId` is NOT an official Titanium view property. Use `accessibilityIdentifier` instead.
|
|
135
|
+
|
|
130
136
|
```xml
|
|
131
137
|
<!-- views/auth/login.xml -->
|
|
132
|
-
<Window
|
|
133
|
-
<TextField id="emailField"
|
|
134
|
-
<TextField id="passwordField"
|
|
135
|
-
<Button id="loginBtn"
|
|
136
|
-
<Label id="errorLabel"
|
|
138
|
+
<Window id="loginWindow" accessibilityIdentifier="loginScreen">
|
|
139
|
+
<TextField id="emailField" accessibilityIdentifier="emailField" />
|
|
140
|
+
<TextField id="passwordField" accessibilityIdentifier="passwordField" />
|
|
141
|
+
<Button id="loginBtn" accessibilityIdentifier="loginButton" />
|
|
142
|
+
<Label id="errorLabel" accessibilityIdentifier="errorLabel" />
|
|
137
143
|
</Window>
|
|
138
144
|
```
|
|
139
145
|
|
|
140
146
|
```javascript
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
147
|
+
// For Android, also set contentDescription to match the same value
|
|
148
|
+
// since accessibilityIdentifier is iOS-specific.
|
|
149
|
+
// In controller:
|
|
150
|
+
if (OS_ANDROID) {
|
|
151
|
+
$.emailField.contentDescription = 'emailField'
|
|
152
|
+
$.passwordField.contentDescription = 'passwordField'
|
|
153
|
+
$.loginBtn.contentDescription = 'loginButton'
|
|
145
154
|
}
|
|
146
155
|
```
|
|
147
156
|
|
|
@@ -412,21 +421,21 @@ end
|
|
|
412
421
|
|
|
413
422
|
Configure in GitHub repository settings:
|
|
414
423
|
|
|
415
|
-
| Rule
|
|
424
|
+
| Rule | Setting |
|
|
416
425
|
| --------------------------------- | ------------------------------------------ |
|
|
417
|
-
| Require pull request reviews
|
|
418
|
-
| Require status checks
|
|
419
|
-
| Require branches to be up to date | Yes
|
|
420
|
-
| Include administrators
|
|
426
|
+
| Require pull request reviews | 1 approval required |
|
|
427
|
+
| Require status checks | lint, unit-tests, build-ios, build-android |
|
|
428
|
+
| Require branches to be up to date | Yes |
|
|
429
|
+
| Include administrators | Yes |
|
|
421
430
|
|
|
422
431
|
## Testing best practices summary
|
|
423
432
|
|
|
424
|
-
| Area
|
|
433
|
+
| Area | Practice |
|
|
425
434
|
| --------------------- | ------------------------------------------ |
|
|
426
|
-
| **Unit Tests**
|
|
435
|
+
| **Unit Tests** | Test business logic in services/helpers |
|
|
427
436
|
| **Integration Tests** | Test controller flows with mocked services |
|
|
428
|
-
| **E2E Tests**
|
|
429
|
-
| **Coverage**
|
|
430
|
-
| **CI Pipeline**
|
|
431
|
-
| **Artifacts**
|
|
432
|
-
| **Notifications**
|
|
437
|
+
| **E2E Tests** | Test critical user journeys |
|
|
438
|
+
| **Coverage** | Aim for 80%+ on services, 60%+ overall |
|
|
439
|
+
| **CI Pipeline** | Run lint -> unit tests -> build -> E2E |
|
|
440
|
+
| **Artifacts** | Save screenshots on failure |
|
|
441
|
+
| **Notifications** | Slack/email on build failures |
|
|
@@ -41,9 +41,8 @@ Set the theme in `config.json`:
|
|
|
41
41
|
}
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
Alloy themes are applied at **build time**. You cannot switch themes at runtime using this system alone. For runtime switching, use the Dynamic Theming approach below.
|
|
46
|
-
:::
|
|
44
|
+
> **⚠️ Theme is compile-time only**
|
|
45
|
+
> Alloy themes are applied at **build time**. You cannot switch themes at runtime using this system alone. For runtime switching, use the Dynamic Theming approach below.
|
|
47
46
|
|
|
48
47
|
### Theme cascade
|
|
49
48
|
|
|
@@ -178,9 +177,8 @@ exports.ThemeService = {
|
|
|
178
177
|
|
|
179
178
|
### Step 4: respond to theme changes in controllers
|
|
180
179
|
|
|
181
|
-
|
|
182
|
-
Since TSS is applied at controller creation time, changing `Alloy.Globals.theme` does NOT retroactively update already-rendered views. You must manually update visible elements or restart the root controller.
|
|
183
|
-
:::
|
|
180
|
+
> **⚠️ Theme change requires UI rebuild**
|
|
181
|
+
> Since TSS is applied at controller creation time, changing `Alloy.Globals.theme` does NOT retroactively update already-rendered views. You must manually update visible elements or restart the root controller.
|
|
184
182
|
|
|
185
183
|
**Approach A: Rebuild root controller (simplest)**
|
|
186
184
|
|
|
@@ -245,20 +243,15 @@ exports.ThemeService = {
|
|
|
245
243
|
|
|
246
244
|
// Initialize system theme listener
|
|
247
245
|
initSystemListener() {
|
|
248
|
-
// Detect current system theme
|
|
246
|
+
// Detect current system theme at startup
|
|
249
247
|
this.followSystem()
|
|
250
248
|
|
|
251
|
-
//
|
|
252
|
-
|
|
249
|
+
// Re-check on resume (user may have changed system Dark Mode while app was backgrounded)
|
|
250
|
+
// Note: Titanium does not fire a dedicated dark-mode-change event.
|
|
251
|
+
// `Ti.UI.semanticColorType` is the correct way to check the current setting.
|
|
252
|
+
Ti.App.addEventListener('resumed', () => {
|
|
253
253
|
this.followSystem()
|
|
254
254
|
})
|
|
255
|
-
|
|
256
|
-
// iOS: userInterfaceStyle change
|
|
257
|
-
if (OS_IOS) {
|
|
258
|
-
Ti.App.addEventListener('traitCollectionChange', () => {
|
|
259
|
-
this.followSystem()
|
|
260
|
-
})
|
|
261
|
-
}
|
|
262
255
|
}
|
|
263
256
|
}
|
|
264
257
|
```
|
|
@@ -322,10 +315,9 @@ $.container.backgroundColor = 'backgroundColor';
|
|
|
322
315
|
- Alpha can be set from 0.0-100.0 (integer or float)
|
|
323
316
|
- Light values can use hex with alpha via ARGB/AARRGGBB format
|
|
324
317
|
|
|
325
|
-
|
|
326
|
-
- **Semantic Colors**: Built-in Dark Mode support, automatic switching, simpler setup. Works cross-platform.
|
|
327
|
-
- **Alloy.Globals palette**: More control over when/how themes switch, easier to add custom themes beyond light/dark, runtime theme changes with UI rebuild.
|
|
328
|
-
:::
|
|
318
|
+
> **💡 When to use Semantic Colors vs Alloy.Globals**
|
|
319
|
+
> - **Semantic Colors**: Built-in Dark Mode support, automatic switching, simpler setup. Works cross-platform.
|
|
320
|
+
> - **Alloy.Globals palette**: More control over when/how themes switch, easier to add custom themes beyond light/dark, runtime theme changes with UI rebuild.
|
|
329
321
|
|
|
330
322
|
---
|
|
331
323
|
|
|
@@ -11,20 +11,19 @@ Use this skill to keep Titanium projects aligned with TiDev standards for stabil
|
|
|
11
11
|
|
|
12
12
|
## Project detection
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
This skill auto-detects Titanium projects. No manual command is needed.
|
|
16
|
-
|
|
17
|
-
Titanium project indicator:
|
|
18
|
-
- `tiapp.xml` file (definitive indicator)
|
|
19
|
-
|
|
20
|
-
Applies to both:
|
|
21
|
-
- Alloy projects (`app/` folder)
|
|
22
|
-
- Classic projects (`Resources/` folder)
|
|
23
|
-
|
|
24
|
-
Behavior:
|
|
25
|
-
- If a Titanium project is detected, provide official Titanium SDK guidance, memory management best practices, and app distribution help.
|
|
26
|
-
- If not detected, say this skill is only for Titanium projects.
|
|
27
|
-
:::
|
|
14
|
+
> **️ℹ️ Auto-detects Titanium projects**
|
|
15
|
+
> This skill auto-detects Titanium projects. No manual command is needed.
|
|
16
|
+
>
|
|
17
|
+
> Titanium project indicator:
|
|
18
|
+
> - `tiapp.xml` file (definitive indicator)
|
|
19
|
+
>
|
|
20
|
+
> Applies to both:
|
|
21
|
+
> - Alloy projects (`app/` folder)
|
|
22
|
+
> - Classic projects (`Resources/` folder)
|
|
23
|
+
>
|
|
24
|
+
> Behavior:
|
|
25
|
+
> - If a Titanium project is detected, provide official Titanium SDK guidance, memory management best practices, and app distribution help.
|
|
26
|
+
> - If not detected, say this skill is only for Titanium projects.
|
|
28
27
|
|
|
29
28
|
## Core workflow
|
|
30
29
|
|
|
@@ -52,9 +51,6 @@ Behavior:
|
|
|
52
51
|
- Hyperloop Native Access (references/hyperloop-native-access.md): Objective-C/Swift/Java syntax, casting, debugging, XIB/Storyboards.
|
|
53
52
|
- Style & Conventions (references/style-and-conventions.md): naming standards and formatting rules.
|
|
54
53
|
- Reserved Words (references/reserved-words.md): ECMAScript, iOS, and Alloy reserved keywords to avoid.
|
|
55
|
-
- Alloy CLI Reference (references/alloy-cli-advanced.md): extract-i18n, code generation, and build hooks.
|
|
56
|
-
- Alloy Data Mastery (references/alloy-data-mastery.md): sync adapters, data binding, and Backbone collections.
|
|
57
|
-
- Alloy Widgets & Themes (references/alloy-widgets-and-themes.md): widget structure, styling priorities, and theming.
|
|
58
54
|
- Android Manifest (references/android-manifest.md): custom AndroidManifest.xml, permissions, and manifest merge.
|
|
59
55
|
- App Distribution (references/app-distribution.md): Google Play (APK/AAB), App Store (IPA), certificates, provisioning, and deployment.
|
|
60
56
|
- tiapp.xml Configuration (references/tiapp-config.md): complete reference for tiapp.xml and timodule.xml, including all elements, properties, and platform-specific settings.
|
|
@@ -15,6 +15,26 @@
|
|
|
15
15
|
```
|
|
16
16
|
- Do not ship large pre-populated databases. Ship a small skeleton and download data on first boot to reduce IPA/APK size. The `Resources` directory is read-only, so installing a database copies it to `applicationDataDirectory`, which creates two copies on the device.
|
|
17
17
|
- Android note: in Android 2.2 and earlier, the installer could not uncompress assets over 1 MB. One workaround was to rename the file with a `.mp3` extension to prevent `aapt` compression.
|
|
18
|
+
- Pattern for downloading a replacement database on first boot:
|
|
19
|
+
```javascript
|
|
20
|
+
const updateDatabase = (newData) => {
|
|
21
|
+
// delete existing content and rehydrate from downloaded JSON
|
|
22
|
+
}
|
|
23
|
+
downloadButton.addEventListener('click', () => {
|
|
24
|
+
const client = Ti.Network.createHTTPClient({
|
|
25
|
+
timeout: 10000,
|
|
26
|
+
onload() {
|
|
27
|
+
updateDatabase(this.responseData)
|
|
28
|
+
Ti.UI.createAlertDialog({ title: 'Info', message: 'Database installed' }).show()
|
|
29
|
+
},
|
|
30
|
+
onerror(e) {
|
|
31
|
+
Ti.UI.createAlertDialog({ title: 'Error', message: e.error }).show()
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
client.open('GET', 'https://example.com/data.json')
|
|
35
|
+
client.send()
|
|
36
|
+
})
|
|
37
|
+
```
|
|
18
38
|
|
|
19
39
|
### Database version management
|
|
20
40
|
|
|
@@ -45,11 +65,11 @@ const addColumn = (dbname, tblName, newFieldName, colSpec) => {
|
|
|
45
65
|
|
|
46
66
|
Choose the right format for each use case:
|
|
47
67
|
|
|
48
|
-
| Format | Compression
|
|
49
|
-
|
|
|
50
|
-
| PNG
|
|
51
|
-
| JPG
|
|
52
|
-
| GIF
|
|
68
|
+
| Format | Compression | Best for | Avoid for |
|
|
69
|
+
| ------ | --------------------- | ------------------------------ | ------------------------------------------------------------------ |
|
|
70
|
+
| PNG | Lossless | Icons, text, line art, buttons | Photos (large files) |
|
|
71
|
+
| JPG | Lossy | Photographs | Text, icons, line drawings (artifacts) |
|
|
72
|
+
| GIF | Lossless (256 colors) | Rare cases | Most cases (limited colors, animated GIF not supported everywhere) |
|
|
53
73
|
|
|
54
74
|
For flip-book animations, use `ImageView.images` with an array of PNG or optimized JPG files instead of animated GIFs.
|
|
55
75
|
|
|
@@ -80,12 +100,12 @@ For flip-book animations, use `ImageView.images` with an array of PNG or optimiz
|
|
|
80
100
|
|
|
81
101
|
Resize and compress images before including them in your app to reduce IPA/APK size and network usage.
|
|
82
102
|
|
|
83
|
-
| Platform
|
|
84
|
-
|
|
|
85
|
-
| Mac
|
|
103
|
+
| Platform | File types | Tool |
|
|
104
|
+
| ------------------- | ------------- | ----------- |
|
|
105
|
+
| Mac | PNG, JPG, GIF | ImageOptim |
|
|
86
106
|
| Mac, Windows, Linux | PNG, JPG, GIF | ImageMagick |
|
|
87
|
-
| Windows/DOS
|
|
88
|
-
| Windows
|
|
107
|
+
| Windows/DOS | PNG | PNGCrush |
|
|
108
|
+
| Windows | JPG | Nikkho |
|
|
89
109
|
|
|
90
110
|
---
|
|
91
111
|
|
|
@@ -118,11 +118,11 @@ app.js:
|
|
|
118
118
|
// Single namespace pattern
|
|
119
119
|
const app = {};
|
|
120
120
|
|
|
121
|
-
//
|
|
122
|
-
|
|
121
|
+
// Load components via require (Ti.include is deprecated)
|
|
122
|
+
const AppWindow = require('ui/ApplicationWindow');
|
|
123
123
|
|
|
124
124
|
// Create and open window
|
|
125
|
-
app.mainWindow =
|
|
125
|
+
app.mainWindow = AppWindow.createApplicationWindow();
|
|
126
126
|
app.mainWindow.open();
|
|
127
127
|
```
|
|
128
128
|
|
|
@@ -34,6 +34,8 @@ Ti.App.fireEvent('my_event')
|
|
|
34
34
|
|
|
35
35
|
- Defer script loading. Only `require` modules when you need them.
|
|
36
36
|
|
|
37
|
+
Defer loading to reduce startup time. Only `require` modules when you need them.
|
|
38
|
+
|
|
37
39
|
Lazy script loading example:
|
|
38
40
|
```javascript
|
|
39
41
|
// must be loaded at launch
|
|
@@ -56,8 +58,16 @@ win1.addEventListener('click', () => {
|
|
|
56
58
|
});
|
|
57
59
|
```
|
|
58
60
|
|
|
59
|
-
- Bridge efficiency: minimize requests for device properties like `Ti.Platform.osname`. Store them in a local variable
|
|
60
|
-
|
|
61
|
+
- Bridge efficiency: minimize requests for device properties like `Ti.Platform.osname`. Store them in a local variable:
|
|
62
|
+
```javascript
|
|
63
|
+
const isAndroid = (Ti.Platform.osname === 'android')
|
|
64
|
+
if (isAndroid) { /* Android path */ } else { /* iOS path */ }
|
|
65
|
+
```
|
|
66
|
+
- Avoid extending the `Ti` namespace. Three reasons:
|
|
67
|
+
1. Ti objects are proxies to native OS components — your extensions may conflict with native functionality.
|
|
68
|
+
2. Arrays stored on the namespace may be immutable; objects may be null unexpectedly.
|
|
69
|
+
3. There is no guarantee properties persist across SDK versions.
|
|
70
|
+
Use your own namespace or CommonJS module instead of modifying `Ti`.
|
|
61
71
|
|
|
62
72
|
## 5. App architecture recommendations
|
|
63
73
|
|
|
@@ -85,6 +95,25 @@ const myModule = require('/MyModule');
|
|
|
85
95
|
myModule.sayHello('User');
|
|
86
96
|
```
|
|
87
97
|
|
|
98
|
+
### Namespace with deferred loading (alternative pattern)
|
|
99
|
+
|
|
100
|
+
If not using CommonJS, you can still defer loading with a namespace:
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
const someNameSpace = () => {
|
|
104
|
+
const API = {
|
|
105
|
+
init() {
|
|
106
|
+
// create UI or initialize
|
|
107
|
+
},
|
|
108
|
+
reset() {
|
|
109
|
+
// null objects, clean up
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return API
|
|
113
|
+
}
|
|
114
|
+
const ns = new someNameSpace()
|
|
115
|
+
```
|
|
116
|
+
|
|
88
117
|
### Custom objects as components
|
|
89
118
|
|
|
90
119
|
Popular for rapid deployment. It uses a namespace hierarchy. This pattern is less efficient than CommonJS modules, and object references can persist after they are no longer needed.
|
|
@@ -162,7 +162,49 @@ const logger = require('logger')
|
|
|
162
162
|
logger.info('some log statement with a timestamp')
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
-
## 7.
|
|
165
|
+
## 7. Packages of related functionality
|
|
166
|
+
|
|
167
|
+
Group related classes or helpers in a single module:
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
// app/lib/geo.js
|
|
171
|
+
class Point {
|
|
172
|
+
constructor(x, y) {
|
|
173
|
+
this.x = x
|
|
174
|
+
this.y = y
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
class Line {
|
|
179
|
+
constructor(start, end) {
|
|
180
|
+
this.start = start
|
|
181
|
+
this.end = end
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
slope() {
|
|
185
|
+
return (this.end.y - this.start.y) / (this.end.x - this.start.x)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
yIntercept() {
|
|
189
|
+
return this.start.y - (this.slope() * this.start.x)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
exports.Point = Point
|
|
194
|
+
exports.Line = Line
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Usage:
|
|
198
|
+
```javascript
|
|
199
|
+
const Geo = require('lib/geo')
|
|
200
|
+
|
|
201
|
+
const start = new Geo.Point(1, -5)
|
|
202
|
+
const end = new Geo.Point(10, 2)
|
|
203
|
+
const line = new Geo.Line(start, end)
|
|
204
|
+
Ti.API.info(line.slope())
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## 8. Inter-module state sharing
|
|
166
208
|
|
|
167
209
|
When a module assigns a primitive value to `exports`, the consumer gets a copy, not a live reference. Changes to the internal variable are not reflected in the exported property.
|
|
168
210
|
|
|
@@ -190,7 +232,7 @@ Ti.API.info(stateful.stepVal) // 5 - still the original copy
|
|
|
190
232
|
|
|
191
233
|
Rule: use getter/setter functions for stateful values. Direct property exports of primitives are snapshots at module load time.
|
|
192
234
|
|
|
193
|
-
##
|
|
235
|
+
## 9. Antipatterns to avoid
|
|
194
236
|
|
|
195
237
|
### Do not assign directly to `exports`
|
|
196
238
|
|
|
@@ -217,7 +259,7 @@ exports.foo = 'bar'
|
|
|
217
259
|
|
|
218
260
|
Any data a module needs should be passed during construction or initialization. Avoid globals shared across modules.
|
|
219
261
|
|
|
220
|
-
##
|
|
262
|
+
## 10. Security and scope
|
|
221
263
|
|
|
222
264
|
All modules have private scope. Variables declared within the module are private unless added to `exports`.
|
|
223
265
|
|
|
@@ -230,7 +272,7 @@ exports.publicMethod = () => {
|
|
|
230
272
|
}
|
|
231
273
|
```
|
|
232
274
|
|
|
233
|
-
##
|
|
275
|
+
## 11. Node.js compatibility
|
|
234
276
|
|
|
235
277
|
Titanium supports Node.js module patterns and `require()` resolution. Node.js modules can often be used directly.
|
|
236
278
|
|