@maccesar/titools 2.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.
- package/AGENTS-TEMPLATE.md +173 -0
- package/README.md +867 -0
- package/agents/ti-researcher.md +108 -0
- package/bin/titools.js +53 -0
- package/lib/commands/agents.js +126 -0
- package/lib/commands/install.js +188 -0
- package/lib/commands/uninstall.js +215 -0
- package/lib/commands/update.js +159 -0
- package/lib/config.js +119 -0
- package/lib/downloader.js +153 -0
- package/lib/installer.js +253 -0
- package/lib/platform.js +108 -0
- package/lib/symlink.js +142 -0
- package/lib/utils.js +270 -0
- package/package.json +67 -0
- package/skills/alloy-expert/SKILL.md +247 -0
- package/skills/alloy-expert/assets/ControllerAutoCleanup.js +182 -0
- package/skills/alloy-expert/references/alloy-structure.md +381 -0
- package/skills/alloy-expert/references/anti-patterns.md +133 -0
- package/skills/alloy-expert/references/code-conventions.md +469 -0
- package/skills/alloy-expert/references/contracts.md +280 -0
- package/skills/alloy-expert/references/controller-patterns.md +520 -0
- package/skills/alloy-expert/references/error-handling.md +484 -0
- package/skills/alloy-expert/references/examples.md +735 -0
- package/skills/alloy-expert/references/migration-patterns.md +298 -0
- package/skills/alloy-expert/references/patterns.md +448 -0
- package/skills/alloy-expert/references/performance-patterns.md +855 -0
- package/skills/alloy-expert/references/security-patterns.md +847 -0
- package/skills/alloy-expert/references/state-management.md +779 -0
- package/skills/alloy-expert/references/testing.md +872 -0
- package/skills/alloy-guides/SKILL.md +214 -0
- package/skills/alloy-guides/references/CLI_TASKS.md +243 -0
- package/skills/alloy-guides/references/CONCEPTS.md +191 -0
- package/skills/alloy-guides/references/CONTROLLERS.md +298 -0
- package/skills/alloy-guides/references/MODELS.md +1028 -0
- package/skills/alloy-guides/references/PURGETSS.md +56 -0
- package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +242 -0
- package/skills/alloy-guides/references/VIEWS_STYLES.md +388 -0
- package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +109 -0
- package/skills/alloy-guides/references/VIEWS_XML.md +558 -0
- package/skills/alloy-guides/references/WIDGETS.md +176 -0
- package/skills/alloy-howtos/SKILL.md +203 -0
- package/skills/alloy-howtos/references/best_practices.md +138 -0
- package/skills/alloy-howtos/references/cli_reference.md +253 -0
- package/skills/alloy-howtos/references/config_files.md +87 -0
- package/skills/alloy-howtos/references/custom_tags.md +147 -0
- package/skills/alloy-howtos/references/debugging_troubleshooting.md +101 -0
- package/skills/alloy-howtos/references/samples.md +167 -0
- package/skills/purgetss/SKILL.md +442 -0
- package/skills/purgetss/assets/purgetss.config.cjs +17 -0
- package/skills/purgetss/references/EXAMPLES.md +247 -0
- package/skills/purgetss/references/animation-system.md +1294 -0
- package/skills/purgetss/references/apply-directive.md +375 -0
- package/skills/purgetss/references/arbitrary-values.md +612 -0
- package/skills/purgetss/references/class-index.md +1350 -0
- package/skills/purgetss/references/cli-commands.md +948 -0
- package/skills/purgetss/references/configurable-properties.md +654 -0
- package/skills/purgetss/references/custom-rules.md +161 -0
- package/skills/purgetss/references/customization-deep-dive.md +722 -0
- package/skills/purgetss/references/dynamic-component-creation.md +489 -0
- package/skills/purgetss/references/grid-layout.md +455 -0
- package/skills/purgetss/references/icon-fonts.md +609 -0
- package/skills/purgetss/references/installation-setup.md +366 -0
- package/skills/purgetss/references/opacity-modifier.md +291 -0
- package/skills/purgetss/references/platform-modifiers.md +479 -0
- package/skills/purgetss/references/smart-mappings.md +42 -0
- package/skills/purgetss/references/titanium-resets.md +359 -0
- package/skills/purgetss/references/ui-ux-design.md +1526 -0
- package/skills/ti-guides/SKILL.md +94 -0
- package/skills/ti-guides/references/advanced-data-and-images.md +19 -0
- package/skills/ti-guides/references/alloy-cli-advanced.md +84 -0
- package/skills/ti-guides/references/alloy-data-mastery.md +29 -0
- package/skills/ti-guides/references/alloy-widgets-and-themes.md +19 -0
- package/skills/ti-guides/references/android-manifest.md +97 -0
- package/skills/ti-guides/references/app-distribution.md +258 -0
- package/skills/ti-guides/references/application-frameworks.md +377 -0
- package/skills/ti-guides/references/cli-reference.md +402 -0
- package/skills/ti-guides/references/coding-best-practices.md +102 -0
- package/skills/ti-guides/references/commonjs-advanced.md +134 -0
- package/skills/ti-guides/references/hello-world.md +100 -0
- package/skills/ti-guides/references/hyperloop-native-access.md +62 -0
- package/skills/ti-guides/references/javascript-primer.md +411 -0
- package/skills/ti-guides/references/reserved-words.md +36 -0
- package/skills/ti-guides/references/resources.md +183 -0
- package/skills/ti-guides/references/style-and-conventions.md +48 -0
- package/skills/ti-guides/references/tiapp-config.md +609 -0
- package/skills/ti-howtos/SKILL.md +174 -0
- package/skills/ti-howtos/references/android-platform-deep-dives.md +658 -0
- package/skills/ti-howtos/references/automation-fastlane-appium.md +95 -0
- package/skills/ti-howtos/references/buffer-codec-streams.md +140 -0
- package/skills/ti-howtos/references/cross-platform-development.md +348 -0
- package/skills/ti-howtos/references/debugging-profiling.md +543 -0
- package/skills/ti-howtos/references/extending-titanium.md +723 -0
- package/skills/ti-howtos/references/google-maps-v2.md +169 -0
- package/skills/ti-howtos/references/ios-map-kit.md +143 -0
- package/skills/ti-howtos/references/ios-platform-deep-dives.md +783 -0
- package/skills/ti-howtos/references/local-data-sources.md +301 -0
- package/skills/ti-howtos/references/location-and-maps.md +252 -0
- package/skills/ti-howtos/references/media-apis.md +210 -0
- package/skills/ti-howtos/references/notification-services.md +599 -0
- package/skills/ti-howtos/references/remote-data-sources.md +349 -0
- package/skills/ti-howtos/references/tutorials.md +502 -0
- package/skills/ti-howtos/references/using-modules.md +237 -0
- package/skills/ti-howtos/references/web-content-integration.md +307 -0
- package/skills/ti-howtos/references/webpack-build-pipeline.md +78 -0
- package/skills/ti-ui/SKILL.md +179 -0
- package/skills/ti-ui/references/accessibility-deep-dive.md +242 -0
- package/skills/ti-ui/references/animation-and-matrices.md +599 -0
- package/skills/ti-ui/references/application-structures.md +655 -0
- package/skills/ti-ui/references/custom-fonts-styling.md +579 -0
- package/skills/ti-ui/references/event-handling.md +393 -0
- package/skills/ti-ui/references/gestures.md +473 -0
- package/skills/ti-ui/references/icons-and-splash-screens.md +409 -0
- package/skills/ti-ui/references/layouts-and-positioning.md +462 -0
- package/skills/ti-ui/references/listviews-and-performance.md +619 -0
- package/skills/ti-ui/references/orientation.md +362 -0
- package/skills/ti-ui/references/platform-ui-android.md +635 -0
- package/skills/ti-ui/references/platform-ui-ios.md +469 -0
- package/skills/ti-ui/references/scrolling-views.md +252 -0
- package/skills/ti-ui/references/tableviews.md +568 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
# Architectural Patterns for Titanium + Alloy
|
|
2
|
+
|
|
3
|
+
## 1. Native Module Abstraction (Wrapper)
|
|
4
|
+
|
|
5
|
+
**Use when:** Using native modules like Maps, Biometrics, or specialized Media players.
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
// lib/services/biometricService.js
|
|
9
|
+
const Identity = require('ti.identity')
|
|
10
|
+
|
|
11
|
+
let isAvailable = null
|
|
12
|
+
|
|
13
|
+
exports.BiometricService = {
|
|
14
|
+
checkAvailability() {
|
|
15
|
+
if (isAvailable !== null) return isAvailable
|
|
16
|
+
|
|
17
|
+
isAvailable = Identity.isSupported() &&
|
|
18
|
+
(Identity.deviceCanAuthenticate() === Identity.SUCCESS)
|
|
19
|
+
|
|
20
|
+
return isAvailable
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
authenticate(reason) {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
if (!this.checkAvailability()) {
|
|
26
|
+
return reject(new Error('Biometrics not available'))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Identity.authenticate({
|
|
30
|
+
reason: reason || L('biometric_reason'),
|
|
31
|
+
callback: (e) => {
|
|
32
|
+
if (e.success) {
|
|
33
|
+
resolve(true)
|
|
34
|
+
} else {
|
|
35
|
+
reject(new Error(e.error || 'Authentication failed'))
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 2. Repository Pattern for Data
|
|
45
|
+
|
|
46
|
+
**Use when:** Complex data access, multiple data sources, or need to abstract SQLite/API.
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
// lib/repositories/userRepository.js
|
|
50
|
+
const db = Ti.Database.open('app')
|
|
51
|
+
|
|
52
|
+
exports.UserRepository = {
|
|
53
|
+
getById(id) {
|
|
54
|
+
const rs = db.execute('SELECT * FROM users WHERE id = ?', id)
|
|
55
|
+
const user = rs.isValidRow() ? this._rowToUser(rs) : null
|
|
56
|
+
rs.close()
|
|
57
|
+
return user
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
getAll() {
|
|
61
|
+
const rs = db.execute('SELECT * FROM users ORDER BY name')
|
|
62
|
+
const users = []
|
|
63
|
+
while (rs.isValidRow()) {
|
|
64
|
+
users.push(this._rowToUser(rs))
|
|
65
|
+
rs.next()
|
|
66
|
+
}
|
|
67
|
+
rs.close()
|
|
68
|
+
return users
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
save(user) {
|
|
72
|
+
if (user.id) {
|
|
73
|
+
db.execute(
|
|
74
|
+
'UPDATE users SET name = ?, email = ? WHERE id = ?',
|
|
75
|
+
user.name, user.email, user.id
|
|
76
|
+
)
|
|
77
|
+
} else {
|
|
78
|
+
db.execute(
|
|
79
|
+
'INSERT INTO users (name, email) VALUES (?, ?)',
|
|
80
|
+
user.name, user.email
|
|
81
|
+
)
|
|
82
|
+
user.id = db.lastInsertRowId
|
|
83
|
+
}
|
|
84
|
+
return user
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
delete(id) {
|
|
88
|
+
db.execute('DELETE FROM users WHERE id = ?', id)
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
_rowToUser(rs) {
|
|
92
|
+
return {
|
|
93
|
+
id: rs.fieldByName('id'),
|
|
94
|
+
name: rs.fieldByName('name'),
|
|
95
|
+
email: rs.fieldByName('email')
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 3. Service Layer Pattern
|
|
102
|
+
|
|
103
|
+
**Use when:** Business logic, orchestrating multiple operations.
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
// lib/services/authService.js
|
|
107
|
+
const authApi = require('lib/api/authApi').authApi
|
|
108
|
+
const { TokenStorage } = require('lib/services/tokenStorage')
|
|
109
|
+
const { appStore } = require('lib/services/stateStore')
|
|
110
|
+
const { Validator } = require('lib/helpers/validator')
|
|
111
|
+
|
|
112
|
+
exports.AuthService = {
|
|
113
|
+
async login(email, password) {
|
|
114
|
+
// 1. Validate input
|
|
115
|
+
Validator.email(email)
|
|
116
|
+
Validator.password(password)
|
|
117
|
+
|
|
118
|
+
// 2. Call API
|
|
119
|
+
const response = await authApi.login({ email, password })
|
|
120
|
+
|
|
121
|
+
// 3. Store token securely
|
|
122
|
+
TokenStorage.save(response.token)
|
|
123
|
+
|
|
124
|
+
// 4. Update app state
|
|
125
|
+
appStore.setState({
|
|
126
|
+
user: response.user,
|
|
127
|
+
isAuthenticated: true
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
return response.user
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
async logout() {
|
|
134
|
+
try {
|
|
135
|
+
await authApi.logout()
|
|
136
|
+
} finally {
|
|
137
|
+
TokenStorage.clear()
|
|
138
|
+
appStore.setState({
|
|
139
|
+
user: null,
|
|
140
|
+
isAuthenticated: false
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
isAuthenticated() {
|
|
146
|
+
return !!TokenStorage.get()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## 4. Event Bus for Decoupling
|
|
152
|
+
|
|
153
|
+
**Use when:** Decoupled modules need to communicate without knowing about each other.
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
// lib/services/eventBus.js
|
|
157
|
+
const _ = require('alloy/underscore')._
|
|
158
|
+
const Backbone = require('alloy/backbone')
|
|
159
|
+
|
|
160
|
+
const EventBus = _.clone(Backbone.Events)
|
|
161
|
+
|
|
162
|
+
// Named events for type safety
|
|
163
|
+
const Events = {
|
|
164
|
+
USER_UPDATED: 'user:updated',
|
|
165
|
+
CART_CHANGED: 'cart:changed',
|
|
166
|
+
NETWORK_STATUS: 'network:status',
|
|
167
|
+
SYNC_COMPLETE: 'sync:complete'
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = EventBus
|
|
171
|
+
module.exports.Events = Events
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Usage in controllers:**
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
// controllers/profile.js
|
|
178
|
+
const EventBus = require('lib/services/eventBus')
|
|
179
|
+
const { Events } = EventBus
|
|
180
|
+
|
|
181
|
+
function init() {
|
|
182
|
+
EventBus.on(Events.USER_UPDATED, onUserUpdated)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function onUserUpdated(user) {
|
|
186
|
+
$.nameLabel.text = user.name
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function cleanup() {
|
|
190
|
+
EventBus.off(Events.USER_UPDATED, onUserUpdated)
|
|
191
|
+
$.destroy()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
$.cleanup = cleanup
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## 5. Factory Pattern for Dynamic Views
|
|
198
|
+
|
|
199
|
+
**Use when:** Creating similar views with different configurations.
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
// lib/factories/cardFactory.js
|
|
203
|
+
exports.createProductCard = function(product) {
|
|
204
|
+
const card = Ti.UI.createView({
|
|
205
|
+
width: Ti.UI.FILL,
|
|
206
|
+
height: 120,
|
|
207
|
+
backgroundColor: '#fff'
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
const image = Ti.UI.createImageView({
|
|
211
|
+
image: product.imageUrl,
|
|
212
|
+
width: 80,
|
|
213
|
+
height: 80,
|
|
214
|
+
left: 16
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
const title = Ti.UI.createLabel({
|
|
218
|
+
text: product.name,
|
|
219
|
+
left: 112,
|
|
220
|
+
top: 16,
|
|
221
|
+
font: { fontSize: 16, fontWeight: 'bold' }
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
const price = Ti.UI.createLabel({
|
|
225
|
+
text: `$${product.price}`,
|
|
226
|
+
left: 112,
|
|
227
|
+
bottom: 16,
|
|
228
|
+
color: '#22c55e'
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
card.add(image)
|
|
232
|
+
card.add(title)
|
|
233
|
+
card.add(price)
|
|
234
|
+
|
|
235
|
+
// Attach data for event handling
|
|
236
|
+
card.productId = product.id
|
|
237
|
+
|
|
238
|
+
return card
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## 6. Navigation Service Pattern
|
|
243
|
+
|
|
244
|
+
**Use when:** Centralizing navigation with automatic cleanup and history management.
|
|
245
|
+
|
|
246
|
+
```javascript
|
|
247
|
+
// lib/services/navigation.js
|
|
248
|
+
const stack = []
|
|
249
|
+
|
|
250
|
+
exports.Navigation = {
|
|
251
|
+
open(route, params = {}, options = {}) {
|
|
252
|
+
const controller = Alloy.createController(route, params)
|
|
253
|
+
const view = controller.getView()
|
|
254
|
+
|
|
255
|
+
// Auto-cleanup on close
|
|
256
|
+
view.addEventListener('close', () => {
|
|
257
|
+
if (typeof controller.cleanup === 'function') {
|
|
258
|
+
controller.cleanup()
|
|
259
|
+
}
|
|
260
|
+
const idx = stack.findIndex(s => s.controller === controller)
|
|
261
|
+
if (idx !== -1) stack.splice(idx, 1)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
stack.push({ route, controller, params })
|
|
265
|
+
|
|
266
|
+
if (options.modal) {
|
|
267
|
+
view.open({ modal: true })
|
|
268
|
+
} else if (options.animated === false) {
|
|
269
|
+
view.open({ animated: false })
|
|
270
|
+
} else {
|
|
271
|
+
view.open()
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return controller
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
back() {
|
|
278
|
+
const current = stack[stack.length - 1]
|
|
279
|
+
if (current) {
|
|
280
|
+
current.controller.getView().close()
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
backTo(route) {
|
|
285
|
+
while (stack.length > 0) {
|
|
286
|
+
const current = stack[stack.length - 1]
|
|
287
|
+
if (current.route === route) break
|
|
288
|
+
current.controller.getView().close()
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
replace(route, params = {}) {
|
|
293
|
+
const current = stack.pop()
|
|
294
|
+
if (current) {
|
|
295
|
+
current.controller.getView().close({ animated: false })
|
|
296
|
+
}
|
|
297
|
+
return this.open(route, params, { animated: false })
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
getStack() {
|
|
301
|
+
return stack.map(s => s.route)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## 7. Collection Binding Pattern
|
|
307
|
+
|
|
308
|
+
**Use when:** Binding API data to ListViews with automatic updates.
|
|
309
|
+
|
|
310
|
+
```javascript
|
|
311
|
+
// alloy.js - Initialize collection
|
|
312
|
+
Alloy.Collections.products = new Backbone.Collection()
|
|
313
|
+
|
|
314
|
+
// controllers/products/list.js
|
|
315
|
+
function init() {
|
|
316
|
+
loadProducts()
|
|
317
|
+
|
|
318
|
+
// Collection changes auto-update bound ListView
|
|
319
|
+
Alloy.Collections.products.on('reset add remove', onCollectionChange)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function loadProducts() {
|
|
323
|
+
const products = await productApi.getAll()
|
|
324
|
+
Alloy.Collections.products.reset(products)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function onCollectionChange() {
|
|
328
|
+
// Optional: Handle empty state
|
|
329
|
+
$.emptyState.visible = Alloy.Collections.products.length === 0
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function cleanup() {
|
|
333
|
+
Alloy.Collections.products.off('reset add remove', onCollectionChange)
|
|
334
|
+
$.destroy()
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
$.cleanup = cleanup
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
```xml
|
|
341
|
+
<!-- views/products/list.xml -->
|
|
342
|
+
<ListView class="wh-screen">
|
|
343
|
+
<ListSection dataCollection="products">
|
|
344
|
+
<ListItem title="{name}" subtitle="{description}" />
|
|
345
|
+
</ListSection>
|
|
346
|
+
</ListView>
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## 8. Cleanup Pattern
|
|
350
|
+
|
|
351
|
+
**Use when:** Preventing memory leaks by removing global listeners.
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
// controllers/dashboard.js
|
|
355
|
+
const EventBus = require('lib/services/eventBus')
|
|
356
|
+
const { Events } = EventBus
|
|
357
|
+
const { appStore } = require('lib/services/stateStore')
|
|
358
|
+
|
|
359
|
+
// Store references for cleanup
|
|
360
|
+
let storeUnsubscribe = null
|
|
361
|
+
|
|
362
|
+
function init() {
|
|
363
|
+
// Event bus listeners
|
|
364
|
+
EventBus.on(Events.USER_UPDATED, onUserUpdated)
|
|
365
|
+
EventBus.on(Events.SYNC_COMPLETE, onSyncComplete)
|
|
366
|
+
|
|
367
|
+
// State store subscription
|
|
368
|
+
storeUnsubscribe = appStore.onChange(onStateChange)
|
|
369
|
+
|
|
370
|
+
// Ti.App listeners (avoid if possible, use EventBus)
|
|
371
|
+
Ti.App.addEventListener('pause', onAppPause)
|
|
372
|
+
Ti.App.addEventListener('resume', onAppResume)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function cleanup() {
|
|
376
|
+
// Remove all event bus listeners
|
|
377
|
+
EventBus.off(Events.USER_UPDATED, onUserUpdated)
|
|
378
|
+
EventBus.off(Events.SYNC_COMPLETE, onSyncComplete)
|
|
379
|
+
|
|
380
|
+
// Unsubscribe from store
|
|
381
|
+
if (storeUnsubscribe) {
|
|
382
|
+
storeUnsubscribe()
|
|
383
|
+
storeUnsubscribe = null
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Remove Ti.App listeners
|
|
387
|
+
Ti.App.removeEventListener('pause', onAppPause)
|
|
388
|
+
Ti.App.removeEventListener('resume', onAppResume)
|
|
389
|
+
|
|
390
|
+
// Destroy Alloy bindings
|
|
391
|
+
$.destroy()
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// CRITICAL: Expose cleanup
|
|
395
|
+
$.cleanup = cleanup
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## 9. Singleton Service Pattern
|
|
399
|
+
|
|
400
|
+
**Use when:** Services that should have only one instance app-wide.
|
|
401
|
+
|
|
402
|
+
```javascript
|
|
403
|
+
// lib/services/analyticsService.js
|
|
404
|
+
let instance = null
|
|
405
|
+
|
|
406
|
+
class AnalyticsService {
|
|
407
|
+
constructor() {
|
|
408
|
+
if (instance) {
|
|
409
|
+
return instance
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
this.queue = []
|
|
413
|
+
this.isInitialized = false
|
|
414
|
+
instance = this
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
init(config) {
|
|
418
|
+
if (this.isInitialized) return
|
|
419
|
+
|
|
420
|
+
// Initialize analytics SDK
|
|
421
|
+
this.isInitialized = true
|
|
422
|
+
|
|
423
|
+
// Flush queued events
|
|
424
|
+
this.queue.forEach(event => this._send(event))
|
|
425
|
+
this.queue = []
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
track(eventName, properties = {}) {
|
|
429
|
+
const event = {
|
|
430
|
+
name: eventName,
|
|
431
|
+
properties,
|
|
432
|
+
timestamp: Date.now()
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (this.isInitialized) {
|
|
436
|
+
this._send(event)
|
|
437
|
+
} else {
|
|
438
|
+
this.queue.push(event)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
_send(event) {
|
|
443
|
+
// Send to analytics backend
|
|
444
|
+
Ti.API.info(`[Analytics] ${event.name}`, event.properties)
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
exports.Analytics = new AnalyticsService()
|