@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,520 @@
|
|
|
1
|
+
# PurgeTSS Implementation Patterns
|
|
2
|
+
|
|
3
|
+
## Standard PurgeTSS View Template
|
|
4
|
+
|
|
5
|
+
```xml
|
|
6
|
+
<!-- views/user/card.xml -->
|
|
7
|
+
<Alloy>
|
|
8
|
+
<View class="border-(1) m-2 rounded-xl border-gray-200 bg-white shadow-md">
|
|
9
|
+
<View class="horizontal m-3 w-screen">
|
|
10
|
+
<Label class="fa-solid fa-user-circle text-4xl text-blue-500" />
|
|
11
|
+
<View class="vertical ml-3">
|
|
12
|
+
<Label id="name" class="text-lg font-bold text-gray-900" />
|
|
13
|
+
<Label id="email" class="text-sm text-gray-500" />
|
|
14
|
+
</View>
|
|
15
|
+
</View>
|
|
16
|
+
<Button class="mx-3 mb-3 mt-4 h-10 w-screen rounded-md bg-blue-600 font-medium text-white"
|
|
17
|
+
title="L('view_profile')"
|
|
18
|
+
onClick="onViewProfile"
|
|
19
|
+
/>
|
|
20
|
+
</View>
|
|
21
|
+
</Alloy>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**PurgeTSS Layout Rules:**
|
|
25
|
+
- Use `horizontal` (not `flex-row`) for left-to-right
|
|
26
|
+
- Use `vertical` (not `flex-col`) for top-to-bottom
|
|
27
|
+
- Omit layout class for composite (absolute positioning)
|
|
28
|
+
- Use `m-*` on children instead of `p-*` on parent
|
|
29
|
+
- Use `border-(1)` with parentheses for arbitrary values
|
|
30
|
+
|
|
31
|
+
## Animation Component Usage
|
|
32
|
+
|
|
33
|
+
Always prefer the PurgeTSS Animation component over manual matrix calculations.
|
|
34
|
+
|
|
35
|
+
```xml
|
|
36
|
+
<Animation id="myAnim" module="purgetss.ui" class="close:opacity-0 duration-500 open:opacity-100" />
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
// controllers/user/card.js
|
|
41
|
+
function show() {
|
|
42
|
+
$.myAnim.open($.container)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function hide() {
|
|
46
|
+
$.myAnim.close($.container, () => {
|
|
47
|
+
$.container.applyProperties({ visible: false })
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Draggable Method
|
|
53
|
+
Use the `draggable` method to convert views into draggable elements.
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
$.myAnim.draggable([$.red, $.green, $.blue])
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Dynamic Styling with Classes
|
|
60
|
+
|
|
61
|
+
To change styles dynamically, use `classes` instead of individual property updates.
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
function setStatus(isActive) {
|
|
65
|
+
$.statusLabel.applyProperties({
|
|
66
|
+
classes: isActive ? ['text-green-500'] : ['text-red-500'],
|
|
67
|
+
text: isActive ? L('active') : L('inactive')
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Grid System
|
|
73
|
+
|
|
74
|
+
Use PurgeTSS percentage-based widths for responsive layouts.
|
|
75
|
+
|
|
76
|
+
```xml
|
|
77
|
+
<!-- Horizontal layout with percentage widths -->
|
|
78
|
+
<View class="horizontal w-screen">
|
|
79
|
+
<View class="h-20 w-8/12 bg-red-100" />
|
|
80
|
+
<View class="h-20 w-4/12 bg-blue-100" />
|
|
81
|
+
</View>
|
|
82
|
+
|
|
83
|
+
<!-- Or use the grid-cols system -->
|
|
84
|
+
<View class="w-screen grid-cols-3 gap-2">
|
|
85
|
+
<View class="h-20 bg-red-100" />
|
|
86
|
+
<View class="h-20 bg-blue-100" />
|
|
87
|
+
<View class="h-20 bg-green-100" />
|
|
88
|
+
</View>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Controller Lifecycle Patterns
|
|
92
|
+
|
|
93
|
+
Show init, focus, blur, close lifecycle:
|
|
94
|
+
```javascript
|
|
95
|
+
// controllers/user/profile.js
|
|
96
|
+
function init() {
|
|
97
|
+
// Called once when controller is created
|
|
98
|
+
loadUserData()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Handle window focus (returning to this screen)
|
|
102
|
+
$.getView().addEventListener('focus', () => {
|
|
103
|
+
// Refresh data when screen becomes visible
|
|
104
|
+
refreshIfNeeded()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// Handle window blur (leaving this screen)
|
|
108
|
+
$.getView().addEventListener('blur', () => {
|
|
109
|
+
// Pause animations, stop timers
|
|
110
|
+
pauseAutoRefresh()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// Handle window close
|
|
114
|
+
$.getView().addEventListener('close', () => {
|
|
115
|
+
// Cleanup is called automatically by Navigation service
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
function cleanup() {
|
|
119
|
+
$.getView().removeEventListener('focus', onFocus)
|
|
120
|
+
$.getView().removeEventListener('blur', onBlur)
|
|
121
|
+
$.destroy()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
$.cleanup = cleanup
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Form Handling Patterns
|
|
128
|
+
|
|
129
|
+
```xml
|
|
130
|
+
<!-- views/user/edit.xml -->
|
|
131
|
+
<Alloy>
|
|
132
|
+
<Window class="vertical bg-white">
|
|
133
|
+
<ScrollView class="wh-screen vertical">
|
|
134
|
+
<View class="vertical mt-4 h-auto w-screen">
|
|
135
|
+
<Label class="ml-4 text-sm text-gray-500" text="L('name_label')" />
|
|
136
|
+
<TextField id="nameField"
|
|
137
|
+
class="border-(1) return-key-type-next mx-4 mt-1 h-12 w-screen rounded-lg border-gray-300 bg-gray-50 px-3"
|
|
138
|
+
hintText="L('name_hint')"
|
|
139
|
+
onReturn="focusNextField"
|
|
140
|
+
/>
|
|
141
|
+
|
|
142
|
+
<Label class="ml-4 mt-4 text-sm text-gray-500" text="L('email_label')" />
|
|
143
|
+
<TextField id="emailField"
|
|
144
|
+
class="border-(1) keyboard-type-email return-key-type-done mx-4 mt-1 h-12 w-screen rounded-lg border-gray-300 bg-gray-50 px-3"
|
|
145
|
+
hintText="L('email_hint')"
|
|
146
|
+
onReturn="submitForm"
|
|
147
|
+
/>
|
|
148
|
+
|
|
149
|
+
<Label id="errorLabel" class="ml-4 mt-2 hidden text-sm text-red-500" />
|
|
150
|
+
|
|
151
|
+
<Button id="submitBtn"
|
|
152
|
+
class="bg-primary mx-4 mt-6 h-14 w-screen rounded-xl font-bold text-white"
|
|
153
|
+
title="L('save')"
|
|
154
|
+
onClick="submitForm"
|
|
155
|
+
/>
|
|
156
|
+
</View>
|
|
157
|
+
</ScrollView>
|
|
158
|
+
</Window>
|
|
159
|
+
</Alloy>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
// controllers/user/edit.js
|
|
164
|
+
const { validateEmail, validateRequired } = require('lib/helpers/validator')
|
|
165
|
+
const { userService } = require('lib/services/userService')
|
|
166
|
+
|
|
167
|
+
const fields = ['nameField', 'emailField']
|
|
168
|
+
let currentFieldIndex = 0
|
|
169
|
+
|
|
170
|
+
function focusNextField() {
|
|
171
|
+
currentFieldIndex++
|
|
172
|
+
if (currentFieldIndex < fields.length) {
|
|
173
|
+
$[fields[currentFieldIndex]].focus()
|
|
174
|
+
} else {
|
|
175
|
+
submitForm()
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function submitForm() {
|
|
180
|
+
// Hide keyboard
|
|
181
|
+
Ti.UI.Android?.hideSoftKeyboard() || $.nameField.blur()
|
|
182
|
+
|
|
183
|
+
// Clear previous errors
|
|
184
|
+
$.errorLabel.applyProperties({ visible: false, text: '' })
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
// Validate
|
|
188
|
+
const name = validateRequired($.nameField.value, 'Name')
|
|
189
|
+
const email = validateEmail($.emailField.value)
|
|
190
|
+
|
|
191
|
+
// Show loading
|
|
192
|
+
setLoading(true)
|
|
193
|
+
|
|
194
|
+
// Submit
|
|
195
|
+
await userService.updateProfile({ name, email })
|
|
196
|
+
|
|
197
|
+
// Success - close and notify
|
|
198
|
+
$.trigger('user:saved')
|
|
199
|
+
Navigation.back()
|
|
200
|
+
|
|
201
|
+
} catch (error) {
|
|
202
|
+
$.errorLabel.applyProperties({
|
|
203
|
+
text: error.message,
|
|
204
|
+
visible: true
|
|
205
|
+
})
|
|
206
|
+
} finally {
|
|
207
|
+
setLoading(false)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function setLoading(isLoading) {
|
|
212
|
+
$.submitBtn.applyProperties({
|
|
213
|
+
enabled: !isLoading,
|
|
214
|
+
title: isLoading ? L('saving') : L('save')
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Loading State Patterns
|
|
220
|
+
|
|
221
|
+
```xml
|
|
222
|
+
<!-- Skeleton loading pattern -->
|
|
223
|
+
<View id="loadingState" class="wh-screen vertical">
|
|
224
|
+
<View class="mx-4 mt-4 h-20 w-screen animate-pulse rounded-lg bg-gray-200" />
|
|
225
|
+
<View class="mx-4 mt-4 h-4 w-8/12 animate-pulse rounded bg-gray-200" />
|
|
226
|
+
<View class="mx-4 mt-2 h-4 w-6/12 animate-pulse rounded bg-gray-200" />
|
|
227
|
+
</View>
|
|
228
|
+
|
|
229
|
+
<View id="contentState" class="wh-screen hidden">
|
|
230
|
+
<!-- Actual content here -->
|
|
231
|
+
</View>
|
|
232
|
+
|
|
233
|
+
<View id="errorState" class="wh-screen vertical hidden">
|
|
234
|
+
<Label class="fa-solid fa-exclamation-circle mt-20 text-6xl text-gray-400" />
|
|
235
|
+
<Label class="mt-4 text-lg text-gray-500" text="L('error_loading')" />
|
|
236
|
+
<Button class="bg-primary mt-4 h-10 rounded-lg px-6 text-white"
|
|
237
|
+
title="L('retry')"
|
|
238
|
+
onClick="loadData"
|
|
239
|
+
/>
|
|
240
|
+
</View>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
// State management helper
|
|
245
|
+
function setState(state) {
|
|
246
|
+
const states = ['loadingState', 'contentState', 'errorState']
|
|
247
|
+
states.forEach(s => {
|
|
248
|
+
$[s].visible = s === state
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function loadData() {
|
|
253
|
+
setState('loadingState')
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
const data = await api.fetchData()
|
|
257
|
+
renderContent(data)
|
|
258
|
+
setState('contentState')
|
|
259
|
+
} catch (error) {
|
|
260
|
+
setState('errorState')
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Modal and Dialog Patterns
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
// AlertDialog
|
|
269
|
+
function showConfirmDelete(itemName) {
|
|
270
|
+
const dialog = Ti.UI.createAlertDialog({
|
|
271
|
+
title: L('confirm_delete'),
|
|
272
|
+
message: String.format(L('delete_message'), itemName),
|
|
273
|
+
buttonNames: [L('cancel'), L('delete')],
|
|
274
|
+
cancel: 0,
|
|
275
|
+
destructive: 1 // iOS: red button
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
dialog.addEventListener('click', (e) => {
|
|
279
|
+
if (e.index === 1) {
|
|
280
|
+
performDelete()
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
dialog.show()
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// OptionDialog (Action Sheet)
|
|
288
|
+
function showSortOptions() {
|
|
289
|
+
const options = [L('sort_name'), L('sort_date'), L('sort_price')]
|
|
290
|
+
|
|
291
|
+
const dialog = Ti.UI.createOptionDialog({
|
|
292
|
+
title: L('sort_by'),
|
|
293
|
+
options: [...options, L('cancel')],
|
|
294
|
+
cancel: options.length
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
dialog.addEventListener('click', (e) => {
|
|
298
|
+
if (e.index < options.length) {
|
|
299
|
+
applySortOption(e.index)
|
|
300
|
+
}
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
dialog.show()
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Modal Window
|
|
307
|
+
function openModal(route, params) {
|
|
308
|
+
const controller = Alloy.createController(route, params)
|
|
309
|
+
const window = controller.getView()
|
|
310
|
+
|
|
311
|
+
// iOS modal presentation
|
|
312
|
+
if (OS_IOS) {
|
|
313
|
+
window.applyProperties({
|
|
314
|
+
modalStyle: Ti.UI.iOS.MODAL_PRESENTATION_PAGESHEET,
|
|
315
|
+
modalTransitionStyle: Ti.UI.iOS.MODAL_TRANSITION_STYLE_COVER_VERTICAL
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
window.addEventListener('close', () => {
|
|
320
|
+
if (controller.cleanup) controller.cleanup()
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
window.open({ modal: true })
|
|
324
|
+
|
|
325
|
+
return controller
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## TabGroup Pattern
|
|
330
|
+
|
|
331
|
+
```xml
|
|
332
|
+
<!-- views/main.xml -->
|
|
333
|
+
<Alloy>
|
|
334
|
+
<TabGroup id="tabGroup" class="bg-white">
|
|
335
|
+
<Tab title="L('home')" icon="/images/tab_home.png">
|
|
336
|
+
<Require src="home/index" />
|
|
337
|
+
</Tab>
|
|
338
|
+
<Tab title="L('search')" icon="/images/tab_search.png">
|
|
339
|
+
<Require src="search/index" />
|
|
340
|
+
</Tab>
|
|
341
|
+
<Tab title="L('profile')" icon="/images/tab_profile.png">
|
|
342
|
+
<Require src="profile/index" />
|
|
343
|
+
</Tab>
|
|
344
|
+
</TabGroup>
|
|
345
|
+
</Alloy>
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
```javascript
|
|
349
|
+
// controllers/main.js
|
|
350
|
+
// Tab styling is handled in XML with PurgeTSS classes:
|
|
351
|
+
// <TabGroup id="tabGroup" class="tabs-bg-white active-tint-blue-500" />
|
|
352
|
+
|
|
353
|
+
// Handle tab changes
|
|
354
|
+
$.tabGroup.addEventListener('focus', (e) => {
|
|
355
|
+
// Track screen view
|
|
356
|
+
Analytics.trackScreen(e.tab.title)
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
// Programmatic tab switching
|
|
360
|
+
function switchToTab(index) {
|
|
361
|
+
$.tabGroup.activeTab = $.tabGroup.tabs[index]
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Badge on tab (notifications)
|
|
365
|
+
function updateNotificationBadge(count) {
|
|
366
|
+
$.tabGroup.tabs[2].badge = count > 0 ? String(count) : null
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## NavigationWindow Pattern (iOS)
|
|
371
|
+
|
|
372
|
+
```xml
|
|
373
|
+
<!-- views/index.xml -->
|
|
374
|
+
<Alloy>
|
|
375
|
+
<NavigationWindow id="navWin" platform="ios">
|
|
376
|
+
<Require src="home/index" />
|
|
377
|
+
</NavigationWindow>
|
|
378
|
+
|
|
379
|
+
<!-- Android uses Window directly -->
|
|
380
|
+
<Window platform="android">
|
|
381
|
+
<Require src="home/index" />
|
|
382
|
+
</Window>
|
|
383
|
+
</Alloy>
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
```javascript
|
|
387
|
+
// Cross-platform navigation helper
|
|
388
|
+
function openWindow(controller, animated = true) {
|
|
389
|
+
const window = controller.getView()
|
|
390
|
+
|
|
391
|
+
if (OS_IOS) {
|
|
392
|
+
$.navWin.openWindow(window, { animated })
|
|
393
|
+
} else {
|
|
394
|
+
window.open({ animated })
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function closeWindow(window, animated = true) {
|
|
399
|
+
if (OS_IOS) {
|
|
400
|
+
$.navWin.closeWindow(window, { animated })
|
|
401
|
+
} else {
|
|
402
|
+
window.close({ animated })
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## SearchBar with ListView
|
|
408
|
+
|
|
409
|
+
```xml
|
|
410
|
+
<!-- views/contacts/list.xml -->
|
|
411
|
+
<Alloy>
|
|
412
|
+
<Window class="bg-gray-50">
|
|
413
|
+
<SearchBar id="searchBar"
|
|
414
|
+
class="w-screen"
|
|
415
|
+
hintText="L('search_contacts')"
|
|
416
|
+
showCancel="true"
|
|
417
|
+
/>
|
|
418
|
+
|
|
419
|
+
<ListView id="listView" class="wh-screen" top="50">
|
|
420
|
+
<Templates>
|
|
421
|
+
<ItemTemplate name="contact" height="64">
|
|
422
|
+
<View class="horizontal h-16 w-screen bg-white">
|
|
423
|
+
<Label bindId="avatar" class="fa-solid fa-user-circle ml-4 text-3xl text-gray-400" />
|
|
424
|
+
<View class="vertical ml-3">
|
|
425
|
+
<Label bindId="name" class="text-base font-semibold" />
|
|
426
|
+
<Label bindId="phone" class="text-sm text-gray-500" />
|
|
427
|
+
</View>
|
|
428
|
+
</View>
|
|
429
|
+
</ItemTemplate>
|
|
430
|
+
</Templates>
|
|
431
|
+
|
|
432
|
+
<ListSection id="section" />
|
|
433
|
+
</ListView>
|
|
434
|
+
</Window>
|
|
435
|
+
</Alloy>
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
```javascript
|
|
439
|
+
// controllers/contacts/list.js
|
|
440
|
+
let allContacts = []
|
|
441
|
+
let searchTimeout = null
|
|
442
|
+
|
|
443
|
+
function init() {
|
|
444
|
+
loadContacts()
|
|
445
|
+
|
|
446
|
+
$.searchBar.addEventListener('change', onSearchChange)
|
|
447
|
+
$.searchBar.addEventListener('cancel', onSearchCancel)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function onSearchChange(e) {
|
|
451
|
+
// Debounce search
|
|
452
|
+
clearTimeout(searchTimeout)
|
|
453
|
+
searchTimeout = setTimeout(() => {
|
|
454
|
+
filterContacts(e.value)
|
|
455
|
+
}, 300)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function onSearchCancel() {
|
|
459
|
+
filterContacts('')
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function filterContacts(query) {
|
|
463
|
+
const q = query.toLowerCase().trim()
|
|
464
|
+
|
|
465
|
+
const filtered = q
|
|
466
|
+
? allContacts.filter(c =>
|
|
467
|
+
c.name.toLowerCase().includes(q) ||
|
|
468
|
+
c.phone.includes(q)
|
|
469
|
+
)
|
|
470
|
+
: allContacts
|
|
471
|
+
|
|
472
|
+
renderContacts(filtered)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function renderContacts(contacts) {
|
|
476
|
+
$.section.items = contacts.map(c => ({
|
|
477
|
+
template: 'contact',
|
|
478
|
+
properties: { itemId: c.id },
|
|
479
|
+
name: { text: c.name },
|
|
480
|
+
phone: { text: c.phone }
|
|
481
|
+
}))
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function cleanup() {
|
|
485
|
+
clearTimeout(searchTimeout)
|
|
486
|
+
$.searchBar.removeEventListener('change', onSearchChange)
|
|
487
|
+
$.searchBar.removeEventListener('cancel', onSearchCancel)
|
|
488
|
+
$.destroy()
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
$.cleanup = cleanup
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
## Pull-to-Refresh Pattern
|
|
495
|
+
|
|
496
|
+
```xml
|
|
497
|
+
<ListView id="listView" class="wh-screen">
|
|
498
|
+
<RefreshControl id="refreshControl" onRefresh="onRefresh" />
|
|
499
|
+
<ListSection id="section" />
|
|
500
|
+
</ListView>
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
```javascript
|
|
504
|
+
let isRefreshing = false
|
|
505
|
+
|
|
506
|
+
async function onRefresh() {
|
|
507
|
+
if (isRefreshing) return
|
|
508
|
+
isRefreshing = true
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
const data = await api.fetchLatest()
|
|
512
|
+
renderItems(data)
|
|
513
|
+
} catch (error) {
|
|
514
|
+
showError(error.message)
|
|
515
|
+
} finally {
|
|
516
|
+
$.refreshControl.endRefreshing()
|
|
517
|
+
isRefreshing = false
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
```
|